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 namespace mod_bigbluebuttonbn\privacy; 18 19 use core_privacy\local\metadata\collection; 20 use core_privacy\local\request\approved_contextlist; 21 use core_privacy\local\request\approved_userlist; 22 use core_privacy\local\request\contextlist; 23 use core_privacy\local\request\helper; 24 use core_privacy\local\request\transform; 25 use core_privacy\local\request\userlist; 26 use core_privacy\local\request\writer; 27 28 /** 29 * Privacy class for requesting user data. 30 * 31 * @package mod_bigbluebuttonbn 32 * @copyright 2018 - present, Blindside Networks Inc 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com) 35 */ 36 class provider implements 37 // This plugin has data. 38 \core_privacy\local\metadata\provider, 39 40 // This plugin currently implements the original plugin\provider interface. 41 \core_privacy\local\request\plugin\provider, 42 43 // This plugin is capable of determining which users have data within it. 44 \core_privacy\local\request\core_userlist_provider { 45 46 /** 47 * Returns metadata. 48 * 49 * @param collection $collection The initialised collection to add items to. 50 * @return collection A listing of user data stored through this system. 51 */ 52 public static function get_metadata(collection $collection): collection { 53 54 // The table bigbluebuttonbn stores only the room properties. 55 // However, there is a chance that some personal information is stored as metadata. 56 // This would be done in the column 'participants' where rules can be set to define BBB roles. 57 // It is fair to say that only the userid is stored, which is useless if user is removed. 58 // But if this is a concern a refactoring on the way the rules are stored will be required. 59 $collection->add_database_table('bigbluebuttonbn', [ 60 'participants' => 'privacy:metadata:bigbluebuttonbn:participants', 61 ], 'privacy:metadata:bigbluebuttonbn'); 62 63 // The table bigbluebuttonbn_logs stores events triggered by users when using the plugin. 64 // Some personal information along with the resource accessed is stored. 65 $collection->add_database_table('bigbluebuttonbn_logs', [ 66 'userid' => 'privacy:metadata:bigbluebuttonbn_logs:userid', 67 'timecreated' => 'privacy:metadata:bigbluebuttonbn_logs:timecreated', 68 'meetingid' => 'privacy:metadata:bigbluebuttonbn_logs:meetingid', 69 'log' => 'privacy:metadata:bigbluebuttonbn_logs:log', 70 'meta' => 'privacy:metadata:bigbluebuttonbn_logs:meta', 71 ], 'privacy:metadata:bigbluebuttonbn_logs'); 72 73 $collection->add_database_table('bigbluebuttonbn_recordings', [ 74 'userid' => 'privacy:metadata:bigbluebuttonbn_logs:userid', 75 ], 'privacy:metadata:bigbluebuttonbn_recordings'); 76 77 // Personal information has to be passed to BigBlueButton. 78 // This includes the user ID and fullname. 79 $collection->add_external_location_link('bigbluebutton', [ 80 'userid' => 'privacy:metadata:bigbluebutton:userid', 81 'fullname' => 'privacy:metadata:bigbluebutton:fullname', 82 ], 'privacy:metadata:bigbluebutton'); 83 84 return $collection; 85 } 86 87 /** 88 * Get the list of contexts that contain user information for the specified user. 89 * 90 * @param int $userid The user to search. 91 * @return contextlist $contextlist The list of contexts used in this plugin. 92 */ 93 public static function get_contexts_for_userid(int $userid): contextlist { 94 // If user was already deleted, do nothing. 95 if (!\core_user::get_user($userid)) { 96 return new contextlist(); 97 } 98 // Fetch all bigbluebuttonbn logs. 99 $sql = "SELECT c.id 100 FROM {context} c 101 INNER JOIN {course_modules} cm 102 ON cm.id = c.instanceid 103 AND c.contextlevel = :contextlevel 104 INNER JOIN {modules} m 105 ON m.id = cm.module 106 AND m.name = :modname 107 INNER JOIN {bigbluebuttonbn} bigbluebuttonbn 108 ON bigbluebuttonbn.id = cm.instance 109 INNER JOIN {bigbluebuttonbn_logs} bigbluebuttonbnlogs 110 ON bigbluebuttonbnlogs.bigbluebuttonbnid = bigbluebuttonbn.id 111 WHERE bigbluebuttonbnlogs.userid = :userid"; 112 113 $params = [ 114 'modname' => 'bigbluebuttonbn', 115 'contextlevel' => CONTEXT_MODULE, 116 'userid' => $userid, 117 ]; 118 $contextlist = new contextlist(); 119 $contextlist->add_from_sql($sql, $params); 120 return $contextlist; 121 } 122 123 /** 124 * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist. 125 * 126 * @param approved_contextlist $contextlist a list of contexts approved for export. 127 */ 128 public static function export_user_data(approved_contextlist $contextlist) { 129 global $DB; 130 131 // Filter out any contexts that are not related to modules. 132 $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) { 133 if ($context->contextlevel == CONTEXT_MODULE) { 134 $carry[] = $context->instanceid; 135 } 136 return $carry; 137 }, []); 138 139 if (empty($cmids)) { 140 return; 141 } 142 143 $user = $contextlist->get_user(); 144 145 // Get all the bigbluebuttonbn activities associated with the above course modules. 146 $instanceidstocmids = self::get_instance_ids_to_cmids_from_cmids($cmids); 147 $instanceids = array_keys($instanceidstocmids); 148 149 list($insql, $inparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED); 150 $params = array_merge($inparams, ['userid' => $user->id]); 151 $recordset = $DB->get_recordset_select( 152 'bigbluebuttonbn_logs', 153 "bigbluebuttonbnid $insql AND userid = :userid", 154 $params, 155 'timecreated, id' 156 ); 157 self::recordset_loop_and_export($recordset, 'bigbluebuttonbnid', [], 158 function($carry, $record) use ($user, $instanceidstocmids) { 159 $carry[] = [ 160 'timecreated' => transform::datetime($record->timecreated), 161 'meetingid' => $record->meetingid, 162 'log' => $record->log, 163 'meta' => $record->meta, 164 ]; 165 return $carry; 166 }, 167 function($instanceid, $data) use ($user, $instanceidstocmids) { 168 $context = \context_module::instance($instanceidstocmids[$instanceid]); 169 $contextdata = helper::get_context_data($context, $user); 170 $finaldata = (object) array_merge((array) $contextdata, ['logs' => $data]); 171 helper::export_context_files($context, $user); 172 writer::with_context($context)->export_data([], $finaldata); 173 } 174 ); 175 } 176 177 /** 178 * Delete all data for all users in the specified context. 179 * 180 * @param \context $context the context to delete in. 181 */ 182 public static function delete_data_for_all_users_in_context(\context $context) { 183 global $DB; 184 185 if (!$context instanceof \context_module) { 186 return; 187 } 188 189 $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST); 190 $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid]); 191 } 192 193 /** 194 * Delete all user data for the specified user, in the specified contexts. 195 * 196 * @param approved_contextlist $contextlist a list of contexts approved for deletion. 197 */ 198 public static function delete_data_for_user(approved_contextlist $contextlist) { 199 global $DB; 200 $count = $contextlist->count(); 201 if (empty($count)) { 202 return; 203 } 204 $userid = $contextlist->get_user()->id; 205 foreach ($contextlist->get_contexts() as $context) { 206 if (!$context instanceof \context_module) { 207 return; 208 } 209 $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST); 210 $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid, 'userid' => $userid]); 211 } 212 } 213 214 /** 215 * Return a dict of bigbluebuttonbn IDs mapped to their course module ID. 216 * 217 * @param array $cmids The course module IDs. 218 * @return array In the form of [$bigbluebuttonbnid => $cmid]. 219 */ 220 protected static function get_instance_ids_to_cmids_from_cmids(array $cmids) { 221 global $DB; 222 223 list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED); 224 $sql = "SELECT bigbluebuttonbn.id, cm.id AS cmid 225 FROM {bigbluebuttonbn} bigbluebuttonbn 226 JOIN {modules} m 227 ON m.name = :bigbluebuttonbn 228 JOIN {course_modules} cm 229 ON cm.instance = bigbluebuttonbn.id 230 AND cm.module = m.id 231 WHERE cm.id $insql"; 232 $params = array_merge($inparams, ['bigbluebuttonbn' => 'bigbluebuttonbn']); 233 234 return $DB->get_records_sql_menu($sql, $params); 235 } 236 237 /** 238 * Loop and export from a recordset. 239 * 240 * @param \moodle_recordset $recordset The recordset. 241 * @param string $splitkey The record key to determine when to export. 242 * @param mixed $initial The initial data to reduce from. 243 * @param callable $reducer The function to return the dataset, receives current dataset, and the current record. 244 * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset. 245 * @return void 246 */ 247 protected static function recordset_loop_and_export( 248 \moodle_recordset $recordset, 249 $splitkey, 250 $initial, 251 callable $reducer, 252 callable $export 253 ) { 254 $data = $initial; 255 $lastid = null; 256 257 foreach ($recordset as $record) { 258 if ($lastid && $record->{$splitkey} != $lastid) { 259 $export($lastid, $data); 260 $data = $initial; 261 } 262 $data = $reducer($data, $record); 263 $lastid = $record->{$splitkey}; 264 } 265 $recordset->close(); 266 267 if (!empty($lastid)) { 268 $export($lastid, $data); 269 } 270 } 271 272 /** 273 * Get the list of users who have data within a context. 274 * 275 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 276 */ 277 public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) { 278 $context = $userlist->get_context(); 279 280 if (!$context instanceof \context_module) { 281 return; 282 } 283 284 $params = [ 285 'instanceid' => $context->instanceid, 286 'modulename' => 'bigbluebuttonbn', 287 ]; 288 289 $sql = "SELECT bnl.userid 290 FROM {course_modules} cm 291 JOIN {modules} m ON m.id = cm.module AND m.name = :modulename 292 JOIN {bigbluebuttonbn} bn ON bn.id = cm.instance 293 JOIN {bigbluebuttonbn_logs} bnl ON bnl.bigbluebuttonbnid = bn.id 294 WHERE cm.id = :instanceid"; 295 296 $userlist->add_from_sql('userid', $sql, $params); 297 } 298 299 /** 300 * Delete multiple users within a single context. 301 * 302 * @param approved_userlist $userlist The approved context and user information to delete information for. 303 */ 304 public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) { 305 global $DB; 306 307 $context = $userlist->get_context(); 308 $cm = $DB->get_record('course_modules', ['id' => $context->instanceid]); 309 310 list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); 311 $params = array_merge(['bigbluebuttonbnid' => $cm->instance], $userinparams); 312 $sql = "bigbluebuttonbnid = :bigbluebuttonbnid AND userid {$userinsql}"; 313 314 $DB->delete_records_select('bigbluebuttonbn_logs', $sql, $params); 315 } 316 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body