MDL-85018 qtype_multianswer: Prevent errors on backup/restore

If a multianswer question has its question_multianswer record missing
for some reason, attempting to backup and restore a bank containing the
question will result a dml_missing_record exception.

The qtype_multianswer::get_question_options() method already copes with
the sequence from this record being empty, so we can just remove the
MUST_EXIST check and let it fall back to that.
This commit is contained in:
Mark Johnson
2025-08-11 10:37:05 +01:00
parent fb02f4fa9f
commit 9cd0fe59a3
2 changed files with 98 additions and 2 deletions
+1 -2
View File
@@ -110,8 +110,7 @@ class qtype_multianswer extends question_type {
parent::get_question_options($question);
// Get relevant data indexed by positionkey from the multianswers table.
$sequence = $DB->get_field('question_multianswer', 'sequence',
array('question' => $question->id), MUST_EXIST);
$sequence = $DB->get_field('question_multianswer', 'sequence', ['question' => $question->id]);
if (empty($sequence)) {
$question->options->questions = [];
@@ -0,0 +1,97 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qtype_multianswer;
/**
* Unit tests for
*
* @package qtype_multianswer
* @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \restore_qtype_multianswer_plugin
*/
final class restore_test extends \advanced_testcase {
/**
* Duplicate a quiz containing a multianswer question with no multianswer record.
*/
public function test_restore_quiz_with_edited_questions(): void {
global $CFG, $DB, $USER;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
$this->resetAfterTest();
$this->setAdminUser();
// Create a course and a user with editing teacher capabilities.
$generator = $this->getDataGenerator();
$course1 = $generator->create_course();
$context = \context_course::instance($course1->id);
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$initialcount = $DB->count_records('question');
// Create a question category.
$cat = $questiongenerator->create_question_category(['contextid' => $context->id]);
// Create a quiz containing a multianswer question from the qbank.
$quiz = $this->getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(['course' => $course1->id]);
$question = $questiongenerator->create_question('multianswer', 'twosubq', ['category' => $cat->id]);
quiz_add_quiz_question($question->id, $quiz);
// Delete the multianswer record.
$DB->delete_records('question_multianswer', ['question' => $question->id]);
// Confirm we have created 3 additional questions (one parent, 2 children).
$this->assertEquals($initialcount + 3, $DB->count_records('question'));
// Backup quiz.
$bc = new \backup_controller(
\backup::TYPE_1ACTIVITY,
$quiz->cmid,
\backup::FORMAT_MOODLE,
\backup::INTERACTIVE_NO,
\backup::MODE_IMPORT,
$USER->id,
);
$backupid = $bc->get_backupid();
$bc->execute_plan();
$bc->destroy();
// Restore the backup into the same course.
$rc = new \restore_controller(
$backupid,
$course1->id,
\backup::INTERACTIVE_NO,
\backup::MODE_IMPORT,
$USER->id,
\backup::TARGET_CURRENT_ADDING,
);
$rc->execute_precheck();
$rc->execute_plan();
$rc->destroy();
// Both quizzes should refer to the same original question.
$quizzes = get_fast_modinfo($course1->id)->get_instances_of('quiz');
$this->assertCount(2, $quizzes);
foreach ($quizzes as $quiz) {
$structure = \mod_quiz\question\bank\qbank_helper::get_question_structure($quiz->instance, $quiz->context);
$this->assertEquals($structure[1]->questionid, $question->id);
}
// There should be no additional questions created during the restore.
$this->assertEquals($initialcount + 3, $DB->count_records('question'));
}
}