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