From fc3192026df7d3be6a53662cb08f409c26d9f3d6 Mon Sep 17 00:00:00 2001 From: Conn Warwicker Date: Wed, 22 Jan 2025 12:18:45 +0000 Subject: [PATCH] MDL-84246 core_question: Fix transfer task for invalid questions. --- lib/questionlib.php | 6 ++ lib/tests/fixtures/1.jpg | Bin 0 -> 645 bytes lib/tests/fixtures/2.jpg | Bin 0 -> 645 bytes lib/tests/questionlib_test.php | 85 ++++++++++++++++++ question/type/missingtype/questiontype.php | 9 ++ question/type/missingtype/tests/helper.php | 49 ++++++++++ .../missingtype/tests/missingtype_test.php | 46 ++++++++++ 7 files changed, 195 insertions(+) create mode 100644 lib/tests/fixtures/1.jpg create mode 100644 lib/tests/fixtures/2.jpg create mode 100644 question/type/missingtype/tests/helper.php diff --git a/lib/questionlib.php b/lib/questionlib.php index 448dcc04ea2..e344ecb29fc 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -705,6 +705,12 @@ function question_move_category_to_context($categoryid, $oldcontextid, $newconte $questionids = $DB->get_records_sql_menu($sql, [$categoryid]); foreach ($questionids as $questionid => $qtype) { + + // If the question type is invalid, use "missingtype" so we have a valid qtype to call move_files() on. + if (!\question_bank::is_qtype_installed($qtype)) { + $qtype = 'missingtype'; + } + question_bank::get_qtype($qtype)->move_files($questionid, $oldcontextid, $newcontextid); // Purge this question from the cache. question_bank::notify_question_edited($questionid); diff --git a/lib/tests/fixtures/1.jpg b/lib/tests/fixtures/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9da5dbcc31a7a04b262835f7977d162f7a7d473c GIT binary patch literal 645 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<c1}I=;VrF4wW9Q)H;sz?% zD!{d!pzFb!U9xX3zTPI5o8roG<0MW4oqZMDikqloVbuf*=gfJ(V&YTRE(2~ znmD<{#3dx9RMpfqG__1j&CD$#!^0Jex_)l{wqQ}01_Kl`|Gx^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<c1}I=;VrF4wW9Q)H;sz?% zD!{d!pzFb!U9xX3zTPI5o8roG<0MW4oqZMDikqloVbuf*=gfJ(V&YTRE(2~ znmD<{#3dx9RMpfqG__1j&CD$#!^cw^i}n}V99Yz)!2rd~|8D{SnZeWF literal 0 HcmV?d00001 diff --git a/lib/tests/questionlib_test.php b/lib/tests/questionlib_test.php index 9dcd362524f..1a1caec279d 100644 --- a/lib/tests/questionlib_test.php +++ b/lib/tests/questionlib_test.php @@ -1549,4 +1549,89 @@ final class questionlib_test extends \advanced_testcase { $this->assertEquals(2, $nextversion); } + /** + * Test moving a question category from one context to another + * + * @covers ::question_move_category_to_context + */ + public function test_question_move_category_to_context(): void { + + global $CFG, $DB; + + $this->setAdminUser(); + + // Create a course. + $course = self::getDataGenerator()->create_course(); + + // Create a quiz activity to store our question in at the start. + $quiz1 = $this->getDataGenerator()->create_module('quiz', [ + 'course' => $course->id, + ]); + + // And then create another one to move the category to. + $quiz2 = $this->getDataGenerator()->create_module('quiz', [ + 'course' => $course->id, + ]); + + // Get the question generator and the context of the activities. + $generator = self::getDataGenerator()->get_plugin_generator('core_question'); + $context1 = \context_module::instance($quiz1->cmid); + $context2 = \context_module::instance($quiz2->cmid); + + // Create a question category within our first quiz activity. + $category = $generator->create_question_category(['contextid' => $context1->id]); + + // And create a question within that. + // We will use `truefalse` but it could be any type. + $question = $generator->create_question('truefalse', null, ['category' => $category->id]); + + $areas = [ + 'questiontext' => '1.jpg', + 'generalfeedback' => '2.jpg', + ]; + + // Add file records to each of the file areas, for our first quiz activity. + foreach ($areas as $area => $img) { + $fs = get_file_storage(); + $filerecord = new \stdClass(); + $filerecord->contextid = $context1->id; + $filerecord->component = 'question'; + $filerecord->filearea = $area; + $filerecord->itemid = $question->id; + $filerecord->filepath = '/'; + $filerecord->filename = $img; + $fs->create_file_from_pathname($filerecord, $CFG->dirroot . + '/lib/tests/fixtures/' . $img); + } + + // Firstly, confirm that the file records exist and there were no problems creating them. + // We don't care in this test about the actual files in the data dir. + $files = $DB->get_records('files', [ + 'component' => 'question', + 'itemid' => $question->id, + 'contextid' => $context1->id, + 'mimetype' => 'image/jpeg', + ]); + + $this->assertCount(2, $files); + + // Move the question category to another context. + question_move_category_to_context( + $category->id, + $context1->id, + $context2->id, + ); + + // Now check that the files have been moved to the new category. + $files = $DB->get_records('files', [ + 'component' => 'question', + 'itemid' => $question->id, + 'contextid' => $context2->id, + 'mimetype' => 'image/jpeg', + ]); + + $this->assertCount(2, $files); + + } + } diff --git a/question/type/missingtype/questiontype.php b/question/type/missingtype/questiontype.php index de3d5cc4300..89d2eb87904 100644 --- a/question/type/missingtype/questiontype.php +++ b/question/type/missingtype/questiontype.php @@ -93,4 +93,13 @@ class qtype_missingtype extends question_type { $mform->display(); } + + #[\Override] + public function move_files($questionid, $oldcontextid, $newcontextid) { + parent::move_files($questionid, $oldcontextid, $newcontextid); + $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid); + $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid); + $this->move_files_in_combined_feedback($questionid, $oldcontextid, $newcontextid); + } + } diff --git a/question/type/missingtype/tests/helper.php b/question/type/missingtype/tests/helper.php new file mode 100644 index 00000000000..5a7cac7b82f --- /dev/null +++ b/question/type/missingtype/tests/helper.php @@ -0,0 +1,49 @@ +. + +/** + * Test helpers for the missingtype question type. + * + * @package qtype_missingtype + * @copyright 2025 onwards Catalyst IT {@link http://www.catalyst-eu.net/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Conn Warwicker + */ +class qtype_missingtype_test_helper extends question_test_helper { + + #[\Override] + public function get_test_questions(): array { + return ['invalid']; + } + + /** + * Gets the question form data for the invalid question. + * + * @return stdClass + */ + public function get_missingtype_question_form_data_invalid(): stdClass { + + $form = new stdClass(); + $form->name = 'Invalid question'; + $form->questiontext = ['text' => 'You will never see this', 'format' => FORMAT_HTML]; + $form->defaultmark = 1.0; + $form->generalfeedback = ['text' => 'How did you even submit this?', 'format' => FORMAT_HTML]; + $form->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY; + return $form; + + } + +} diff --git a/question/type/missingtype/tests/missingtype_test.php b/question/type/missingtype/tests/missingtype_test.php index c1ea26f4d28..bafbe34e6eb 100644 --- a/question/type/missingtype/tests/missingtype_test.php +++ b/question/type/missingtype/tests/missingtype_test.php @@ -143,4 +143,50 @@ final class missingtype_test extends \question_testcase { $qtype = new qtype_missingtype(); $this->assertEquals(array(), $qtype->get_possible_responses(null)); } + + /** + * Test moving a question category from one context to another when it contains questions of an invalid type + * + * @covers ::question_move_category_to_context + */ + public function test_move_question_category_with_missing_question_types(): void { + + global $DB; + + $this->resetAfterTest(); + + // Create a course we can move the category to. + $course = $this->getDataGenerator()->create_course(); + + // Create a quiz module to attach the question category to. + $module1 = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]); + + // And another one to move things to. + $module2 = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]); + + // Create a question category on the system context. + $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $category = $generator->create_question_category([ + 'contextid' => \core\context\module::instance($module1->cmid)->id, + ]); + + // Create a question of an invalid type and put it in the category. + $question = $generator->create_question('missingtype', null, ['category' => $category->id]); + + // Update the question to set an invalid qtype, as "missingtype" is actually installed and won't fail. + $question->qtype = 'invalid'; + $DB->update_record('question', $question); + + // We just want to assert that no exception is thrown. + $this->expectNotToPerformAssertions(); + + // Try and move the categories. + question_move_category_to_context( + $category->id, + \core\context\module::instance($module1->cmid)->id, + \core\context\module::instance($module2->cmid)->id, + ); + + } + }