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\approved_userlist; 37 use core_privacy\local\request\contextlist; 38 use core_privacy\local\request\helper; 39 use core_privacy\local\request\transform; 40 use core_privacy\local\request\userlist; 41 use core_privacy\local\request\writer; 42 43 /** 44 * Data provider class. 45 * 46 * @package mod_chat 47 * @copyright 2018 Frédéric Massart 48 * @author Frédéric Massart <fred@branchup.tech> 49 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 50 */ 51 class provider implements 52 \core_privacy\local\metadata\provider, 53 \core_privacy\local\request\core_userlist_provider, 54 \core_privacy\local\request\plugin\provider { 55 56 /** 57 * Returns metadata. 58 * 59 * @param collection $collection The initialised collection to add items to. 60 * @return collection A listing of user data stored through this system. 61 */ 62 public static function get_metadata(collection $collection) : collection { 63 64 $collection->add_database_table('chat_messages', [ 65 'userid' => 'privacy:metadata:messages:userid', 66 'message' => 'privacy:metadata:messages:message', 67 'issystem' => 'privacy:metadata:messages:issystem', 68 'timestamp' => 'privacy:metadata:messages:timestamp', 69 ], 'privacy:metadata:messages'); 70 71 // The tables chat_messages_current and chat_users are not exported/deleted 72 // because they are considered as short-lived data and are deleted on a 73 // regular basis by cron, or during normal requests. TODO MDL-62006. 74 75 $collection->add_database_table('chat_messages_current', [ 76 'userid' => 'privacy:metadata:messages:userid', 77 'message' => 'privacy:metadata:messages:message', 78 'issystem' => 'privacy:metadata:messages:issystem', 79 'timestamp' => 'privacy:metadata:messages:timestamp' 80 ], 'privacy:metadata:chat_messages_current'); 81 82 $collection->add_database_table('chat_users', [ 83 'userid' => 'privacy:metadata:chat_users:userid', 84 'version' => 'privacy:metadata:chat_users:version', 85 'ip' => 'privacy:metadata:chat_users:ip', 86 'firstping' => 'privacy:metadata:chat_users:firstping', 87 'lastping' => 'privacy:metadata:chat_users:lastping', 88 'lastmessageping' => 'privacy:metadata:chat_users:lastmessageping', 89 'lang' => 'privacy:metadata:chat_users:lang' 90 ], 'privacy:metadata:chat_users'); 91 92 return $collection; 93 } 94 95 /** 96 * Get the list of contexts that contain user information for the specified user. 97 * 98 * @param int $userid The user to search. 99 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. 100 */ 101 public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist { 102 $contextlist = new \core_privacy\local\request\contextlist(); 103 104 $sql = " 105 SELECT DISTINCT ctx.id 106 FROM {chat} c 107 JOIN {modules} m 108 ON m.name = :chat 109 JOIN {course_modules} cm 110 ON cm.instance = c.id 111 AND cm.module = m.id 112 JOIN {context} ctx 113 ON ctx.instanceid = cm.id 114 AND ctx.contextlevel = :modulelevel 115 JOIN {chat_messages} chm 116 ON chm.chatid = c.id 117 WHERE chm.userid = :userid"; 118 119 $params = [ 120 'chat' => 'chat', 121 'modulelevel' => CONTEXT_MODULE, 122 'userid' => $userid, 123 ]; 124 $contextlist->add_from_sql($sql, $params); 125 126 return $contextlist; 127 } 128 129 /** 130 * Get the list of users who have data within a context. 131 * 132 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 133 */ 134 public static function get_users_in_context(userlist $userlist) { 135 $context = $userlist->get_context(); 136 137 if (!is_a($context, \context_module::class)) { 138 return; 139 } 140 141 $params = [ 142 'instanceid' => $context->instanceid, 143 'modulename' => 'chat', 144 ]; 145 146 $sql = "SELECT chm.userid 147 FROM {course_modules} cm 148 JOIN {modules} m ON m.id = cm.module AND m.name = :modulename 149 JOIN {chat} c ON c.id = cm.instance 150 JOIN {chat_messages} chm ON chm.chatid = c.id 151 WHERE cm.id = :instanceid"; 152 153 $userlist->add_from_sql('userid', $sql, $params); 154 } 155 156 /** 157 * Export all user data for the specified user, in the specified contexts. 158 * 159 * @param approved_contextlist $contextlist The approved contexts to export information for. 160 */ 161 public static function export_user_data(approved_contextlist $contextlist) { 162 global $DB; 163 164 $user = $contextlist->get_user(); 165 $userid = $user->id; 166 $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) { 167 if ($context->contextlevel == CONTEXT_MODULE) { 168 $carry[] = $context->instanceid; 169 } 170 return $carry; 171 }, []); 172 if (empty($cmids)) { 173 return; 174 } 175 176 $chatidstocmids = static::get_chat_ids_to_cmids_from_cmids($cmids); 177 $chatids = array_keys($chatidstocmids); 178 179 // Export the messages. 180 list($insql, $inparams) = $DB->get_in_or_equal($chatids, SQL_PARAMS_NAMED); 181 $params = array_merge($inparams, ['userid' => $userid]); 182 $recordset = $DB->get_recordset_select('chat_messages', "chatid $insql AND userid = :userid", $params, 'timestamp, id'); 183 static::recordset_loop_and_export($recordset, 'chatid', [], function($carry, $record) use ($user, $chatidstocmids) { 184 $message = $record->message; 185 if ($record->issystem) { 186 $message = get_string('message' . $record->message, 'mod_chat', fullname($user)); 187 } 188 $carry[] = [ 189 'message' => $message, 190 'sent_at' => transform::datetime($record->timestamp), 191 'is_system_generated' => transform::yesno($record->issystem), 192 ]; 193 return $carry; 194 195 }, function($chatid, $data) use ($user, $chatidstocmids) { 196 $context = context_module::instance($chatidstocmids[$chatid]); 197 $contextdata = helper::get_context_data($context, $user); 198 $finaldata = (object) array_merge((array) $contextdata, ['messages' => $data]); 199 helper::export_context_files($context, $user); 200 writer::with_context($context)->export_data([], $finaldata); 201 }); 202 } 203 204 /** 205 * Delete all data for all users in the specified context. 206 * 207 * @param context $context The specific context to delete data for. 208 */ 209 public static function delete_data_for_all_users_in_context(context $context) { 210 global $DB; 211 212 if ($context->contextlevel != CONTEXT_MODULE) { 213 return; 214 } 215 216 $cm = get_coursemodule_from_id('chat', $context->instanceid); 217 if (!$cm) { 218 return; 219 } 220 221 $chatid = $cm->instance; 222 $DB->delete_records_select('chat_messages', 'chatid = :chatid', ['chatid' => $chatid]); 223 $DB->delete_records_select('chat_messages_current', 'chatid = :chatid', ['chatid' => $chatid]); 224 $DB->delete_records_select('chat_users', 'chatid = :chatid', ['chatid' => $chatid]); 225 } 226 227 /** 228 * Delete all user data for the specified user, in the specified contexts. 229 * 230 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 231 */ 232 public static function delete_data_for_user(approved_contextlist $contextlist) { 233 global $DB; 234 235 $userid = $contextlist->get_user()->id; 236 $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) { 237 if ($context->contextlevel == CONTEXT_MODULE) { 238 $carry[] = $context->instanceid; 239 } 240 return $carry; 241 }, []); 242 if (empty($cmids)) { 243 return; 244 } 245 246 $chatidstocmids = static::get_chat_ids_to_cmids_from_cmids($cmids); 247 $chatids = array_keys($chatidstocmids); 248 249 list($insql, $inparams) = $DB->get_in_or_equal($chatids, SQL_PARAMS_NAMED); 250 $sql = "chatid $insql AND userid = :userid"; 251 $params = array_merge($inparams, ['userid' => $userid]); 252 253 $DB->delete_records_select('chat_messages', $sql, $params); 254 $DB->delete_records_select('chat_messages_current', $sql, $params); 255 $DB->delete_records_select('chat_users', $sql, $params); 256 } 257 258 259 /** 260 * Delete multiple users within a single context. 261 * 262 * @param approved_userlist $userlist The approved context and user information to delete information for. 263 */ 264 public static function delete_data_for_users(approved_userlist $userlist) { 265 global $DB; 266 267 $context = $userlist->get_context(); 268 $cm = $DB->get_record('course_modules', ['id' => $context->instanceid]); 269 $chat = $DB->get_record('chat', ['id' => $cm->instance]); 270 271 list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); 272 $params = array_merge(['chatid' => $chat->id], $userinparams); 273 $sql = "chatid = :chatid AND userid {$userinsql}"; 274 275 $DB->delete_records_select('chat_messages', $sql, $params); 276 $DB->delete_records_select('chat_messages_current', $sql, $params); 277 $DB->delete_records_select('chat_users', $sql, $params); 278 } 279 280 /** 281 * Return a dict of chat IDs mapped to their course module ID. 282 * 283 * @param array $cmids The course module IDs. 284 * @return array In the form of [$chatid => $cmid]. 285 */ 286 protected static function get_chat_ids_to_cmids_from_cmids(array $cmids) { 287 global $DB; 288 list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED); 289 $sql = " 290 SELECT c.id, cm.id AS cmid 291 FROM {chat} c 292 JOIN {modules} m 293 ON m.name = :chat 294 JOIN {course_modules} cm 295 ON cm.instance = c.id 296 AND cm.module = m.id 297 WHERE cm.id $insql"; 298 $params = array_merge($inparams, ['chat' => 'chat']); 299 return $DB->get_records_sql_menu($sql, $params); 300 } 301 302 /** 303 * Loop and export from a recordset. 304 * 305 * @param moodle_recordset $recordset The recordset. 306 * @param string $splitkey The record key to determine when to export. 307 * @param mixed $initial The initial data to reduce from. 308 * @param callable $reducer The function to return the dataset, receives current dataset, and the current record. 309 * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset. 310 * @return void 311 */ 312 protected static function recordset_loop_and_export(moodle_recordset $recordset, $splitkey, $initial, 313 callable $reducer, callable $export) { 314 315 $data = $initial; 316 $lastid = null; 317 318 foreach ($recordset as $record) { 319 if ($lastid && $record->{$splitkey} != $lastid) { 320 $export($lastid, $data); 321 $data = $initial; 322 } 323 $data = $reducer($data, $record); 324 $lastid = $record->{$splitkey}; 325 } 326 $recordset->close(); 327 328 if (!empty($lastid)) { 329 $export($lastid, $data); 330 } 331 } 332 333 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body