MDL-73602 quiz: Remove all qtype_random references

This removes the use of the "random" qtype for random questions from the
quiz module. Instead, the quiz structure now idenfies slots containing
random questions using a `random` boolean field on the slot.

This includes some amendments to statistics classes in the core_question
namespace, as they ultimately get their question data from
mod/quiz/report/reportlib.php

It also introduces a proper (if limited) way to add random questions in
behat tests.
This commit is contained in:
Mark Johnson
2026-01-02 15:37:53 +00:00
parent 0b5311c2dc
commit fe29d48b87
21 changed files with 80 additions and 88 deletions
@@ -25,6 +25,7 @@
namespace mod_quiz\output;
use core_question\local\bank\question_version_status;
use mod_quiz\question\bank\qbank_helper;
use \mod_quiz\structure;
use \html_writer;
use qbank_previewquestion\question_preview_options;
@@ -802,7 +803,7 @@ class edit_renderer extends \plugin_renderer_base {
];
$data['versionoptions'] = [];
if ($structure->get_slot_by_number($slot)->qtype !== 'random') {
if (!$structure->get_slot_by_number($slot)->random) {
$data['versionselection'] = true;
$data['versionoption'] = $structure->get_version_choices_for_slot($slot);
}
@@ -846,7 +847,7 @@ class edit_renderer extends \plugin_renderer_base {
*/
public function get_question_name_for_slot(structure $structure, int $slot, \moodle_url $pageurl): string {
// Display the link to the question (or do nothing if question has no url).
if ($structure->get_question_type_for_slot($slot) === 'random') {
if ($structure->get_slot_by_number($slot)->random) {
$questionname = $this->random_question($structure, $slot, $pageurl);
} else {
$questionname = $this->question_name($structure, $slot, $pageurl);
@@ -868,7 +869,7 @@ class edit_renderer extends \plugin_renderer_base {
$qtype = $structure->get_question_type_for_slot($slot);
$slotinfo = $structure->get_slot_by_number($slot);
$questionicons = '';
if ($qtype !== 'random' && question_bank::is_qtype_usable($qtype)) {
if (!$slotinfo->random && question_bank::is_qtype_usable($qtype)) {
$questionicons .= $this->question_preview_icon($structure->get_quiz(),
$structure->get_question_in_slot($slot),
null, null, $slotinfo->requestedversion ?: question_preview_options::ALWAYS_LATEST);
@@ -1128,9 +1129,8 @@ class edit_renderer extends \plugin_renderer_base {
}
$configuretitle = get_string('configurerandomquestion', 'quiz');
$qtype = question_bank::get_qtype($question->qtype, false);
$namestr = $qtype->local_name();
$icon = $this->pix_icon('icon', $namestr, $qtype->plugin_name(), ['class' => 'icon activityicon']);
$namestr = get_string('randomquestion', 'quiz');
$icon = $this->pix_icon('random', $namestr, 'mod_quiz', ['class' => 'icon activityicon']);
$editicon = $this->pix_icon('t/edit', $configuretitle, 'moodle', ['title' => '']);
$qbankurlparams = [
@@ -166,6 +166,7 @@ class qbank_helper {
foreach ($slotdata as $slot) {
// Ensure the right id is the id.
$slot->id = $slot->slotid;
$slot->random = false;
if ($slot->filtercondition) {
// Unpack the information about a random question.
@@ -175,7 +176,8 @@ class qbank_helper {
$slot->category = $slot->filtercondition['filter']['category']['values'][0] ?? 0;
$slot->qtype = 'random';
$slot->qtype = null; // Random slots don’t have an associated question.
$slot->random = true;
$slot->name = get_string('random', 'quiz');
$slot->length = 1;
} else if ($slot->qtype === null) {
@@ -254,7 +256,7 @@ class qbank_helper {
$slotdata = reset($slotdata);
// Non-random question.
if ($slotdata->qtype != 'random') {
if (!$slotdata->random) {
return $slotdata->questionid;
}
+1 -1
View File
@@ -594,7 +594,7 @@ class quiz_settings {
// Skip questions where all versions are draft.
continue;
}
if ($questiondata->qtype === 'random' && $includepotential) {
if ($questiondata->random && $includepotential) {
$filtercondition = $questiondata->filtercondition;
if (!empty($filtercondition)) {
$filter = $filtercondition['filter'];
+2 -2
View File
@@ -272,7 +272,7 @@ class structure {
return false;
}
if (in_array($this->get_question_type_for_slot($slotnumber), ['random', 'missingtype'])) {
if ($this->slotsinorder[$slotnumber]->random || $this->get_question_type_for_slot($slotnumber) === 'missingtype') {
return \question_engine::can_questions_finish_during_the_attempt(
$this->quizobj->get_quiz()->preferredbehaviour);
}
@@ -1796,7 +1796,7 @@ class structure {
// Find the random slots.
$allslots = $this->get_slots();
foreach ($allslots as $key => $slot) {
if ($slot->qtype != 'random') {
if (!$slot->random) {
unset($allslots[$key]);
}
}
+3 -11
View File
@@ -182,7 +182,7 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
if ($questiondata->status == \core_question\local\bank\question_version_status::QUESTION_STATUS_DRAFT) {
throw new moodle_exception('questiondraftonly', 'mod_quiz', '', $questiondata->name);
}
if ($questiondata->qtype == 'random') {
if ($questiondata->random) {
$randomfound = true;
continue;
}
@@ -204,7 +204,7 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
foreach ($quizobj->get_questions(null, false) as $questiondata) {
$slot += 1;
if ($questiondata->qtype != 'random') {
if (!$questiondata->random) {
continue;
}
@@ -1039,7 +1039,7 @@ function quiz_attempt_state_name($state) {
*/
function quiz_question_action_icons($quiz, $cmid, $question, $returnurl, $variant = null) {
$html = '';
if ($question->qtype !== 'random') {
if (!$question->random) {
$html = quiz_question_preview_button($quiz, $question, false, $variant);
}
$html .= quiz_question_edit_button($cmid, $question, $returnurl);
@@ -1748,15 +1748,7 @@ function quiz_add_quiz_question($questionid, $quiz, $page = 0, $maxmark = null)
$quiz->cmid = $cm->id;
}
// Make sue the question is not of the "random" type.
$questiontype = $DB->get_field('question', 'qtype', ['id' => $questionid]);
if ($questiontype == 'random') {
throw new coding_exception(
'Adding "random" questions via quiz_add_quiz_question() is deprecated. '.
'Please use mod_quiz\structure::add_random_questions().'
);
}
// If the question type is invalid, we cannot add it to the quiz. It shouldn't be possible to get to this
// point without fiddling with the DOM so we can just throw an exception.
if (!\question_bank::is_qtype_installed($questiontype)) {
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" style="isolation:isolate" width="16" height="16" preserveAspectRatio="xMinYMid meet"><defs><clipPath id="a"><path d="M0 0h16v16H0z"/></clipPath></defs><g clip-path="url(#a)"><path d="M14.7 5.5l-.096 5.97-5.008 3.23L9.5 8.143 14.7 5.5zM7.8 1.1l5.7 2.3-4.9 3-5.8-2.8 5-2.5zm-.1 7l.1 6.7-5.9-3.2-.1-6.7 5.9 3.2z" vector-effect="non-scaling-stroke" stroke="#000" stroke-miterlimit="10"/><path d="M7.9.1s-.1 0-.1.1L1.6 3.6c-.1 0 0 0 0 .1l6.9 3.5c.1.1.2.1.3 0l6.1-3.7c.1-.1.1-.1 0-.2L8.2.3C8.1.1 8 .1 7.9.1zm-.6.8h.4c.6 0 1.1.3 1.1.6 0 .2-.5.5-1.1.5-.6 0-1.1-.3-1.1-.6 0-.2.3-.4.7-.5zM3.8 2.8h.4c.6 0 1.1.3 1.1.6s-.5.6-1.1.6c-.6 0-1.1-.3-1.1-.6s.3-.5.7-.6zm5.7-.9h.4c.6 0 1.1.3 1.1.6s-.5.6-1.1.6c-.6-.1-1-.3-1-.7 0-.2.3-.4.6-.5zM1.2 4c0 .1 0 .1 0 0l.1 7.9c0 .1 0 .1.1.1l6.7 3.7c.2.1.2.1.2-.1v-8c0-.1 0-.2-.1-.2L1.2 4c.1.1 0 0 0 0zM6 3.9h.4c.6 0 1.1.3 1.1.6s-.5.6-1.1.5c-.6 0-1.1-.2-1-.5 0-.3.2-.5.6-.6zm5.8-1h.4c.6 0 1.1.3 1.1.6s-.5.6-1.1.6c-.6 0-1.1-.3-1.1-.6.1-.3.3-.5.7-.6zM1.9 5.4c0-.1 0-.1 0 0 .3-.1.7.1 1 .5.3.5.3 1.1.1 1.4-.3.2-.8.1-1.2-.4-.3-.5-.4-1.2-.1-1.4.1-.1.1-.1.2-.1zM8.3 5h.4c.6 0 1.1.3 1.1.6s-.5.6-1.1.6c-.6 0-1.1-.3-1.1-.6.1-.3.3-.5.7-.6zm7-1.3s0 .1 0 0L9 7.6c-.1.1-.1.1-.1.2v7.9c0 .1 0 .2.1.1l6.2-3.9c.1-.1.1-.1.1-.2v-8c.1 0 .1 0 0 0zm-.8 1.5c.2 0 .4 0 .5.2.2.3.1 1-.3 1.4-.4.4-.9.5-1.1.2-.2-.3-.1-1 .3-1.4.2-.2.4-.4.6-.4zM6.4 7.6c.3 0 .6.2.9.5.3.5.4 1.1.1 1.4-.3.3-.8.1-1.1-.4-.4-.5-.4-1.1-.1-1.4 0 0 .1 0 .2-.1zM4.1 9c.3 0 .7.2.9.5.3.5.4 1.1.1 1.4-.2.3-.7.1-1.1-.4-.3-.5-.4-1.1-.1-1.4.1 0 .1-.1.2-.1zm-2.2 1.4c.1 0 .1 0 0 0 .3-.1.7.1 1 .5.3.5.4 1.1.1 1.4-.3.3-.8.1-1.1-.4-.3-.5-.4-1.1-.1-1.4 0-.1.1-.1.1-.1zm10.7-1.5c.2 0 .4 0 .5.2.2.3.1 1-.3 1.4-.4.4-.9.5-1.1.2-.2-.3-.1-1 .3-1.4.2-.2.4-.3.6-.4zm-6.3 3.9c.1-.1.1-.1 0 0 .3-.1.7.1 1 .5.3.5.4 1.1.1 1.4-.3.3-.8.1-1.1-.4-.3-.5-.4-1.1-.1-1.4 0-.1.1-.1.1-.1zm4.2-.1c.2 0 .4 0 .5.2.2.3.1 1-.3 1.4-.4.4-.9.5-1.1.2-.2-.3-.1-1 .3-1.4.2-.2.4-.4.6-.4z" fill="#C4CCCB"/></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+1
View File
@@ -111,6 +111,7 @@ function quiz_report_get_significant_questions($quiz) {
$slotreport->slot = $slot->slot;
$slotreport->id = $slot->questionid;
$slotreport->qtype = $slot->qtype;
$slotreport->random = $slot->random;
$slotreport->length = $slot->length;
$slotreport->number = $number;
$slotreport->displaynumber = $slot->displaynumber ?? $number;
@@ -54,7 +54,6 @@ final class responses_from_steps_walkthrough_test extends \mod_quiz\tests\attemp
*/
public function test_walkthrough_from_csv($quizsettings, $csvdata): void {
$this->resetAfterTest(true);
question_bank::get_qtype('random')->clear_caches_before_testing();
$this->create_quiz($quizsettings, $csvdata['questions']);
+2 -1
View File
@@ -891,7 +891,7 @@ class quiz_statistics_report extends report_base {
$questiondata = [];
foreach ($questions as $qs => $question) {
$displaynumber = $question->displaynumber;
if ($question->qtype === 'random') {
if ($question->random) {
$question->id = 0;
$question->name = get_string('random', 'quiz');
$question->questiontext = get_string('random', 'quiz');
@@ -909,6 +909,7 @@ class quiz_statistics_report extends report_base {
$q->slot = $question->slot;
$q->number = $displaynumber;
$q->parenttype = null;
$q->random = false;
$questiondata[$question->slot] = $q;
}
}
@@ -172,7 +172,7 @@ class quiz_statistics_table extends flexible_table {
$number = $number . '.'.$questionstat->subqdisplayorder;
}
if ($questionstat->question->qtype != 'random' && !is_null($questionstat->variant)) {
if (!$questionstat->question->random && !is_null($questionstat->variant)) {
$number = $number . '.'.$questionstat->variant;
}
@@ -263,7 +263,7 @@ class quiz_statistics_table extends flexible_table {
// Question in a slot, we are not on a page showing structural analysis of one slot,
// we don't want linking on those pages.
$number = $questionstat->question->number;
$israndomquestion = $questionstat->question->qtype == 'random';
$israndomquestion = $questionstat->question->random;
$url = new moodle_url($baseurl, ['slot' => $questionstat->slot]);
if ($this->is_calculated_question_summary($questionstat)) {
@@ -25,13 +25,12 @@ Feature: Statistics calculations with random questions
| questioncategory | qtype | template | name | questiontext |
| Test questions | essay | plain | Test question 1 | |
| Test questions | essay | plain | Test question 2 | |
| Test questions | random | | Random (Test questions) | 0 |
And the following "activities" exist:
| activity | name | course | idnumber |
| quiz | Quiz 1 | C1 | quiz1 |
And quiz "Quiz 1" contains the following questions:
| question | page |
| Random (Test questions) | 1 |
| question | page | randomcategory |
| Random (Test questions) | 1 | Test questions |
And user "student" has attempted "Quiz 1" with responses:
| slot | response |
| 1 | Here is my wonderful essay |
@@ -43,13 +42,12 @@ Feature: Statistics calculations with random questions
| questioncategory | qtype | template | name | questiontext |
| Test questions | multichoice | one_of_four | Test question 1 | |
| Test questions | multichoice | one_of_four | Test question 2 | |
| Test questions | random | | Random (Test questions) | 0 |
And the following "activities" exist:
| activity | name | course | idnumber |
| quiz | Quiz 1 | C1 | quiz1 |
And quiz "Quiz 1" contains the following questions:
| question | page |
| Random (Test questions) | 1 |
| question | page | randomcategory |
| Random (Test questions) | 1 | Test questions |
And user "student" has attempted "Quiz 1" with responses:
| slot | response |
| 1 | One |
@@ -1,21 +1,21 @@
slot,id,number,category,parent,name,questiontext,questiontextformat,image,generalfeedback,defaultgrade,penalty,qtype,length,stamp,version,hidden,timecreated,timemodified,createdby,modifiedby,maxmark
1,1,1,5,0,questionessay-11,What is the purpose of life?,0,,General feedback,1,0,essay,1,localhost+080922073527+WeUaUK,localhost+080922073527+qxLjv1,0,1222068927,0,2,NULL,1
2,2,2,5,0,questionessay-17,What is the purpose of life?,0,,General feedback,1,0,essay,1,localhost+080922073527+pvCseX,localhost+080922073527+mzy6tY,0,1222068927,0,2,NULL,1
3,3,3,5,0,questionessay-6,What is the purpose of life?,0,,General feedback,1,0,essay,1,localhost+080922073527+Cr3gDO,localhost+080922073527+hnxfTy,0,1222068927,0,2,NULL,1
4,4,4,5,0,questionessay-8,What is the purpose of life?,0,,General feedback,1,0,essay,1,localhost+080922073527+sSq9ln,localhost+080922073527+PGyab3,0,1222068927,0,2,NULL,1
5,5,5,5,0,questionmatch-10,"test question, generated by script",0,,Well done,1,0.1,match,1,localhost+080922073527+oG1i2f,localhost+080922073527+S1UxZy,0,1222068927,0,2,NULL,0
6,6,6,5,0,questionmatch-16,"test question, generated by script",0,,Well done,1,0.1,match,1,localhost+080922073527+vMFHyY,localhost+080922073527+4GZIyQ,0,1222068927,0,2,NULL,0
7,7,7,5,0,questionmatch-18,"test question, generated by script",0,,Well done,1,0.1,match,1,localhost+080922073527+Xkxqn1,localhost+080922073527+xbU6U7,0,1222068927,0,2,NULL,0
8,8,8,5,0,questionmultianswer-12,This question consists of some text with an answer embedded right here {#1} and right after that you will have to deal with this short answer {#2} and finally we have a floating point number {#3}. Note that addresses like www.moodle.org and smileys :-) all work as normal: a) How good is this? {#4} b) What grade would you give it? {#5} Good luck!,0,,General feedback,8,0.1,multianswer,1,localhost+080922073527+0zKgpF,localhost+080922073527+r1gsde,0,1222068927,0,2,NULL,8
9,14,9,5,0,questionmultichoice-13,How old is the sun?,0,,General feedback,1,0.1,multichoice,1,localhost+080922073527+AjIjeV,localhost+080922073527+UhtTLR,0,1222068927,0,2,NULL,1
10,15,10,5,0,questionmultichoice-14,How old is the sun?,0,,General feedback,1,0.1,multichoice,1,localhost+080922073527+IrAqRl,localhost+080922073527+xRfta8,0,1222068927,0,2,NULL,1
11,16,11,5,0,questionmultichoice-3,How old is the sun?,0,,General feedback,1,0.1,multichoice,1,localhost+080922073527+DMGirU,localhost+080922073527+689V8k,0,1222068927,0,2,NULL,1
12,17,12,5,0,questionmultichoice-5,How old is the sun?,0,,General feedback,1,0.1,multichoice,1,localhost+080922073527+wileZw,localhost+080922073527+zGcaDa,0,1222068927,0,2,NULL,1
13,25,13,5,0,Random Short-Answer Matching,"<p>For each of the following questions, select the matching answer from the menu.</p>",1,,,1,0.1,randomsamatch,1,localhost+080922073724+qF803I,localhost+080922075820+zbZtaD,0,1222069044,1222070300,2,2,1
14,22,14,5,0,Is Thai difficult?,Is Thai difficult?,0,,,1,0.1,shortanswer,1,localhost+080922073655+2FLtCU,localhost+080922073655+fgUeOj,0,1222069015,0,2,NULL,1
15,23,15,5,0,Is Thai grammar difficult?,Is Thai grammar difficult?,0,,,1,0.1,shortanswer,1,localhost+080922073655+LYSD32,localhost+080922073655+WgRYk4,0,1222069015,0,2,NULL,1
16,24,16,5,0,Is Thai pronunciation difficult?,Is Thai pronunciation difficult?,0,,,1,0.1,shortanswer,1,localhost+080922073655+5p1w22,localhost+080922073655+g5jrXa,0,1222069015,0,2,NULL,1
17,20,17,5,0,Who's buried in Grant's tomb?,Who's buried in Grant's tomb?,0,,,1,0.1,shortanswer,1,localhost+080922073655+PTDcDZ,localhost+080922073655+aghyfu,0,1222069015,0,2,NULL,1
18,21,18,5,0,Who's buried in Jamie's tomb?,Who's buried in Jamie's tomb?,0,,,1,0.1,shortanswer,1,localhost+080922073655+Xvy1ns,localhost+080922073655+Mx0Izs,0,1222069015,0,2,NULL,1
19,18,19,5,0,questiontruefalse-7,This question is really stupid,0,,Well done,1,1,truefalse,1,localhost+080922073527+9bzTef,localhost+080922073527+hjcQR1,0,1222068927,0,2,NULL,1
20,19,20,5,0,questiontruefalse-9,This question is really stupid,0,,Well done,1,1,truefalse,1,localhost+080922073527+TI0yD4,localhost+080922073527+iXIulQ,0,1222068927,0,2,NULL,1
slot,id,number,category,parent,name,questiontext,questiontextformat,image,generalfeedback,defaultgrade,penalty,qtype,length,stamp,version,hidden,timecreated,timemodified,createdby,modifiedby,maxmark,random
1,1,1,5,0,questionessay-11,What is the purpose of life?,0,,General feedback,1,0,essay,1,localhost+080922073527+WeUaUK,localhost+080922073527+qxLjv1,0,1222068927,0,2,NULL,1,0
2,2,2,5,0,questionessay-17,What is the purpose of life?,0,,General feedback,1,0,essay,1,localhost+080922073527+pvCseX,localhost+080922073527+mzy6tY,0,1222068927,0,2,NULL,1,0
3,3,3,5,0,questionessay-6,What is the purpose of life?,0,,General feedback,1,0,essay,1,localhost+080922073527+Cr3gDO,localhost+080922073527+hnxfTy,0,1222068927,0,2,NULL,1,0
4,4,4,5,0,questionessay-8,What is the purpose of life?,0,,General feedback,1,0,essay,1,localhost+080922073527+sSq9ln,localhost+080922073527+PGyab3,0,1222068927,0,2,NULL,1,0
5,5,5,5,0,questionmatch-10,"test question, generated by script",0,,Well done,1,0.1,match,1,localhost+080922073527+oG1i2f,localhost+080922073527+S1UxZy,0,1222068927,0,2,NULL,0,0
6,6,6,5,0,questionmatch-16,"test question, generated by script",0,,Well done,1,0.1,match,1,localhost+080922073527+vMFHyY,localhost+080922073527+4GZIyQ,0,1222068927,0,2,NULL,0,0
7,7,7,5,0,questionmatch-18,"test question, generated by script",0,,Well done,1,0.1,match,1,localhost+080922073527+Xkxqn1,localhost+080922073527+xbU6U7,0,1222068927,0,2,NULL,0,0
8,8,8,5,0,questionmultianswer-12,This question consists of some text with an answer embedded right here {#1} and right after that you will have to deal with this short answer {#2} and finally we have a floating point number {#3}. Note that addresses like www.moodle.org and smileys :-) all work as normal: a) How good is this? {#4} b) What grade would you give it? {#5} Good luck!,0,,General feedback,8,0.1,multianswer,1,localhost+080922073527+0zKgpF,localhost+080922073527+r1gsde,0,1222068927,0,2,NULL,8,0
9,14,9,5,0,questionmultichoice-13,How old is the sun?,0,,General feedback,1,0.1,multichoice,1,localhost+080922073527+AjIjeV,localhost+080922073527+UhtTLR,0,1222068927,0,2,NULL,1,0
10,15,10,5,0,questionmultichoice-14,How old is the sun?,0,,General feedback,1,0.1,multichoice,1,localhost+080922073527+IrAqRl,localhost+080922073527+xRfta8,0,1222068927,0,2,NULL,1,0
11,16,11,5,0,questionmultichoice-3,How old is the sun?,0,,General feedback,1,0.1,multichoice,1,localhost+080922073527+DMGirU,localhost+080922073527+689V8k,0,1222068927,0,2,NULL,1,0
12,17,12,5,0,questionmultichoice-5,How old is the sun?,0,,General feedback,1,0.1,multichoice,1,localhost+080922073527+wileZw,localhost+080922073527+zGcaDa,0,1222068927,0,2,NULL,1,0
13,25,13,5,0,Random Short-Answer Matching,"<p>For each of the following questions, select the matching answer from the menu.</p>",1,,,1,0.1,randomsamatch,1,localhost+080922073724+qF803I,localhost+080922075820+zbZtaD,0,1222069044,1222070300,2,2,1,0
14,22,14,5,0,Is Thai difficult?,Is Thai difficult?,0,,,1,0.1,shortanswer,1,localhost+080922073655+2FLtCU,localhost+080922073655+fgUeOj,0,1222069015,0,2,NULL,1,0
15,23,15,5,0,Is Thai grammar difficult?,Is Thai grammar difficult?,0,,,1,0.1,shortanswer,1,localhost+080922073655+LYSD32,localhost+080922073655+WgRYk4,0,1222069015,0,2,NULL,1,0
16,24,16,5,0,Is Thai pronunciation difficult?,Is Thai pronunciation difficult?,0,,,1,0.1,shortanswer,1,localhost+080922073655+5p1w22,localhost+080922073655+g5jrXa,0,1222069015,0,2,NULL,1,0
17,20,17,5,0,Who's buried in Grant's tomb?,Who's buried in Grant's tomb?,0,,,1,0.1,shortanswer,1,localhost+080922073655+PTDcDZ,localhost+080922073655+aghyfu,0,1222069015,0,2,NULL,1,0
18,21,18,5,0,Who's buried in Jamie's tomb?,Who's buried in Jamie's tomb?,0,,,1,0.1,shortanswer,1,localhost+080922073655+Xvy1ns,localhost+080922073655+Mx0Izs,0,1222069015,0,2,NULL,1,0
19,18,19,5,0,questiontruefalse-7,This question is really stupid,0,,Well done,1,1,truefalse,1,localhost+080922073527+9bzTef,localhost+080922073527+hjcQR1,0,1222068927,0,2,NULL,1,0
20,19,20,5,0,questiontruefalse-9,This question is really stupid,0,,Well done,1,1,truefalse,1,localhost+080922073527+TI0yD4,localhost+080922073527+iXIulQ,0,1222068927,0,2,NULL,1,0
1 slot id number category parent name questiontext questiontextformat image generalfeedback defaultgrade penalty qtype length stamp version hidden timecreated timemodified createdby modifiedby maxmark random
2 1 1 1 5 0 questionessay-11 What is the purpose of life? 0 General feedback 1 0 essay 1 localhost+080922073527+WeUaUK localhost+080922073527+qxLjv1 0 1222068927 0 2 NULL 1 0
3 2 2 2 5 0 questionessay-17 What is the purpose of life? 0 General feedback 1 0 essay 1 localhost+080922073527+pvCseX localhost+080922073527+mzy6tY 0 1222068927 0 2 NULL 1 0
4 3 3 3 5 0 questionessay-6 What is the purpose of life? 0 General feedback 1 0 essay 1 localhost+080922073527+Cr3gDO localhost+080922073527+hnxfTy 0 1222068927 0 2 NULL 1 0
5 4 4 4 5 0 questionessay-8 What is the purpose of life? 0 General feedback 1 0 essay 1 localhost+080922073527+sSq9ln localhost+080922073527+PGyab3 0 1222068927 0 2 NULL 1 0
6 5 5 5 5 0 questionmatch-10 test question, generated by script 0 Well done 1 0.1 match 1 localhost+080922073527+oG1i2f localhost+080922073527+S1UxZy 0 1222068927 0 2 NULL 0 0
7 6 6 6 5 0 questionmatch-16 test question, generated by script 0 Well done 1 0.1 match 1 localhost+080922073527+vMFHyY localhost+080922073527+4GZIyQ 0 1222068927 0 2 NULL 0 0
8 7 7 7 5 0 questionmatch-18 test question, generated by script 0 Well done 1 0.1 match 1 localhost+080922073527+Xkxqn1 localhost+080922073527+xbU6U7 0 1222068927 0 2 NULL 0 0
9 8 8 8 5 0 questionmultianswer-12 This question consists of some text with an answer embedded right here {#1} and right after that you will have to deal with this short answer {#2} and finally we have a floating point number {#3}. Note that addresses like www.moodle.org and smileys :-) all work as normal: a) How good is this? {#4} b) What grade would you give it? {#5} Good luck! 0 General feedback 8 0.1 multianswer 1 localhost+080922073527+0zKgpF localhost+080922073527+r1gsde 0 1222068927 0 2 NULL 8 0
10 9 14 9 5 0 questionmultichoice-13 How old is the sun? 0 General feedback 1 0.1 multichoice 1 localhost+080922073527+AjIjeV localhost+080922073527+UhtTLR 0 1222068927 0 2 NULL 1 0
11 10 15 10 5 0 questionmultichoice-14 How old is the sun? 0 General feedback 1 0.1 multichoice 1 localhost+080922073527+IrAqRl localhost+080922073527+xRfta8 0 1222068927 0 2 NULL 1 0
12 11 16 11 5 0 questionmultichoice-3 How old is the sun? 0 General feedback 1 0.1 multichoice 1 localhost+080922073527+DMGirU localhost+080922073527+689V8k 0 1222068927 0 2 NULL 1 0
13 12 17 12 5 0 questionmultichoice-5 How old is the sun? 0 General feedback 1 0.1 multichoice 1 localhost+080922073527+wileZw localhost+080922073527+zGcaDa 0 1222068927 0 2 NULL 1 0
14 13 25 13 5 0 Random Short-Answer Matching <p>For each of the following questions, select the matching answer from the menu.</p> 1 1 0.1 randomsamatch 1 localhost+080922073724+qF803I localhost+080922075820+zbZtaD 0 1222069044 1222070300 2 2 1 0
15 14 22 14 5 0 Is Thai difficult? Is Thai difficult? 0 1 0.1 shortanswer 1 localhost+080922073655+2FLtCU localhost+080922073655+fgUeOj 0 1222069015 0 2 NULL 1 0
16 15 23 15 5 0 Is Thai grammar difficult? Is Thai grammar difficult? 0 1 0.1 shortanswer 1 localhost+080922073655+LYSD32 localhost+080922073655+WgRYk4 0 1222069015 0 2 NULL 1 0
17 16 24 16 5 0 Is Thai pronunciation difficult? Is Thai pronunciation difficult? 0 1 0.1 shortanswer 1 localhost+080922073655+5p1w22 localhost+080922073655+g5jrXa 0 1222069015 0 2 NULL 1 0
18 17 20 17 5 0 Who's buried in Grant's tomb? Who's buried in Grant's tomb? 0 1 0.1 shortanswer 1 localhost+080922073655+PTDcDZ localhost+080922073655+aghyfu 0 1222069015 0 2 NULL 1 0
19 18 21 18 5 0 Who's buried in Jamie's tomb? Who's buried in Jamie's tomb? 0 1 0.1 shortanswer 1 localhost+080922073655+Xvy1ns localhost+080922073655+Mx0Izs 0 1222069015 0 2 NULL 1 0
20 19 18 19 5 0 questiontruefalse-7 This question is really stupid 0 Well done 1 1 truefalse 1 localhost+080922073527+9bzTef localhost+080922073527+hjcQR1 0 1222068927 0 2 NULL 1 0
21 20 19 20 5 0 questiontruefalse-9 This question is really stupid 0 Well done 1 1 truefalse 1 localhost+080922073527+TI0yD4 localhost+080922073527+iXIulQ 0 1222068927 0 2 NULL 1 0
@@ -534,7 +534,6 @@ final class attempt_walkthrough_test extends \advanced_testcase {
global $SITE;
$this->resetAfterTest(true);
question_bank::get_qtype('random')->clear_caches_before_testing();
$this->setAdminUser();
@@ -247,9 +247,6 @@ final class repeated_restore_test extends advanced_testcase {
global $CFG;
$generators = [];
foreach (\core\plugin_manager::instance()->get_plugins_of_type('qtype') as $qtype) {
if ($qtype->name == 'random') {
continue;
}
$helperpath = "{$CFG->dirroot}/question/type/{$qtype->name}/tests/helper.php";
if (!file_exists($helperpath)) {
continue;
+14 -5
View File
@@ -191,14 +191,16 @@ class behat_mod_quiz extends behat_question_base {
* Put the specified questions on the specified pages of a given quiz.
*
* The first row should be column names:
* | question | page | maxmark | requireprevious |
* | question | page | maxmark | requireprevious | randomcategory | includesubcategories |
* The first two of those are required. The others are optional.
*
* question needs to uniquely match a question name.
* question needs to uniquely match a question name, otherwise you must include randomcategory.
* page is a page number. Must start at 1, and on each following
* row should be the same as the previous, or one more.
* maxmark What the question is marked out of. Defaults to question.defaultmark.
* requireprevious The question can only be attempted after the previous one was completed.
* randomcategory To add a random question, pass a non-existant name for "question", then a valid category name here.
* includesubcategories Option for the random question category filter, if randomcategory is specified.
*
* Then there should be a number of rows of data, one for each question you want to add.
*
@@ -251,7 +253,7 @@ class behat_mod_quiz extends behat_question_base {
JOIN {question_versions} qv ON qv.questionid = q.id
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
WHERE q.name = :name';
$question = $DB->get_record_sql($sql, ['name' => $questiondata['question']], MUST_EXIST);
$question = $DB->get_record_sql($sql, ['name' => $questiondata['question']]);
// Page number.
$page = clean_param($questiondata['page'], PARAM_INT);
@@ -280,7 +282,14 @@ class behat_mod_quiz extends behat_question_base {
}
}
if ($question->qtype == 'random') {
if (!$question) {
if (!array_key_exists('randomcategory', $questiondata) && $questiondata['randomcategory'] !== '') {
throw new ExpectationException(
'To add a random question, you must include a category name in the randomcategory column.',
$this->getSession()->getDriver(),
);
}
$categoryid = $DB->get_field('question_categories', 'id', ['name' => $questiondata['randomcategory']], MUST_EXIST);
if (!array_key_exists('includingsubcategories', $questiondata) || $questiondata['includingsubcategories'] === '') {
$includingsubcategories = false;
} else {
@@ -290,7 +299,7 @@ class behat_mod_quiz extends behat_question_base {
$filter = [
'category' => [
'jointype' => \qbank_managecategories\category_condition::JOINTYPE_DEFAULT,
'values' => [$question->category],
'values' => [$categoryid],
'filteroptions' => ['includesubcategories' => $includingsubcategories],
],
];
@@ -61,11 +61,10 @@ Feature: Edit quizzes where some questions require the previous one to have been
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | random | Random (Test questions) | 0 |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| Random (Test questions) | 1 | 0 |
| TF1 | 1 | 1 |
| question | page | requireprevious | randomcategory |
| Random (Test questions) | 1 | 0 | Test questions |
| TF1 | 1 | 1 | |
And I am on the "Quiz 1" "mod_quiz > Edit" page
Then "This question cannot be attempted until the previous question has been completed." "link" should be visible
@@ -118,12 +117,11 @@ Feature: Edit quizzes where some questions require the previous one to have been
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
| Test questions | random | Random (Test questions) | 0 |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| Random (Test questions) | 1 | 0 |
| TF1 | 1 | 1 |
| TF2 | 1 | 1 |
| question | page | requireprevious | randomcategory |
| Random (Test questions) | 1 | 0 | Test questions |
| TF1 | 1 | 1 | |
| TF2 | 1 | 1 | |
And I am on the "Quiz 1" "mod_quiz > Edit" page
Then "be attempted" "link" in the "TF1" "list_item" should not be visible
Then "be attempted" "link" in the "TF2" "list_item" should not be visible
@@ -31,15 +31,14 @@ Feature: Teacher can build quiz in a single activity format course
| Test questions | multichoice | one_of_four | MCQ2 |
| Qbank questions 1 | truefalse | | TFQ1 |
| Qbank questions 2 | truefalse | | TFQ2 |
| Test questions | random | | Random (Test questions) |
# Add questions from different question categories and question banks to the quiz.
# Add at least 1 random question.
And quiz "Quiz 1" contains the following questions:
| question | page |
| MCQ1 | 1 |
| TFQ1 | 1 |
| TFQ2 | 2 |
| Random (Test questions) | 3 |
| question | page | randomcategory |
| MCQ1 | 1 | |
| TFQ1 | 1 | |
| TFQ2 | 2 | |
| Random (Test questions) | 3 | Test questions |
@javascript
Scenario: Student can preview and answer quiz in a single activity format course
+2 -6
View File
@@ -116,15 +116,11 @@ final class lib_test extends \advanced_testcase {
quiz_add_quiz_question($standardq->id, $quiz);
$this->add_random_questions($quiz->id, 0, $cat->id, 1);
// Get the random question.
$randomq = $DB->get_record('question', ['qtype' => 'random']);
// Confirm we have a question set reference for the random question.
$this->assertEquals(1, $DB->count_records('question_set_references', ['usingcontextid' => $context->id]));
quiz_delete_instance($quiz->id);
// Check that the random question was deleted.
if ($randomq) {
$this->assertEquals(0, $DB->count_records('question', ['id' => $randomq->id]));
}
// Check that the standard question was not deleted.
$this->assertEquals(1, $DB->count_records('question', ['id' => $standardq->id]));
@@ -467,6 +467,7 @@ class all_calculated_for_qubaid_condition {
protected function make_new_subq_stat_for($displaynumber, $slot, $subqid, $variant = null) {
$slotstat = fullclone($this->for_subq($subqid, $variant));
$slotstat->question->number = $this->for_slot($slot)->question->number;
$slotstat->question->random = false;
$slotstat->subqdisplayorder = $displaynumber;
return $slotstat;
}
@@ -208,7 +208,7 @@ class calculated {
* @return null|string represents the pool of questions from which this question draws if it is random, or null if not.
*/
public function random_selector_string() {
if ($this->question->qtype == 'random') {
if ($this->question->random) {
return $this->question->category .'/'. $this->question->questiontext;
} else {
return null;
@@ -67,7 +67,7 @@ class calculator {
* Constructor.
*
* @param object[] questions to analyze, keyed by slot, also analyses sub questions for random questions.
* we expect some extra fields - slot, maxmark and number on the full question data objects.
* We expect some extra fields - slot, random, maxmark and number on the full question data objects.
* @param \core\progress\base|null $progress the element to send progress messages to, default is {@link \core\progress\none}.
*/
public function __construct($questions, $progress = null) {
@@ -194,7 +194,7 @@ class calculator {
$this->initial_question_walker($this->stats->for_slot($slot));
// The rest of this loop is to finish working out where randomly selected question stats should be displayed.
if ($this->stats->for_slot($slot)->question->qtype == 'random') {
if ($this->stats->for_slot($slot)->question->random) {
$randomselectorstring = $this->stats->for_slot($slot)->random_selector_string();
if ($nextslot && ($randomselectorstring == $this->stats->for_slot($nextslot)->random_selector_string())) {
continue; // Next loop iteration.
@@ -210,10 +210,9 @@ class calculator {
$this->progress->start_progress('', count($lateststeps), 1);
foreach ($lateststeps as $step) {
$this->progress->increment_progress();
$israndomquestion = ($this->stats->for_slot($step->slot)->question->qtype == 'random');
$this->secondary_steps_walker($step, $this->stats->for_slot($step->slot), $summarks);
if ($israndomquestion) {
if ($this->stats->for_slot($step->slot)->question->random) {
$this->secondary_steps_walker($step, $this->stats->for_subq($step->questionid), $summarks);
}
}