Commit | Line | Data |
---|---|---|
bf3b4f3e FM |
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 | * Data provider. | |
19 | * | |
20 | * @package mod_chat | |
21 | * @copyright 2018 Frédéric Massart | |
22 | * @author Frédéric Massart <fred@branchup.tech> | |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
24 | */ | |
25 | ||
26 | namespace mod_chat\privacy; | |
27 | defined('MOODLE_INTERNAL') || die(); | |
28 | ||
29 | use context; | |
30 | use context_helper; | |
31 | use context_module; | |
32 | use moodle_recordset; | |
33 | use stdClass; | |
34 | use core_privacy\local\metadata\collection; | |
35 | use core_privacy\local\request\approved_contextlist; | |
36 | use core_privacy\local\request\contextlist; | |
37 | use core_privacy\local\request\helper; | |
38 | use core_privacy\local\request\transform; | |
39 | use core_privacy\local\request\writer; | |
40 | ||
41 | /** | |
42 | * Data provider class. | |
43 | * | |
44 | * @package mod_chat | |
45 | * @copyright 2018 Frédéric Massart | |
46 | * @author Frédéric Massart <fred@branchup.tech> | |
47 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
48 | */ | |
49 | class provider implements | |
50 | \core_privacy\local\metadata\provider, | |
51 | \core_privacy\local\request\plugin\provider { | |
52 | ||
53 | /** | |
54 | * Returns metadata. | |
55 | * | |
56 | * @param collection $collection The initialised collection to add items to. | |
57 | * @return collection A listing of user data stored through this system. | |
58 | */ | |
59 | public static function get_metadata(collection $collection) : collection { | |
60 | ||
61 | $collection->add_database_table('chat_messages', [ | |
62 | 'userid' => 'privacy:metadata:messages:userid', | |
63 | 'message' => 'privacy:metadata:messages:message', | |
64 | 'issystem' => 'privacy:metadata:messages:issystem', | |
65 | 'timestamp' => 'privacy:metadata:messages:timestamp', | |
66 | ], 'privacy:metadata:messages'); | |
67 | ||
68 | // The tables chat_messages_current and chat_users are not reported here | |
69 | // because they are considered as short-lived data and are deleted on a | |
70 | // regular basis by cron, or during normal requests. MDL-62006 was raised | |
71 | // to discuss and/or implement support for those tables. | |
72 | ||
73 | return $collection; | |
74 | } | |
75 | ||
76 | /** | |
77 | * Get the list of contexts that contain user information for the specified user. | |
78 | * | |
79 | * @param int $userid The user to search. | |
80 | * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. | |
81 | */ | |
82 | public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist { | |
83 | $contextlist = new \core_privacy\local\request\contextlist(); | |
84 | ||
85 | $sql = " | |
86 | SELECT DISTINCT ctx.id | |
87 | FROM {chat} c | |
88 | JOIN {modules} m | |
89 | ON m.name = :chat | |
90 | JOIN {course_modules} cm | |
91 | ON cm.instance = c.id | |
92 | AND cm.module = m.id | |
93 | JOIN {context} ctx | |
94 | ON ctx.instanceid = cm.id | |
95 | AND ctx.contextlevel = :modulelevel | |
96 | JOIN {chat_messages} chm | |
97 | ON chm.chatid = c.id | |
98 | WHERE chm.userid = :userid"; | |
99 | ||
100 | $params = [ | |
101 | 'chat' => 'chat', | |
102 | 'modulelevel' => CONTEXT_MODULE, | |
103 | 'userid' => $userid, | |
104 | ]; | |
105 | $contextlist->add_from_sql($sql, $params); | |
106 | ||
107 | return $contextlist; | |
108 | } | |
109 | ||
110 | /** | |
111 | * Export all user data for the specified user, in the specified contexts. | |
112 | * | |
113 | * @param approved_contextlist $contextlist The approved contexts to export information for. | |
114 | */ | |
115 | public static function export_user_data(approved_contextlist $contextlist) { | |
116 | global $DB; | |
117 | ||
118 | $user = $contextlist->get_user(); | |
119 | $userid = $user->id; | |
120 | $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) { | |
121 | if ($context->contextlevel == CONTEXT_MODULE) { | |
122 | $carry[] = $context->instanceid; | |
123 | } | |
124 | return $carry; | |
125 | }, []); | |
126 | if (empty($cmids)) { | |
127 | return; | |
128 | } | |
129 | ||
130 | $chatidstocmids = static::get_chat_ids_to_cmids_from_cmids($cmids); | |
131 | $chatids = array_keys($chatidstocmids); | |
132 | ||
133 | // Export the messages. | |
134 | list($insql, $inparams) = $DB->get_in_or_equal($chatids, SQL_PARAMS_NAMED); | |
135 | $params = array_merge($inparams, ['userid' => $userid]); | |
136 | $recordset = $DB->get_recordset_select('chat_messages', "chatid $insql AND userid = :userid", $params, 'timestamp, id'); | |
137 | static::recordset_loop_and_export($recordset, 'chatid', [], function($carry, $record) use ($user, $chatidstocmids) { | |
138 | $message = $record->message; | |
139 | if ($record->issystem) { | |
140 | $message = get_string('message' . $record->message, 'mod_chat', fullname($user)); | |
141 | } | |
142 | $carry[] = [ | |
143 | 'message' => $message, | |
144 | 'sent_at' => transform::datetime($record->timestamp), | |
145 | 'is_system_generated' => transform::yesno($record->issystem), | |
146 | ]; | |
147 | return $carry; | |
148 | ||
149 | }, function($chatid, $data) use ($user, $chatidstocmids) { | |
150 | $context = context_module::instance($chatidstocmids[$chatid]); | |
151 | $contextdata = helper::get_context_data($context, $user); | |
152 | $finaldata = (object) array_merge((array) $contextdata, ['messages' => $data]); | |
153 | helper::export_context_files($context, $user); | |
154 | writer::with_context($context)->export_data([], $finaldata); | |
155 | }); | |
156 | } | |
157 | ||
158 | /** | |
159 | * Delete all data for all users in the specified context. | |
160 | * | |
161 | * @param context $context The specific context to delete data for. | |
162 | */ | |
163 | public static function delete_data_for_all_users_in_context(context $context) { | |
164 | global $DB; | |
165 | ||
166 | if ($context->contextlevel != CONTEXT_MODULE) { | |
167 | return; | |
168 | } | |
169 | ||
2b25b67e FM |
170 | $cm = get_coursemodule_from_id('chat', $context->instanceid); |
171 | if (!$cm) { | |
172 | return; | |
173 | } | |
174 | ||
175 | $chatid = $cm->instance; | |
bf3b4f3e FM |
176 | $DB->delete_records_select('chat_messages', 'chatid = :chatid', ['chatid' => $chatid]); |
177 | $DB->delete_records_select('chat_messages_current', 'chatid = :chatid', ['chatid' => $chatid]); | |
178 | $DB->delete_records_select('chat_users', 'chatid = :chatid', ['chatid' => $chatid]); | |
179 | } | |
180 | ||
181 | /** | |
182 | * Delete all user data for the specified user, in the specified contexts. | |
183 | * | |
184 | * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. | |
185 | */ | |
186 | public static function delete_data_for_user(approved_contextlist $contextlist) { | |
187 | global $DB; | |
188 | ||
189 | $userid = $contextlist->get_user()->id; | |
190 | $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) { | |
191 | if ($context->contextlevel == CONTEXT_MODULE) { | |
192 | $carry[] = $context->instanceid; | |
193 | } | |
194 | return $carry; | |
195 | }, []); | |
196 | if (empty($cmids)) { | |
197 | return; | |
198 | } | |
199 | ||
200 | $chatidstocmids = static::get_chat_ids_to_cmids_from_cmids($cmids); | |
201 | $chatids = array_keys($chatidstocmids); | |
202 | ||
203 | list($insql, $inparams) = $DB->get_in_or_equal($chatids, SQL_PARAMS_NAMED); | |
204 | $sql = "chatid $insql AND userid = :userid"; | |
205 | $params = array_merge($inparams, ['userid' => $userid]); | |
206 | ||
207 | $DB->delete_records_select('chat_messages', $sql, $params); | |
208 | $DB->delete_records_select('chat_messages_current', $sql, $params); | |
209 | $DB->delete_records_select('chat_users', $sql, $params); | |
210 | } | |
211 | ||
212 | /** | |
213 | * Return a dict of chat IDs mapped to their course module ID. | |
214 | * | |
215 | * @param array $cmids The course module IDs. | |
216 | * @return array In the form of [$chatid => $cmid]. | |
217 | */ | |
218 | protected static function get_chat_ids_to_cmids_from_cmids(array $cmids) { | |
219 | global $DB; | |
220 | list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED); | |
221 | $sql = " | |
222 | SELECT c.id, cm.id AS cmid | |
223 | FROM {chat} c | |
224 | JOIN {modules} m | |
225 | ON m.name = :chat | |
226 | JOIN {course_modules} cm | |
227 | ON cm.instance = c.id | |
228 | AND cm.module = m.id | |
229 | WHERE cm.id $insql"; | |
230 | $params = array_merge($inparams, ['chat' => 'chat']); | |
231 | return $DB->get_records_sql_menu($sql, $params); | |
232 | } | |
233 | ||
234 | /** | |
235 | * Loop and export from a recordset. | |
236 | * | |
237 | * @param moodle_recordset $recordset The recordset. | |
238 | * @param string $splitkey The record key to determine when to export. | |
239 | * @param mixed $initial The initial data to reduce from. | |
240 | * @param callable $reducer The function to return the dataset, receives current dataset, and the current record. | |
241 | * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset. | |
242 | * @return void | |
243 | */ | |
244 | protected static function recordset_loop_and_export(moodle_recordset $recordset, $splitkey, $initial, | |
245 | callable $reducer, callable $export) { | |
246 | ||
247 | $data = $initial; | |
248 | $lastid = null; | |
249 | ||
250 | foreach ($recordset as $record) { | |
251 | if ($lastid && $record->{$splitkey} != $lastid) { | |
252 | $export($lastid, $data); | |
253 | $data = $initial; | |
254 | } | |
255 | $data = $reducer($data, $record); | |
256 | $lastid = $record->{$splitkey}; | |
257 | } | |
258 | $recordset->close(); | |
259 | ||
260 | if (!empty($lastid)) { | |
261 | $export($lastid, $data); | |
262 | } | |
263 | } | |
264 | ||
265 | } |