MDL-82639 mod_quiz: Improve events for random questions
This commit is contained in:
@@ -24,6 +24,9 @@
|
||||
|
||||
namespace mod_quiz\event;
|
||||
|
||||
use core\exception\coding_exception;
|
||||
use core\url;
|
||||
|
||||
/**
|
||||
* The mod_quiz slot created event class.
|
||||
*
|
||||
@@ -51,14 +54,30 @@ class slot_created extends \core\event\base {
|
||||
}
|
||||
|
||||
public function get_description() {
|
||||
return "The user with id '$this->userid' created a new slot with id '{$this->objectid}' " .
|
||||
"and slot number '{$this->other['slotnumber']}' " .
|
||||
"on page '{$this->other['page']}' " .
|
||||
"of the quiz with course module id '$this->contextinstanceid'.";
|
||||
|
||||
if (isset($this->other['questionbankentryid'])) {
|
||||
$version = $this->other['version'] ?? 'Always latest';
|
||||
return "The user with id '$this->userid' created a new slot with " .
|
||||
"id '{$this->objectid}', " .
|
||||
"slot number '{$this->other['slotnumber']}', and " .
|
||||
"question bank entry id '{$this->other['questionbankentryid']}' (version '$version') " .
|
||||
"on page '{$this->other['page']}' " .
|
||||
"of the quiz with course module id '$this->contextinstanceid'.";
|
||||
}
|
||||
|
||||
if (isset($this->other['questionscontextid'])) {
|
||||
return "The user with id '$this->userid' created a new slot using question references with " .
|
||||
"id '{$this->objectid}', " .
|
||||
"slot number '{$this->other['slotnumber']}', " .
|
||||
"question context '{$this->other['questionscontextid']}', and " .
|
||||
"filter condition '{$this->other['filtercondition']}' " .
|
||||
"on page '{$this->other['page']}' " .
|
||||
"of the quiz with course module id '$this->contextinstanceid'.";
|
||||
}
|
||||
}
|
||||
|
||||
public function get_url() {
|
||||
return new \moodle_url('/mod/quiz/edit.php', [
|
||||
return new url('/mod/quiz/edit.php', [
|
||||
'cmid' => $this->contextinstanceid
|
||||
]);
|
||||
}
|
||||
@@ -66,24 +85,63 @@ class slot_created extends \core\event\base {
|
||||
protected function validate_data() {
|
||||
parent::validate_data();
|
||||
|
||||
$errors = [];
|
||||
|
||||
if (!isset($this->objectid)) {
|
||||
throw new \coding_exception('The \'objectid\' value must be set.');
|
||||
$errors[] = "The 'objectid' value must be set.";
|
||||
}
|
||||
|
||||
if (!isset($this->contextinstanceid)) {
|
||||
throw new \coding_exception('The \'contextinstanceid\' value must be set.');
|
||||
$errors[] = "The 'contextinstanceid' value must be set.";
|
||||
}
|
||||
|
||||
if (!isset($this->other['quizid'])) {
|
||||
throw new \coding_exception('The \'quizid\' value must be set in other.');
|
||||
$errors[] = "The 'quizid' value must be set in other.";
|
||||
}
|
||||
|
||||
if (!isset($this->other['slotnumber'])) {
|
||||
throw new \coding_exception('The \'slotnumber\' value must be set in other.');
|
||||
$errors[] = "The 'slotnumber' value must be set in other.";
|
||||
}
|
||||
|
||||
if (!isset($this->other['page'])) {
|
||||
throw new \coding_exception('The \'page\' value must be set in other.');
|
||||
$errors[] = "The 'page' value must be set in other.";
|
||||
}
|
||||
|
||||
$questionbankfields = ['questionbankentryid', 'version'];
|
||||
$referencefields = ['questionscontextid', 'filtercondition'];
|
||||
$questionbankset = isset($this->other['questionbankentryid']) || array_key_exists('version', $this->other);
|
||||
$referenceset = isset($this->other['questionscontextid']) || isset($this->other['filtercondition']);
|
||||
|
||||
if ($questionbankset && $referenceset) {
|
||||
$errors[] = "Values for exactly one of these field sets must be set in 'other': " .
|
||||
'(' . implode(', ', $questionbankfields) . '), ' .
|
||||
'(' . implode(', ', $referencefields) . ')';
|
||||
} else if (!$questionbankset && !$referenceset) {
|
||||
$errors[] = "Values for exactly one of these field sets must be set in 'other': " .
|
||||
'(' . implode(', ', $questionbankfields) . '), ' .
|
||||
'(' . implode(', ', $referencefields) . ')';
|
||||
}
|
||||
|
||||
if ($questionbankset) {
|
||||
if (!isset($this->other['questionbankentryid'])) {
|
||||
$errors[] = "The 'questionbankentryid' value must be set in other.";
|
||||
}
|
||||
if (!array_key_exists('version', $this->other)) {
|
||||
$errors[] = "The 'version' value must be set in other.";
|
||||
}
|
||||
}
|
||||
|
||||
if ($referenceset) {
|
||||
if (!isset($this->other['questionscontextid'])) {
|
||||
$errors[] = "The 'questionscontextid' value must be set in other.";
|
||||
}
|
||||
if (!isset($this->other['filtercondition'])) {
|
||||
$errors[] = "The 'filtercondition' value must be set in other.";
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
throw new coding_exception("Errors in event data:\n\n" . implode("\n", $errors));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mod_quiz\event;
|
||||
|
||||
use core\event\base;
|
||||
use core\exception\coding_exception;
|
||||
use core\url;
|
||||
|
||||
/**
|
||||
* This event is fired when the filter condition of a slot
|
||||
* using the question set references table is updated.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2024 Catalyst IT Australia Pty Ltd
|
||||
* @author Cameron Ball <[email protected]>
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class slot_filtercondition_updated extends base {
|
||||
#[\Override]
|
||||
protected function init() {
|
||||
$this->data['objecttable'] = 'quiz_slots';
|
||||
$this->data['crud'] = 'c';
|
||||
$this->data['edulevel'] = self::LEVEL_TEACHING;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function get_name() {
|
||||
return get_string('eventslotfilterconditionupdated', 'mod_quiz');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_description() {
|
||||
return "The user with id '$this->userid' updated a slot using question references with " .
|
||||
"id '{$this->objectid}' and " .
|
||||
"slot number '{$this->other['slotnumber']}' " .
|
||||
"on page '{$this->other['page']}' " .
|
||||
"of the quiz with course module id '$this->contextinstanceid' " .
|
||||
"to use question context '{$this->other['questionscontextid']}' and " .
|
||||
"filter condition '{$this->other['filtercondition']}'.";
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_url() {
|
||||
return new url('/mod/quiz/edit.php', ['cmid' => $this->contextinstanceid]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function validate_data() {
|
||||
$missing = fn(string $field) => !isset($this->other[$field]);
|
||||
$missingmembers = array_filter(['objectid'], fn(string $member): bool => !isset($this->$member));
|
||||
$missingfields = array_filter(['quizid', 'slotnumber', 'page', 'questionscontextid', 'filtercondition'], $missing);
|
||||
parent::validate_data();
|
||||
|
||||
$errors = [
|
||||
...array_map(fn(string $member): string => "The '$member' value must be set.", $missingmembers),
|
||||
...array_map(fn(string $field): string => "The '$field' value must be set in other.", $missingfields),
|
||||
];
|
||||
|
||||
if ($errors) {
|
||||
throw new coding_exception("Errors in event data:\n\n" . implode("\n", $errors));
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function get_objectid_mapping() {
|
||||
return ['db' => 'quiz_slots', 'restore' => 'quiz_question_instance'];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function get_other_mapping() {
|
||||
$othermapped = [];
|
||||
$othermapped['quizid'] = ['db' => 'quiz', 'restore' => 'quiz'];
|
||||
|
||||
return $othermapped;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
namespace mod_quiz\local\structure;
|
||||
|
||||
use context_module;
|
||||
use core\context\module;
|
||||
use core\exception\coding_exception;
|
||||
use mod_quiz\event\slot_created;
|
||||
use mod_quiz\event\slot_filtercondition_updated;
|
||||
|
||||
/**
|
||||
* Class slot_random, represents a random question slot type.
|
||||
@@ -196,20 +199,21 @@ class slot_random {
|
||||
$this->referencerecord->filtercondition = $this->filtercondition;
|
||||
$DB->insert_record('question_set_references', $this->referencerecord);
|
||||
|
||||
$trans->allow_commit();
|
||||
|
||||
// Log slot created event.
|
||||
$cm = get_coursemodule_from_instance('quiz', $quiz->id);
|
||||
$event = \mod_quiz\event\slot_created::create([
|
||||
'context' => context_module::instance($cm->id),
|
||||
slot_created::create([
|
||||
'context' => module::instance($cm->id),
|
||||
'objectid' => $this->record->id,
|
||||
'other' => [
|
||||
'quizid' => $quiz->id,
|
||||
'slotnumber' => $this->record->slot,
|
||||
'page' => $this->record->page
|
||||
]
|
||||
]);
|
||||
$event->trigger();
|
||||
'page' => $this->record->page,
|
||||
'questionscontextid' => $this->referencerecord->questionscontextid,
|
||||
'filtercondition' => $this->referencerecord->filtercondition,
|
||||
],
|
||||
])->trigger();
|
||||
|
||||
$trans->allow_commit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,20 +225,35 @@ class slot_random {
|
||||
global $DB;
|
||||
|
||||
if (!isset($this->record->id)) {
|
||||
throw new \coding_exception('Cannot update filtercondition without slot record ID.');
|
||||
throw new coding_exception('Cannot update filtercondition without slot record ID.');
|
||||
}
|
||||
|
||||
$setreferenceid = $DB->get_field('question_set_references', 'id', [
|
||||
$cm = get_coursemodule_from_instance('quiz', $this->get_quiz()->id);
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$filterconditionjson = json_encode($filtercondition);
|
||||
$params = [
|
||||
'component' => 'mod_quiz',
|
||||
'questionarea' => 'slot',
|
||||
'itemid' => $this->record->id,
|
||||
], MUST_EXIST);
|
||||
|
||||
$update = (object)[
|
||||
'id' => $setreferenceid,
|
||||
'filtercondition' => json_encode($filtercondition),
|
||||
'questionscontextid' => $this->referencerecord->questionscontextid,
|
||||
'usingcontextid' => $this->referencerecord->usingcontextid ?? module::instance($cm->id)->id,
|
||||
];
|
||||
$DB->update_record('question_set_references', $update);
|
||||
$setreferenceid = $DB->get_field('question_set_references', 'id', $params, MUST_EXIST);
|
||||
$DB->update_record('question_set_references', (object)[
|
||||
'id' => $setreferenceid,
|
||||
'filtercondition' => $filterconditionjson,
|
||||
'questionscontextid' => $this->referencerecord->questionscontextid,
|
||||
]);
|
||||
slot_filtercondition_updated::create([
|
||||
'context' => module::instance($cm->id),
|
||||
'objectid' => $this->record->id,
|
||||
'other' => [
|
||||
'quizid' => $this->get_quiz()->id,
|
||||
'slotnumber' => $this->record->slot,
|
||||
'page' => $this->record->page,
|
||||
'questionscontextid' => $this->referencerecord->questionscontextid,
|
||||
'filtercondition' => $filterconditionjson,
|
||||
],
|
||||
])->trigger();
|
||||
$transaction->allow_commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,6 +395,7 @@ $string['eventsectionshuffleupdated'] = 'Section shuffle updated';
|
||||
$string['eventslotcreated'] = 'Slot created';
|
||||
$string['eventslotdeleted'] = 'Slot deleted';
|
||||
$string['eventslotdisplayedquestionnumberupdated'] = 'Slot displayed question number updated';
|
||||
$string['eventslotfilterconditionupdated'] = 'Slot filter condition updated';
|
||||
$string['eventslotgradeitemupdated'] = 'Slot grade item updated';
|
||||
$string['eventslotmarkupdated'] = 'Slot mark updated';
|
||||
$string['eventslotversionupdated'] = 'Slot version updated';
|
||||
|
||||
@@ -1885,8 +1885,6 @@ function quiz_add_quiz_question($questionid, $quiz, $page = 0, $maxmark = null)
|
||||
$DB->insert_record('question_references', $questionreferences);
|
||||
}
|
||||
|
||||
$trans->allow_commit();
|
||||
|
||||
// Log slot created event.
|
||||
$cm = get_coursemodule_from_instance('quiz', $quiz->id);
|
||||
$event = \mod_quiz\event\slot_created::create([
|
||||
@@ -1895,10 +1893,14 @@ function quiz_add_quiz_question($questionid, $quiz, $page = 0, $maxmark = null)
|
||||
'other' => [
|
||||
'quizid' => $quiz->id,
|
||||
'slotnumber' => $slot->slot,
|
||||
'page' => $slot->page
|
||||
'page' => $slot->page,
|
||||
'questionbankentryid' => $questionreferences->questionbankentryid,
|
||||
'version' => $questionreferences->version,
|
||||
]
|
||||
]);
|
||||
$event->trigger();
|
||||
|
||||
$trans->allow_commit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace mod_quiz\event;
|
||||
use mod_quiz\quiz_attempt;
|
||||
use mod_quiz\quiz_settings;
|
||||
use context_module;
|
||||
use core_question\local\bank\condition;
|
||||
use mod_quiz\external\submit_question_version;
|
||||
|
||||
/**
|
||||
@@ -1158,8 +1159,10 @@ final class events_test extends \advanced_testcase {
|
||||
'other' => [
|
||||
'quizid' => $quizobj->get_quizid(),
|
||||
'slotnumber' => 1,
|
||||
'page' => 1
|
||||
]
|
||||
'page' => 1,
|
||||
'questionbankentryid' => 1,
|
||||
'version' => null,
|
||||
],
|
||||
];
|
||||
$event = \mod_quiz\event\slot_created::create($params);
|
||||
|
||||
@@ -1175,6 +1178,61 @@ final class events_test extends \advanced_testcase {
|
||||
$this->assertEventContextNotUsed($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify slot_created carries question reference information when a specific question is added.
|
||||
*/
|
||||
public function test_slot_created_for_question_reference(): void {
|
||||
$quizobj = $this->prepare_quiz();
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$category = $questiongenerator->create_question_category();
|
||||
$question = $questiongenerator->create_question('shortanswer', null, ['category' => $category->id]);
|
||||
|
||||
$sink = $this->redirectEvents();
|
||||
quiz_add_quiz_question($question->id, $quizobj->get_quiz());
|
||||
$events = array_filter($sink->get_events(), fn(\core\event\base $event): bool => $event instanceof slot_created);
|
||||
|
||||
$this->assertNotEmpty($events);
|
||||
$event = reset($events);
|
||||
$this->assertArrayHasKey('questionbankentryid', $event->other);
|
||||
$this->assertArrayHasKey('version', $event->other);
|
||||
$this->assertArrayNotHasKey('questionscontextid', $event->other);
|
||||
$this->assertArrayNotHasKey('filtercondition', $event->other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify slot_created carries question set reference information for random slots.
|
||||
*/
|
||||
public function test_slot_created_for_random_question(): void {
|
||||
$quizobj = $this->prepare_quiz();
|
||||
$this->setAdminUser();
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$category = $questiongenerator->create_question_category();
|
||||
$questiongenerator->create_question('shortanswer', null, ['category' => $category->id]);
|
||||
$questiongenerator->create_question('shortanswer', null, ['category' => $category->id]);
|
||||
|
||||
$filtercondition = [
|
||||
'filter' => [
|
||||
'category' => [
|
||||
'jointype' => condition::JOINTYPE_DEFAULT,
|
||||
'values' => [$category->id],
|
||||
'filteroptions' => ['includesubcategories' => false],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$sink = $this->redirectEvents();
|
||||
$quizobj->get_structure()->add_random_questions(1, 1, $filtercondition);
|
||||
$events = array_filter($sink->get_events(), fn(\core\event\base $event): bool => $event instanceof slot_created);
|
||||
|
||||
$this->assertNotEmpty($events);
|
||||
$event = reset($events);
|
||||
$this->assertArrayHasKey('questionscontextid', $event->other);
|
||||
$this->assertArrayHasKey('filtercondition', $event->other);
|
||||
$this->assertArrayNotHasKey('questionbankentryid', $event->other);
|
||||
$this->assertArrayNotHasKey('version', $event->other);
|
||||
$this->assertEquals(json_encode($filtercondition), $event->other['filtercondition']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the slot deleted event.
|
||||
*
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace mod_quiz\external;
|
||||
|
||||
use advanced_testcase;
|
||||
use core_question\local\bank\condition;
|
||||
use mod_quiz\event\slot_filtercondition_updated;
|
||||
use mod_quiz\quiz_settings;
|
||||
|
||||
/**
|
||||
@@ -115,7 +116,24 @@ final class update_filter_condition_test extends advanced_testcase {
|
||||
|
||||
// Call the webservice execute method.
|
||||
$filtercondition['filter']['category']['values'] = [$questioncategory2->id];
|
||||
update_filter_condition::execute($cm->id, $qsetref->itemid, json_encode($filtercondition));
|
||||
$slot = $DB->get_record('quiz_slots', ['id' => $qsetref->itemid], '*', MUST_EXIST);
|
||||
$filterjson = json_encode($filtercondition);
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
update_filter_condition::execute($cm->id, $qsetref->itemid, $filterjson);
|
||||
$events = array_filter(
|
||||
$sink->get_events(),
|
||||
fn(\core\event\base $event): bool => $event instanceof slot_filtercondition_updated
|
||||
);
|
||||
$this->assertCount(1, $events);
|
||||
|
||||
$event = reset($events);
|
||||
$this->assertEquals($slot->id, $event->objectid);
|
||||
$this->assertEquals($quizcontext, $event->get_context());
|
||||
$this->assertEquals($slot->slot, $event->other['slotnumber']);
|
||||
$this->assertEquals($slot->page, $event->other['page']);
|
||||
$this->assertEquals($questioncategory2->contextid, $event->other['questionscontextid']);
|
||||
$this->assertEquals($filterjson, $event->other['filtercondition']);
|
||||
|
||||
// Check that the questionscontextid value has been updated.
|
||||
$qsetref = $DB->get_record('question_set_references', [
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2025100600;
|
||||
$plugin->version = 2025121300;
|
||||
$plugin->requires = 2025092600;
|
||||
$plugin->component = 'mod_quiz';
|
||||
|
||||
Reference in New Issue
Block a user