Skip to content

Commit a0915ca

Browse files
committed
Merge branch 'dev' of https://github.com/maths/moodle-qtype_stack into iss1678
2 parents 441f20e + 499d7c8 commit a0915ca

18 files changed

Lines changed: 1208 additions & 262 deletions

File tree

adminui/bulktestquiz.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
// This file is part of Stack - http://stack.maths.ed.ac.uk/
3+
//
4+
// Stack is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Stack is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Stack. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/**
18+
* This script runs all the quesion tests for all deployed variants of all
19+
* questions in a given context.
20+
*
21+
* @package qtype_stack
22+
* @copyright 2013 The Open University
23+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24+
*/
25+
26+
define('NO_OUTPUT_BUFFERING', true);
27+
28+
require_once(__DIR__ . '/../../../../config.php');
29+
30+
require_once($CFG->libdir . '/questionlib.php');
31+
require_once(__DIR__ . '/../locallib.php');
32+
require_once(__DIR__ . '/../stack/utils.class.php');
33+
require_once(__DIR__ . '/../stack/bulktester.class.php');
34+
35+
// Increase memory limit: some users with very large numbers of questions/variants have needed this.
36+
raise_memory_limit(MEMORY_HUGE);
37+
38+
// Get the parameters from the URL.
39+
$contextid = required_param('contextid', PARAM_INT);
40+
$context = context::instance_by_id($contextid);
41+
$categoryid = optional_param('categoryid', null, PARAM_INT);
42+
43+
$skippreviouspasses = optional_param('skippreviouspasses', false, PARAM_BOOL);
44+
$addtags = optional_param('addtags', false, PARAM_BOOL);
45+
$urlparams = ['contextid' => $context->id, 'categoryid' => $categoryid];
46+
if ($skippreviouspasses) {
47+
$urlparams['skippreviouspasses'] = 1;
48+
}
49+
50+
// Login and check permissions.
51+
require_login();
52+
require_capability('moodle/question:editall', $context);
53+
$PAGE->set_url('/question/type/stack/bulktest.php', $urlparams);
54+
$PAGE->set_context($context);
55+
$title = stack_string('bulktesttitle', $context->get_context_name());
56+
$PAGE->set_title($title);
57+
58+
if ($context->contextlevel == CONTEXT_MODULE) {
59+
// Calling $PAGE->set_context should be enough, but it seems that it is not.
60+
// Therefore, we get the right $cm and $course, and set things up ourselves.
61+
$cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST);
62+
$PAGE->set_cm($cm, $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST));
63+
}
64+
65+
// Create the helper class.
66+
$bulktester = new stack_bulk_tester();
67+
68+
// Release the session, so the user can do other things while this runs.
69+
\core\session\manager::write_close();
70+
71+
// Display.
72+
echo $OUTPUT->header();
73+
echo $OUTPUT->heading($title);
74+
75+
// Run the tests.
76+
[$allpassed, $failing] = $bulktester->run_all_tests_for_context(
77+
$context,
78+
$categoryid,
79+
'web',
80+
false,
81+
$skippreviouspasses,
82+
$addtags,
83+
true
84+
);
85+
86+
// Display the final summary.
87+
$bulktester->print_overall_result($allpassed, $failing);
88+
89+
// If we used the cache, report state.
90+
if (class_exists('stack_cas_connection_db_cache')) {
91+
echo html_writer::tag('p', stack_string(
92+
'healthcheckcachestatus',
93+
stack_cas_connection_db_cache::entries_count($DB)
94+
));
95+
}
96+
97+
echo $OUTPUT->footer();

api/controller/GradingController.php

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -79,50 +79,51 @@ public function __invoke(Request $request, Response $response, array $args): Res
7979
$storeprefix = uniqid();
8080
$gradingresponse = new StackGradingResponse();
8181
$gradingresponse->isgradable = true;
82-
82+
83+
if (!$question->is_gradable_response($data['answers'])) {
84+
$gradingresponse->isgradable = false;
85+
$response->getBody()->write(json_encode($gradingresponse));
86+
return $response->withHeader('Content-Type', 'application/json');
87+
}
88+
8389
$scores = [];
8490
foreach ($question->prts as $index => $prt) {
8591
$result = $question->get_prt_result($index, $data['answers'], true);
92+
$scores[$index] = $result->get_score();
8693

87-
// If not all inputs required for the prt have been filled out,
88-
// or the prt evaluation caused an error, we abort the grading,
89-
// and indicate that this input state is not gradable.
90-
if ($result->get_errors() || !$question->has_necessary_prt_inputs($prt, $data['answers'], true)) {
91-
$gradingresponse = new StackGradingResponse();
92-
$gradingresponse->isgradable = false;
93-
94-
$response->getBody()->write(json_encode($gradingresponse));
95-
return $response->withHeader('Content-Type', 'application/json');
96-
}
97-
98-
$feedbackstyle = $prt->get_feedbackstyle();
99-
100-
$feedback = $result->apply_placeholder_holder($result->get_feedback());
101-
$standardfeedback = $this->standard_prt_feedback($question, $result, $feedbackstyle);
102-
103-
switch ($feedbackstyle) {
104-
// Formative.
105-
case 0:
106-
$overallfeedback = $feedback;
107-
break;
108-
// Standard.
109-
case 1:
110-
case 2:
111-
$overallfeedback = $standardfeedback . $feedback;
112-
break;
113-
// Compact.
114-
// Symbolic.
115-
case 3:
116-
$overallfeedback = $standardfeedback;
117-
break;
118-
// Invalid.
119-
default:
120-
$overallfeedback = "Invalid Feedback style";
121-
break;
94+
$errors = $result->get_errors();
95+
if ($errors) {
96+
$overallfeedback = $errors;
97+
} else if (!$question->has_necessary_prt_inputs($prt, $data['answers'], true)) {
98+
continue;
99+
} else {
100+
$feedbackstyle = $prt->get_feedbackstyle();
101+
102+
$feedback = $result->apply_placeholder_holder($result->get_feedback());
103+
$standardfeedback = $this->standard_prt_feedback($question, $result, $feedbackstyle);
104+
105+
switch ($feedbackstyle) {
106+
// Formative.
107+
case 0:
108+
$overallfeedback = $feedback;
109+
break;
110+
// Standard.
111+
case 1:
112+
case 2:
113+
$overallfeedback = $standardfeedback . $feedback;
114+
break;
115+
// Compact.
116+
// Symbolic.
117+
case 3:
118+
$overallfeedback = $standardfeedback;
119+
break;
120+
// Invalid.
121+
default:
122+
$overallfeedback = "Invalid Feedback style";
123+
break;
124+
}
122125
}
123126

124-
$scores[$index] = $result->get_score();
125-
126127
$gradingresponse->prts[$index] = $translate->filter(
127128
\stack_maths::process_display_castext($overallfeedback),
128129
$language

api/public/stackshared.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,9 @@ function answer() {
269269
document.getElementById('errors').innerText = '';
270270
}
271271
if (!json.isgradable) {
272+
// Should we display this or nothing (like Moodle)?
272273
document.getElementById('stackapi_validity').innerText
273-
= ' Please enter valid answers for all parts of the question.';
274+
= ' Please supply additional valid answers.';
274275
return;
275276
}
276277
renameIframeHolders();
@@ -307,7 +308,7 @@ function answer() {
307308
const elements = document.getElementsByName(`${feedbackPrefix + name}`);
308309
if (elements.length > 0) {
309310
const element = elements[0];
310-
if (json.scores[name] !== undefined) {
311+
if (json.scores[name] !== undefined && json.scoreweights[name]) {
311312
fb = fb + `<div>Marks for this submission:
312313
${(json.scores[name] * json.scoreweights[name] * json.scoreweights.total).toFixed(2)}
313314
/ ${(json.scoreweights[name] * json.scoreweights.total).toFixed(2)}.</div>`;

api/questiondefaults.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ qtest:
8787
description:
8888
testinput:
8989
name: 'ans1'
90-
value: 'ta1'
90+
value: ''
9191
expected:
9292
name: 'prt1'
9393
expectedscore:

api/util/StackQuestionLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ public static function loadxml($xml, $includetests = false) {
509509
$testiname = isset($testinput->name) ? (string) $testinput->name :
510510
self::get_default('testinput', 'name', 'ans1');
511511
$testivalue = (array) $testinput->value ? (string) $testinput->value :
512-
self::get_default('testinput', 'value', 'ta1');
512+
self::get_default('testinput', 'value', '');
513513
$testinputs[$testiname] = $testivalue;
514514
}
515515
$testdescription = isset($test->description) ? (string) $test->description :

cli/bulktestall.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@
111111
'cli',
112112
(int) $options['id'],
113113
false,
114-
(bool) $options['addtags']
114+
(bool) $options['addtags'],
115+
false
115116
);
116117
} else {
117118
[$passed, $failing] = $bulktester->run_all_tests_for_context(
@@ -120,7 +121,8 @@
120121
'cli',
121122
false,
122123
false,
123-
(bool) $options['addtags']
124+
(bool) $options['addtags'],
125+
false
124126
);
125127
}
126128

doc/en/Developer/Development_track.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Changes and new features.
2121
4. Editing of question XML within STACK from link in STACK dashboard. STACK now requires the importasversion plugin to make this possible.
2222
5. Much code tidying to comply with updated to Moodle Code Checker.
2323
6. Add in suppport for `style` in Parsons blocks. E.g. you can now use `style="compact"` to get smaller, tighter items.
24+
7. Facilitate bulk test for questions in a particular quiz. (Issue #1521) Follow the link from the question dashboard to see quizzes in which that question is used.
2425

2526
Issues with [github milestone 4.12.0](https://github.com/maths/moodle-qtype_stack/issues?q=is%3Aissue+milestone%3A4.12.0) include
2627

doc/en/STACK_question_admin/Bulk_testing.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
# Bulk testing STACK questions on your site
22

3-
You can bulk test all question tests on all variants of all STACK questions by using the bulk-test script. Access this functionality from STACK's "adminui" page (or the plugin setting page)
3+
You can bulk test all question tests on all variants of all STACK questions by using the bulk-test script. There are two ways to bulk test.
44

5-
[...]/question/type/stack/adminui/index.php
5+
1. Bulk test all questions in a course or category. To do this, access STACK's "adminui" page (or the plugin setting page) `[...]/question/type/stack/adminui/index.php`
6+
2. Bulk test all questions in a particular quiz. Follow the link from the question dashboard to see quizzes in which that question is used.
67

78
To make use of the bulk test users require the capability `qtype/stack:usediagnostictools` via Moodle's capability system.
89

9-
The bulk-test index page lists all Moodle contexts which contain STACK questions. You can bulk-test by context.
10-
1110
Bulk testing does the following.
1211

1312
1. Validate the STACK question against it's `stackversion` number. STACK questions store the version of the STACK plug-in _last used_ to edit the question. The bulk tester checks for changes with the current STACK plug-in version.

lang/en/qtype_stack.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,9 @@
627627
$string['seetodolist'] = '<i class="fa fa-exclamation-triangle"></i> Find <tt>[[todo]]</tt> blocks';
628628
$string['seetodolist_desc'] = 'The purpose of this page is to find all questions containing <tt>[[todo]]</tt> blocks and to group them by any tags. Questions that have been marked as broken will also be found and displayed.';
629629
$string['seetodolist_help'] = 'Clicking on the question name takes you to the dashboard. You can also preview the question.';
630+
$string['bulktestquiz'] = '<i class="fa fa-certificate"></i> Bulk test quiz';
631+
$string['bulktestquiznotes'] = 'Bulk test the latest version of all the questions in a quiz containing this question.';
632+
$string['bulktestquizselect'] = 'Select a quiz';
630633

631634
$string['basicquestionreport'] = '<i class="fa fa-bar-chart"></i> Analyze responses';
632635
$string['basicquestionreport_help'] = 'Generates a very basic report on attempts at this question on the server. Useful for deciding which PRT test can be added to improve feedback in the light of what the student actually does. (Most questions are only used in one place)';

questionbulktest.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
// This file is part of Stack - http://stack.maths.ed.ac.uk/
3+
//
4+
// Stack is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Stack is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with STACK. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/**
18+
* This script lets the user bulk test quizzes for a particular question.
19+
*
20+
* @package qtype_stack
21+
* @copyright 2020 the University of Edinburgh
22+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23+
*/
24+
25+
require_once(__DIR__ . '/../../../config.php');
26+
require_once($CFG->libdir . '/questionlib.php');
27+
require_once(__DIR__ . '/locallib.php');
28+
require_once(__DIR__ . '/stack/utils.class.php');
29+
require_once(__DIR__ . '/stack/bulktester.class.php');
30+
require_once(__DIR__ . '/stack/questionreport.class.php');
31+
require_login();
32+
33+
// Get the parameters from the URL.
34+
$questionid = required_param('questionid', PARAM_INT);
35+
[$qversion, $questionid] = get_latest_question_version($questionid);
36+
$quizcontext = optional_param('context', null, PARAM_INT);
37+
// Load the necessary data.
38+
$questiondata = question_bank::load_question_data($questionid);
39+
if (!$questiondata) {
40+
throw new stack_exception('questiondoesnotexist');
41+
}
42+
$question = question_bank::load_question($questionid);
43+
44+
// Process any other URL parameters, and do require_login.
45+
[$context, $seed, $urlparams] = qtype_stack_setup_question_test_page($question);
46+
47+
// Check permissions.
48+
question_require_capability_on($questiondata, 'view');
49+
$canedit = question_has_capability_on($questiondata, 'edit');
50+
51+
// Initialise $PAGE.
52+
$PAGE->set_context($context);
53+
$PAGE->set_url('/question/type/stack/questionbulktest.php', $urlparams);
54+
$title = stack_string('bulktestquiz');
55+
$PAGE->set_title($title);
56+
$PAGE->set_heading($title);
57+
58+
// This layout has minimal header/footer.
59+
$PAGE->set_pagelayout('popup');
60+
61+
$testquestionlink = new moodle_url('/question/type/stack/questiontestrun.php', $urlparams);
62+
$qurl = qbank_previewquestion\helper::question_preview_url($questionid, null, null, null, null, $context);
63+
$editparams = $urlparams;
64+
unset($editparams['questionid']);
65+
unset($editparams['seed']);
66+
$editparams['id'] = $question->id;
67+
$questioneditlatesturl = new moodle_url('/question/type/stack/questioneditlatest.php', $editparams);
68+
69+
// Start output.
70+
echo $OUTPUT->header();
71+
72+
// Get quizzes in which the question is used.
73+
// Add data for creating quiz selection dropdown.
74+
$quizzes = stack_question_report::get_relevant_quizzes($questionid, (int) $question->contextid);
75+
$quizoutput = [];
76+
foreach ($quizzes as $contextid => $quiz) {
77+
$quiz->url = new moodle_url('/question/type/stack/adminui/bulktestquiz.php', ['contextid' => $contextid]);
78+
$quiz->url = $quiz->url->out();
79+
$quiz->active = ($contextid === $quizcontext) ? true : false;
80+
$quizoutput[] = $quiz;
81+
}
82+
83+
$outputdata = new StdClass();
84+
$outputdata->question = new StdClass();
85+
$outputdata->question->version = $qversion;
86+
$outputdata->question->name = $question->name;
87+
88+
// Add additional page creation data.
89+
$outputdata->quizzes = $quizoutput;
90+
$outputdata->general = new Stdclass();
91+
$outputdata->general->testquestionlink = $testquestionlink->out();
92+
$outputdata->general->previewquestionlink = $qurl->out();
93+
$outputdata->general->editquestionlink = $questioneditlatesturl->out();
94+
95+
// Render report.
96+
echo $OUTPUT->render_from_template('qtype_stack/questionbulktest', $outputdata);
97+
echo $OUTPUT->footer();

0 commit comments

Comments
 (0)