diff --git a/mod/quiz/classes/cache/overrides.php b/mod/quiz/classes/cache/overrides.php new file mode 100644 index 00000000000..839b3b29554 --- /dev/null +++ b/mod/quiz/classes/cache/overrides.php @@ -0,0 +1,110 @@ +. + +/** + * Cache data source for the quiz overrides. + * + * @package mod_quiz + * @copyright 2021 Shamim Rezaie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +declare(strict_types=1); + +namespace mod_quiz\cache; + +use cache_definition; + +/** + * Class quiz_overrides + * + * @package mod_quiz + * @copyright 2021 Shamim Rezaie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class overrides implements \cache_data_source { + + /** @var overrides the singleton instance of this class. */ + protected static $instance = null; + + /** + * Returns an instance of the data source class that the cache can use for loading data using the other methods + * specified by this interface. + * + * @param cache_definition $definition + * @return object + */ + public static function get_instance_for_cache(cache_definition $definition): overrides { + if (is_null(self::$instance)) { + self::$instance = new overrides(); + } + return self::$instance; + } + + /** + * Loads the data for the key provided ready formatted for caching. + * + * @param string|int $key The key to load. + * @return mixed What ever data should be returned, or false if it can't be loaded. + * @throws \coding_exception + */ + public function load_for_cache($key) { + global $DB; + + [$quizid, $ug, $ugid] = explode('_', $key); + $quizid = (int) $quizid; + + switch ($ug) { + case 'u': + $userid = (int) $ugid; + $override = $DB->get_record( + 'quiz_overrides', + ['quiz' => $quizid, 'userid' => $userid], + 'timeopen, timeclose, timelimit, attempts, password' + ); + break; + case 'g': + $groupid = (int) $ugid; + $override = $DB->get_record( + 'quiz_overrides', + ['quiz' => $quizid, 'groupid' => $groupid], + 'timeopen, timeclose, timelimit, attempts, password' + ); + break; + default: + throw new \coding_exception('Invalid cache key'); + } + + // Return null instead of false, because false will not be cached. + return $override ?: null; + } + + /** + * Loads several keys for the cache. + * + * @param array $keys An array of keys each of which will be string|int. + * @return array An array of matching data items. + */ + public function load_many_for_cache(array $keys) { + $results = []; + + foreach ($keys as $key) { + $results[] = $this->load_for_cache($key); + } + + return $results; + } +} diff --git a/mod/quiz/db/caches.php b/mod/quiz/db/caches.php new file mode 100644 index 00000000000..0b0e541c79c --- /dev/null +++ b/mod/quiz/db/caches.php @@ -0,0 +1,35 @@ +. + +/** + * Defined caches used internally by the plugin. + * + * @package mod_quiz + * @copyright 2021 Shamim Rezaie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +declare(strict_types=1); + +defined('MOODLE_INTERNAL') || die(); + +$definitions = [ + 'overrides' => [ + 'mode' => cache_store::MODE_APPLICATION, + 'simplekeys' => true, + 'datasource' => '\mod_quiz\cache\overrides', + ], +]; diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index 2bb95b02ca8..c5089568ae0 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -131,6 +131,7 @@ $string['browsersecurity_help'] = 'If "Full screen pop-up with some JavaScript s * The quiz will only start if the student has a JavaScript-enabled web-browser * The quiz appears in a full screen popup window that covers all the other windows and has no navigation controls * Students are prevented, as far as is possible, from using facilities like copy and paste'; +$string['cachedef_overrides'] = 'User and group override information'; $string['calculated'] = 'Calculated'; $string['calculatedquestion'] = 'Calculated question not supported at line {$a}. The question will be ignored'; $string['cannotcreatepath'] = 'Path cannot be created ({$a})'; diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index f20bbfd0329..7d5d3afa836 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -232,10 +232,12 @@ function quiz_delete_override($quiz, $overrideid, $log = true) { // Create the search array for a group override. $eventsearcharray = array('modulename' => 'quiz', 'instance' => $quiz->id, 'groupid' => (int)$override->groupid); + $cachekey = "{$quiz->id}_g_{$override->groupid}"; } else { // Create the search array for a user override. $eventsearcharray = array('modulename' => 'quiz', 'instance' => $quiz->id, 'userid' => (int)$override->userid); + $cachekey = "{$quiz->id}_u_{$override->userid}"; } $events = $DB->get_records('event', $eventsearcharray); foreach ($events as $event) { @@ -244,6 +246,7 @@ function quiz_delete_override($quiz, $overrideid, $log = true) { } $DB->delete_records('quiz_overrides', array('id' => $overrideid)); + cache::make('mod_quiz', 'overrides')->delete($cachekey); if ($log) { // Set the common parameters for one of the events we will be triggering. @@ -1536,6 +1539,8 @@ function quiz_reset_userdata($data) { 'error' => false); } + $purgeoverrides = false; + // Remove user overrides. if (!empty($data->reset_quiz_user_overrides)) { $DB->delete_records_select('quiz_overrides', @@ -1544,6 +1549,7 @@ function quiz_reset_userdata($data) { 'component' => $componentstr, 'item' => get_string('useroverridesdeleted', 'quiz'), 'error' => false); + $purgeoverrides = true; } // Remove group overrides. if (!empty($data->reset_quiz_group_overrides)) { @@ -1553,6 +1559,7 @@ function quiz_reset_userdata($data) { 'component' => $componentstr, 'item' => get_string('groupoverridesdeleted', 'quiz'), 'error' => false); + $purgeoverrides = true; } // Updating dates - shift may be negative too. @@ -1566,6 +1573,8 @@ function quiz_reset_userdata($data) { WHERE quiz IN (SELECT id FROM {quiz} WHERE course = ?) AND timeclose <> 0", array($data->timeshift, $data->courseid)); + $purgeoverrides = true; + // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. // See MDL-9367. shift_course_mod_dates('quiz', array('timeopen', 'timeclose'), @@ -1577,6 +1586,10 @@ function quiz_reset_userdata($data) { 'error' => false); } + if ($purgeoverrides) { + cache::make('mod_quiz', 'overrides')->purge(); + } + return $status; } diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index c78df8220ca..9cda10953f1 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -1923,7 +1923,7 @@ function quiz_process_group_deleted_in_course($courseid) { // It would be nice if we got the groupid that was deleted. // Instead, we just update all quizzes with orphaned group overrides. - $sql = "SELECT o.id, o.quiz + $sql = "SELECT o.id, o.quiz, o.groupid FROM {quiz_overrides} o JOIN {quiz} quiz ON quiz.id = o.quiz LEFT JOIN {groups} grp ON grp.id = o.groupid @@ -1931,12 +1931,16 @@ function quiz_process_group_deleted_in_course($courseid) { AND o.groupid IS NOT NULL AND grp.id IS NULL"; $params = array('courseid' => $courseid); - $records = $DB->get_records_sql_menu($sql, $params); + $records = $DB->get_records_sql($sql, $params); if (!$records) { return; // Nothing to do. } $DB->delete_records_list('quiz_overrides', 'id', array_keys($records)); - quiz_update_open_attempts(array('quizid' => array_unique(array_values($records)))); + $cache = cache::make('mod_quiz', 'overrides'); + foreach ($records as $record) { + $cache->delete("{$record->quiz}_g_{$record->groupid}"); + } + quiz_update_open_attempts(['quizid' => array_unique(array_column($records, 'quiz'))]); } /** diff --git a/mod/quiz/overrideedit.php b/mod/quiz/overrideedit.php index e3c01eb9e18..d02bba6d7e8 100644 --- a/mod/quiz/overrideedit.php +++ b/mod/quiz/overrideedit.php @@ -176,6 +176,8 @@ if ($mform->is_cancelled()) { if (!empty($override->id)) { $fromform->id = $override->id; $DB->update_record('quiz_overrides', $fromform); + $cachekey = $groupmode ? "{$fromform->quiz}_g_{$fromform->groupid}" : "{$fromform->quiz}_u_{$fromform->userid}"; + cache::make('mod_quiz', 'overrides')->delete($cachekey); // Determine which override updated event to fire. $params['objectid'] = $override->id; @@ -192,6 +194,8 @@ if ($mform->is_cancelled()) { } else { unset($fromform->id); $fromform->id = $DB->insert_record('quiz_overrides', $fromform); + $cachekey = $groupmode ? "{$fromform->quiz}_g_{$fromform->groupid}" : "{$fromform->quiz}_u_{$fromform->userid}"; + cache::make('mod_quiz', 'overrides')->delete($cachekey); // Determine which override created event to fire. $params['objectid'] = $fromform->id; diff --git a/mod/quiz/version.php b/mod/quiz/version.php index e93456d755a..5e19c6da52d 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020121100; +$plugin->version = 2021042700; $plugin->requires = 2020110300; $plugin->component = 'mod_quiz';