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_backup. 19 * 20 * @package core_backup 21 * @copyright 2018 Mark Nelson <markn@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace core_backup\privacy; 26 27 use core_privacy\local\metadata\collection; 28 use core_privacy\local\request\approved_contextlist; 29 use core_privacy\local\request\contextlist; 30 use core_privacy\local\request\transform; 31 use core_privacy\local\request\writer; 32 use core_privacy\local\request\userlist; 33 use core_privacy\local\request\approved_userlist; 34 35 defined('MOODLE_INTERNAL') || die(); 36 37 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 38 39 /** 40 * Privacy Subsystem implementation for core_backup. 41 * 42 * @copyright 2018 Mark Nelson <markn@moodle.com> 43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 */ 45 class provider implements 46 \core_privacy\local\metadata\provider, 47 \core_privacy\local\request\core_userlist_provider, 48 \core_privacy\local\request\subsystem\provider { 49 50 /** 51 * Return the fields which contain personal data. 52 * 53 * @param collection $items a reference to the collection to use to store the metadata. 54 * @return collection the updated collection of metadata items. 55 */ 56 public static function get_metadata(collection $items) : collection { 57 $items->link_external_location( 58 'Backup', 59 [ 60 'detailsofarchive' => 'privacy:metadata:backup:detailsofarchive' 61 ], 62 'privacy:metadata:backup:externalpurpose' 63 ); 64 65 $items->add_database_table( 66 'backup_controllers', 67 [ 68 'operation' => 'privacy:metadata:backup_controllers:operation', 69 'type' => 'privacy:metadata:backup_controllers:type', 70 'itemid' => 'privacy:metadata:backup_controllers:itemid', 71 'timecreated' => 'privacy:metadata:backup_controllers:timecreated', 72 'timemodified' => 'privacy:metadata:backup_controllers:timemodified' 73 ], 74 'privacy:metadata:backup_controllers' 75 ); 76 77 return $items; 78 } 79 80 /** 81 * Get the list of contexts that contain user information for the specified user. 82 * 83 * @param int $userid The user to search. 84 * @return contextlist The contextlist containing the list of contexts used in this plugin. 85 */ 86 public static function get_contexts_for_userid(int $userid) : contextlist { 87 $contextlist = new contextlist(); 88 89 $sql = "SELECT ctx.id 90 FROM {backup_controllers} bc 91 JOIN {context} ctx 92 ON ctx.instanceid = bc.itemid 93 AND ctx.contextlevel = :contextlevel 94 AND bc.type = :type 95 WHERE bc.userid = :userid"; 96 $params = [ 97 'contextlevel' => CONTEXT_COURSE, 98 'userid' => $userid, 99 'type' => 'course', 100 ]; 101 $contextlist->add_from_sql($sql, $params); 102 103 $sql = "SELECT ctx.id 104 FROM {backup_controllers} bc 105 JOIN {course_sections} c 106 ON bc.itemid = c.id 107 AND bc.type = :type 108 JOIN {context} ctx 109 ON ctx.instanceid = c.course 110 AND ctx.contextlevel = :contextlevel 111 WHERE bc.userid = :userid"; 112 $params = [ 113 'contextlevel' => CONTEXT_COURSE, 114 'userid' => $userid, 115 'type' => 'section', 116 ]; 117 $contextlist->add_from_sql($sql, $params); 118 119 $sql = "SELECT ctx.id 120 FROM {backup_controllers} bc 121 JOIN {context} ctx 122 ON ctx.instanceid = bc.itemid 123 AND ctx.contextlevel = :contextlevel 124 AND bc.type = :type 125 WHERE bc.userid = :userid"; 126 $params = [ 127 'contextlevel' => CONTEXT_MODULE, 128 'userid' => $userid, 129 'type' => 'activity', 130 ]; 131 $contextlist->add_from_sql($sql, $params); 132 133 return $contextlist; 134 } 135 136 /** 137 * Get the list of users within a specific context. 138 * 139 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 140 */ 141 public static function get_users_in_context(userlist $userlist) { 142 $context = $userlist->get_context(); 143 144 if ($context instanceof \context_course) { 145 $params = ['courseid' => $context->instanceid]; 146 147 $sql = "SELECT bc.userid 148 FROM {backup_controllers} bc 149 WHERE bc.itemid = :courseid 150 AND bc.type = :typecourse"; 151 152 $courseparams = ['typecourse' => 'course'] + $params; 153 154 $userlist->add_from_sql('userid', $sql, $courseparams); 155 156 $sql = "SELECT bc.userid 157 FROM {backup_controllers} bc 158 JOIN {course_sections} c 159 ON bc.itemid = c.id 160 WHERE c.course = :courseid 161 AND bc.type = :typesection"; 162 163 $sectionparams = ['typesection' => 'section'] + $params; 164 165 $userlist->add_from_sql('userid', $sql, $sectionparams); 166 } 167 168 if ($context instanceof \context_module) { 169 $params = [ 170 'cmid' => $context->instanceid, 171 'typeactivity' => 'activity' 172 ]; 173 174 $sql = "SELECT bc.userid 175 FROM {backup_controllers} bc 176 WHERE bc.itemid = :cmid 177 AND bc.type = :typeactivity"; 178 179 $userlist->add_from_sql('userid', $sql, $params); 180 } 181 } 182 183 /** 184 * Export all user data for the specified user, in the specified contexts. 185 * 186 * @param approved_contextlist $contextlist The approved contexts to export information for. 187 */ 188 public static function export_user_data(approved_contextlist $contextlist) { 189 global $DB; 190 191 if (empty($contextlist->count())) { 192 return; 193 } 194 195 $user = $contextlist->get_user(); 196 197 list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); 198 199 $sql = "SELECT bc.* 200 FROM {backup_controllers} bc 201 JOIN {context} ctx 202 ON ctx.instanceid = bc.itemid AND ctx.contextlevel = :contextlevel 203 WHERE ctx.id {$contextsql} 204 AND bc.userid = :userid 205 ORDER BY bc.timecreated ASC"; 206 $params = ['contextlevel' => CONTEXT_COURSE, 'userid' => $user->id] + $contextparams; 207 $backupcontrollers = $DB->get_recordset_sql($sql, $params); 208 self::recordset_loop_and_export($backupcontrollers, 'itemid', [], function($carry, $record) { 209 $carry[] = [ 210 'operation' => $record->operation, 211 'type' => $record->type, 212 'itemid' => $record->itemid, 213 'timecreated' => transform::datetime($record->timecreated), 214 'timemodified' => transform::datetime($record->timemodified), 215 ]; 216 return $carry; 217 }, function($courseid, $data) { 218 $context = \context_course::instance($courseid); 219 $finaldata = (object) $data; 220 writer::with_context($context)->export_data([get_string('backup'), $courseid], $finaldata); 221 }); 222 } 223 224 /** 225 * Delete all user data which matches the specified context. 226 * Only dealing with the specific context - not it's child contexts. 227 * 228 * @param \context $context A user context. 229 */ 230 public static function delete_data_for_all_users_in_context(\context $context) { 231 global $DB; 232 233 if ($context instanceof \context_course) { 234 $sectionsql = "itemid IN (SELECT id FROM {course_sections} WHERE course = ?) AND type = ?"; 235 $DB->delete_records_select('backup_controllers', $sectionsql, [$context->instanceid, \backup::TYPE_1SECTION]); 236 $DB->delete_records('backup_controllers', ['itemid' => $context->instanceid, 'type' => \backup::TYPE_1COURSE]); 237 } 238 if ($context instanceof \context_module) { 239 $DB->delete_records('backup_controllers', ['itemid' => $context->instanceid, 'type' => \backup::TYPE_1ACTIVITY]); 240 } 241 return; 242 } 243 244 /** 245 * Delete multiple users within a single context. 246 * Only dealing with the specific context - not it's child contexts. 247 * 248 * @param approved_userlist $userlist The approved context and user information to delete information for. 249 */ 250 public static function delete_data_for_users(approved_userlist $userlist) { 251 global $DB; 252 253 if (empty($userlist->get_userids())) { 254 return; 255 } 256 257 $context = $userlist->get_context(); 258 if ($context instanceof \context_course) { 259 list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); 260 $select = "itemid = :itemid AND userid {$usersql} AND type = :type"; 261 $params = $userparams; 262 $params['itemid'] = $context->instanceid; 263 $params['type'] = \backup::TYPE_1COURSE; 264 265 $DB->delete_records_select('backup_controllers', $select, $params); 266 267 $params = $userparams; 268 $params['course'] = $context->instanceid; 269 $params['type'] = \backup::TYPE_1SECTION; 270 $sectionsql = "itemid IN (SELECT id FROM {course_sections} WHERE course = :course)"; 271 $select = $sectionsql . " AND userid {$usersql} AND type = :type"; 272 $DB->delete_records_select('backup_controllers', $select, $params); 273 } 274 if ($context instanceof \context_module) { 275 list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); 276 $select = "itemid = :itemid AND userid {$usersql} AND type = :type"; 277 $params = $userparams; 278 $params['itemid'] = $context->instanceid; 279 $params['type'] = \backup::TYPE_1ACTIVITY; 280 281 // Delete activity backup data. 282 $select = "itemid = :itemid AND type = :type AND userid {$usersql}"; 283 $params = ['itemid' => $context->instanceid, 'type' => 'activity'] + $userparams; 284 $DB->delete_records_select('backup_controllers', $select, $params); 285 } 286 } 287 288 /** 289 * Delete all user data for the specified user, in the specified contexts. 290 * Only dealing with the specific context - not it's child contexts. 291 * 292 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 293 */ 294 public static function delete_data_for_user(approved_contextlist $contextlist) { 295 global $DB; 296 297 if (empty($contextlist->count())) { 298 return; 299 } 300 301 $userid = $contextlist->get_user()->id; 302 foreach ($contextlist->get_contexts() as $context) { 303 if ($context instanceof \context_course) { 304 $select = "itemid = :itemid AND userid = :userid AND type = :type"; 305 $params = [ 306 'userid' => $userid, 307 'itemid' => $context->instanceid, 308 'type' => \backup::TYPE_1COURSE 309 ]; 310 311 $DB->delete_records_select('backup_controllers', $select, $params); 312 313 $params = [ 314 'userid' => $userid, 315 'course' => $context->instanceid, 316 'type' => \backup::TYPE_1SECTION 317 ]; 318 $sectionsql = "itemid IN (SELECT id FROM {course_sections} WHERE course = :course)"; 319 $select = $sectionsql . " AND userid = :userid AND type = :type"; 320 $DB->delete_records_select('backup_controllers', $select, $params); 321 } 322 if ($context instanceof \context_module) { 323 list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); 324 $select = "itemid = :itemid AND userid = :userid AND type = :type"; 325 $params = [ 326 'itemid' => $context->instanceid, 327 'userid' => $userid, 328 'type' => \backup::TYPE_1ACTIVITY 329 ]; 330 331 $DB->delete_records_select('backup_controllers', $select, $params); 332 } 333 334 } 335 } 336 337 /** 338 * Loop and export from a recordset. 339 * 340 * @param \moodle_recordset $recordset The recordset. 341 * @param string $splitkey The record key to determine when to export. 342 * @param mixed $initial The initial data to reduce from. 343 * @param callable $reducer The function to return the dataset, receives current dataset, and the current record. 344 * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset. 345 * @return void 346 */ 347 protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial, 348 callable $reducer, callable $export) { 349 $data = $initial; 350 $lastid = null; 351 352 foreach ($recordset as $record) { 353 if ($lastid && $record->{$splitkey} != $lastid) { 354 $export($lastid, $data); 355 $data = $initial; 356 } 357 $data = $reducer($data, $record); 358 $lastid = $record->{$splitkey}; 359 } 360 $recordset->close(); 361 362 if (!empty($lastid)) { 363 $export($lastid, $data); 364 } 365 } 366 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body