on-demand release 3.6dev+
[moodle.git] / message / classes / privacy / provider.php
CommitLineData
04cd1b3f
MN
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 * Privacy Subsystem implementation for core_message.
19 *
20 * @package core_message
21 * @copyright 2018 Mark Nelson <markn@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24namespace core_message\privacy;
25
26use core_privacy\local\metadata\collection;
27use core_privacy\local\request\approved_contextlist;
28use core_privacy\local\request\contextlist;
29use core_privacy\local\request\transform;
30use core_privacy\local\request\writer;
31
32defined('MOODLE_INTERNAL') || die();
33
34/**
35 * Privacy Subsystem implementation for core_message.
36 *
37 * @copyright 2018 Mark Nelson <markn@moodle.com>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 */
40class provider implements
41 \core_privacy\local\metadata\provider,
42 \core_privacy\local\request\subsystem\provider,
43 \core_privacy\local\request\user_preference_provider {
44
45 /**
46 * Return the fields which contain personal data.
47 *
48 * @param collection $items a reference to the collection to use to store the metadata.
49 * @return collection the updated collection of metadata items.
50 */
51 public static function get_metadata(collection $items) : collection {
52 $items->add_database_table(
53 'messages',
54 [
55 'useridfrom' => 'privacy:metadata:messages:useridfrom',
56 'conversationid' => 'privacy:metadata:messages:conversationid',
57 'subject' => 'privacy:metadata:messages:subject',
58 'fullmessage' => 'privacy:metadata:messages:fullmessage',
59 'fullmessageformat' => 'privacy:metadata:messages:fullmessageformat',
60 'fullmessagehtml' => 'privacy:metadata:messages:fullmessagehtml',
61 'smallmessage' => 'privacy:metadata:messages:smallmessage',
62 'timecreated' => 'privacy:metadata:messages:timecreated'
63 ],
64 'privacy:metadata:messages'
65 );
66
67 $items->add_database_table(
68 'message_user_actions',
69 [
70 'userid' => 'privacy:metadata:message_user_actions:userid',
71 'messageid' => 'privacy:metadata:message_user_actions:messageid',
72 'action' => 'privacy:metadata:message_user_actions:action',
73 'timecreated' => 'privacy:metadata:message_user_actions:timecreated'
74 ],
75 'privacy:metadata:message_user_actions'
76 );
77
78 $items->add_database_table(
79 'message_conversation_members',
80 [
81 'conversationid' => 'privacy:metadata:message_conversation_members:conversationid',
82 'userid' => 'privacy:metadata:message_conversation_members:userid',
83 'timecreated' => 'privacy:metadata:message_conversation_members:timecreated',
84 ],
85 'privacy:metadata:message_conversation_members'
86 );
87
88 $items->add_database_table(
89 'message_contacts',
90 [
91 'userid' => 'privacy:metadata:message_contacts:userid',
92 'contactid' => 'privacy:metadata:message_contacts:contactid',
742c27b3 93 'timecreated' => 'privacy:metadata:message_contacts:timecreated',
04cd1b3f
MN
94 ],
95 'privacy:metadata:message_contacts'
96 );
97
742c27b3
MN
98 $items->add_database_table(
99 'message_contact_requests',
100 [
101 'userid' => 'privacy:metadata:message_contact_requests:userid',
102 'requesteduserid' => 'privacy:metadata:message_contact_requests:requesteduserid',
103 'timecreated' => 'privacy:metadata:message_contact_requests:timecreated',
104 ],
105 'privacy:metadata:message_contact_requests'
106 );
107
108 $items->add_database_table(
109 'message_users_blocked',
110 [
111 'userid' => 'privacy:metadata:message_users_blocked:userid',
112 'blockeduserid' => 'privacy:metadata:message_users_blocked:blockeduserid',
113 'timecreated' => 'privacy:metadata:message_users_blocked:timecreated',
114 ],
115 'privacy:metadata:message_users_blocked'
116 );
117
04cd1b3f
MN
118 $items->add_database_table(
119 'notifications',
120 [
121 'useridfrom' => 'privacy:metadata:notifications:useridfrom',
122 'useridto' => 'privacy:metadata:notifications:useridto',
123 'subject' => 'privacy:metadata:notifications:subject',
124 'fullmessage' => 'privacy:metadata:notifications:fullmessage',
125 'fullmessageformat' => 'privacy:metadata:notifications:fullmessageformat',
126 'fullmessagehtml' => 'privacy:metadata:notifications:fullmessagehtml',
127 'smallmessage' => 'privacy:metadata:notifications:smallmessage',
128 'component' => 'privacy:metadata:notifications:component',
129 'eventtype' => 'privacy:metadata:notifications:eventtype',
130 'contexturl' => 'privacy:metadata:notifications:contexturl',
131 'contexturlname' => 'privacy:metadata:notifications:contexturlname',
132 'timeread' => 'privacy:metadata:notifications:timeread',
133 'timecreated' => 'privacy:metadata:notifications:timecreated',
134 ],
135 'privacy:metadata:notifications'
136 );
137
138 // Note - we are not adding the 'message' and 'message_read' tables
139 // as they are legacy tables. This information is moved to these
140 // new tables in a separate ad-hoc task. See MDL-61255.
141
142 // Now add that we also have user preferences.
143 $items->add_user_preference('core_message_messageprovider_settings',
144 'privacy:metadata:preference:core_message_settings');
145
146 return $items;
147 }
148
149 /**
150 * Store all user preferences for core message.
151 *
152 * @param int $userid The userid of the user whose data is to be exported.
153 */
154 public static function export_user_preferences(int $userid) {
155 $preferences = get_user_preferences(null, null, $userid);
156 foreach ($preferences as $name => $value) {
157 if ((substr($name, 0, 16) == 'message_provider') || ($name == 'message_blocknoncontacts')) {
158 writer::export_user_preference(
159 'core_message',
160 $name,
161 $value,
162 get_string('privacy:request:preference:set', 'core_message', (object) [
163 'name' => $name,
164 'value' => $value,
165 ])
166 );
167 }
168 }
169 }
170
171 /**
172 * Get the list of contexts that contain user information for the specified user.
173 *
174 * @param int $userid the userid.
175 * @return contextlist the list of contexts containing user info for the user.
176 */
177 public static function get_contexts_for_userid(int $userid) : contextlist {
178 // Messages are in the system context.
179 $contextlist = new contextlist();
180 $contextlist->add_system_context();
181
182 return $contextlist;
183 }
184
185 /**
186 * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
187 *
188 * @param approved_contextlist $contextlist a list of contexts approved for export.
189 */
190 public static function export_user_data(approved_contextlist $contextlist) {
191 if (empty($contextlist->count())) {
192 return;
193 }
194
195 // Remove non-system contexts. If it ends up empty then early return.
196 $contexts = array_filter($contextlist->get_contexts(), function($context) {
197 return $context->contextlevel == CONTEXT_SYSTEM;
198 });
199
200 if (empty($contexts)) {
201 return;
202 }
203
204 $userid = $contextlist->get_user()->id;
205
206 // Export the contacts.
207 self::export_user_data_contacts($userid);
208
742c27b3
MN
209 // Export the contact requests.
210 self::export_user_data_contact_requests($userid);
211
212 // Export the blocked users.
213 self::export_user_data_blocked_users($userid);
214
04cd1b3f
MN
215 // Export the notifications.
216 self::export_user_data_notifications($userid);
217
218 // Export the messages, with any related actions.
219 self::export_user_data_messages($userid);
220 }
221
222 /**
223 * Delete all data for all users in the specified context.
224 *
225 * @param \context $context the context to delete in.
226 */
227 public static function delete_data_for_all_users_in_context(\context $context) {
228 global $DB;
229
230 if (!$context instanceof \context_system) {
231 return;
232 }
233
234 $DB->delete_records('messages');
235 $DB->delete_records('message_user_actions');
236 $DB->delete_records('message_conversation_members');
237 $DB->delete_records('message_contacts');
742c27b3
MN
238 $DB->delete_records('message_contact_requests');
239 $DB->delete_records('message_users_blocked');
04cd1b3f
MN
240 $DB->delete_records('notifications');
241 }
242
243 /**
244 * Delete all user data for the specified user, in the specified contexts.
245 *
246 * @param approved_contextlist $contextlist a list of contexts approved for deletion.
247 */
248 public static function delete_data_for_user(approved_contextlist $contextlist) {
249 global $DB;
250
251 if (empty($contextlist->count())) {
252 return;
253 }
254
255 // Remove non-system contexts. If it ends up empty then early return.
256 $contexts = array_filter($contextlist->get_contexts(), function($context) {
257 return $context->contextlevel == CONTEXT_SYSTEM;
258 });
259
260 if (empty($contexts)) {
261 return;
262 }
263
264 $userid = $contextlist->get_user()->id;
265
266 $DB->delete_records('messages', ['useridfrom' => $userid]);
267 $DB->delete_records('message_user_actions', ['userid' => $userid]);
268 $DB->delete_records('message_conversation_members', ['userid' => $userid]);
269 $DB->delete_records_select('message_contacts', 'userid = ? OR contactid = ?', [$userid, $userid]);
742c27b3
MN
270 $DB->delete_records_select('message_contact_requests', 'userid = ? OR requesteduserid = ?', [$userid, $userid]);
271 $DB->delete_records_select('message_users_blocked', 'userid = ? OR blockeduserid = ?', [$userid, $userid]);
04cd1b3f
MN
272 $DB->delete_records_select('notifications', 'useridfrom = ? OR useridto = ?', [$userid, $userid]);
273 }
274
275 /**
276 * Export the messaging contact data.
277 *
278 * @param int $userid
279 */
280 protected static function export_user_data_contacts(int $userid) {
281 global $DB;
282
283 $context = \context_system::instance();
284
285 // Get the user's contacts.
742c27b3 286 if ($contacts = $DB->get_records_select('message_contacts', 'userid = ? OR contactid = ?', [$userid, $userid], 'id ASC')) {
04cd1b3f
MN
287 $contactdata = [];
288 foreach ($contacts as $contact) {
289 $contactdata[] = (object) [
742c27b3 290 'contact' => transform::user($contact->contactid)
04cd1b3f
MN
291 ];
292 }
293 writer::with_context($context)->export_data([get_string('contacts', 'core_message')], (object) $contactdata);
294 }
742c27b3
MN
295 }
296
297 /**
298 * Export the messaging contact requests data.
299 *
300 * @param int $userid
301 */
302 protected static function export_user_data_contact_requests(int $userid) {
303 global $DB;
304
305 $context = \context_system::instance();
306
307 if ($contactrequests = $DB->get_records_select('message_contact_requests', 'userid = ? OR requesteduserid = ?',
308 [$userid, $userid], 'id ASC')) {
309 $contactrequestsdata = [];
310 foreach ($contactrequests as $contactrequest) {
311 if ($userid == $contactrequest->requesteduserid) {
312 $maderequest = false;
313 $contactid = $contactrequest->userid;
314 } else {
315 $maderequest = true;
316 $contactid = $contactrequest->requesteduserid;
317 }
318
319 $contactrequestsdata[] = (object) [
320 'contactrequest' => transform::user($contactid),
321 'maderequest' => transform::yesno($maderequest)
322 ];
323 }
324 writer::with_context($context)->export_data([get_string('contactrequests', 'core_message')],
325 (object) $contactrequestsdata);
326 }
327 }
328
329 /**
330 * Export the messaging blocked users data.
331 *
332 * @param int $userid
333 */
334 protected static function export_user_data_blocked_users(int $userid) {
335 global $DB;
336
337 $context = \context_system::instance();
338
339 if ($blockedusers = $DB->get_records('message_users_blocked', ['userid' => $userid], 'id ASC')) {
340 $blockedusersdata = [];
341 foreach ($blockedusers as $blockeduser) {
342 $blockedusersdata[] = (object) [
343 'blockeduser' => transform::user($blockeduser->blockeduserid)
344 ];
345 }
346 writer::with_context($context)->export_data([get_string('blockedusers', 'core_message')], (object) $blockedusersdata);
347 }
04cd1b3f
MN
348 }
349
350 /**
351 * Export the messaging data.
352 *
353 * @param int $userid
354 */
355 protected static function export_user_data_messages(int $userid) {
356 global $DB;
357
358 $context = \context_system::instance();
359
360 $sql = "SELECT DISTINCT mcm.conversationid as id
361 FROM {message_conversation_members} mcm
362 WHERE mcm.userid = :userid";
363 if ($conversations = $DB->get_records_sql($sql, ['userid' => $userid])) {
364 // Ok, let's get the other users in the conversations.
365 $conversationids = array_keys($conversations);
366 list($conversationidsql, $conversationparams) = $DB->get_in_or_equal($conversationids, SQL_PARAMS_NAMED);
367 $userfields = \user_picture::fields('u');
368 $userssql = "SELECT mcm.conversationid, $userfields
369 FROM {user} u
370 INNER JOIN {message_conversation_members} mcm
371 ON u.id = mcm.userid
372 WHERE mcm.conversationid $conversationidsql
373 AND mcm.userid != :userid
374 AND u.deleted = 0";
375 $otherusers = $DB->get_records_sql($userssql, $conversationparams + ['userid' => $userid]);
376 foreach ($conversations as $conversation) {
377 $otheruserfullname = get_string('unknownuser', 'core_message');
378
379 // It's possible the other user has requested to be deleted, so might not exist
380 // as a conversation member, or they have just been deleted.
381 if (isset($otherusers[$conversation->id])) {
382 $otheruserfullname = fullname($otherusers[$conversation->id]);
383 }
384
385 // Get all the messages for this conversation from start to finish.
386 $sql = "SELECT m.*, muadelete.timecreated as timedeleted, muaread.timecreated as timeread
387 FROM {messages} m
388 LEFT JOIN {message_user_actions} muadelete
389 ON m.id = muadelete.messageid AND muadelete.action = :deleteaction
390 LEFT JOIN {message_user_actions} muaread
391 ON m.id = muaread.messageid AND muaread.action = :readaction
392 WHERE conversationid = :conversationid
393 ORDER BY m.timecreated ASC";
394 $messages = $DB->get_recordset_sql($sql, ['deleteaction' => \core_message\api::MESSAGE_ACTION_DELETED,
395 'readaction' => \core_message\api::MESSAGE_ACTION_READ, 'conversationid' => $conversation->id]);
396 $messagedata = [];
397 foreach ($messages as $message) {
398 $timeread = !is_null($message->timeread) ? transform::datetime($message->timeread) : '-';
399 $issender = $userid == $message->useridfrom;
400
401 $data = [
402 'sender' => transform::yesno($issender),
403 'message' => message_format_message_text($message),
404 'timecreated' => transform::datetime($message->timecreated),
405 'timeread' => $timeread
406 ];
407
408 if (!is_null($message->timedeleted)) {
409 $data['timedeleted'] = transform::datetime($message->timedeleted);
410 }
411
412 $messagedata[] = (object) $data;
413 }
414 $messages->close();
415
416 writer::with_context($context)->export_data([get_string('messages', 'core_message'), $otheruserfullname],
417 (object) $messagedata);
418 }
419 }
420 }
421
422 /**
423 * Export the notification data.
424 *
425 * @param int $userid
426 */
427 protected static function export_user_data_notifications(int $userid) {
428 global $DB;
429
430 $context = \context_system::instance();
431
432 $notificationdata = [];
433 $select = "useridfrom = ? OR useridto = ?";
434 $notifications = $DB->get_recordset_select('notifications', $select, [$userid, $userid], 'timecreated ASC');
435 foreach ($notifications as $notification) {
436 $timeread = !is_null($notification->timeread) ? transform::datetime($notification->timeread) : '-';
437
438 $data = (object) [
439 'subject' => $notification->subject,
440 'fullmessage' => $notification->fullmessage,
441 'smallmessage' => $notification->smallmessage,
442 'component' => $notification->component,
443 'eventtype' => $notification->eventtype,
444 'contexturl' => $notification->contexturl,
445 'contexturlname' => $notification->contexturlname,
446 'timeread' => $timeread,
447 'timecreated' => transform::datetime($notification->timecreated)
448 ];
449
450 $notificationdata[] = $data;
451 }
452 $notifications->close();
453
454 writer::with_context($context)->export_data([get_string('notifications', 'core_message')], (object) $notificationdata);
455 }
456}