on-demand release 3.6dev+
[moodle.git] / message / classes / privacy / provider.php
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/>.
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  */
24 namespace core_message\privacy;
26 use core_privacy\local\metadata\collection;
27 use core_privacy\local\request\approved_contextlist;
28 use core_privacy\local\request\contextlist;
29 use core_privacy\local\request\transform;
30 use core_privacy\local\request\writer;
32 defined('MOODLE_INTERNAL') || die();
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  */
40 class provider implements
41     \core_privacy\local\metadata\provider,
42     \core_privacy\local\request\subsystem\provider,
43     \core_privacy\local\request\user_preference_provider {
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         );
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         );
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         );
88         $items->add_database_table(
89             'message_contacts',
90             [
91                 'userid' => 'privacy:metadata:message_contacts:userid',
92                 'contactid' => 'privacy:metadata:message_contacts:contactid',
93                 'timecreated' => 'privacy:metadata:message_contacts:timecreated',
94             ],
95             'privacy:metadata:message_contacts'
96         );
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         );
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         );
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         );
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.
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');
146         return $items;
147     }
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     }
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();
182         return $contextlist;
183     }
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         }
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         });
200         if (empty($contexts)) {
201             return;
202         }
204         $userid = $contextlist->get_user()->id;
206         // Export the contacts.
207         self::export_user_data_contacts($userid);
209         // Export the contact requests.
210         self::export_user_data_contact_requests($userid);
212         // Export the blocked users.
213         self::export_user_data_blocked_users($userid);
215         // Export the notifications.
216         self::export_user_data_notifications($userid);
218         // Export the messages, with any related actions.
219         self::export_user_data_messages($userid);
220     }
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;
230         if (!$context instanceof \context_system) {
231             return;
232         }
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');
238         $DB->delete_records('message_contact_requests');
239         $DB->delete_records('message_users_blocked');
240         $DB->delete_records('notifications');
241     }
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;
251         if (empty($contextlist->count())) {
252             return;
253         }
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         });
260         if (empty($contexts)) {
261             return;
262         }
264         $userid = $contextlist->get_user()->id;
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]);
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]);
272         $DB->delete_records_select('notifications', 'useridfrom = ? OR useridto = ?', [$userid, $userid]);
273     }
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;
283         $context = \context_system::instance();
285         // Get the user's contacts.
286         if ($contacts = $DB->get_records_select('message_contacts', 'userid = ? OR contactid = ?', [$userid, $userid], 'id ASC')) {
287             $contactdata = [];
288             foreach ($contacts as $contact) {
289                 $contactdata[] = (object) [
290                     'contact' => transform::user($contact->contactid)
291                 ];
292             }
293             writer::with_context($context)->export_data([get_string('contacts', 'core_message')], (object) $contactdata);
294         }
295     }
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;
305         $context = \context_system::instance();
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                 }
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     }
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;
337         $context = \context_system::instance();
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         }
348     }
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;
358         $context = \context_system::instance();
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');
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                 }
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;
401                     $data = [
402                         'sender' => transform::yesno($issender),
403                         'message' => message_format_message_text($message),
404                         'timecreated' => transform::datetime($message->timecreated),
405                         'timeread' => $timeread
406                     ];
408                     if (!is_null($message->timedeleted)) {
409                         $data['timedeleted'] = transform::datetime($message->timedeleted);
410                     }
412                     $messagedata[] = (object) $data;
413                 }
414                 $messages->close();
416                 writer::with_context($context)->export_data([get_string('messages', 'core_message'), $otheruserfullname],
417                     (object) $messagedata);
418             }
419         }
420     }
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;
430         $context = \context_system::instance();
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) : '-';
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             ];
450             $notificationdata[] = $data;
451         }
452         $notifications->close();
454         writer::with_context($context)->export_data([get_string('notifications', 'core_message')], (object) $notificationdata);
455     }