Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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