Merge branch 'MDL-85210_500_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_500_STABLE

This commit is contained in:
Mihail Geshoski
2025-07-05 20:13:13 +08:00
4 changed files with 215 additions and 9 deletions
+11 -9
View File
@@ -667,16 +667,17 @@ function move_question_set_references(int $oldcategoryid, int $newcatgoryid,
if ($delete || $oldcontextid !== $newcontextid) {
$setreferences = $DB->get_recordset('question_set_references', ['questionscontextid' => $oldcontextid]);
foreach ($setreferences as $setreference) {
$filter = json_decode($setreference->filtercondition);
if (isset($filter->questioncategoryid)) {
if ((int)$filter->questioncategoryid === $oldcategoryid) {
$setreference->questionscontextid = $newcontextid;
if ($oldcategoryid !== $newcatgoryid) {
$filter->questioncategoryid = $newcatgoryid;
$setreference->filtercondition = json_encode($filter);
}
$DB->update_record('question_set_references', $setreference);
$filter = json_decode($setreference->filtercondition, true);
if (isset($filter['questioncategoryid'])) {
$filter = question_reference_manager::convert_legacy_set_reference_filter_condition($filter);
}
if ((int)$filter['filter']['category']['values'][0] === $oldcategoryid) {
$setreference->questionscontextid = $newcontextid;
if ($oldcategoryid !== $newcatgoryid) {
$filter['filter']['category']['values'][0] = $newcatgoryid;
$setreference->filtercondition = json_encode($filter);
}
$DB->update_record('question_set_references', $setreference);
}
}
$setreferences->close();
@@ -728,6 +729,7 @@ function question_move_category_to_context($categoryid, $oldcontextid, $newconte
$subcatids = $DB->get_records_menu('question_categories', ['parent' => $categoryid], '', 'id,1');
foreach ($subcatids as $subcatid => $notused) {
move_question_set_references($subcatid, $subcatid, $oldcontextid, $newcontext->id);
$DB->set_field('question_categories', 'contextid', $newcontextid, ['id' => $subcatid]);
question_move_category_to_context($subcatid, $oldcontextid, $newcontextid);
}
+106
View File
@@ -17,6 +17,7 @@
namespace core;
use core_question\local\bank\question_bank_helper;
use mod_quiz\quiz_settings;
use question_bank;
defined('MOODLE_INTERNAL') || die();
@@ -1673,4 +1674,109 @@ final class questionlib_test extends \advanced_testcase {
}
/**
* Update the context for a set reference, keeping the original category.
*
* @covers ::move_question_set_references()
*/
public function test_move_question_set_references_context(): void {
$this->setAdminUser();
// Create a course with a quiz containing a random question from a qbank context.
$randomcourse = self::getDataGenerator()->create_course(['shortname' => 'Random']);
$qbank1 = self::getDataGenerator()->get_plugin_generator('mod_qbank')->create_instance(['course' => $randomcourse->id]);
$context1 = \context_module::instance($qbank1->cmid);
$qbank2 = self::getDataGenerator()->get_plugin_generator('mod_qbank')->create_instance(['course' => $randomcourse->id]);
$context2 = \context_module::instance($qbank2->cmid);
$topcategory = question_get_top_category($context1->id, true);
$randomcategory = self::getDataGenerator()->get_plugin_generator('core_question')->create_question_category(
['parent' => $topcategory->id],
);
$randomquiz = self::getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(
[
'course' => $randomcourse->id,
'grade' => 100.0,
'sumgrades' => 2,
'layout' => '1,0',
],
);
$randomquizsettings = quiz_settings::create($randomquiz->id);
$structure = $randomquizsettings->get_structure();
$filtercondition = [
'filter' => [
'category' => [
'jointype' => \core_question\local\bank\condition::JOINTYPE_DEFAULT,
'values' => [$randomcategory->id],
'filteroptions' => ['includesubcategories' => true],
],
],
];
$structure->add_random_questions(1, 1, $filtercondition);
$structure = $randomquizsettings->get_structure();
$randomquestion = $structure->get_question_in_slot(1);
$this->assertEquals($randomquestion->contextid, $context1->id);
$this->assertEquals($randomquestion->filtercondition['filter']['category']['values'][0], $randomcategory->id);
move_question_set_references($randomcategory->id, $randomcategory->id, $context1->id, $context2->id);
$structure = $randomquizsettings->get_structure();
$randomquestion = $structure->get_question_in_slot(1);
$this->assertEquals($randomquestion->contextid, $context2->id);
$this->assertEquals($randomquestion->filtercondition['filter']['category']['values'][0], $randomcategory->id);
}
/**
* Update the context and category for a set reference.
*
* @covers ::move_question_set_references()
*/
public function test_move_question_set_references_category(): void {
$this->setAdminUser();
// Create a course with a quiz containing a random question from a qbank context.
$randomcourse = self::getDataGenerator()->create_course(['shortname' => 'Random']);
$qbank1 = self::getDataGenerator()->get_plugin_generator('mod_qbank')->create_instance(['course' => $randomcourse->id]);
$context1 = \context_module::instance($qbank1->cmid);
$qbank2 = self::getDataGenerator()->get_plugin_generator('mod_qbank')->create_instance(['course' => $randomcourse->id]);
$context2 = \context_module::instance($qbank2->cmid);
$topcategory1 = question_get_top_category($context1->id, true);
$topcategory2 = question_get_top_category($context2->id, true);
$randomquiz = self::getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(
[
'course' => $randomcourse->id,
'grade' => 100.0,
'sumgrades' => 2,
'layout' => '1,0',
],
);
$randomquizsettings = quiz_settings::create($randomquiz->id);
$structure = $randomquizsettings->get_structure();
$filtercondition = [
'filter' => [
'category' => [
'jointype' => \core_question\local\bank\condition::JOINTYPE_DEFAULT,
'values' => [$topcategory1->id],
'filteroptions' => ['includesubcategories' => true],
],
],
];
$structure->add_random_questions(1, 1, $filtercondition);
$structure = $randomquizsettings->get_structure();
$randomquestion = $structure->get_question_in_slot(1);
$this->assertEquals($randomquestion->contextid, $context1->id);
$this->assertEquals($randomquestion->filtercondition['filter']['category']['values'][0], $topcategory1->id);
move_question_set_references($topcategory1->id, $topcategory2->id, $context1->id, $context2->id);
$structure = $randomquizsettings->get_structure();
$randomquestion = $structure->get_question_in_slot(1);
$this->assertEquals($randomquestion->contextid, $context2->id);
$this->assertEquals($randomquestion->filtercondition['filter']['category']['values'][0], $topcategory2->id);
}
}
@@ -171,6 +171,8 @@ class transfer_question_categories extends adhoc_task {
$newtopcategory = question_get_top_category($newcontext->id, true);
move_question_set_references($oldtopcategory->id, $newtopcategory->id, $oldtopcategory->contextid, $newcontext->id, true);
// This function moves subcategories, so we have to start at the top.
question_move_category_to_context($oldtopcategory->id, $oldtopcategory->contextid, $newcontext->id);
@@ -21,6 +21,8 @@ use context_course;
use context_coursecat;
use context_module;
use context_system;
use core_question\local\bank\random_question_loader;
use mod_quiz\quiz_settings;
use stdClass;
use core_question\local\bank\question_version_status;
@@ -159,6 +161,30 @@ final class transfer_question_categories_test extends \advanced_testcase {
quiz_add_quiz_question($question1->id, $quiz, 1);
quiz_add_quiz_question($question2->id, $quiz, 1);
// Create a course with a quiz containing a random question from the system context.
$randomcourse = self::getDataGenerator()->create_course(['shortname' => 'Random']);
$randomquiz = $quizgenerator->create_instance(
[
'course' => $randomcourse->id,
'grade' => 100.0,
'sumgrades' => 2,
'layout' => '1,0',
],
);
$randomquizsettings = quiz_settings::create($randomquiz->id);
$structure = $randomquizsettings->get_structure();
$topcategory = $DB->get_record('question_categories', ['contextid' => $sitecontext->id, 'parent' => 0]);
$filtercondition = [
'filter' => [
'category' => [
'jointype' => \core_question\local\bank\condition::JOINTYPE_DEFAULT,
'values' => [$topcategory->id],
'filteroptions' => ['includesubcategories' => true],
],
],
];
$structure->add_random_questions(1, 1, $filtercondition);
// Create a course category and then a question category attached to that context.
$coursecategory = self::getDataGenerator()->create_category();
$this->coursecatcontext = context_coursecat::instance($coursecategory->id);
@@ -275,6 +301,20 @@ final class transfer_question_categories_test extends \advanced_testcase {
$question4 = $questiongenerator->create_question('shortanswer', null, ['category' => $unusedcategory->id]);
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'grade' => 100.0, 'sumgrades' => 2, 'layout' => '1,0']);
quiz_add_quiz_question($question1->id, $quiz, 1);
// The quiz also contains a random question from the used category.
$quizsettings = quiz_settings::create($quiz->id);
$structure = $quizsettings->get_structure();
$filtercondition = [
'filter' => [
'category' => [
'jointype' => \core_question\local\bank\condition::JOINTYPE_DEFAULT,
'values' => [$usedcategory->id],
'filteroptions' => ['includesubcategories' => false],
],
],
];
$structure->add_random_questions(1, 1, $filtercondition);
}
/**
@@ -305,6 +345,24 @@ final class transfer_question_categories_test extends \advanced_testcase {
$this->assertEquals($parentcat->id, $parentcatq->categoryid);
$this->assertEquals($childcat->id, $childcatq->categoryid);
// Make sure the "Random" course has 1 quiz with 1 random question that returns the questions from the system top category.
$randomcourse = $DB->get_record('course', ['shortname' => 'Random']);
$coursemods = get_course_mods($randomcourse->id);
$randomquiz = reset($coursemods);
$randomquizsettings = quiz_settings::create($randomquiz->instance);
$structure = $randomquizsettings->get_structure();
$randomquestionslot = $structure->get_question_in_slot(1);
$this->assertEquals($randomquestionslot->contextid, $sitecontext->id);
$loader = new random_question_loader(new \qubaid_list([]));
$randomquestions = $loader->get_filtered_questions($randomquestionslot->filtercondition['filter']);
$this->assertCount(2, $randomquestions);
$randomq1 = reset($randomquestions);
$randomq2 = end($randomquestions);
$this->assertEquals($parentcatq->id, $randomq1->id);
$this->assertEquals($parentcat->id, $randomq1->category);
$this->assertEquals($childcatq->id, $randomq2->id);
$this->assertEquals($childcat->id, $randomq2->category);
// Make sure that the course category has a question category below 'top'.
$allcoursecatcats = $DB->get_records('question_categories', ['contextid' => $this->coursecatcontext->id], 'id ASC');
$this->assertCount(2, $allcoursecatcats);
@@ -360,6 +418,15 @@ final class transfer_question_categories_test extends \advanced_testcase {
$this->assertCount(2, $this->get_question_data([$unusedcat->id]));
$emptycat = next($questioncats);
$this->assertCount(0, $this->get_question_data([$emptycat->id]));
// The question reference for the random question is using the "used" category, and the site context.
$coursemods = get_course_mods($this->usedunusedcontext->instanceid);
$quiz = reset($coursemods);
$quizsettings = quiz_settings::create($quiz->instance);
$structure = $quizsettings->get_structure();
$randomquestionslot = $structure->get_question_in_slot(2);
$this->assertEquals($this->usedunusedcontext->id, $randomquestionslot->contextid);
$this->assertEquals($usedcat->id, $randomquestionslot->filtercondition['filter']['category']['values'][0]);
}
/**
@@ -410,6 +477,22 @@ final class transfer_question_categories_test extends \advanced_testcase {
$this->assertEquals($topcat->id, $parentcat->parent);
$this->assertEquals($parentcat->id, $childcat->parent);
// The random question should now point to the questions in the site course question bank.
$randomcourse = $DB->get_record('course', ['shortname' => 'Random']);
$coursemods = get_course_mods($randomcourse->id);
$randomquiz = reset($coursemods);
$randomquizsettings = quiz_settings::create($randomquiz->instance);
$structure = $randomquizsettings->get_structure();
$randomquestionslot = $structure->get_question_in_slot(1);
$this->assertEquals($randomquestionslot->contextid, $sitemodcontext->id);
$loader = new random_question_loader(new \qubaid_list([]));
$randomquestions = $loader->get_filtered_questions($randomquestionslot->filtercondition['filter']);
$this->assertCount(2, $randomquestions);
$randomq1 = reset($randomquestions);
$randomq2 = end($randomquestions);
$this->assertEquals($parentcat->id, $randomq1->category);
$this->assertEquals($childcat->id, $randomq2->category);
// Course category context checks.
// Make sure that the course category has no question categories, not even 'top'.
@@ -525,6 +608,19 @@ final class transfer_question_categories_test extends \advanced_testcase {
$this->assertEmpty($this->get_question_data([$usedunusedcats['top']->id]));
$this->assertCount(2, $this->get_question_data([$usedunusedcats['Used Question Cat']->id]));
$this->assertCount(2, $this->get_question_data([$usedunusedcats['Unused Question Cat']->id]));
// The question reference for the random question is using the same category, but the new context.
$modinfo = get_fast_modinfo($this->usedunusedcontext->instanceid);
$quizzes = $modinfo->get_instances_of('quiz');
$quiz = reset($quizzes);
$quizsettings = quiz_settings::create($quiz->instance);
$structure = $quizsettings->get_structure();
$randomquestionslot = $structure->get_question_in_slot(2);
$this->assertEquals($usedunusedqbank->context->id, $randomquestionslot->contextid);
$this->assertEquals(
$usedunusedcats['Used Question Cat']->id,
$randomquestionslot->filtercondition['filter']['category']['values'][0]
);
}
public function test_fix_wrong_parents(): void {