Merge branch 'MDL-63102-master' of git://github.com/peterRd/moodle
[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 and access to manage the registry.
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         $requests = data_request::get_records_select($select, $params);
103         $requestexists = count($requests) === 1;
105         $result = false;
106         if ($requestexists) {
107             $request = reset($requests);
108             $datasubject = $request->get('userid');
110             if ($datasubject !== $USER->id) {
111                 // The user is not the subject. Check that they can cancel this request.
112                 if (!api::can_create_data_request_for_user($datasubject)) {
113                     $forusercontext = \context_user::instance($datasubject);
114                     throw new required_capability_exception($forusercontext,
115                             'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
116                 }
117             }
119             // TODO: Do we want a request to be non-cancellable past a certain point? E.g. When it's already approved/processing.
120             $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_CANCELLED);
121         } else {
122             $warnings[] = [
123                 'item' => $requestid,
124                 'warningcode' => 'errorrequestnotfound',
125                 'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
126             ];
127         }
129         return [
130             'result' => $result,
131             'warnings' => $warnings
132         ];
133     }
135     /**
136      * Parameter description for cancel_data_request().
137      *
138      * @since Moodle 3.5
139      * @return external_description
140      */
141     public static function cancel_data_request_returns() {
142         return new external_single_structure([
143             'result' => new external_value(PARAM_BOOL, 'The processing result'),
144             'warnings' => new external_warnings()
145         ]);
146     }
148     /**
149      * Parameter description for contact_dpo().
150      *
151      * @since Moodle 3.5
152      * @return external_function_parameters
153      */
154     public static function contact_dpo_parameters() {
155         return new external_function_parameters([
156             'message' => new external_value(PARAM_TEXT, 'The user\'s message to the Data Protection Officer(s)', VALUE_REQUIRED)
157         ]);
158     }
160     /**
161      * Make a general enquiry to a DPO.
162      *
163      * @since Moodle 3.5
164      * @param string $message The message to be sent to the DPO.
165      * @return array
166      * @throws coding_exception
167      * @throws invalid_parameter_exception
168      * @throws invalid_persistent_exception
169      * @throws restricted_context_exception
170      * @throws dml_exception
171      * @throws moodle_exception
172      */
173     public static function contact_dpo($message) {
174         global $USER;
176         $warnings = [];
177         $params = external_api::validate_parameters(self::contact_dpo_parameters(), [
178             'message' => $message
179         ]);
180         $message = $params['message'];
182         // Validate context.
183         $userid = $USER->id;
184         $context = context_user::instance($userid);
185         self::validate_context($context);
187         // Lodge the request.
188         $datarequest = new data_request();
189         // The user the request is being made for.
190         $datarequest->set('userid', $userid);
191         // The user making the request.
192         $datarequest->set('requestedby', $userid);
193         // Set status.
194         $datarequest->set('status', api::DATAREQUEST_STATUS_PENDING);
195         // Set request type.
196         $datarequest->set('type', api::DATAREQUEST_TYPE_OTHERS);
197         // Set request comments.
198         $datarequest->set('comments', $message);
200         // Store subject access request.
201         $datarequest->create();
203         // Get the list of the site Data Protection Officers.
204         $dpos = api::get_site_dpos();
206         // Email the data request to the Data Protection Officer(s)/Admin(s).
207         $result = true;
208         foreach ($dpos as $dpo) {
209             $sendresult = api::notify_dpo($dpo, $datarequest);
210             if (!$sendresult) {
211                 $result = false;
212                 $warnings[] = [
213                     'item' => $dpo->id,
214                     'warningcode' => 'errorsendingtodpo',
215                     'message' => get_string('errorsendingmessagetodpo', 'tool_dataprivacy')
216                 ];
217             }
218         }
220         return [
221             'result' => $result,
222             'warnings' => $warnings
223         ];
224     }
226     /**
227      * Parameter description for contact_dpo().
228      *
229      * @since Moodle 3.5
230      * @return external_description
231      */
232     public static function contact_dpo_returns() {
233         return new external_single_structure([
234             'result' => new external_value(PARAM_BOOL, 'The processing result'),
235             'warnings' => new external_warnings()
236         ]);
237     }
239     /**
240      * Parameter description for mark_complete().
241      *
242      * @since Moodle 3.5.2
243      * @return external_function_parameters
244      */
245     public static function mark_complete_parameters() {
246         return new external_function_parameters([
247             'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
248         ]);
249     }
251     /**
252      * Mark a user's general enquiry's status as complete.
253      *
254      * @since Moodle 3.5.2
255      * @param int $requestid The request ID of the general enquiry.
256      * @return array
257      * @throws coding_exception
258      * @throws invalid_parameter_exception
259      * @throws invalid_persistent_exception
260      * @throws restricted_context_exception
261      * @throws dml_exception
262      * @throws moodle_exception
263      */
264     public static function mark_complete($requestid) {
265         global $USER;
267         $warnings = [];
268         $params = external_api::validate_parameters(self::mark_complete_parameters(), [
269             'requestid' => $requestid,
270         ]);
271         $requestid = $params['requestid'];
273         // Validate context and access to manage the registry.
274         $context = context_system::instance();
275         self::validate_context($context);
276         api::check_can_manage_data_registry();
278         $message = get_string('markedcomplete', 'tool_dataprivacy');
279         // Update the data request record.
280         if ($result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE, $USER->id, $message)) {
281             // Add notification in the session to be shown when the page is reloaded on the JS side.
282             notification::success(get_string('requestmarkedcomplete', 'tool_dataprivacy'));
283         }
285         return [
286             'result' => $result,
287             'warnings' => $warnings
288         ];
289     }
291     /**
292      * Parameter description for mark_complete().
293      *
294      * @since Moodle 3.5.2
295      * @return external_description
296      */
297     public static function mark_complete_returns() {
298         return new external_single_structure([
299             'result' => new external_value(PARAM_BOOL, 'The processing result'),
300             'warnings' => new external_warnings()
301         ]);
302     }
304     /**
305      * Parameter description for get_data_request().
306      *
307      * @since Moodle 3.5
308      * @return external_function_parameters
309      */
310     public static function get_data_request_parameters() {
311         return new external_function_parameters([
312             'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
313         ]);
314     }
316     /**
317      * Fetch the details of a user's data request.
318      *
319      * @since Moodle 3.5
320      * @param int $requestid The request ID.
321      * @return array
322      * @throws coding_exception
323      * @throws dml_exception
324      * @throws invalid_parameter_exception
325      * @throws restricted_context_exception
326      * @throws moodle_exception
327      */
328     public static function get_data_request($requestid) {
329         global $PAGE;
331         $warnings = [];
332         $params = external_api::validate_parameters(self::get_data_request_parameters(), [
333             'requestid' => $requestid
334         ]);
335         $requestid = $params['requestid'];
337         // Validate context.
338         $context = context_system::instance();
339         self::validate_context($context);
340         $requestpersistent = new data_request($requestid);
341         require_capability('tool/dataprivacy:managedatarequests', $context);
343         $exporter = new data_request_exporter($requestpersistent, ['context' => $context]);
344         $renderer = $PAGE->get_renderer('tool_dataprivacy');
345         $result = $exporter->export($renderer);
347         return [
348             'result' => $result,
349             'warnings' => $warnings
350         ];
351     }
353     /**
354      * Parameter description for get_data_request().
355      *
356      * @since Moodle 3.5
357      * @return external_description
358      */
359     public static function get_data_request_returns() {
360         return new external_single_structure([
361             'result' => data_request_exporter::get_read_structure(),
362             'warnings' => new external_warnings()
363         ]);
364     }
366     /**
367      * Parameter description for approve_data_request().
368      *
369      * @since Moodle 3.5
370      * @return external_function_parameters
371      */
372     public static function approve_data_request_parameters() {
373         return new external_function_parameters([
374             'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
375         ]);
376     }
378     /**
379      * Approve a data request.
380      *
381      * @since Moodle 3.5
382      * @param int $requestid The request ID.
383      * @return array
384      * @throws coding_exception
385      * @throws dml_exception
386      * @throws invalid_parameter_exception
387      * @throws restricted_context_exception
388      * @throws moodle_exception
389      */
390     public static function approve_data_request($requestid) {
391         $warnings = [];
392         $params = external_api::validate_parameters(self::approve_data_request_parameters(), [
393             'requestid' => $requestid
394         ]);
395         $requestid = $params['requestid'];
397         // Validate context.
398         $context = context_system::instance();
399         self::validate_context($context);
400         require_capability('tool/dataprivacy:managedatarequests', $context);
402         // Ensure the request exists.
403         $requestexists = data_request::record_exists($requestid);
405         $result = false;
406         if ($requestexists) {
407             $result = api::approve_data_request($requestid);
409             // Add notification in the session to be shown when the page is reloaded on the JS side.
410             notification::success(get_string('requestapproved', 'tool_dataprivacy'));
411         } else {
412             $warnings[] = [
413                 'item' => $requestid,
414                 'warningcode' => 'errorrequestnotfound',
415                 'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
416             ];
417         }
419         return [
420             'result' => $result,
421             'warnings' => $warnings
422         ];
423     }
425     /**
426      * Parameter description for approve_data_request().
427      *
428      * @since Moodle 3.5
429      * @return external_description
430      */
431     public static function approve_data_request_returns() {
432         return new external_single_structure([
433             'result' => new external_value(PARAM_BOOL, 'The processing result'),
434             'warnings' => new external_warnings()
435         ]);
436     }
438     /**
439      * Parameter description for bulk_approve_data_requests().
440      *
441      * @since Moodle 3.5
442      * @return external_function_parameters
443      */
444     public static function bulk_approve_data_requests_parameters() {
445         return new external_function_parameters([
446             'requestids' => new external_multiple_structure(
447                 new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
448             )
449         ]);
450     }
452     /**
453      * Bulk approve bulk data request.
454      *
455      * @since Moodle 3.5
456      * @param array $requestids Array consisting the request ID's.
457      * @return array
458      * @throws coding_exception
459      * @throws dml_exception
460      * @throws invalid_parameter_exception
461      * @throws restricted_context_exception
462      * @throws moodle_exception
463      */
464     public static function bulk_approve_data_requests($requestids) {
465         $warnings = [];
466         $result = false;
467         $params = external_api::validate_parameters(self::bulk_approve_data_requests_parameters(), [
468             'requestids' => $requestids
469         ]);
470         $requestids = $params['requestids'];
472         // Validate context.
473         $context = context_system::instance();
474         self::validate_context($context);
475         require_capability('tool/dataprivacy:managedatarequests', $context);
477         foreach ($requestids as $requestid) {
478             // Ensure the request exists.
479             $requestexists = data_request::record_exists($requestid);
481             if ($requestexists) {
482                 api::approve_data_request($requestid);
483             } else {
484                 $warnings[] = [
485                     'item' => $requestid,
486                     'warningcode' => 'errorrequestnotfound',
487                     'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
488                 ];
489             }
490         }
492         if (empty($warnings)) {
493             $result = true;
494             // Add notification in the session to be shown when the page is reloaded on the JS side.
495             notification::success(get_string('requestsapproved', 'tool_dataprivacy'));
496         }
498         return [
499             'result' => $result,
500             'warnings' => $warnings
501         ];
502     }
504     /**
505      * Parameter description for bulk_approve_data_requests().
506      *
507      * @since Moodle 3.5
508      * @return external_description
509      */
510     public static function bulk_approve_data_requests_returns() {
511         return new external_single_structure([
512             'result' => new external_value(PARAM_BOOL, 'The processing result'),
513             'warnings' => new external_warnings()
514         ]);
515     }
517     /**
518      * Parameter description for deny_data_request().
519      *
520      * @since Moodle 3.5
521      * @return external_function_parameters
522      */
523     public static function deny_data_request_parameters() {
524         return new external_function_parameters([
525             'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
526         ]);
527     }
529     /**
530      * Deny a data request.
531      *
532      * @since Moodle 3.5
533      * @param int $requestid The request ID.
534      * @return array
535      * @throws coding_exception
536      * @throws dml_exception
537      * @throws invalid_parameter_exception
538      * @throws restricted_context_exception
539      * @throws moodle_exception
540      */
541     public static function deny_data_request($requestid) {
542         $warnings = [];
543         $params = external_api::validate_parameters(self::deny_data_request_parameters(), [
544             'requestid' => $requestid
545         ]);
546         $requestid = $params['requestid'];
548         // Validate context.
549         $context = context_system::instance();
550         self::validate_context($context);
551         require_capability('tool/dataprivacy:managedatarequests', $context);
553         // Ensure the request exists.
554         $requestexists = data_request::record_exists($requestid);
556         $result = false;
557         if ($requestexists) {
558             $result = api::deny_data_request($requestid);
560             // Add notification in the session to be shown when the page is reloaded on the JS side.
561             notification::success(get_string('requestdenied', 'tool_dataprivacy'));
562         } else {
563             $warnings[] = [
564                 'item' => $requestid,
565                 'warningcode' => 'errorrequestnotfound',
566                 'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
567             ];
568         }
570         return [
571             'result' => $result,
572             'warnings' => $warnings
573         ];
574     }
576     /**
577      * Parameter description for deny_data_request().
578      *
579      * @since Moodle 3.5
580      * @return external_description
581      */
582     public static function deny_data_request_returns() {
583         return new external_single_structure([
584             'result' => new external_value(PARAM_BOOL, 'The processing result'),
585             'warnings' => new external_warnings()
586         ]);
587     }
589     /**
590      * Parameter description for bulk_deny_data_requests().
591      *
592      * @since Moodle 3.5
593      * @return external_function_parameters
594      */
595     public static function bulk_deny_data_requests_parameters() {
596         return new external_function_parameters([
597             'requestids' => new external_multiple_structure(
598                 new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
599             )
600         ]);
601     }
603     /**
604      * Bulk deny data requests.
605      *
606      * @since Moodle 3.5
607      * @param array $requestids Array consisting of request ID's.
608      * @return array
609      * @throws coding_exception
610      * @throws dml_exception
611      * @throws invalid_parameter_exception
612      * @throws restricted_context_exception
613      * @throws moodle_exception
614      */
615     public static function bulk_deny_data_requests($requestids) {
616         $warnings = [];
617         $result = false;
618         $params = external_api::validate_parameters(self::bulk_deny_data_requests_parameters(), [
619             'requestids' => $requestids
620         ]);
621         $requestids = $params['requestids'];
623         // Validate context.
624         $context = context_system::instance();
625         self::validate_context($context);
626         require_capability('tool/dataprivacy:managedatarequests', $context);
628         foreach ($requestids as $requestid) {
629             // Ensure the request exists.
630             $requestexists = data_request::record_exists($requestid);
632             if ($requestexists) {
633                 api::deny_data_request($requestid);
634             } else {
635                 $warnings[] = [
636                     'item' => $requestid,
637                     'warningcode' => 'errorrequestnotfound',
638                     'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
639                 ];
640             }
641         }
643         if (empty($warnings)) {
644             $result = true;
645             // Add notification in the session to be shown when the page is reloaded on the JS side.
646             notification::success(get_string('requestsdenied', 'tool_dataprivacy'));
647         }
649         return [
650             'result' => $result,
651             'warnings' => $warnings
652         ];
653     }
655     /**
656      * Parameter description for bulk_deny_data_requests().
657      *
658      * @since Moodle 3.5
659      * @return external_description
660      */
661     public static function bulk_deny_data_requests_returns() {
662         return new external_single_structure([
663             'result' => new external_value(PARAM_BOOL, 'The processing result'),
664             'warnings' => new external_warnings()
665         ]);
666     }
668     /**
669      * Parameter description for get_data_request().
670      *
671      * @since Moodle 3.5
672      * @return external_function_parameters
673      */
674     public static function get_users_parameters() {
675         return new external_function_parameters([
676             'query' => new external_value(PARAM_TEXT, 'The search query', VALUE_REQUIRED)
677         ]);
678     }
680     /**
681      * Fetch the details of a user's data request.
682      *
683      * @since Moodle 3.5
684      * @param string $query The search request.
685      * @return array
686      * @throws required_capability_exception
687      * @throws dml_exception
688      * @throws invalid_parameter_exception
689      * @throws restricted_context_exception
690      */
691     public static function get_users($query) {
692         $params = external_api::validate_parameters(self::get_users_parameters(), [
693             'query' => $query
694         ]);
695         $query = $params['query'];
697         // Validate context.
698         $context = context_system::instance();
699         self::validate_context($context);
700         require_capability('tool/dataprivacy:managedatarequests', $context);
702         $allusernames = get_all_user_name_fields(true);
703         // Exclude admins and guest user.
704         $excludedusers = array_keys(get_admins()) + [guest_user()->id];
705         $sort = 'lastname ASC, firstname ASC';
706         $fields = 'id, email, ' . $allusernames;
707         $users = get_users(true, $query, true, $excludedusers, $sort, '', '', 0, 30, $fields);
708         $useroptions = [];
709         foreach ($users as $user) {
710             $useroptions[$user->id] = (object)[
711                 'id' => $user->id,
712                 'fullname' => fullname($user),
713                 'email' => $user->email
714             ];
715         }
717         return $useroptions;
718     }
720     /**
721      * Parameter description for get_users().
722      *
723      * @since Moodle 3.5
724      * @return external_description
725      * @throws coding_exception
726      */
727     public static function get_users_returns() {
728         return new external_multiple_structure(new external_single_structure(
729             [
730                 'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'),
731                 'fullname' => new external_value(core_user::get_property_type('firstname'), 'The fullname of the user'),
732                 'email' => new external_value(core_user::get_property_type('email'), 'The user\'s email address', VALUE_OPTIONAL),
733             ]
734         ));
735     }
737     /**
738      * Parameter description for create_purpose_form().
739      *
740      * @since Moodle 3.5
741      * @return external_function_parameters
742      */
743     public static function create_purpose_form_parameters() {
744         return new external_function_parameters([
745             'jsonformdata' => new external_value(PARAM_RAW, 'The data to create the purpose, encoded as a json array')
746         ]);
747     }
749     /**
750      * Creates a data purpose from form data.
751      *
752      * @since Moodle 3.5
753      * @param string $jsonformdata
754      * @return array
755      */
756     public static function create_purpose_form($jsonformdata) {
757         global $PAGE;
759         $warnings = [];
761         $params = external_api::validate_parameters(self::create_purpose_form_parameters(), [
762             'jsonformdata' => $jsonformdata
763         ]);
765         // Validate context and access to manage the registry.
766         self::validate_context(\context_system::instance());
767         api::check_can_manage_data_registry();
769         $serialiseddata = json_decode($params['jsonformdata']);
770         $data = array();
771         parse_str($serialiseddata, $data);
773         $purpose = new \tool_dataprivacy\purpose(0);
774         $mform = new \tool_dataprivacy\form\purpose(null, ['persistent' => $purpose], 'post', '', null, true, $data);
776         $validationerrors = true;
777         if ($validateddata = $mform->get_data()) {
778             $purpose = api::create_purpose($validateddata);
779             $validationerrors = false;
780         } else if ($errors = $mform->is_validated()) {
781             throw new moodle_exception('generalerror');
782         }
784         $exporter = new purpose_exporter($purpose, ['context' => \context_system::instance()]);
785         return [
786             'purpose' => $exporter->export($PAGE->get_renderer('core')),
787             'validationerrors' => $validationerrors,
788             'warnings' => $warnings
789         ];
790     }
792     /**
793      * Returns for create_purpose_form().
794      *
795      * @since Moodle 3.5
796      * @return external_single_structure
797      */
798     public static function create_purpose_form_returns() {
799         return new external_single_structure([
800             'purpose' => purpose_exporter::get_read_structure(),
801             'validationerrors' => new external_value(PARAM_BOOL, 'Were there validation errors', VALUE_REQUIRED),
802             'warnings' => new external_warnings()
803         ]);
804     }
806     /**
807      * Parameter description for delete_purpose().
808      *
809      * @since Moodle 3.5
810      * @return external_function_parameters
811      */
812     public static function delete_purpose_parameters() {
813         return new external_function_parameters([
814             'id' => new external_value(PARAM_INT, 'The purpose ID', VALUE_REQUIRED)
815         ]);
816     }
818     /**
819      * Deletes a data purpose.
820      *
821      * @since Moodle 3.5
822      * @param int $id The ID.
823      * @return array
824      * @throws invalid_persistent_exception
825      * @throws coding_exception
826      * @throws invalid_parameter_exception
827      */
828     public static function delete_purpose($id) {
829         global $USER;
831         $params = external_api::validate_parameters(self::delete_purpose_parameters(), [
832             'id' => $id
833         ]);
835         // Validate context and access to manage the registry.
836         self::validate_context(\context_system::instance());
837         api::check_can_manage_data_registry();
839         $result = api::delete_purpose($params['id']);
841         return [
842             'result' => $result,
843             'warnings' => []
844         ];
845     }
847     /**
848      * Parameter description for delete_purpose().
849      *
850      * @since Moodle 3.5
851      * @return external_single_structure
852      */
853     public static function delete_purpose_returns() {
854         return new external_single_structure([
855             'result' => new external_value(PARAM_BOOL, 'The processing result'),
856             'warnings' => new external_warnings()
857         ]);
858     }
860     /**
861      * Parameter description for create_category_form().
862      *
863      * @since Moodle 3.5
864      * @return external_function_parameters
865      */
866     public static function create_category_form_parameters() {
867         return new external_function_parameters([
868             'jsonformdata' => new external_value(PARAM_RAW, 'The data to create the category, encoded as a json array')
869         ]);
870     }
872     /**
873      * Creates a data category from form data.
874      *
875      * @since Moodle 3.5
876      * @param string $jsonformdata
877      * @return array
878      */
879     public static function create_category_form($jsonformdata) {
880         global $PAGE;
882         $warnings = [];
884         $params = external_api::validate_parameters(self::create_category_form_parameters(), [
885             'jsonformdata' => $jsonformdata
886         ]);
888         // Validate context and access to manage the registry.
889         self::validate_context(\context_system::instance());
890         api::check_can_manage_data_registry();
892         $serialiseddata = json_decode($params['jsonformdata']);
893         $data = array();
894         parse_str($serialiseddata, $data);
896         $category = new \tool_dataprivacy\category(0);
897         $mform = new \tool_dataprivacy\form\category(null, ['persistent' => $category], 'post', '', null, true, $data);
899         $validationerrors = true;
900         if ($validateddata = $mform->get_data()) {
901             $category = api::create_category($validateddata);
902             $validationerrors = false;
903         } else if ($errors = $mform->is_validated()) {
904             throw new moodle_exception('generalerror');
905         }
907         $exporter = new category_exporter($category, ['context' => \context_system::instance()]);
908         return [
909             'category' => $exporter->export($PAGE->get_renderer('core')),
910             'validationerrors' => $validationerrors,
911             'warnings' => $warnings
912         ];
913     }
915     /**
916      * Returns for create_category_form().
917      *
918      * @since Moodle 3.5
919      * @return external_single_structure
920      */
921     public static function create_category_form_returns() {
922         return new external_single_structure([
923             'category' => category_exporter::get_read_structure(),
924             'validationerrors' => new external_value(PARAM_BOOL, 'Were there validation errors', VALUE_REQUIRED),
925             'warnings' => new external_warnings()
926         ]);
927     }
929     /**
930      * Parameter description for delete_category().
931      *
932      * @since Moodle 3.5
933      * @return external_function_parameters
934      */
935     public static function delete_category_parameters() {
936         return new external_function_parameters([
937             'id' => new external_value(PARAM_INT, 'The category ID', VALUE_REQUIRED)
938         ]);
939     }
941     /**
942      * Deletes a data category.
943      *
944      * @since Moodle 3.5
945      * @param int $id The ID.
946      * @return array
947      * @throws invalid_persistent_exception
948      * @throws coding_exception
949      * @throws invalid_parameter_exception
950      */
951     public static function delete_category($id) {
952         global $USER;
954         $params = external_api::validate_parameters(self::delete_category_parameters(), [
955             'id' => $id
956         ]);
958         // Validate context and access to manage the registry.
959         self::validate_context(\context_system::instance());
960         api::check_can_manage_data_registry();
962         $result = api::delete_category($params['id']);
964         return [
965             'result' => $result,
966             'warnings' => []
967         ];
968     }
970     /**
971      * Parameter description for delete_category().
972      *
973      * @since Moodle 3.5
974      * @return external_single_structure
975      */
976     public static function delete_category_returns() {
977         return new external_single_structure([
978             'result' => new external_value(PARAM_BOOL, 'The processing result'),
979             'warnings' => new external_warnings()
980         ]);
981     }
983     /**
984      * Parameter description for set_contextlevel_form().
985      *
986      * @since Moodle 3.5
987      * @return external_function_parameters
988      */
989     public static function set_contextlevel_form_parameters() {
990         return new external_function_parameters([
991             'jsonformdata' => new external_value(PARAM_RAW, 'The context level data, encoded as a json array')
992         ]);
993     }
995     /**
996      * Creates a data category from form data.
997      *
998      * @since Moodle 3.5
999      * @param string $jsonformdata
1000      * @return array
1001      */
1002     public static function set_contextlevel_form($jsonformdata) {
1003         global $PAGE;
1005         $warnings = [];
1007         $params = external_api::validate_parameters(self::set_contextlevel_form_parameters(), [
1008             'jsonformdata' => $jsonformdata
1009         ]);
1011         // Validate context and access to manage the registry.
1012         self::validate_context(\context_system::instance());
1013         api::check_can_manage_data_registry();
1015         $serialiseddata = json_decode($params['jsonformdata']);
1016         $data = array();
1017         parse_str($serialiseddata, $data);
1019         $contextlevel = $data['contextlevel'];
1021         $customdata = \tool_dataprivacy\form\contextlevel::get_contextlevel_customdata($contextlevel);
1022         $mform = new \tool_dataprivacy\form\contextlevel(null, $customdata, 'post', '', null, true, $data);
1023         if ($validateddata = $mform->get_data()) {
1024             $contextlevel = api::set_contextlevel($validateddata);
1025         } else if ($errors = $mform->is_validated()) {
1026             $warnings[] = json_encode($errors);
1027         }
1029         if ($contextlevel) {
1030             $result = true;
1031         } else {
1032             $result = false;
1033         }
1034         return [
1035             'result' => $result,
1036             'warnings' => $warnings
1037         ];
1038     }
1040     /**
1041      * Returns for set_contextlevel_form().
1042      *
1043      * @since Moodle 3.5
1044      * @return external_single_structure
1045      */
1046     public static function set_contextlevel_form_returns() {
1047         return new external_single_structure([
1048             'result' => new external_value(PARAM_BOOL, 'Whether the data was properly set or not'),
1049             'warnings' => new external_warnings()
1050         ]);
1051     }
1053     /**
1054      * Parameter description for set_context_form().
1055      *
1056      * @since Moodle 3.5
1057      * @return external_function_parameters
1058      */
1059     public static function set_context_form_parameters() {
1060         return new external_function_parameters([
1061             'jsonformdata' => new external_value(PARAM_RAW, 'The context level data, encoded as a json array')
1062         ]);
1063     }
1065     /**
1066      * Creates a data category from form data.
1067      *
1068      * @since Moodle 3.5
1069      * @param string $jsonformdata
1070      * @return array
1071      */
1072     public static function set_context_form($jsonformdata) {
1073         global $PAGE;
1075         $warnings = [];
1077         $params = external_api::validate_parameters(self::set_context_form_parameters(), [
1078             'jsonformdata' => $jsonformdata
1079         ]);
1081         // Validate context and access to manage the registry.
1082         self::validate_context(\context_system::instance());
1083         api::check_can_manage_data_registry();
1085         $serialiseddata = json_decode($params['jsonformdata']);
1086         $data = array();
1087         parse_str($serialiseddata, $data);
1089         $context = context_helper::instance_by_id($data['contextid']);
1090         $customdata = \tool_dataprivacy\form\context_instance::get_context_instance_customdata($context);
1091         $mform = new \tool_dataprivacy\form\context_instance(null, $customdata, 'post', '', null, true, $data);
1092         if ($validateddata = $mform->get_data()) {
1093             api::check_can_manage_data_registry($validateddata->contextid);
1094             $context = api::set_context_instance($validateddata);
1095         } else if ($errors = $mform->is_validated()) {
1096             $warnings[] = json_encode($errors);
1097             throw new moodle_exception('generalerror');
1098         }
1100         if ($context) {
1101             $result = true;
1102         } else {
1103             $result = false;
1104         }
1105         return [
1106             'result' => $result,
1107             'warnings' => $warnings
1108         ];
1109     }
1111     /**
1112      * Returns for set_context_form().
1113      *
1114      * @since Moodle 3.5
1115      * @return external_single_structure
1116      */
1117     public static function set_context_form_returns() {
1118         return new external_single_structure([
1119             'result' => new external_value(PARAM_BOOL, 'Whether the data was properly set or not'),
1120             'warnings' => new external_warnings()
1121         ]);
1122     }
1124     /**
1125      * Parameter description for tree_extra_branches().
1126      *
1127      * @since Moodle 3.5
1128      * @return external_function_parameters
1129      */
1130     public static function tree_extra_branches_parameters() {
1131         return new external_function_parameters([
1132             'contextid' => new external_value(PARAM_INT, 'The context id to expand'),
1133             'element' => new external_value(PARAM_ALPHA, 'The element we are interested on')
1134         ]);
1135     }
1137     /**
1138      * Returns tree extra branches.
1139      *
1140      * @since Moodle 3.5
1141      * @param int $contextid
1142      * @param string $element
1143      * @return array
1144      */
1145     public static function tree_extra_branches($contextid, $element) {
1147         $params = external_api::validate_parameters(self::tree_extra_branches_parameters(), [
1148             'contextid' => $contextid,
1149             'element' => $element,
1150         ]);
1152         $context = context_helper::instance_by_id($params['contextid']);
1154         self::validate_context($context);
1155         api::check_can_manage_data_registry($context->id);
1157         switch ($params['element']) {
1158             case 'course':
1159                 $branches = data_registry_page::get_courses_branch($context);
1160                 break;
1161             case 'module':
1162                 $branches = data_registry_page::get_modules_branch($context);
1163                 break;
1164             case 'block':
1165                 $branches = data_registry_page::get_blocks_branch($context);
1166                 break;
1167             default:
1168                 throw new \moodle_exception('Unsupported element provided.');
1169         }
1171         return [
1172             'branches' => $branches,
1173             'warnings' => [],
1174         ];
1175     }
1177     /**
1178      * Returns for tree_extra_branches().
1179      *
1180      * @since Moodle 3.5
1181      * @return external_single_structure
1182      */
1183     public static function tree_extra_branches_returns() {
1184         return new external_single_structure([
1185             'branches' => new external_multiple_structure(self::get_tree_node_structure(true)),
1186             'warnings' => new external_warnings()
1187         ]);
1188     }
1190     /**
1191      * Parameters for confirm_contexts_for_deletion().
1192      *
1193      * @since Moodle 3.5
1194      * @return external_function_parameters
1195      */
1196     public static function confirm_contexts_for_deletion_parameters() {
1197         return new external_function_parameters([
1198             'ids' => new external_multiple_structure(
1199                 new external_value(PARAM_INT, 'Expired context record ID', VALUE_REQUIRED),
1200                 'Array of expired context record IDs', VALUE_DEFAULT, []
1201             ),
1202         ]);
1203     }
1205     /**
1206      * Confirm a given array of expired context record IDs
1207      *
1208      * @since Moodle 3.5
1209      * @param int[] $ids Array of record IDs from the expired contexts table.
1210      * @return array
1211      * @throws coding_exception
1212      * @throws dml_exception
1213      * @throws invalid_parameter_exception
1214      * @throws restricted_context_exception
1215      */
1216     public static function confirm_contexts_for_deletion($ids) {
1217         $warnings = [];
1218         $params = external_api::validate_parameters(self::confirm_contexts_for_deletion_parameters(), [
1219             'ids' => $ids
1220         ]);
1221         $ids = $params['ids'];
1223         // Validate context and access to manage the registry.
1224         self::validate_context(\context_system::instance());
1225         api::check_can_manage_data_registry();
1227         $result = true;
1228         if (!empty($ids)) {
1229             $expiredcontextstoapprove = [];
1230             // Loop through the deletion of expired contexts and their children if necessary.
1231             foreach ($ids as $id) {
1232                 $expiredcontext = new expired_context($id);
1233                 $targetcontext = context_helper::instance_by_id($expiredcontext->get('contextid'));
1235                 if (!$targetcontext instanceof \context_user) {
1236                     // Fetch this context's child contexts. Make sure that all of the child contexts are flagged for deletion.
1237                     // User context children do not need to be considered.
1238                     $childcontexts = $targetcontext->get_child_contexts();
1239                     foreach ($childcontexts as $child) {
1240                         if ($expiredchildcontext = expired_context::get_record(['contextid' => $child->id])) {
1241                             // Add this child context to the list for approval.
1242                             $expiredcontextstoapprove[] = $expiredchildcontext;
1243                         } else {
1244                             // This context has not yet been flagged for deletion.
1245                             $result = false;
1246                             $message = get_string('errorcontexthasunexpiredchildren', 'tool_dataprivacy',
1247                                 $targetcontext->get_context_name(false));
1248                             $warnings[] = [
1249                                 'item' => 'tool_dataprivacy_ctxexpired',
1250                                 'warningcode' => 'errorcontexthasunexpiredchildren',
1251                                 'message' => $message
1252                             ];
1253                             // Exit the process.
1254                             break 2;
1255                         }
1256                     }
1257                 }
1259                 $expiredcontextstoapprove[] = $expiredcontext;
1260             }
1262             // Proceed with the approval if everything's in order.
1263             if ($result) {
1264                 // Mark expired contexts as approved for deletion.
1265                 foreach ($expiredcontextstoapprove as $expired) {
1266                     // Only mark expired contexts that are pending approval.
1267                     if ($expired->get('status') == expired_context::STATUS_EXPIRED) {
1268                         api::set_expired_context_status($expired, expired_context::STATUS_APPROVED);
1269                     }
1270                 }
1271             }
1273         } else {
1274             // We don't have anything to process.
1275             $result = false;
1276             $warnings[] = [
1277                 'item' => 'tool_dataprivacy_ctxexpired',
1278                 'warningcode' => 'errornoexpiredcontexts',
1279                 'message' => get_string('errornoexpiredcontexts', 'tool_dataprivacy')
1280             ];
1281         }
1283         return [
1284             'result' => $result,
1285             'warnings' => $warnings
1286         ];
1287     }
1289     /**
1290      * Returns for confirm_contexts_for_deletion().
1291      *
1292      * @since Moodle 3.5
1293      * @return external_single_structure
1294      */
1295     public static function confirm_contexts_for_deletion_returns() {
1296         return new external_single_structure([
1297             'result' => new external_value(PARAM_BOOL, 'Whether the record was properly marked for deletion or not'),
1298             'warnings' => new external_warnings()
1299         ]);
1300     }
1302     /**
1303      * Parameters for set_context_defaults().
1304      *
1305      * @return external_function_parameters
1306      */
1307     public static function set_context_defaults_parameters() {
1308         return new external_function_parameters([
1309             'contextlevel' => new external_value(PARAM_INT, 'The context level', VALUE_REQUIRED),
1310             'category' => new external_value(PARAM_INT, 'The default category for the given context level', VALUE_REQUIRED),
1311             'purpose' => new external_value(PARAM_INT, 'The default purpose for the given context level', VALUE_REQUIRED),
1312             'activity' => new external_value(PARAM_PLUGIN, 'The plugin name of the activity', VALUE_DEFAULT, null),
1313             'override' => new external_value(PARAM_BOOL, 'Whether to override existing instances with the defaults', VALUE_DEFAULT,
1314                 false),
1315         ]);
1316     }
1318     /**
1319      * Updates the default category and purpose for a given context level (and optionally, a plugin).
1320      *
1321      * @param int $contextlevel The context level.
1322      * @param int $category The ID matching the category.
1323      * @param int $purpose The ID matching the purpose record.
1324      * @param int $activity The name of the activity that we're making a defaults configuration for.
1325      * @param bool $override Whether to override the purpose/categories of existing instances to these defaults.
1326      * @return array
1327      */
1328     public static function set_context_defaults($contextlevel, $category, $purpose, $activity, $override) {
1329         $warnings = [];
1331         $params = external_api::validate_parameters(self::set_context_defaults_parameters(), [
1332             'contextlevel' => $contextlevel,
1333             'category' => $category,
1334             'purpose' => $purpose,
1335             'activity' => $activity,
1336             'override' => $override,
1337         ]);
1338         $contextlevel = $params['contextlevel'];
1339         $category = $params['category'];
1340         $purpose = $params['purpose'];
1341         $activity = $params['activity'];
1342         $override = $params['override'];
1344         // Validate context.
1345         $context = context_system::instance();
1346         self::validate_context($context);
1347         api::check_can_manage_data_registry();
1349         // Set the context defaults.
1350         $result = api::set_context_defaults($contextlevel, $category, $purpose, $activity, $override);
1352         return [
1353             'result' => $result,
1354             'warnings' => $warnings
1355         ];
1356     }
1358     /**
1359      * Returns for set_context_defaults().
1360      *
1361      * @return external_single_structure
1362      */
1363     public static function set_context_defaults_returns() {
1364         return new external_single_structure([
1365             'result' => new external_value(PARAM_BOOL, 'Whether the context defaults were successfully set or not'),
1366             'warnings' => new external_warnings()
1367         ]);
1368     }
1370     /**
1371      * Parameters for get_category_options().
1372      *
1373      * @return external_function_parameters
1374      */
1375     public static function get_category_options_parameters() {
1376         return new external_function_parameters([
1377             'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
1378             'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
1379         ]);
1380     }
1382     /**
1383      * Fetches a list of data category options containing category IDs as keys and the category name for the value.
1384      *
1385      * @param bool $includeinherit Whether to include the "Inherit" option.
1386      * @param bool $includenotset Whether to include the "Not set" option.
1387      * @return array
1388      */
1389     public static function get_category_options($includeinherit, $includenotset) {
1390         $warnings = [];
1392         $params = self::validate_parameters(self::get_category_options_parameters(), [
1393             'includeinherit' => $includeinherit,
1394             'includenotset' => $includenotset
1395         ]);
1396         $includeinherit = $params['includeinherit'];
1397         $includenotset = $params['includenotset'];
1399         $context = context_system::instance();
1400         self::validate_context($context);
1401         api::check_can_manage_data_registry();
1403         $categories = api::get_categories();
1404         $options = data_registry_page::category_options($categories, $includenotset, $includeinherit);
1405         $categoryoptions = [];
1406         foreach ($options as $id => $name) {
1407             $categoryoptions[] = [
1408                 'id' => $id,
1409                 'name' => $name,
1410             ];
1411         }
1413         return [
1414             'options' => $categoryoptions,
1415             'warnings' => $warnings
1416         ];
1417     }
1419     /**
1420      * Returns for get_category_options().
1421      *
1422      * @return external_single_structure
1423      */
1424     public static function get_category_options_returns() {
1425         $optiondefinition = new external_single_structure(
1426             [
1427                 'id' => new external_value(PARAM_INT, 'The category ID'),
1428                 'name' => new external_value(PARAM_TEXT, 'The category name'),
1429             ]
1430         );
1432         return new external_single_structure([
1433             'options' => new external_multiple_structure($optiondefinition),
1434             'warnings' => new external_warnings()
1435         ]);
1436     }
1438     /**
1439      * Parameters for get_purpose_options().
1440      *
1441      * @return external_function_parameters
1442      */
1443     public static function get_purpose_options_parameters() {
1444         return new external_function_parameters([
1445             'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
1446             'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
1447         ]);
1448     }
1450     /**
1451      * Fetches a list of data storage purposes containing purpose IDs as keys and the purpose name for the value.
1452      *
1453      * @param bool $includeinherit Whether to include the "Inherit" option.
1454      * @param bool $includenotset Whether to include the "Not set" option.
1455      * @return array
1456      */
1457     public static function get_purpose_options($includeinherit, $includenotset) {
1458         $warnings = [];
1460         $params = self::validate_parameters(self::get_category_options_parameters(), [
1461             'includeinherit' => $includeinherit,
1462             'includenotset' => $includenotset
1463         ]);
1464         $includeinherit = $params['includeinherit'];
1465         $includenotset = $params['includenotset'];
1467         $context = context_system::instance();
1468         self::validate_context($context);
1470         $purposes = api::get_purposes();
1471         $options = data_registry_page::purpose_options($purposes, $includenotset, $includeinherit);
1472         $purposeoptions = [];
1473         foreach ($options as $id => $name) {
1474             $purposeoptions[] = [
1475                 'id' => $id,
1476                 'name' => $name,
1477             ];
1478         }
1480         return [
1481             'options' => $purposeoptions,
1482             'warnings' => $warnings
1483         ];
1484     }
1486     /**
1487      * Returns for get_purpose_options().
1488      *
1489      * @return external_single_structure
1490      */
1491     public static function get_purpose_options_returns() {
1492         $optiondefinition = new external_single_structure(
1493             [
1494                 'id' => new external_value(PARAM_INT, 'The purpose ID'),
1495                 'name' => new external_value(PARAM_TEXT, 'The purpose name'),
1496             ]
1497         );
1499         return new external_single_structure([
1500             'options' => new external_multiple_structure($optiondefinition),
1501             'warnings' => new external_warnings()
1502         ]);
1503     }
1505     /**
1506      * Parameters for get_activity_options().
1507      *
1508      * @return external_function_parameters
1509      */
1510     public static function get_activity_options_parameters() {
1511         return new external_function_parameters([
1512             'nodefaults' => new external_value(PARAM_BOOL, 'Whether to fetch all activities or only those without defaults',
1513                 VALUE_DEFAULT, false),
1514         ]);
1515     }
1517     /**
1518      * Fetches a list of activity options for setting data registry defaults.
1519      *
1520      * @param boolean $nodefaults If false, it will fetch all of the activities. Otherwise, it will only fetch the activities
1521      *                            that don't have defaults yet (e.g. when adding a new activity module defaults).
1522      * @return array
1523      */
1524     public static function get_activity_options($nodefaults) {
1525         $warnings = [];
1527         $params = self::validate_parameters(self::get_activity_options_parameters(), [
1528             'nodefaults' => $nodefaults,
1529         ]);
1530         $nodefaults = $params['nodefaults'];
1532         $context = context_system::instance();
1533         self::validate_context($context);
1535         // Get activity module plugin info.
1536         $pluginmanager = \core_plugin_manager::instance();
1537         $modplugins = $pluginmanager->get_enabled_plugins('mod');
1538         $modoptions = [];
1540         // Get the module-level defaults. data_registry::get_defaults falls back to this when there are no activity defaults.
1541         list($levelpurpose, $levelcategory) = data_registry::get_defaults(CONTEXT_MODULE);
1542         foreach ($modplugins as $name) {
1543             // Check if we have default purpose and category for this module if we want don't want to fetch everything.
1544             if ($nodefaults) {
1545                 list($purpose, $category) = data_registry::get_defaults(CONTEXT_MODULE, $name);
1546                 // Compare this with the module-level defaults.
1547                 if ($purpose !== $levelpurpose || $category !== $levelcategory) {
1548                     // If the defaults for this activity has been already set, there's no need to add this in the list of options.
1549                     continue;
1550                 }
1551             }
1553             $displayname = $pluginmanager->plugin_name('mod_' . $name);
1554             $modoptions[] = (object)[
1555                 'name' => $name,
1556                 'displayname' => $displayname
1557             ];
1558         }
1560         return [
1561             'options' => $modoptions,
1562             'warnings' => $warnings
1563         ];
1564     }
1566     /**
1567      * Returns for get_category_options().
1568      *
1569      * @return external_single_structure
1570      */
1571     public static function get_activity_options_returns() {
1572         $optionsdefinition = new external_single_structure(
1573             [
1574                 'name' => new external_value(PARAM_TEXT, 'The plugin name of the activity'),
1575                 'displayname' => new external_value(PARAM_TEXT, 'The display name of the activity'),
1576             ]
1577         );
1579         return new external_single_structure([
1580             'options' => new external_multiple_structure($optionsdefinition),
1581             'warnings' => new external_warnings()
1582         ]);
1583     }
1585     /**
1586      * Gets the structure of a tree node (link + child branches).
1587      *
1588      * @since Moodle 3.5
1589      * @param bool $allowchildbranches
1590      * @return array
1591      */
1592     private static function get_tree_node_structure($allowchildbranches = true) {
1593         $fields = [
1594             'text' => new external_value(PARAM_TEXT, 'The node text', VALUE_REQUIRED),
1595             'expandcontextid' => new external_value(PARAM_INT, 'The contextid this node expands', VALUE_REQUIRED),
1596             'expandelement' => new external_value(PARAM_ALPHA, 'What element is this node expanded to', VALUE_REQUIRED),
1597             'contextid' => new external_value(PARAM_INT, 'The node contextid', VALUE_REQUIRED),
1598             'contextlevel' => new external_value(PARAM_INT, 'The node contextlevel', VALUE_REQUIRED),
1599             'expanded' => new external_value(PARAM_INT, 'Is it expanded', VALUE_REQUIRED),
1600         ];
1602         if ($allowchildbranches) {
1603             // Passing false as we will not have more than 1 sub-level.
1604             $fields['branches'] = new external_multiple_structure(
1605                 self::get_tree_node_structure(false),
1606                 'Children node structure',
1607                 VALUE_OPTIONAL
1608             );
1609         } else {
1610             // We will only have 1 sub-level and we don't want an infinite get_tree_node_structure, this is a hacky
1611             // way to prevent this infinite loop when calling get_tree_node_structure recursively.
1612             $fields['branches'] = new external_multiple_structure(
1613                 new external_value(
1614                     PARAM_TEXT,
1615                     'Nothing really, it will always be an empty array',
1616                     VALUE_OPTIONAL
1617                 )
1618             );
1619         }
1621         return new external_single_structure($fields, 'Node structure', VALUE_OPTIONAL);
1622     }