2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 * Privacy class for requesting user data.
19 * @package core_calendar
20 * @copyright 2018 Zig Tan <zig@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 namespace core_calendar\privacy
;
24 defined('MOODLE_INTERNAL') ||
die();
26 use \core_privacy\local\metadata\collection
;
27 use \core_privacy\local\request\approved_contextlist
;
28 use \core_privacy\local\request\context
;
29 use \core_privacy\local\request\contextlist
;
30 use \core_privacy\local\request\transform
;
31 use \core_privacy\local\request\writer
;
34 * Privacy Subsystem for core_calendar implementing metadata, plugin, and user_preference providers.
36 * @package core_calendar
37 * @copyright 2018 Zig Tan <zig@moodle.com>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 class provider
implements
41 \core_privacy\local\metadata\provider
,
42 \core_privacy\local\request\plugin\provider
,
43 \core_privacy\local\request\user_preference_provider
47 * Provides meta data that is stored about a user with core_calendar.
49 * @param collection $collection A collection of meta data items to be added to.
50 * @return collection Returns the collection of metadata.
52 public static function get_metadata(collection
$collection) : collection
{
53 // The calendar 'event' table contains user data.
54 $collection->add_database_table(
57 'name' => 'privacy:metadata:calendar:event:name',
58 'description' => 'privacy:metadata:calendar:event:description',
59 'eventtype' => 'privacy:metadata:calendar:event:eventtype',
60 'timestart' => 'privacy:metadata:calendar:event:timestart',
61 'timeduration' => 'privacy:metadata:calendar:event:timeduration',
63 'privacy:metadata:calendar:event'
66 // The calendar 'event_subscriptions' table contains user data.
67 $collection->add_database_table(
68 'event_subscriptions',
70 'name' => 'privacy:metadata:calendar:event_subscriptions:name',
71 'url' => 'privacy:metadata:calendar:event_subscriptions:url',
72 'eventtype' => 'privacy:metadata:calendar:event_subscriptions:eventtype',
74 'privacy:metadata:calendar:event_subscriptions'
77 // The calendar user preference setting 'calendar_savedflt'.
78 $collection->add_user_preference(
80 'privacy:metadata:calendar:preferences:calendar_savedflt'
87 * Get the list of contexts that contain calendar user information for the specified user.
89 * @param int $userid The user to search.
90 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
92 public static function get_contexts_for_userid(int $userid) : contextlist
{
93 $contextlist = new contextlist();
95 // Calendar Events can exist at Site, Course Category, Course, Course Group, User, or Course Modules contexts.
97 'sitecontext' => CONTEXT_SYSTEM
,
98 'categorycontext' => CONTEXT_COURSECAT
,
99 'coursecontext' => CONTEXT_COURSE
,
100 'groupcontext' => CONTEXT_COURSE
,
101 'usercontext' => CONTEXT_USER
,
102 'cuserid' => $userid,
103 'modulecontext' => CONTEXT_MODULE
,
107 // Get contexts of Calendar Events for the owner.
108 $sql = "SELECT ctx.id
111 (e.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
112 (e.categoryid = ctx.instanceid AND e.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
113 (e.courseid = ctx.instanceid AND e.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
114 (e.courseid = ctx.instanceid AND e.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
115 (e.userid = ctx.instanceid AND e.eventtype = 'user' AND ctx.contextlevel = :usercontext)
116 WHERE e.userid = :cuserid
120 JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :modulecontext
121 JOIN {modules} m ON m.id = cm.module
122 JOIN {event} e ON e.modulename = m.name AND e.courseid = cm.course AND e.instance = cm.instance
123 WHERE e.userid = :muserid";
124 $contextlist->add_from_sql($sql, $params);
126 // Calendar Subscriptions can exist at Site, Course Category, Course, Course Group, or User contexts.
128 'sitecontext' => CONTEXT_SYSTEM
,
129 'categorycontext' => CONTEXT_COURSECAT
,
130 'coursecontext' => CONTEXT_COURSE
,
131 'groupcontext' => CONTEXT_COURSE
,
132 'usercontext' => CONTEXT_USER
,
136 // Get contexts for Calendar Subscriptions for the owner.
137 $sql = "SELECT ctx.id
139 JOIN {event_subscriptions} s ON
140 (s.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
141 (s.categoryid = ctx.instanceid AND s.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
142 (s.courseid = ctx.instanceid AND s.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
143 (s.courseid = ctx.instanceid AND s.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
144 (s.userid = ctx.instanceid AND s.eventtype = 'user' AND ctx.contextlevel = :usercontext)
145 WHERE s.userid = :userid";
146 $contextlist->add_from_sql($sql, $params);
148 // Return combined contextlist for Calendar Events & Calendar Subscriptions.
153 * Export all user data for the specified user, in the specified contexts.
155 * @param approved_contextlist $contextlist The approved contexts to export information for.
157 public static function export_user_data(approved_contextlist
$contextlist) {
158 if (empty($contextlist)) {
162 self
::export_user_calendar_event_data($contextlist);
163 self
::export_user_calendar_subscription_data($contextlist);
167 * Export all user preferences for the plugin.
169 * @param int $userid The userid of the user whose data is to be exported.
171 public static function export_user_preferences(int $userid) {
172 $calendarsavedflt = get_user_preferences('calendar_savedflt', null, $userid);
174 if (null !== $calendarsavedflt) {
175 writer
::export_user_preference(
179 get_string('privacy:metadata:calendar:preferences:calendar_savedflt', 'core_calendar')
185 * Delete all Calendar Event and Calendar Subscription data for all users in the specified context.
187 * @param context $context Transform the specific context to delete data for.
189 public static function delete_data_for_all_users_in_context(\context
$context) {
190 if (empty($context)) {
194 // Delete all Calendar Events in the specified context in batches.
195 $eventids = array_keys(self
::get_calendar_event_ids_by_context($context));
196 self
::delete_batch_records('event', 'id', $eventids);
198 // Delete all Calendar Subscriptions in the specified context in batches.
199 $subscriptionids = array_keys(self
::get_calendar_subscription_ids_by_context($context));
200 self
::delete_batch_records('event_subscriptions', 'id', $subscriptionids);
204 * Delete all user data for the specified user, in the specified contexts.
206 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
208 public static function delete_data_for_user(approved_contextlist
$contextlist) {
209 if (empty($contextlist)) {
213 // Delete all Calendar Events for the owner and specified contexts in batches.
214 $eventdetails = self
::get_calendar_event_details_by_contextlist($contextlist);
216 foreach ($eventdetails as $eventdetail) {
217 $eventids[] = $eventdetail->eventid
;
219 $eventdetails->close();
220 self
::delete_batch_records('event', 'id', $eventids);
222 // Delete all Calendar Subscriptions for the owner and specified contexts in batches.
223 $subscriptiondetails = self
::get_calendar_subscription_details_by_contextlist($contextlist);
224 $subscriptionids = [];
225 foreach ($subscriptiondetails as $subscriptiondetail) {
226 $subscriptionids[] = $subscriptiondetail->subscriptionid
;
228 $subscriptiondetails->close();
229 self
::delete_batch_records('event_subscriptions', 'id', $subscriptionids);
233 * Helper function to export Calendar Events data by a User's contextlist.
235 * @param approved_contextlist $contextlist
236 * @throws \coding_exception
238 protected static function export_user_calendar_event_data(approved_contextlist
$contextlist) {
239 // Calendar Events can exist at Site, Course Category, Course, Course Group, User, or Course Modules contexts.
240 $eventdetails = self
::get_calendar_event_details_by_contextlist($contextlist);
242 // Multiple Calendar Events of the same eventtype and time can exist for a context, so collate them for export.
244 foreach ($eventdetails as $eventdetail) {
245 // Create an array key based on the contextid, eventtype, and time.
246 $key = $eventdetail->contextid
. $eventdetail->eventtype
. $eventdetail->timestart
;
248 if (array_key_exists($key, $eventrecords) === false) {
249 $eventrecords[$key] = [ $eventdetail ];
251 $eventrecords[$key] = array_merge($eventrecords[$key], [$eventdetail]);
254 $eventdetails->close();
256 // Export Calendar Event data.
257 foreach ($eventrecords as $eventrecord) {
258 $index = (count($eventrecord) > 1) ?
1 : 0;
260 foreach ($eventrecord as $event) {
261 // Export the events using the structure Calendar/Events/{datetime}/{eventtype}-event.json.
263 get_string('calendar', 'calendar'),
264 get_string('events', 'calendar'),
265 date('c', $event->timestart
)
267 $name = $event->eventtype
. '-event';
269 // Use name {eventtype}-event-{index}.json if multiple eventtypes and time exists at the same context.
271 $name .= '-' . $index;
275 $eventdetails = (object) [
276 'name' => $event->name
,
277 'description' => $event->description
,
278 'eventtype' => $event->eventtype
,
279 'timestart' => transform
::datetime($event->timestart
),
280 'timeduration' => $event->timeduration
283 $context = \context
::instance_by_id($event->contextid
);
284 writer
::with_context($context)->export_related_data($subcontexts, $name, $eventdetails);
290 * Helper function to export Calendar Subscriptions data by a User's contextlist.
292 * @param approved_contextlist $contextlist
293 * @throws \coding_exception
295 protected static function export_user_calendar_subscription_data(approved_contextlist
$contextlist) {
296 // Calendar Subscriptions can exist at Site, Course Category, Course, Course Group, or User contexts.
297 $subscriptiondetails = self
::get_calendar_subscription_details_by_contextlist($contextlist);
299 // Multiple Calendar Subscriptions of the same eventtype can exist for a context, so collate them for export.
300 $subscriptionrecords = [];
301 foreach ($subscriptiondetails as $subscriptiondetail) {
302 // Create an array key based on the contextid and eventtype.
303 $key = $subscriptiondetail->contextid
. $subscriptiondetail->eventtype
;
305 if (array_key_exists($key, $subscriptionrecords) === false) {
306 $subscriptionrecords[$key] = [ $subscriptiondetail ];
308 $subscriptionrecords[$key] = array_merge($subscriptionrecords[$key], [$subscriptiondetail]);
311 $subscriptiondetails->close();
313 // Export Calendar Subscription data.
314 foreach ($subscriptionrecords as $subscriptionrecord) {
315 $index = (count($subscriptionrecord) > 1) ?
1 : 0;
317 foreach ($subscriptionrecord as $subscription) {
318 // Export the events using the structure Calendar/Subscriptions/{eventtype}-subscription.json.
320 get_string('calendar', 'calendar'),
321 get_string('subscriptions', 'calendar')
323 $name = $subscription->eventtype
. '-subscription';
325 // Use name {eventtype}-subscription-{index}.json if multiple eventtypes exists at the same context.
327 $name .= '-' . $index;
331 $context = \context
::instance_by_id($subscription->contextid
);
332 writer
::with_context($context)->export_related_data($subcontexts, $name, $subscription);
338 * Helper function to return all Calendar Event id results for a specified context.
340 * @param \context $context
342 * @throws \dml_exception
344 protected static function get_calendar_event_ids_by_context(\context
$context) {
347 // Calendar Events can exist at Site, Course Category, Course, Course Group, User, or Course Modules contexts.
350 if ($context->contextlevel
== CONTEXT_MODULE
) { // Course Module Contexts.
352 'modulecontext' => $context->contextlevel
,
353 'contextid' => $context->id
356 // Get Calendar Events for the specified Course Module context.
357 $sql = "SELECT DISTINCT
360 INNER JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :modulecontext
361 INNER JOIN {modules} m ON m.id = cm.module
362 INNER JOIN {event} e ON e.modulename = m.name AND e.courseid = cm.course AND e.instance = cm.instance
363 WHERE ctx.id = :contextid";
364 $events = $DB->get_records_sql($sql, $params);
365 } else { // Other Moodle Contexts.
367 'sitecontext' => CONTEXT_SYSTEM
,
368 'categorycontext' => CONTEXT_COURSECAT
,
369 'coursecontext' => CONTEXT_COURSE
,
370 'groupcontext' => CONTEXT_COURSE
,
371 'usercontext' => CONTEXT_USER
,
372 'contextid' => $context->id
375 // Get Calendar Events for the specified Moodle context.
376 $sql = "SELECT DISTINCT
379 INNER JOIN {event} e ON
380 (e.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
381 (e.categoryid = ctx.instanceid AND e.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
382 (e.courseid = ctx.instanceid AND (e.eventtype = 'course' OR e.eventtype = 'group' OR e.modulename != '0') AND ctx.contextlevel = :coursecontext) OR
383 (e.userid = ctx.instanceid AND e.eventtype = 'user' AND ctx.contextlevel = :usercontext)
384 WHERE ctx.id = :contextid";
385 $events = $DB->get_records_sql($sql, $params);
392 * Helper function to return all Calendar Subscription id results for a specified context.
394 * @param \context $context
396 * @throws \dml_exception
398 protected static function get_calendar_subscription_ids_by_context(\context
$context) {
401 // Calendar Subscriptions can exist at Site, Course Category, Course, Course Group, or User contexts.
403 'sitecontext' => CONTEXT_SYSTEM
,
404 'categorycontext' => CONTEXT_COURSECAT
,
405 'coursecontext' => CONTEXT_COURSE
,
406 'groupcontext' => CONTEXT_COURSE
,
407 'usercontext' => CONTEXT_USER
,
408 'contextid' => $context->id
411 // Get Calendar Subscriptions for the specified context.
412 $sql = "SELECT DISTINCT
413 s.id AS subscriptionid
415 INNER JOIN {event_subscriptions} s ON
416 (s.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
417 (s.categoryid = ctx.instanceid AND s.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
418 (s.courseid = ctx.instanceid AND s.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
419 (s.courseid = ctx.instanceid AND s.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
420 (s.userid = ctx.instanceid AND s.eventtype = 'user' AND ctx.contextlevel = :usercontext)
421 WHERE ctx.id = :contextid";
423 return $DB->get_records_sql($sql, $params);
427 * Helper function to return the Calendar Events for a given user and context list.
429 * @param approved_contextlist $contextlist
431 * @throws \coding_exception
432 * @throws \dml_exception
434 protected static function get_calendar_event_details_by_contextlist(approved_contextlist
$contextlist) {
437 $userid = $contextlist->get_user()->id
;
439 list($contextsql1, $contextparams1) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED
);
440 list($contextsql2, $contextparams2) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED
);
442 // Calendar Events can exist at Site, Course Category, Course, Course Group, User, or Course Modules contexts.
444 'sitecontext' => CONTEXT_SYSTEM
,
445 'categorycontext' => CONTEXT_COURSECAT
,
446 'coursecontext' => CONTEXT_COURSE
,
447 'groupcontext' => CONTEXT_COURSE
,
448 'usercontext' => CONTEXT_USER
,
449 'cuserid' => $userid,
450 'modulecontext' => CONTEXT_MODULE
,
453 $params +
= $contextparams1;
454 $params +
= $contextparams2;
456 // Get Calendar Events details for the approved contexts and the owner.
457 $sql = "SELECT ctxid as contextid,
458 details.id as eventid,
459 details.name as name,
460 details.description as description,
461 details.eventtype as eventtype,
462 details.timestart as timestart,
463 details.timeduration as timeduration
468 INNER JOIN {event} e ON
469 (e.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
470 (e.categoryid = ctx.instanceid AND e.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
471 (e.courseid = ctx.instanceid AND e.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
472 (e.courseid = ctx.instanceid AND e.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
473 (e.userid = ctx.instanceid AND e.eventtype = 'user' AND ctx.contextlevel = :usercontext)
474 WHERE e.userid = :cuserid
475 AND ctx.id {$contextsql1}
480 INNER JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :modulecontext
481 INNER JOIN {modules} m ON m.id = cm.module
482 INNER JOIN {event} e ON e.modulename = m.name AND e.courseid = cm.course AND e.instance = cm.instance
483 WHERE e.userid = :muserid
484 AND ctx.id {$contextsql2}
486 JOIN {event} details ON details.id = ids.id
489 return $DB->get_recordset_sql($sql, $params);
493 * Helper function to return the Calendar Subscriptions for a given user and context list.
495 * @param approved_contextlist $contextlist
497 * @throws \coding_exception
498 * @throws \dml_exception
500 protected static function get_calendar_subscription_details_by_contextlist(approved_contextlist
$contextlist) {
503 $user = $contextlist->get_user();
505 list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED
);
508 'sitecontext' => CONTEXT_SYSTEM
,
509 'categorycontext' => CONTEXT_COURSECAT
,
510 'coursecontext' => CONTEXT_COURSE
,
511 'groupcontext' => CONTEXT_COURSE
,
512 'usercontext' => CONTEXT_USER
,
513 'userid' => $user->id
515 $params +
= $contextparams;
517 // Get Calendar Subscriptions for the approved contexts and the owner.
518 $sql = "SELECT DISTINCT
520 s.id as subscriptionid,
523 s.eventtype as eventtype
525 INNER JOIN {event_subscriptions} s ON
526 (s.eventtype = 'site' AND c.contextlevel = :sitecontext) OR
527 (s.categoryid = c.instanceid AND s.eventtype = 'category' AND c.contextlevel = :categorycontext) OR
528 (s.courseid = c.instanceid AND s.eventtype = 'course' AND c.contextlevel = :coursecontext) OR
529 (s.courseid = c.instanceid AND s.eventtype = 'group' AND c.contextlevel = :groupcontext) OR
530 (s.userid = c.instanceid AND s.eventtype = 'user' AND c.contextlevel = :usercontext)
531 WHERE s.userid = :userid
532 AND c.id {$contextsql}";
534 return $DB->get_recordset_sql($sql, $params);
538 * Helper function to delete records in batches in order to minimise amount of deletion queries.
540 * @param string $tablename The table name to delete from.
541 * @param string $field The table column field name to delete records by.
542 * @param array $values The table column field values to delete records by.
543 * @throws \dml_exception
545 protected static function delete_batch_records($tablename, $field, $values) {
548 // Batch deletion with an upper limit of 2000 records to minimise the number of deletion queries.
549 $batchrecords = array_chunk($values, 2000);
551 foreach ($batchrecords as $batchrecord) {
552 $DB->delete_records_list($tablename, $field, $batchrecord);