MDL-74255 quiz: fix redo and regrade question version handling

This commit is contained in:
Tim Hunt 2022-04-06 22:23:46 +01:00
parent 839cccead4
commit a43f56836a
11 changed files with 197 additions and 202 deletions

View File

@ -2111,8 +2111,9 @@ class quiz_attempt {
$transaction = $DB->start_delegated_transaction();
// Add the question to the usage. It is important we do this before we choose a variant.
$newquestion = question_bank::load_question(
\mod_quiz\question\bank\qbank_helper::choose_question_for_redo($this->slots[$slot]->id, $qubaids));
$newquestionid = qbank_helper::choose_question_for_redo($this->get_quizid(),
$this->get_quizobj()->get_context(), $this->slots[$slot]->id, $qubaids);
$newquestion = question_bank::load_question($newquestionid, $this->get_quiz()->shuffleanswers);
$newslot = $this->quba->add_question_in_place_of_other($slot, $newquestion);
// Choose the variant.

View File

@ -58,7 +58,7 @@ class submit_question_version extends external_api {
* Set the questions slot parameters to display the question template.
*
* @param int $slotid Slot id to display.
* @param int $newversion
* @param int $newversion the version to set. 0 means 'always latest'.
* @return array
*/
public static function execute(int $slotid, int $newversion): array {

View File

@ -17,6 +17,9 @@
namespace mod_quiz\question\bank;
use core_question\local\bank\question_version_status;
use core_question\local\bank\random_question_loader;
use qubaid_condition;
use quiz_attempt;
defined('MOODLE_INTERNAL') || die();
@ -34,22 +37,6 @@ require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
*/
class qbank_helper {
/**
* Check if the slot is a random question or not.
*
* @param int $slotid
* @return bool
*/
public static function is_random(int $slotid): bool {
global $DB;
$params = [
'itemid' => $slotid,
'component' => 'mod_quiz',
'questionarea' => 'slot'
];
return $DB->record_exists('question_set_references', $params);
}
/**
* Get the available versions of a question where one of the version has the given question id.
*
@ -78,39 +65,6 @@ class qbank_helper {
", [$questionid, question_version_status::QUESTION_STATUS_DRAFT]);
}
/**
* Get the question id from slot id.
*
* @param int $slotid
* @return mixed
*/
public static function get_question_for_redo(int $slotid) {
global $DB;
$params = [
'itemid' => $slotid,
'component' => 'mod_quiz',
'questionarea' => 'slot'
];
$referencerecord = $DB->get_record('question_references', $params);
if ($referencerecord->version === null) {
$questionsql = 'SELECT q.id
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
WHERE qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be
ON be.id = v.questionbankentryid
WHERE be.id = qv.questionbankentryid)
AND qv.questionbankentryid = ?';
$questionid = $DB->get_record_sql($questionsql, [$referencerecord->questionbankentryid])->id;
} else {
$questionid = $DB->get_field('question_versions', 'questionid',
['questionbankentryid' => $referencerecord->questionbankentryid,
'version' => $referencerecord->version]);
}
return $questionid;
}
/**
* Get the information about which questions should be used to create a quiz attempt.
*
@ -124,12 +78,25 @@ class qbank_helper {
* randomtags, and note that these also have a ->name set and ->qtype set to 'random'.
*
* @param int $quizid the id of the quiz to load the data for.
* @param \context $quizcontext
* @param \context_module $quizcontext the context of this quiz.
* @param int|null $slotid optional, if passed only load the data for this one slot (if it is in this quiz).
* @return array indexed by slot, with information about the content of each slot.
*/
public static function get_question_structure(int $quizid, \context $quizcontext) {
public static function get_question_structure(int $quizid, \context_module $quizcontext,
int $slotid = null): array {
global $DB;
$params = [
'draft' => question_version_status::QUESTION_STATUS_DRAFT,
'quizcontextid' => $quizcontext->id, 'quizcontextid2' => $quizcontext->id,
'quizid' => $quizid
];
$slotidtest = '';
if ($slotid !== null) {
$params['slotid'] = $slotid;
$slotidtest = ' AND slot.id = :slotid';
}
// Load all the data about each slot.
$slotdata = $DB->get_records_sql("
SELECT slot.slot,
@ -169,12 +136,12 @@ class qbank_helper {
AND qsr.questionarea = 'slot' AND qsr.itemid = slot.id
WHERE slot.quizid = :quizid
ORDER BY slot.slot
", ['draft' => question_version_status::QUESTION_STATUS_DRAFT,
'quizcontextid' => $quizcontext->id, 'quizcontextid2' => $quizcontext->id,
'quizid' => $quizid]);
$slotidtest
// Uppack the random info from question_set_reference.
ORDER BY slot.slot
", $params);
// Unpack the random info from question_set_reference.
foreach ($slotdata as $slot) {
// Ensure the right id is the id.
$slot->id = $slot->slotid;
@ -184,7 +151,7 @@ class qbank_helper {
$filtercondition = json_decode($slot->filtercondition);
$slot->questionid = 's' . $slot->id; // Sometimes this is used as an array key, so needs to be unique.
$slot->category = $filtercondition->questioncategoryid;
$slot->randomrecurse = $filtercondition->includingsubcategories;
$slot->randomrecurse = (bool) $filtercondition->includingsubcategories;
$slot->randomtags = isset($filtercondition->tags) ? (array) $filtercondition->tags : [];
$slot->qtype = 'random';
$slot->name = get_string('random', 'quiz');
@ -229,34 +196,31 @@ class qbank_helper {
}
/**
* Choose question for redo.
* Choose question for redo in a particular slot.
*
* @param int $slotid
* @param \qubaid_condition $qubaids
* @return int
* @param int $quizid the id of the quiz to load the data for.
* @param \context_module $quizcontext the context of this quiz.
* @param int $slotid optional, if passed only load the data for this one slot (if it is in this quiz).
* @param qubaid_condition $qubaids attempts to consider when avoiding picking repeats of random questions.
* @return int the id of the question to use.
*/
public static function choose_question_for_redo($slotid, $qubaids): int {
// Choose the replacement question.
if (!self::is_random($slotid)) {
$newqusetionid = self::get_question_for_redo($slotid);
} else {
$tagids = [];
$randomquestiondata = self::get_random_question_data_from_slot($slotid);
$filtercondition = json_decode($randomquestiondata->filtercondition);
if (isset($filtercondition->tags)) {
foreach ($filtercondition->tags as $tag) {
$tagstring = explode(',', $tag);
$tagids [] = $tagstring[0];
}
}
public static function choose_question_for_redo(int $quizid, \context_module $quizcontext,
int $slotid, qubaid_condition $qubaids): int {
$slotdata = self::get_question_structure($quizid, $quizcontext, $slotid);
$slotdata = reset($slotdata);
$randomloader = new \core_question\local\bank\random_question_loader($qubaids, []);
$newqusetionid = $randomloader->get_next_question_id($filtercondition->questioncategoryid,
(bool) $filtercondition->includingsubcategories, $tagids);
if ($newqusetionid === null) {
throw new \moodle_exception('notenoughrandomquestions', 'quiz');
}
// Non-random question.
if ($slotdata->qtype != 'random') {
return $slotdata->questionid;
}
// Random question.
$randomloader = new random_question_loader($qubaids, []);
$newqusetionid = $randomloader->get_next_question_id($slotdata->category,
$slotdata->randomrecurse, self::get_tag_ids_for_slot($slotdata));
if ($newqusetionid === null) {
throw new \moodle_exception('notenoughrandomquestions', 'quiz');
}
return $newqusetionid;
}

View File

@ -79,7 +79,7 @@ abstract class quiz_attempts_report extends quiz_default_report {
* 3 => \core\dml\sql_join Contains joins, wheres, params for all the students to show in the report.
* Will be the same as either element 1 or 2.
*/
protected function init($mode, $formclass, $quiz, $cm, $course) {
public function init($mode, $formclass, $quiz, $cm, $course) {
$this->mode = $mode;
$this->context = context_module::instance($cm->id);

View File

@ -22,6 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\question\bank\qbank_helper;
defined('MOODLE_INTERNAL') || die();
@ -45,11 +46,9 @@ class quiz_overview_report extends quiz_attempts_report {
protected $hasgroupstudents;
/**
* @var array|null used during regrades, to cache which new questionid to use for each old on.
* for slots which are not random, stores slot => questionid for the question to use.
* See get_new_question_for_regrade.
* @var array|null cached copy of qbank_helper::get_question_structure for use during regrades.
*/
protected $slotquestionids = null;
protected $structureforregrade = null;
/**
* @var array|null used during regrades, to cache which new questionid to use for each old on.
@ -339,7 +338,7 @@ class quiz_overview_report extends quiz_attempts_report {
* @param array $slots if null, regrade all questions, otherwise, just regrade
* the questions with those slots.
*/
protected function regrade_attempt($attempt, $dryrun = false, $slots = null) {
public function regrade_attempt($attempt, $dryrun = false, $slots = null) {
global $DB;
// Need more time for a quiz with many questions.
core_php_time_limit::raise(300);
@ -394,6 +393,14 @@ class quiz_overview_report extends quiz_attempts_report {
gc_collect_cycles();
}
/**
* For use in tests only. Clear the cached regrade data.
*/
public function clear_regrade_date_cache(): void {
$this->structureforregrade = null;
$this->newquestionidsforold = null;
}
/**
* Work out of we should be using a new question version for a particular slot in a regrade.
*
@ -407,40 +414,27 @@ class quiz_overview_report extends quiz_attempts_report {
global $DB;
// If the cache is empty, get information about all the slots.
if ($this->slotquestionids === null) {
if ($this->structureforregrade === null) {
$this->newquestionidsforold = [];
// Load the data about all the non-random slots now.
$this->slotquestionids = $DB->get_records_sql_menu("
SELECT slot.slot, qve.questionid
FROM {quiz_slots} slot
JOIN {question_references} qref ON qref.itemid = slot.id
AND qref.component = 'mod_quiz' AND qref.questionarea = 'slot' AND qref.usingcontextid = ?
JOIN {question_versions} qve ON qve.questionbankentryid = qref.questionbankentryid
WHERE slot.quizid = ?
AND (qve.version = qref.version OR
qref.version IS NULL AND qve.version = (
SELECT MAX(version)
FROM {question_versions} iqve
WHERE iqve.questionbankentryid = qref.questionbankentryid
)
)
", [$this->context->id, $attempt->quiz]);
$this->structureforregrade = qbank_helper::get_question_structure(
$attempt->quiz, $this->context);
}
// If this is a non-random slot, we will have the right info cached.
if (array_key_exists($slot, $this->slotquestionids)) {
if ($this->structureforregrade[$slot]->qtype != 'random') {
// This is a non-random slot.
return question_bank::load_question($this->slotquestionids[$slot]);
return question_bank::load_question($this->structureforregrade[$slot]->questionid);
}
// We must be dealing with a random question. Check that cache.
$currentquestion = $quba->get_question_attempt($slot)->get_question(false);
if (array_key_exists($currentquestion->id, $this->newquestionidsforold)) {
if (isset($this->newquestionidsforold[$currentquestion->id])) {
return question_bank::load_question($this->newquestionidsforold[$currentquestion->id]);
}
// This is a random question we have not seen yet. Find the latest version.
$versionsoptions = \mod_quiz\question\bank\qbank_helper::get_version_options($currentquestion->id);
$versionsoptions = qbank_helper::get_version_options($currentquestion->id);
$latestversion = reset($versionsoptions);
$this->newquestionidsforold[$currentquestion->id] = $latestversion->questionid;
return question_bank::load_question($latestversion->questionid);

View File

@ -22,8 +22,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_question\local\bank\question_version_status;
use mod_quiz\external\submit_question_version;
use mod_quiz\question\bank\qbank_helper;
defined('MOODLE_INTERNAL') || die();
@ -32,6 +32,7 @@ require_once($CFG->dirroot . '/mod/quiz/locallib.php');
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
require_once($CFG->dirroot . '/mod/quiz/report/default.php');
require_once($CFG->dirroot . '/mod/quiz/report/overview/report.php');
require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_form.php');
require_once($CFG->dirroot . '/mod/quiz/report/overview/tests/helpers.php');
require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php');
@ -41,7 +42,6 @@ require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.ph
*
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \question_usage_by_activity
*/
class quiz_overview_report_testcase extends advanced_testcase {
use \quiz_question_helper_test_trait;
@ -51,19 +51,19 @@ class quiz_overview_report_testcase extends advanced_testcase {
*
* @return array the data for the test sub-cases.
*/
public function report_sql_cases() {
public function report_sql_cases(): array {
return [[null], ['csv']]; // Only need to test on or off, not all download types.
}
/**
* Test how the report queries the database.
*
* @param bool $isdownloading a download type, or null.
* @param string|null $isdownloading a download type, or null.
* @dataProvider report_sql_cases
*/
public function test_report_sql($isdownloading) {
public function test_report_sql(?string $isdownloading): void {
global $DB;
$this->resetAfterTest(true);
$this->resetAfterTest();
// Create a course and a quiz.
$generator = $this->getDataGenerator();
@ -74,6 +74,7 @@ class quiz_overview_report_testcase extends advanced_testcase {
'attempts' => 10));
// Add one question.
/** @var core_question_generator $questiongenerator */
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$q = $questiongenerator->create_question('essay', 'plain', ['category' => $cat->id]);
@ -244,7 +245,7 @@ class quiz_overview_report_testcase extends advanced_testcase {
* Bands provider.
* @return array
*/
public function get_bands_count_and_width_provider() {
public function get_bands_count_and_width_provider(): array {
return [
[10, [20, .5]],
[20, [20, 1]],
@ -263,8 +264,8 @@ class quiz_overview_report_testcase extends advanced_testcase {
* @param int $grade grade
* @param array $expected
*/
public function test_get_bands_count_and_width($grade, $expected) {
$this->resetAfterTest(true);
public function test_get_bands_count_and_width(int $grade, array $expected): void {
$this->resetAfterTest();
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance(['course' => SITEID, 'grade' => $grade]);
$this->assertEquals($expected, quiz_overview_report::get_bands_count_and_width($quiz));
@ -273,8 +274,8 @@ class quiz_overview_report_testcase extends advanced_testcase {
/**
* Test delete_selected_attempts function.
*/
public function test_delete_selected_attempts() {
$this->resetAfterTest(true);
public function test_delete_selected_attempts(): void {
$this->resetAfterTest();
$timestamp = 1234567890;
$timestart = $timestamp + 3600;
@ -292,6 +293,7 @@ class quiz_overview_report_testcase extends advanced_testcase {
]);
// Add one question.
/** @var core_question_generator $questiongenerator */
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$q = $questiongenerator->create_question('essay', 'plain', ['category' => $cat->id]);
@ -326,53 +328,80 @@ class quiz_overview_report_testcase extends advanced_testcase {
* @covers ::regrade_question
*/
public function test_regrade_question() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$quiz = $this->create_test_quiz($course);
$student = $this->getDataGenerator()->create_user();
// Test for questions from a different context.
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $course->id)->id);
$cm = get_fast_modinfo($course->id)->get_cm($quiz->cmid);
$context = context_module::instance($quiz->cmid);
/** @var core_question_generator $questiongenerator */
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
// Create a couple of questions.
$cat = $questiongenerator->create_question_category(['contextid' => $context->id]);
$numq = $questiongenerator->create_question('essay', null,
['category' => $cat->id, 'name' => 'This is the first version', 'correctanswer' => false]);
// Create two version.
$numq2 = $questiongenerator->update_question($numq, null,
['name' => 'This is the second version', 'correctanswer' => true]);
$numq3 = $questiongenerator->update_question($numq, null,
['name' => 'This is the third version', 'correctanswer' => false]);
quiz_add_quiz_question($numq->id, $quiz);
// Create the quiz object.
$quizobj = \quiz::create($quiz->id);
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
$slots = $structure->get_slots();
$slot = reset($slots);
// Now change the version using the external service.
$versions = qbank_helper::get_version_options($slot->questionid);
// We dont want the current version.
$selectversions = [];
foreach ($versions as $version) {
if ($version->version === $slot->version) {
continue;
}
$selectversions[$version->version] = $version;
}
// Change to version 1, with correct response.
$this->expectException('moodle_exception');
submit_question_version::execute($slot->id, (int)$selectversions[1]->version);
list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $student);
$q = $questiongenerator->create_question('shortanswer', null,
['category' => $cat->id, 'name' => 'Toad scores 0.8']);
// Create a version, the last one draft.
// Sadly, update_question is a bit dodgy, so it can't handle updating the answer score.
$q2 = $questiongenerator->update_question($q, null,
['name' => 'Toad now scores 1.0']);
$toadanswer = $DB->get_record_select('question_answers',
'question = ? AND ' . $DB->sql_compare_text('answer') . ' = ?',
[$q2->id, 'toad'], '*', MUST_EXIST);
$DB->set_field('question_answers', 'fraction', 1, ['id' => $toadanswer->id]);
// Add the question to the quiz.
quiz_add_quiz_question($q2->id, $quiz, 0, 10);
// Attempt the quiz, submitting response 'toad'.
$quizobj = quiz::create($quiz->id);
$attempt = quiz_prepare_and_start_new_attempt($quizobj, 1, null);
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_submitted_actions(time(), false, [1 => ['answer' => 'toad']]);
$attemptobj->process_finish(time(), false);
// We should be using 'always latest' version, which is currently v2, so should be right.
$this->assertEquals(10, $attemptobj->get_question_usage()->get_total_mark());
// Change to version 2, with wrong response.
submit_question_version::execute($slot->id, (int)$selectversions[2]->version);
$quba->regrade_question(1, quiz_attempt::FINISHED, null, $numq2->id);
$this->assertEquals(0, $attemptobj->get_question_usage()->get_total_mark());
// Now change the quiz to use fixed version 1.
$slot = $quizobj->get_question($q2->id);
submit_question_version::execute($slot->slotid, 1);
// Change to version 3, with correct response.
submit_question_version::execute($slot->id, (int)$selectversions[3]->version);
$quba->regrade_question(1, quiz_attempt::FINISHED, null, $numq3->id);
$this->assertEquals(10, $attemptobj->get_question_usage()->get_total_mark());
// Regrade.
$report = new quiz_overview_report();
$report->init('overview', 'quiz_overview_settings_form', $quiz, $cm, $course);
$report->regrade_attempt($attempt);
// The mark should now be 8.
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertEquals(8, $attemptobj->get_question_usage()->get_total_mark());
// Now add two more versions, the second of which is draft.
$q3 = $questiongenerator->update_question($q, null,
['name' => 'Toad now scores 0.5']);
$toadanswer = $DB->get_record_select('question_answers',
'question = ? AND ' . $DB->sql_compare_text('answer') . ' = ?',
[$q3->id, 'toad'], '*', MUST_EXIST);
$DB->set_field('question_answers', 'fraction', 0.5, ['id' => $toadanswer->id]);
$q4 = $questiongenerator->update_question($q, null,
['name' => 'Toad now scores 0.3',
'status' => question_version_status::QUESTION_STATUS_DRAFT]);
$toadanswer = $DB->get_record_select('question_answers',
'question = ? AND ' . $DB->sql_compare_text('answer') . ' = ?',
[$q4->id, 'toad'], '*', MUST_EXIST);
$DB->set_field('question_answers', 'fraction', 0.3, ['id' => $toadanswer->id]);
// Now change the quiz back to always latest and regrade again.
submit_question_version::execute($slot->slotid, 0);
$report->clear_regrade_date_cache();
$report->regrade_attempt($attempt);
// Score should now be 5, because v3 is the latest non-draft version.
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertEquals(5, $attemptobj->get_question_usage()->get_total_mark());
}
}

View File

@ -86,9 +86,6 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase {
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertFalse($attemptobj->has_response_to_at_least_one_graded_question());
$prefix1 = $quba->get_field_prefix(1);
$prefix2 = $quba->get_field_prefix(2);
$tosubmit = array(1 => array('answer' => 'frog'),
2 => array('answer' => '3.14'));

View File

@ -6,16 +6,18 @@ Feature: Allow students to redo questions in a practice quiz, without starting a
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student | Student | One | student@example.com |
| teacher | Teacher | One | teacher@example.com |
| username | firstname | lastname |
| student | Student | One |
| teacher | Teacher | One |
| editor | Question | Editor |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student | C1 | student |
| teacher | C1 | teacher |
| user | course | role |
| student | C1 | student |
| teacher | C1 | teacher |
| editor | C1 | editingteacher |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Test questions |
@ -41,6 +43,39 @@ Feature: Allow students to redo questions in a practice quiz, without starting a
Then the state of "First question" question is shown as "Not complete"
And I should see "Marked out of 2.00" in the "First question" "question"
@javascript
Scenario: Start attempt, teacher edits question, redo picks up latest non-draft version
# Start attempt as student.
Given I am on the "Quiz 1" "mod_quiz > View" page logged in as "student"
And I press "Attempt quiz"
And I click on "False" "radio" in the "First question" "question"
And I click on "Check" "button" in the "First question" "question"
And I log out
# Now edit the question as teacher to add a real version and a draft version.
# Would be nice to do this with a generator, but I don't have time right now.
And I am on the "TF1" "core_question > edit" page logged in as "editor"
And I set the following fields to these values:
| Question name | TF1-v2 |
| Question text | The new first question |
| Correct answer | False |
And I press "id_submitbutton"
And I am on the "TF1-v2" "core_question > edit" page
And I set the following fields to these values:
| Question name | TF1-v3 |
| Question text | This is only draft for now |
| Correct answer | True |
| Question status | Draft |
And I press "id_submitbutton"
And I log out
When I am on the "Quiz 1" "mod_quiz > View" page logged in as "student"
And I press "Continue your attempt"
And I press "Try another question like this one"
Then the state of "The new first question" question is shown as "Not complete"
And I should see "Marked out of 2.00" in the "The new first question" "question"
And I should not see "This is only draft for now"
@javascript
Scenario: The redo question button is visible but disabled for teachers
Given I am on the "Quiz 1" "mod_quiz > View" page logged in as "student"

View File

@ -53,40 +53,17 @@ class qbank_helper_test extends \advanced_testcase {
$this->user = $USER;
}
/**
* Test is random.
*
* @covers ::is_random
* @covers ::get_random_question_data_from_slot
*/
public function test_is_random() {
$this->resetAfterTest();
$quiz = $this->create_test_quiz($this->course);
// Test for questions from a different context.
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $context->id]);
// Create the quiz object.
$quizobj = \quiz::create($quiz->id);
$structure = structure::create_for_quiz($quizobj);
$slots = $structure->get_slots();
foreach ($slots as $slot) {
$this->assertEquals(true, qbank_helper::is_random($slot->id));
}
}
/**
* Test reference records.
*
* @covers ::get_version_options
* @covers ::get_question_for_redo
*/
public function test_reference_records() {
$this->resetAfterTest();
$quiz = $this->create_test_quiz($this->course);
// Test for questions from a different context.
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
$context = \context_module::instance($quiz->cmid);
// Create a couple of questions.
/** @var \core_question_generator $questiongenerator */
@ -110,11 +87,8 @@ class qbank_helper_test extends \advanced_testcase {
$slots = $structure->get_slots();
$slot = reset($slots);
$this->assertEquals(3, count(qbank_helper::get_version_options($question->id)));
$quizobj->preload_questions();
$quizobj->load_questions();
$questions = $quizobj->get_questions();
$question = reset($questions);
$this->assertEquals($question->id, qbank_helper::get_question_for_redo($slot->id));
$this->assertEquals($question->id, qbank_helper::choose_question_for_redo(
$quiz->id, $context, $slot->id, new \qubaid_list([])));
// Create another version.
$questiongenerator->update_question($numq, null, ['name' => 'This is the latest version']);
@ -125,7 +99,8 @@ class qbank_helper_test extends \advanced_testcase {
$quizobj->load_questions();
$questions = $quizobj->get_questions();
$question = reset($questions);
$this->assertEquals($question->id, qbank_helper::get_question_for_redo($slot->id));
$this->assertEquals($question->id, qbank_helper::choose_question_for_redo(
$quiz->id, $context, $slot->id, new \qubaid_list([])));
}
/**

View File

@ -82,7 +82,7 @@ class quiz_question_restore_test extends \advanced_testcase {
$this->assertEquals(0, count(\mod_quiz\question\bank\qbank_helper::get_question_structure(
$quiz->id, $oldquizcontext)));
/// Restore the course.
// Restore the course.
$newcourse = $this->getDataGenerator()->create_course();
$this->restore_quiz($backupid, $newcourse, $this->user);
@ -120,7 +120,7 @@ class quiz_question_restore_test extends \advanced_testcase {
$this->assertEquals(0, count(\mod_quiz\question\bank\qbank_helper::get_question_structure(
$quiz->id, $quizcontext)));
/// Restore the course.
// Restore the course.
$newcourse = $this->getDataGenerator()->create_course();
$this->restore_quiz($backupid, $newcourse, $this->user);

View File

@ -129,7 +129,7 @@ class core_question_generator extends component_generator_base {
$fromform = test_question_maker::get_question_form_data($qtype, $which);
$fromform = (object) $this->datagenerator->combine_defaults_and_record((array) $question, $fromform);
$fromform = (object) $this->datagenerator->combine_defaults_and_record((array) $fromform, $overrides);
$fromform->status = $question->status;
$fromform->status = $fromform->status ?? $question->status;
$question = question_bank::get_qtype($qtype)->save_question($question, $fromform);