mirrored from git://git.moodle.org/moodle.git
/
api.php
1098 lines (959 loc) · 41 KB
/
api.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class containing helper methods for processing data requests.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use coding_exception;
use context_course;
use context_system;
use core\invalid_persistent_exception;
use core\message\message;
use core\task\manager;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\contextlist_collection;
use core_user;
use dml_exception;
use moodle_exception;
use moodle_url;
use required_capability_exception;
use stdClass;
use tool_dataprivacy\external\data_request_exporter;
use tool_dataprivacy\local\helper;
use tool_dataprivacy\task\initiate_data_request_task;
use tool_dataprivacy\task\process_data_request_task;
defined('MOODLE_INTERNAL') || die();
/**
* Class containing helper methods for processing data requests.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api {
/** Data export request type. */
const DATAREQUEST_TYPE_EXPORT = 1;
/** Data deletion request type. */
const DATAREQUEST_TYPE_DELETE = 2;
/** Other request type. Usually of enquiries to the DPO. */
const DATAREQUEST_TYPE_OTHERS = 3;
/** Newly submitted and we haven't yet started finding out where they have data. */
const DATAREQUEST_STATUS_PENDING = 0;
/** Newly submitted and we have started to find the location of data. */
const DATAREQUEST_STATUS_PREPROCESSING = 1;
/** Metadata ready and awaiting review and approval by the Data Protection officer. */
const DATAREQUEST_STATUS_AWAITING_APPROVAL = 2;
/** Request approved and will be processed soon. */
const DATAREQUEST_STATUS_APPROVED = 3;
/** The request is now being processed. */
const DATAREQUEST_STATUS_PROCESSING = 4;
/** Data request completed. */
const DATAREQUEST_STATUS_COMPLETE = 5;
/** Data request cancelled by the user. */
const DATAREQUEST_STATUS_CANCELLED = 6;
/** Data request rejected by the DPO. */
const DATAREQUEST_STATUS_REJECTED = 7;
/**
* Determines whether the user can contact the site's Data Protection Officer via Moodle.
*
* @return boolean True when tool_dataprivacy|contactdataprotectionofficer is enabled.
* @throws dml_exception
*/
public static function can_contact_dpo() {
return get_config('tool_dataprivacy', 'contactdataprotectionofficer') == 1;
}
/**
* Check's whether the current user has the capability to manage data requests.
*
* @param int $userid The user ID.
* @return bool
* @throws coding_exception
* @throws dml_exception
*/
public static function can_manage_data_requests($userid) {
$context = context_system::instance();
// A user can manage data requests if he/she has the site DPO role and has the capability to manage data requests.
return self::is_site_dpo($userid) && has_capability('tool/dataprivacy:managedatarequests', $context, $userid);
}
/**
* Checks if the current user can manage the data registry at the provided id.
*
* @param int $contextid Fallback to system context id.
* @throws \required_capability_exception
* @return null
*/
public static function check_can_manage_data_registry($contextid = false) {
if ($contextid) {
$context = \context_helper::instance_by_id($contextid);
} else {
$context = \context_system::instance();
}
require_capability('tool/dataprivacy:managedataregistry', $context);
}
/**
* Fetches the list of users with the Data Protection Officer role.
*
* @throws dml_exception
*/
public static function get_site_dpos() {
// Get role(s) that can manage data requests.
$dporoles = explode(',', get_config('tool_dataprivacy', 'dporoles'));
$dpos = [];
$context = context_system::instance();
foreach ($dporoles as $roleid) {
if (empty($roleid)) {
continue;
}
$allnames = get_all_user_name_fields(true, 'u');
$fields = 'u.id, u.confirmed, u.username, '. $allnames . ', ' .
'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '.
'u.country, u.picture, u.idnumber, u.department, u.institution, '.
'u.lang, u.timezone, u.lastaccess, u.mnethostid, u.auth, u.suspended, u.deleted, ' .
'r.name AS rolename, r.sortorder, '.
'r.shortname AS roleshortname, rn.name AS rolecoursealias';
// Fetch users that can manage data requests.
$dpos += get_role_users($roleid, $context, false, $fields);
}
// If the site has no data protection officer, defer to site admin(s).
if (empty($dpos)) {
$dpos = get_admins();
}
return $dpos;
}
/**
* Checks whether a given user is a site DPO.
*
* @param int $userid The user ID.
* @return bool
* @throws dml_exception
*/
public static function is_site_dpo($userid) {
$dpos = self::get_site_dpos();
return array_key_exists($userid, $dpos);
}
/**
* Lodges a data request and sends the request details to the site Data Protection Officer(s).
*
* @param int $foruser The user whom the request is being made for.
* @param int $type The request type.
* @param string $comments Request comments.
* @return data_request
* @throws invalid_persistent_exception
* @throws coding_exception
*/
public static function create_data_request($foruser, $type, $comments = '') {
global $USER;
$datarequest = new data_request();
// The user the request is being made for.
$datarequest->set('userid', $foruser);
$requestinguser = $USER->id;
// Check when the user is making a request on behalf of another.
if ($requestinguser != $foruser) {
if (self::is_site_dpo($requestinguser)) {
// The user making the request is a DPO. Should be fine.
$datarequest->set('dpo', $requestinguser);
} else {
// If not a DPO, only users with the capability to make data requests for the user should be allowed.
// (e.g. users with the Parent role, etc).
if (!self::can_create_data_request_for_user($foruser)) {
$forusercontext = \context_user::instance($foruser);
throw new required_capability_exception($forusercontext,
'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
}
}
}
// The user making the request.
$datarequest->set('requestedby', $requestinguser);
// Set status.
$datarequest->set('status', self::DATAREQUEST_STATUS_PENDING);
// Set request type.
$datarequest->set('type', $type);
// Set request comments.
$datarequest->set('comments', $comments);
// Store subject access request.
$datarequest->create();
// Fire an ad hoc task to initiate the data request process.
$task = new initiate_data_request_task();
$task->set_custom_data(['requestid' => $datarequest->get('id')]);
manager::queue_adhoc_task($task, true);
return $datarequest;
}
/**
* Fetches the list of the data requests.
*
* If user ID is provided, it fetches the data requests for the user.
* Otherwise, it fetches all of the data requests, provided that the user has the capability to manage data requests.
* (e.g. Users with the Data Protection Officer roles)
*
* @param int $userid The User ID.
* @param int[] $statuses The status filters.
* @param int[] $types The request type filters.
* @param string $sort The order by clause.
* @param int $offset Amount of records to skip.
* @param int $limit Amount of records to fetch.
* @return data_request[]
* @throws coding_exception
* @throws dml_exception
*/
public static function get_data_requests($userid = 0, $statuses = [], $types = [], $sort = '', $offset = 0, $limit = 0) {
global $DB, $USER;
$results = [];
$sqlparams = [];
$sqlconditions = [];
// Set default sort.
if (empty($sort)) {
$sort = 'status ASC, timemodified ASC';
}
// Set status filters.
if (!empty($statuses)) {
list($statusinsql, $sqlparams) = $DB->get_in_or_equal($statuses, SQL_PARAMS_NAMED);
$sqlconditions[] = "status $statusinsql";
}
// Set request type filter.
if (!empty($types)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($types, SQL_PARAMS_NAMED);
$sqlconditions[] = "type $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
$params = [
'userid' => $userid,
'requestedby' => $userid
];
// Build a list of user IDs that the user is allowed to make data requests for.
// Of course, the user should be included in this list.
$alloweduserids = [$userid];
// Get any users that the user can make data requests for.
if ($children = helper::get_children_of_user($userid)) {
// Get the list of user IDs of the children and merge to the allowed user IDs.
$alloweduserids = array_merge($alloweduserids, array_keys($children));
}
list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
$sqlconditions[] .= "userid $insql";
$select = implode(' AND ', $sqlconditions);
$params = array_merge($params, $inparams, $sqlparams);
$results = data_request::get_records_select($select, $params, $sort, '*', $offset, $limit);
} else {
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
if (self::is_site_dpo($USER->id)) {
if (!empty($sqlconditions)) {
$select = implode(' AND ', $sqlconditions);
$results = data_request::get_records_select($select, $sqlparams, $sort, '*', $offset, $limit);
} else {
$results = data_request::get_records(null, $sort, '', $offset, $limit);
}
}
}
return $results;
}
/**
* Fetches the count of data request records based on the given parameters.
*
* @param int $userid The User ID.
* @param int[] $statuses The status filters.
* @param int[] $types The request type filters.
* @return int
* @throws coding_exception
* @throws dml_exception
*/
public static function get_data_requests_count($userid = 0, $statuses = [], $types = []) {
global $DB, $USER;
$count = 0;
$sqlparams = [];
$sqlconditions = [];
if (!empty($statuses)) {
list($statusinsql, $sqlparams) = $DB->get_in_or_equal($statuses, SQL_PARAMS_NAMED);
$sqlconditions[] = "status $statusinsql";
}
if (!empty($types)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($types, SQL_PARAMS_NAMED);
$sqlconditions[] = "type $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
$params = [
'userid' => $userid,
'requestedby' => $userid
];
// Build a list of user IDs that the user is allowed to make data requests for.
// Of course, the user should be included in this list.
$alloweduserids = [$userid];
// Get any users that the user can make data requests for.
if ($children = helper::get_children_of_user($userid)) {
// Get the list of user IDs of the children and merge to the allowed user IDs.
$alloweduserids = array_merge($alloweduserids, array_keys($children));
}
list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
$sqlconditions[] .= "userid $insql";
$select = implode(' AND ', $sqlconditions);
$params = array_merge($params, $inparams, $sqlparams);
$count = data_request::count_records_select($select, $params);
} else {
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
if (self::is_site_dpo($USER->id)) {
if (!empty($sqlconditions)) {
$select = implode(' AND ', $sqlconditions);
$count = data_request::count_records_select($select, $sqlparams);
} else {
$count = data_request::count_records();
}
}
}
return $count;
}
/**
* Checks whether there is already an existing pending/in-progress data request for a user for a given request type.
*
* @param int $userid The user ID.
* @param int $type The request type.
* @return bool
* @throws coding_exception
* @throws dml_exception
*/
public static function has_ongoing_request($userid, $type) {
global $DB;
// Check if the user already has an incomplete data request of the same type.
$nonpendingstatuses = [
self::DATAREQUEST_STATUS_COMPLETE,
self::DATAREQUEST_STATUS_CANCELLED,
self::DATAREQUEST_STATUS_REJECTED,
];
list($insql, $inparams) = $DB->get_in_or_equal($nonpendingstatuses, SQL_PARAMS_NAMED);
$select = 'type = :type AND userid = :userid AND status NOT ' . $insql;
$params = array_merge([
'type' => $type,
'userid' => $userid
], $inparams);
return data_request::record_exists_select($select, $params);
}
/**
* Determines whether a request is active or not based on its status.
*
* @param int $status The request status.
* @return bool
*/
public static function is_active($status) {
// List of statuses which doesn't require any further processing.
$finalstatuses = [
self::DATAREQUEST_STATUS_COMPLETE,
self::DATAREQUEST_STATUS_CANCELLED,
self::DATAREQUEST_STATUS_REJECTED,
];
return !in_array($status, $finalstatuses);
}
/**
* Cancels the data request for a given request ID.
*
* @param int $requestid The request identifier.
* @param int $status The request status.
* @param int $dpoid The user ID of the Data Protection Officer
* @param string $comment The comment about the status update.
* @return bool
* @throws invalid_persistent_exception
* @throws coding_exception
*/
public static function update_request_status($requestid, $status, $dpoid = 0, $comment = '') {
// Update the request.
$datarequest = new data_request($requestid);
$datarequest->set('status', $status);
if ($dpoid) {
$datarequest->set('dpo', $dpoid);
}
// Update the comment if necessary.
if (!empty(trim($comment))) {
$params = [
'date' => userdate(time()),
'comment' => $comment
];
$commenttosave = get_string('datecomment', 'tool_dataprivacy', $params);
// Check if there's an existing DPO comment.
$currentcomment = trim($datarequest->get('dpocomment'));
if ($currentcomment) {
// Append the new comment to the current comment and give them 1 line space in between.
$commenttosave = $currentcomment . PHP_EOL . PHP_EOL . $commenttosave;
}
$datarequest->set('dpocomment', $commenttosave);
}
return $datarequest->update();
}
/**
* Fetches a request based on the request ID.
*
* @param int $requestid The request identifier
* @return data_request
*/
public static function get_request($requestid) {
return new data_request($requestid);
}
/**
* Approves a data request based on the request ID.
*
* @param int $requestid The request identifier
* @return bool
* @throws coding_exception
* @throws dml_exception
* @throws invalid_persistent_exception
* @throws required_capability_exception
* @throws moodle_exception
*/
public static function approve_data_request($requestid) {
global $USER;
// Check first whether the user can manage data requests.
if (!self::can_manage_data_requests($USER->id)) {
$context = context_system::instance();
throw new required_capability_exception($context, 'tool/dataprivacy:managedatarequests', 'nopermissions', '');
}
// Check if request is already awaiting for approval.
$request = new data_request($requestid);
if ($request->get('status') != self::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
throw new moodle_exception('errorrequestnotwaitingforapproval', 'tool_dataprivacy');
}
// Update the status and the DPO.
$result = self::update_request_status($requestid, self::DATAREQUEST_STATUS_APPROVED, $USER->id);
// Approve all the contexts attached to the request.
// Currently, approving the request implicitly approves all associated contexts, but this may change in future, allowing
// users to selectively approve certain contexts only.
self::update_request_contexts_with_status($requestid, contextlist_context::STATUS_APPROVED);
// Fire an ad hoc task to initiate the data request process.
$task = new process_data_request_task();
$task->set_custom_data(['requestid' => $requestid]);
if ($request->get('type') == self::DATAREQUEST_TYPE_EXPORT) {
$task->set_userid($request->get('userid'));
}
manager::queue_adhoc_task($task, true);
return $result;
}
/**
* Rejects a data request based on the request ID.
*
* @param int $requestid The request identifier
* @return bool
* @throws coding_exception
* @throws dml_exception
* @throws invalid_persistent_exception
* @throws required_capability_exception
* @throws moodle_exception
*/
public static function deny_data_request($requestid) {
global $USER;
if (!self::can_manage_data_requests($USER->id)) {
$context = context_system::instance();
throw new required_capability_exception($context, 'tool/dataprivacy:managedatarequests', 'nopermissions', '');
}
// Check if request is already awaiting for approval.
$request = new data_request($requestid);
if ($request->get('status') != self::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
throw new moodle_exception('errorrequestnotwaitingforapproval', 'tool_dataprivacy');
}
// Update the status and the DPO.
return self::update_request_status($requestid, self::DATAREQUEST_STATUS_REJECTED, $USER->id);
}
/**
* Sends a message to the site's Data Protection Officer about a request.
*
* @param stdClass $dpo The DPO user record
* @param data_request $request The data request
* @return int|false
* @throws coding_exception
* @throws moodle_exception
*/
public static function notify_dpo($dpo, data_request $request) {
global $PAGE, $SITE;
$output = $PAGE->get_renderer('tool_dataprivacy');
$usercontext = \context_user::instance($request->get('requestedby'));
$requestexporter = new data_request_exporter($request, ['context' => $usercontext]);
$requestdata = $requestexporter->export($output);
// Create message to send to the Data Protection Officer(s).
$typetext = null;
$typetext = $requestdata->typename;
$subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typetext);
$requestedby = $requestdata->requestedbyuser;
$datarequestsurl = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
$message = new message();
$message->courseid = $SITE->id;
$message->component = 'tool_dataprivacy';
$message->name = 'contactdataprotectionofficer';
$message->userfrom = $requestedby->id;
$message->replyto = $requestedby->email;
$message->replytoname = $requestedby->fullname;
$message->subject = $subject;
$message->fullmessageformat = FORMAT_HTML;
$message->notification = 1;
$message->contexturl = $datarequestsurl;
$message->contexturlname = get_string('datarequests', 'tool_dataprivacy');
// Prepare the context data for the email message body.
$messagetextdata = [
'requestedby' => $requestedby->fullname,
'requesttype' => $typetext,
'requestdate' => userdate($requestdata->timecreated),
'requestcomments' => $requestdata->messagehtml,
'datarequestsurl' => $datarequestsurl
];
$requestingfor = $requestdata->foruser;
if ($requestedby->id == $requestingfor->id) {
$messagetextdata['requestfor'] = $messagetextdata['requestedby'];
} else {
$messagetextdata['requestfor'] = $requestingfor->fullname;
}
// Email the data request to the Data Protection Officer(s)/Admin(s).
$messagetextdata['dponame'] = fullname($dpo);
// Render message email body.
$messagehtml = $output->render_from_template('tool_dataprivacy/data_request_email', $messagetextdata);
$message->userto = $dpo;
$message->fullmessage = html_to_text($messagehtml);
$message->fullmessagehtml = $messagehtml;
// Send message.
return message_send($message);
}
/**
* Checks whether a non-DPO user can make a data request for another user.
*
* @param int $user The user ID of the target user.
* @param int $requester The user ID of the user making the request.
* @return bool
* @throws coding_exception
*/
public static function can_create_data_request_for_user($user, $requester = null) {
$usercontext = \context_user::instance($user);
return has_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
}
/**
* Checks whether a user can download a data request.
*
* @param int $userid Target user id (subject of data request)
* @param int $requesterid Requester user id (person who requsted it)
* @param int|null $downloaderid Person who wants to download user id (default current)
* @return bool
* @throws coding_exception
*/
public static function can_download_data_request_for_user($userid, $requesterid, $downloaderid = null) {
global $USER;
if (!$downloaderid) {
$downloaderid = $USER->id;
}
$usercontext = \context_user::instance($userid);
// If it's your own and you have the right capability, you can download it.
if ($userid == $downloaderid && has_capability('tool/dataprivacy:downloadownrequest', $usercontext, $downloaderid)) {
return true;
}
// If you can download anyone's in that context, you can download it.
if (has_capability('tool/dataprivacy:downloadallrequests', $usercontext, $downloaderid)) {
return true;
}
// If you can have the 'child access' ability to request in that context, and you are the one
// who requested it, then you can download it.
if ($requesterid == $downloaderid && self::can_create_data_request_for_user($userid, $requesterid)) {
return true;
}
return false;
}
/**
* Gets an action menu link to download a data request.
*
* @param \context_user $usercontext User context (of user who the data is for)
* @param int $requestid Request id
* @return \action_menu_link_secondary Action menu link
* @throws coding_exception
*/
public static function get_download_link(\context_user $usercontext, $requestid) {
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id,
'tool_dataprivacy', 'export', $requestid, '/', 'export.zip', true);
$downloadtext = get_string('download', 'tool_dataprivacy');
return new \action_menu_link_secondary($downloadurl, null, $downloadtext);
}
/**
* Creates a new data purpose.
*
* @param stdClass $record
* @return \tool_dataprivacy\purpose.
*/
public static function create_purpose(stdClass $record) {
self::check_can_manage_data_registry();
$purpose = new purpose(0, $record);
$purpose->create();
return $purpose;
}
/**
* Updates an existing data purpose.
*
* @param stdClass $record
* @return \tool_dataprivacy\purpose.
*/
public static function update_purpose(stdClass $record) {
self::check_can_manage_data_registry();
if (!isset($record->sensitivedatareasons)) {
$record->sensitivedatareasons = '';
}
$purpose = new purpose($record->id);
$purpose->from_record($record);
$result = $purpose->update();
return $purpose;
}
/**
* Deletes a data purpose.
*
* @param int $id
* @return bool
*/
public static function delete_purpose($id) {
self::check_can_manage_data_registry();
$purpose = new purpose($id);
if ($purpose->is_used()) {
throw new \moodle_exception('Purpose with id ' . $id . ' can not be deleted because it is used.');
}
return $purpose->delete();
}
/**
* Get all system data purposes.
*
* @return \tool_dataprivacy\purpose[]
*/
public static function get_purposes() {
self::check_can_manage_data_registry();
return purpose::get_records([], 'name', 'ASC');
}
/**
* Creates a new data category.
*
* @param stdClass $record
* @return \tool_dataprivacy\category.
*/
public static function create_category(stdClass $record) {
self::check_can_manage_data_registry();
$category = new category(0, $record);
$category->create();
return $category;
}
/**
* Updates an existing data category.
*
* @param stdClass $record
* @return \tool_dataprivacy\category.
*/
public static function update_category(stdClass $record) {
self::check_can_manage_data_registry();
$category = new category($record->id);
$category->from_record($record);
$result = $category->update();
return $category;
}
/**
* Deletes a data category.
*
* @param int $id
* @return bool
*/
public static function delete_category($id) {
self::check_can_manage_data_registry();
$category = new category($id);
if ($category->is_used()) {
throw new \moodle_exception('Category with id ' . $id . ' can not be deleted because it is used.');
}
return $category->delete();
}
/**
* Get all system data categories.
*
* @return \tool_dataprivacy\category[]
*/
public static function get_categories() {
self::check_can_manage_data_registry();
return category::get_records([], 'name', 'ASC');
}
/**
* Sets the context instance purpose and category.
*
* @param \stdClass $record
* @return \tool_dataprivacy\context_instance
*/
public static function set_context_instance($record) {
self::check_can_manage_data_registry($record->contextid);
if ($instance = context_instance::get_record_by_contextid($record->contextid, false)) {
// Update.
$instance->from_record($record);
if (empty($record->purposeid) && empty($record->categoryid)) {
// We accept one of them to be null but we delete it if both are null.
self::unset_context_instance($instance);
return;
}
} else {
// Add.
$instance = new context_instance(0, $record);
}
$instance->save();
return $instance;
}
/**
* Unsets the context instance record.
*
* @param \tool_dataprivacy\context_instance $instance
* @return null
*/
public static function unset_context_instance(context_instance $instance) {
self::check_can_manage_data_registry($instance->get('contextid'));
$instance->delete();
}
/**
* Sets the context level purpose and category.
*
* @throws \coding_exception
* @param \stdClass $record
* @return contextlevel
*/
public static function set_contextlevel($record) {
global $DB;
// Only manager at system level can set this.
self::check_can_manage_data_registry();
if ($record->contextlevel != CONTEXT_SYSTEM && $record->contextlevel != CONTEXT_USER) {
throw new \coding_exception('Only context system and context user can set a contextlevel ' .
'purpose and retention');
}
if ($contextlevel = contextlevel::get_record_by_contextlevel($record->contextlevel, false)) {
// Update.
$contextlevel->from_record($record);
} else {
// Add.
$contextlevel = new contextlevel(0, $record);
}
$contextlevel->save();
// We sync with their defaults as we removed these options from the defaults page.
$classname = \context_helper::get_class_for_level($record->contextlevel);
list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
set_config($purposevar, $record->purposeid, 'tool_dataprivacy');
set_config($categoryvar, $record->categoryid, 'tool_dataprivacy');
return $contextlevel;
}
/**
* Returns the effective category given a context instance.
*
* @param \context $context
* @param int $forcedvalue Use this categoryid value as if this was this context instance category.
* @return category|false
*/
public static function get_effective_context_category(\context $context, $forcedvalue=false) {
self::check_can_manage_data_registry($context->id);
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_context_value($context, 'category', $forcedvalue);
}
/**
* Returns the effective purpose given a context instance.
*
* @param \context $context
* @param int $forcedvalue Use this purposeid value as if this was this context instance purpose.
* @return purpose|false
*/
public static function get_effective_context_purpose(\context $context, $forcedvalue=false) {
self::check_can_manage_data_registry($context->id);
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_context_value($context, 'purpose', $forcedvalue);
}
/**
* Returns the effective category given a context level.
*
* @param int $contextlevel
* @param int $forcedvalue Use this categoryid value as if this was this context level category.
* @return category|false
*/
public static function get_effective_contextlevel_category($contextlevel, $forcedvalue=false) {
self::check_can_manage_data_registry(\context_system::instance()->id);
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_contextlevel_value($contextlevel, 'category', $forcedvalue);
}
/**
* Returns the effective purpose given a context level.
*
* @param int $contextlevel
* @param int $forcedvalue Use this purposeid value as if this was this context level purpose.
* @return purpose|false
*/
public static function get_effective_contextlevel_purpose($contextlevel, $forcedvalue=false) {
self::check_can_manage_data_registry(\context_system::instance()->id);
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_contextlevel_value($contextlevel, 'purpose', $forcedvalue);
}
/**
* Creates an expired context record for the provided context id.
*
* @param int $contextid
* @return \tool_dataprivacy\expired_context
*/
public static function create_expired_context($contextid) {
self::check_can_manage_data_registry();
$record = (object)[
'contextid' => $contextid,
'status' => expired_context::STATUS_EXPIRED,
];
$expiredctx = new expired_context(0, $record);
$expiredctx->save();
return $expiredctx;
}
/**
* Deletes an expired context record.
*
* @param int $id The tool_dataprivacy_ctxexpire id.
* @return bool True on success.
*/
public static function delete_expired_context($id) {
self::check_can_manage_data_registry();
$expiredcontext = new expired_context($id);
return $expiredcontext->delete();
}
/**
* Updates the status of an expired context.
*
* @param \tool_dataprivacy\expired_context $expiredctx
* @param int $status
* @return null
*/
public static function set_expired_context_status(expired_context $expiredctx, $status) {
self::check_can_manage_data_registry();
$expiredctx->set('status', $status);
$expiredctx->save();
}
/**
* Adds the contexts from the contextlist_collection to the request with the status provided.
*
* @param contextlist_collection $clcollection a collection of contextlists for all components.
* @param int $requestid the id of the request.
* @param int $status the status to set the contexts to.
*/
public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
$request = new data_request($requestid);
foreach ($clcollection as $contextlist) {
// Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it.
$clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist);
$clp->create();
$contextlistid = $clp->get('id');
// Store the associated contexts in the contextlist.
foreach ($contextlist->get_contextids() as $contextid) {
if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
$context = \context::instance_by_id($contextid);
if (($purpose = static::get_effective_context_purpose($context)) && !empty($purpose->get('protected'))) {
continue;
}
}
$context = new contextlist_context();
$context->set('contextid', $contextid)
->set('contextlistid', $contextlistid)
->set('status', $status)
->create();
}
// Create the relation to the request.
$requestcontextlist = request_contextlist::create_relation($requestid, $contextlistid);
$requestcontextlist->create();
}
}