|
1 | 1 | <?php
|
2 |
| -// This file is part of Moodle - http://moodle.org/ |
| 2 | +// This file is part of Moodle - https://moodle.org/ |
3 | 3 | //
|
4 | 4 | // Moodle is free software: you can redistribute it and/or modify
|
5 | 5 | // it under the terms of the GNU General Public License as published by
|
|
12 | 12 | // GNU General Public License for more details.
|
13 | 13 | //
|
14 | 14 | // You should have received a copy of the GNU General Public License
|
15 |
| -// along with Moodle. If not, see <http://www.gnu.org/licenses/>. |
| 15 | +// along with Moodle. If not, see <https://www.gnu.org/licenses/>. |
16 | 16 |
|
17 | 17 | namespace mod_quiz\local;
|
18 | 18 |
|
| 19 | +use cache; |
| 20 | +use core_cache\data_source_interface; |
| 21 | +use core_cache\definition; |
| 22 | + |
19 | 23 | /**
|
20 |
| - * Cache manager for quiz overrides |
| 24 | + * Cache encapsulation for quiz overrides. |
21 | 25 | *
|
22 |
| - * Override cache data is set via its data source, {@see \mod_quiz\cache\overrides} |
23 |
| - * @package mod_quiz |
24 |
| - * @copyright 2024 Matthew Hilton <[email protected]> |
25 |
| - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
| 26 | + * @package mod_quiz |
| 27 | + * @copyright 2025 Catalyst IT Australia Pty Ltd |
| 28 | + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
26 | 29 | */
|
27 |
| -class override_cache { |
28 |
| - /** @var string invalidation event used to purge data when reset_userdata is called, {@see \cache_helper::purge_by_event()} **/ |
29 |
| - public const INVALIDATION_USERDATARESET = 'userdatareset'; |
| 30 | +class override_cache implements data_source_interface { |
| 31 | + /** |
| 32 | + * @var string Invalidation event used to purge data when reset_userdata is called. |
| 33 | + * @see \cache_helper::purge_by_event() |
| 34 | + **/ |
| 35 | + public const INVALIDATION_RESET_USERDATA = 'resetuserdata'; |
| 36 | + |
| 37 | + /** @var ?override_cache The singleton instance for this class. */ |
| 38 | + private static $instance = null; |
30 | 39 |
|
31 | 40 | /**
|
32 |
| - * Create override_cache object and link to quiz |
| 41 | + * Returns the singleton instance of this class. |
33 | 42 | *
|
34 |
| - * @param int $quizid The quiz to link this cache to |
| 43 | + * @param definition $definition The cache definition. |
| 44 | + * @return override_cache The singleton instance. |
35 | 45 | */
|
36 |
| - public function __construct( |
37 |
| - /** @var int $quizid ID of quiz cache is being operated on **/ |
38 |
| - protected readonly int $quizid |
39 |
| - ) { |
| 46 | + public static function get_instance_for_cache(definition $definition): override_cache { |
| 47 | + return self::$instance ??= new override_cache(); |
40 | 48 | }
|
41 | 49 |
|
42 | 50 | /**
|
43 |
| - * Returns the override cache |
| 51 | + * {@inheritdoc} |
| 52 | + * @see \core_cache\data_source_interface::load_for_cache() |
44 | 53 | *
|
45 |
| - * @return \cache |
| 54 | + * @param string|int $key The key to load. |
| 55 | + * @return mixed An array of override records or null if none are found or false for invalidation. |
46 | 56 | */
|
47 |
| - protected function get_cache(): \cache { |
48 |
| - return \cache::make('mod_quiz', 'overrides'); |
| 57 | + public function load_for_cache($key) { |
| 58 | + global $DB; |
| 59 | + |
| 60 | + if ($key === 'lastinvalidation') { |
| 61 | + return false; |
| 62 | + } |
| 63 | + |
| 64 | + [$quizid, $userid] = self::split_cache_key($key); |
| 65 | + |
| 66 | + $subquery = "SELECT g.id |
| 67 | + FROM {groups} g |
| 68 | + JOIN {groups_members} gm ON gm.groupid = g.id |
| 69 | + JOIN {quiz} q ON q.course = g.courseid |
| 70 | + WHERE q.id = :subqueryquizid AND gm.userid = :subqueryuserid"; |
| 71 | + |
| 72 | + $sql = "SELECT * |
| 73 | + FROM {quiz_overrides} |
| 74 | + WHERE quiz = :quizid AND (userid = :userid OR groupid IN ($subquery))"; |
| 75 | + |
| 76 | + $records = $DB->get_records_sql($sql, [ |
| 77 | + 'quizid' => $quizid, |
| 78 | + 'userid' => $userid, |
| 79 | + 'subqueryquizid' => $quizid, |
| 80 | + 'subqueryuserid' => $userid, |
| 81 | + ]); |
| 82 | + |
| 83 | + return empty($records) ? null : $records; |
49 | 84 | }
|
50 | 85 |
|
51 | 86 | /**
|
52 |
| - * Returns group cache key |
| 87 | + * {@inheritdoc} |
| 88 | + * @see \core_cache\data_source_interface::load_many_for_cache() |
53 | 89 | *
|
54 |
| - * @param int $groupid |
55 |
| - * @return string the group cache key |
| 90 | + * @param array $keys An array of keys each of type string. |
| 91 | + * @return array An array of matching overrides. |
56 | 92 | */
|
57 |
| - protected function get_group_cache_key(int $groupid): string { |
58 |
| - return "{$this->quizid}_g_{$groupid}"; |
| 93 | + public function load_many_for_cache(array $keys) { |
| 94 | + $results = []; |
| 95 | + foreach ($keys as $key) { |
| 96 | + $results[$key] = $this->load_for_cache($key); |
| 97 | + } |
| 98 | + return $results; |
59 | 99 | }
|
60 | 100 |
|
61 | 101 | /**
|
62 |
| - * Returns user cache key |
| 102 | + * Get all overrides for a given quiz and user. |
63 | 103 | *
|
64 |
| - * @param int $userid |
65 |
| - * @return string the user cache key |
| 104 | + * @param int $quizid The quiz id. |
| 105 | + * @param int $userid The user id. |
| 106 | + * @return ?array Array of overrides or null if none found. |
66 | 107 | */
|
67 |
| - protected function get_user_cache_key(int $userid): string { |
68 |
| - return "{$this->quizid}_u_{$userid}"; |
| 108 | + public static function get_overrides(int $quizid, int $userid): array|null { |
| 109 | + $cache = self::get_cache(); |
| 110 | + $key = self::get_cache_key($quizid, $userid); |
| 111 | + return $cache->get($key); |
69 | 112 | }
|
70 | 113 |
|
71 | 114 | /**
|
72 |
| - * Returns the override value in the cache for the given group |
73 |
| - * |
74 |
| - * @param int $groupid group to get cached override data for |
75 |
| - * @return ?\stdClass override value in the cache for the given group, or null if there is none. |
| 115 | + * Purge all overrides from the cache. |
76 | 116 | */
|
77 |
| - public function get_cached_group_override(int $groupid): ?\stdClass { |
78 |
| - $raw = $this->get_cache()->get($this->get_group_cache_key($groupid)); |
79 |
| - return empty($raw) || !is_object($raw) ? null : (object) $raw; |
| 117 | + public static function purge_all(): void { |
| 118 | + self::get_cache()->purge(); |
80 | 119 | }
|
81 | 120 |
|
82 | 121 | /**
|
83 |
| - * Returns the override value in the cache for the given user |
| 122 | + * Purge overrides for a specific user in a specific quiz. |
84 | 123 | *
|
85 |
| - * @param int $userid user to get cached override data for |
86 |
| - * @return ?\stdClass the override value in the cache for the given user, or null if there is none. |
| 124 | + * @param int $quizid The quiz id. |
| 125 | + * @param int $userid The user id. |
87 | 126 | */
|
88 |
| - public function get_cached_user_override(int $userid): ?\stdClass { |
89 |
| - $raw = $this->get_cache()->get($this->get_user_cache_key($userid)); |
90 |
| - return empty($raw) || !is_object($raw) ? null : (object) $raw; |
| 127 | + public static function purge_for_user(int $quizid, int $userid): void { |
| 128 | + self::purge_for_users($quizid, [$userid]); |
91 | 129 | }
|
92 | 130 |
|
93 | 131 | /**
|
94 |
| - * Deletes the cached override data for a given group |
| 132 | + * Purge overrides for specific users in a specific quiz. |
95 | 133 | *
|
96 |
| - * @param int $groupid group to delete data for |
| 134 | + * @param int $quizid The quiz id. |
| 135 | + * @param int[] $userids The user ids. |
97 | 136 | */
|
98 |
| - public function clear_for_group(int $groupid): void { |
99 |
| - $this->get_cache()->delete($this->get_group_cache_key($groupid)); |
| 137 | + public static function purge_for_users(int $quizid, array $userids): void { |
| 138 | + if (empty($userids)) { |
| 139 | + return; |
| 140 | + } |
| 141 | + |
| 142 | + $keys = array_map(fn($userid): string => self::get_cache_key($quizid, $userid), $userids); |
| 143 | + $cache = self::get_cache(); |
| 144 | + $cache->delete_many($keys); |
100 | 145 | }
|
101 | 146 |
|
102 | 147 | /**
|
103 |
| - * Deletes the cached override data for the given user |
| 148 | + * Get the cache instance. |
104 | 149 | *
|
105 |
| - * @param int $userid user to delete data for |
| 150 | + * @return cache The cache instance. |
106 | 151 | */
|
107 |
| - public function clear_for_user(int $userid): void { |
108 |
| - $this->get_cache()->delete($this->get_user_cache_key($userid)); |
| 152 | + private static function get_cache(): cache { |
| 153 | + return cache::make('mod_quiz', 'overrides'); |
109 | 154 | }
|
110 | 155 |
|
111 | 156 | /**
|
112 |
| - * Clears the cache for the given user and/or group. |
| 157 | + * Generate a cache key for a given quiz and user. |
113 | 158 | *
|
114 |
| - * @param ?int $userid user to delete data for, or null. |
115 |
| - * @param ?int $groupid group to delete data for, or null. |
| 159 | + * @param int $quizid The quiz id. |
| 160 | + * @param int $userid The user id. |
| 161 | + * @return string The cache key. |
116 | 162 | */
|
117 |
| - public function clear_for(?int $userid = null, ?int $groupid = null): void { |
118 |
| - if (!empty($userid)) { |
119 |
| - $this->clear_for_user($userid); |
120 |
| - } |
| 163 | + private static function get_cache_key(int $quizid, int $userid): string { |
| 164 | + return "{$quizid}_{$userid}"; |
| 165 | + } |
121 | 166 |
|
122 |
| - if (!empty($groupid)) { |
123 |
| - $this->clear_for_group($groupid); |
124 |
| - } |
| 167 | + /** |
| 168 | + * Split a cache key into its quizid and userid components. |
| 169 | + * |
| 170 | + * @param string $key The cache key. |
| 171 | + * @return array An array with quizid and userid. |
| 172 | + */ |
| 173 | + private static function split_cache_key(string $key): array { |
| 174 | + return array_map('intval', explode('_', $key)); |
125 | 175 | }
|
126 | 176 | }
|
0 commit comments