MDL-61899 tool_dataprivacy: Use default parameters for get_role_users()
[moodle.git] / admin / tool / dataprivacy / classes / api.php
CommitLineData
5efc1f9e
DM
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/**
18 * Class containing helper methods for processing data requests.
19 *
20 * @package tool_dataprivacy
21 * @copyright 2018 Jun Pataleta
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24namespace tool_dataprivacy;
25
26use coding_exception;
27use context_system;
28use core\invalid_persistent_exception;
29use core\message\message;
30use core\task\manager;
cb775057
JP
31use core_privacy\local\request\approved_contextlist;
32use core_privacy\local\request\contextlist_collection;
5efc1f9e
DM
33use core_user;
34use dml_exception;
35use moodle_exception;
36use moodle_url;
37use required_capability_exception;
38use stdClass;
ba5b59c0 39use tool_dataprivacy\external\data_request_exporter;
5efc1f9e
DM
40use tool_dataprivacy\task\initiate_data_request_task;
41use tool_dataprivacy\task\process_data_request_task;
5efc1f9e
DM
42
43defined('MOODLE_INTERNAL') || die();
44
45/**
46 * Class containing helper methods for processing data requests.
47 *
48 * @copyright 2018 Jun Pataleta
49 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50 */
51class api {
52
53 /** Data export request type. */
54 const DATAREQUEST_TYPE_EXPORT = 1;
55
56 /** Data deletion request type. */
57 const DATAREQUEST_TYPE_DELETE = 2;
58
59 /** Other request type. Usually of enquiries to the DPO. */
60 const DATAREQUEST_TYPE_OTHERS = 3;
61
62 /** Newly submitted and we haven't yet started finding out where they have data. */
63 const DATAREQUEST_STATUS_PENDING = 0;
64
65 /** Newly submitted and we have started to find the location of data. */
66 const DATAREQUEST_STATUS_PREPROCESSING = 1;
67
68 /** Metadata ready and awaiting review and approval by the Data Protection officer. */
69 const DATAREQUEST_STATUS_AWAITING_APPROVAL = 2;
70
71 /** Request approved and will be processed soon. */
72 const DATAREQUEST_STATUS_APPROVED = 3;
73
74 /** The request is now being processed. */
75 const DATAREQUEST_STATUS_PROCESSING = 4;
76
77 /** Data request completed. */
78 const DATAREQUEST_STATUS_COMPLETE = 5;
79
80 /** Data request cancelled by the user. */
81 const DATAREQUEST_STATUS_CANCELLED = 6;
82
83 /** Data request rejected by the DPO. */
84 const DATAREQUEST_STATUS_REJECTED = 7;
85
86 /**
87 * Determines whether the user can contact the site's Data Protection Officer via Moodle.
88 *
89 * @return boolean True when tool_dataprivacy|contactdataprotectionofficer is enabled.
90 * @throws dml_exception
91 */
92 public static function can_contact_dpo() {
93 return get_config('tool_dataprivacy', 'contactdataprotectionofficer') == 1;
94 }
95
96 /**
97 * Check's whether the current user has the capability to manage data requests.
98 *
99 * @param int $userid The user ID.
100 * @return bool
101 * @throws coding_exception
102 * @throws dml_exception
103 */
104 public static function can_manage_data_requests($userid) {
105 $context = context_system::instance();
106
107 // A user can manage data requests if he/she has the site DPO role and has the capability to manage data requests.
108 return self::is_site_dpo($userid) && has_capability('tool/dataprivacy:managedatarequests', $context, $userid);
109 }
110
111 /**
112 * Checks if the current user can manage the data registry at the provided id.
113 *
114 * @param int $contextid Fallback to system context id.
115 * @throws \required_capability_exception
116 * @return null
117 */
118 public static function check_can_manage_data_registry($contextid = false) {
119 if ($contextid) {
120 $context = \context_helper::instance_by_id($contextid);
121 } else {
122 $context = \context_system::instance();
123 }
124
125 require_capability('tool/dataprivacy:managedataregistry', $context);
126 }
127
128 /**
129 * Fetches the list of users with the Data Protection Officer role.
130 *
131 * @throws dml_exception
132 */
133 public static function get_site_dpos() {
134 // Get role(s) that can manage data requests.
135 $dporoles = explode(',', get_config('tool_dataprivacy', 'dporoles'));
136
137 $dpos = [];
138 $context = context_system::instance();
139 foreach ($dporoles as $roleid) {
140 if (empty($roleid)) {
141 continue;
142 }
143 // Fetch users that can manage data requests.
32924133 144 $dpos += get_role_users($roleid, $context);
5efc1f9e
DM
145 }
146
147 // If the site has no data protection officer, defer to site admin(s).
148 if (empty($dpos)) {
149 $dpos = get_admins();
150 }
151 return $dpos;
152 }
153
154 /**
155 * Checks whether a given user is a site DPO.
156 *
157 * @param int $userid The user ID.
158 * @return bool
159 * @throws dml_exception
160 */
161 public static function is_site_dpo($userid) {
162 $dpos = self::get_site_dpos();
163 return array_key_exists($userid, $dpos);
164 }
165
166 /**
167 * Lodges a data request and sends the request details to the site Data Protection Officer(s).
168 *
169 * @param int $foruser The user whom the request is being made for.
170 * @param int $type The request type.
171 * @param string $comments Request comments.
172 * @return data_request
173 * @throws invalid_persistent_exception
174 * @throws coding_exception
175 */
176 public static function create_data_request($foruser, $type, $comments = '') {
177 global $USER;
178
179 $datarequest = new data_request();
180 // The user the request is being made for.
181 $datarequest->set('userid', $foruser);
182 // The user making the request.
183 $datarequest->set('requestedby', $USER->id);
184 // Set status.
185 $datarequest->set('status', self::DATAREQUEST_STATUS_PENDING);
186 // Set request type.
187 $datarequest->set('type', $type);
188 // Set request comments.
189 $datarequest->set('comments', $comments);
190
191 // Store subject access request.
192 $datarequest->create();
193
194 // Fire an ad hoc task to initiate the data request process.
195 $task = new initiate_data_request_task();
196 $task->set_custom_data(['requestid' => $datarequest->get('id')]);
197 manager::queue_adhoc_task($task, true);
198
199 return $datarequest;
200 }
201
202 /**
203 * Fetches the list of the data requests.
204 *
205 * If user ID is provided, it fetches the data requests for the user.
206 * Otherwise, it fetches all of the data requests, provided that the user has the capability to manage data requests.
207 * (e.g. Users with the Data Protection Officer roles)
208 *
209 * @param int $userid The User ID.
210 * @return data_request[]
211 * @throws dml_exception
212 */
213 public static function get_data_requests($userid = 0) {
214 global $USER;
215 $results = [];
216 if ($userid) {
217 // Get the data requests for the user or data requests made by the user.
218 $select = "userid = :userid OR requestedby = :requestedby";
219 $params = [
220 'userid' => $userid,
221 'requestedby' => $userid
222 ];
223 $results = data_request::get_records_select($select, $params, 'status DESC, timemodified DESC');
224 } else {
225 // If the current user is one of the site's Data Protection Officers, then fetch all data requests.
226 if (self::is_site_dpo($USER->id)) {
227 $results = data_request::get_records(null, 'status DESC, timemodified DESC', '');
228 }
229 }
230
231 return $results;
232 }
233
234 /**
235 * Checks whether there is already an existing pending/in-progress data request for a user for a given request type.
236 *
237 * @param int $userid The user ID.
238 * @param int $type The request type.
239 * @return bool
240 * @throws coding_exception
241 * @throws dml_exception
242 */
243 public static function has_ongoing_request($userid, $type) {
244 global $DB;
245
246 // Check if the user already has an incomplete data request of the same type.
247 $nonpendingstatuses = [
248 self::DATAREQUEST_STATUS_COMPLETE,
249 self::DATAREQUEST_STATUS_CANCELLED,
250 self::DATAREQUEST_STATUS_REJECTED,
251 ];
252 list($insql, $inparams) = $DB->get_in_or_equal($nonpendingstatuses, SQL_PARAMS_NAMED);
253 $select = 'type = :type AND userid = :userid AND status NOT ' . $insql;
254 $params = array_merge([
255 'type' => $type,
256 'userid' => $userid
257 ], $inparams);
258
259 return data_request::record_exists_select($select, $params);
260 }
261
262 /**
263 * Determines whether a request is active or not based on its status.
264 *
265 * @param int $status The request status.
266 * @return bool
267 */
268 public static function is_active($status) {
269 // List of statuses which doesn't require any further processing.
270 $finalstatuses = [
271 self::DATAREQUEST_STATUS_COMPLETE,
272 self::DATAREQUEST_STATUS_CANCELLED,
273 self::DATAREQUEST_STATUS_REJECTED,
274 ];
275
276 return !in_array($status, $finalstatuses);
277 }
278
279 /**
280 * Cancels the data request for a given request ID.
281 *
282 * @param int $requestid The request identifier.
283 * @param int $status The request status.
284 * @param int $dpoid The user ID of the Data Protection Officer
285 * @return bool
286 * @throws invalid_persistent_exception
287 * @throws coding_exception
288 */
289 public static function update_request_status($requestid, $status, $dpoid = 0) {
290 // Update the request.
291 $datarequest = new data_request($requestid);
292 $datarequest->set('status', $status);
293 if ($dpoid) {
294 $datarequest->set('dpo', $dpoid);
295 }
296 return $datarequest->update();
297 }
298
299 /**
300 * Fetches a request based on the request ID.
301 *
302 * @param int $requestid The request identifier
303 * @return data_request
304 */
305 public static function get_request($requestid) {
306 return new data_request($requestid);
307 }
308
309 /**
310 * Approves a data request based on the request ID.
311 *
312 * @param int $requestid The request identifier
313 * @return bool
314 * @throws coding_exception
315 * @throws dml_exception
316 * @throws invalid_persistent_exception
317 * @throws required_capability_exception
318 * @throws moodle_exception
319 */
320 public static function approve_data_request($requestid) {
321 global $USER;
322
323 // Check first whether the user can manage data requests.
324 if (!self::can_manage_data_requests($USER->id)) {
325 $context = context_system::instance();
326 throw new required_capability_exception($context, 'tool/dataprivacy:managedatarequests', 'nopermissions', '');
327 }
328
329 // Check if request is already awaiting for approval.
330 $request = new data_request($requestid);
331 if ($request->get('status') != self::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
332 throw new moodle_exception('errorrequestnotwaitingforapproval', 'tool_dataprivacy');
333 }
334
335 // Update the status and the DPO.
336 $result = self::update_request_status($requestid, self::DATAREQUEST_STATUS_APPROVED, $USER->id);
337
cb775057
JP
338 // Approve all the contexts attached to the request.
339 // Currently, approving the request implicitly approves all associated contexts, but this may change in future, allowing
340 // users to selectively approve certain contexts only.
341 self::update_request_contexts_with_status($requestid, contextlist_context::STATUS_APPROVED);
342
5efc1f9e
DM
343 // Fire an ad hoc task to initiate the data request process.
344 $task = new process_data_request_task();
345 $task->set_custom_data(['requestid' => $requestid]);
346 if ($request->get('type') == self::DATAREQUEST_TYPE_EXPORT) {
347 $task->set_userid($request->get('userid'));
348 }
349 manager::queue_adhoc_task($task, true);
350
351 return $result;
352 }
353
354 /**
355 * Rejects a data request based on the request ID.
356 *
357 * @param int $requestid The request identifier
358 * @return bool
359 * @throws coding_exception
360 * @throws dml_exception
361 * @throws invalid_persistent_exception
362 * @throws required_capability_exception
363 * @throws moodle_exception
364 */
365 public static function deny_data_request($requestid) {
366 global $USER;
367
368 if (!self::can_manage_data_requests($USER->id)) {
369 $context = context_system::instance();
370 throw new required_capability_exception($context, 'tool/dataprivacy:managedatarequests', 'nopermissions', '');
371 }
372
373 // Check if request is already awaiting for approval.
374 $request = new data_request($requestid);
375 if ($request->get('status') != self::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
376 throw new moodle_exception('errorrequestnotwaitingforapproval', 'tool_dataprivacy');
377 }
378
379 // Update the status and the DPO.
380 return self::update_request_status($requestid, self::DATAREQUEST_STATUS_REJECTED, $USER->id);
381 }
382
383 /**
384 * Sends a message to the site's Data Protection Officer about a request.
385 *
386 * @param stdClass $dpo The DPO user record
387 * @param data_request $request The data request
388 * @return int|false
389 * @throws coding_exception
390 * @throws dml_exception
391 * @throws moodle_exception
392 */
393 public static function notify_dpo($dpo, data_request $request) {
394 global $PAGE, $SITE;
395
ba5b59c0
JP
396 $output = $PAGE->get_renderer('tool_dataprivacy');
397
398 $usercontext = \context_user::instance($request->get('requestedby'));
399 $requestexporter = new data_request_exporter($request, ['context' => $usercontext]);
400 $requestdata = $requestexporter->export($output);
401
5efc1f9e
DM
402 // Create message to send to the Data Protection Officer(s).
403 $typetext = null;
ba5b59c0 404 $typetext = $requestdata->typename;
5efc1f9e
DM
405 $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typetext);
406
ba5b59c0 407 $requestedby = $requestdata->requestedbyuser;
5efc1f9e
DM
408 $datarequestsurl = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
409 $message = new message();
410 $message->courseid = $SITE->id;
411 $message->component = 'tool_dataprivacy';
412 $message->name = 'contactdataprotectionofficer';
413 $message->userfrom = $requestedby;
414 $message->replyto = $requestedby->email;
ba5b59c0 415 $message->replytoname = $requestedby->fullname;
5efc1f9e
DM
416 $message->subject = $subject;
417 $message->fullmessageformat = FORMAT_HTML;
418 $message->notification = 1;
419 $message->contexturl = $datarequestsurl;
420 $message->contexturlname = get_string('datarequests', 'tool_dataprivacy');
421
422 // Prepare the context data for the email message body.
423 $messagetextdata = [
ba5b59c0 424 'requestedby' => $requestedby->fullname,
5efc1f9e 425 'requesttype' => $typetext,
ba5b59c0
JP
426 'requestdate' => userdate($requestdata->timecreated),
427 'requestcomments' => $requestdata->messagehtml,
5efc1f9e
DM
428 'datarequestsurl' => $datarequestsurl
429 ];
ba5b59c0 430 $requestingfor = $requestdata->foruser;
5efc1f9e
DM
431 if ($requestedby->id == $requestingfor->id) {
432 $messagetextdata['requestfor'] = $messagetextdata['requestedby'];
433 } else {
ba5b59c0 434 $messagetextdata['requestfor'] = $requestingfor->fullname;
5efc1f9e
DM
435 }
436
5efc1f9e
DM
437 // Email the data request to the Data Protection Officer(s)/Admin(s).
438 $messagetextdata['dponame'] = fullname($dpo);
439 // Render message email body.
440 $messagehtml = $output->render_from_template('tool_dataprivacy/data_request_email', $messagetextdata);
441 $message->userto = $dpo;
442 $message->fullmessage = html_to_text($messagehtml);
443 $message->fullmessagehtml = $messagehtml;
444
445 // Send message.
446 return message_send($message);
447 }
448
449 /**
450 * Creates a new data purpose.
451 *
452 * @param stdClass $record
453 * @return \tool_dataprivacy\purpose.
454 */
455 public static function create_purpose(stdClass $record) {
456 self::check_can_manage_data_registry();
457
458 $purpose = new purpose(0, $record);
459 $purpose->create();
460
461 return $purpose;
462 }
463
464 /**
465 * Updates an existing data purpose.
466 *
467 * @param stdClass $record
468 * @return \tool_dataprivacy\purpose.
469 */
470 public static function update_purpose(stdClass $record) {
471 self::check_can_manage_data_registry();
472
473 $purpose = new purpose($record->id);
474 $purpose->from_record($record);
475
476 $result = $purpose->update();
477
478 return $purpose;
479 }
480
481 /**
482 * Deletes a data purpose.
483 *
484 * @param int $id
485 * @return bool
486 */
487 public static function delete_purpose($id) {
488 self::check_can_manage_data_registry();
489
490 $purpose = new purpose($id);
491 if ($purpose->is_used()) {
492 throw new \moodle_exception('Purpose with id ' . $id . ' can not be deleted because it is used.');
493 }
494 return $purpose->delete();
495 }
496
497 /**
498 * Get all system data purposes.
499 *
500 * @return \tool_dataprivacy\purpose[]
501 */
502 public static function get_purposes() {
503 self::check_can_manage_data_registry();
504
505 return purpose::get_records([], 'name', 'ASC');
506 }
507
508 /**
509 * Creates a new data category.
510 *
511 * @param stdClass $record
512 * @return \tool_dataprivacy\category.
513 */
514 public static function create_category(stdClass $record) {
515 self::check_can_manage_data_registry();
516
517 $category = new category(0, $record);
518 $category->create();
519
520 return $category;
521 }
522
523 /**
524 * Updates an existing data category.
525 *
526 * @param stdClass $record
527 * @return \tool_dataprivacy\category.
528 */
529 public static function update_category(stdClass $record) {
530 self::check_can_manage_data_registry();
531
532 $category = new category($record->id);
533 $category->from_record($record);
534
535 $result = $category->update();
536
537 return $category;
538 }
539
540 /**
541 * Deletes a data category.
542 *
543 * @param int $id
544 * @return bool
545 */
546 public static function delete_category($id) {
547 self::check_can_manage_data_registry();
548
549 $category = new category($id);
550 if ($category->is_used()) {
551 throw new \moodle_exception('Category with id ' . $id . ' can not be deleted because it is used.');
552 }
553 return $category->delete();
554 }
555
556 /**
557 * Get all system data categories.
558 *
559 * @return \tool_dataprivacy\category[]
560 */
561 public static function get_categories() {
562 self::check_can_manage_data_registry();
563
564 return category::get_records([], 'name', 'ASC');
565 }
566
567 /**
568 * Sets the context instance purpose and category.
569 *
570 * @param \stdClass $record
571 * @return \tool_dataprivacy\context_instance
572 */
573 public static function set_context_instance($record) {
574 self::check_can_manage_data_registry($record->contextid);
575
576 if ($instance = context_instance::get_record_by_contextid($record->contextid, false)) {
577 // Update.
578 $instance->from_record($record);
579
580 if (empty($record->purposeid) && empty($record->categoryid)) {
581 // We accept one of them to be null but we delete it if both are null.
582 self::unset_context_instance($instance);
583 return;
584 }
585
586 } else {
587 // Add.
588 $instance = new context_instance(0, $record);
589 }
590 $instance->save();
591
592 return $instance;
593 }
594
595 /**
596 * Unsets the context instance record.
597 *
598 * @param \tool_dataprivacy\context_instance $instance
599 * @return null
600 */
601 public static function unset_context_instance(context_instance $instance) {
602 self::check_can_manage_data_registry($instance->get('contextid'));
603 $instance->delete();
604 }
605
606 /**
607 * Sets the context level purpose and category.
608 *
609 * @throws \coding_exception
610 * @param \stdClass $record
611 * @return contextlevel
612 */
613 public static function set_contextlevel($record) {
614 global $DB;
615
616 // Only manager at system level can set this.
617 self::check_can_manage_data_registry();
618
619 if ($record->contextlevel != CONTEXT_SYSTEM && $record->contextlevel != CONTEXT_USER) {
620 throw new \coding_exception('Only context system and context user can set a contextlevel ' .
621 'purpose and retention');
622 }
623
624 if ($contextlevel = contextlevel::get_record_by_contextlevel($record->contextlevel, false)) {
625 // Update.
626 $contextlevel->from_record($record);
627 } else {
628 // Add.
629 $contextlevel = new contextlevel(0, $record);
630 }
631 $contextlevel->save();
632
633 // We sync with their defaults as we removed these options from the defaults page.
634 $classname = \context_helper::get_class_for_level($record->contextlevel);
635 list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
636 set_config($purposevar, $record->purposeid, 'tool_dataprivacy');
637 set_config($categoryvar, $record->categoryid, 'tool_dataprivacy');
638
639 return $contextlevel;
640 }
641
642 /**
643 * Returns the effective category given a context instance.
644 *
645 * @param \context $context
646 * @param int $forcedvalue Use this categoryid value as if this was this context instance category.
647 * @return category|false
648 */
649 public static function get_effective_context_category(\context $context, $forcedvalue=false) {
650 self::check_can_manage_data_registry($context->id);
651 if (!data_registry::defaults_set()) {
652 return false;
653 }
654
655 return data_registry::get_effective_context_value($context, 'category', $forcedvalue);
656 }
657
658 /**
659 * Returns the effective purpose given a context instance.
660 *
661 * @param \context $context
662 * @param int $forcedvalue Use this purposeid value as if this was this context instance purpose.
663 * @return purpose|false
664 */
665 public static function get_effective_context_purpose(\context $context, $forcedvalue=false) {
666 self::check_can_manage_data_registry($context->id);
667 if (!data_registry::defaults_set()) {
668 return false;
669 }
670
671 return data_registry::get_effective_context_value($context, 'purpose', $forcedvalue);
672 }
673
674 /**
675 * Returns the effective category given a context level.
676 *
677 * @param int $contextlevel
678 * @param int $forcedvalue Use this categoryid value as if this was this context level category.
679 * @return category|false
680 */
681 public static function get_effective_contextlevel_category($contextlevel, $forcedvalue=false) {
682 self::check_can_manage_data_registry(\context_system::instance()->id);
683 if (!data_registry::defaults_set()) {
684 return false;
685 }
686
687 return data_registry::get_effective_contextlevel_value($contextlevel, 'category', $forcedvalue);
688 }
689
690 /**
691 * Returns the effective purpose given a context level.
692 *
693 * @param int $contextlevel
694 * @param int $forcedvalue Use this purposeid value as if this was this context level purpose.
695 * @return purpose|false
696 */
697 public static function get_effective_contextlevel_purpose($contextlevel, $forcedvalue=false) {
698 self::check_can_manage_data_registry(\context_system::instance()->id);
699 if (!data_registry::defaults_set()) {
700 return false;
701 }
702
703 return data_registry::get_effective_contextlevel_value($contextlevel, 'purpose', $forcedvalue);
704 }
705
706 /**
707 * Creates an expired context record for the provided context id.
708 *
709 * @param int $contextid
710 * @return \tool_dataprivacy\expired_context
711 */
712 public static function create_expired_context($contextid) {
713 self::check_can_manage_data_registry();
714
715 $record = (object)[
716 'contextid' => $contextid,
717 'status' => expired_context::STATUS_EXPIRED,
718 ];
719 $expiredctx = new expired_context(0, $record);
720 $expiredctx->save();
721
722 return $expiredctx;
723 }
724
725 /**
726 * Deletes an expired context record.
727 *
728 * @param int $id The tool_dataprivacy_ctxexpire id.
729 * @return bool True on success.
730 */
731 public static function delete_expired_context($id) {
732 self::check_can_manage_data_registry();
733
734 $expiredcontext = new expired_context($id);
735 return $expiredcontext->delete();
736 }
737
738 /**
739 * Updates the status of an expired context.
740 *
741 * @param \tool_dataprivacy\expired_context $expiredctx
742 * @param int $status
743 * @return null
744 */
745 public static function set_expired_context_status(expired_context $expiredctx, $status) {
746 self::check_can_manage_data_registry();
747
748 $expiredctx->set('status', $status);
749 $expiredctx->save();
750 }
cb775057
JP
751
752 /**
753 * Adds the contexts from the contextlist_collection to the request with the status provided.
754 *
755 * @param contextlist_collection $clcollection a collection of contextlists for all components.
756 * @param int $requestid the id of the request.
757 * @param int $status the status to set the contexts to.
758 */
759 public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
760 foreach ($clcollection as $contextlist) {
761 // Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it.
762 $clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist);
763 $clp->create();
764 $contextlistid = $clp->get('id');
765
766 // Store the associated contexts in the contextlist.
767 foreach ($contextlist->get_contextids() as $contextid) {
768 $context = new contextlist_context();
769 $context->set('contextid', $contextid)
770 ->set('contextlistid', $contextlistid)
771 ->set('status', $status)
772 ->create();
773 }
774
775 // Create the relation to the request.
776 $requestcontextlist = request_contextlist::create_relation($requestid, $contextlistid);
777 $requestcontextlist->create();
778 }
779 }
780
781 /**
782 * Sets the status of all contexts associated with the request.
783 *
784 * @param int $requestid the requestid to which the contexts belong.
785 * @param int $status the status to set to.
786 * @throws \dml_exception if the requestid is invalid.
787 * @throws \moodle_exception if the status is invalid.
788 */
789 public static function update_request_contexts_with_status(int $requestid, int $status) {
790 // Validate contextlist_context status using the persistent's attribute validation.
791 $contextlistcontext = new contextlist_context();
792 $contextlistcontext->set('status', $status);
793 if (array_key_exists('status', $contextlistcontext->get_errors())) {
794 throw new moodle_exception("Invalid contextlist_context status: $status");
795 }
796
797 // Validate requestid using the persistent's record validation.
798 // A dml_exception is thrown if the record is missing.
799 $datarequest = new data_request($requestid);
800
801 // Bulk update the status of the request contexts.
802 global $DB;
803
804 $select = "SELECT ctx.id as id
805 FROM {" . request_contextlist::TABLE . "} rcl
806 JOIN {" . contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id
807 JOIN {" . contextlist_context::TABLE . "} ctx ON cl.id = ctx.contextlistid
808 WHERE rcl.requestid = ?";
809
6f094d49
JP
810 // Fetch records IDs to be updated and update by chunks, if applicable (limit of 1000 records per update).
811 $limit = 1000;
812 $idstoupdate = $DB->get_fieldset_sql($select, [$requestid]);
813 $count = count($idstoupdate);
814 $idchunks = $idstoupdate;
815 if ($count > $limit) {
816 $idchunks = array_chunk($idstoupdate, $limit);
817 }
818 $transaction = $DB->start_delegated_transaction();
819 $initialparams = [$status];
820 foreach ($idchunks as $chunk) {
821 list($insql, $inparams) = $DB->get_in_or_equal($chunk);
822 $update = "UPDATE {" . contextlist_context::TABLE . "}
823 SET status = ?
824 WHERE id $insql";
825 $params = array_merge($initialparams, $inparams);
826 $DB->execute($update, $params);
827 }
828 $transaction->allow_commit();
cb775057
JP
829 }
830
831 /**
832 * Finds all request contextlists having at least on approved context, and returns them as in a contextlist_collection.
833 *
834 * @param data_request $request the data request with which the contextlists are associated.
835 * @return contextlist_collection the collection of approved_contextlist objects.
836 */
837 public static function get_approved_contextlist_collection_for_request(data_request $request) : contextlist_collection {
838 $foruser = core_user::get_user($request->get('userid'));
839
840 // Fetch all approved contextlists and create the core_privacy\local\request\contextlist objects here.
841 global $DB;
842 $sql = "SELECT cl.component, ctx.contextid
843 FROM {" . request_contextlist::TABLE . "} rcl
844 JOIN {" . contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id
845 JOIN {" . contextlist_context::TABLE . "} ctx ON cl.id = ctx.contextlistid
846 WHERE rcl.requestid = ?
847 AND ctx.status = ?
848 ORDER BY cl.component, ctx.contextid";
849
850 // Create the approved contextlist collection object.
851 $lastcomponent = null;
852 $approvedcollection = new contextlist_collection($foruser->id);
853
854 $rs = $DB->get_recordset_sql($sql, [$request->get('id'), contextlist_context::STATUS_APPROVED]);
855 foreach ($rs as $record) {
856 // If we encounter a new component, and we've built up contexts for the last, then add the approved_contextlist for the
857 // last (the one we've just finished with) and reset the context array for the next one.
858 if ($lastcomponent != $record->component) {
859 if (!empty($contexts)) {
860 $approvedcollection->add_contextlist(new approved_contextlist($foruser, $lastcomponent, $contexts));
861 }
862 $contexts = [];
863 }
864 $contexts[] = $record->contextid;
865 $lastcomponent = $record->component;
866 }
867 $rs->close();
868
869 // The data for the last component contextlist won't have been written yet, so write it now.
870 if (!empty($contexts)) {
871 $approvedcollection->add_contextlist(new approved_contextlist($foruser, $lastcomponent, $contexts));
872 }
873
874 return $approvedcollection;
875 }
5efc1f9e 876}