MDL-85334 quiz: Adds record snapshots to slot deleted event

Ensures critical question data is preserved when a quiz slot is deleted.
This is especially useful when the slot-related information is no longer
available. It adds snapshots for question and random question references
to the event when present.
This commit is contained in:
Jose
2025-11-19 10:11:20 +11:00
committed by Jose Pico
parent 982a10e9cf
commit b9f70d1fde
4 changed files with 104 additions and 4 deletions
@@ -32,6 +32,8 @@ namespace mod_quiz\event;
*
* - int quizid: the id of the quiz.
* - int slotnumber: the slot number in quiz.
* - int questionreferenceid: (optional) the question reference id for the slot.
* - int questionsetreferenceid: (optional) the question set reference id for the slot (if added via random cat).
* }
*
* @package mod_quiz
@@ -79,6 +81,10 @@ class slot_deleted extends \core\event\base {
if (!isset($this->other['slotnumber'])) {
throw new \coding_exception('The \'slotnumber\' value must be set in other.');
}
// At least one of questionreferenceid or questionsetreferenceid must be set.
if (!isset($this->other['questionreferenceid']) && !isset($this->other['questionsetreferenceid'])) {
throw new \coding_exception('Either \'questionreferenceid\' or \'questionsetreferenceid\' must be set in other.');
}
}
public static function get_objectid_mapping() {
@@ -88,7 +94,8 @@ class slot_deleted extends \core\event\base {
public static function get_other_mapping() {
$othermapped = [];
$othermapped['quizid'] = ['db' => 'quiz', 'restore' => 'quiz'];
$othermapped['questionreferenceid'] = ['db' => 'question_references', 'restore' => 'question_references'];
$othermapped['questionsetreferenceid'] = ['db' => 'question_set_references', 'restore' => 'question_set_references'];
return $othermapped;
}
}
+17 -2
View File
@@ -1096,7 +1096,9 @@ class structure {
$this->refresh_page_numbers_and_update_db();
$trans->allow_commit();
// Safely extract reference IDs if available.
$questionreferenceid = $questionreference->id ?? null;
$questionsetreferenceid = $questionsetreference->id ?? null;
// Log slot deleted event.
$event = \mod_quiz\event\slot_deleted::create([
@@ -1105,9 +1107,22 @@ class structure {
'other' => [
'quizid' => $this->get_quizid(),
'slotnumber' => $slotnumber,
]
'questionreferenceid' => $questionreferenceid,
'questionsetreferenceid' => $questionsetreferenceid,
],
]);
$event->add_record_snapshot('quiz_slots', $slot);
// Add snapshots for question and random question references when available.
if ($questionreference) {
$event->add_record_snapshot('question_references', $questionreference);
}
if ($questionsetreference) {
$event->add_record_snapshot('question_set_references', $questionsetreference);
}
$event->trigger();
$trans->allow_commit();
}
/**
+23 -1
View File
@@ -1190,7 +1190,8 @@ final class events_test extends \advanced_testcase {
'other' => [
'quizid' => $quizobj->get_quizid(),
'slotnumber' => 1,
]
'questionreferenceid' => 2,
],
];
$event = \mod_quiz\event\slot_deleted::create($params);
@@ -1203,9 +1204,30 @@ final class events_test extends \advanced_testcase {
// Check that the event data is valid.
$this->assertInstanceOf('\mod_quiz\event\slot_deleted', $event);
$this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
$this->assertEquals(2, $event->other['questionreferenceid']);
$this->assertEventContextNotUsed($event);
}
/**
* Test validation failure when neither questionreferenceid nor questionsetreferenceid is provided.
*/
public function test_slot_deleted_no_questionreferenceids(): void {
$quizobj = $this->prepare_quiz();
$params = [
'objectid' => 1,
'context' => \context_module::instance($quizobj->get_cmid()),
'other' => [
'quizid' => $quizobj->get_quizid(),
'slotnumber' => 1,
],
];
$this->expectException(\coding_exception::class);
$this->expectExceptionMessage('Either \'questionreferenceid\' or \'questionsetreferenceid\' must be set in other.');
slot_deleted::create($params);
}
/**
* Test the slot mark updated event.
*
+56
View File
@@ -653,8 +653,36 @@ final class structure_test extends \advanced_testcase {
]);
$structure = structure::create_for_quiz($quizobj);
// Set up event monitoring.
$sink = $this->redirectEvents();
$structure->remove_slot(2);
// Get the event.
$events = $sink->get_events();
$sink->close();
$this->assertCount(1, $events);
$event = reset($events);
// Verify it's the right type of event.
$this->assertInstanceOf(\mod_quiz\event\slot_deleted::class, $event);
// Get the quiz_slot snapshot.
$slotid = $event->objectid;
$quizslotsnapshot = $event->get_record_snapshot('quiz_slots', $slotid);
$this->assertNotNull($quizslotsnapshot);
// Get the snapshot for question_references.
$questionreference = $event->other['questionreferenceid'];
if ($questionreference) {
$qreferencesnapshot = $event->get_record_snapshot('question_references', $questionreference);
$this->assertNotNull($qreferencesnapshot);
}
// Should NOT have the snapshot for question_set_references.
$questionsetreference = $event->other['questionsetreferenceid'];
$this->assertNull($questionsetreference);
$structure = structure::create_for_quiz($quizobj);
$this->assert_quiz_layout([
['TF1', 1, 'truefalse'],
@@ -684,10 +712,38 @@ final class structure_test extends \advanced_testcase {
WHERE qs.quizid = ?
AND qsr.component = ?
AND qsr.questionarea = ?';
$randomq = $DB->get_record_sql($sql, [$quizobj->get_quizid(), 'mod_quiz', 'slot']);
// Set up event monitoring.
$sink = $this->redirectEvents();
$structure->remove_slot(2);
// Get the event.
$events = $sink->get_events();
$sink->close();
$this->assertCount(1, $events);
$event = reset($events);
// Verify it's the right type of event.
$this->assertInstanceOf(\mod_quiz\event\slot_deleted::class, $event);
// Get the quiz_slot snapshot.
$slotid = $event->objectid;
$quizslotsnapshot = $event->get_record_snapshot('quiz_slots', $slotid);
$this->assertNotNull($quizslotsnapshot);
// Should NOT have the snapshot for question_references.
$questionreference = $event->other['questionreferenceid'];
$this->assertNull($questionreference);
// Get the snapshot for question_set_references.
$questionsetreference = $event->other['questionsetreferenceid'];
if ($questionsetreference) {
$qsetreferencesnapshot = $event->get_record_snapshot('question_set_references', $questionsetreference);
$this->assertNotNull($qsetreferencesnapshot);
}
$structure = structure::create_for_quiz($quizobj);
$this->assert_quiz_layout([
['TF1', 1, 'truefalse'],