See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
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 core_webservice 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 core_webservice\privacy; 27 defined('MOODLE_INTERNAL') || die(); 28 29 use context; 30 use context_user; 31 use core_privacy\local\metadata\collection; 32 use core_privacy\local\request\approved_contextlist; 33 use core_privacy\local\request\transform; 34 use core_privacy\local\request\writer; 35 use core_privacy\local\request\userlist; 36 use core_privacy\local\request\approved_userlist; 37 38 /** 39 * Data provider class. 40 * 41 * @package core_webservice 42 * @copyright 2018 Frédéric Massart 43 * @author Frédéric Massart <fred@branchup.tech> 44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 45 */ 46 class provider implements 47 \core_privacy\local\metadata\provider, 48 \core_privacy\local\request\core_userlist_provider, 49 \core_privacy\local\request\subsystem\provider { 50 51 /** 52 * Returns metadata. 53 * 54 * @param collection $collection The initialised collection to add items to. 55 * @return collection A listing of user data stored through this system. 56 */ 57 public static function get_metadata(collection $collection) : collection { 58 59 $collection->add_database_table('external_tokens', [ 60 'token' => 'privacy:metadata:tokens:token', 61 'privatetoken' => 'privacy:metadata:tokens:privatetoken', 62 'tokentype' => 'privacy:metadata:tokens:tokentype', 63 'userid' => 'privacy:metadata:tokens:userid', 64 'creatorid' => 'privacy:metadata:tokens:creatorid', 65 'iprestriction' => 'privacy:metadata:tokens:iprestriction', 66 'validuntil' => 'privacy:metadata:tokens:validuntil', 67 'timecreated' => 'privacy:metadata:tokens:timecreated', 68 'lastaccess' => 'privacy:metadata:tokens:lastaccess', 69 ], 'privacy:metadata:tokens'); 70 71 $collection->add_database_table('external_services_users', [ 72 'userid' => 'privacy:metadata:serviceusers:userid', 73 'iprestriction' => 'privacy:metadata:serviceusers:iprestriction', 74 'validuntil' => 'privacy:metadata:serviceusers:validuntil', 75 'timecreated' => 'privacy:metadata:serviceusers:timecreated', 76 ], 'privacy:metadata:serviceusers'); 77 78 return $collection; 79 } 80 81 /** 82 * Get the list of contexts that contain user information for the specified user. 83 * 84 * @param int $userid The user to search. 85 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. 86 */ 87 public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist { 88 $contextlist = new \core_privacy\local\request\contextlist(); 89 90 $sql = " 91 SELECT ctx.id 92 FROM {external_tokens} t 93 JOIN {context} ctx 94 ON ctx.instanceid = t.userid 95 AND ctx.contextlevel = :userlevel 96 WHERE t.userid = :userid1 97 OR t.creatorid = :userid2"; 98 $contextlist->add_from_sql($sql, ['userlevel' => CONTEXT_USER, 'userid1' => $userid, 'userid2' => $userid]); 99 100 $sql = " 101 SELECT ctx.id 102 FROM {external_services_users} su 103 JOIN {context} ctx 104 ON ctx.instanceid = su.userid 105 AND ctx.contextlevel = :userlevel 106 WHERE su.userid = :userid"; 107 $contextlist->add_from_sql($sql, ['userlevel' => CONTEXT_USER, 'userid' => $userid]); 108 109 return $contextlist; 110 } 111 112 /** 113 * Get the list of users within a specific context. 114 * 115 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 116 */ 117 public static function get_users_in_context(userlist $userlist) { 118 global $DB; 119 120 $context = $userlist->get_context(); 121 122 if (!$context instanceof \context_user) { 123 return; 124 } 125 126 $userid = $context->instanceid; 127 128 $hasdata = false; 129 $hasdata = $hasdata || $DB->record_exists_select('external_tokens', 'userid = ? OR creatorid = ?', [$userid, $userid]); 130 $hasdata = $hasdata || $DB->record_exists('external_services_users', ['userid' => $userid]); 131 132 if ($hasdata) { 133 $userlist->add_user($userid); 134 } 135 } 136 137 /** 138 * Export all user data for the specified user, in the specified contexts. 139 * 140 * @param approved_contextlist $contextlist The approved contexts to export information for. 141 */ 142 public static function export_user_data(approved_contextlist $contextlist) { 143 global $DB; 144 145 $userid = $contextlist->get_user()->id; 146 $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) use ($userid) { 147 if ($context->contextlevel == CONTEXT_USER) { 148 if ($context->instanceid == $userid) { 149 $carry['has_mine'] = true; 150 } else { 151 $carry['others'][] = $context->instanceid; 152 } 153 } 154 return $carry; 155 }, [ 156 'has_mine' => false, 157 'others' => [] 158 ]); 159 160 $path = [get_string('webservices', 'core_webservice')]; 161 162 // Exporting my stuff. 163 if ($contexts['has_mine']) { 164 165 $data = []; 166 167 // Exporting my tokens. 168 $sql = " 169 SELECT t.*, s.name as externalservicename 170 FROM {external_tokens} t 171 JOIN {external_services} s 172 ON s.id = t.externalserviceid 173 WHERE t.userid = :userid 174 ORDER BY t.id"; 175 $recordset = $DB->get_recordset_sql($sql, ['userid' => $userid]); 176 foreach ($recordset as $record) { 177 if (!isset($data['tokens'])) { 178 $data['tokens'] = []; 179 } 180 $data['tokens'][] = static::transform_token($record); 181 } 182 $recordset->close(); 183 184 // Exporting the services I have access to. 185 $sql = " 186 SELECT su.*, s.name as externalservicename 187 FROM {external_services_users} su 188 JOIN {external_services} s 189 ON s.id = su.externalserviceid 190 WHERE su.userid = :userid 191 ORDER BY su.id"; 192 $recordset = $DB->get_recordset_sql($sql, ['userid' => $userid]); 193 foreach ($recordset as $record) { 194 if (!isset($data['services_user'])) { 195 $data['services_user'] = []; 196 } 197 $data['services_user'][] = [ 198 'external_service' => $record->externalservicename, 199 'ip_restriction' => $record->iprestriction, 200 'valid_until' => $record->validuntil ? transform::datetime($record->validuntil) : null, 201 'created_on' => transform::datetime($record->timecreated), 202 ]; 203 } 204 $recordset->close(); 205 206 if (!empty($data)) { 207 writer::with_context(context_user::instance($userid))->export_data($path, (object) $data); 208 }; 209 } 210 211 // Exporting the tokens I created. 212 if (!empty($contexts['others'])) { 213 list($insql, $inparams) = $DB->get_in_or_equal($contexts['others'], SQL_PARAMS_NAMED); 214 $sql = " 215 SELECT t.*, s.name as externalservicename 216 FROM {external_tokens} t 217 JOIN {external_services} s 218 ON s.id = t.externalserviceid 219 WHERE t.userid $insql 220 AND t.creatorid = :userid1 221 AND t.userid <> :userid2 222 ORDER BY t.userid, t.id"; 223 $params = array_merge($inparams, ['userid1' => $userid, 'userid2' => $userid]); 224 $recordset = $DB->get_recordset_sql($sql, $params); 225 static::recordset_loop_and_export($recordset, 'userid', [], function($carry, $record) { 226 $carry[] = static::transform_token($record); 227 return $carry; 228 }, function($userid, $data) use ($path) { 229 writer::with_context(context_user::instance($userid))->export_related_data($path, 'created_by_you', (object) [ 230 'tokens' => $data 231 ]); 232 }); 233 } 234 } 235 236 /** 237 * Delete all data for all users in the specified context. 238 * 239 * @param context $context The specific context to delete data for. 240 */ 241 public static function delete_data_for_all_users_in_context(context $context) { 242 if ($context->contextlevel != CONTEXT_USER) { 243 return; 244 } 245 static::delete_user_data($context->instanceid); 246 } 247 248 /** 249 * Delete multiple users within a single context. 250 * 251 * @param approved_userlist $userlist The approved context and user information to delete information for. 252 */ 253 public static function delete_data_for_users(approved_userlist $userlist) { 254 255 $context = $userlist->get_context(); 256 257 if ($context instanceof \context_user) { 258 static::delete_user_data($context->instanceid); 259 } 260 } 261 262 /** 263 * Delete all user data for the specified user, in the specified contexts. 264 * 265 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 266 */ 267 public static function delete_data_for_user(approved_contextlist $contextlist) { 268 $userid = $contextlist->get_user()->id; 269 foreach ($contextlist as $context) { 270 if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) { 271 static::delete_user_data($context->instanceid); 272 break; 273 } 274 } 275 } 276 277 /** 278 * Delete user data. 279 * 280 * @param int $userid The user ID. 281 * @return void 282 */ 283 protected static function delete_user_data($userid) { 284 global $DB; 285 $DB->delete_records('external_tokens', ['userid' => $userid]); 286 $DB->delete_records('external_services_users', ['userid' => $userid]); 287 } 288 289 /** 290 * Transform a token entry. 291 * 292 * @param object $record The token record. 293 * @return array 294 */ 295 protected static function transform_token($record) { 296 $notexportedstr = get_string('privacy:request:notexportedsecurity', 'core_webservice'); 297 return [ 298 'external_service' => $record->externalservicename, 299 'token' => $notexportedstr, 300 'private_token' => $record->privatetoken ? $notexportedstr : null, 301 'ip_restriction' => $record->iprestriction, 302 'valid_until' => $record->validuntil ? transform::datetime($record->validuntil) : null, 303 'created_on' => transform::datetime($record->timecreated), 304 'last_access' => $record->lastaccess ? transform::datetime($record->lastaccess) : null, 305 ]; 306 } 307 308 /** 309 * Loop and export from a recordset. 310 * 311 * @param \moodle_recordset $recordset The recordset. 312 * @param string $splitkey The record key to determine when to export. 313 * @param mixed $initial The initial data to reduce from. 314 * @param callable $reducer The function to return the dataset, receives current dataset, and the current record. 315 * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset. 316 * @return void 317 */ 318 protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial, 319 callable $reducer, callable $export) { 320 321 $data = $initial; 322 $lastid = null; 323 324 foreach ($recordset as $record) { 325 if ($lastid && $record->{$splitkey} != $lastid) { 326 $export($lastid, $data); 327 $data = $initial; 328 } 329 $data = $reducer($data, $record); 330 $lastid = $record->{$splitkey}; 331 } 332 $recordset->close(); 333 334 if (!empty($lastid)) { 335 $export($lastid, $data); 336 } 337 } 338 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body