MDL-87606 backup: Include child questions in backups
Since MDL-41924, because we are not always including the whole category of each question being used in a backup, we have been missing some child questions. This expands the query selecting questions belonging to categories where only annotated questions are being included, to also select any children of those questions.
This commit is contained in:
@@ -2726,7 +2726,7 @@ class backup_questions_structure_step extends backup_structure_step {
|
||||
WHERE bi.backupid = ?
|
||||
AND bi.itemname = 'question_categoryfinal'", [backup::VAR_BACKUPID]);
|
||||
|
||||
// Add all question bank entries from "complete" categories, plus annotated question bank entires
|
||||
// Add all question bank entries from "complete" categories, plus annotated question bank entires and their children
|
||||
// from "partial" categories.
|
||||
$questionbankentry->set_source_sql(
|
||||
"
|
||||
@@ -2738,8 +2738,11 @@ class backup_questions_structure_step extends backup_structure_step {
|
||||
UNION
|
||||
SELECT qbe.*
|
||||
FROM {question_bank_entries} qbe
|
||||
JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id
|
||||
JOIN {question} q ON q.id = qv.questionid
|
||||
LEFT JOIN {question_versions} parentqv ON parentqv.questionid = q.parent
|
||||
JOIN {question_category_partial_temp} qcp ON qcp.itemid = qbe.questioncategoryid
|
||||
JOIN {backup_ids_temp} biq ON biq.itemid = qbe.id
|
||||
JOIN {backup_ids_temp} biq ON biq.itemid = qbe.id OR biq.itemid = parentqv.questionbankentryid
|
||||
WHERE qcp.itemid = ?
|
||||
AND qcp.backupid = ?
|
||||
AND biq.backupid = ?
|
||||
|
||||
@@ -250,7 +250,8 @@ final class backup_question_selection_test extends \advanced_testcase {
|
||||
] = $this->create_quiz_and_questions();
|
||||
|
||||
// Revert the filtercondition to the legacy JSON format.
|
||||
$questionsetreference = $DB->get_record('question_set_references', []);
|
||||
$questionsetreferences = $DB->get_records('question_set_references');
|
||||
$questionsetreference = reset($questionsetreferences);
|
||||
$filtercondition = json_decode($questionsetreference->filtercondition);
|
||||
$tag = \core_tag_tag::get($filtercondition->filter->qtagids->values[0]);
|
||||
$questionsetreference->filtercondition = json_encode([
|
||||
@@ -379,5 +380,137 @@ final class backup_question_selection_test extends \advanced_testcase {
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Backing up a quiz using questions with children includes those children in the backup.
|
||||
*/
|
||||
public function test_quiz_backup_includes_child_questions(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$manager = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($manager);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$sharedcourse = $this->getDataGenerator()->create_course();
|
||||
$this->getDataGenerator()->enrol_user($manager->id, $course->id, 'manager');
|
||||
$this->getDataGenerator()->enrol_user($manager->id, $sharedcourse->id, 'manager');
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
|
||||
// Create a question bank with 3 shared questions, each with 2 children.
|
||||
$courseqbank = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
|
||||
$coursequestions = $questiongenerator->create_categories_and_questions(
|
||||
\context_module::instance($courseqbank->cmid),
|
||||
[
|
||||
'courseparentcat' => [
|
||||
'courseq1' => 'multianswer',
|
||||
'courseq2' => 'multianswer',
|
||||
],
|
||||
'randomcat' => [
|
||||
'courseq3' => 'multianswer',
|
||||
],
|
||||
]
|
||||
);
|
||||
$coursechildquestions = [
|
||||
'courseq1' => $DB->get_records(
|
||||
'question',
|
||||
['parent' => $coursequestions['courseparentcat']['courseq1']->id],
|
||||
),
|
||||
'courseq2' => $DB->get_records(
|
||||
'question',
|
||||
['parent' => $coursequestions['courseparentcat']['courseq2']->id],
|
||||
),
|
||||
'courseq3' => $DB->get_records(
|
||||
'question',
|
||||
['parent' => $coursequestions['randomcat']['courseq3']->id],
|
||||
),
|
||||
];
|
||||
$quiz = $this->create_test_quiz($course);
|
||||
$quizquestions = $questiongenerator->create_categories_and_questions(
|
||||
\context_module::instance($quiz->cmid),
|
||||
[
|
||||
'quizcat' => [
|
||||
'quizq1' => 'multianswer',
|
||||
'quizq2' => 'multianswer',
|
||||
],
|
||||
]
|
||||
);
|
||||
$quizchildquestions = [
|
||||
'quizq1' => $DB->get_records(
|
||||
'question',
|
||||
['parent' => $quizquestions['quizcat']['quizq1']->id],
|
||||
),
|
||||
'quizq2' => $DB->get_records(
|
||||
'question',
|
||||
['parent' => $quizquestions['quizcat']['quizq2']->id],
|
||||
),
|
||||
];
|
||||
// Add a question from the course bank parent category.
|
||||
quiz_add_quiz_question($coursequestions['courseparentcat']['courseq2']->id, $quiz);
|
||||
// A question from quiz bank.
|
||||
quiz_add_quiz_question($quizquestions['quizcat']['quizq2']->id, $quiz);
|
||||
// Add a random question from the course bank.
|
||||
$settings = quiz_settings::create($quiz->id);
|
||||
$structure = structure::create_for_quiz($settings);
|
||||
$structure->add_random_questions(1, 1, [
|
||||
'filter' => [
|
||||
'category' => [
|
||||
'jointype' => \core\output\datafilter::JOINTYPE_ANY,
|
||||
'values' => [$coursequestions['randomcat']['courseq3']->category],
|
||||
'filteroptions' => ['includesubcategories' => false],
|
||||
],
|
||||
],
|
||||
]);
|
||||
// Backup the quiz.
|
||||
$bc = new \backup_controller(
|
||||
\backup::TYPE_1ACTIVITY,
|
||||
$quiz->cmid,
|
||||
\backup::FORMAT_MOODLE,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_IMPORT,
|
||||
$manager->id,
|
||||
);
|
||||
$backupid = $bc->get_backupid();
|
||||
$bc->execute_plan();
|
||||
$bc->destroy();
|
||||
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$this->getDataGenerator()->enrol_user($manager->id, $course2->id, 'manager');
|
||||
$rc = new \restore_controller(
|
||||
$backupid,
|
||||
$course2->id,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_IMPORT,
|
||||
$manager->id,
|
||||
\backup::TARGET_CURRENT_ADDING,
|
||||
);
|
||||
$rc->execute_precheck();
|
||||
$backupquestions = $DB->get_records_menu('backup_ids_temp', ['itemname' => 'question'], '', 'id, itemid');
|
||||
|
||||
// Backup should contain the used parent category question and its children.
|
||||
$this->assertContains((string) $coursequestions['courseparentcat']['courseq2']->id, $backupquestions);
|
||||
foreach ($coursechildquestions['courseq2'] as $childquestion) {
|
||||
$this->assertContains((string) $childquestion->id, $backupquestions);
|
||||
}
|
||||
// Backup should not contain the parent category question or its children.
|
||||
$this->assertNotContains((string) $coursequestions['courseparentcat']['courseq1']->id, $backupquestions);
|
||||
foreach ($coursechildquestions['courseq1'] as $childquestion) {
|
||||
$this->assertNotContains((string) $childquestion->id, $backupquestions);
|
||||
}
|
||||
// Backup should contain the random category question and its children.
|
||||
$this->assertContains((string) $coursequestions['randomcat']['courseq3']->id, $backupquestions);
|
||||
foreach ($coursechildquestions['courseq3'] as $childquestion) {
|
||||
$this->assertContains((string) $childquestion->id, $backupquestions);
|
||||
}
|
||||
// Backup should contain all quiz category questions and their children, used or unused.
|
||||
$this->assertContains((string) $quizquestions['quizcat']['quizq1']->id, $backupquestions);
|
||||
foreach ($quizchildquestions['quizq1'] as $childquestion) {
|
||||
$this->assertContains((string) $childquestion->id, $backupquestions);
|
||||
}
|
||||
$this->assertContains((string) $quizquestions['quizcat']['quizq2']->id, $backupquestions);
|
||||
foreach ($quizchildquestions['quizq2'] as $childquestion) {
|
||||
$this->assertContains((string) $childquestion->id, $backupquestions);
|
||||
}
|
||||
// Clean up.
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user