Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Adhoc task that processes an approved data request and prepares/deletes the user's data.
 *
 * @package    tool_dataprivacy
 * @copyright  2018 Jun Pataleta
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace tool_dataprivacy\task;

use action_link;
use coding_exception;
use context_system;
use core\message\message;
use core\task\adhoc_task;
use core_user;
use moodle_exception;
use moodle_url;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;

< defined('MOODLE_INTERNAL') || die(); <
/** * Class that processes an approved data request and prepares/deletes the user's data. * * Custom data accepted: * - requestid -> The ID of the data request to be processed. * * @package tool_dataprivacy * @copyright 2018 Jun Pataleta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class process_data_request_task extends adhoc_task { /** * Run the task to initiate the data request process. * * @throws coding_exception * @throws moodle_exception */ public function execute() { global $CFG, $PAGE, $SITE; require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php"); if (!isset($this->get_custom_data()->requestid)) { throw new coding_exception('The custom data \'requestid\' is required.'); } $requestid = $this->get_custom_data()->requestid; $requestpersistent = new data_request($requestid); $request = $requestpersistent->to_record(); // Check if this request still needs to be processed. e.g. The user might have cancelled it before this task has run. $status = $requestpersistent->get('status'); if (!api::is_active($status)) { mtrace("Request {$requestid} with status {$status} doesn't need to be processed. Skipping..."); return; } if (!\tool_dataprivacy\data_registry::defaults_set()) { // Warn if no site purpose is defined. mtrace('Warning: No purpose is defined at the system level. Deletion will delete all.'); } // Grab the manager. // We set an observer against it to handle failures.
> $allowfiltering = get_config('tool_dataprivacy', 'allowfiltering');
$manager = new \core_privacy\manager(); $manager->set_observer(new \tool_dataprivacy\manager_observer()); // Get the user details now. We might not be able to retrieve it later if it's a deletion processing. $foruser = core_user::get_user($request->userid); // Update the status of this request as pre-processing. mtrace('Pre-processing request...'); api::update_request_status($requestid, api::DATAREQUEST_STATUS_PROCESSING); $contextlistcollection = $manager->get_contexts_for_userid($requestpersistent->get('userid')); mtrace('Fetching approved contextlists from collection');
< $approvedclcollection = api::get_approved_contextlist_collection_for_collection( < $contextlistcollection, $foruser, $request->type);
mtrace('Processing request...'); $completestatus = api::DATAREQUEST_STATUS_COMPLETE; $deleteuser = false; if ($request->type == api::DATAREQUEST_TYPE_EXPORT) { // Get the user context.
> if ($allowfiltering) { $usercontext = \context_user::instance($foruser->id, IGNORE_MISSING); > // Get the collection of approved_contextlist objects needed for core_privacy data export. if (!$usercontext) { > $approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent); mtrace("Request {$requestid} cannot be processed due to a missing user context instance for the user > } else { with ID {$foruser->id}. Skipping..."); > $approvedclcollection = api::get_approved_contextlist_collection_for_collection( return; > $contextlistcollection, } > $foruser, > $request->type, // Export the data. > ); $exportedcontent = $manager->export_user_data($approvedclcollection); > } >
$fs = get_file_storage(); $filerecord = new \stdClass; $filerecord->component = 'tool_dataprivacy'; $filerecord->contextid = $usercontext->id; $filerecord->userid = $foruser->id; $filerecord->filearea = 'export'; $filerecord->filename = 'export.zip'; $filerecord->filepath = '/'; $filerecord->itemid = $requestid; $filerecord->license = $CFG->sitedefaultlicense; $filerecord->author = fullname($foruser); // Save somewhere. $thing = $fs->create_file_from_pathname($filerecord, $exportedcontent); $completestatus = api::DATAREQUEST_STATUS_DOWNLOAD_READY; } else if ($request->type == api::DATAREQUEST_TYPE_DELETE) { // Delete the data for users other than the primary admin, which is rejected. if (is_primary_admin($foruser->id)) { $completestatus = api::DATAREQUEST_STATUS_REJECTED; } else {
> $approvedclcollection = api::get_approved_contextlist_collection_for_collection( $manager = new \core_privacy\manager(); > $contextlistcollection, $manager->set_observer(new \tool_dataprivacy\manager_observer()); > $foruser, > $request->type, $manager->delete_data_for_user($approvedclcollection); > );
$completestatus = api::DATAREQUEST_STATUS_DELETED; $deleteuser = !$foruser->deleted; } } // When the preparation of the metadata finishes, update the request status to awaiting approval. api::update_request_status($requestid, $completestatus); mtrace('The processing of the user data request has been completed...'); // Create message to notify the user regarding the processing results. $message = new message(); $message->courseid = $SITE->id; $message->component = 'tool_dataprivacy'; $message->name = 'datarequestprocessingresults'; if (empty($request->dpo)) { // Use the no-reply user as the sender if the privacy officer is not set. This is the case for automatically // approved requests. $fromuser = core_user::get_noreply_user(); } else { $fromuser = core_user::get_user($request->dpo); $message->replyto = $fromuser->email; $message->replytoname = fullname($fromuser); } $message->userfrom = $fromuser; $typetext = null; // Prepare the context data for the email message body. $messagetextdata = [ 'username' => fullname($foruser) ]; $output = $PAGE->get_renderer('tool_dataprivacy'); $emailonly = false; $notifyuser = true; switch ($request->type) { case api::DATAREQUEST_TYPE_EXPORT: // Check if the user is allowed to download their own export. (This is for // institutions which centrally co-ordinate subject access request across many // systems, not just one Moodle instance, so we don't want every instance emailing // the user.) if (!api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->userid)) { $notifyuser = false; } $typetext = get_string('requesttypeexport', 'tool_dataprivacy'); // We want to notify the user in Moodle about the processing results. $message->notification = 1; $datarequestsurl = new moodle_url('/admin/tool/dataprivacy/mydatarequests.php'); $message->contexturl = $datarequestsurl; $message->contexturlname = get_string('datarequests', 'tool_dataprivacy'); // Message to the recipient. $messagetextdata['message'] = get_string('resultdownloadready', 'tool_dataprivacy', format_string($SITE->fullname, true, ['context' => context_system::instance()])); // Prepare download link. $downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $thing->get_itemid(), $thing->get_filepath(), $thing->get_filename(), true); $downloadlink = new action_link($downloadurl, get_string('download', 'tool_dataprivacy')); $messagetextdata['downloadlink'] = $downloadlink->export_for_template($output); break; case api::DATAREQUEST_TYPE_DELETE: $typetext = get_string('requesttypedelete', 'tool_dataprivacy'); // No point notifying a deleted user in Moodle. $message->notification = 0; // Message to the recipient. $messagetextdata['message'] = get_string('resultdeleted', 'tool_dataprivacy', format_string($SITE->fullname, true, ['context' => context_system::instance()])); // Message will be sent to the deleted user via email only. $emailonly = true; break; default: throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy'); } $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typetext); $message->subject = $subject; $message->fullmessageformat = FORMAT_HTML; $message->userto = $foruser; // Render message email body. $messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata); $message->fullmessage = html_to_text($messagehtml); $message->fullmessagehtml = $messagehtml; // Send message to the user involved. if ($notifyuser) { $messagesent = false; if ($emailonly) { // Do not sent an email if the user has been deleted. The user email has been previously deleted. if (!$foruser->deleted) { $messagesent = email_to_user($foruser, $fromuser, $subject, $message->fullmessage, $messagehtml); } } else { $messagesent = message_send($message); } if ($messagesent) { mtrace('Message sent to user: ' . $messagetextdata['username']); } } // Send to requester as well in some circumstances. if ($foruser->id != $request->requestedby) { $sendtorequester = false; switch ($request->type) { case api::DATAREQUEST_TYPE_EXPORT: // Send to the requester as well if they can download it, unless they are the // DPO. If we didn't notify the user themselves (because they can't download) // then send to requester even if it is the DPO, as in that case the requester // needs to take some action. if (api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->requestedby)) { $sendtorequester = !$notifyuser || !api::is_site_dpo($request->requestedby); } break; case api::DATAREQUEST_TYPE_DELETE: // Send to the requester if they are not the DPO and if they are allowed to // create data requests for the user (e.g. Parent). $sendtorequester = !api::is_site_dpo($request->requestedby) && api::can_create_data_request_for_user($request->userid, $request->requestedby); break; default: throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy'); } // Ensure the requester has the capability to make data requests for this user. if ($sendtorequester) { $requestedby = core_user::get_user($request->requestedby); $message->userto = $requestedby; $messagetextdata['username'] = fullname($requestedby); // Render message email body. $messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata); $message->fullmessage = html_to_text($messagehtml); $message->fullmessagehtml = $messagehtml; // Send message. if ($emailonly) { email_to_user($requestedby, $fromuser, $subject, $message->fullmessage, $messagehtml); } else { message_send($message); } mtrace('Message sent to requester: ' . $messagetextdata['username']); } } if ($deleteuser) { // Delete the user. delete_user($foruser); } } }