diff --git a/public/mod/quiz/classes/event/slot_deleted.php b/public/mod/quiz/classes/event/slot_deleted.php index 08e8f2f6719..d555163b1fb 100644 --- a/public/mod/quiz/classes/event/slot_deleted.php +++ b/public/mod/quiz/classes/event/slot_deleted.php @@ -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; } } diff --git a/public/mod/quiz/classes/structure.php b/public/mod/quiz/classes/structure.php index 2dfcaa6b28a..24605af767d 100644 --- a/public/mod/quiz/classes/structure.php +++ b/public/mod/quiz/classes/structure.php @@ -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(); } /** diff --git a/public/mod/quiz/tests/event/events_test.php b/public/mod/quiz/tests/event/events_test.php index 8cd37eff509..adbc95cdd00 100644 --- a/public/mod/quiz/tests/event/events_test.php +++ b/public/mod/quiz/tests/event/events_test.php @@ -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. * diff --git a/public/mod/quiz/tests/structure_test.php b/public/mod/quiz/tests/structure_test.php index c32bbc2aa3f..3e258f0de05 100644 --- a/public/mod/quiz/tests/structure_test.php +++ b/public/mod/quiz/tests/structure_test.php @@ -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'],