MDL-66752 tool_dataprivacy: Add automatic data request approval feature
[moodle.git] / admin / tool / dataprivacy / tests / api_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * API tests.
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  */
25 use core\invalid_persistent_exception;
26 use core\task\manager;
27 use tool_dataprivacy\context_instance;
28 use tool_dataprivacy\api;
29 use tool_dataprivacy\data_registry;
30 use tool_dataprivacy\expired_context;
31 use tool_dataprivacy\data_request;
32 use tool_dataprivacy\purpose;
33 use tool_dataprivacy\category;
34 use tool_dataprivacy\local\helper;
35 use tool_dataprivacy\task\process_data_request_task;
37 defined('MOODLE_INTERNAL') || die();
38 global $CFG;
40 /**
41  * API tests.
42  *
43  * @package    tool_dataprivacy
44  * @copyright  2018 Jun Pataleta
45  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46  */
47 class tool_dataprivacy_api_testcase extends advanced_testcase {
49     /**
50      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
51      * tested with the default context.
52      */
53     public function test_check_can_manage_data_registry_admin() {
54         $this->resetAfterTest();
56         $this->setAdminUser();
57         // Technically this actually returns void, but assertNull will suffice to avoid a pointless test.
58         $this->assertNull(api::check_can_manage_data_registry());
59     }
61     /**
62      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
63      * tested with the default context.
64      */
65     public function test_check_can_manage_data_registry_without_cap_default() {
66         $this->resetAfterTest();
68         $user = $this->getDataGenerator()->create_user();
69         $this->setUser($user);
71         $this->expectException(required_capability_exception::class);
72         api::check_can_manage_data_registry();
73     }
75     /**
76      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
77      * tested with the default context.
78      */
79     public function test_check_can_manage_data_registry_without_cap_system() {
80         $this->resetAfterTest();
82         $user = $this->getDataGenerator()->create_user();
83         $this->setUser($user);
85         $this->expectException(required_capability_exception::class);
86         api::check_can_manage_data_registry(\context_system::instance()->id);
87     }
89     /**
90      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
91      * tested with the default context.
92      */
93     public function test_check_can_manage_data_registry_without_cap_own_user() {
94         $this->resetAfterTest();
96         $user = $this->getDataGenerator()->create_user();
97         $this->setUser($user);
99         $this->expectException(required_capability_exception::class);
100         api::check_can_manage_data_registry(\context_user::instance($user->id)->id);
101     }
103     /**
104      * Test for api::update_request_status().
105      */
106     public function test_update_request_status() {
107         $this->resetAfterTest();
109         $generator = new testing_data_generator();
110         $s1 = $generator->create_user();
111         $this->setUser($s1);
113         // Create the sample data request.
114         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
116         $requestid = $datarequest->get('id');
118         // Update with a comment.
119         $comment = 'This is an example of a comment';
120         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, $comment);
121         $this->assertTrue($result);
122         $datarequest = new data_request($requestid);
123         $this->assertStringEndsWith($comment, $datarequest->get('dpocomment'));
125         // Update with a comment which will be trimmed.
126         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, '  ');
127         $this->assertTrue($result);
128         $datarequest = new data_request($requestid);
129         $this->assertStringEndsWith($comment, $datarequest->get('dpocomment'));
131         // Update with a comment.
132         $secondcomment = '  - More comments -  ';
133         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, $secondcomment);
134         $this->assertTrue($result);
135         $datarequest = new data_request($requestid);
136         $this->assertRegExp("/.*{$comment}.*{$secondcomment}/s", $datarequest->get('dpocomment'));
138         // Update with a valid status.
139         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_DOWNLOAD_READY);
140         $this->assertTrue($result);
142         // Fetch the request record again.
143         $datarequest = new data_request($requestid);
144         $this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $datarequest->get('status'));
146         // Update with an invalid status.
147         $this->expectException(invalid_persistent_exception::class);
148         api::update_request_status($requestid, -1);
149     }
151     /**
152      * Test for api::get_site_dpos() when there are no users with the DPO role.
153      */
154     public function test_get_site_dpos_no_dpos() {
155         $this->resetAfterTest();
157         $admin = get_admin();
159         $dpos = api::get_site_dpos();
160         $this->assertCount(1, $dpos);
161         $dpo = reset($dpos);
162         $this->assertEquals($admin->id, $dpo->id);
163     }
165     /**
166      * Test for api::get_site_dpos() when there are no users with the DPO role.
167      */
168     public function test_get_site_dpos() {
169         global $DB;
171         $this->resetAfterTest();
173         $generator = new testing_data_generator();
174         $u1 = $generator->create_user();
175         $u2 = $generator->create_user();
177         $context = context_system::instance();
179         // Give the manager role with the capability to manage data requests.
180         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
181         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
182         // Assign u1 as a manager.
183         role_assign($managerroleid, $u1->id, $context->id);
185         // Give the editing teacher role with the capability to manage data requests.
186         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
187         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
188         // Assign u1 as an editing teacher as well.
189         role_assign($editingteacherroleid, $u1->id, $context->id);
190         // Assign u2 as an editing teacher.
191         role_assign($editingteacherroleid, $u2->id, $context->id);
193         // Only map the manager role to the DPO role.
194         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
196         $dpos = api::get_site_dpos();
197         $this->assertCount(1, $dpos);
198         $dpo = reset($dpos);
199         $this->assertEquals($u1->id, $dpo->id);
200     }
202     /**
203      * Test for \tool_dataprivacy\api::get_assigned_privacy_officer_roles().
204      */
205     public function test_get_assigned_privacy_officer_roles() {
206         global $DB;
208         $this->resetAfterTest();
210         // Erroneously set the manager roles as the PO, even if it doesn't have the managedatarequests capability yet.
211         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
212         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
213         // Get the assigned PO roles when nothing has been set yet.
214         $roleids = api::get_assigned_privacy_officer_roles();
215         // Confirm that the returned list is empty.
216         $this->assertEmpty($roleids);
218         $context = context_system::instance();
220         // Give the manager role with the capability to manage data requests.
221         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
223         // Give the editing teacher role with the capability to manage data requests.
224         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
225         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
227         // Get the non-editing teacher role ID.
228         $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
230         // Erroneously map the manager and the non-editing teacher roles to the PO role.
231         $badconfig = $managerroleid . ',' . $teacherroleid;
232         set_config('dporoles', $badconfig, 'tool_dataprivacy');
234         // Get the assigned PO roles.
235         $roleids = api::get_assigned_privacy_officer_roles();
237         // There should only be one PO role.
238         $this->assertCount(1, $roleids);
239         // Confirm it contains the manager role.
240         $this->assertContains($managerroleid, $roleids);
241         // And it does not contain the editing teacher role.
242         $this->assertNotContains($editingteacherroleid, $roleids);
243     }
245     /**
246      * Test for api::approve_data_request().
247      */
248     public function test_approve_data_request() {
249         global $DB;
251         $this->resetAfterTest();
253         $generator = new testing_data_generator();
254         $s1 = $generator->create_user();
255         $u1 = $generator->create_user();
257         $context = context_system::instance();
259         // Manager role.
260         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
261         // Give the manager role with the capability to manage data requests.
262         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
263         // Assign u1 as a manager.
264         role_assign($managerroleid, $u1->id, $context->id);
266         // Map the manager role to the DPO role.
267         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
269         // Create the sample data request.
270         $this->setUser($s1);
271         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
272         $requestid = $datarequest->get('id');
274         // Make this ready for approval.
275         api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
277         $this->setUser($u1);
278         $result = api::approve_data_request($requestid);
279         $this->assertTrue($result);
280         $datarequest = new data_request($requestid);
281         $this->assertEquals($u1->id, $datarequest->get('dpo'));
282         $this->assertEquals(api::DATAREQUEST_STATUS_APPROVED, $datarequest->get('status'));
284         // Test adhoc task creation.
285         $adhoctasks = manager::get_adhoc_tasks(process_data_request_task::class);
286         $this->assertCount(1, $adhoctasks);
287     }
289     /**
290      * Test for api::approve_data_request() when called by a user who doesn't have the DPO role.
291      */
292     public function test_approve_data_request_non_dpo_user() {
293         $this->resetAfterTest();
295         $generator = new testing_data_generator();
296         $student = $generator->create_user();
297         $teacher = $generator->create_user();
299         // Create the sample data request.
300         $this->setUser($student);
301         $datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
303         $requestid = $datarequest->get('id');
304     }
306     /**
307      * Test that deletion requests for the primary admin are rejected
308      */
309     public function test_reject_data_deletion_request_primary_admin() {
310         $this->resetAfterTest();
311         $this->setAdminUser();
313         $datarequest = api::create_data_request(get_admin()->id, api::DATAREQUEST_TYPE_DELETE);
315         // Approve the request and execute the ad-hoc process task.
316         ob_start();
317         api::approve_data_request($datarequest->get('id'));
318         $this->runAdhocTasks('\tool_dataprivacy\task\process_data_request_task');
319         ob_end_clean();
321         $request = api::get_request($datarequest->get('id'));
322         $this->assertEquals(api::DATAREQUEST_STATUS_REJECTED, $request->get('status'));
324         // Confirm they weren't deleted.
325         $user = core_user::get_user($request->get('userid'));
326         core_user::require_active_user($user);
327     }
329     /**
330      * Test for api::can_contact_dpo()
331      */
332     public function test_can_contact_dpo() {
333         $this->resetAfterTest();
335         // Default ('contactdataprotectionofficer' is disabled by default).
336         $this->assertFalse(api::can_contact_dpo());
338         // Enable.
339         set_config('contactdataprotectionofficer', 1, 'tool_dataprivacy');
340         $this->assertTrue(api::can_contact_dpo());
342         // Disable again.
343         set_config('contactdataprotectionofficer', 0, 'tool_dataprivacy');
344         $this->assertFalse(api::can_contact_dpo());
345     }
347     /**
348      * Test for api::can_manage_data_requests()
349      */
350     public function test_can_manage_data_requests() {
351         global $DB;
353         $this->resetAfterTest();
355         // No configured site DPOs yet.
356         $admin = get_admin();
357         $this->assertTrue(api::can_manage_data_requests($admin->id));
359         $generator = new testing_data_generator();
360         $dpo = $generator->create_user();
361         $nondpocapable = $generator->create_user();
362         $nondpoincapable = $generator->create_user();
364         $context = context_system::instance();
366         // Manager role.
367         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
368         // Give the manager role with the capability to manage data requests.
369         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
370         // Assign u1 as a manager.
371         role_assign($managerroleid, $dpo->id, $context->id);
373         // Editing teacher role.
374         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
375         // Give the editing teacher role with the capability to manage data requests.
376         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
377         // Assign u2 as an editing teacher.
378         role_assign($editingteacherroleid, $nondpocapable->id, $context->id);
380         // Map only the manager role to the DPO role.
381         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
383         // User with capability and has DPO role.
384         $this->assertTrue(api::can_manage_data_requests($dpo->id));
385         // User with capability but has no DPO role.
386         $this->assertFalse(api::can_manage_data_requests($nondpocapable->id));
387         // User without the capability and has no DPO role.
388         $this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
389     }
391     /**
392      * Test that a user who has no capability to make any data requests for children cannot create data requests for any
393      * other user.
394      */
395     public function test_can_create_data_request_for_user_no() {
396         $this->resetAfterTest();
398         $parent = $this->getDataGenerator()->create_user();
399         $otheruser = $this->getDataGenerator()->create_user();
401         $this->setUser($parent);
402         $this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
403     }
405     /**
406      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
407      * for any other user.
408      */
409     public function test_can_create_data_request_for_user_some() {
410         $this->resetAfterTest();
412         $parent = $this->getDataGenerator()->create_user();
413         $child = $this->getDataGenerator()->create_user();
414         $otheruser = $this->getDataGenerator()->create_user();
416         $systemcontext = \context_system::instance();
417         $parentrole = $this->getDataGenerator()->create_role();
418         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
419         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
421         $this->setUser($parent);
422         $this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
423     }
425     /**
426      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
427      * for any other user.
428      */
429     public function test_can_create_data_request_for_user_own_child() {
430         $this->resetAfterTest();
432         $parent = $this->getDataGenerator()->create_user();
433         $child = $this->getDataGenerator()->create_user();
435         $systemcontext = \context_system::instance();
436         $parentrole = $this->getDataGenerator()->create_role();
437         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
438         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
440         $this->setUser($parent);
441         $this->assertTrue(api::can_create_data_request_for_user($child->id));
442     }
444     /**
445      * Test that a user who has no capability to make any data requests for children cannot create data requests for any
446      * other user.
447      */
448     public function test_require_can_create_data_request_for_user_no() {
449         $this->resetAfterTest();
451         $parent = $this->getDataGenerator()->create_user();
452         $otheruser = $this->getDataGenerator()->create_user();
454         $this->setUser($parent);
455         $this->expectException('required_capability_exception');
456         api::require_can_create_data_request_for_user($otheruser->id);
457     }
459     /**
460      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
461      * for any other user.
462      */
463     public function test_require_can_create_data_request_for_user_some() {
464         $this->resetAfterTest();
466         $parent = $this->getDataGenerator()->create_user();
467         $child = $this->getDataGenerator()->create_user();
468         $otheruser = $this->getDataGenerator()->create_user();
470         $systemcontext = \context_system::instance();
471         $parentrole = $this->getDataGenerator()->create_role();
472         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
473         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
475         $this->setUser($parent);
476         $this->expectException('required_capability_exception');
477         api::require_can_create_data_request_for_user($otheruser->id);
478     }
480     /**
481      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
482      * for any other user.
483      */
484     public function test_require_can_create_data_request_for_user_own_child() {
485         $this->resetAfterTest();
487         $parent = $this->getDataGenerator()->create_user();
488         $child = $this->getDataGenerator()->create_user();
490         $systemcontext = \context_system::instance();
491         $parentrole = $this->getDataGenerator()->create_role();
492         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
493         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
495         $this->setUser($parent);
496         $this->assertTrue(api::require_can_create_data_request_for_user($child->id));
497     }
499     /**
500      * Test for api::can_download_data_request_for_user()
501      */
502     public function test_can_download_data_request_for_user() {
503         $this->resetAfterTest();
505         $generator = $this->getDataGenerator();
507         // Three victims.
508         $victim1 = $generator->create_user();
509         $victim2 = $generator->create_user();
510         $victim3 = $generator->create_user();
512         // Assign a user as victim 1's parent.
513         $systemcontext = \context_system::instance();
514         $parentrole = $generator->create_role();
515         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
516         $parent = $generator->create_user();
517         role_assign($parentrole, $parent->id, \context_user::instance($victim1->id));
519         // Assign another user as data access wonder woman.
520         $wonderrole = $generator->create_role();
521         assign_capability('tool/dataprivacy:downloadallrequests', CAP_ALLOW, $wonderrole, $systemcontext);
522         $staff = $generator->create_user();
523         role_assign($wonderrole, $staff->id, $systemcontext);
525         // Finally, victim 3 has been naughty; stop them accessing their own data.
526         $naughtyrole = $generator->create_role();
527         assign_capability('tool/dataprivacy:downloadownrequest', CAP_PROHIBIT, $naughtyrole, $systemcontext);
528         role_assign($naughtyrole, $victim3->id, $systemcontext);
530         // Victims 1 and 2 can access their own data, regardless of who requested it.
531         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $victim1->id));
532         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $victim2->id));
534         // Victim 3 cannot access his own data.
535         $this->assertFalse(api::can_download_data_request_for_user($victim3->id, $victim3->id, $victim3->id));
537         // Victims 1 and 2 cannot access another victim's data.
538         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $victim1->id, $victim1->id));
539         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $victim2->id));
541         // Staff can access everyone's data.
542         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $staff->id));
543         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $staff->id));
544         $this->assertTrue(api::can_download_data_request_for_user($victim3->id, $staff->id, $staff->id));
546         // Parent can access victim 1's data only if they requested it.
547         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $parent->id, $parent->id));
548         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $parent->id));
549         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $parent->id, $parent->id));
550     }
552     /**
553      * Data provider for data request creation tests.
554      *
555      * @return array
556      */
557     public function data_request_creation_provider() {
558         return [
559             'Export request by user, automatic approval off' => [
560                 false, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', false, 0,
561                 api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
562             ],
563             'Export request by user, automatic approval on' => [
564                 false, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', true, 0,
565                 api::DATAREQUEST_STATUS_APPROVED, 1
566             ],
567             'Export request by PO, automatic approval off' => [
568                 true, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', false, 0,
569                 api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
570             ],
571             'Export request by PO, automatic approval on' => [
572                 true, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', true, 'dpo',
573                 api::DATAREQUEST_STATUS_APPROVED, 1
574             ],
575             'Delete request by user, automatic approval off' => [
576                 false, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', false, 0,
577                 api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
578             ],
579             'Delete request by user, automatic approval on' => [
580                 false, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', true, 0,
581                 api::DATAREQUEST_STATUS_APPROVED, 1
582             ],
583             'Delete request by PO, automatic approval off' => [
584                 true, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', false, 0,
585                 api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
586             ],
587             'Delete request by PO, automatic approval on' => [
588                 true, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', true, 'dpo',
589                 api::DATAREQUEST_STATUS_APPROVED, 1
590             ],
591         ];
592     }
594     /**
595      * Test for api::create_data_request()
596      *
597      * @dataProvider data_request_creation_provider
598      * @param bool $asprivacyofficer Whether the request is made as the Privacy Officer or the user itself.
599      * @param string $type The data request type.
600      * @param string $setting The automatic approval setting.
601      * @param bool $automaticapproval Whether automatic data request approval is turned on or not.
602      * @param int|string $expecteddpoval The expected value for the 'dpo' field. 'dpo' means we'd the expected value would be the
603      *                                   user ID of the privacy officer which happens in the case where a PO requests on behalf of
604      *                                   someone else and automatic data request approval is turned on.
605      * @param int $expectedstatus The expected status of the data request.
606      * @param int $expectedtaskcount The number of expected queued data requests tasks.
607      * @throws coding_exception
608      * @throws invalid_persistent_exception
609      */
610     public function test_create_data_request($asprivacyofficer, $type, $setting, $automaticapproval, $expecteddpoval,
611                                              $expectedstatus, $expectedtaskcount) {
612         global $USER;
614         $this->resetAfterTest();
616         $generator = new testing_data_generator();
617         $user = $generator->create_user();
618         $comment = 'sample comment';
620         // Login.
621         if ($asprivacyofficer) {
622             $this->setAdminUser();
623         } else {
624             $this->setUser($user->id);
625         }
627         // Set the automatic data request approval setting value.
628         set_config($setting, $automaticapproval, 'tool_dataprivacy');
630         // If set to 'dpo' use the currently logged-in user's ID (which should be the admin user's ID).
631         if ($expecteddpoval === 'dpo') {
632             $expecteddpoval = $USER->id;
633         }
635         // Test data request creation.
636         $datarequest = api::create_data_request($user->id, $type, $comment);
637         $this->assertEquals($user->id, $datarequest->get('userid'));
638         $this->assertEquals($USER->id, $datarequest->get('requestedby'));
639         $this->assertEquals($expecteddpoval, $datarequest->get('dpo'));
640         $this->assertEquals($type, $datarequest->get('type'));
641         $this->assertEquals($expectedstatus, $datarequest->get('status'));
642         $this->assertEquals($comment, $datarequest->get('comments'));
643         $this->assertEquals($automaticapproval, $datarequest->get('systemapproved'));
645         // Test number of queued data request tasks.
646         $datarequesttasks = manager::get_adhoc_tasks(process_data_request_task::class);
647         $this->assertCount($expectedtaskcount, $datarequesttasks);
648     }
650     /**
651      * Test for api::create_data_request() made by a parent.
652      */
653     public function test_create_data_request_by_parent() {
654         global $DB;
656         $this->resetAfterTest();
658         $generator = new testing_data_generator();
659         $user = $generator->create_user();
660         $parent = $generator->create_user();
661         $comment = 'sample comment';
663         // Get the teacher role pretend it's the parent roles ;).
664         $systemcontext = context_system::instance();
665         $usercontext = context_user::instance($user->id);
666         $parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
667         // Give the manager role with the capability to manage data requests.
668         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
669         // Assign the parent to user.
670         role_assign($parentroleid, $parent->id, $usercontext->id);
672         // Login as the user's parent.
673         $this->setUser($parent);
675         // Test data request creation.
676         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
677         $this->assertEquals($user->id, $datarequest->get('userid'));
678         $this->assertEquals($parent->id, $datarequest->get('requestedby'));
679         $this->assertEquals(0, $datarequest->get('dpo'));
680         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
681         $this->assertEquals(api::DATAREQUEST_STATUS_AWAITING_APPROVAL, $datarequest->get('status'));
682         $this->assertEquals($comment, $datarequest->get('comments'));
683     }
685     /**
686      * Test for api::deny_data_request()
687      */
688     public function test_deny_data_request() {
689         $this->resetAfterTest();
691         $generator = new testing_data_generator();
692         $user = $generator->create_user();
693         $comment = 'sample comment';
695         // Login as user.
696         $this->setUser($user->id);
698         // Test data request creation.
699         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
701         // Login as the admin (default DPO when no one is set).
702         $this->setAdminUser();
704         // Make this ready for approval.
705         api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
707         // Deny the data request.
708         $result = api::deny_data_request($datarequest->get('id'));
709         $this->assertTrue($result);
710     }
712     /**
713      * Data provider for \tool_dataprivacy_api_testcase::test_get_data_requests().
714      *
715      * @return array
716      */
717     public function get_data_requests_provider() {
718         $completeonly = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DOWNLOAD_READY, api::DATAREQUEST_STATUS_DELETED];
719         $completeandcancelled = array_merge($completeonly, [api::DATAREQUEST_STATUS_CANCELLED]);
721         return [
722             // Own data requests.
723             ['user', false, $completeonly],
724             // Non-DPO fetching all requets.
725             ['user', true, $completeonly],
726             // Admin fetching all completed and cancelled requests.
727             ['dpo', true, $completeandcancelled],
728             // Admin fetching all completed requests.
729             ['dpo', true, $completeonly],
730             // Guest fetching all requests.
731             ['guest', true, $completeonly],
732         ];
733     }
735     /**
736      * Test for api::get_data_requests()
737      *
738      * @dataProvider get_data_requests_provider
739      * @param string $usertype The type of the user logging in.
740      * @param boolean $fetchall Whether to fetch all records.
741      * @param int[] $statuses Status filters.
742      */
743     public function test_get_data_requests($usertype, $fetchall, $statuses) {
744         $this->resetAfterTest();
746         $generator = new testing_data_generator();
747         $user1 = $generator->create_user();
748         $user2 = $generator->create_user();
749         $user3 = $generator->create_user();
750         $user4 = $generator->create_user();
751         $user5 = $generator->create_user();
752         $users = [$user1, $user2, $user3, $user4, $user5];
754         switch ($usertype) {
755             case 'user':
756                 $loggeduser = $user1;
757                 break;
758             case 'dpo':
759                 $loggeduser = get_admin();
760                 break;
761             case 'guest':
762                 $loggeduser = guest_user();
763                 break;
764         }
766         $comment = 'Data %s request comment by user %d';
767         $exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
768         $deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
769         // Make a data requests for the users.
770         foreach ($users as $user) {
771             $this->setUser($user);
772             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $exportstring, $user->id));
773             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $deletionstring, $user->id));
774         }
776         // Log in as the target user.
777         $this->setUser($loggeduser);
778         // Get records count based on the filters.
779         $userid = $loggeduser->id;
780         if ($fetchall) {
781             $userid = 0;
782         }
783         $count = api::get_data_requests_count($userid);
784         if (api::is_site_dpo($loggeduser->id)) {
785             // DPOs should see all the requests.
786             $this->assertEquals(count($users) * 2, $count);
787         } else {
788             if (empty($userid)) {
789                 // There should be no data requests for this user available.
790                 $this->assertEquals(0, $count);
791             } else {
792                 // There should be only one (request with pending status).
793                 $this->assertEquals(2, $count);
794             }
795         }
796         // Get data requests.
797         $requests = api::get_data_requests($userid);
798         // The number of requests should match the count.
799         $this->assertCount($count, $requests);
801         // Test filtering by status.
802         if ($count && !empty($statuses)) {
803             $filteredcount = api::get_data_requests_count($userid, $statuses);
804             // There should be none as they are all pending.
805             $this->assertEquals(0, $filteredcount);
806             $filteredrequests = api::get_data_requests($userid, $statuses);
807             $this->assertCount($filteredcount, $filteredrequests);
809             $statuscounts = [];
810             foreach ($statuses as $stat) {
811                 $statuscounts[$stat] = 0;
812             }
813             $numstatus = count($statuses);
814             // Get all requests with status filter and update statuses, randomly.
815             foreach ($requests as $request) {
816                 if (rand(0, 1)) {
817                     continue;
818                 }
820                 if ($numstatus > 1) {
821                     $index = rand(0, $numstatus - 1);
822                     $status = $statuses[$index];
823                 } else {
824                     $status = reset($statuses);
825                 }
826                 $statuscounts[$status]++;
827                 api::update_request_status($request->get('id'), $status);
828             }
829             $total = array_sum($statuscounts);
830             $filteredcount = api::get_data_requests_count($userid, $statuses);
831             $this->assertEquals($total, $filteredcount);
832             $filteredrequests = api::get_data_requests($userid, $statuses);
833             $this->assertCount($filteredcount, $filteredrequests);
834             // Confirm the filtered requests match the status filter(s).
835             foreach ($filteredrequests as $request) {
836                 $this->assertContains($request->get('status'), $statuses);
837             }
839             if ($numstatus > 1) {
840                 // Fetch by individual status to check the numbers match.
841                 foreach ($statuses as $status) {
842                     $filteredcount = api::get_data_requests_count($userid, [$status]);
843                     $this->assertEquals($statuscounts[$status], $filteredcount);
844                     $filteredrequests = api::get_data_requests($userid, [$status]);
845                     $this->assertCount($filteredcount, $filteredrequests);
846                 }
847             }
848         }
849     }
851     /**
852      * Data provider for test_has_ongoing_request.
853      */
854     public function status_provider() {
855         return [
856             [api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
857             [api::DATAREQUEST_STATUS_APPROVED, true],
858             [api::DATAREQUEST_STATUS_PROCESSING, true],
859             [api::DATAREQUEST_STATUS_COMPLETE, false],
860             [api::DATAREQUEST_STATUS_CANCELLED, false],
861             [api::DATAREQUEST_STATUS_REJECTED, false],
862             [api::DATAREQUEST_STATUS_DOWNLOAD_READY, false],
863             [api::DATAREQUEST_STATUS_EXPIRED, false],
864             [api::DATAREQUEST_STATUS_DELETED, false],
865         ];
866     }
868     /**
869      * Test for api::has_ongoing_request()
870      *
871      * @dataProvider status_provider
872      * @param int $status The request status.
873      * @param bool $expected The expected result.
874      */
875     public function test_has_ongoing_request($status, $expected) {
876         $this->resetAfterTest();
878         $generator = new testing_data_generator();
879         $user1 = $generator->create_user();
881         // Make a data request as user 1.
882         $this->setUser($user1);
883         $request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
884         // Set the status.
885         api::update_request_status($request->get('id'), $status);
887         // Check if this request is ongoing.
888         $result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
889         $this->assertEquals($expected, $result);
890     }
892     /**
893      * Test for api::is_active()
894      *
895      * @dataProvider status_provider
896      * @param int $status The request status
897      * @param bool $expected The expected result
898      */
899     public function test_is_active($status, $expected) {
900         // Check if this request is ongoing.
901         $result = api::is_active($status);
902         $this->assertEquals($expected, $result);
903     }
905     /**
906      * Test for api::is_site_dpo()
907      */
908     public function test_is_site_dpo() {
909         global $DB;
911         $this->resetAfterTest();
913         // No configured site DPOs yet.
914         $admin = get_admin();
915         $this->assertTrue(api::is_site_dpo($admin->id));
917         $generator = new testing_data_generator();
918         $dpo = $generator->create_user();
919         $nondpo = $generator->create_user();
921         $context = context_system::instance();
923         // Manager role.
924         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
925         // Give the manager role with the capability to manage data requests.
926         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
927         // Assign u1 as a manager.
928         role_assign($managerroleid, $dpo->id, $context->id);
930         // Map only the manager role to the DPO role.
931         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
933         // User is a DPO.
934         $this->assertTrue(api::is_site_dpo($dpo->id));
935         // User is not a DPO.
936         $this->assertFalse(api::is_site_dpo($nondpo->id));
937     }
939     /**
940      * Data provider function for test_notify_dpo
941      *
942      * @return array
943      */
944     public function notify_dpo_provider() {
945         return [
946             [false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
947             [false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
948             [false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
949             [true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
950         ];
951     }
953     /**
954      * Test for api::notify_dpo()
955      *
956      * @dataProvider notify_dpo_provider
957      * @param bool $byadmin Whether the admin requests data on behalf of the user
958      * @param int $type The request type
959      * @param string $typestringid The request lang string identifier
960      * @param string $comments The requestor's message to the DPO.
961      */
962     public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
963         $this->resetAfterTest();
965         $generator = new testing_data_generator();
966         $user1 = $generator->create_user();
967         // Let's just use admin as DPO (It's the default if not set).
968         $dpo = get_admin();
969         if ($byadmin) {
970             $this->setAdminUser();
971             $requestedby = $dpo;
972         } else {
973             $this->setUser($user1);
974             $requestedby = $user1;
975         }
977         // Make a data request for user 1.
978         $request = api::create_data_request($user1->id, $type, $comments);
980         $sink = $this->redirectMessages();
981         $messageid = api::notify_dpo($dpo, $request);
982         $this->assertNotFalse($messageid);
983         $messages = $sink->get_messages();
984         $this->assertCount(1, $messages);
985         $message = reset($messages);
987         // Check some of the message properties.
988         $this->assertEquals($requestedby->id, $message->useridfrom);
989         $this->assertEquals($dpo->id, $message->useridto);
990         $typestring = get_string($typestringid, 'tool_dataprivacy');
991         $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
992         $this->assertEquals($subject, $message->subject);
993         $this->assertEquals('tool_dataprivacy', $message->component);
994         $this->assertEquals('contactdataprotectionofficer', $message->eventtype);
995         $this->assertContains(fullname($dpo), $message->fullmessage);
996         $this->assertContains(fullname($user1), $message->fullmessage);
997     }
999     /**
1000      * Test data purposes CRUD actions.
1001      *
1002      * @return null
1003      */
1004     public function test_purpose_crud() {
1005         $this->resetAfterTest();
1007         $this->setAdminUser();
1009         // Add.
1010         $purpose = api::create_purpose((object)[
1011             'name' => 'bbb',
1012             'description' => '<b>yeah</b>',
1013             'descriptionformat' => 1,
1014             'retentionperiod' => 'PT1M',
1015             'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
1016         ]);
1017         $this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
1018         $this->assertEquals('bbb', $purpose->get('name'));
1019         $this->assertEquals('PT1M', $purpose->get('retentionperiod'));
1020         $this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
1022         // Update.
1023         $purpose->set('retentionperiod', 'PT2M');
1024         $purpose = api::update_purpose($purpose->to_record());
1025         $this->assertEquals('PT2M', $purpose->get('retentionperiod'));
1027         // Retrieve.
1028         $purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1029         $purposes = api::get_purposes();
1030         $this->assertCount(2, $purposes);
1031         $this->assertEquals('aaa', $purposes[0]->get('name'));
1032         $this->assertEquals('bbb', $purposes[1]->get('name'));
1034         // Delete.
1035         api::delete_purpose($purposes[0]->get('id'));
1036         $this->assertCount(1, api::get_purposes());
1037         api::delete_purpose($purposes[1]->get('id'));
1038         $this->assertCount(0, api::get_purposes());
1039     }
1041     /**
1042      * Test data categories CRUD actions.
1043      *
1044      * @return null
1045      */
1046     public function test_category_crud() {
1047         $this->resetAfterTest();
1049         $this->setAdminUser();
1051         // Add.
1052         $category = api::create_category((object)[
1053             'name' => 'bbb',
1054             'description' => '<b>yeah</b>',
1055             'descriptionformat' => 1
1056         ]);
1057         $this->assertInstanceOf('\tool_dataprivacy\category', $category);
1058         $this->assertEquals('bbb', $category->get('name'));
1060         // Update.
1061         $category->set('name', 'bcd');
1062         $category = api::update_category($category->to_record());
1063         $this->assertEquals('bcd', $category->get('name'));
1065         // Retrieve.
1066         $category = api::create_category((object)['name' => 'aaa']);
1067         $categories = api::get_categories();
1068         $this->assertCount(2, $categories);
1069         $this->assertEquals('aaa', $categories[0]->get('name'));
1070         $this->assertEquals('bcd', $categories[1]->get('name'));
1072         // Delete.
1073         api::delete_category($categories[0]->get('id'));
1074         $this->assertCount(1, api::get_categories());
1075         api::delete_category($categories[1]->get('id'));
1076         $this->assertCount(0, api::get_categories());
1077     }
1079     /**
1080      * Test context instances.
1081      *
1082      * @return null
1083      */
1084     public function test_context_instances() {
1085         global $DB;
1087         $this->resetAfterTest();
1089         $this->setAdminUser();
1091         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1093         $coursecontext1 = \context_course::instance($courses[0]->id);
1094         $coursecontext2 = \context_course::instance($courses[1]->id);
1096         $record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
1097             'categoryid' => $categories[0]->get('id')];
1098         $contextinstance1 = api::set_context_instance($record1);
1100         $record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
1101             'categoryid' => $categories[1]->get('id')];
1102         $contextinstance2 = api::set_context_instance($record2);
1104         $this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
1106         api::unset_context_instance($contextinstance1);
1107         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1109         $update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
1110             'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
1111         $contextinstance2 = api::set_context_instance($update);
1112         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1113     }
1115     /**
1116      * Test contextlevel.
1117      *
1118      * @return null
1119      */
1120     public function test_contextlevel() {
1121         global $DB;
1123         $this->resetAfterTest();
1125         $this->setAdminUser();
1126         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1128         $record = (object)[
1129             'purposeid' => $purposes[0]->get('id'),
1130             'categoryid' => $categories[0]->get('id'),
1131             'contextlevel' => CONTEXT_SYSTEM,
1132         ];
1133         $contextlevel = api::set_contextlevel($record);
1134         $this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
1135         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1136         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1137         $this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
1139         // Now update it.
1140         $record->purposeid = $purposes[1]->get('id');
1141         $contextlevel = api::set_contextlevel($record);
1142         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1143         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1144         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
1146         $record->contextlevel = CONTEXT_USER;
1147         $contextlevel = api::set_contextlevel($record);
1148         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
1149     }
1151     /**
1152      * Test effective context levels purpose and category defaults.
1153      *
1154      * @return null
1155      */
1156     public function test_effective_contextlevel_defaults() {
1157         $this->setAdminUser();
1159         $this->resetAfterTest();
1161         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1163         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1164         $this->assertEquals(false, $purposeid);
1165         $this->assertEquals(false, $categoryid);
1167         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1168             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1169         );
1170         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1172         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1173         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1174         $this->assertEquals(false, $categoryid);
1176         // Course defined values should have preference.
1177         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1178             \context_helper::get_class_for_level(CONTEXT_COURSE)
1179         );
1180         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1181         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1183         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1184         $this->assertEquals($purposes[1]->get('id'), $purposeid);
1185         $this->assertEquals($categories[0]->get('id'), $categoryid);
1187         // Context level defaults are also allowed to be set to 'inherit'.
1188         set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
1189     }
1191     /**
1192      * Ensure that when nothing is configured, all values return false.
1193      */
1194     public function test_get_effective_contextlevel_unset() {
1195         // Before setup, get_effective_contextlevel_purpose will return false.
1196         $this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
1197         $this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
1199         $this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_USER));
1200         $this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_USER));
1201     }
1203     /**
1204      * Ensure that when nothing is configured, all values return false.
1205      */
1206     public function test_get_effective_context_unset() {
1207         // Before setup, get_effective_contextlevel_purpose will return false.
1208         $this->assertFalse(api::get_effective_context_category(\context_system::instance()));
1209         $this->assertFalse(api::get_effective_context_purpose(\context_system::instance()));
1210     }
1212     /**
1213      * Ensure that fetching the effective value for context levels is only available to system, and user context levels.
1214      *
1215      * @dataProvider invalid_effective_contextlevel_provider
1216      * @param   int $contextlevel
1217      */
1218     public function test_set_contextlevel_invalid_contextlevels($contextlevel) {
1220         $this->expectException(coding_exception::class);
1221         api::set_contextlevel((object) [
1222                 'contextlevel' => $contextlevel,
1223             ]);
1225     }
1227     /**
1228      * Test effective contextlevel return.
1229      */
1230     public function test_effective_contextlevel() {
1231         $this->resetAfterTest();
1233         // Set the initial purpose and category.
1234         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1235         $category1 = api::create_category((object)['name' => 'a']);
1236         api::set_contextlevel((object)[
1237             'contextlevel' => CONTEXT_SYSTEM,
1238             'purposeid' => $purpose1->get('id'),
1239             'categoryid' => $category1->get('id'),
1240         ]);
1242         $this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
1243         $this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
1245         // The user context inherits from the system context when not set.
1246         $this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_USER));
1247         $this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_USER));
1249         // Forcing the behaviour to inherit will have the same result.
1250         api::set_contextlevel((object) [
1251                 'contextlevel' => CONTEXT_USER,
1252                 'purposeid' => context_instance::INHERIT,
1253                 'categoryid' => context_instance::INHERIT,
1254             ]);
1255         $this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_USER));
1256         $this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_USER));
1258         // Setting specific values will override the inheritance behaviour.
1259         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1260         $category2 = api::create_category((object)['name' => 'b']);
1261         // Set the system context level to purpose 1.
1262         api::set_contextlevel((object) [
1263                 'contextlevel' => CONTEXT_USER,
1264                 'purposeid' => $purpose2->get('id'),
1265                 'categoryid' => $category2->get('id'),
1266             ]);
1268         $this->assertEquals($purpose2, api::get_effective_contextlevel_purpose(CONTEXT_USER));
1269         $this->assertEquals($category2, api::get_effective_contextlevel_category(CONTEXT_USER));
1270     }
1272     /**
1273      * Ensure that fetching the effective value for context levels is only available to system, and user context levels.
1274      *
1275      * @dataProvider invalid_effective_contextlevel_provider
1276      * @param   int $contextlevel
1277      */
1278     public function test_effective_contextlevel_invalid_contextlevels($contextlevel) {
1279         $this->resetAfterTest();
1281         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1282         $category1 = api::create_category((object)['name' => 'a']);
1283         api::set_contextlevel((object)[
1284             'contextlevel' => CONTEXT_SYSTEM,
1285             'purposeid' => $purpose1->get('id'),
1286             'categoryid' => $category1->get('id'),
1287         ]);
1289         $this->expectException(coding_exception::class);
1290         api::get_effective_contextlevel_purpose($contextlevel);
1291     }
1293     /**
1294      * Data provider for invalid contextlevel fetchers.
1295      */
1296     public function invalid_effective_contextlevel_provider() {
1297         return [
1298             [CONTEXT_COURSECAT],
1299             [CONTEXT_COURSE],
1300             [CONTEXT_MODULE],
1301             [CONTEXT_BLOCK],
1302         ];
1303     }
1305     /**
1306      * Ensure that context inheritance works up the context tree.
1307      */
1308     public function test_effective_context_inheritance() {
1309         $this->resetAfterTest();
1311         $systemdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_SYSTEM);
1313         /*
1314          * System
1315          * - Cat
1316          *   - Subcat
1317          *     - Course
1318          *       - Forum
1319          * - User
1320          *   - User block
1321          */
1322         $cat = $this->getDataGenerator()->create_category();
1323         $subcat = $this->getDataGenerator()->create_category(['parent' => $cat->id]);
1324         $course = $this->getDataGenerator()->create_course(['category' => $subcat->id]);
1325         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1326         list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1328         $user = $this->getDataGenerator()->create_user();
1330         $contextsystem = \context_system::instance();
1331         $contextcat = \context_coursecat::instance($cat->id);
1332         $contextsubcat = \context_coursecat::instance($subcat->id);
1333         $contextcourse = \context_course::instance($course->id);
1334         $contextforum = \context_module::instance($forumcm->id);
1335         $contextuser = \context_user::instance($user->id);
1337         // Initially everything is set to Inherit.
1338         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1339         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1340         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1341         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "0"));
1342         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1343         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1344         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "0"));
1345         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1346         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "-1"));
1347         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "0"));
1348         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1349         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "-1"));
1350         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "0"));
1351         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser));
1352         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "-1"));
1353         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "0"));
1355         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1356         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1357         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1358         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "0"));
1359         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1360         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "-1"));
1361         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "0"));
1362         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1363         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "-1"));
1364         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "0"));
1365         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1366         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "-1"));
1367         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "0"));
1368         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser));
1369         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser, "-1"));
1370         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser, "0"));
1372         // When actively set, user will use the specified value.
1373         $userdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_USER);
1375         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1376         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1377         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1378         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "0"));
1379         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1380         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1381         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "0"));
1382         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1383         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "-1"));
1384         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "0"));
1385         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1386         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "-1"));
1387         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "0"));
1388         $this->assertEquals($userdata->purpose, api::get_effective_context_purpose($contextuser));
1389         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "-1"));
1391         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1392         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1393         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1394         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "0"));
1395         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1396         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "-1"));
1397         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "0"));
1398         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1399         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "-1"));
1400         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "0"));
1401         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1402         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "-1"));
1403         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "0"));
1404         $this->assertEquals($userdata->category, api::get_effective_context_category($contextuser));
1405         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "-1"));
1407         // Set a context for the top category.
1408         $catpurpose = new purpose(0, (object) [
1409                 'name' => 'Purpose',
1410                 'retentionperiod' => 'P1D',
1411                 'lawfulbases' => 'gdpr_art_6_1_a',
1412             ]);
1413         $catpurpose->save();
1414         $catcategory = new category(0, (object) ['name' => 'Category']);
1415         $catcategory->save();
1416         api::set_context_instance((object) [
1417                 'contextid' => $contextcat->id,
1418                 'purposeid' => $catpurpose->get('id'),
1419                 'categoryid' => $catcategory->get('id'),
1420             ]);
1422         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1423         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
1424         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1425         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
1426         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat));
1427         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1428         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
1429         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse));
1430         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
1431         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse, "0"));
1432         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum));
1433         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum, "-1"));
1434         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum, "0"));
1435         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum, "0"));
1437         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1438         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
1439         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1440         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
1441         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat));
1442         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
1443         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "0"));
1444         $this->assertEquals($catcategory, api::get_effective_context_category($contextcourse));
1445         $this->assertEquals($catcategory, api::get_effective_context_category($contextcourse, "-1"));
1446         $this->assertEquals($catcategory, api::get_effective_context_category($contextcourse, "0"));
1447         $this->assertEquals($catcategory, api::get_effective_context_category($contextforum));
1448         $this->assertEquals($catcategory, api::get_effective_context_category($contextforum, "-1"));
1449         $this->assertEquals($catcategory, api::get_effective_context_category($contextforum, "0"));
1451         // Set a context for the sub category.
1452         $subcatpurpose = new purpose(0, (object) [
1453                 'name' => 'Purpose',
1454                 'retentionperiod' => 'P1D',
1455                 'lawfulbases' => 'gdpr_art_6_1_a',
1456             ]);
1457         $subcatpurpose->save();
1458         $subcatcategory = new category(0, (object) ['name' => 'Category']);
1459         $subcatcategory->save();
1460         api::set_context_instance((object) [
1461                 'contextid' => $contextsubcat->id,
1462                 'purposeid' => $subcatpurpose->get('id'),
1463                 'categoryid' => $subcatcategory->get('id'),
1464             ]);
1466         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1467         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
1468         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1469         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
1470         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
1471         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1472         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
1473         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse));
1474         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
1475         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "0"));
1476         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum));
1477         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum, "-1"));
1478         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum, "0"));
1480         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1481         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
1482         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1483         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
1484         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
1485         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
1486         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat, "0"));
1487         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse));
1488         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "-1"));
1489         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "0"));
1490         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum));
1491         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum, "-1"));
1492         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum, "0"));
1494         // Set a context for the course.
1495         $coursepurpose = new purpose(0, (object) [
1496                 'name' => 'Purpose',
1497                 'retentionperiod' => 'P1D',
1498                 'lawfulbases' => 'gdpr_art_6_1_a',
1499             ]);
1500         $coursepurpose->save();
1501         $coursecategory = new category(0, (object) ['name' => 'Category']);
1502         $coursecategory->save();
1503         api::set_context_instance((object) [
1504                 'contextid' => $contextcourse->id,
1505                 'purposeid' => $coursepurpose->get('id'),
1506                 'categoryid' => $coursecategory->get('id'),
1507             ]);
1509         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1510         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
1511         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1512         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
1513         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
1514         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1515         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
1516         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse));
1517         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
1518         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse, "0"));
1519         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum));
1520         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum, "-1"));
1521         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum, "0"));
1523         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1524         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
1525         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1526         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
1527         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
1528         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
1529         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat, "0"));
1530         $this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse));
1531         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "-1"));
1532         $this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse, "0"));
1533         $this->assertEquals($coursecategory, api::get_effective_context_category($contextforum));
1534         $this->assertEquals($coursecategory, api::get_effective_context_category($contextforum, "-1"));
1535         $this->assertEquals($coursecategory, api::get_effective_context_category($contextforum, "0"));
1537         // Set a context for the forum.
1538         $forumpurpose = new purpose(0, (object) [
1539                 'name' => 'Purpose',
1540                 'retentionperiod' => 'P1D',
1541                 'lawfulbases' => 'gdpr_art_6_1_a',
1542             ]);
1543         $forumpurpose->save();
1544         $forumcategory = new category(0, (object) ['name' => 'Category']);
1545         $forumcategory->save();
1546         api::set_context_instance((object) [
1547                 'contextid' => $contextforum->id,
1548                 'purposeid' => $forumpurpose->get('id'),
1549                 'categoryid' => $forumcategory->get('id'),
1550             ]);
1552         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1553         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
1554         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1555         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
1556         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
1557         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1558         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
1559         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse));
1560         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
1561         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse, "0"));
1562         $this->assertEquals($forumpurpose, api::get_effective_context_purpose($contextforum));
1563         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum, "-1"));
1564         $this->assertEquals($forumpurpose, api::get_effective_context_purpose($contextforum, "0"));
1566         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1567         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
1568         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1569         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
1570         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
1571         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
1572         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat, "0"));
1573         $this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse));
1574         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "-1"));
1575         $this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse, "0"));
1576         $this->assertEquals($forumcategory, api::get_effective_context_category($contextforum));
1577         $this->assertEquals($coursecategory, api::get_effective_context_category($contextforum, "-1"));
1578         $this->assertEquals($forumcategory, api::get_effective_context_category($contextforum, "0"));
1579     }
1581     /**
1582      * Ensure that context inheritance works up the context tree when inherit values are explicitly set at the
1583      * contextlevel.
1584      *
1585      * Although it should not be possible to set hard INHERIT values at this level, there may be legacy data which still
1586      * contains this.
1587      */
1588     public function test_effective_context_inheritance_explicitly_set() {
1589         $this->resetAfterTest();
1591         $systemdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_SYSTEM);
1593         /*
1594          * System
1595          * - Cat
1596          *   - Subcat
1597          *     - Course
1598          *       - Forum
1599          * - User
1600          *   - User block
1601          */
1602         $cat = $this->getDataGenerator()->create_category();
1603         $subcat = $this->getDataGenerator()->create_category(['parent' => $cat->id]);
1604         $course = $this->getDataGenerator()->create_course(['category' => $subcat->id]);
1605         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1606         list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1608         $contextsystem = \context_system::instance();
1609         $contextcat = \context_coursecat::instance($cat->id);
1610         $contextsubcat = \context_coursecat::instance($subcat->id);
1611         $contextcourse = \context_course::instance($course->id);
1612         $contextforum = \context_module::instance($forumcm->id);
1614         // Initially everything is set to Inherit.
1615         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1616         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1617         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1618         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1619         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1621         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1622         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1623         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1624         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1625         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1627         // Set a default value of inherit for CONTEXT_COURSECAT.
1628         $classname = \context_helper::get_class_for_level(CONTEXT_COURSECAT);
1629         list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
1630         set_config($purposevar, '-1', 'tool_dataprivacy');
1631         set_config($categoryvar, '-1', 'tool_dataprivacy');
1633         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1634         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1635         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1636         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1637         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1639         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1640         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1641         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1642         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1643         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1645         // Set a default value of inherit for CONTEXT_COURSE.
1646         $classname = \context_helper::get_class_for_level(CONTEXT_COURSE);
1647         list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
1648         set_config($purposevar, '-1', 'tool_dataprivacy');
1649         set_config($categoryvar, '-1', 'tool_dataprivacy');
1651         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1652         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1653         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1654         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1655         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1657         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1658         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1659         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1660         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1661         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1663         // Set a default value of inherit for CONTEXT_MODULE.
1664         $classname = \context_helper::get_class_for_level(CONTEXT_MODULE);
1665         list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
1666         set_config($purposevar, '-1', 'tool_dataprivacy');
1667         set_config($categoryvar, '-1', 'tool_dataprivacy');
1669         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1670         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1671         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1672         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1673         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1675         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1676         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1677         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1678         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1679         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1680     }
1682     /**
1683      * Creates test purposes and categories.
1684      *
1685      * @return null
1686      */
1687     protected function add_purposes_and_categories() {
1688         $this->resetAfterTest();
1690         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1691         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
1692         $purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
1694         $cat1 = api::create_category((object)['name' => 'a']);
1695         $cat2 = api::create_category((object)['name' => 'b']);
1696         $cat3 = api::create_category((object)['name' => 'c']);
1698         $course1 = $this->getDataGenerator()->create_course();
1699         $course2 = $this->getDataGenerator()->create_course();
1701         $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
1702         $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
1704         return [
1705             [$purpose1, $purpose2, $purpose3],
1706             [$cat1, $cat2, $cat3],
1707             [$course1, $course2],
1708             [$module1, $module2]
1709         ];
1710     }
1712     /**
1713      * Test that delete requests do not filter out protected purpose contexts if the the site is properly configured.
1714      */
1715     public function test_get_approved_contextlist_collection_for_collection_delete_course_no_site_config() {
1716         $this->resetAfterTest();
1718         $user = $this->getDataGenerator()->create_user();
1720         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
1721         $coursecontext = \context_course::instance($course->id);
1723         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1724         list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1725         $contextforum = \context_module::instance($forumcm->id);
1727         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1729         // Create the initial contextlist.
1730         $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
1732         $contextlist = new \core_privacy\local\request\contextlist();
1733         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
1734         $contextlist->set_component('tool_dataprivacy');
1735         $initialcollection->add_contextlist($contextlist);
1737         $contextlist = new \core_privacy\local\request\contextlist();
1738         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $contextforum->id]);
1739         $contextlist->set_component('mod_forum');
1740         $initialcollection->add_contextlist($contextlist);
1742         $collection = api::get_approved_contextlist_collection_for_collection(
1743                 $initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
1745         $this->assertCount(2, $collection);
1747         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1748         $this->assertCount(1, $list);
1750         $list = $collection->get_contextlist_for_component('mod_forum');
1751         $this->assertCount(1, $list);
1752     }
1754     /**
1755      * Test that delete requests do not filter out protected purpose contexts if they are already expired.
1756      */
1757     public function test_get_approved_contextlist_collection_for_collection_delete_course_expired_protected() {
1758         $this->resetAfterTest();
1760         $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
1761         $purposes->course->purpose->set('protected', 1)->save();
1763         $user = $this->getDataGenerator()->create_user();
1764         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
1765         $coursecontext = \context_course::instance($course->id);
1767         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1769         // Create the initial contextlist.
1770         $contextlist = new \core_privacy\local\request\contextlist();
1771         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
1772         $contextlist->set_component('tool_dataprivacy');
1774         $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
1775         $initialcollection->add_contextlist($contextlist);
1777         $purposes->course->purpose->set('protected', 1)->save();
1778         $collection = api::get_approved_contextlist_collection_for_collection(
1779                 $initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
1781         $this->assertCount(1, $collection);
1783         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1784         $this->assertCount(1, $list);
1785     }
1787     /**
1788      * Test that delete requests does filter out protected purpose contexts which are not expired.
1789      */
1790     public function test_get_approved_contextlist_collection_for_collection_delete_course_unexpired_protected() {
1791         $this->resetAfterTest();
1793         $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
1794         $purposes->course->purpose->set('protected', 1)->save();
1796         $user = $this->getDataGenerator()->create_user();
1797         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
1798         $coursecontext = \context_course::instance($course->id);
1800         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1802         // Create the initial contextlist.
1803         $contextlist = new \core_privacy\local\request\contextlist();
1804         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
1805         $contextlist->set_component('tool_dataprivacy');
1807         $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
1808         $initialcollection->add_contextlist($contextlist);
1810         $purposes->course->purpose->set('protected', 1)->save();
1811         $collection = api::get_approved_contextlist_collection_for_collection(
1812                 $initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
1814         $this->assertCount(0, $collection);
1816         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1817         $this->assertEmpty($list);
1818     }
1820     /**
1821      * Test that delete requests do not filter out unexpired contexts if they are not protected.
1822      */
1823     public function test_get_approved_contextlist_collection_for_collection_delete_course_unexpired_unprotected() {
1824         $this->resetAfterTest();
1826         $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
1827         $purposes->course->purpose->set('protected', 1)->save();
1829         $user = $this->getDataGenerator()->create_user();
1830         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
1831         $coursecontext = \context_course::instance($course->id);
1833         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1835         // Create the initial contextlist.
1836         $contextlist = new \core_privacy\local\request\contextlist();
1837         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
1838         $contextlist->set_component('tool_dataprivacy');
1840         $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
1841         $initialcollection->add_contextlist($contextlist);
1843         $purposes->course->purpose->set('protected', 0)->save();
1844         $collection = api::get_approved_contextlist_collection_for_collection(
1845                 $initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
1847         $this->assertCount(1, $collection);
1849         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1850         $this->assertCount(1, $list);
1851     }
1853     /**
1854      * Data provider for \tool_dataprivacy_api_testcase::test_set_context_defaults
1855      */
1856     public function set_context_defaults_provider() {
1857         $contextlevels = [
1858             [CONTEXT_COURSECAT],
1859             [CONTEXT_COURSE],
1860             [CONTEXT_MODULE],
1861             [CONTEXT_BLOCK],
1862         ];
1863         $paramsets = [
1864             [true, true, false, false], // Inherit category and purpose, Not for activity, Don't override.
1865             [true, false, false, false], // Inherit category but not purpose, Not for activity, Don't override.
1866             [false, true, false, false], // Inherit purpose but not category, Not for activity, Don't override.
1867             [false, false, false, false], // Don't inherit both category and purpose, Not for activity, Don't override.
1868             [false, false, false, true], // Don't inherit both category and purpose, Not for activity, Override instances.
1869         ];
1870         $data = [];
1871         foreach ($contextlevels as $level) {
1872             foreach ($paramsets as $set) {
1873                 $data[] = array_merge($level, $set);
1874             }
1875             if ($level == CONTEXT_MODULE) {
1876                 // Add a combination where defaults for activity is being set.
1877                 $data[] = [CONTEXT_MODULE, false, false, true, false];
1878                 $data[] = [CONTEXT_MODULE, false, false, true, true];
1879             }
1880         }
1881         return $data;
1882     }
1884     /**
1885      * Test for \tool_dataprivacy\api::set_context_defaults()
1886      *
1887      * @dataProvider set_context_defaults_provider
1888      * @param int $contextlevel The context level
1889      * @param bool $inheritcategory Whether to set category value as INHERIT.
1890      * @param bool $inheritpurpose Whether to set purpose value as INHERIT.
1891      * @param bool $foractivity Whether to set defaults for an activity.
1892      * @param bool $override Whether to override instances.
1893      */
1894     public function test_set_context_defaults($contextlevel, $inheritcategory, $inheritpurpose, $foractivity, $override) {
1895         $this->resetAfterTest();
1897         $generator = $this->getDataGenerator();
1899         // Generate course cat, course, block, assignment, forum instances.
1900         $coursecat = $generator->create_category();
1901         $course = $generator->create_course(['category' => $coursecat->id]);
1902         $block = $generator->create_block('online_users');
1903         $assign = $generator->create_module('assign', ['course' => $course->id]);
1904         $forum = $generator->create_module('forum', ['course' => $course->id]);
1906         $coursecatcontext = context_coursecat::instance($coursecat->id);
1907         $coursecontext = context_course::instance($course->id);
1908         $blockcontext = context_block::instance($block->id);
1910         list($course, $assigncm) = get_course_and_cm_from_instance($assign->id, 'assign');
1911         list($course, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1912         $assigncontext = context_module::instance($assigncm->id);
1913         $forumcontext = context_module::instance($forumcm->id);
1915         // Generate purposes and categories.
1916         $category1 = api::create_category((object)['name' => 'Test category 1']);
1917         $category2 = api::create_category((object)['name' => 'Test category 2']);
1918         $purpose1 = api::create_purpose((object)[
1919             'name' => 'Test purpose 1', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1920         ]);
1921         $purpose2 = api::create_purpose((object)[
1922             'name' => 'Test purpose 2', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1923         ]);
1925         // Assign purposes and categories to contexts.
1926         $coursecatctxinstance = api::set_context_instance((object) [
1927             'contextid' => $coursecatcontext->id,
1928             'purposeid' => $purpose1->get('id'),
1929             'categoryid' => $category1->get('id'),
1930         ]);
1931         $coursectxinstance = api::set_context_instance((object) [
1932             'contextid' => $coursecontext->id,
1933             'purposeid' => $purpose1->get('id'),
1934             'categoryid' => $category1->get('id'),
1935         ]);
1936         $blockctxinstance = api::set_context_instance((object) [
1937             'contextid' => $blockcontext->id,
1938             'purposeid' => $purpose1->get('id'),
1939             'categoryid' => $category1->get('id'),
1940         ]);
1941         $assignctxinstance = api::set_context_instance((object) [
1942             'contextid' => $assigncontext->id,
1943             'purposeid' => $purpose1->get('id'),
1944             'categoryid' => $category1->get('id'),
1945         ]);
1946         $forumctxinstance = api::set_context_instance((object) [
1947             'contextid' => $forumcontext->id,
1948             'purposeid' => $purpose1->get('id'),
1949             'categoryid' => $category1->get('id'),
1950         ]);
1952         $categoryid = $inheritcategory ? context_instance::INHERIT : $category2->get('id');
1953         $purposeid = $inheritpurpose ? context_instance::INHERIT : $purpose2->get('id');
1954         $activity = '';
1955         if ($contextlevel == CONTEXT_MODULE && $foractivity) {
1956             $activity = 'assign';
1957         }
1958         $result = api::set_context_defaults($contextlevel, $categoryid, $purposeid, $activity, $override);
1959         $this->assertTrue($result);
1961         $targetctxinstance = false;
1962         switch ($contextlevel) {
1963             case CONTEXT_COURSECAT:
1964                 $targetctxinstance = $coursecatctxinstance;
1965                 break;
1966             case CONTEXT_COURSE:
1967                 $targetctxinstance = $coursectxinstance;
1968                 break;
1969             case CONTEXT_MODULE:
1970                 $targetctxinstance = $assignctxinstance;
1971                 break;
1972             case CONTEXT_BLOCK:
1973                 $targetctxinstance = $blockctxinstance;
1974                 break;
1975         }
1976         $this->assertNotFalse($targetctxinstance);
1978         // Check the context instances.
1979         $instanceexists = context_instance::record_exists($targetctxinstance->get('id'));
1980         if ($override) {
1981             // If overridden, context instances on this context level would have been deleted.
1982             $this->assertFalse($instanceexists);
1984             // Check forum context instance.
1985             $forumctxexists = context_instance::record_exists($forumctxinstance->get('id'));
1986             if ($contextlevel != CONTEXT_MODULE || $foractivity) {
1987                 // The forum context instance won't be affected in this test if:
1988                 // - The overridden defaults are not for context modules.
1989                 // - Only the defaults for assign have been set.
1990                 $this->assertTrue($forumctxexists);
1991             } else {
1992                 // If we're overriding for the whole course module context level,
1993                 // then this forum context instance will be deleted as well.
1994                 $this->assertFalse($forumctxexists);
1995             }
1996         } else {
1997             // Otherwise, the context instance record remains.
1998             $this->assertTrue($instanceexists);
1999         }
2001         // Check defaults.
2002         list($defaultpurpose, $defaultcategory) = data_registry::get_defaults($contextlevel, $activity);
2003         if (!$inheritpurpose) {
2004             $this->assertEquals($purposeid, $defaultpurpose);
2005         }
2006         if (!$inheritcategory) {
2007             $this->assertEquals($categoryid, $defaultcategory);
2008         }
2009     }
2011     /**
2012      * Setup the basics with the specified retention period.
2013      *
2014      * @param   string  $system Retention policy for the system.
2015      * @param   string  $user Retention policy for users.
2016      * @param   string  $course Retention policy for courses.
2017      * @param   string  $activity Retention policy for activities.
2018      */
2019     protected function setup_basics(string $system, string $user, string $course = null, string $activity = null) : \stdClass {
2020         $this->resetAfterTest();
2022         $purposes = (object) [
2023             'system' => $this->create_and_set_purpose_for_contextlevel($system, CONTEXT_SYSTEM),
2024             'user' => $this->create_and_set_purpose_for_contextlevel($user, CONTEXT_USER),
2025         ];
2027         if (null !== $course) {
2028             $purposes->course = $this->create_and_set_purpose_for_contextlevel($course, CONTEXT_COURSE);
2029         }
2031         if (null !== $activity) {
2032             $purposes->activity = $this->create_and_set_purpose_for_contextlevel($activity, CONTEXT_MODULE);
2033         }
2035         return $purposes;
2036     }
2038     /**
2039      * Create a retention period and set it for the specified context level.
2040      *
2041      * @param   string  $retention
2042      * @param   int     $contextlevel
2043      */
2044     protected function create_and_set_purpose_for_contextlevel(string $retention, int $contextlevel) {
2045         $purpose = new purpose(0, (object) [
2046             'name' => 'Test purpose ' . rand(1, 1000),
2047             'retentionperiod' => $retention,
2048             'lawfulbases' => 'gdpr_art_6_1_a',
2049         ]);
2050         $purpose->create();
2052         $cat = new category(0, (object) ['name' => 'Test category']);
2053         $cat->create();
2055         if ($contextlevel <= CONTEXT_USER) {
2056             $record = (object) [
2057                 'purposeid'     => $purpose->get('id'),
2058                 'categoryid'    => $cat->get('id'),
2059                 'contextlevel'  => $contextlevel,
2060             ];
2061             api::set_contextlevel($record);
2062         } else {
2063             list($purposevar, ) = data_registry::var_names_from_context(
2064                     \context_helper::get_class_for_level(CONTEXT_COURSE)
2065                 );
2066             set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy');
2067         }
2069         return (object) [
2070             'purpose' => $purpose,
2071             'category' => $cat,
2072         ];
2073     }
2075     /**
2076      * Ensure that the find_ongoing_request_types_for_users only returns requests which are active.
2077      */
2078     public function test_find_ongoing_request_types_for_users() {
2079         $this->resetAfterTest();
2081         // Create users and their requests:.
2082         // - u1 has no requests of any type.
2083         // - u2 has one rejected export request.
2084         // - u3 has one rejected other request.
2085         // - u4 has one rejected delete request.
2086         // - u5 has one active and one rejected export request.
2087         // - u6 has one active and one rejected other request.
2088         // - u7 has one active and one rejected delete request.
2089         // - u8 has one active export, and one active delete request.
2090         $u1 = $this->getDataGenerator()->create_user();
2091         $u1expect = (object) [];
2093         $u2 = $this->getDataGenerator()->create_user();
2094         $this->create_request_with_type_and_status($u2->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_REJECTED);
2095         $u2expect = (object) [];
2097         $u3 = $this->getDataGenerator()->create_user();
2098         $this->create_request_with_type_and_status($u3->id, api::DATAREQUEST_TYPE_OTHERS, api::DATAREQUEST_STATUS_REJECTED);
2099         $u3expect = (object) [];
2101         $u4 = $this->getDataGenerator()->create_user();
2102         $this->create_request_with_type_and_status($u4->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_REJECTED);
2103         $u4expect = (object) [];
2105         $u5 = $this->getDataGenerator()->create_user();
2106         $this->create_request_with_type_and_status($u5->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_REJECTED);
2107         $this->create_request_with_type_and_status($u5->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_APPROVED);
2108         $u5expect = (object) [
2109             api::DATAREQUEST_TYPE_EXPORT => true,
2110         ];
2112         $u6 = $this->getDataGenerator()->create_user();
2113         $this->create_request_with_type_and_status($u6->id, api::DATAREQUEST_TYPE_OTHERS, api::DATAREQUEST_STATUS_REJECTED);
2114         $this->create_request_with_type_and_status($u6->id, api::DATAREQUEST_TYPE_OTHERS, api::DATAREQUEST_STATUS_APPROVED);
2115         $u6expect = (object) [
2116             api::DATAREQUEST_TYPE_OTHERS => true,
2117         ];
2119         $u7 = $this->getDataGenerator()->create_user();
2120         $this->create_request_with_type_and_status($u7->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_REJECTED);
2121         $this->create_request_with_type_and_status($u7->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_APPROVED);
2122         $u7expect = (object) [
2123             api::DATAREQUEST_TYPE_DELETE => true,
2124         ];
2126         $u8 = $this->getDataGenerator()->create_user();
2127         $this->create_request_with_type_and_status($u8->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_APPROVED);
2128         $this->create_request_with_type_and_status($u8->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_APPROVED);
2129         $u8expect = (object) [
2130             api::DATAREQUEST_TYPE_EXPORT => true,
2131             api::DATAREQUEST_TYPE_DELETE => true,
2132         ];
2134         // Test with no users specified.
2135         $result = api::find_ongoing_request_types_for_users([]);
2136         $this->assertEquals([], $result);
2138         // Fetch a subset of the users.
2139         $result = api::find_ongoing_request_types_for_users([$u3->id, $u4->id, $u5->id]);
2140         $this->assertEquals([
2141                 $u3->id => $u3expect,
2142                 $u4->id => $u4expect,
2143                 $u5->id => $u5expect,
2144             ], $result);
2146         // Fetch the empty user.
2147         $result = api::find_ongoing_request_types_for_users([$u1->id]);
2148         $this->assertEquals([
2149                 $u1->id => $u1expect,
2150             ], $result);
2152         // Fetch all.
2153         $result = api::find_ongoing_request_types_for_users(
2154             [$u1->id, $u2->id, $u3->id, $u4->id, $u5->id, $u6->id, $u7->id, $u8->id]);
2155         $this->assertEquals([
2156                 $u1->id => $u1expect,
2157                 $u2->id => $u2expect,
2158                 $u3->id => $u3expect,
2159                 $u4->id => $u4expect,
2160                 $u5->id => $u5expect,
2161                 $u6->id => $u6expect,
2162                 $u7->id => $u7expect,
2163                 $u8->id => $u8expect,
2164             ], $result);
2165     }
2167     /**
2168      * Create  a new data request for the user with the type and status specified.
2169      *
2170      * @param   int     $userid
2171      * @param   int     $type
2172      * @param   int     $status
2173      * @return  \tool_dataprivacy\data_request
2174      */
2175     protected function create_request_with_type_and_status(int $userid, int $type, int $status) : \tool_dataprivacy\data_request {
2176         $request = new \tool_dataprivacy\data_request(0, (object) [
2177             'userid' => $userid,
2178             'type' => $type,
2179             'status' => $status,
2180         ]);
2182         $request->save();
2184         return $request;
2185     }
2187     /**
2188      * Test user cannot create data deletion request for themselves if they don't have
2189      * "tool/dataprivacy:requestdelete" capability.
2190      *
2191      * @throws coding_exception
2192      */
2193     public function test_can_create_data_deletion_request_for_self_no() {
2194         $this->resetAfterTest();
2195         $userid = $this->getDataGenerator()->create_user()->id;
2196         $roleid = $this->getDataGenerator()->create_role();
2197         assign_capability('tool/dataprivacy:requestdelete', CAP_PROHIBIT, $roleid, context_user::instance($userid));
2198         role_assign($roleid, $userid, context_user::instance($userid));
2199         $this->setUser($userid);
2200         $this->assertFalse(api::can_create_data_deletion_request_for_self());
2201     }
2203     /**
2204      * Test primary admin cannot create data deletion request for themselves
2205      */
2206     public function test_can_create_data_deletion_request_for_self_primary_admin() {
2207         $this->resetAfterTest();
2208         $this->setAdminUser();
2209         $this->assertFalse(api::can_create_data_deletion_request_for_self());
2210     }
2212     /**
2213      * Test secondary admin can create data deletion request for themselves
2214      */
2215     public function test_can_create_data_deletion_request_for_self_secondary_admin() {
2216         $this->resetAfterTest();
2218         $admin1 = $this->getDataGenerator()->create_user();
2219         $admin2 = $this->getDataGenerator()->create_user();
2221         // The primary admin is the one listed first in the 'siteadmins' config.
2222         set_config('siteadmins', implode(',', [$admin1->id, $admin2->id]));
2224         // Set the current user as the second admin (non-primary).
2225         $this->setUser($admin2);
2227         $this->assertTrue(api::can_create_data_deletion_request_for_self());
2228     }
2230     /**
2231      * Test user can create data deletion request for themselves if they have
2232      * "tool/dataprivacy:requestdelete" capability.
2233      *
2234      * @throws coding_exception
2235      */
2236     public function test_can_create_data_deletion_request_for_self_yes() {
2237         $this->resetAfterTest();
2238         $userid = $this->getDataGenerator()->create_user()->id;
2239         $this->setUser($userid);
2240         $this->assertTrue(api::can_create_data_deletion_request_for_self());
2241     }
2243     /**
2244      * Test user cannot create data deletion request for another user if they
2245      * don't have "tool/dataprivacy:requestdeleteforotheruser" capability.
2246      *
2247      * @throws coding_exception
2248      * @throws dml_exception
2249      */
2250     public function test_can_create_data_deletion_request_for_other_no() {
2251         $this->resetAfterTest();
2252         $userid = $this->getDataGenerator()->create_user()->id;
2253         $this->setUser($userid);
2254         $this->assertFalse(api::can_create_data_deletion_request_for_other());
2255     }
2257     /**
2258      * Test user can create data deletion request for another user if they
2259      * don't have "tool/dataprivacy:requestdeleteforotheruser" capability.
2260      *
2261      * @throws coding_exception
2262      */
2263     public function test_can_create_data_deletion_request_for_other_yes() {
2264         $this->resetAfterTest();
2265         $userid = $this->getDataGenerator()->create_user()->id;
2266         $roleid = $this->getDataGenerator()->create_role();
2267         $contextsystem = context_system::instance();
2268         assign_capability('tool/dataprivacy:requestdeleteforotheruser', CAP_ALLOW, $roleid, $contextsystem);
2269         role_assign($roleid, $userid, $contextsystem);
2270         $this->setUser($userid);
2271         $this->assertTrue(api::can_create_data_deletion_request_for_other($userid));
2272     }
2274     /**
2275      * Check parents can create data deletion request for their children (unless the child is the primary admin),
2276      * but not other users.
2277      *
2278      * @throws coding_exception
2279      * @throws dml_exception
2280      */
2281     public function test_can_create_data_deletion_request_for_children() {
2282         $this->resetAfterTest();
2284         $parent = $this->getDataGenerator()->create_user();
2285         $child = $this->getDataGenerator()->create_user();
2286         $otheruser = $this->getDataGenerator()->create_user();
2288         $contextsystem = \context_system::instance();
2289         $parentrole = $this->getDataGenerator()->create_role();
2290         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW,
2291             $parentrole, $contextsystem);
2292         assign_capability('tool/dataprivacy:makedatadeletionrequestsforchildren', CAP_ALLOW,
2293             $parentrole, $contextsystem);
2294         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
2296         $this->setUser($parent);
2297         $this->assertTrue(api::can_create_data_deletion_request_for_children($child->id));
2298         $this->assertFalse(api::can_create_data_deletion_request_for_children($otheruser->id));
2300         // Now make child the primary admin, confirm parent can't make deletion request.
2301         set_config('siteadmins', $child->id);
2302         $this->assertFalse(api::can_create_data_deletion_request_for_children($child->id));
2303     }
2305     /**
2306      * Data provider function for testing \tool_dataprivacy\api::queue_data_request_task().
2307      *
2308      * @return array
2309      */
2310     public function queue_data_request_task_provider() {
2311         return [
2312             'With user ID provided' => [true],
2313             'Without user ID provided' => [false],
2314         ];
2315     }
2317     /**
2318      * Test for \tool_dataprivacy\api::queue_data_request_task().
2319      *
2320      * @dataProvider queue_data_request_task_provider
2321      * @param bool $withuserid
2322      */
2323     public function test_queue_data_request_task(bool $withuserid) {
2324         $this->resetAfterTest();
2326         $this->setAdminUser();
2328         if ($withuserid) {
2329             $user = $this->getDataGenerator()->create_user();
2330             api::queue_data_request_task(1, $user->id);
2331             $expecteduserid = $user->id;
2332         } else {
2333             api::queue_data_request_task(1);
2334             $expecteduserid = null;
2335         }
2337         // Test number of queued data request tasks.
2338         $datarequesttasks = manager::get_adhoc_tasks(process_data_request_task::class);
2339         $this->assertCount(1, $datarequesttasks);
2340         $requesttask = reset($datarequesttasks);
2341         $this->assertEquals($expecteduserid, $requesttask->get_userid());
2342     }
2344     /**
2345      * Data provider for test_is_automatic_request_approval_on().
2346      */
2347     public function automatic_request_approval_setting_provider() {
2348         return [
2349             'Data export, not set' => [
2350                 'automaticdataexportapproval', api::DATAREQUEST_TYPE_EXPORT, null, false
2351             ],
2352             'Data export, turned on' => [
2353                 'automaticdataexportapproval', api::DATAREQUEST_TYPE_EXPORT, true, true
2354             ],
2355             'Data export, turned off' => [
2356                 'automaticdataexportapproval', api::DATAREQUEST_TYPE_EXPORT, false, false
2357             ],
2358             'Data deletion, not set' => [
2359                 'automaticdatadeletionapproval', api::DATAREQUEST_TYPE_DELETE, null, false
2360             ],
2361             'Data deletion, turned on' => [
2362                 'automaticdatadeletionapproval', api::DATAREQUEST_TYPE_DELETE, true, true
2363             ],
2364             'Data deletion, turned off' => [
2365                 'automaticdatadeletionapproval', api::DATAREQUEST_TYPE_DELETE, false, false
2366             ],
2367         ];
2368     }
2370     /**
2371      * Test for \tool_dataprivacy\api::is_automatic_request_approval_on().
2372      *
2373      * @dataProvider automatic_request_approval_setting_provider
2374      * @param string $setting The automatic approval setting.
2375      * @param int $type The data request type.
2376      * @param bool $value The setting's value.
2377      * @param bool $expected The expected result.
2378      */
2379     public function test_is_automatic_request_approval_on($setting, $type, $value, $expected) {
2380         $this->resetAfterTest();
2382         if ($value !== null) {
2383             set_config($setting, $value, 'tool_dataprivacy');
2384         }
2386         $this->assertEquals($expected, api::is_automatic_request_approval_on($type));
2387     }