Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
<?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/>.
/**
 * Class containing the external API functions functions for the Data Privacy tool.
 *
 * @package    tool_dataprivacy
 * @copyright  2018 Jun Pataleta
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
namespace tool_dataprivacy;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');

use context_helper;
use context_system;
use context_user;
use core\notification;
use core_user;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use moodle_exception;
use required_capability_exception;
use tool_dataprivacy\external\category_exporter;
use tool_dataprivacy\external\data_request_exporter;
use tool_dataprivacy\external\purpose_exporter;
use tool_dataprivacy\output\data_registry_page;

/**
 * Class external.
 *
 * The external API for the Data Privacy tool.
 *
 * @copyright  2017 Jun Pataleta
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class external extends external_api {

    /**
     * Parameter description for cancel_data_request().
     *
     * @since Moodle 3.5
     * @return external_function_parameters
     */
    public static function cancel_data_request_parameters() {
        return new external_function_parameters([
            'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
        ]);
    }

    /**
     * Cancel a data request.
     *
     * @since Moodle 3.5
     * @param int $requestid The request ID.
     * @return array
     * @throws invalid_persistent_exception
     * @throws coding_exception
     * @throws invalid_parameter_exception
     * @throws restricted_context_exception
     */
    public static function cancel_data_request($requestid) {
        global $USER;

        $warnings = [];
        $params = external_api::validate_parameters(self::cancel_data_request_parameters(), [
            'requestid' => $requestid
        ]);
        $requestid = $params['requestid'];

        // Validate context and access to manage the registry.
        $context = context_user::instance($USER->id);
        self::validate_context($context);

        // Ensure the request exists.
        $select = 'id = :id AND (userid = :userid OR requestedby = :requestedby)';
        $params = ['id' => $requestid, 'userid' => $USER->id, 'requestedby' => $USER->id];
        $requests = data_request::get_records_select($select, $params);
        $requestexists = count($requests) === 1;

        $result = false;
        if ($requestexists) {
            $request = reset($requests);
            $datasubject = $request->get('userid');

            if ($datasubject !== (int) $USER->id) {
                // The user is not the subject. Check that they can cancel this request.
                if (!api::can_create_data_request_for_user($datasubject)) {
                    $forusercontext = \context_user::instance($datasubject);
                    throw new required_capability_exception($forusercontext,
                            'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
                }
            }

            // TODO: Do we want a request to be non-cancellable past a certain point? E.g. When it's already approved/processing.
            $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_CANCELLED);
        } else {
            $warnings[] = [
                'item' => $requestid,
                'warningcode' => 'errorrequestnotfound',
                'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
            ];
        }

        return [
            'result' => $result,
            'warnings' => $warnings
        ];
    }

    /**
     * Parameter description for cancel_data_request().
     *
     * @since Moodle 3.5
     * @return \core_external\external_description
     */
    public static function cancel_data_request_returns() {
        return new external_single_structure([
            'result' => new external_value(PARAM_BOOL, 'The processing result'),
            'warnings' => new external_warnings()
        ]);
    }

    /**
     * Parameter description for contact_dpo().
     *
     * @since Moodle 3.5
     * @return external_function_parameters
     */
    public static function contact_dpo_parameters() {
        return new external_function_parameters([
            'message' => new external_value(PARAM_TEXT, 'The user\'s message to the Data Protection Officer(s)', VALUE_REQUIRED)
        ]);
    }

    /**
     * Make a general enquiry to a DPO.
     *
     * @since Moodle 3.5
     * @param string $message The message to be sent to the DPO.
     * @return array
     * @throws coding_exception
     * @throws invalid_parameter_exception
     * @throws invalid_persistent_exception
     * @throws restricted_context_exception
     * @throws dml_exception
     * @throws moodle_exception
     */
    public static function contact_dpo($message) {
        global $USER;

        $warnings = [];
        $params = external_api::validate_parameters(self::contact_dpo_parameters(), [
            'message' => $message
        ]);
        $message = $params['message'];

        // Validate context.
        $userid = $USER->id;
        $context = context_user::instance($userid);
        self::validate_context($context);

        // Lodge the request.
        $datarequest = new data_request();
        // The user the request is being made for.
        $datarequest->set('userid', $userid);
        // The user making the request.
        $datarequest->set('requestedby', $userid);
        // Set status.
        $datarequest->set('status', api::DATAREQUEST_STATUS_PENDING);
        // Set request type.
        $datarequest->set('type', api::DATAREQUEST_TYPE_OTHERS);
        // Set request comments.
        $datarequest->set('comments', $message);

        // Store subject access request.
        $datarequest->create();

        // Get the list of the site Data Protection Officers.
        $dpos = api::get_site_dpos();

        // Email the data request to the Data Protection Officer(s)/Admin(s).
        $result = true;
        foreach ($dpos as $dpo) {
            $sendresult = api::notify_dpo($dpo, $datarequest);
            if (!$sendresult) {
                $result = false;
                $warnings[] = [
                    'item' => $dpo->id,
                    'warningcode' => 'errorsendingtodpo',
                    'message' => get_string('errorsendingmessagetodpo', 'tool_dataprivacy',
                        fullname($dpo))
                ];
            }
        }

        return [
            'result' => $result,
            'warnings' => $warnings
        ];
    }

    /**
     * Parameter description for contact_dpo().
     *
     * @since Moodle 3.5
     * @return \core_external\external_description
     */
    public static function contact_dpo_returns() {
        return new external_single_structure([
            'result' => new external_value(PARAM_BOOL, 'The processing result'),
            'warnings' => new external_warnings()
        ]);
    }

    /**
     * Parameter description for mark_complete().
     *
     * @since Moodle 3.5.2
     * @return external_function_parameters
     */
    public static function mark_complete_parameters() {
        return new external_function_parameters([
            'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
        ]);
    }

    /**
     * Mark a user's general enquiry's status as complete.
     *
     * @since Moodle 3.5.2
     * @param int $requestid The request ID of the general enquiry.
     * @return array
     * @throws coding_exception
     * @throws invalid_parameter_exception
     * @throws invalid_persistent_exception
     * @throws restricted_context_exception
     * @throws dml_exception
     * @throws moodle_exception
     */
    public static function mark_complete($requestid) {
        global $USER;

        $warnings = [];
        $params = external_api::validate_parameters(self::mark_complete_parameters(), [
            'requestid' => $requestid,
        ]);
        $requestid = $params['requestid'];

        // Validate context and access to manage the registry.
        $context = context_system::instance();
        self::validate_context($context);
        api::check_can_manage_data_registry();

        $message = get_string('markedcomplete', 'tool_dataprivacy');
        // Update the data request record.
        if ($result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE, $USER->id, $message)) {
            // Add notification in the session to be shown when the page is reloaded on the JS side.
            notification::success(get_string('requestmarkedcomplete', 'tool_dataprivacy'));
        }

        return [
            'result' => $result,
            'warnings' => $warnings
        ];
    }

    /**
     * Parameter description for mark_complete().
     *
     * @since Moodle 3.5.2
     * @return \core_external\external_description
     */
    public static function mark_complete_returns() {
        return new external_single_structure([
            'result' => new external_value(PARAM_BOOL, 'The processing result'),
            'warnings' => new external_warnings()
        ]);
    }

    /**
     * Parameter description for get_data_request().
     *
     * @since Moodle 3.5
     * @return external_function_parameters
     */
    public static function get_data_request_parameters() {
        return new external_function_parameters([
            'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
        ]);
    }

    /**
     * Fetch the details of a user's data request.
     *
     * @since Moodle 3.5
     * @param int $requestid The request ID.
     * @return array
     * @throws coding_exception
     * @throws dml_exception
     * @throws invalid_parameter_exception
     * @throws restricted_context_exception
     * @throws moodle_exception
     */
    public static function get_data_request($requestid) {
        global $PAGE;

        $warnings = [];
        $params = external_api::validate_parameters(self::get_data_request_parameters(), [
            'requestid' => $requestid
        ]);
        $requestid = $params['requestid'];

        // Validate context.
        $context = context_system::instance();
        self::validate_context($context);
        $requestpersistent = new data_request($requestid);
        require_capability('tool/dataprivacy:managedatarequests', $context);

        $exporter = new data_request_exporter($requestpersistent, ['context' => $context]);
        $renderer = $PAGE->get_renderer('tool_dataprivacy');
        $result = $exporter->export($renderer);

        return [
            'result' => $result,
            'warnings' => $warnings
        ];
    }

    /**
     * Parameter description for get_data_request().
     *
     * @since Moodle 3.5
     * @return \core_external\external_description
     */
    public static function get_data_request_returns() {
        return new external_single_structure([
            'result' => data_request_exporter::get_read_structure(),
            'warnings' => new external_warnings()
        ]);
    }

    /**
     * Parameter description for approve_data_request().
     *
     * @since Moodle 3.5
     * @return external_function_parameters
     */
    public static function approve_data_request_parameters() {
        return new external_function_parameters([
            'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
        ]);
    }

    /**
     * Approve a data request.
     *
     * @since Moodle 3.5
     * @param int $requestid The request ID.
     * @return array
     * @throws coding_exception
     * @throws dml_exception
     * @throws invalid_parameter_exception
     * @throws restricted_context_exception
     * @throws moodle_exception
     */
    public static function approve_data_request($requestid) {
        $warnings = [];
        $params = external_api::validate_parameters(self::approve_data_request_parameters(), [
            'requestid' => $requestid
        ]);
        $requestid = $params['requestid'];

        // Validate context.
        $context = context_system::instance();
        self::validate_context($context);
        require_capability('tool/dataprivacy:managedatarequests', $context);

        // Ensure the request exists.
        $requestexists = data_request::record_exists($requestid);

        $result = false;
        if ($requestexists) {
            $result = api::approve_data_request($requestid);

            // Add notification in the session to be shown when the page is reloaded on the JS side.
            notification::success(get_string('requestapproved', 'tool_dataprivacy'));
        } else {
            $warnings[] = [
                'item' => $requestid,
                'warningcode' => 'errorrequestnotfound',
                'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
            ];
        }

        return [
            'result' => $result,
            'warnings' => $warnings
        ];
    }

    /**
     * Parameter description for approve_data_request().
     *
     * @since Moodle 3.5
     * @return \core_external\external_description
     */
    public static function approve_data_request_returns() {
        return new external_single_structure([
            'result' => new external_value(PARAM_BOOL, 'The processing result'),
            'warnings' => new external_warnings()
        ]);
    }

    /**
     * Parameter description for bulk_approve_data_requests().
     *
     * @since Moodle 3.5
     * @return external_function_parameters
     */
    public static function bulk_approve_data_requests_parameters() {
        return new external_function_parameters([
            'requestids' => new external_multiple_structure(
                new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
            )
        ]);
    }

    /**
     * Bulk approve bulk data request.
     *
     * @since Moodle 3.5
     * @param array $requestids Array consisting the request ID's.
     * @return array
     * @throws coding_exception
     * @throws dml_exception
     * @throws invalid_parameter_exception
     * @throws restricted_context_exception
     * @throws moodle_exception
     */
    public static function bulk_approve_data_requests($requestids) {
        $warnings = [];
        $result = false;
        $params = external_api::validate_parameters(self::bulk_approve_data_requests_parameters(), [
            'requestids' => $requestids
        ]);
        $requestids = $params['requestids'];

        // Validate context.
        $context = context_system::instance();
        self::validate_context($context);
        require_capability('tool/dataprivacy:managedatarequests', $context);

        foreach ($requestids as $requestid) {
            // Ensure the request exists.
            $requestexists = data_request::record_exists($requestid);

            if ($requestexists) {
                api::approve_data_request($requestid);
            } else {
                $warnings[] = [
                    'item' => $requestid,
                    'warningcode' => 'errorrequestnotfound',
                    'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
                ];
            }
        }

        if (empty($warnings)) {
            $result = true;
            // Add notification in the session to be shown when the page is reloaded on the JS side.
            notification::success(get_string('requestsapproved', 'tool_dataprivacy'));
        }

        return [
            'result' => $result,
            'warnings' => $warnings
        ];
    }

    /**
     * Parameter description for bulk_approve_data_requests().
     *
     * @since Moodle 3.5
     * @return \core_external\external_description
     */
    public static function bulk_approve_data_requests_returns() {
        return new external_single_structure([
            'result' => new external_value(PARAM_BOOL, 'The processing result'),
            'warnings' => new external_warnings()
        ]);
    }

    /**
     * Parameter description for deny_data_request().
     *
     * @since Moodle 3.5
     * @return external_function_parameters
     */
    public static function deny_data_request_parameters() {
        return new external_function_parameters([
            'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
        ]);
    }

    /**
     * Deny a data request.
     *
     * @since Moodle 3.5
     * @param int $requestid The request ID.
     * @return array
     * @throws coding_exception
     * @throws dml_exception
     * @throws invalid_parameter_exception
     * @throws restricted_context_exception
     * @throws moodle_exception
     */
    public static function deny_data_request($requestid) {
        $warnings = [];
        $params = external_api::validate_parameters(self::deny_data_request_parameters(), [
            'requestid' => $requestid
        ]);
        $requestid = $params['requestid'];

        // Validate context.
        $context = context_system::instance();
        self::validate_context($context);
        require_capability('tool/dataprivacy:managedatarequests', $context);

        // Ensure the request exists.
        $requestexists = data_request::record_exists($requestid);

        $result = false;
        if ($requestexists) {
            $result = api::deny_data_request($requestid);

            // Add notification in the session to be shown when the page is reloaded on the JS side.
            notification::success(get_string('requestdenied', 'tool_dataprivacy'));
        } else {
            $warnings[] = [
                'item' => $requestid,
                'warningcode' => 'errorrequestnotfound',
                'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
            ];
        }

        return [
            'result' => $result,
            'warnings' => $warnings
        ];
    }

    /**
     * Parameter description for deny_data_request().
     *
     * @since Moodle 3.5
     * @return \core_external\external_description
     */
    public static function deny_data_request_returns() {
        return new external_single_structure([
            'result' => new external_value(PARAM_BOOL, 'The processing result'),
            'warnings' => new external_warnings()
        ]);
    }

    /**
     * Parameter description for bulk_deny_data_requests().
     *
     * @since Moodle 3.5
     * @return external_function_parameters
     */
    public static function bulk_deny_data_requests_parameters() {
        return new external_function_parameters([
            'requestids' => new external_multiple_structure(
                new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
            )
        ]);
    }

    /**
     * Bulk deny data requests.
     *
     * @since Moodle 3.5
     * @param array $requestids Array consisting of request ID's.
     * @return array
     * @throws coding_exception
     * @throws dml_exception
     * @throws invalid_parameter_exception
     * @throws restricted_context_exception
     * @throws moodle_exception
     */
    public static function bulk_deny_data_requests($requestids) {
        $warnings = [];
        $result = false;
        $params = external_api::validate_parameters(self::bulk_deny_data_requests_parameters(), [
            'requestids' => $requestids
        ]);
        $requestids = $params['requestids'];

        // Validate context.
        $context = context_system::instance();
        self::validate_context($context);
        require_capability('tool/dataprivacy:managedatarequests', $context);

        foreach ($requestids as $requestid) {
            // Ensure the request exists.
            $requestexists = data_request::record_exists($requestid);

            if ($requestexists) {
                api::deny_data_request($requestid);
            } else {
                $warnings[] = [
                    'item' => $requestid,
                    'warningcode' => 'errorrequestnotfound',
                    'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
                ];
            }
        }

        if (empty($warnings)) {
            $result = true;
            // Add notification in the session to be shown when the page is reloaded on the JS side.
            notification::success(get_string('requestsdenied', 'tool_dataprivacy'));
        }

        return [
            'result' => $result,
            'warnings' => $warnings
        ];
    }

    /**
     * Parameter description for bulk_deny_data_requests().
     *
     * @since Moodle 3.5
     * @return \core_external\external_description
     */
    public static function bulk_deny_data_requests_returns() {
        return new external_single_structure([
            'result' => new external_value(PARAM_BOOL, 'The processing result'),
            'warnings' => new external_warnings()
        ]);
    }

    /**
     * Parameter description for get_data_request().
     *
     * @since Moodle 3.5
     * @return external_function_parameters
     */
    public static function get_users_parameters() {
        return new external_function_parameters([
            'query' => new external_value(PARAM_TEXT, 'The search query', VALUE_REQUIRED)
        ]);
    }

    /**
     * Fetch the details of a user's data request.
     *
     * @since Moodle 3.5
     * @param string $query The search request.
     * @return array
     * @throws required_capability_exception
     * @throws dml_exception
     * @throws invalid_parameter_exception
     * @throws restricted_context_exception
     */
    public static function get_users($query) {
        global $DB;
        $params = external_api::validate_parameters(self::get_users_parameters(), [
            'query' => $query
        ]);
        $query = $params['query'];

        // Validate context.
        $context = context_system::instance();
        self::validate_context($context);
        require_capability('tool/dataprivacy:managedatarequests', $context);

        $userfieldsapi = \core_user\fields::for_name();
        $allusernames = $userfieldsapi->get_sql('', false, '', '', false)->selects;
        // Exclude admins and guest user.
        $excludedusers = array_keys(get_admins()) + [guest_user()->id];
        $sort = 'lastname ASC, firstname ASC';
        $fields = 'id,' . $allusernames;

        // TODO Does not support custom user profile fields (MDL-70456).
        $extrafields = \core_user\fields::get_identity_fields($context, false);
        if (!empty($extrafields)) {
            $fields .= ',' . implode(',', $extrafields);
        }

< list($sql, $params) = users_search_sql($query, '', false, $extrafields, $excludedusers);
> list($sql, $params) = users_search_sql($query, '', USER_SEARCH_STARTS_WITH, $extrafields, $excludedusers);
$users = $DB->get_records_select('user', $sql, $params, $sort, $fields, 0, 30); $useroptions = []; foreach ($users as $user) { $useroption = (object)[ 'id' => $user->id, 'fullname' => fullname($user) ]; $useroption->extrafields = []; foreach ($extrafields as $extrafield) { // Sanitize the extra fields to prevent potential XSS exploit. $useroption->extrafields[] = (object)[ 'name' => $extrafield, 'value' => s($user->$extrafield) ]; } $useroptions[$user->id] = $useroption; } return $useroptions; } /** * Parameter description for get_users(). * * @since Moodle 3.5 * @return \core_external\external_description * @throws coding_exception */ public static function get_users_returns() { return new external_multiple_structure(new external_single_structure( [ 'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'), 'fullname' => new external_value(core_user::get_property_type('firstname'), 'The fullname of the user'), 'extrafields' => new external_multiple_structure( new external_single_structure([ 'name' => new external_value(PARAM_TEXT, 'Name of the extrafield.'), 'value' => new external_value(PARAM_TEXT, 'Value of the extrafield.') ] ), 'List of extra fields', VALUE_OPTIONAL ) ] )); } /** * Parameter description for create_purpose_form(). * * @since Moodle 3.5 * @return external_function_parameters */ public static function create_purpose_form_parameters() { return new external_function_parameters([ 'jsonformdata' => new external_value(PARAM_RAW, 'The data to create the purpose, encoded as a json array') ]); } /** * Creates a data purpose from form data. * * @since Moodle 3.5 * @param string $jsonformdata * @return array */ public static function create_purpose_form($jsonformdata) { global $PAGE; $warnings = []; $params = external_api::validate_parameters(self::create_purpose_form_parameters(), [ 'jsonformdata' => $jsonformdata ]); // Validate context and access to manage the registry. self::validate_context(\context_system::instance()); api::check_can_manage_data_registry(); $serialiseddata = json_decode($params['jsonformdata']); $data = array(); parse_str($serialiseddata, $data); $purpose = new \tool_dataprivacy\purpose(0); $mform = new \tool_dataprivacy\form\purpose(null, ['persistent' => $purpose], 'post', '', null, true, $data); $validationerrors = true; if ($validateddata = $mform->get_data()) { $purpose = api::create_purpose($validateddata); $validationerrors = false; } else if ($errors = $mform->is_validated()) { throw new moodle_exception('generalerror'); } $exporter = new purpose_exporter($purpose, ['context' => \context_system::instance()]); return [ 'purpose' => $exporter->export($PAGE->get_renderer('core')), 'validationerrors' => $validationerrors, 'warnings' => $warnings ]; } /** * Returns for create_purpose_form(). * * @since Moodle 3.5 * @return external_single_structure */ public static function create_purpose_form_returns() { return new external_single_structure([ 'purpose' => purpose_exporter::get_read_structure(), 'validationerrors' => new external_value(PARAM_BOOL, 'Were there validation errors', VALUE_REQUIRED), 'warnings' => new external_warnings() ]); } /** * Parameter description for delete_purpose(). * * @since Moodle 3.5 * @return external_function_parameters */ public static function delete_purpose_parameters() { return new external_function_parameters([ 'id' => new external_value(PARAM_INT, 'The purpose ID', VALUE_REQUIRED) ]); } /** * Deletes a data purpose. * * @since Moodle 3.5 * @param int $id The ID. * @return array * @throws invalid_persistent_exception * @throws coding_exception * @throws invalid_parameter_exception */ public static function delete_purpose($id) { global $USER; $params = external_api::validate_parameters(self::delete_purpose_parameters(), [ 'id' => $id ]); // Validate context and access to manage the registry. self::validate_context(\context_system::instance()); api::check_can_manage_data_registry(); $result = api::delete_purpose($params['id']); return [ 'result' => $result, 'warnings' => [] ]; } /** * Parameter description for delete_purpose(). * * @since Moodle 3.5 * @return external_single_structure */ public static function delete_purpose_returns() { return new external_single_structure([ 'result' => new external_value(PARAM_BOOL, 'The processing result'), 'warnings' => new external_warnings() ]); } /** * Parameter description for create_category_form(). * * @since Moodle 3.5 * @return external_function_parameters */ public static function create_category_form_parameters() { return new external_function_parameters([ 'jsonformdata' => new external_value(PARAM_RAW, 'The data to create the category, encoded as a json array') ]); } /** * Creates a data category from form data. * * @since Moodle 3.5 * @param string $jsonformdata * @return array */ public static function create_category_form($jsonformdata) { global $PAGE; $warnings = []; $params = external_api::validate_parameters(self::create_category_form_parameters(), [ 'jsonformdata' => $jsonformdata ]); // Validate context and access to manage the registry. self::validate_context(\context_system::instance()); api::check_can_manage_data_registry(); $serialiseddata = json_decode($params['jsonformdata']); $data = array(); parse_str($serialiseddata, $data); $category = new \tool_dataprivacy\category(0); $mform = new \tool_dataprivacy\form\category(null, ['persistent' => $category], 'post', '', null, true, $data); $validationerrors = true; if ($validateddata = $mform->get_data()) { $category = api::create_category($validateddata); $validationerrors = false; } else if ($errors = $mform->is_validated()) { throw new moodle_exception('generalerror'); } $exporter = new category_exporter($category, ['context' => \context_system::instance()]); return [ 'category' => $exporter->export($PAGE->get_renderer('core')), 'validationerrors' => $validationerrors, 'warnings' => $warnings ]; } /** * Returns for create_category_form(). * * @since Moodle 3.5 * @return external_single_structure */ public static function create_category_form_returns() { return new external_single_structure([ 'category' => category_exporter::get_read_structure(), 'validationerrors' => new external_value(PARAM_BOOL, 'Were there validation errors', VALUE_REQUIRED), 'warnings' => new external_warnings() ]); } /** * Parameter description for delete_category(). * * @since Moodle 3.5 * @return external_function_parameters */ public static function delete_category_parameters() { return new external_function_parameters([ 'id' => new external_value(PARAM_INT, 'The category ID', VALUE_REQUIRED) ]); } /** * Deletes a data category. * * @since Moodle 3.5 * @param int $id The ID. * @return array * @throws invalid_persistent_exception * @throws coding_exception * @throws invalid_parameter_exception */ public static function delete_category($id) { global $USER; $params = external_api::validate_parameters(self::delete_category_parameters(), [ 'id' => $id ]); // Validate context and access to manage the registry. self::validate_context(\context_system::instance()); api::check_can_manage_data_registry(); $result = api::delete_category($params['id']); return [ 'result' => $result, 'warnings' => [] ]; } /** * Parameter description for delete_category(). * * @since Moodle 3.5 * @return external_single_structure */ public static function delete_category_returns() { return new external_single_structure([ 'result' => new external_value(PARAM_BOOL, 'The processing result'), 'warnings' => new external_warnings() ]); } /** * Parameter description for set_contextlevel_form(). * * @since Moodle 3.5 * @return external_function_parameters */ public static function set_contextlevel_form_parameters() { return new external_function_parameters([ 'jsonformdata' => new external_value(PARAM_RAW, 'The context level data, encoded as a json array') ]); } /** * Creates a data category from form data. * * @since Moodle 3.5 * @param string $jsonformdata * @return array */ public static function set_contextlevel_form($jsonformdata) { global $PAGE; $warnings = []; $params = external_api::validate_parameters(self::set_contextlevel_form_parameters(), [ 'jsonformdata' => $jsonformdata ]); // Validate context and access to manage the registry. self::validate_context(\context_system::instance()); api::check_can_manage_data_registry(); $serialiseddata = json_decode($params['jsonformdata']); $data = array(); parse_str($serialiseddata, $data); $contextlevel = $data['contextlevel']; $customdata = \tool_dataprivacy\form\contextlevel::get_contextlevel_customdata($contextlevel); $mform = new \tool_dataprivacy\form\contextlevel(null, $customdata, 'post', '', null, true, $data); if ($validateddata = $mform->get_data()) { $contextlevel = api::set_contextlevel($validateddata); } else if ($errors = $mform->is_validated()) { $warnings[] = json_encode($errors); } if ($contextlevel) { $result = true; } else { $result = false; } return [ 'result' => $result, 'warnings' => $warnings ]; } /** * Returns for set_contextlevel_form(). * * @since Moodle 3.5 * @return external_single_structure */ public static function set_contextlevel_form_returns() { return new external_single_structure([ 'result' => new external_value(PARAM_BOOL, 'Whether the data was properly set or not'), 'warnings' => new external_warnings() ]); } /** * Parameter description for set_context_form(). * * @since Moodle 3.5 * @return external_function_parameters */ public static function set_context_form_parameters() { return new external_function_parameters([ 'jsonformdata' => new external_value(PARAM_RAW, 'The context level data, encoded as a json array') ]); } /** * Creates a data category from form data. * * @since Moodle 3.5 * @param string $jsonformdata * @return array */ public static function set_context_form($jsonformdata) { global $PAGE; $warnings = []; $params = external_api::validate_parameters(self::set_context_form_parameters(), [ 'jsonformdata' => $jsonformdata ]); // Validate context and access to manage the registry. self::validate_context(\context_system::instance()); api::check_can_manage_data_registry(); $serialiseddata = json_decode($params['jsonformdata']); $data = array(); parse_str($serialiseddata, $data); $context = context_helper::instance_by_id($data['contextid']); $customdata = \tool_dataprivacy\form\context_instance::get_context_instance_customdata($context); $mform = new \tool_dataprivacy\form\context_instance(null, $customdata, 'post', '', null, true, $data); if ($validateddata = $mform->get_data()) { api::check_can_manage_data_registry($validateddata->contextid); $context = api::set_context_instance($validateddata); } else if ($errors = $mform->is_validated()) { $warnings[] = json_encode($errors); throw new moodle_exception('generalerror'); } if ($context) { $result = true; } else { $result = false; } return [ 'result' => $result, 'warnings' => $warnings ]; } /** * Returns for set_context_form(). * * @since Moodle 3.5 * @return external_single_structure */ public static function set_context_form_returns() { return new external_single_structure([ 'result' => new external_value(PARAM_BOOL, 'Whether the data was properly set or not'), 'warnings' => new external_warnings() ]); } /** * Parameter description for tree_extra_branches(). * * @since Moodle 3.5 * @return external_function_parameters */ public static function tree_extra_branches_parameters() { return new external_function_parameters([ 'contextid' => new external_value(PARAM_INT, 'The context id to expand'), 'element' => new external_value(PARAM_ALPHA, 'The element we are interested on') ]); } /** * Returns tree extra branches. * * @since Moodle 3.5 * @param int $contextid * @param string $element * @return array */ public static function tree_extra_branches($contextid, $element) { $params = external_api::validate_parameters(self::tree_extra_branches_parameters(), [ 'contextid' => $contextid, 'element' => $element, ]); $context = context_helper::instance_by_id($params['contextid']); self::validate_context($context); api::check_can_manage_data_registry($context->id); switch ($params['element']) { case 'course': $branches = data_registry_page::get_courses_branch($context); break; case 'module': $branches = data_registry_page::get_modules_branch($context); break; case 'block': $branches = data_registry_page::get_blocks_branch($context); break; default: throw new \moodle_exception('Unsupported element provided.'); } return [ 'branches' => $branches, 'warnings' => [], ]; } /** * Returns for tree_extra_branches(). * * @since Moodle 3.5 * @return external_single_structure */ public static function tree_extra_branches_returns() { return new external_single_structure([ 'branches' => new external_multiple_structure(self::get_tree_node_structure(true)), 'warnings' => new external_warnings() ]); } /** * Parameters for confirm_contexts_for_deletion(). * * @since Moodle 3.5 * @return external_function_parameters */ public static function confirm_contexts_for_deletion_parameters() { return new external_function_parameters([ 'ids' => new external_multiple_structure( new external_value(PARAM_INT, 'Expired context record ID', VALUE_REQUIRED), 'Array of expired context record IDs', VALUE_DEFAULT, [] ), ]); } /** * Confirm a given array of expired context record IDs * * @since Moodle 3.5 * @param int[] $ids Array of record IDs from the expired contexts table. * @return array * @throws coding_exception * @throws dml_exception * @throws invalid_parameter_exception * @throws restricted_context_exception */ public static function confirm_contexts_for_deletion($ids) { $warnings = []; $params = external_api::validate_parameters(self::confirm_contexts_for_deletion_parameters(), [ 'ids' => $ids ]); $ids = $params['ids']; // Validate context and access to manage the registry. self::validate_context(\context_system::instance()); api::check_can_manage_data_registry(); $result = true; if (!empty($ids)) { $expiredcontextstoapprove = []; // Loop through the deletion of expired contexts and their children if necessary. foreach ($ids as $id) { $expiredcontext = new expired_context($id); $targetcontext = context_helper::instance_by_id($expiredcontext->get('contextid')); if (!$targetcontext instanceof \context_user) { // Fetch this context's child contexts. Make sure that all of the child contexts are flagged for deletion. // User context children do not need to be considered. $childcontexts = $targetcontext->get_child_contexts(); foreach ($childcontexts as $child) { if ($expiredchildcontext = expired_context::get_record(['contextid' => $child->id])) { // Add this child context to the list for approval. $expiredcontextstoapprove[] = $expiredchildcontext; } else { // This context has not yet been flagged for deletion. $result = false; $message = get_string('errorcontexthasunexpiredchildren', 'tool_dataprivacy', $targetcontext->get_context_name(false)); $warnings[] = [ 'item' => 'tool_dataprivacy_ctxexpired', 'warningcode' => 'errorcontexthasunexpiredchildren', 'message' => $message ]; // Exit the process. break 2; } } } $expiredcontextstoapprove[] = $expiredcontext; } // Proceed with the approval if everything's in order. if ($result) { // Mark expired contexts as approved for deletion. foreach ($expiredcontextstoapprove as $expired) { // Only mark expired contexts that are pending approval. if ($expired->get('status') == expired_context::STATUS_EXPIRED) { api::set_expired_context_status($expired, expired_context::STATUS_APPROVED); } } } } else { // We don't have anything to process. $result = false; $warnings[] = [ 'item' => 'tool_dataprivacy_ctxexpired', 'warningcode' => 'errornoexpiredcontexts', 'message' => get_string('errornoexpiredcontexts', 'tool_dataprivacy') ]; } return [ 'result' => $result, 'warnings' => $warnings ]; } /** * Returns for confirm_contexts_for_deletion(). * * @since Moodle 3.5 * @return external_single_structure */ public static function confirm_contexts_for_deletion_returns() { return new external_single_structure([ 'result' => new external_value(PARAM_BOOL, 'Whether the record was properly marked for deletion or not'), 'warnings' => new external_warnings() ]); } /** * Parameters for set_context_defaults(). * * @return external_function_parameters */ public static function set_context_defaults_parameters() { return new external_function_parameters([ 'contextlevel' => new external_value(PARAM_INT, 'The context level', VALUE_REQUIRED), 'category' => new external_value(PARAM_INT, 'The default category for the given context level', VALUE_REQUIRED), 'purpose' => new external_value(PARAM_INT, 'The default purpose for the given context level', VALUE_REQUIRED), 'activity' => new external_value(PARAM_PLUGIN, 'The plugin name of the activity', VALUE_DEFAULT, null), 'override' => new external_value(PARAM_BOOL, 'Whether to override existing instances with the defaults', VALUE_DEFAULT, false), ]); } /** * Updates the default category and purpose for a given context level (and optionally, a plugin). * * @param int $contextlevel The context level. * @param int $category The ID matching the category. * @param int $purpose The ID matching the purpose record. * @param int $activity The name of the activity that we're making a defaults configuration for. * @param bool $override Whether to override the purpose/categories of existing instances to these defaults. * @return array */ public static function set_context_defaults($contextlevel, $category, $purpose, $activity, $override) { $warnings = []; $params = external_api::validate_parameters(self::set_context_defaults_parameters(), [ 'contextlevel' => $contextlevel, 'category' => $category, 'purpose' => $purpose, 'activity' => $activity, 'override' => $override, ]); $contextlevel = $params['contextlevel']; $category = $params['category']; $purpose = $params['purpose']; $activity = $params['activity']; $override = $params['override']; // Validate context. $context = context_system::instance(); self::validate_context($context); api::check_can_manage_data_registry(); // Set the context defaults. $result = api::set_context_defaults($contextlevel, $category, $purpose, $activity, $override); return [ 'result' => $result, 'warnings' => $warnings ]; } /** * Returns for set_context_defaults(). * * @return external_single_structure */ public static function set_context_defaults_returns() { return new external_single_structure([ 'result' => new external_value(PARAM_BOOL, 'Whether the context defaults were successfully set or not'), 'warnings' => new external_warnings() ]); } /** * Parameters for get_category_options(). * * @return external_function_parameters */ public static function get_category_options_parameters() { return new external_function_parameters([ 'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true), 'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false), ]); } /** * Fetches a list of data category options containing category IDs as keys and the category name for the value. * * @param bool $includeinherit Whether to include the "Inherit" option. * @param bool $includenotset Whether to include the "Not set" option. * @return array */ public static function get_category_options($includeinherit, $includenotset) { $warnings = []; $params = self::validate_parameters(self::get_category_options_parameters(), [ 'includeinherit' => $includeinherit, 'includenotset' => $includenotset ]); $includeinherit = $params['includeinherit']; $includenotset = $params['includenotset']; $context = context_system::instance(); self::validate_context($context); api::check_can_manage_data_registry(); $categories = api::get_categories(); $options = data_registry_page::category_options($categories, $includenotset, $includeinherit); $categoryoptions = []; foreach ($options as $id => $name) { $categoryoptions[] = [ 'id' => $id, 'name' => $name, ]; } return [ 'options' => $categoryoptions, 'warnings' => $warnings ]; } /** * Returns for get_category_options(). * * @return external_single_structure */ public static function get_category_options_returns() { $optiondefinition = new external_single_structure( [ 'id' => new external_value(PARAM_INT, 'The category ID'), 'name' => new external_value(PARAM_TEXT, 'The category name'), ] ); return new external_single_structure([ 'options' => new external_multiple_structure($optiondefinition), 'warnings' => new external_warnings() ]); } /** * Parameters for get_purpose_options(). * * @return external_function_parameters */ public static function get_purpose_options_parameters() { return new external_function_parameters([ 'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true), 'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false), ]); } /** * Fetches a list of data storage purposes containing purpose IDs as keys and the purpose name for the value. * * @param bool $includeinherit Whether to include the "Inherit" option. * @param bool $includenotset Whether to include the "Not set" option. * @return array */ public static function get_purpose_options($includeinherit, $includenotset) { $warnings = []; $params = self::validate_parameters(self::get_category_options_parameters(), [ 'includeinherit' => $includeinherit, 'includenotset' => $includenotset ]); $includeinherit = $params['includeinherit']; $includenotset = $params['includenotset']; $context = context_system::instance(); self::validate_context($context); $purposes = api::get_purposes(); $options = data_registry_page::purpose_options($purposes, $includenotset, $includeinherit); $purposeoptions = []; foreach ($options as $id => $name) { $purposeoptions[] = [ 'id' => $id, 'name' => $name, ]; } return [ 'options' => $purposeoptions, 'warnings' => $warnings ]; } /** * Returns for get_purpose_options(). * * @return external_single_structure */ public static function get_purpose_options_returns() { $optiondefinition = new external_single_structure( [ 'id' => new external_value(PARAM_INT, 'The purpose ID'), 'name' => new external_value(PARAM_TEXT, 'The purpose name'), ] ); return new external_single_structure([ 'options' => new external_multiple_structure($optiondefinition), 'warnings' => new external_warnings() ]); } /** * Parameters for get_activity_options(). * * @return external_function_parameters */ public static function get_activity_options_parameters() { return new external_function_parameters([ 'nodefaults' => new external_value(PARAM_BOOL, 'Whether to fetch all activities or only those without defaults', VALUE_DEFAULT, false), ]); } /** * Fetches a list of activity options for setting data registry defaults. * * @param boolean $nodefaults If false, it will fetch all of the activities. Otherwise, it will only fetch the activities * that don't have defaults yet (e.g. when adding a new activity module defaults). * @return array */ public static function get_activity_options($nodefaults) { $warnings = []; $params = self::validate_parameters(self::get_activity_options_parameters(), [ 'nodefaults' => $nodefaults, ]); $nodefaults = $params['nodefaults']; $context = context_system::instance(); self::validate_context($context); // Get activity module plugin info. $pluginmanager = \core_plugin_manager::instance(); $modplugins = $pluginmanager->get_enabled_plugins('mod'); $modoptions = []; // Get the module-level defaults. data_registry::get_defaults falls back to this when there are no activity defaults. list($levelpurpose, $levelcategory) = data_registry::get_defaults(CONTEXT_MODULE); foreach ($modplugins as $name) { // Check if we have default purpose and category for this module if we want don't want to fetch everything. if ($nodefaults) { list($purpose, $category) = data_registry::get_defaults(CONTEXT_MODULE, $name); // Compare this with the module-level defaults. if ($purpose !== $levelpurpose || $category !== $levelcategory) { // If the defaults for this activity has been already set, there's no need to add this in the list of options. continue; } } $displayname = $pluginmanager->plugin_name('mod_' . $name); $modoptions[] = (object)[ 'name' => $name, 'displayname' => $displayname ]; } return [ 'options' => $modoptions, 'warnings' => $warnings ]; } /** * Returns for get_category_options(). * * @return external_single_structure */ public static function get_activity_options_returns() { $optionsdefinition = new external_single_structure( [ 'name' => new external_value(PARAM_TEXT, 'The plugin name of the activity'), 'displayname' => new external_value(PARAM_TEXT, 'The display name of the activity'), ] ); return new external_single_structure([ 'options' => new external_multiple_structure($optionsdefinition), 'warnings' => new external_warnings() ]); } /** * Gets the structure of a tree node (link + child branches). * * @since Moodle 3.5 * @param bool $allowchildbranches * @return array */ private static function get_tree_node_structure($allowchildbranches = true) { $fields = [ 'text' => new external_value(PARAM_RAW, 'The node text', VALUE_REQUIRED), 'expandcontextid' => new external_value(PARAM_INT, 'The contextid this node expands', VALUE_REQUIRED), 'expandelement' => new external_value(PARAM_ALPHA, 'What element is this node expanded to', VALUE_REQUIRED), 'contextid' => new external_value(PARAM_INT, 'The node contextid', VALUE_REQUIRED), 'contextlevel' => new external_value(PARAM_INT, 'The node contextlevel', VALUE_REQUIRED), 'expanded' => new external_value(PARAM_INT, 'Is it expanded', VALUE_REQUIRED), ]; if ($allowchildbranches) { // Passing false as we will not have more than 1 sub-level. $fields['branches'] = new external_multiple_structure( self::get_tree_node_structure(false), 'Children node structure', VALUE_OPTIONAL ); } else { // We will only have 1 sub-level and we don't want an infinite get_tree_node_structure, this is a hacky // way to prevent this infinite loop when calling get_tree_node_structure recursively. $fields['branches'] = new external_multiple_structure( new external_value( PARAM_TEXT, 'Nothing really, it will always be an empty array', VALUE_OPTIONAL ) ); } return new external_single_structure($fields, 'Node structure', VALUE_OPTIONAL); } }