mirror of
https://github.com/moodle/moodle.git
synced 2025-04-16 05:54:19 +02:00
MDL-80099 quiz: Add attempt_state_changed hook
This creates a new hook which is dispatched when a quiz attempt is set to a new state, or deleted. This is then used by quiz_statistics to trigger a recalulation, replacing the old event observer (for submissions) and class callback (for deletions).
This commit is contained in:
parent
9a2f82a709
commit
ee952d6556
71
mod/quiz/classes/hook/attempt_state_changed.php
Normal file
71
mod/quiz/classes/hook/attempt_state_changed.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?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 mod_quiz\hook;
|
||||
|
||||
use core\attribute;
|
||||
|
||||
/**
|
||||
* A quiz attempt changed state.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[attribute\label('A quiz attempt changed state.')]
|
||||
#[attribute\tags('quiz', 'attempt')]
|
||||
#[attribute\hook\replaces_callbacks('quiz_attempt_deleted::callback')]
|
||||
class attempt_state_changed {
|
||||
/**
|
||||
* Create a new hook instance.
|
||||
*
|
||||
* @param ?\stdClass $originalattempt The original database record for the attempt, null if it has just been created.
|
||||
* @param ?\stdClass $updatedattempt The updated database record of the new attempt, null if it has just been deleted.
|
||||
*/
|
||||
public function __construct(
|
||||
protected ?\stdClass $originalattempt,
|
||||
protected ?\stdClass $updatedattempt,
|
||||
) {
|
||||
if (is_null($this->originalattempt) && is_null($this->updatedattempt)) {
|
||||
throw new \InvalidArgumentException('originalattempt and updatedattempt cannot both be null.');
|
||||
}
|
||||
if (
|
||||
!is_null($this->originalattempt)
|
||||
&& !is_null($this->updatedattempt)
|
||||
&& $this->originalattempt->id != $this->updatedattempt->id
|
||||
) {
|
||||
throw new \InvalidArgumentException('originalattempt and updatedattempt must have the same id.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original attempt, null if it has just been created.
|
||||
*
|
||||
* @return ?\stdClass
|
||||
*/
|
||||
public function get_original_attempt(): ?\stdClass {
|
||||
return $this->originalattempt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the updated attempt, null if it has just been deleted.
|
||||
*
|
||||
* @return ?\stdClass
|
||||
*/
|
||||
public function get_updated_attempt(): ?\stdClass {
|
||||
return $this->updatedattempt;
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ use coding_exception;
|
||||
use context_module;
|
||||
use Exception;
|
||||
use html_writer;
|
||||
use mod_quiz\hook\attempt_state_changed;
|
||||
use mod_quiz\output\links_to_other_attempts;
|
||||
use mod_quiz\output\renderer;
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
@ -1763,6 +1764,8 @@ class quiz_attempt {
|
||||
|
||||
question_engine::save_questions_usage_by_activity($this->quba);
|
||||
|
||||
$originalattempt = clone $this->attempt;
|
||||
|
||||
$this->attempt->timemodified = $timestamp;
|
||||
$this->attempt->timefinish = $timefinish ?? $timestamp;
|
||||
$this->attempt->sumgrades = $this->quba->get_total_mark();
|
||||
@ -1784,6 +1787,7 @@ class quiz_attempt {
|
||||
// Trigger event.
|
||||
$this->fire_state_transition_event('\mod_quiz\event\attempt_submitted', $timestamp, $studentisonline);
|
||||
|
||||
\core\hook\manager::get_instance()->dispatch(new attempt_state_changed($originalattempt, $this->attempt));
|
||||
// Tell any access rules that care that the attempt is over.
|
||||
$this->get_access_manager($timestamp)->current_attempt_finished();
|
||||
}
|
||||
@ -1820,6 +1824,7 @@ class quiz_attempt {
|
||||
public function process_going_overdue($timestamp, $studentisonline) {
|
||||
global $DB;
|
||||
|
||||
$originalattempt = clone $this->attempt;
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$this->attempt->timemodified = $timestamp;
|
||||
$this->attempt->state = self::OVERDUE;
|
||||
@ -1830,6 +1835,7 @@ class quiz_attempt {
|
||||
|
||||
$this->fire_state_transition_event('\mod_quiz\event\attempt_becameoverdue', $timestamp, $studentisonline);
|
||||
|
||||
\core\hook\manager::get_instance()->dispatch(new attempt_state_changed($originalattempt, $this->attempt));
|
||||
$transaction->allow_commit();
|
||||
|
||||
quiz_send_overdue_message($this);
|
||||
@ -1844,6 +1850,7 @@ class quiz_attempt {
|
||||
public function process_abandon($timestamp, $studentisonline) {
|
||||
global $DB;
|
||||
|
||||
$originalattempt = clone $this->attempt;
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$this->attempt->timemodified = $timestamp;
|
||||
$this->attempt->state = self::ABANDONED;
|
||||
@ -1852,6 +1859,8 @@ class quiz_attempt {
|
||||
|
||||
$this->fire_state_transition_event('\mod_quiz\event\attempt_abandoned', $timestamp, $studentisonline);
|
||||
|
||||
\core\hook\manager::get_instance()->dispatch(new attempt_state_changed($originalattempt, $this->attempt));
|
||||
|
||||
$transaction->allow_commit();
|
||||
}
|
||||
|
||||
@ -1872,6 +1881,7 @@ class quiz_attempt {
|
||||
throw new coding_exception('Can only reopen an attempt that was never submitted.');
|
||||
}
|
||||
|
||||
$originalattempt = clone $this->attempt;
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$this->attempt->timemodified = $timestamp;
|
||||
$this->attempt->state = self::IN_PROGRESS;
|
||||
@ -1880,6 +1890,7 @@ class quiz_attempt {
|
||||
|
||||
$this->fire_state_transition_event('\mod_quiz\event\attempt_reopened', $timestamp, false);
|
||||
|
||||
\core\hook\manager::get_instance()->dispatch(new attempt_state_changed($originalattempt, $this->attempt));
|
||||
$timeclose = $this->get_access_manager($timestamp)->get_end_time($this->attempt);
|
||||
if ($timeclose && $timestamp > $timeclose) {
|
||||
$this->process_finish($timestamp, false, $timeclose);
|
||||
|
@ -39,6 +39,7 @@ use core_question\local\bank\condition;
|
||||
use mod_quiz\access_manager;
|
||||
use mod_quiz\event\attempt_submitted;
|
||||
use mod_quiz\grade_calculator;
|
||||
use mod_quiz\hook\attempt_state_changed;
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
use mod_quiz\question\display_options;
|
||||
use mod_quiz\quiz_attempt;
|
||||
@ -145,6 +146,8 @@ function quiz_create_attempt(quiz_settings $quizobj, $attemptnumber, $lastattemp
|
||||
$attempt->timecheckstate = $timeclose;
|
||||
}
|
||||
|
||||
\core\hook\manager::get_instance()->dispatch(new attempt_state_changed(null, $attempt));
|
||||
|
||||
return $attempt;
|
||||
}
|
||||
/**
|
||||
@ -448,10 +451,14 @@ function quiz_delete_attempt($attempt, $quiz) {
|
||||
$event->add_record_snapshot('quiz_attempts', $attempt);
|
||||
$event->trigger();
|
||||
|
||||
// This class callback is deprecated, and will be removed in Moodle 4.8 (MDL-80327).
|
||||
// Use the attempt_state_changed hook instead.
|
||||
$callbackclasses = \core_component::get_plugin_list_with_class('quiz', 'quiz_attempt_deleted');
|
||||
foreach ($callbackclasses as $callbackclass) {
|
||||
component_class_callback($callbackclass, 'callback', [$quiz->id]);
|
||||
component_class_callback($callbackclass, 'callback', [$quiz->id], null, true);
|
||||
}
|
||||
|
||||
\core\hook\manager::get_instance()->dispatch(new attempt_state_changed($attempt, null));
|
||||
}
|
||||
|
||||
// Search quiz_attempts for other instances by this user.
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
namespace quiz_statistics\event\observer;
|
||||
|
||||
use core\check\performance\debugging;
|
||||
use quiz_statistics\task\recalculate;
|
||||
|
||||
/**
|
||||
@ -25,6 +26,8 @@ use quiz_statistics\task\recalculate;
|
||||
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @deprecated Since Moodle 4.4 MDL-80099.
|
||||
* @todo Final deprecation in Moodle 4.8 MDL-80956.
|
||||
*/
|
||||
class attempt_submitted {
|
||||
/**
|
||||
@ -35,8 +38,11 @@ class attempt_submitted {
|
||||
*
|
||||
* @param \mod_quiz\event\attempt_submitted $event
|
||||
* @return void
|
||||
* @deprecated Since Moodle 4.4 MDL-80099
|
||||
*/
|
||||
public static function process(\mod_quiz\event\attempt_submitted $event): void {
|
||||
debugging('quiz_statistics\event\observer\attempt_submitted event observer has been deprecated in favour of ' .
|
||||
'the quiz_statistics\hook_callbacks::quiz_attempt_submitted_or_deleted hook callback.', DEBUG_DEVELOPER);
|
||||
$data = $event->get_data();
|
||||
recalculate::queue_future_run($data['other']['quizid']);
|
||||
}
|
||||
|
@ -16,7 +16,10 @@
|
||||
namespace quiz_statistics;
|
||||
|
||||
use core\dml\sql_join;
|
||||
use mod_quiz\hook\attempt_state_changed;
|
||||
use mod_quiz\hook\structure_modified;
|
||||
use mod_quiz\quiz_attempt;
|
||||
use quiz_statistics\task\recalculate;
|
||||
|
||||
/**
|
||||
* Hook callbacks
|
||||
@ -47,4 +50,20 @@ class hook_callbacks {
|
||||
$report = new \quiz_statistics_report();
|
||||
$report->clear_cached_data($qubaids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a statistics recalculation when an attempt is submitted or deleting.
|
||||
*
|
||||
* @param attempt_state_changed $hook
|
||||
* @return bool True if a task was queued.
|
||||
*/
|
||||
public static function quiz_attempt_submitted_or_deleted(attempt_state_changed $hook): bool {
|
||||
$originalattempt = $hook->get_original_attempt();
|
||||
$updatedattempt = $hook->get_updated_attempt();
|
||||
if (is_null($updatedattempt) || $updatedattempt->state === quiz_attempt::FINISHED) {
|
||||
// Only recalculate on deletion or submission.
|
||||
return recalculate::queue_future_run($originalattempt->quiz);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ use quiz_statistics\task\recalculate;
|
||||
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @deprecated Since Moodle 4.4 MDL-80099.
|
||||
* @todo Final deprecation in Moodle 4.8 MDL-80956.
|
||||
*/
|
||||
class quiz_attempt_deleted {
|
||||
/**
|
||||
@ -32,8 +34,11 @@ class quiz_attempt_deleted {
|
||||
*
|
||||
* @param int $quizid The quiz the attempt belongs to.
|
||||
* @return void
|
||||
* @deprecated Since Moodle 4.4 MDL-80099.
|
||||
*/
|
||||
public static function callback(int $quizid): void {
|
||||
debugging('quiz_statistics\quiz_attempt_deleted callback class has been deprecated in favour of ' .
|
||||
'the quiz_statistics\hook_callbacks::quiz_attempt_submitted_or_deleted hook callback.', DEBUG_DEVELOPER);
|
||||
recalculate::queue_future_run($quizid);
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Add event observers for quiz_statistics
|
||||
*
|
||||
* @package quiz_statistics
|
||||
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$observers = [
|
||||
[
|
||||
'eventname' => '\mod_quiz\event\attempt_submitted',
|
||||
'callback' => '\quiz_statistics\event\observer\attempt_submitted::process',
|
||||
],
|
||||
];
|
@ -29,4 +29,9 @@ $callbacks = [
|
||||
'callback' => quiz_statistics\hook_callbacks::class . '::quiz_structure_modified',
|
||||
'priority' => 500,
|
||||
],
|
||||
[
|
||||
'hook' => mod_quiz\hook\attempt_state_changed::class,
|
||||
'callback' => quiz_statistics\hook_callbacks::class . '::quiz_attempt_submitted_or_deleted',
|
||||
'priority' => 500,
|
||||
],
|
||||
];
|
||||
|
@ -32,7 +32,7 @@ use quiz_statistics\tests\statistics_test_trait;
|
||||
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \quiz_statistics\quiz_attempt_deleted
|
||||
* @covers \quiz_statistics\hook_callbacks::quiz_attempt_submitted_or_deleted
|
||||
*/
|
||||
class quiz_attempt_deleted_test extends \advanced_testcase {
|
||||
use \quiz_question_helper_test_trait;
|
||||
|
@ -13,7 +13,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
namespace quiz_statistics\event\observer;
|
||||
namespace quiz_statistics;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@ -32,13 +32,12 @@ use quiz_statistics\tests\statistics_test_trait;
|
||||
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \quiz_statistics\event\observer\attempt_submitted
|
||||
* @covers \quiz_statistics\hook_callbacks::quiz_attempt_submitted_or_deleted
|
||||
*/
|
||||
class attempt_submitted_test extends \advanced_testcase {
|
||||
class quiz_attempt_submitted_test extends \advanced_testcase {
|
||||
use \quiz_question_helper_test_trait;
|
||||
use statistics_test_trait;
|
||||
|
||||
|
||||
/**
|
||||
* Attempting a quiz should queue the recalculation task for that quiz in 1 hour's time.
|
||||
*
|
@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2023100900;
|
||||
$plugin->version = 2023100901;
|
||||
$plugin->requires = 2023100400;
|
||||
$plugin->component = 'quiz_statistics';
|
||||
|
Loading…
x
Reference in New Issue
Block a user