Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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 * Adhoc task that processes an approved data request and prepares/deletes the user's data. 19 * 20 * @package tool_dataprivacy 21 * @copyright 2018 Jun Pataleta 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace tool_dataprivacy\task; 26 27 use action_link; 28 use coding_exception; 29 use context_system; 30 use core\message\message; 31 use core\task\adhoc_task; 32 use core_user; 33 use moodle_exception; 34 use moodle_url; 35 use tool_dataprivacy\api; 36 use tool_dataprivacy\data_request; 37 38 /** 39 * Class that processes an approved data request and prepares/deletes the user's data. 40 * 41 * Custom data accepted: 42 * - requestid -> The ID of the data request to be processed. 43 * 44 * @package tool_dataprivacy 45 * @copyright 2018 Jun Pataleta 46 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 47 */ 48 class process_data_request_task extends adhoc_task { 49 50 /** 51 * Run the task to initiate the data request process. 52 * 53 * @throws coding_exception 54 * @throws moodle_exception 55 */ 56 public function execute() { 57 global $CFG, $PAGE, $SITE; 58 59 require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php"); 60 61 if (!isset($this->get_custom_data()->requestid)) { 62 throw new coding_exception('The custom data \'requestid\' is required.'); 63 } 64 $requestid = $this->get_custom_data()->requestid; 65 66 $requestpersistent = new data_request($requestid); 67 $request = $requestpersistent->to_record(); 68 69 // Check if this request still needs to be processed. e.g. The user might have cancelled it before this task has run. 70 $status = $requestpersistent->get('status'); 71 if (!api::is_active($status)) { 72 mtrace("Request {$requestid} with status {$status} doesn't need to be processed. Skipping..."); 73 return; 74 } 75 76 if (!\tool_dataprivacy\data_registry::defaults_set()) { 77 // Warn if no site purpose is defined. 78 mtrace('Warning: No purpose is defined at the system level. Deletion will delete all.'); 79 } 80 81 // Grab the manager. 82 // We set an observer against it to handle failures. 83 $allowfiltering = get_config('tool_dataprivacy', 'allowfiltering'); 84 $manager = new \core_privacy\manager(); 85 $manager->set_observer(new \tool_dataprivacy\manager_observer()); 86 87 // Get the user details now. We might not be able to retrieve it later if it's a deletion processing. 88 $foruser = core_user::get_user($request->userid); 89 90 // Update the status of this request as pre-processing. 91 mtrace('Pre-processing request...'); 92 api::update_request_status($requestid, api::DATAREQUEST_STATUS_PROCESSING); 93 $contextlistcollection = $manager->get_contexts_for_userid($requestpersistent->get('userid')); 94 95 mtrace('Fetching approved contextlists from collection'); 96 97 mtrace('Processing request...'); 98 $completestatus = api::DATAREQUEST_STATUS_COMPLETE; 99 $deleteuser = false; 100 101 if ($request->type == api::DATAREQUEST_TYPE_EXPORT) { 102 // Get the user context. 103 if ($allowfiltering) { 104 // Get the collection of approved_contextlist objects needed for core_privacy data export. 105 $approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent); 106 } else { 107 $approvedclcollection = api::get_approved_contextlist_collection_for_collection( 108 $contextlistcollection, 109 $foruser, 110 $request->type, 111 ); 112 } 113 114 $usercontext = \context_user::instance($foruser->id, IGNORE_MISSING); 115 if (!$usercontext) { 116 mtrace("Request {$requestid} cannot be processed due to a missing user context instance for the user 117 with ID {$foruser->id}. Skipping..."); 118 return; 119 } 120 121 // Export the data. 122 $exportedcontent = $manager->export_user_data($approvedclcollection); 123 124 $fs = get_file_storage(); 125 $filerecord = new \stdClass; 126 $filerecord->component = 'tool_dataprivacy'; 127 $filerecord->contextid = $usercontext->id; 128 $filerecord->userid = $foruser->id; 129 $filerecord->filearea = 'export'; 130 $filerecord->filename = 'export.zip'; 131 $filerecord->filepath = '/'; 132 $filerecord->itemid = $requestid; 133 $filerecord->license = $CFG->sitedefaultlicense; 134 $filerecord->author = fullname($foruser); 135 // Save somewhere. 136 $thing = $fs->create_file_from_pathname($filerecord, $exportedcontent); 137 $completestatus = api::DATAREQUEST_STATUS_DOWNLOAD_READY; 138 } else if ($request->type == api::DATAREQUEST_TYPE_DELETE) { 139 // Delete the data for users other than the primary admin, which is rejected. 140 if (is_primary_admin($foruser->id)) { 141 $completestatus = api::DATAREQUEST_STATUS_REJECTED; 142 } else { 143 $approvedclcollection = api::get_approved_contextlist_collection_for_collection( 144 $contextlistcollection, 145 $foruser, 146 $request->type, 147 ); 148 $manager = new \core_privacy\manager(); 149 $manager->set_observer(new \tool_dataprivacy\manager_observer()); 150 151 $manager->delete_data_for_user($approvedclcollection); 152 $completestatus = api::DATAREQUEST_STATUS_DELETED; 153 $deleteuser = !$foruser->deleted; 154 } 155 } 156 157 // When the preparation of the metadata finishes, update the request status to awaiting approval. 158 api::update_request_status($requestid, $completestatus); 159 mtrace('The processing of the user data request has been completed...'); 160 161 // Create message to notify the user regarding the processing results. 162 $message = new message(); 163 $message->courseid = $SITE->id; 164 $message->component = 'tool_dataprivacy'; 165 $message->name = 'datarequestprocessingresults'; 166 if (empty($request->dpo)) { 167 // Use the no-reply user as the sender if the privacy officer is not set. This is the case for automatically 168 // approved requests. 169 $fromuser = core_user::get_noreply_user(); 170 } else { 171 $fromuser = core_user::get_user($request->dpo); 172 $message->replyto = $fromuser->email; 173 $message->replytoname = fullname($fromuser); 174 } 175 $message->userfrom = $fromuser; 176 177 $typetext = null; 178 // Prepare the context data for the email message body. 179 $messagetextdata = [ 180 'username' => fullname($foruser) 181 ]; 182 183 $output = $PAGE->get_renderer('tool_dataprivacy'); 184 $emailonly = false; 185 $notifyuser = true; 186 switch ($request->type) { 187 case api::DATAREQUEST_TYPE_EXPORT: 188 // Check if the user is allowed to download their own export. (This is for 189 // institutions which centrally co-ordinate subject access request across many 190 // systems, not just one Moodle instance, so we don't want every instance emailing 191 // the user.) 192 if (!api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->userid)) { 193 $notifyuser = false; 194 } 195 196 $typetext = get_string('requesttypeexport', 'tool_dataprivacy'); 197 // We want to notify the user in Moodle about the processing results. 198 $message->notification = 1; 199 $datarequestsurl = new moodle_url('/admin/tool/dataprivacy/mydatarequests.php'); 200 $message->contexturl = $datarequestsurl; 201 $message->contexturlname = get_string('datarequests', 'tool_dataprivacy'); 202 // Message to the recipient. 203 $messagetextdata['message'] = get_string('resultdownloadready', 'tool_dataprivacy', 204 format_string($SITE->fullname, true, ['context' => context_system::instance()])); 205 // Prepare download link. 206 $downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $thing->get_itemid(), 207 $thing->get_filepath(), $thing->get_filename(), true); 208 $downloadlink = new action_link($downloadurl, get_string('download', 'tool_dataprivacy')); 209 $messagetextdata['downloadlink'] = $downloadlink->export_for_template($output); 210 break; 211 case api::DATAREQUEST_TYPE_DELETE: 212 $typetext = get_string('requesttypedelete', 'tool_dataprivacy'); 213 // No point notifying a deleted user in Moodle. 214 $message->notification = 0; 215 // Message to the recipient. 216 $messagetextdata['message'] = get_string('resultdeleted', 'tool_dataprivacy', 217 format_string($SITE->fullname, true, ['context' => context_system::instance()])); 218 // Message will be sent to the deleted user via email only. 219 $emailonly = true; 220 break; 221 default: 222 throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy'); 223 } 224 225 $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typetext); 226 $message->subject = $subject; 227 $message->fullmessageformat = FORMAT_HTML; 228 $message->userto = $foruser; 229 230 // Render message email body. 231 $messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata); 232 $message->fullmessage = html_to_text($messagehtml); 233 $message->fullmessagehtml = $messagehtml; 234 235 // Send message to the user involved. 236 if ($notifyuser) { 237 $messagesent = false; 238 if ($emailonly) { 239 // Do not sent an email if the user has been deleted. The user email has been previously deleted. 240 if (!$foruser->deleted) { 241 $messagesent = email_to_user($foruser, $fromuser, $subject, $message->fullmessage, $messagehtml); 242 } 243 } else { 244 $messagesent = message_send($message); 245 } 246 247 if ($messagesent) { 248 mtrace('Message sent to user: ' . $messagetextdata['username']); 249 } 250 } 251 252 // Send to requester as well in some circumstances. 253 if ($foruser->id != $request->requestedby) { 254 $sendtorequester = false; 255 switch ($request->type) { 256 case api::DATAREQUEST_TYPE_EXPORT: 257 // Send to the requester as well if they can download it, unless they are the 258 // DPO. If we didn't notify the user themselves (because they can't download) 259 // then send to requester even if it is the DPO, as in that case the requester 260 // needs to take some action. 261 if (api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->requestedby)) { 262 $sendtorequester = !$notifyuser || !api::is_site_dpo($request->requestedby); 263 } 264 break; 265 case api::DATAREQUEST_TYPE_DELETE: 266 // Send to the requester if they are not the DPO and if they are allowed to 267 // create data requests for the user (e.g. Parent). 268 $sendtorequester = !api::is_site_dpo($request->requestedby) && 269 api::can_create_data_request_for_user($request->userid, $request->requestedby); 270 break; 271 default: 272 throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy'); 273 } 274 275 // Ensure the requester has the capability to make data requests for this user. 276 if ($sendtorequester) { 277 $requestedby = core_user::get_user($request->requestedby); 278 $message->userto = $requestedby; 279 $messagetextdata['username'] = fullname($requestedby); 280 // Render message email body. 281 $messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata); 282 $message->fullmessage = html_to_text($messagehtml); 283 $message->fullmessagehtml = $messagehtml; 284 285 // Send message. 286 if ($emailonly) { 287 email_to_user($requestedby, $fromuser, $subject, $message->fullmessage, $messagehtml); 288 } else { 289 message_send($message); 290 } 291 mtrace('Message sent to requester: ' . $messagetextdata['username']); 292 } 293 } 294 295 if ($deleteuser) { 296 // Delete the user. 297 delete_user($foruser); 298 } 299 } 300 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body