MDL-61919 core_message: implement privacy provider
[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',
93 'blocked' => 'privacy:metadata:message_contacts:blocked',
94 ],
95 'privacy:metadata:message_contacts'
96 );
97
98 $items->add_database_table(
99 'notifications',
100 [
101 'useridfrom' => 'privacy:metadata:notifications:useridfrom',
102 'useridto' => 'privacy:metadata:notifications:useridto',
103 'subject' => 'privacy:metadata:notifications:subject',
104 'fullmessage' => 'privacy:metadata:notifications:fullmessage',
105 'fullmessageformat' => 'privacy:metadata:notifications:fullmessageformat',
106 'fullmessagehtml' => 'privacy:metadata:notifications:fullmessagehtml',
107 'smallmessage' => 'privacy:metadata:notifications:smallmessage',
108 'component' => 'privacy:metadata:notifications:component',
109 'eventtype' => 'privacy:metadata:notifications:eventtype',
110 'contexturl' => 'privacy:metadata:notifications:contexturl',
111 'contexturlname' => 'privacy:metadata:notifications:contexturlname',
112 'timeread' => 'privacy:metadata:notifications:timeread',
113 'timecreated' => 'privacy:metadata:notifications:timecreated',
114 ],
115 'privacy:metadata:notifications'
116 );
117
118 // Note - we are not adding the 'message' and 'message_read' tables
119 // as they are legacy tables. This information is moved to these
120 // new tables in a separate ad-hoc task. See MDL-61255.
121
122 // Now add that we also have user preferences.
123 $items->add_user_preference('core_message_messageprovider_settings',
124 'privacy:metadata:preference:core_message_settings');
125
126 return $items;
127 }
128
129 /**
130 * Store all user preferences for core message.
131 *
132 * @param int $userid The userid of the user whose data is to be exported.
133 */
134 public static function export_user_preferences(int $userid) {
135 $preferences = get_user_preferences(null, null, $userid);
136 foreach ($preferences as $name => $value) {
137 if ((substr($name, 0, 16) == 'message_provider') || ($name == 'message_blocknoncontacts')) {
138 writer::export_user_preference(
139 'core_message',
140 $name,
141 $value,
142 get_string('privacy:request:preference:set', 'core_message', (object) [
143 'name' => $name,
144 'value' => $value,
145 ])
146 );
147 }
148 }
149 }
150
151 /**
152 * Get the list of contexts that contain user information for the specified user.
153 *
154 * @param int $userid the userid.
155 * @return contextlist the list of contexts containing user info for the user.
156 */
157 public static function get_contexts_for_userid(int $userid) : contextlist {
158 // Messages are in the system context.
159 $contextlist = new contextlist();
160 $contextlist->add_system_context();
161
162 return $contextlist;
163 }
164
165 /**
166 * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
167 *
168 * @param approved_contextlist $contextlist a list of contexts approved for export.
169 */
170 public static function export_user_data(approved_contextlist $contextlist) {
171 if (empty($contextlist->count())) {
172 return;
173 }
174
175 // Remove non-system contexts. If it ends up empty then early return.
176 $contexts = array_filter($contextlist->get_contexts(), function($context) {
177 return $context->contextlevel == CONTEXT_SYSTEM;
178 });
179
180 if (empty($contexts)) {
181 return;
182 }
183
184 $userid = $contextlist->get_user()->id;
185
186 // Export the contacts.
187 self::export_user_data_contacts($userid);
188
189 // Export the notifications.
190 self::export_user_data_notifications($userid);
191
192 // Export the messages, with any related actions.
193 self::export_user_data_messages($userid);
194 }
195
196 /**
197 * Delete all data for all users in the specified context.
198 *
199 * @param \context $context the context to delete in.
200 */
201 public static function delete_data_for_all_users_in_context(\context $context) {
202 global $DB;
203
204 if (!$context instanceof \context_system) {
205 return;
206 }
207
208 $DB->delete_records('messages');
209 $DB->delete_records('message_user_actions');
210 $DB->delete_records('message_conversation_members');
211 $DB->delete_records('message_contacts');
212 $DB->delete_records('notifications');
213 }
214
215 /**
216 * Delete all user data for the specified user, in the specified contexts.
217 *
218 * @param approved_contextlist $contextlist a list of contexts approved for deletion.
219 */
220 public static function delete_data_for_user(approved_contextlist $contextlist) {
221 global $DB;
222
223 if (empty($contextlist->count())) {
224 return;
225 }
226
227 // Remove non-system contexts. If it ends up empty then early return.
228 $contexts = array_filter($contextlist->get_contexts(), function($context) {
229 return $context->contextlevel == CONTEXT_SYSTEM;
230 });
231
232 if (empty($contexts)) {
233 return;
234 }
235
236 $userid = $contextlist->get_user()->id;
237
238 $DB->delete_records('messages', ['useridfrom' => $userid]);
239 $DB->delete_records('message_user_actions', ['userid' => $userid]);
240 $DB->delete_records('message_conversation_members', ['userid' => $userid]);
241 $DB->delete_records_select('message_contacts', 'userid = ? OR contactid = ?', [$userid, $userid]);
242 $DB->delete_records_select('notifications', 'useridfrom = ? OR useridto = ?', [$userid, $userid]);
243 }
244
245 /**
246 * Export the messaging contact data.
247 *
248 * @param int $userid
249 */
250 protected static function export_user_data_contacts(int $userid) {
251 global $DB;
252
253 $context = \context_system::instance();
254
255 // Get the user's contacts.
256 if ($contacts = $DB->get_records('message_contacts', ['userid' => $userid], 'id ASC')) {
257 $contactdata = [];
258 foreach ($contacts as $contact) {
259 $contactdata[] = (object) [
260 'contact' => transform::user($contact->contactid),
261 'blocked' => transform::yesno($contact->blocked)
262 ];
263 }
264 writer::with_context($context)->export_data([get_string('contacts', 'core_message')], (object) $contactdata);
265 }
266 }
267
268 /**
269 * Export the messaging data.
270 *
271 * @param int $userid
272 */
273 protected static function export_user_data_messages(int $userid) {
274 global $DB;
275
276 $context = \context_system::instance();
277
278 $sql = "SELECT DISTINCT mcm.conversationid as id
279 FROM {message_conversation_members} mcm
280 WHERE mcm.userid = :userid";
281 if ($conversations = $DB->get_records_sql($sql, ['userid' => $userid])) {
282 // Ok, let's get the other users in the conversations.
283 $conversationids = array_keys($conversations);
284 list($conversationidsql, $conversationparams) = $DB->get_in_or_equal($conversationids, SQL_PARAMS_NAMED);
285 $userfields = \user_picture::fields('u');
286 $userssql = "SELECT mcm.conversationid, $userfields
287 FROM {user} u
288 INNER JOIN {message_conversation_members} mcm
289 ON u.id = mcm.userid
290 WHERE mcm.conversationid $conversationidsql
291 AND mcm.userid != :userid
292 AND u.deleted = 0";
293 $otherusers = $DB->get_records_sql($userssql, $conversationparams + ['userid' => $userid]);
294 foreach ($conversations as $conversation) {
295 $otheruserfullname = get_string('unknownuser', 'core_message');
296
297 // It's possible the other user has requested to be deleted, so might not exist
298 // as a conversation member, or they have just been deleted.
299 if (isset($otherusers[$conversation->id])) {
300 $otheruserfullname = fullname($otherusers[$conversation->id]);
301 }
302
303 // Get all the messages for this conversation from start to finish.
304 $sql = "SELECT m.*, muadelete.timecreated as timedeleted, muaread.timecreated as timeread
305 FROM {messages} m
306 LEFT JOIN {message_user_actions} muadelete
307 ON m.id = muadelete.messageid AND muadelete.action = :deleteaction
308 LEFT JOIN {message_user_actions} muaread
309 ON m.id = muaread.messageid AND muaread.action = :readaction
310 WHERE conversationid = :conversationid
311 ORDER BY m.timecreated ASC";
312 $messages = $DB->get_recordset_sql($sql, ['deleteaction' => \core_message\api::MESSAGE_ACTION_DELETED,
313 'readaction' => \core_message\api::MESSAGE_ACTION_READ, 'conversationid' => $conversation->id]);
314 $messagedata = [];
315 foreach ($messages as $message) {
316 $timeread = !is_null($message->timeread) ? transform::datetime($message->timeread) : '-';
317 $issender = $userid == $message->useridfrom;
318
319 $data = [
320 'sender' => transform::yesno($issender),
321 'message' => message_format_message_text($message),
322 'timecreated' => transform::datetime($message->timecreated),
323 'timeread' => $timeread
324 ];
325
326 if (!is_null($message->timedeleted)) {
327 $data['timedeleted'] = transform::datetime($message->timedeleted);
328 }
329
330 $messagedata[] = (object) $data;
331 }
332 $messages->close();
333
334 writer::with_context($context)->export_data([get_string('messages', 'core_message'), $otheruserfullname],
335 (object) $messagedata);
336 }
337 }
338 }
339
340 /**
341 * Export the notification data.
342 *
343 * @param int $userid
344 */
345 protected static function export_user_data_notifications(int $userid) {
346 global $DB;
347
348 $context = \context_system::instance();
349
350 $notificationdata = [];
351 $select = "useridfrom = ? OR useridto = ?";
352 $notifications = $DB->get_recordset_select('notifications', $select, [$userid, $userid], 'timecreated ASC');
353 foreach ($notifications as $notification) {
354 $timeread = !is_null($notification->timeread) ? transform::datetime($notification->timeread) : '-';
355
356 $data = (object) [
357 'subject' => $notification->subject,
358 'fullmessage' => $notification->fullmessage,
359 'smallmessage' => $notification->smallmessage,
360 'component' => $notification->component,
361 'eventtype' => $notification->eventtype,
362 'contexturl' => $notification->contexturl,
363 'contexturlname' => $notification->contexturlname,
364 'timeread' => $timeread,
365 'timecreated' => transform::datetime($notification->timecreated)
366 ];
367
368 $notificationdata[] = $data;
369 }
370 $notifications->close();
371
372 writer::with_context($context)->export_data([get_string('notifications', 'core_message')], (object) $notificationdata);
373 }
374}