MDL-58087 core_calendar: Don't pass non action events through our plumbing
[moodle.git] / calendar / classes / local / event / data_access / event_vault.php
CommitLineData
84d865d6
RW
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
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/>.
16
17/**
18 * Event vault class
19 *
20 * @package core_calendar
21 * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25namespace core_calendar\local\event\data_access;
26
27use core_calendar\local\event\exceptions\limit_invalid_parameter_exception;
28use core_calendar\local\event\exceptions\timesort_invalid_parameter_exception;
29use core_calendar\local\interfaces\action_event_interface;
30use core_calendar\local\interfaces\event_interface;
31use core_calendar\local\interfaces\event_factory_interface;
32use core_calendar\local\interfaces\event_vault_interface;
33
34/**
35 * This class will handle interacting with the database layer to retrieve
36 * the records. This is required to house the complex logic required for
37 * pagination because it's not a one-to-one mapping between database records
38 * and users.
39 *
40 * This is a repository. It's called a vault to reduce confusion because
41 * Moodle has already taken the name repository. Vault is cooler anyway.
42 *
43 * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45 */
46class event_vault implements event_vault_interface {
47
5a0974c7
RW
48 /**
49 * @var event_factory_interface $factory Factory for creating events.
50 */
84d865d6
RW
51 private $factory;
52
53 /**
54 * Create an event vault.
55 *
56 * @param event_factory_interface $factory An event factory
57 */
58 public function __construct(event_factory_interface $factory) {
59 $this->factory = $factory;
60 }
61
62 /**
63 * Retrieve an event for the given id.
64 *
5e6e3bab 65 * @param int $id The event id
84d865d6
RW
66 * @return event_interface
67 */
5e6e3bab 68 public function get_event_by_id($id) {
84d865d6
RW
69 global $DB;
70
71 if ($record = $DB->get_record('event', ['id' => $id])) {
72 return $this->transform_from_database_record($record);
73 } else {
74 return false;
75 }
76 }
77
78 /**
79 * Retrieve an array of events for the given user and time constraints.
80 *
5a0974c7
RW
81 * If using this function for pagination then you can provide the last event that you've seen
82 * ($afterevent) and it will be used to appropriately offset the result set so that you don't
83 * receive the same events again.
84 *
84d865d6
RW
85 * @param \stdClass $user The user for whom the events belong
86 * @param int|null $timesortfrom Events with timesort from this value (inclusive)
87 * @param int|null $timesortto Events with timesort until this value (inclusive)
88 * @param event_interface|null $afterevent Only return events after this one
89 * @param int $limitnum Return at most this number of events
5e6e3bab
MN
90 * @throws timesort_invalid_parameter_exception
91 * @throws limit_invalid_parameter_exception
84d865d6
RW
92 * @return action_event_interface
93 */
94 public function get_action_events_by_timesort(
95 \stdClass $user,
5e6e3bab
MN
96 $timesortfrom = null,
97 $timesortto = null,
84d865d6 98 event_interface $afterevent = null,
5e6e3bab 99 $limitnum = 20
84d865d6
RW
100 ) {
101 global $DB;
102
103 if (is_null($timesortfrom) && is_null($timesortto)) {
104 throw new timesort_invalid_parameter_exception("Must provide a timesort to and/or from value");
105 }
106
107 if ($limitnum < 1 || $limitnum > 50) {
108 throw new limit_invalid_parameter_exception("Limit must be between 1 and 50 (inclusive)");
109 }
110
5a0974c7 111 $lastseentimesort = null;
84d865d6
RW
112 $params = ['type' => CALENDAR_EVENT_TYPE_ACTION];
113 $where = ['type = :type'];
114
5a0974c7
RW
115 if (!is_null($afterevent)) {
116 $lastseentimesort = $afterevent->get_times()->get_sort_time()->getTimestamp();
84d865d6
RW
117 }
118
5a0974c7
RW
119 if ($timesortfrom) {
120 if ($lastseentimesort && $lastseentimesort >= $timesortfrom) {
121 $where[] = '((timesort = :timesortfrom1 AND id > :timesortfromid) '.
122 'OR timesort > :timesortfrom2)';
123 $params['timesortfromid'] = $afterevent->get_id();
124 $params['timesortfrom1'] = $lastseentimesort;
125 $params['timesortfrom2'] = $lastseentimesort;
126 } else {
127 $where[] = 'timesort >= :timesortfrom';
128 $params['timesortfrom'] = $timesortfrom;
129 }
84d865d6
RW
130 }
131
5a0974c7
RW
132 if ($timesortto) {
133 if ($lastseentimesort && $lastseentimesort > $timesortto) {
134 // The last seen event from this set is after the time sort range which
135 // means all events in this range have been seen, so we can just return
136 // early here.
137 return [];
138 } else if ($lastseentimesort && $lastseentimesort == $timesortto) {
139 $where[] = '((timesort = :timesortto1 AND id > :timesorttoid) OR timesort < :timesortto2)';
140 $params['timesorttoid'] = $afterevent->get_id();
141 $params['timesortto1'] = $timesortto;
142 $params['timesortto2'] = $timesortto;
143 } else {
144 $where[] = 'timesort <= :timesortto';
145 $params['timesortto'] = $timesortto;
146 }
84d865d6
RW
147 }
148
149 $sql = sprintf("SELECT * FROM {event} WHERE %s ORDER BY timesort ASC, id ASC",
150 implode(' AND ', $where));
151
152 $offset = 0;
153 $events = [];
154 // We need to continue to pull records from the database until we reach
155 // the requested amount of events because not all records in the database
e62cd85f
RW
156 // will be visible for the current user.
157 while ($records = array_values($DB->get_records_sql($sql, $params, $offset, $limitnum))) {
158 foreach ($records as $record) {
159 if ($event = $this->transform_from_database_record($record)) {
160 if ($event instanceof action_event_interface) {
161 $events[] = $event;
162 }
163
164 if (count($events) == $limitnum) {
165 // We've got all of the events so break both loops.
166 break 2;
167 }
168 }
169 }
170
171 $offset += $limitnum;
172 }
173
174 return $events;
175 }
176
177 /**
178 * Retrieve an array of events for the given user filtered by the course and time constraints.
179 *
5a0974c7
RW
180 * If using this function for pagination then you can provide the last event that you've seen
181 * ($afterevent) and it will be used to appropriately offset the result set so that you don't
182 * receive the same events again.
183 *
e62cd85f 184 * @param \stdClass $user The user for whom the events belong
5a0974c7 185 * @param \stdClass $course The course to filter by
e62cd85f
RW
186 * @param int|null $timesortfrom Events with timesort from this value (inclusive)
187 * @param int|null $timesortto Events with timesort until this value (inclusive)
188 * @param event_interface|null $afterevent Only return events after this one
189 * @param int $limitnum Return at most this number of events
190 * @return action_event_interface
191 */
192 public function get_action_events_by_course(
193 \stdClass $user,
194 \stdClass $course,
8aa761b0
MN
195 $timesortfrom = null,
196 $timesortto = null,
e62cd85f 197 event_interface $afterevent = null,
8aa761b0 198 $limitnum = 20
e62cd85f
RW
199 ) {
200 global $DB;
201
202 if ($limitnum < 1 || $limitnum > 50) {
203 throw new limit_invalid_parameter_exception("Limit must be between 1 and 50 (inclusive)");
204 }
205
5a0974c7 206 $lastseentimesort = null;
e62cd85f
RW
207 $params = [
208 'type' => CALENDAR_EVENT_TYPE_ACTION,
209 'courseid' => $course->id,
210 ];
211 $where = [
212 'type = :type',
213 'courseid = :courseid',
214 ];
215
5a0974c7
RW
216 if (!is_null($afterevent)) {
217 $lastseentimesort = $afterevent->get_times()->get_sort_time()->getTimestamp();
e62cd85f
RW
218 }
219
5a0974c7
RW
220 if ($timesortfrom) {
221 if ($lastseentimesort && $lastseentimesort >= $timesortfrom) {
222 $where[] = '((timesort = :timesortfrom1 AND id > :timesortfromid) '.
223 'OR timesort > :timesortfrom2)';
224 $params['timesortfromid'] = $afterevent->get_id();
225 $params['timesortfrom1'] = $lastseentimesort;
226 $params['timesortfrom2'] = $lastseentimesort;
227 } else {
228 $where[] = 'timesort >= :timesortfrom';
229 $params['timesortfrom'] = $timesortfrom;
230 }
e62cd85f
RW
231 }
232
5a0974c7
RW
233 if ($timesortto) {
234 if ($lastseentimesort && $lastseentimesort > $timesortto) {
235 // The last seen event from this set is after the time sort range which
236 // means all events in this range have been seen, so we can just return
237 // early here.
238 return [];
239 } else if ($lastseentimesort && $lastseentimesort == $timesortto) {
240 $where[] = '((timesort = :timesortto1 AND id > :timesorttoid) OR timesort < :timesortto2)';
241 $params['timesorttoid'] = $afterevent->get_id();
242 $params['timesortto1'] = $timesortto;
243 $params['timesortto2'] = $timesortto;
244 } else {
245 $where[] = 'timesort <= :timesortto';
246 $params['timesortto'] = $timesortto;
247 }
e62cd85f
RW
248 }
249
250 $wheresql = implode(' AND ', $where);
251 $sql = sprintf("SELECT * FROM {event} WHERE %s ORDER BY timesort ASC, id ASC", $wheresql);
e62cd85f
RW
252 $offset = 0;
253 $events = [];
254 // We need to continue to pull records from the database until we reach
255 // the requested amount of events because not all records in the database
84d865d6
RW
256 // will be visible for the current user.
257 while ($records = array_values($DB->get_records_sql($sql, $params, $offset, $limitnum))) {
258 foreach ($records as $record) {
259 if ($event = $this->transform_from_database_record($record)) {
260 if ($event instanceof action_event_interface) {
261 $events[] = $event;
262 }
263
264 if (count($events) == $limitnum) {
265 // We've got all of the events so break both loops.
266 break 2;
267 }
268 }
269 }
270
271 $offset += $limitnum;
272 }
273
274 return $events;
275 }
276
277 /**
278 * Create an event from a database record.
279 *
280 * @param \stdClass $record The database record
281 * @return event_interface|false
282 */
283 private function transform_from_database_record(\stdClass $record) {
284 return $this->factory->create_instance($record);
285 }
84d865d6 286}