From d9a75f5315e0d115d8ef808c4f8437be24259541 Mon Sep 17 00:00:00 2001 From: Nathan Nguyen Date: Thu, 29 Sep 2022 14:07:26 +1000 Subject: [PATCH] MDL-72321 core: backup/restore filter condition --- backup/moodle2/restore_stepslib.php | 23 +++++-- .../fixtures/moodle_42_random_question.mbz | Bin 0 -> 5044 bytes mod/quiz/tests/quiz_question_restore_test.php | 61 ++++++++++++++++++ .../classes/question_reference_manager.php | 27 +++++--- 4 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 mod/quiz/tests/fixtures/moodle_42_random_question.mbz diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index 9cbd36d7167..ec1d76e3d93 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -6191,15 +6191,30 @@ trait restore_question_set_reference_data_trait { $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 0000000000000000000000000000000000000000..f7a0dcef055b591495dcb6116b4a346eeea462dc GIT binary patch literal 5044 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 zO2ekel3pIK*`|kvk$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 049a39fbb81..1ccdf5f2af9 100644 --- a/question/classes/question_reference_manager.php +++ b/question/classes/question_reference_manager.php @@ -86,15 +86,15 @@ class question_reference_manager { * @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 @@ class question_reference_manager { // 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; }