MDL-63102 core_block: Reduced spacing between blocks
[moodle.git] / admin / tool / dataprivacy / classes / external.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/>.
16 /**
17  * Class containing the external API functions functions for the Data Privacy tool.
18  *
19  * @package    tool_dataprivacy
20  * @copyright  2018 Jun Pataleta
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
23 namespace tool_dataprivacy;
24 defined('MOODLE_INTERNAL') || die();
26 require_once("$CFG->libdir/externallib.php");
27 require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
29 use coding_exception;
30 use context_helper;
31 use context_system;
32 use context_user;
33 use core\invalid_persistent_exception;
34 use core\notification;
35 use core_user;
36 use dml_exception;
37 use external_api;
38 use external_description;
39 use external_function_parameters;
40 use external_multiple_structure;
41 use external_single_structure;
42 use external_value;
43 use external_warnings;
44 use invalid_parameter_exception;
45 use moodle_exception;
46 use required_capability_exception;
47 use restricted_context_exception;
48 use tool_dataprivacy\external\category_exporter;
49 use tool_dataprivacy\external\data_request_exporter;
50 use tool_dataprivacy\external\purpose_exporter;
51 use tool_dataprivacy\output\data_registry_page;
53 /**
54  * Class external.
55  *
56  * The external API for the Data Privacy tool.
57  *
58  * @copyright  2017 Jun Pataleta
59  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
60  */
61 class external extends external_api {
63     /**
64      * Parameter description for cancel_data_request().
65      *
66      * @since Moodle 3.5
67      * @return external_function_parameters
68      */
69     public static function cancel_data_request_parameters() {
70         return new external_function_parameters([
71             'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
72         ]);
73     }
75     /**
76      * Cancel a data request.
77      *
78      * @since Moodle 3.5
79      * @param int $requestid The request ID.
80      * @return array
81      * @throws invalid_persistent_exception
82      * @throws coding_exception
83      * @throws invalid_parameter_exception
84      * @throws restricted_context_exception
85      */
86     public static function cancel_data_request($requestid) {
87         global $USER;
89         $warnings = [];
90         $params = external_api::validate_parameters(self::cancel_data_request_parameters(), [
91             'requestid' => $requestid
92         ]);
93         $requestid = $params['requestid'];
95         // Validate context.
96         $context = context_user::instance($USER->id);
97         self::validate_context($context);
99         // Ensure the request exists.
100         $select = 'id = :id AND (userid = :userid OR requestedby = :requestedby)';
101         $params = ['id' => $requestid, 'userid' => $USER->id, 'requestedby' => $USER->id];
102         $requestexists = data_request::record_exists_select($select, $params);
104         $result = false;
105         if ($requestexists) {
106             // TODO: Do we want a request to be non-cancellable past a certain point? E.g. When it's already approved/processing.
107             $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_CANCELLED);
108         } else {
109             $warnings[] = [
110                 'item' => $requestid,
111                 'warningcode' => 'errorrequestnotfound',
112                 'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
113             ];
114         }
116         return [
117             'result' => $result,
118             'warnings' => $warnings
119         ];
120     }
122     /**
123      * Parameter description for cancel_data_request().
124      *
125      * @since Moodle 3.5
126      * @return external_description
127      */
128     public static function cancel_data_request_returns() {
129         return new external_single_structure([
130             'result' => new external_value(PARAM_BOOL, 'The processing result'),
131             'warnings' => new external_warnings()
132         ]);
133     }
135     /**
136      * Parameter description for contact_dpo().
137      *
138      * @since Moodle 3.5
139      * @return external_function_parameters
140      */
141     public static function contact_dpo_parameters() {
142         return new external_function_parameters([
143             'message' => new external_value(PARAM_TEXT, 'The user\'s message to the Data Protection Officer(s)', VALUE_REQUIRED)
144         ]);
145     }
147     /**
148      * Make a general enquiry to a DPO.
149      *
150      * @since Moodle 3.5
151      * @param string $message The message to be sent to the DPO.
152      * @return array
153      * @throws coding_exception
154      * @throws invalid_parameter_exception
155      * @throws invalid_persistent_exception
156      * @throws restricted_context_exception
157      * @throws dml_exception
158      * @throws moodle_exception
159      */
160     public static function contact_dpo($message) {
161         global $USER;
163         $warnings = [];
164         $params = external_api::validate_parameters(self::contact_dpo_parameters(), [
165             'message' => $message
166         ]);
167         $message = $params['message'];
169         // Validate context.
170         $userid = $USER->id;
171         $context = context_user::instance($userid);
172         self::validate_context($context);
174         // Lodge the request.
175         $datarequest = new data_request();
176         // The user the request is being made for.
177         $datarequest->set('userid', $userid);
178         // The user making the request.
179         $datarequest->set('requestedby', $userid);
180         // Set status.
181         $datarequest->set('status', api::DATAREQUEST_STATUS_PENDING);
182         // Set request type.
183         $datarequest->set('type', api::DATAREQUEST_TYPE_OTHERS);
184         // Set request comments.
185         $datarequest->set('comments', $message);
187         // Store subject access request.
188         $datarequest->create();
190         // Get the list of the site Data Protection Officers.
191         $dpos = api::get_site_dpos();
193         // Email the data request to the Data Protection Officer(s)/Admin(s).
194         $result = true;
195         foreach ($dpos as $dpo) {
196             $sendresult = api::notify_dpo($dpo, $datarequest);
197             if (!$sendresult) {
198                 $result = false;
199                 $warnings[] = [
200                     'item' => $dpo->id,
201                     'warningcode' => 'errorsendingtodpo',
202                     'message' => get_string('errorsendingmessagetodpo', 'tool_dataprivacy')
203                 ];
204             }
205         }
207         return [
208             'result' => $result,
209             'warnings' => $warnings
210         ];
211     }
213     /**
214      * Parameter description for contact_dpo().
215      *
216      * @since Moodle 3.5
217      * @return external_description
218      */
219     public static function contact_dpo_returns() {
220         return new external_single_structure([
221             'result' => new external_value(PARAM_BOOL, 'The processing result'),
222             'warnings' => new external_warnings()
223         ]);
224     }
226     /**
227      * Parameter description for mark_complete().
228      *
229      * @since Moodle 3.5.2
230      * @return external_function_parameters
231      */
232     public static function mark_complete_parameters() {
233         return new external_function_parameters([
234             'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
235         ]);
236     }
238     /**
239      * Mark a user's general enquiry's status as complete.
240      *
241      * @since Moodle 3.5.2
242      * @param int $requestid The request ID of the general enquiry.
243      * @return array
244      * @throws coding_exception
245      * @throws invalid_parameter_exception
246      * @throws invalid_persistent_exception
247      * @throws restricted_context_exception
248      * @throws dml_exception
249      * @throws moodle_exception
250      */
251     public static function mark_complete($requestid) {
252         global $USER;
254         $warnings = [];
255         $params = external_api::validate_parameters(self::mark_complete_parameters(), [
256             'requestid' => $requestid,
257         ]);
258         $requestid = $params['requestid'];
260         // Validate context.
261         $context = context_system::instance();
262         self::validate_context($context);
264         $message = get_string('markedcomplete', 'tool_dataprivacy');
265         // Update the data request record.
266         if ($result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE, $USER->id, $message)) {
267             // Add notification in the session to be shown when the page is reloaded on the JS side.
268             notification::success(get_string('requestmarkedcomplete', 'tool_dataprivacy'));
269         }
271         return [
272             'result' => $result,
273             'warnings' => $warnings
274         ];
275     }
277     /**
278      * Parameter description for mark_complete().
279      *
280      * @since Moodle 3.5.2
281      * @return external_description
282      */
283     public static function mark_complete_returns() {
284         return new external_single_structure([
285             'result' => new external_value(PARAM_BOOL, 'The processing result'),
286             'warnings' => new external_warnings()
287         ]);
288     }
290     /**
291      * Parameter description for get_data_request().
292      *
293      * @since Moodle 3.5
294      * @return external_function_parameters
295      */
296     public static function get_data_request_parameters() {
297         return new external_function_parameters([
298             'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
299         ]);
300     }
302     /**
303      * Fetch the details of a user's data request.
304      *
305      * @since Moodle 3.5
306      * @param int $requestid The request ID.
307      * @return array
308      * @throws coding_exception
309      * @throws dml_exception
310      * @throws invalid_parameter_exception
311      * @throws restricted_context_exception
312      * @throws moodle_exception
313      */
314     public static function get_data_request($requestid) {
315         global $PAGE;
317         $warnings = [];
318         $params = external_api::validate_parameters(self::get_data_request_parameters(), [
319             'requestid' => $requestid
320         ]);
321         $requestid = $params['requestid'];
323         // Validate context.
324         $context = context_system::instance();
325         self::validate_context($context);
326         $requestpersistent = new data_request($requestid);
327         require_capability('tool/dataprivacy:managedatarequests', $context);
329         $exporter = new data_request_exporter($requestpersistent, ['context' => $context]);
330         $renderer = $PAGE->get_renderer('tool_dataprivacy');
331         $result = $exporter->export($renderer);
333         return [
334             'result' => $result,
335             'warnings' => $warnings
336         ];
337     }
339     /**
340      * Parameter description for get_data_request().
341      *
342      * @since Moodle 3.5
343      * @return external_description
344      */
345     public static function get_data_request_returns() {
346         return new external_single_structure([
347             'result' => data_request_exporter::get_read_structure(),
348             'warnings' => new external_warnings()
349         ]);
350     }
352     /**
353      * Parameter description for approve_data_request().
354      *
355      * @since Moodle 3.5
356      * @return external_function_parameters
357      */
358     public static function approve_data_request_parameters() {
359         return new external_function_parameters([
360             'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
361         ]);
362     }
364     /**
365      * Approve a data request.
366      *
367      * @since Moodle 3.5
368      * @param int $requestid The request ID.
369      * @return array
370      * @throws coding_exception
371      * @throws dml_exception
372      * @throws invalid_parameter_exception
373      * @throws restricted_context_exception
374      * @throws moodle_exception
375      */
376     public static function approve_data_request($requestid) {
377         $warnings = [];
378         $params = external_api::validate_parameters(self::approve_data_request_parameters(), [
379             'requestid' => $requestid
380         ]);
381         $requestid = $params['requestid'];
383         // Validate context.
384         $context = context_system::instance();
385         self::validate_context($context);
386         require_capability('tool/dataprivacy:managedatarequests', $context);
388         // Ensure the request exists.
389         $requestexists = data_request::record_exists($requestid);
391         $result = false;
392         if ($requestexists) {
393             $result = api::approve_data_request($requestid);
395             // Add notification in the session to be shown when the page is reloaded on the JS side.
396             notification::success(get_string('requestapproved', 'tool_dataprivacy'));
397         } else {
398             $warnings[] = [
399                 'item' => $requestid,
400                 'warningcode' => 'errorrequestnotfound',
401                 'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
402             ];
403         }
405         return [
406             'result' => $result,
407             'warnings' => $warnings
408         ];
409     }
411     /**
412      * Parameter description for approve_data_request().
413      *
414      * @since Moodle 3.5
415      * @return external_description
416      */
417     public static function approve_data_request_returns() {
418         return new external_single_structure([
419             'result' => new external_value(PARAM_BOOL, 'The processing result'),
420             'warnings' => new external_warnings()
421         ]);
422     }
424     /**
425      * Parameter description for bulk_approve_data_requests().
426      *
427      * @since Moodle 3.5
428      * @return external_function_parameters
429      */
430     public static function bulk_approve_data_requests_parameters() {
431         return new external_function_parameters([
432             'requestids' => new external_multiple_structure(
433                 new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
434             )
435         ]);
436     }
438     /**
439      * Bulk approve bulk data request.
440      *
441      * @since Moodle 3.5
442      * @param array $requestids Array consisting the request ID's.
443      * @return array
444      * @throws coding_exception
445      * @throws dml_exception
446      * @throws invalid_parameter_exception
447      * @throws restricted_context_exception
448      * @throws moodle_exception
449      */
450     public static function bulk_approve_data_requests($requestids) {
451         $warnings = [];
452         $result = false;
453         $params = external_api::validate_parameters(self::bulk_approve_data_requests_parameters(), [
454             'requestids' => $requestids
455         ]);
456         $requestids = $params['requestids'];
458         // Validate context.
459         $context = context_system::instance();
460         self::validate_context($context);
461         require_capability('tool/dataprivacy:managedatarequests', $context);
463         foreach ($requestids as $requestid) {
464             // Ensure the request exists.
465             $requestexists = data_request::record_exists($requestid);
467             if ($requestexists) {
468                 api::approve_data_request($requestid);
469             } else {
470                 $warnings[] = [
471                     'item' => $requestid,
472                     'warningcode' => 'errorrequestnotfound',
473                     'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
474                 ];
475             }
476         }
478         if (empty($warnings)) {
479             $result = true;
480             // Add notification in the session to be shown when the page is reloaded on the JS side.
481             notification::success(get_string('requestsapproved', 'tool_dataprivacy'));
482         }
484         return [
485             'result' => $result,
486             'warnings' => $warnings
487         ];
488     }
490     /**
491      * Parameter description for bulk_approve_data_requests().
492      *
493      * @since Moodle 3.5
494      * @return external_description
495      */
496     public static function bulk_approve_data_requests_returns() {
497         return new external_single_structure([
498             'result' => new external_value(PARAM_BOOL, 'The processing result'),
499             'warnings' => new external_warnings()
500         ]);
501     }
503     /**
504      * Parameter description for deny_data_request().
505      *
506      * @since Moodle 3.5
507      * @return external_function_parameters
508      */
509     public static function deny_data_request_parameters() {
510         return new external_function_parameters([
511             'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
512         ]);
513     }
515     /**
516      * Deny a data request.
517      *
518      * @since Moodle 3.5
519      * @param int $requestid The request ID.
520      * @return array
521      * @throws coding_exception
522      * @throws dml_exception
523      * @throws invalid_parameter_exception
524      * @throws restricted_context_exception
525      * @throws moodle_exception
526      */
527     public static function deny_data_request($requestid) {
528         $warnings = [];
529         $params = external_api::validate_parameters(self::deny_data_request_parameters(), [
530             'requestid' => $requestid
531         ]);
532         $requestid = $params['requestid'];
534         // Validate context.
535         $context = context_system::instance();
536         self::validate_context($context);
537         require_capability('tool/dataprivacy:managedatarequests', $context);
539         // Ensure the request exists.
540         $requestexists = data_request::record_exists($requestid);
542         $result = false;
543         if ($requestexists) {
544             $result = api::deny_data_request($requestid);
546             // Add notification in the session to be shown when the page is reloaded on the JS side.
547             notification::success(get_string('requestdenied', 'tool_dataprivacy'));
548         } else {
549             $warnings[] = [
550                 'item' => $requestid,
551                 'warningcode' => 'errorrequestnotfound',
552                 'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
553             ];
554         }
556         return [
557             'result' => $result,
558             'warnings' => $warnings
559         ];
560     }
562     /**
563      * Parameter description for deny_data_request().
564      *
565      * @since Moodle 3.5
566      * @return external_description
567      */
568     public static function deny_data_request_returns() {
569         return new external_single_structure([
570             'result' => new external_value(PARAM_BOOL, 'The processing result'),
571             'warnings' => new external_warnings()
572         ]);
573     }
575     /**
576      * Parameter description for bulk_deny_data_requests().
577      *
578      * @since Moodle 3.5
579      * @return external_function_parameters
580      */
581     public static function bulk_deny_data_requests_parameters() {
582         return new external_function_parameters([
583             'requestids' => new external_multiple_structure(
584                 new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
585             )
586         ]);
587     }
589     /**
590      * Bulk deny data requests.
591      *
592      * @since Moodle 3.5
593      * @param array $requestids Array consisting of request ID's.
594      * @return array
595      * @throws coding_exception
596      * @throws dml_exception
597      * @throws invalid_parameter_exception
598      * @throws restricted_context_exception
599      * @throws moodle_exception
600      */
601     public static function bulk_deny_data_requests($requestids) {
602         $warnings = [];
603         $result = false;
604         $params = external_api::validate_parameters(self::bulk_deny_data_requests_parameters(), [
605             'requestids' => $requestids
606         ]);
607         $requestids = $params['requestids'];
609         // Validate context.
610         $context = context_system::instance();
611         self::validate_context($context);
612         require_capability('tool/dataprivacy:managedatarequests', $context);
614         foreach ($requestids as $requestid) {
615             // Ensure the request exists.
616             $requestexists = data_request::record_exists($requestid);
618             if ($requestexists) {
619                 api::deny_data_request($requestid);
620             } else {
621                 $warnings[] = [
622                     'item' => $requestid,
623                     'warningcode' => 'errorrequestnotfound',
624                     'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
625                 ];
626             }
627         }
629         if (empty($warnings)) {
630             $result = true;
631             // Add notification in the session to be shown when the page is reloaded on the JS side.
632             notification::success(get_string('requestsdenied', 'tool_dataprivacy'));
633         }
635         return [
636             'result' => $result,
637             'warnings' => $warnings
638         ];
639     }
641     /**
642      * Parameter description for bulk_deny_data_requests().
643      *
644      * @since Moodle 3.5
645      * @return external_description
646      */
647     public static function bulk_deny_data_requests_returns() {
648         return new external_single_structure([
649             'result' => new external_value(PARAM_BOOL, 'The processing result'),
650             'warnings' => new external_warnings()
651         ]);
652     }
654     /**
655      * Parameter description for get_data_request().
656      *
657      * @since Moodle 3.5
658      * @return external_function_parameters
659      */
660     public static function get_users_parameters() {
661         return new external_function_parameters([
662             'query' => new external_value(PARAM_TEXT, 'The search query', VALUE_REQUIRED)
663         ]);
664     }
666     /**
667      * Fetch the details of a user's data request.
668      *
669      * @since Moodle 3.5
670      * @param string $query The search request.
671      * @return array
672      * @throws required_capability_exception
673      * @throws dml_exception
674      * @throws invalid_parameter_exception
675      * @throws restricted_context_exception
676      */
677     public static function get_users($query) {
678         $params = external_api::validate_parameters(self::get_users_parameters(), [
679             'query' => $query
680         ]);
681         $query = $params['query'];
683         // Validate context.
684         $context = context_system::instance();
685         self::validate_context($context);
686         require_capability('tool/dataprivacy:managedatarequests', $context);
688         $allusernames = get_all_user_name_fields(true);
689         // Exclude admins and guest user.
690         $excludedusers = array_keys(get_admins()) + [guest_user()->id];
691         $sort = 'lastname ASC, firstname ASC';
692         $fields = 'id, email, ' . $allusernames;
693         $users = get_users(true, $query, true, $excludedusers, $sort, '', '', 0, 30, $fields);
694         $useroptions = [];
695         foreach ($users as $user) {
696             $useroptions[$user->id] = (object)[
697                 'id' => $user->id,
698                 'fullname' => fullname($user),
699                 'email' => $user->email
700             ];
701         }
703         return $useroptions;
704     }
706     /**
707      * Parameter description for get_users().
708      *
709      * @since Moodle 3.5
710      * @return external_description
711      * @throws coding_exception
712      */
713     public static function get_users_returns() {
714         return new external_multiple_structure(new external_single_structure(
715             [
716                 'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'),
717                 'fullname' => new external_value(core_user::get_property_type('firstname'), 'The fullname of the user'),
718                 'email' => new external_value(core_user::get_property_type('email'), 'The user\'s email address', VALUE_OPTIONAL),
719             ]
720         ));
721     }
723     /**
724      * Parameter description for create_purpose_form().
725      *
726      * @since Moodle 3.5
727      * @return external_function_parameters
728      */
729     public static function create_purpose_form_parameters() {
730         return new external_function_parameters([
731             'jsonformdata' => new external_value(PARAM_RAW, 'The data to create the purpose, encoded as a json array')
732         ]);
733     }
735     /**
736      * Creates a data purpose from form data.
737      *
738      * @since Moodle 3.5
739      * @param string $jsonformdata
740      * @return array
741      */
742     public static function create_purpose_form($jsonformdata) {
743         global $PAGE;
745         $warnings = [];
747         $params = external_api::validate_parameters(self::create_purpose_form_parameters(), [
748             'jsonformdata' => $jsonformdata
749         ]);
751         self::validate_context(\context_system::instance());
753         $serialiseddata = json_decode($params['jsonformdata']);
754         $data = array();
755         parse_str($serialiseddata, $data);
757         $purpose = new \tool_dataprivacy\purpose(0);
758         $mform = new \tool_dataprivacy\form\purpose(null, ['persistent' => $purpose], 'post', '', null, true, $data);
760         $validationerrors = true;
761         if ($validateddata = $mform->get_data()) {
762             $purpose = api::create_purpose($validateddata);
763             $validationerrors = false;
764         } else if ($errors = $mform->is_validated()) {
765             throw new moodle_exception('generalerror');
766         }
768         $exporter = new purpose_exporter($purpose, ['context' => \context_system::instance()]);
769         return [
770             'purpose' => $exporter->export($PAGE->get_renderer('core')),
771             'validationerrors' => $validationerrors,
772             'warnings' => $warnings
773         ];
774     }
776     /**
777      * Returns for create_purpose_form().
778      *
779      * @since Moodle 3.5
780      * @return external_single_structure
781      */
782     public static function create_purpose_form_returns() {
783         return new external_single_structure([
784             'purpose' => purpose_exporter::get_read_structure(),
785             'validationerrors' => new external_value(PARAM_BOOL, 'Were there validation errors', VALUE_REQUIRED),
786             'warnings' => new external_warnings()
787         ]);
788     }
790     /**
791      * Parameter description for delete_purpose().
792      *
793      * @since Moodle 3.5
794      * @return external_function_parameters
795      */
796     public static function delete_purpose_parameters() {
797         return new external_function_parameters([
798             'id' => new external_value(PARAM_INT, 'The purpose ID', VALUE_REQUIRED)
799         ]);
800     }
802     /**
803      * Deletes a data purpose.
804      *
805      * @since Moodle 3.5
806      * @param int $id The ID.
807      * @return array
808      * @throws invalid_persistent_exception
809      * @throws coding_exception
810      * @throws invalid_parameter_exception
811      */
812     public static function delete_purpose($id) {
813         global $USER;
815         $params = external_api::validate_parameters(self::delete_purpose_parameters(), [
816             'id' => $id
817         ]);
819         $result = api::delete_purpose($params['id']);
821         return [
822             'result' => $result,
823             'warnings' => []
824         ];
825     }
827     /**
828      * Parameter description for delete_purpose().
829      *
830      * @since Moodle 3.5
831      * @return external_single_structure
832      */
833     public static function delete_purpose_returns() {
834         return new external_single_structure([
835             'result' => new external_value(PARAM_BOOL, 'The processing result'),
836             'warnings' => new external_warnings()
837         ]);
838     }
840     /**
841      * Parameter description for create_category_form().
842      *
843      * @since Moodle 3.5
844      * @return external_function_parameters
845      */
846     public static function create_category_form_parameters() {
847         return new external_function_parameters([
848             'jsonformdata' => new external_value(PARAM_RAW, 'The data to create the category, encoded as a json array')
849         ]);
850     }
852     /**
853      * Creates a data category from form data.
854      *
855      * @since Moodle 3.5
856      * @param string $jsonformdata
857      * @return array
858      */
859     public static function create_category_form($jsonformdata) {
860         global $PAGE;
862         $warnings = [];
864         $params = external_api::validate_parameters(self::create_category_form_parameters(), [
865             'jsonformdata' => $jsonformdata
866         ]);
868         self::validate_context(\context_system::instance());
870         $serialiseddata = json_decode($params['jsonformdata']);
871         $data = array();
872         parse_str($serialiseddata, $data);
874         $category = new \tool_dataprivacy\category(0);
875         $mform = new \tool_dataprivacy\form\category(null, ['persistent' => $category], 'post', '', null, true, $data);
877         $validationerrors = true;
878         if ($validateddata = $mform->get_data()) {
879             $category = api::create_category($validateddata);
880             $validationerrors = false;
881         } else if ($errors = $mform->is_validated()) {
882             throw new moodle_exception('generalerror');
883         }
885         $exporter = new category_exporter($category, ['context' => \context_system::instance()]);
886         return [
887             'category' => $exporter->export($PAGE->get_renderer('core')),
888             'validationerrors' => $validationerrors,
889             'warnings' => $warnings
890         ];
891     }
893     /**
894      * Returns for create_category_form().
895      *
896      * @since Moodle 3.5
897      * @return external_single_structure
898      */
899     public static function create_category_form_returns() {
900         return new external_single_structure([
901             'category' => category_exporter::get_read_structure(),
902             'validationerrors' => new external_value(PARAM_BOOL, 'Were there validation errors', VALUE_REQUIRED),
903             'warnings' => new external_warnings()
904         ]);
905     }
907     /**
908      * Parameter description for delete_category().
909      *
910      * @since Moodle 3.5
911      * @return external_function_parameters
912      */
913     public static function delete_category_parameters() {
914         return new external_function_parameters([
915             'id' => new external_value(PARAM_INT, 'The category ID', VALUE_REQUIRED)
916         ]);
917     }
919     /**
920      * Deletes a data category.
921      *
922      * @since Moodle 3.5
923      * @param int $id The ID.
924      * @return array
925      * @throws invalid_persistent_exception
926      * @throws coding_exception
927      * @throws invalid_parameter_exception
928      */
929     public static function delete_category($id) {
930         global $USER;
932         $params = external_api::validate_parameters(self::delete_category_parameters(), [
933             'id' => $id
934         ]);
936         $result = api::delete_category($params['id']);
938         return [
939             'result' => $result,
940             'warnings' => []
941         ];
942     }
944     /**
945      * Parameter description for delete_category().
946      *
947      * @since Moodle 3.5
948      * @return external_single_structure
949      */
950     public static function delete_category_returns() {
951         return new external_single_structure([
952             'result' => new external_value(PARAM_BOOL, 'The processing result'),
953             'warnings' => new external_warnings()
954         ]);
955     }
957     /**
958      * Parameter description for set_contextlevel_form().
959      *
960      * @since Moodle 3.5
961      * @return external_function_parameters
962      */
963     public static function set_contextlevel_form_parameters() {
964         return new external_function_parameters([
965             'jsonformdata' => new external_value(PARAM_RAW, 'The context level data, encoded as a json array')
966         ]);
967     }
969     /**
970      * Creates a data category from form data.
971      *
972      * @since Moodle 3.5
973      * @param string $jsonformdata
974      * @return array
975      */
976     public static function set_contextlevel_form($jsonformdata) {
977         global $PAGE;
979         $warnings = [];
981         $params = external_api::validate_parameters(self::set_contextlevel_form_parameters(), [
982             'jsonformdata' => $jsonformdata
983         ]);
985         // Extra permission checkings are delegated to api::set_contextlevel.
986         self::validate_context(\context_system::instance());
988         $serialiseddata = json_decode($params['jsonformdata']);
989         $data = array();
990         parse_str($serialiseddata, $data);
992         $contextlevel = $data['contextlevel'];
994         $customdata = \tool_dataprivacy\form\contextlevel::get_contextlevel_customdata($contextlevel);
995         $mform = new \tool_dataprivacy\form\contextlevel(null, $customdata, 'post', '', null, true, $data);
996         if ($validateddata = $mform->get_data()) {
997             $contextlevel = api::set_contextlevel($validateddata);
998         } else if ($errors = $mform->is_validated()) {
999             $warnings[] = json_encode($errors);
1000             throw new moodle_exception('generalerror');
1001         }
1003         if ($contextlevel) {
1004             $result = true;
1005         } else {
1006             $result = false;
1007         }
1008         return [
1009             'result' => $result,
1010             'warnings' => $warnings
1011         ];
1012     }
1014     /**
1015      * Returns for set_contextlevel_form().
1016      *
1017      * @since Moodle 3.5
1018      * @return external_single_structure
1019      */
1020     public static function set_contextlevel_form_returns() {
1021         return new external_single_structure([
1022             'result' => new external_value(PARAM_BOOL, 'Whether the data was properly set or not'),
1023             'warnings' => new external_warnings()
1024         ]);
1025     }
1027     /**
1028      * Parameter description for set_context_form().
1029      *
1030      * @since Moodle 3.5
1031      * @return external_function_parameters
1032      */
1033     public static function set_context_form_parameters() {
1034         return new external_function_parameters([
1035             'jsonformdata' => new external_value(PARAM_RAW, 'The context level data, encoded as a json array')
1036         ]);
1037     }
1039     /**
1040      * Creates a data category from form data.
1041      *
1042      * @since Moodle 3.5
1043      * @param string $jsonformdata
1044      * @return array
1045      */
1046     public static function set_context_form($jsonformdata) {
1047         global $PAGE;
1049         $warnings = [];
1051         $params = external_api::validate_parameters(self::set_context_form_parameters(), [
1052             'jsonformdata' => $jsonformdata
1053         ]);
1055         // Extra permission checkings are delegated to api::set_context_instance.
1056         self::validate_context(\context_system::instance());
1058         $serialiseddata = json_decode($params['jsonformdata']);
1059         $data = array();
1060         parse_str($serialiseddata, $data);
1062         $context = context_helper::instance_by_id($data['contextid']);
1063         $customdata = \tool_dataprivacy\form\context_instance::get_context_instance_customdata($context);
1064         $mform = new \tool_dataprivacy\form\context_instance(null, $customdata, 'post', '', null, true, $data);
1065         if ($validateddata = $mform->get_data()) {
1066             $context = api::set_context_instance($validateddata);
1067         } else if ($errors = $mform->is_validated()) {
1068             $warnings[] = json_encode($errors);
1069             throw new moodle_exception('generalerror');
1070         }
1072         if ($context) {
1073             $result = true;
1074         } else {
1075             $result = false;
1076         }
1077         return [
1078             'result' => $result,
1079             'warnings' => $warnings
1080         ];
1081     }
1083     /**
1084      * Returns for set_context_form().
1085      *
1086      * @since Moodle 3.5
1087      * @return external_single_structure
1088      */
1089     public static function set_context_form_returns() {
1090         return new external_single_structure([
1091             'result' => new external_value(PARAM_BOOL, 'Whether the data was properly set or not'),
1092             'warnings' => new external_warnings()
1093         ]);
1094     }
1096     /**
1097      * Parameter description for tree_extra_branches().
1098      *
1099      * @since Moodle 3.5
1100      * @return external_function_parameters
1101      */
1102     public static function tree_extra_branches_parameters() {
1103         return new external_function_parameters([
1104             'contextid' => new external_value(PARAM_INT, 'The context id to expand'),
1105             'element' => new external_value(PARAM_ALPHA, 'The element we are interested on')
1106         ]);
1107     }
1109     /**
1110      * Returns tree extra branches.
1111      *
1112      * @since Moodle 3.5
1113      * @param int $contextid
1114      * @param string $element
1115      * @return array
1116      */
1117     public static function tree_extra_branches($contextid, $element) {
1119         $params = external_api::validate_parameters(self::tree_extra_branches_parameters(), [
1120             'contextid' => $contextid,
1121             'element' => $element,
1122         ]);
1124         $context = context_helper::instance_by_id($params['contextid']);
1126         self::validate_context($context);
1127         api::check_can_manage_data_registry($context->id);
1129         switch ($params['element']) {
1130             case 'course':
1131                 $branches = data_registry_page::get_courses_branch($context);
1132                 break;
1133             case 'module':
1134                 $branches = data_registry_page::get_modules_branch($context);
1135                 break;
1136             case 'block':
1137                 $branches = data_registry_page::get_blocks_branch($context);
1138                 break;
1139             default:
1140                 throw new \moodle_exception('Unsupported element provided.');
1141         }
1143         return [
1144             'branches' => $branches,
1145             'warnings' => [],
1146         ];
1147     }
1149     /**
1150      * Returns for tree_extra_branches().
1151      *
1152      * @since Moodle 3.5
1153      * @return external_single_structure
1154      */
1155     public static function tree_extra_branches_returns() {
1156         return new external_single_structure([
1157             'branches' => new external_multiple_structure(self::get_tree_node_structure(true)),
1158             'warnings' => new external_warnings()
1159         ]);
1160     }
1162     /**
1163      * Parameters for confirm_contexts_for_deletion().
1164      *
1165      * @since Moodle 3.5
1166      * @return external_function_parameters
1167      */
1168     public static function confirm_contexts_for_deletion_parameters() {
1169         return new external_function_parameters([
1170             'ids' => new external_multiple_structure(
1171                 new external_value(PARAM_INT, 'Expired context record ID', VALUE_REQUIRED),
1172                 'Array of expired context record IDs', VALUE_DEFAULT, []
1173             ),
1174         ]);
1175     }
1177     /**
1178      * Confirm a given array of expired context record IDs
1179      *
1180      * @since Moodle 3.5
1181      * @param int[] $ids Array of record IDs from the expired contexts table.
1182      * @return array
1183      * @throws coding_exception
1184      * @throws dml_exception
1185      * @throws invalid_parameter_exception
1186      * @throws restricted_context_exception
1187      */
1188     public static function confirm_contexts_for_deletion($ids) {
1189         $warnings = [];
1190         $params = external_api::validate_parameters(self::confirm_contexts_for_deletion_parameters(), [
1191             'ids' => $ids
1192         ]);
1193         $ids = $params['ids'];
1195         // Validate context.
1196         $context = context_system::instance();
1197         self::validate_context($context);
1199         $result = true;
1200         if (!empty($ids)) {
1201             $expiredcontextstoapprove = [];
1202             // Loop through the deletion of expired contexts and their children if necessary.
1203             foreach ($ids as $id) {
1204                 $expiredcontext = new expired_context($id);
1205                 $targetcontext = context_helper::instance_by_id($expiredcontext->get('contextid'));
1207                 // Fetch this context's child contexts. Make sure that all of the child contexts are flagged for deletion.
1208                 $childcontexts = $targetcontext->get_child_contexts();
1209                 foreach ($childcontexts as $child) {
1210                     if ($expiredchildcontext = expired_context::get_record(['contextid' => $child->id])) {
1211                         // Add this child context to the list for approval.
1212                         $expiredcontextstoapprove[] = $expiredchildcontext;
1213                     } else {
1214                         // This context has not yet been flagged for deletion.
1215                         $result = false;
1216                         $message = get_string('errorcontexthasunexpiredchildren', 'tool_dataprivacy',
1217                             $targetcontext->get_context_name(false));
1218                         $warnings[] = [
1219                             'item' => 'tool_dataprivacy_ctxexpired',
1220                             'warningcode' => 'errorcontexthasunexpiredchildren',
1221                             'message' => $message
1222                         ];
1223                         // Exit the process.
1224                         break 2;
1225                     }
1226                 }
1228                 $expiredcontextstoapprove[] = $expiredcontext;
1229             }
1231             // Proceed with the approval if everything's in order.
1232             if ($result) {
1233                 // Mark expired contexts as approved for deletion.
1234                 foreach ($expiredcontextstoapprove as $expired) {
1235                     // Only mark expired contexts that are pending approval.
1236                     if ($expired->get('status') == expired_context::STATUS_EXPIRED) {
1237                         api::set_expired_context_status($expired, expired_context::STATUS_APPROVED);
1238                     }
1239                 }
1240             }
1242         } else {
1243             // We don't have anything to process.
1244             $result = false;
1245             $warnings[] = [
1246                 'item' => 'tool_dataprivacy_ctxexpired',
1247                 'warningcode' => 'errornoexpiredcontexts',
1248                 'message' => get_string('errornoexpiredcontexts', 'tool_dataprivacy')
1249             ];
1250         }
1252         return [
1253             'result' => $result,
1254             'warnings' => $warnings
1255         ];
1256     }
1258     /**
1259      * Returns for confirm_contexts_for_deletion().
1260      *
1261      * @since Moodle 3.5
1262      * @return external_single_structure
1263      */
1264     public static function confirm_contexts_for_deletion_returns() {
1265         return new external_single_structure([
1266             'result' => new external_value(PARAM_BOOL, 'Whether the record was properly marked for deletion or not'),
1267             'warnings' => new external_warnings()
1268         ]);
1269     }
1271     /**
1272      * Parameters for set_context_defaults().
1273      *
1274      * @return external_function_parameters
1275      */
1276     public static function set_context_defaults_parameters() {
1277         return new external_function_parameters([
1278             'contextlevel' => new external_value(PARAM_INT, 'The context level', VALUE_REQUIRED),
1279             'category' => new external_value(PARAM_INT, 'The default category for the given context level', VALUE_REQUIRED),
1280             'purpose' => new external_value(PARAM_INT, 'The default purpose for the given context level', VALUE_REQUIRED),
1281             'activity' => new external_value(PARAM_PLUGIN, 'The plugin name of the activity', VALUE_DEFAULT, null),
1282             'override' => new external_value(PARAM_BOOL, 'Whether to override existing instances with the defaults', VALUE_DEFAULT,
1283                 false),
1284         ]);
1285     }
1287     /**
1288      * Updates the default category and purpose for a given context level (and optionally, a plugin).
1289      *
1290      * @param int $contextlevel The context level.
1291      * @param int $category The ID matching the category.
1292      * @param int $purpose The ID matching the purpose record.
1293      * @param int $activity The name of the activity that we're making a defaults configuration for.
1294      * @param bool $override Whether to override the purpose/categories of existing instances to these defaults.
1295      * @return array
1296      */
1297     public static function set_context_defaults($contextlevel, $category, $purpose, $activity, $override) {
1298         $warnings = [];
1300         $params = external_api::validate_parameters(self::set_context_defaults_parameters(), [
1301             'contextlevel' => $contextlevel,
1302             'category' => $category,
1303             'purpose' => $purpose,
1304             'activity' => $activity,
1305             'override' => $override,
1306         ]);
1307         $contextlevel = $params['contextlevel'];
1308         $category = $params['category'];
1309         $purpose = $params['purpose'];
1310         $activity = $params['activity'];
1311         $override = $params['override'];
1313         // Validate context.
1314         $context = context_system::instance();
1315         self::validate_context($context);
1317         // Set the context defaults.
1318         $result = api::set_context_defaults($contextlevel, $category, $purpose, $activity, $override);
1320         return [
1321             'result' => $result,
1322             'warnings' => $warnings
1323         ];
1324     }
1326     /**
1327      * Returns for set_context_defaults().
1328      *
1329      * @return external_single_structure
1330      */
1331     public static function set_context_defaults_returns() {
1332         return new external_single_structure([
1333             'result' => new external_value(PARAM_BOOL, 'Whether the context defaults were successfully set or not'),
1334             'warnings' => new external_warnings()
1335         ]);
1336     }
1338     /**
1339      * Parameters for get_category_options().
1340      *
1341      * @return external_function_parameters
1342      */
1343     public static function get_category_options_parameters() {
1344         return new external_function_parameters([
1345             'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
1346             'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
1347         ]);
1348     }
1350     /**
1351      * Fetches a list of data category options containing category IDs as keys and the category name for the value.
1352      *
1353      * @param bool $includeinherit Whether to include the "Inherit" option.
1354      * @param bool $includenotset Whether to include the "Not set" option.
1355      * @return array
1356      */
1357     public static function get_category_options($includeinherit, $includenotset) {
1358         $warnings = [];
1360         $params = self::validate_parameters(self::get_category_options_parameters(), [
1361             'includeinherit' => $includeinherit,
1362             'includenotset' => $includenotset
1363         ]);
1364         $includeinherit = $params['includeinherit'];
1365         $includenotset = $params['includenotset'];
1367         $context = context_system::instance();
1368         self::validate_context($context);
1370         $categories = api::get_categories();
1371         $options = data_registry_page::category_options($categories, $includenotset, $includeinherit);
1372         $categoryoptions = [];
1373         foreach ($options as $id => $name) {
1374             $categoryoptions[] = [
1375                 'id' => $id,
1376                 'name' => $name,
1377             ];
1378         }
1380         return [
1381             'options' => $categoryoptions,
1382             'warnings' => $warnings
1383         ];
1384     }
1386     /**
1387      * Returns for get_category_options().
1388      *
1389      * @return external_single_structure
1390      */
1391     public static function get_category_options_returns() {
1392         $optiondefinition = new external_single_structure(
1393             [
1394                 'id' => new external_value(PARAM_INT, 'The category ID'),
1395                 'name' => new external_value(PARAM_TEXT, 'The category name'),
1396             ]
1397         );
1399         return new external_single_structure([
1400             'options' => new external_multiple_structure($optiondefinition),
1401             'warnings' => new external_warnings()
1402         ]);
1403     }
1405     /**
1406      * Parameters for get_purpose_options().
1407      *
1408      * @return external_function_parameters
1409      */
1410     public static function get_purpose_options_parameters() {
1411         return new external_function_parameters([
1412             'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
1413             'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
1414         ]);
1415     }
1417     /**
1418      * Fetches a list of data storage purposes containing purpose IDs as keys and the purpose name for the value.
1419      *
1420      * @param bool $includeinherit Whether to include the "Inherit" option.
1421      * @param bool $includenotset Whether to include the "Not set" option.
1422      * @return array
1423      */
1424     public static function get_purpose_options($includeinherit, $includenotset) {
1425         $warnings = [];
1427         $params = self::validate_parameters(self::get_category_options_parameters(), [
1428             'includeinherit' => $includeinherit,
1429             'includenotset' => $includenotset
1430         ]);
1431         $includeinherit = $params['includeinherit'];
1432         $includenotset = $params['includenotset'];
1434         $context = context_system::instance();
1435         self::validate_context($context);
1437         $purposes = api::get_purposes();
1438         $options = data_registry_page::purpose_options($purposes, $includenotset, $includeinherit);
1439         $purposeoptions = [];
1440         foreach ($options as $id => $name) {
1441             $purposeoptions[] = [
1442                 'id' => $id,
1443                 'name' => $name,
1444             ];
1445         }
1447         return [
1448             'options' => $purposeoptions,
1449             'warnings' => $warnings
1450         ];
1451     }
1453     /**
1454      * Returns for get_purpose_options().
1455      *
1456      * @return external_single_structure
1457      */
1458     public static function get_purpose_options_returns() {
1459         $optiondefinition = new external_single_structure(
1460             [
1461                 'id' => new external_value(PARAM_INT, 'The purpose ID'),
1462                 'name' => new external_value(PARAM_TEXT, 'The purpose name'),
1463             ]
1464         );
1466         return new external_single_structure([
1467             'options' => new external_multiple_structure($optiondefinition),
1468             'warnings' => new external_warnings()
1469         ]);
1470     }
1472     /**
1473      * Parameters for get_activity_options().
1474      *
1475      * @return external_function_parameters
1476      */
1477     public static function get_activity_options_parameters() {
1478         return new external_function_parameters([
1479             'nodefaults' => new external_value(PARAM_BOOL, 'Whether to fetch all activities or only those without defaults',
1480                 VALUE_DEFAULT, false),
1481         ]);
1482     }
1484     /**
1485      * Fetches a list of activity options for setting data registry defaults.
1486      *
1487      * @param boolean $nodefaults If false, it will fetch all of the activities. Otherwise, it will only fetch the activities
1488      *                            that don't have defaults yet (e.g. when adding a new activity module defaults).
1489      * @return array
1490      */
1491     public static function get_activity_options($nodefaults) {
1492         $warnings = [];
1494         $params = self::validate_parameters(self::get_activity_options_parameters(), [
1495             'nodefaults' => $nodefaults,
1496         ]);
1497         $nodefaults = $params['nodefaults'];
1499         $context = context_system::instance();
1500         self::validate_context($context);
1502         // Get activity module plugin info.
1503         $pluginmanager = \core_plugin_manager::instance();
1504         $modplugins = $pluginmanager->get_enabled_plugins('mod');
1505         $modoptions = [];
1507         // Get the module-level defaults. data_registry::get_defaults falls back to this when there are no activity defaults.
1508         list($levelpurpose, $levelcategory) = data_registry::get_defaults(CONTEXT_MODULE);
1509         foreach ($modplugins as $name) {
1510             // Check if we have default purpose and category for this module if we want don't want to fetch everything.
1511             if ($nodefaults) {
1512                 list($purpose, $category) = data_registry::get_defaults(CONTEXT_MODULE, $name);
1513                 // Compare this with the module-level defaults.
1514                 if ($purpose !== $levelpurpose || $category !== $levelcategory) {
1515                     // If the defaults for this activity has been already set, there's no need to add this in the list of options.
1516                     continue;
1517                 }
1518             }
1520             $displayname = $pluginmanager->plugin_name('mod_' . $name);
1521             $modoptions[] = (object)[
1522                 'name' => $name,
1523                 'displayname' => $displayname
1524             ];
1525         }
1527         return [
1528             'options' => $modoptions,
1529             'warnings' => $warnings
1530         ];
1531     }
1533     /**
1534      * Returns for get_category_options().
1535      *
1536      * @return external_single_structure
1537      */
1538     public static function get_activity_options_returns() {
1539         $optionsdefinition = new external_single_structure(
1540             [
1541                 'name' => new external_value(PARAM_TEXT, 'The plugin name of the activity'),
1542                 'displayname' => new external_value(PARAM_TEXT, 'The display name of the activity'),
1543             ]
1544         );
1546         return new external_single_structure([
1547             'options' => new external_multiple_structure($optionsdefinition),
1548             'warnings' => new external_warnings()
1549         ]);
1550     }
1552     /**
1553      * Gets the structure of a tree node (link + child branches).
1554      *
1555      * @since Moodle 3.5
1556      * @param bool $allowchildbranches
1557      * @return array
1558      */
1559     private static function get_tree_node_structure($allowchildbranches = true) {
1560         $fields = [
1561             'text' => new external_value(PARAM_TEXT, 'The node text', VALUE_REQUIRED),
1562             'expandcontextid' => new external_value(PARAM_INT, 'The contextid this node expands', VALUE_REQUIRED),
1563             'expandelement' => new external_value(PARAM_ALPHA, 'What element is this node expanded to', VALUE_REQUIRED),
1564             'contextid' => new external_value(PARAM_INT, 'The node contextid', VALUE_REQUIRED),
1565             'contextlevel' => new external_value(PARAM_INT, 'The node contextlevel', VALUE_REQUIRED),
1566             'expanded' => new external_value(PARAM_INT, 'Is it expanded', VALUE_REQUIRED),
1567         ];
1569         if ($allowchildbranches) {
1570             // Passing false as we will not have more than 1 sub-level.
1571             $fields['branches'] = new external_multiple_structure(
1572                 self::get_tree_node_structure(false),
1573                 'Children node structure',
1574                 VALUE_OPTIONAL
1575             );
1576         } else {
1577             // We will only have 1 sub-level and we don't want an infinite get_tree_node_structure, this is a hacky
1578             // way to prevent this infinite loop when calling get_tree_node_structure recursively.
1579             $fields['branches'] = new external_multiple_structure(
1580                 new external_value(
1581                     PARAM_TEXT,
1582                     'Nothing really, it will always be an empty array',
1583                     VALUE_OPTIONAL
1584                 )
1585             );
1586         }
1588         return new external_single_structure($fields, 'Node structure', VALUE_OPTIONAL);
1589     }