diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index 9cbd36d7167ab..ec1d76e3d9381 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -6191,15 +6191,30 @@ public function process_question_set_reference($data) { $data = (object) $data; $data->usingcontextid = $this->get_mappingid('context', $data->usingcontextid); $data->itemid = $this->get_new_parentid('quiz_question_instance'); - $filtercondition = json_decode($data->filtercondition); - if ($category = $this->get_mappingid('question_category', $filtercondition->questioncategoryid)) { - $filtercondition->questioncategoryid = $category; + $filtercondition = json_decode($data->filtercondition, true); + + if (!isset($filtercondition['filter'])) { + // Pre-4.3, convert the old filtercondition format to the new format. + $filtercondition = \core_question\question_reference_manager::convert_legacy_set_reference_filter_condition( + $filtercondition); } - $data->filtercondition = json_encode($filtercondition); + + // Map category id used for category filter condition and corresponding context id. + $oldcategoryid = $filtercondition['filter']['category']['values'][0]; + $newcategoryid = $this->get_mappingid('question_category', $oldcategoryid); + $filtercondition['filter']['category']['values'][0] = $newcategoryid; + if ($context = $this->get_mappingid('context', $data->questionscontextid)) { $data->questionscontextid = $context; } + $filtercondition['cat'] = implode(',', [ + $filtercondition['filter']['category']['values'][0], + $data->questionscontextid, + ]); + + $data->filtercondition = json_encode($filtercondition); + $DB->insert_record('question_set_references', $data); } } diff --git a/mod/quiz/tests/fixtures/moodle_42_random_question.mbz b/mod/quiz/tests/fixtures/moodle_42_random_question.mbz new file mode 100644 index 0000000000000..f7a0dcef055b5 Binary files /dev/null and b/mod/quiz/tests/fixtures/moodle_42_random_question.mbz differ diff --git a/mod/quiz/tests/quiz_question_restore_test.php b/mod/quiz/tests/quiz_question_restore_test.php index 1086016c2173d..f08679b4d61f6 100644 --- a/mod/quiz/tests/quiz_question_restore_test.php +++ b/mod/quiz/tests/quiz_question_restore_test.php @@ -508,4 +508,65 @@ public function test_backup_restore_question_slots(): void { $this->assertEquals($originalslot->maxmark, $restoredslot->maxmark); } } + + /** + * Test pre 4.3 quiz restore for random question filter conditions. + * + * @covers \restore_question_set_reference_data_trait::process_question_set_reference + */ + public function test_pre_43_quiz_restore_for_random_question_filtercondition() { + global $USER, $DB; + $this->resetAfterTest(); + $backupid = 'abc'; + $backuppath = make_backup_temp_directory($backupid); + get_file_packer('application/vnd.moodle.backup')->extract_to_pathname( + __DIR__ . "/fixtures/moodle_42_random_question.mbz", $backuppath); + + // Do the restore to new course with default settings. + $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}"); + $newcourseid = \restore_dbops::create_new_course('Test fullname', 'Test shortname', $categoryid); + $rc = new \restore_controller($backupid, $newcourseid, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id, + \backup::TARGET_NEW_COURSE); + + $this->assertTrue($rc->execute_precheck()); + $rc->execute_plan(); + $rc->destroy(); + + // Get the information about the resulting course and check that it is set up correctly. + $modinfo = get_fast_modinfo($newcourseid); + $quiz = array_values($modinfo->get_instances_of('quiz'))[0]; + $quizobj = \mod_quiz\quiz_settings::create($quiz->instance); + $structure = \mod_quiz\structure::create_for_quiz($quizobj); + + // Count the questions in quiz qbank. + $context = \context_module::instance(get_coursemodule_from_instance("quiz", $quizobj->get_quizid(), $newcourseid)->id); + $this->assertEquals(2, $this->question_count($context->id)); + + // Are the correct slots returned? + $slots = $structure->get_slots(); + $this->assertCount(1, $slots); + + // Check that the filtercondition now matches the 4.3 structure. + foreach ($slots as $slot) { + $setreference = $DB->get_record('question_set_references', + ['itemid' => $slot->id, 'component' => 'mod_quiz', 'questionarea' => 'slot']); + $filterconditions = json_decode($setreference->filtercondition, true); + $this->assertArrayHasKey('cat', $filterconditions); + $this->assertArrayHasKey('jointype', $filterconditions); + $this->assertArrayHasKey('qpage', $filterconditions); + $this->assertArrayHasKey('qperpage', $filterconditions); + $this->assertArrayHasKey('filter', $filterconditions); + $this->assertArrayHasKey('category', $filterconditions['filter']); + $this->assertArrayHasKey('qtagids', $filterconditions['filter']); + $this->assertArrayNotHasKey('questioncategoryid', $filterconditions); + $this->assertArrayNotHasKey('tags', $filterconditions); + $expectedtags = \core_tag_tag::get_by_name_bulk(1, ['foo', 'bar']); + $expectedtagids = array_values(array_map(fn($expectedtag) => $expectedtag->id, $expectedtags)); + $this->assertEquals($expectedtagids, $filterconditions['filter']['qtagids']['values']); + $expectedcategory = $DB->get_record('question_categories', ['idnumber' => 'RAND']); + $this->assertEquals($expectedcategory->id, $filterconditions['filter']['category']['values'][0]); + $expectedcat = implode(',', [$expectedcategory->id, $expectedcategory->contextid]); + $this->assertEquals($expectedcat, $filterconditions['cat']); + } + } } diff --git a/question/classes/question_reference_manager.php b/question/classes/question_reference_manager.php index 049a39fbb8113..1ccdf5f2af983 100644 --- a/question/classes/question_reference_manager.php +++ b/question/classes/question_reference_manager.php @@ -86,15 +86,15 @@ public static function questions_with_references(array $questionids): array { * @return array Post-4.3 filter condition. */ public static function convert_legacy_set_reference_filter_condition(array $filtercondition): array { - if (!isset($filtercondition['filters'])) { - $filtercondition['filters'] = []; + if (!isset($filtercondition['filter'])) { + $filtercondition['filter'] = []; // Question category filter. if (isset($filtercondition['questioncategoryid'])) { - $filtercondition['filters']['category'] = [ - 'jointype' => \qbank_managecategories\category_condition::JOINTYPE_DEFAULT, - 'values' => [$filtercondition['questioncategoryid']], - 'includesubcategories' => $filtercondition['includingsubcategories'], + $filtercondition['filter']['category'] = [ + 'jointype' => \qbank_managecategories\category_condition::JOINTYPE_DEFAULT, + 'values' => [$filtercondition['questioncategoryid']], + 'includesubcategories' => $filtercondition['includingsubcategories'], ]; unset($filtercondition['questioncategoryid']); unset($filtercondition['includingsubcategories']); @@ -102,12 +102,23 @@ public static function convert_legacy_set_reference_filter_condition(array $filt // Tag filters. if (isset($filtercondition['tags'])) { - $filtercondition['filters']['qtagid'] = [ + // Get the names of the tags in the condition. Find or create corresponding tags, + // and set their ids in the new condition. + $oldtags = array_map(fn($oldtag) => explode(',', $oldtag)[1], $filtercondition['tags']); + $newtags = \core_tag_tag::create_if_missing(1, $oldtags); + $newtagids = array_map(fn($newtag) => $newtag->id, $newtags); + + $filtercondition['filter']['qtagids'] = [ 'jointype' => \qbank_tagquestion\tag_condition::JOINTYPE_DEFAULT, - 'values' => $filtercondition['tags'] + 'values' => array_values($newtagids), ]; unset($filtercondition['tags']); } + // Add additional default properties to the filtercondition. + $filtercondition['tabname'] = 'questions'; + $filtercondition['qpage'] = 0; + $filtercondition['qperpage'] = 100; + $filtercondition['jointype'] = \core\output\datafilter::JOINTYPE_ALL; } return $filtercondition; }