Merge branch 'MDL-66752-master-2' of git://github.com/junpataleta/moodle
[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');
305         // Login as a user without DPO role.
306         $this->setUser($teacher);
307         $this->expectException(required_capability_exception::class);
308         api::approve_data_request($requestid);
309     }
311     /**
312      * Test that deletion requests for the primary admin are rejected
313      */
314     public function test_reject_data_deletion_request_primary_admin() {
315         $this->resetAfterTest();
316         $this->setAdminUser();
318         $datarequest = api::create_data_request(get_admin()->id, api::DATAREQUEST_TYPE_DELETE);
320         // Approve the request and execute the ad-hoc process task.
321         ob_start();
322         api::approve_data_request($datarequest->get('id'));
323         $this->runAdhocTasks('\tool_dataprivacy\task\process_data_request_task');
324         ob_end_clean();
326         $request = api::get_request($datarequest->get('id'));
327         $this->assertEquals(api::DATAREQUEST_STATUS_REJECTED, $request->get('status'));
329         // Confirm they weren't deleted.
330         $user = core_user::get_user($request->get('userid'));
331         core_user::require_active_user($user);
332     }
334     /**
335      * Test for api::can_contact_dpo()
336      */
337     public function test_can_contact_dpo() {
338         $this->resetAfterTest();
340         // Default ('contactdataprotectionofficer' is disabled by default).
341         $this->assertFalse(api::can_contact_dpo());
343         // Enable.
344         set_config('contactdataprotectionofficer', 1, 'tool_dataprivacy');
345         $this->assertTrue(api::can_contact_dpo());
347         // Disable again.
348         set_config('contactdataprotectionofficer', 0, 'tool_dataprivacy');
349         $this->assertFalse(api::can_contact_dpo());
350     }
352     /**
353      * Test for api::can_manage_data_requests()
354      */
355     public function test_can_manage_data_requests() {
356         global $DB;
358         $this->resetAfterTest();
360         // No configured site DPOs yet.
361         $admin = get_admin();
362         $this->assertTrue(api::can_manage_data_requests($admin->id));
364         $generator = new testing_data_generator();
365         $dpo = $generator->create_user();
366         $nondpocapable = $generator->create_user();
367         $nondpoincapable = $generator->create_user();
369         $context = context_system::instance();
371         // Manager role.
372         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
373         // Give the manager role with the capability to manage data requests.
374         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
375         // Assign u1 as a manager.
376         role_assign($managerroleid, $dpo->id, $context->id);
378         // Editing teacher role.
379         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
380         // Give the editing teacher role with the capability to manage data requests.
381         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
382         // Assign u2 as an editing teacher.
383         role_assign($editingteacherroleid, $nondpocapable->id, $context->id);
385         // Map only the manager role to the DPO role.
386         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
388         // User with capability and has DPO role.
389         $this->assertTrue(api::can_manage_data_requests($dpo->id));
390         // User with capability but has no DPO role.
391         $this->assertFalse(api::can_manage_data_requests($nondpocapable->id));
392         // User without the capability and has no DPO role.
393         $this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
394     }
396     /**
397      * Test that a user who has no capability to make any data requests for children cannot create data requests for any
398      * other user.
399      */
400     public function test_can_create_data_request_for_user_no() {
401         $this->resetAfterTest();
403         $parent = $this->getDataGenerator()->create_user();
404         $otheruser = $this->getDataGenerator()->create_user();
406         $this->setUser($parent);
407         $this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
408     }
410     /**
411      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
412      * for any other user.
413      */
414     public function test_can_create_data_request_for_user_some() {
415         $this->resetAfterTest();
417         $parent = $this->getDataGenerator()->create_user();
418         $child = $this->getDataGenerator()->create_user();
419         $otheruser = $this->getDataGenerator()->create_user();
421         $systemcontext = \context_system::instance();
422         $parentrole = $this->getDataGenerator()->create_role();
423         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
424         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
426         $this->setUser($parent);
427         $this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
428     }
430     /**
431      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
432      * for any other user.
433      */
434     public function test_can_create_data_request_for_user_own_child() {
435         $this->resetAfterTest();
437         $parent = $this->getDataGenerator()->create_user();
438         $child = $this->getDataGenerator()->create_user();
440         $systemcontext = \context_system::instance();
441         $parentrole = $this->getDataGenerator()->create_role();
442         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
443         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
445         $this->setUser($parent);
446         $this->assertTrue(api::can_create_data_request_for_user($child->id));
447     }
449     /**
450      * Test that a user who has no capability to make any data requests for children cannot create data requests for any
451      * other user.
452      */
453     public function test_require_can_create_data_request_for_user_no() {
454         $this->resetAfterTest();
456         $parent = $this->getDataGenerator()->create_user();
457         $otheruser = $this->getDataGenerator()->create_user();
459         $this->setUser($parent);
460         $this->expectException('required_capability_exception');
461         api::require_can_create_data_request_for_user($otheruser->id);
462     }
464     /**
465      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
466      * for any other user.
467      */
468     public function test_require_can_create_data_request_for_user_some() {
469         $this->resetAfterTest();
471         $parent = $this->getDataGenerator()->create_user();
472         $child = $this->getDataGenerator()->create_user();
473         $otheruser = $this->getDataGenerator()->create_user();
475         $systemcontext = \context_system::instance();
476         $parentrole = $this->getDataGenerator()->create_role();
477         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
478         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
480         $this->setUser($parent);
481         $this->expectException('required_capability_exception');
482         api::require_can_create_data_request_for_user($otheruser->id);
483     }
485     /**
486      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
487      * for any other user.
488      */
489     public function test_require_can_create_data_request_for_user_own_child() {
490         $this->resetAfterTest();
492         $parent = $this->getDataGenerator()->create_user();
493         $child = $this->getDataGenerator()->create_user();
495         $systemcontext = \context_system::instance();
496         $parentrole = $this->getDataGenerator()->create_role();
497         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
498         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
500         $this->setUser($parent);
501         $this->assertTrue(api::require_can_create_data_request_for_user($child->id));
502     }
504     /**
505      * Test for api::can_download_data_request_for_user()
506      */
507     public function test_can_download_data_request_for_user() {
508         $this->resetAfterTest();
510         $generator = $this->getDataGenerator();
512         // Three victims.
513         $victim1 = $generator->create_user();
514         $victim2 = $generator->create_user();
515         $victim3 = $generator->create_user();
517         // Assign a user as victim 1's parent.
518         $systemcontext = \context_system::instance();
519         $parentrole = $generator->create_role();
520         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
521         $parent = $generator->create_user();
522         role_assign($parentrole, $parent->id, \context_user::instance($victim1->id));
524         // Assign another user as data access wonder woman.
525         $wonderrole = $generator->create_role();
526         assign_capability('tool/dataprivacy:downloadallrequests', CAP_ALLOW, $wonderrole, $systemcontext);
527         $staff = $generator->create_user();
528         role_assign($wonderrole, $staff->id, $systemcontext);
530         // Finally, victim 3 has been naughty; stop them accessing their own data.
531         $naughtyrole = $generator->create_role();
532         assign_capability('tool/dataprivacy:downloadownrequest', CAP_PROHIBIT, $naughtyrole, $systemcontext);
533         role_assign($naughtyrole, $victim3->id, $systemcontext);
535         // Victims 1 and 2 can access their own data, regardless of who requested it.
536         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $victim1->id));
537         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $victim2->id));
539         // Victim 3 cannot access his own data.
540         $this->assertFalse(api::can_download_data_request_for_user($victim3->id, $victim3->id, $victim3->id));
542         // Victims 1 and 2 cannot access another victim's data.
543         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $victim1->id, $victim1->id));
544         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $victim2->id));
546         // Staff can access everyone's data.
547         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $staff->id));
548         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $staff->id));
549         $this->assertTrue(api::can_download_data_request_for_user($victim3->id, $staff->id, $staff->id));
551         // Parent can access victim 1's data only if they requested it.
552         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $parent->id, $parent->id));
553         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $parent->id));
554         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $parent->id, $parent->id));
555     }
557     /**
558      * Data provider for data request creation tests.
559      *
560      * @return array
561      */
562     public function data_request_creation_provider() {
563         return [
564             'Export request by user, automatic approval off' => [
565                 false, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', false, 0,
566                 api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
567             ],
568             'Export request by user, automatic approval on' => [
569                 false, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', true, 0,
570                 api::DATAREQUEST_STATUS_APPROVED, 1
571             ],
572             'Export request by PO, automatic approval off' => [
573                 true, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', false, 0,
574                 api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
575             ],
576             'Export request by PO, automatic approval on' => [
577                 true, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', true, 'dpo',
578                 api::DATAREQUEST_STATUS_APPROVED, 1
579             ],
580             'Delete request by user, automatic approval off' => [
581                 false, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', false, 0,
582                 api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
583             ],
584             'Delete request by user, automatic approval on' => [
585                 false, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', true, 0,
586                 api::DATAREQUEST_STATUS_APPROVED, 1
587             ],
588             'Delete request by PO, automatic approval off' => [
589                 true, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', false, 0,
590                 api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
591             ],
592             'Delete request by PO, automatic approval on' => [
593                 true, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', true, 'dpo',
594                 api::DATAREQUEST_STATUS_APPROVED, 1
595             ],
596         ];
597     }
599     /**
600      * Test for api::create_data_request()
601      *
602      * @dataProvider data_request_creation_provider
603      * @param bool $asprivacyofficer Whether the request is made as the Privacy Officer or the user itself.
604      * @param string $type The data request type.
605      * @param string $setting The automatic approval setting.
606      * @param bool $automaticapproval Whether automatic data request approval is turned on or not.
607      * @param int|string $expecteddpoval The expected value for the 'dpo' field. 'dpo' means we'd the expected value would be the
608      *                                   user ID of the privacy officer which happens in the case where a PO requests on behalf of
609      *                                   someone else and automatic data request approval is turned on.
610      * @param int $expectedstatus The expected status of the data request.
611      * @param int $expectedtaskcount The number of expected queued data requests tasks.
612      * @throws coding_exception
613      * @throws invalid_persistent_exception
614      */
615     public function test_create_data_request($asprivacyofficer, $type, $setting, $automaticapproval, $expecteddpoval,
616                                              $expectedstatus, $expectedtaskcount) {
617         global $USER;
619         $this->resetAfterTest();
621         $generator = new testing_data_generator();
622         $user = $generator->create_user();
623         $comment = 'sample comment';
625         // Login.
626         if ($asprivacyofficer) {
627             $this->setAdminUser();
628         } else {
629             $this->setUser($user->id);
630         }
632         // Set the automatic data request approval setting value.
633         set_config($setting, $automaticapproval, 'tool_dataprivacy');
635         // If set to 'dpo' use the currently logged-in user's ID (which should be the admin user's ID).
636         if ($expecteddpoval === 'dpo') {
637             $expecteddpoval = $USER->id;
638         }
640         // Test data request creation.
641         $datarequest = api::create_data_request($user->id, $type, $comment);
642         $this->assertEquals($user->id, $datarequest->get('userid'));
643         $this->assertEquals($USER->id, $datarequest->get('requestedby'));
644         $this->assertEquals($expecteddpoval, $datarequest->get('dpo'));
645         $this->assertEquals($type, $datarequest->get('type'));
646         $this->assertEquals($expectedstatus, $datarequest->get('status'));
647         $this->assertEquals($comment, $datarequest->get('comments'));
648         $this->assertEquals($automaticapproval, $datarequest->get('systemapproved'));
650         // Test number of queued data request tasks.
651         $datarequesttasks = manager::get_adhoc_tasks(process_data_request_task::class);
652         $this->assertCount($expectedtaskcount, $datarequesttasks);
653     }
655     /**
656      * Test for api::create_data_request() made by a parent.
657      */
658     public function test_create_data_request_by_parent() {
659         global $DB;
661         $this->resetAfterTest();
663         $generator = new testing_data_generator();
664         $user = $generator->create_user();
665         $parent = $generator->create_user();
666         $comment = 'sample comment';
668         // Get the teacher role pretend it's the parent roles ;).
669         $systemcontext = context_system::instance();
670         $usercontext = context_user::instance($user->id);
671         $parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
672         // Give the manager role with the capability to manage data requests.
673         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
674         // Assign the parent to user.
675         role_assign($parentroleid, $parent->id, $usercontext->id);
677         // Login as the user's parent.
678         $this->setUser($parent);
680         // Test data request creation.
681         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
682         $this->assertEquals($user->id, $datarequest->get('userid'));
683         $this->assertEquals($parent->id, $datarequest->get('requestedby'));
684         $this->assertEquals(0, $datarequest->get('dpo'));
685         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
686         $this->assertEquals(api::DATAREQUEST_STATUS_AWAITING_APPROVAL, $datarequest->get('status'));
687         $this->assertEquals($comment, $datarequest->get('comments'));
688     }
690     /**
691      * Test for api::deny_data_request()
692      */
693     public function test_deny_data_request() {
694         $this->resetAfterTest();
696         $generator = new testing_data_generator();
697         $user = $generator->create_user();
698         $comment = 'sample comment';
700         // Login as user.
701         $this->setUser($user->id);
703         // Test data request creation.
704         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
706         // Login as the admin (default DPO when no one is set).
707         $this->setAdminUser();
709         // Make this ready for approval.
710         api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
712         // Deny the data request.
713         $result = api::deny_data_request($datarequest->get('id'));
714         $this->assertTrue($result);
715     }
717     /**
718      * Data provider for \tool_dataprivacy_api_testcase::test_get_data_requests().
719      *
720      * @return array
721      */
722     public function get_data_requests_provider() {
723         $completeonly = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DOWNLOAD_READY, api::DATAREQUEST_STATUS_DELETED];
724         $completeandcancelled = array_merge($completeonly, [api::DATAREQUEST_STATUS_CANCELLED]);
726         return [
727             // Own data requests.
728             ['user', false, $completeonly],
729             // Non-DPO fetching all requets.
730             ['user', true, $completeonly],
731             // Admin fetching all completed and cancelled requests.
732             ['dpo', true, $completeandcancelled],
733             // Admin fetching all completed requests.
734             ['dpo', true, $completeonly],
735             // Guest fetching all requests.
736             ['guest', true, $completeonly],
737         ];
738     }
740     /**
741      * Test for api::get_data_requests()
742      *
743      * @dataProvider get_data_requests_provider
744      * @param string $usertype The type of the user logging in.
745      * @param boolean $fetchall Whether to fetch all records.
746      * @param int[] $statuses Status filters.
747      */
748     public function test_get_data_requests($usertype, $fetchall, $statuses) {
749         $this->resetAfterTest();
751         $generator = new testing_data_generator();
752         $user1 = $generator->create_user();
753         $user2 = $generator->create_user();
754         $user3 = $generator->create_user();
755         $user4 = $generator->create_user();
756         $user5 = $generator->create_user();
757         $users = [$user1, $user2, $user3, $user4, $user5];
759         switch ($usertype) {
760             case 'user':
761                 $loggeduser = $user1;
762                 break;
763             case 'dpo':
764                 $loggeduser = get_admin();
765                 break;
766             case 'guest':
767                 $loggeduser = guest_user();
768                 break;
769         }
771         $comment = 'Data %s request comment by user %d';
772         $exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
773         $deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
774         // Make a data requests for the users.
775         foreach ($users as $user) {
776             $this->setUser($user);
777             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $exportstring, $user->id));
778             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $deletionstring, $user->id));
779         }
781         // Log in as the target user.
782         $this->setUser($loggeduser);
783         // Get records count based on the filters.
784         $userid = $loggeduser->id;
785         if ($fetchall) {
786             $userid = 0;
787         }
788         $count = api::get_data_requests_count($userid);
789         if (api::is_site_dpo($loggeduser->id)) {
790             // DPOs should see all the requests.
791             $this->assertEquals(count($users) * 2, $count);
792         } else {
793             if (empty($userid)) {
794                 // There should be no data requests for this user available.
795                 $this->assertEquals(0, $count);
796             } else {
797                 // There should be only one (request with pending status).
798                 $this->assertEquals(2, $count);
799             }
800         }
801         // Get data requests.
802         $requests = api::get_data_requests($userid);
803         // The number of requests should match the count.
804         $this->assertCount($count, $requests);
806         // Test filtering by status.
807         if ($count && !empty($statuses)) {
808             $filteredcount = api::get_data_requests_count($userid, $statuses);
809             // There should be none as they are all pending.
810             $this->assertEquals(0, $filteredcount);
811             $filteredrequests = api::get_data_requests($userid, $statuses);
812             $this->assertCount($filteredcount, $filteredrequests);
814             $statuscounts = [];
815             foreach ($statuses as $stat) {
816                 $statuscounts[$stat] = 0;
817             }
818             $numstatus = count($statuses);
819             // Get all requests with status filter and update statuses, randomly.
820             foreach ($requests as $request) {
821                 if (rand(0, 1)) {
822                     continue;
823                 }
825                 if ($numstatus > 1) {
826                     $index = rand(0, $numstatus - 1);
827                     $status = $statuses[$index];
828                 } else {
829                     $status = reset($statuses);
830                 }
831                 $statuscounts[$status]++;
832                 api::update_request_status($request->get('id'), $status);
833             }
834             $total = array_sum($statuscounts);
835             $filteredcount = api::get_data_requests_count($userid, $statuses);
836             $this->assertEquals($total, $filteredcount);
837             $filteredrequests = api::get_data_requests($userid, $statuses);
838             $this->assertCount($filteredcount, $filteredrequests);
839             // Confirm the filtered requests match the status filter(s).
840             foreach ($filteredrequests as $request) {
841                 $this->assertContains($request->get('status'), $statuses);
842             }
844             if ($numstatus > 1) {
845                 // Fetch by individual status to check the numbers match.
846                 foreach ($statuses as $status) {
847                     $filteredcount = api::get_data_requests_count($userid, [$status]);
848                     $this->assertEquals($statuscounts[$status], $filteredcount);
849                     $filteredrequests = api::get_data_requests($userid, [$status]);
850                     $this->assertCount($filteredcount, $filteredrequests);
851                 }
852             }
853         }
854     }
856     /**
857      * Data provider for test_has_ongoing_request.
858      */
859     public function status_provider() {
860         return [
861             [api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
862             [api::DATAREQUEST_STATUS_APPROVED, true],
863             [api::DATAREQUEST_STATUS_PROCESSING, true],
864             [api::DATAREQUEST_STATUS_COMPLETE, false],
865             [api::DATAREQUEST_STATUS_CANCELLED, false],
866             [api::DATAREQUEST_STATUS_REJECTED, false],
867             [api::DATAREQUEST_STATUS_DOWNLOAD_READY, false],
868             [api::DATAREQUEST_STATUS_EXPIRED, false],
869             [api::DATAREQUEST_STATUS_DELETED, false],
870         ];
871     }
873     /**
874      * Test for api::has_ongoing_request()
875      *
876      * @dataProvider status_provider
877      * @param int $status The request status.
878      * @param bool $expected The expected result.
879      */
880     public function test_has_ongoing_request($status, $expected) {
881         $this->resetAfterTest();
883         $generator = new testing_data_generator();
884         $user1 = $generator->create_user();
886         // Make a data request as user 1.
887         $this->setUser($user1);
888         $request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
889         // Set the status.
890         api::update_request_status($request->get('id'), $status);
892         // Check if this request is ongoing.
893         $result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
894         $this->assertEquals($expected, $result);
895     }
897     /**
898      * Test for api::is_active()
899      *
900      * @dataProvider status_provider
901      * @param int $status The request status
902      * @param bool $expected The expected result
903      */
904     public function test_is_active($status, $expected) {
905         // Check if this request is ongoing.
906         $result = api::is_active($status);
907         $this->assertEquals($expected, $result);
908     }
910     /**
911      * Test for api::is_site_dpo()
912      */
913     public function test_is_site_dpo() {
914         global $DB;
916         $this->resetAfterTest();
918         // No configured site DPOs yet.
919         $admin = get_admin();
920         $this->assertTrue(api::is_site_dpo($admin->id));
922         $generator = new testing_data_generator();
923         $dpo = $generator->create_user();
924         $nondpo = $generator->create_user();
926         $context = context_system::instance();
928         // Manager role.
929         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
930         // Give the manager role with the capability to manage data requests.
931         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
932         // Assign u1 as a manager.
933         role_assign($managerroleid, $dpo->id, $context->id);
935         // Map only the manager role to the DPO role.
936         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
938         // User is a DPO.
939         $this->assertTrue(api::is_site_dpo($dpo->id));
940         // User is not a DPO.
941         $this->assertFalse(api::is_site_dpo($nondpo->id));
942     }
944     /**
945      * Data provider function for test_notify_dpo
946      *
947      * @return array
948      */
949     public function notify_dpo_provider() {
950         return [
951             [false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
952             [false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
953             [false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
954             [true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
955         ];
956     }
958     /**
959      * Test for api::notify_dpo()
960      *
961      * @dataProvider notify_dpo_provider
962      * @param bool $byadmin Whether the admin requests data on behalf of the user
963      * @param int $type The request type
964      * @param string $typestringid The request lang string identifier
965      * @param string $comments The requestor's message to the DPO.
966      */
967     public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
968         $this->resetAfterTest();
970         $generator = new testing_data_generator();
971         $user1 = $generator->create_user();
972         // Let's just use admin as DPO (It's the default if not set).
973         $dpo = get_admin();
974         if ($byadmin) {
975             $this->setAdminUser();
976             $requestedby = $dpo;
977         } else {
978             $this->setUser($user1);
979             $requestedby = $user1;
980         }
982         // Make a data request for user 1.
983         $request = api::create_data_request($user1->id, $type, $comments);
985         $sink = $this->redirectMessages();
986         $messageid = api::notify_dpo($dpo, $request);
987         $this->assertNotFalse($messageid);
988         $messages = $sink->get_messages();
989         $this->assertCount(1, $messages);
990         $message = reset($messages);
992         // Check some of the message properties.
993         $this->assertEquals($requestedby->id, $message->useridfrom);
994         $this->assertEquals($dpo->id, $message->useridto);
995         $typestring = get_string($typestringid, 'tool_dataprivacy');
996         $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
997         $this->assertEquals($subject, $message->subject);
998         $this->assertEquals('tool_dataprivacy', $message->component);
999         $this->assertEquals('contactdataprotectionofficer', $message->eventtype);
1000         $this->assertContains(fullname($dpo), $message->fullmessage);
1001         $this->assertContains(fullname($user1), $message->fullmessage);
1002     }
1004     /**
1005      * Test data purposes CRUD actions.
1006      *
1007      * @return null
1008      */
1009     public function test_purpose_crud() {
1010         $this->resetAfterTest();
1012         $this->setAdminUser();
1014         // Add.
1015         $purpose = api::create_purpose((object)[
1016             'name' => 'bbb',
1017             'description' => '<b>yeah</b>',
1018             'descriptionformat' => 1,
1019             'retentionperiod' => 'PT1M',
1020             'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
1021         ]);
1022         $this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
1023         $this->assertEquals('bbb', $purpose->get('name'));
1024         $this->assertEquals('PT1M', $purpose->get('retentionperiod'));
1025         $this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
1027         // Update.
1028         $purpose->set('retentionperiod', 'PT2M');
1029         $purpose = api::update_purpose($purpose->to_record());
1030         $this->assertEquals('PT2M', $purpose->get('retentionperiod'));
1032         // Retrieve.
1033         $purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1034         $purposes = api::get_purposes();
1035         $this->assertCount(2, $purposes);
1036         $this->assertEquals('aaa', $purposes[0]->get('name'));
1037         $this->assertEquals('bbb', $purposes[1]->get('name'));
1039         // Delete.
1040         api::delete_purpose($purposes[0]->get('id'));
1041         $this->assertCount(1, api::get_purposes());
1042         api::delete_purpose($purposes[1]->get('id'));
1043         $this->assertCount(0, api::get_purposes());
1044     }
1046     /**
1047      * Test data categories CRUD actions.
1048      *
1049      * @return null
1050      */
1051     public function test_category_crud() {
1052         $this->resetAfterTest();
1054         $this->setAdminUser();
1056         // Add.
1057         $category = api::create_category((object)[
1058             'name' => 'bbb',
1059             'description' => '<b>yeah</b>',
1060             'descriptionformat' => 1
1061         ]);
1062         $this->assertInstanceOf('\tool_dataprivacy\category', $category);
1063         $this->assertEquals('bbb', $category->get('name'));
1065         // Update.
1066         $category->set('name', 'bcd');
1067         $category = api::update_category($category->to_record());
1068         $this->assertEquals('bcd', $category->get('name'));
1070         // Retrieve.
1071         $category = api::create_category((object)['name' => 'aaa']);
1072         $categories = api::get_categories();
1073         $this->assertCount(2, $categories);
1074         $this->assertEquals('aaa', $categories[0]->get('name'));
1075         $this->assertEquals('bcd', $categories[1]->get('name'));
1077         // Delete.
1078         api::delete_category($categories[0]->get('id'));
1079         $this->assertCount(1, api::get_categories());
1080         api::delete_category($categories[1]->get('id'));
1081         $this->assertCount(0, api::get_categories());
1082     }
1084     /**
1085      * Test context instances.
1086      *
1087      * @return null
1088      */
1089     public function test_context_instances() {
1090         global $DB;
1092         $this->resetAfterTest();
1094         $this->setAdminUser();
1096         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1098         $coursecontext1 = \context_course::instance($courses[0]->id);
1099         $coursecontext2 = \context_course::instance($courses[1]->id);
1101         $record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
1102             'categoryid' => $categories[0]->get('id')];
1103         $contextinstance1 = api::set_context_instance($record1);
1105         $record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
1106             'categoryid' => $categories[1]->get('id')];
1107         $contextinstance2 = api::set_context_instance($record2);
1109         $this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
1111         api::unset_context_instance($contextinstance1);
1112         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1114         $update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
1115             'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
1116         $contextinstance2 = api::set_context_instance($update);
1117         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1118     }
1120     /**
1121      * Test contextlevel.
1122      *
1123      * @return null
1124      */
1125     public function test_contextlevel() {
1126         global $DB;
1128         $this->resetAfterTest();
1130         $this->setAdminUser();
1131         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1133         $record = (object)[
1134             'purposeid' => $purposes[0]->get('id'),
1135             'categoryid' => $categories[0]->get('id'),
1136             'contextlevel' => CONTEXT_SYSTEM,
1137         ];
1138         $contextlevel = api::set_contextlevel($record);
1139         $this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
1140         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1141         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1142         $this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
1144         // Now update it.
1145         $record->purposeid = $purposes[1]->get('id');
1146         $contextlevel = api::set_contextlevel($record);
1147         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1148         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1149         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
1151         $record->contextlevel = CONTEXT_USER;
1152         $contextlevel = api::set_contextlevel($record);
1153         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
1154     }
1156     /**
1157      * Test effective context levels purpose and category defaults.
1158      *
1159      * @return null
1160      */
1161     public function test_effective_contextlevel_defaults() {
1162         $this->setAdminUser();
1164         $this->resetAfterTest();
1166         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1168         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1169         $this->assertEquals(false, $purposeid);
1170         $this->assertEquals(false, $categoryid);
1172         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1173             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1174         );
1175         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1177         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1178         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1179         $this->assertEquals(false, $categoryid);
1181         // Course defined values should have preference.
1182         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1183             \context_helper::get_class_for_level(CONTEXT_COURSE)
1184         );
1185         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1186         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1188         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1189         $this->assertEquals($purposes[1]->get('id'), $purposeid);
1190         $this->assertEquals($categories[0]->get('id'), $categoryid);
1192         // Context level defaults are also allowed to be set to 'inherit'.
1193         set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
1194     }
1196     /**
1197      * Ensure that when nothing is configured, all values return false.
1198      */
1199     public function test_get_effective_contextlevel_unset() {
1200         // Before setup, get_effective_contextlevel_purpose will return false.
1201         $this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
1202         $this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
1204         $this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_USER));
1205         $this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_USER));
1206     }
1208     /**
1209      * Ensure that when nothing is configured, all values return false.
1210      */
1211     public function test_get_effective_context_unset() {
1212         // Before setup, get_effective_contextlevel_purpose will return false.
1213         $this->assertFalse(api::get_effective_context_category(\context_system::instance()));
1214         $this->assertFalse(api::get_effective_context_purpose(\context_system::instance()));
1215     }
1217     /**
1218      * Ensure that fetching the effective value for context levels is only available to system, and user context levels.
1219      *
1220      * @dataProvider invalid_effective_contextlevel_provider
1221      * @param   int $contextlevel
1222      */
1223     public function test_set_contextlevel_invalid_contextlevels($contextlevel) {
1225         $this->expectException(coding_exception::class);
1226         api::set_contextlevel((object) [
1227                 'contextlevel' => $contextlevel,
1228             ]);
1230     }
1232     /**
1233      * Test effective contextlevel return.
1234      */
1235     public function test_effective_contextlevel() {
1236         $this->resetAfterTest();
1238         // Set the initial purpose and category.
1239         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1240         $category1 = api::create_category((object)['name' => 'a']);
1241         api::set_contextlevel((object)[
1242             'contextlevel' => CONTEXT_SYSTEM,
1243             'purposeid' => $purpose1->get('id'),
1244             'categoryid' => $category1->get('id'),
1245         ]);
1247         $this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
1248         $this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
1250         // The user context inherits from the system context when not set.
1251         $this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_USER));
1252         $this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_USER));
1254         // Forcing the behaviour to inherit will have the same result.
1255         api::set_contextlevel((object) [
1256                 'contextlevel' => CONTEXT_USER,
1257                 'purposeid' => context_instance::INHERIT,
1258                 'categoryid' => context_instance::INHERIT,
1259             ]);
1260         $this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_USER));
1261         $this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_USER));
1263         // Setting specific values will override the inheritance behaviour.
1264         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1265         $category2 = api::create_category((object)['name' => 'b']);
1266         // Set the system context level to purpose 1.
1267         api::set_contextlevel((object) [
1268                 'contextlevel' => CONTEXT_USER,
1269                 'purposeid' => $purpose2->get('id'),
1270                 'categoryid' => $category2->get('id'),
1271             ]);
1273         $this->assertEquals($purpose2, api::get_effective_contextlevel_purpose(CONTEXT_USER));
1274         $this->assertEquals($category2, api::get_effective_contextlevel_category(CONTEXT_USER));
1275     }
1277     /**
1278      * Ensure that fetching the effective value for context levels is only available to system, and user context levels.
1279      *
1280      * @dataProvider invalid_effective_contextlevel_provider
1281      * @param   int $contextlevel
1282      */
1283     public function test_effective_contextlevel_invalid_contextlevels($contextlevel) {
1284         $this->resetAfterTest();
1286         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1287         $category1 = api::create_category((object)['name' => 'a']);
1288         api::set_contextlevel((object)[
1289             'contextlevel' => CONTEXT_SYSTEM,
1290             'purposeid' => $purpose1->get('id'),
1291             'categoryid' => $category1->get('id'),
1292         ]);
1294         $this->expectException(coding_exception::class);
1295         api::get_effective_contextlevel_purpose($contextlevel);
1296     }
1298     /**
1299      * Data provider for invalid contextlevel fetchers.
1300      */
1301     public function invalid_effective_contextlevel_provider() {
1302         return [
1303             [CONTEXT_COURSECAT],
1304             [CONTEXT_COURSE],
1305             [CONTEXT_MODULE],
1306             [CONTEXT_BLOCK],
1307         ];
1308     }
1310     /**
1311      * Ensure that context inheritance works up the context tree.
1312      */
1313     public function test_effective_context_inheritance() {
1314         $this->resetAfterTest();
1316         $systemdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_SYSTEM);
1318         /*
1319          * System
1320          * - Cat
1321          *   - Subcat
1322          *     - Course
1323          *       - Forum
1324          * - User
1325          *   - User block
1326          */
1327         $cat = $this->getDataGenerator()->create_category();
1328         $subcat = $this->getDataGenerator()->create_category(['parent' => $cat->id]);
1329         $course = $this->getDataGenerator()->create_course(['category' => $subcat->id]);
1330         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1331         list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1333         $user = $this->getDataGenerator()->create_user();
1335         $contextsystem = \context_system::instance();
1336         $contextcat = \context_coursecat::instance($cat->id);
1337         $contextsubcat = \context_coursecat::instance($subcat->id);
1338         $contextcourse = \context_course::instance($course->id);
1339         $contextforum = \context_module::instance($forumcm->id);
1340         $contextuser = \context_user::instance($user->id);
1342         // Initially everything is set to Inherit.
1343         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1344         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1345         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1346         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "0"));
1347         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1348         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1349         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "0"));
1350         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1351         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "-1"));
1352         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "0"));
1353         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1354         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "-1"));
1355         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "0"));
1356         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser));
1357         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "-1"));
1358         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "0"));
1360         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1361         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1362         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1363         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "0"));
1364         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1365         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "-1"));
1366         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "0"));
1367         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1368         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "-1"));
1369         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "0"));
1370         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1371         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "-1"));
1372         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "0"));
1373         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser));
1374         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser, "-1"));
1375         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser, "0"));
1377         // When actively set, user will use the specified value.
1378         $userdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_USER);
1380         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1381         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1382         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1383         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "0"));
1384         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1385         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1386         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "0"));
1387         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1388         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "-1"));
1389         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "0"));
1390         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1391         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "-1"));
1392         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "0"));
1393         $this->assertEquals($userdata->purpose, api::get_effective_context_purpose($contextuser));
1394         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "-1"));
1396         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1397         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1398         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1399         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "0"));
1400         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1401         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "-1"));
1402         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "0"));
1403         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1404         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "-1"));
1405         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "0"));
1406         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1407         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "-1"));
1408         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "0"));
1409         $this->assertEquals($userdata->category, api::get_effective_context_category($contextuser));
1410         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "-1"));
1412         // Set a context for the top category.
1413         $catpurpose = new purpose(0, (object) [
1414                 'name' => 'Purpose',
1415                 'retentionperiod' => 'P1D',
1416                 'lawfulbases' => 'gdpr_art_6_1_a',
1417             ]);
1418         $catpurpose->save();
1419         $catcategory = new category(0, (object) ['name' => 'Category']);
1420         $catcategory->save();
1421         api::set_context_instance((object) [
1422                 'contextid' => $contextcat->id,
1423                 'purposeid' => $catpurpose->get('id'),
1424                 'categoryid' => $catcategory->get('id'),
1425             ]);
1427         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1428         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
1429         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1430         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
1431         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat));
1432         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1433         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
1434         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse));
1435         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
1436         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse, "0"));
1437         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum));
1438         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum, "-1"));
1439         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum, "0"));
1440         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum, "0"));
1442         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1443         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
1444         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1445         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
1446         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat));
1447         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
1448         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "0"));
1449         $this->assertEquals($catcategory, api::get_effective_context_category($contextcourse));
1450         $this->assertEquals($catcategory, api::get_effective_context_category($contextcourse, "-1"));
1451         $this->assertEquals($catcategory, api::get_effective_context_category($contextcourse, "0"));
1452         $this->assertEquals($catcategory, api::get_effective_context_category($contextforum));
1453         $this->assertEquals($catcategory, api::get_effective_context_category($contextforum, "-1"));
1454         $this->assertEquals($catcategory, api::get_effective_context_category($contextforum, "0"));
1456         // Set a context for the sub category.
1457         $subcatpurpose = new purpose(0, (object) [
1458                 'name' => 'Purpose',
1459                 'retentionperiod' => 'P1D',
1460                 'lawfulbases' => 'gdpr_art_6_1_a',
1461             ]);
1462         $subcatpurpose->save();
1463         $subcatcategory = new category(0, (object) ['name' => 'Category']);
1464         $subcatcategory->save();
1465         api::set_context_instance((object) [
1466                 'contextid' => $contextsubcat->id,
1467                 'purposeid' => $subcatpurpose->get('id'),
1468                 'categoryid' => $subcatcategory->get('id'),
1469             ]);
1471         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1472         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
1473         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1474         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
1475         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
1476         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1477         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
1478         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse));
1479         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
1480         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "0"));
1481         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum));
1482         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum, "-1"));
1483         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum, "0"));
1485         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1486         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
1487         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1488         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
1489         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
1490         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
1491         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat, "0"));
1492         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse));
1493         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "-1"));
1494         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "0"));
1495         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum));
1496         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum, "-1"));
1497         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum, "0"));
1499         // Set a context for the course.
1500         $coursepurpose = new purpose(0, (object) [
1501                 'name' => 'Purpose',
1502                 'retentionperiod' => 'P1D',
1503                 'lawfulbases' => 'gdpr_art_6_1_a',
1504             ]);
1505         $coursepurpose->save();
1506         $coursecategory = new category(0, (object) ['name' => 'Category']);
1507         $coursecategory->save();
1508         api::set_context_instance((object) [
1509                 'contextid' => $contextcourse->id,
1510                 'purposeid' => $coursepurpose->get('id'),
1511                 'categoryid' => $coursecategory->get('id'),
1512             ]);
1514         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1515         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
1516         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1517         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
1518         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
1519         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1520         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
1521         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse));
1522         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
1523         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse, "0"));
1524         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum));
1525         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum, "-1"));
1526         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum, "0"));
1528         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1529         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
1530         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1531         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
1532         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
1533         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
1534         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat, "0"));
1535         $this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse));
1536         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "-1"));
1537         $this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse, "0"));
1538         $this->assertEquals($coursecategory, api::get_effective_context_category($contextforum));
1539         $this->assertEquals($coursecategory, api::get_effective_context_category($contextforum, "-1"));
1540         $this->assertEquals($coursecategory, api::get_effective_context_category($contextforum, "0"));
1542         // Set a context for the forum.
1543         $forumpurpose = new purpose(0, (object) [
1544                 'name' => 'Purpose',
1545                 'retentionperiod' => 'P1D',
1546                 'lawfulbases' => 'gdpr_art_6_1_a',
1547             ]);
1548         $forumpurpose->save();
1549         $forumcategory = new category(0, (object) ['name' => 'Category']);
1550         $forumcategory->save();
1551         api::set_context_instance((object) [
1552                 'contextid' => $contextforum->id,
1553                 'purposeid' => $forumpurpose->get('id'),
1554                 'categoryid' => $forumcategory->get('id'),
1555             ]);
1557         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1558         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
1559         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
1560         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
1561         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
1562         $this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
1563         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
1564         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse));
1565         $this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
1566         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse, "0"));
1567         $this->assertEquals($forumpurpose, api::get_effective_context_purpose($contextforum));
1568         $this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum, "-1"));
1569         $this->assertEquals($forumpurpose, api::get_effective_context_purpose($contextforum, "0"));
1571         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1572         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
1573         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
1574         $this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
1575         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
1576         $this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
1577         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat, "0"));
1578         $this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse));
1579         $this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "-1"));
1580         $this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse, "0"));
1581         $this->assertEquals($forumcategory, api::get_effective_context_category($contextforum));
1582         $this->assertEquals($coursecategory, api::get_effective_context_category($contextforum, "-1"));
1583         $this->assertEquals($forumcategory, api::get_effective_context_category($contextforum, "0"));
1584     }
1586     /**
1587      * Ensure that context inheritance works up the context tree when inherit values are explicitly set at the
1588      * contextlevel.
1589      *
1590      * Although it should not be possible to set hard INHERIT values at this level, there may be legacy data which still
1591      * contains this.
1592      */
1593     public function test_effective_context_inheritance_explicitly_set() {
1594         $this->resetAfterTest();
1596         $systemdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_SYSTEM);
1598         /*
1599          * System
1600          * - Cat
1601          *   - Subcat
1602          *     - Course
1603          *       - Forum
1604          * - User
1605          *   - User block
1606          */
1607         $cat = $this->getDataGenerator()->create_category();
1608         $subcat = $this->getDataGenerator()->create_category(['parent' => $cat->id]);
1609         $course = $this->getDataGenerator()->create_course(['category' => $subcat->id]);
1610         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1611         list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1613         $contextsystem = \context_system::instance();
1614         $contextcat = \context_coursecat::instance($cat->id);
1615         $contextsubcat = \context_coursecat::instance($subcat->id);
1616         $contextcourse = \context_course::instance($course->id);
1617         $contextforum = \context_module::instance($forumcm->id);
1619         // Initially everything is set to Inherit.
1620         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1621         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1622         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1623         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1624         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1626         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1627         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1628         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1629         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1630         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1632         // Set a default value of inherit for CONTEXT_COURSECAT.
1633         $classname = \context_helper::get_class_for_level(CONTEXT_COURSECAT);
1634         list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
1635         set_config($purposevar, '-1', 'tool_dataprivacy');
1636         set_config($categoryvar, '-1', 'tool_dataprivacy');
1638         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1639         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1640         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1641         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1642         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1644         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1645         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1646         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1647         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1648         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1650         // Set a default value of inherit for CONTEXT_COURSE.
1651         $classname = \context_helper::get_class_for_level(CONTEXT_COURSE);
1652         list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
1653         set_config($purposevar, '-1', 'tool_dataprivacy');
1654         set_config($categoryvar, '-1', 'tool_dataprivacy');
1656         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1657         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1658         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1659         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1660         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1662         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1663         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1664         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1665         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1666         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1668         // Set a default value of inherit for CONTEXT_MODULE.
1669         $classname = \context_helper::get_class_for_level(CONTEXT_MODULE);
1670         list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
1671         set_config($purposevar, '-1', 'tool_dataprivacy');
1672         set_config($categoryvar, '-1', 'tool_dataprivacy');
1674         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
1675         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
1676         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
1677         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
1678         $this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
1680         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
1681         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
1682         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
1683         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
1684         $this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
1685     }
1687     /**
1688      * Creates test purposes and categories.
1689      *
1690      * @return null
1691      */
1692     protected function add_purposes_and_categories() {
1693         $this->resetAfterTest();
1695         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1696         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
1697         $purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
1699         $cat1 = api::create_category((object)['name' => 'a']);
1700         $cat2 = api::create_category((object)['name' => 'b']);
1701         $cat3 = api::create_category((object)['name' => 'c']);
1703         $course1 = $this->getDataGenerator()->create_course();
1704         $course2 = $this->getDataGenerator()->create_course();
1706         $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
1707         $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
1709         return [
1710             [$purpose1, $purpose2, $purpose3],
1711             [$cat1, $cat2, $cat3],
1712             [$course1, $course2],
1713             [$module1, $module2]
1714         ];
1715     }
1717     /**
1718      * Test that delete requests do not filter out protected purpose contexts if the the site is properly configured.
1719      */
1720     public function test_get_approved_contextlist_collection_for_collection_delete_course_no_site_config() {
1721         $this->resetAfterTest();
1723         $user = $this->getDataGenerator()->create_user();
1725         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
1726         $coursecontext = \context_course::instance($course->id);
1728         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1729         list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1730         $contextforum = \context_module::instance($forumcm->id);
1732         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1734         // Create the initial contextlist.
1735         $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
1737         $contextlist = new \core_privacy\local\request\contextlist();
1738         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
1739         $contextlist->set_component('tool_dataprivacy');
1740         $initialcollection->add_contextlist($contextlist);
1742         $contextlist = new \core_privacy\local\request\contextlist();
1743         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $contextforum->id]);
1744         $contextlist->set_component('mod_forum');
1745         $initialcollection->add_contextlist($contextlist);
1747         $collection = api::get_approved_contextlist_collection_for_collection(
1748                 $initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
1750         $this->assertCount(2, $collection);
1752         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1753         $this->assertCount(1, $list);
1755         $list = $collection->get_contextlist_for_component('mod_forum');
1756         $this->assertCount(1, $list);
1757     }
1759     /**
1760      * Test that delete requests do not filter out protected purpose contexts if they are already expired.
1761      */
1762     public function test_get_approved_contextlist_collection_for_collection_delete_course_expired_protected() {
1763         $this->resetAfterTest();
1765         $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
1766         $purposes->course->purpose->set('protected', 1)->save();
1768         $user = $this->getDataGenerator()->create_user();
1769         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
1770         $coursecontext = \context_course::instance($course->id);
1772         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1774         // Create the initial contextlist.
1775         $contextlist = new \core_privacy\local\request\contextlist();
1776         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
1777         $contextlist->set_component('tool_dataprivacy');
1779         $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
1780         $initialcollection->add_contextlist($contextlist);
1782         $purposes->course->purpose->set('protected', 1)->save();
1783         $collection = api::get_approved_contextlist_collection_for_collection(
1784                 $initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
1786         $this->assertCount(1, $collection);
1788         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1789         $this->assertCount(1, $list);
1790     }
1792     /**
1793      * Test that delete requests does filter out protected purpose contexts which are not expired.
1794      */
1795     public function test_get_approved_contextlist_collection_for_collection_delete_course_unexpired_protected() {
1796         $this->resetAfterTest();
1798         $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
1799         $purposes->course->purpose->set('protected', 1)->save();
1801         $user = $this->getDataGenerator()->create_user();
1802         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
1803         $coursecontext = \context_course::instance($course->id);
1805         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1807         // Create the initial contextlist.
1808         $contextlist = new \core_privacy\local\request\contextlist();
1809         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
1810         $contextlist->set_component('tool_dataprivacy');
1812         $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
1813         $initialcollection->add_contextlist($contextlist);
1815         $purposes->course->purpose->set('protected', 1)->save();
1816         $collection = api::get_approved_contextlist_collection_for_collection(
1817                 $initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
1819         $this->assertCount(0, $collection);
1821         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1822         $this->assertEmpty($list);
1823     }
1825     /**
1826      * Test that delete requests do not filter out unexpired contexts if they are not protected.
1827      */
1828     public function test_get_approved_contextlist_collection_for_collection_delete_course_unexpired_unprotected() {
1829         $this->resetAfterTest();
1831         $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
1832         $purposes->course->purpose->set('protected', 1)->save();
1834         $user = $this->getDataGenerator()->create_user();
1835         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
1836         $coursecontext = \context_course::instance($course->id);
1838         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1840         // Create the initial contextlist.
1841         $contextlist = new \core_privacy\local\request\contextlist();
1842         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
1843         $contextlist->set_component('tool_dataprivacy');
1845         $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
1846         $initialcollection->add_contextlist($contextlist);
1848         $purposes->course->purpose->set('protected', 0)->save();
1849         $collection = api::get_approved_contextlist_collection_for_collection(
1850                 $initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
1852         $this->assertCount(1, $collection);
1854         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1855         $this->assertCount(1, $list);
1856     }
1858     /**
1859      * Data provider for \tool_dataprivacy_api_testcase::test_set_context_defaults
1860      */
1861     public function set_context_defaults_provider() {
1862         $contextlevels = [
1863             [CONTEXT_COURSECAT],
1864             [CONTEXT_COURSE],
1865             [CONTEXT_MODULE],
1866             [CONTEXT_BLOCK],
1867         ];
1868         $paramsets = [
1869             [true, true, false, false], // Inherit category and purpose, Not for activity, Don't override.
1870             [true, false, false, false], // Inherit category but not purpose, Not for activity, Don't override.
1871             [false, true, false, false], // Inherit purpose but not category, Not for activity, Don't override.
1872             [false, false, false, false], // Don't inherit both category and purpose, Not for activity, Don't override.
1873             [false, false, false, true], // Don't inherit both category and purpose, Not for activity, Override instances.
1874         ];
1875         $data = [];
1876         foreach ($contextlevels as $level) {
1877             foreach ($paramsets as $set) {
1878                 $data[] = array_merge($level, $set);
1879             }
1880             if ($level == CONTEXT_MODULE) {
1881                 // Add a combination where defaults for activity is being set.
1882                 $data[] = [CONTEXT_MODULE, false, false, true, false];
1883                 $data[] = [CONTEXT_MODULE, false, false, true, true];
1884             }
1885         }
1886         return $data;
1887     }
1889     /**
1890      * Test for \tool_dataprivacy\api::set_context_defaults()
1891      *
1892      * @dataProvider set_context_defaults_provider
1893      * @param int $contextlevel The context level
1894      * @param bool $inheritcategory Whether to set category value as INHERIT.
1895      * @param bool $inheritpurpose Whether to set purpose value as INHERIT.
1896      * @param bool $foractivity Whether to set defaults for an activity.
1897      * @param bool $override Whether to override instances.
1898      */
1899     public function test_set_context_defaults($contextlevel, $inheritcategory, $inheritpurpose, $foractivity, $override) {
1900         $this->resetAfterTest();
1902         $generator = $this->getDataGenerator();
1904         // Generate course cat, course, block, assignment, forum instances.
1905         $coursecat = $generator->create_category();
1906         $course = $generator->create_course(['category' => $coursecat->id]);
1907         $block = $generator->create_block('online_users');
1908         $assign = $generator->create_module('assign', ['course' => $course->id]);
1909         $forum = $generator->create_module('forum', ['course' => $course->id]);
1911         $coursecatcontext = context_coursecat::instance($coursecat->id);
1912         $coursecontext = context_course::instance($course->id);
1913         $blockcontext = context_block::instance($block->id);
1915         list($course, $assigncm) = get_course_and_cm_from_instance($assign->id, 'assign');
1916         list($course, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1917         $assigncontext = context_module::instance($assigncm->id);
1918         $forumcontext = context_module::instance($forumcm->id);
1920         // Generate purposes and categories.
1921         $category1 = api::create_category((object)['name' => 'Test category 1']);
1922         $category2 = api::create_category((object)['name' => 'Test category 2']);
1923         $purpose1 = api::create_purpose((object)[
1924             'name' => 'Test purpose 1', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1925         ]);
1926         $purpose2 = api::create_purpose((object)[
1927             'name' => 'Test purpose 2', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1928         ]);
1930         // Assign purposes and categories to contexts.
1931         $coursecatctxinstance = api::set_context_instance((object) [
1932             'contextid' => $coursecatcontext->id,
1933             'purposeid' => $purpose1->get('id'),
1934             'categoryid' => $category1->get('id'),
1935         ]);
1936         $coursectxinstance = api::set_context_instance((object) [
1937             'contextid' => $coursecontext->id,
1938             'purposeid' => $purpose1->get('id'),
1939             'categoryid' => $category1->get('id'),
1940         ]);
1941         $blockctxinstance = api::set_context_instance((object) [
1942             'contextid' => $blockcontext->id,
1943             'purposeid' => $purpose1->get('id'),
1944             'categoryid' => $category1->get('id'),
1945         ]);
1946         $assignctxinstance = api::set_context_instance((object) [
1947             'contextid' => $assigncontext->id,
1948             'purposeid' => $purpose1->get('id'),
1949             'categoryid' => $category1->get('id'),
1950         ]);
1951         $forumctxinstance = api::set_context_instance((object) [
1952             'contextid' => $forumcontext->id,
1953             'purposeid' => $purpose1->get('id'),
1954             'categoryid' => $category1->get('id'),
1955         ]);
1957         $categoryid = $inheritcategory ? context_instance::INHERIT : $category2->get('id');
1958         $purposeid = $inheritpurpose ? context_instance::INHERIT : $purpose2->get('id');
1959         $activity = '';
1960         if ($contextlevel == CONTEXT_MODULE && $foractivity) {
1961             $activity = 'assign';
1962         }
1963         $result = api::set_context_defaults($contextlevel, $categoryid, $purposeid, $activity, $override);
1964         $this->assertTrue($result);
1966         $targetctxinstance = false;
1967         switch ($contextlevel) {
1968             case CONTEXT_COURSECAT:
1969                 $targetctxinstance = $coursecatctxinstance;
1970                 break;
1971             case CONTEXT_COURSE:
1972                 $targetctxinstance = $coursectxinstance;
1973                 break;
1974             case CONTEXT_MODULE:
1975                 $targetctxinstance = $assignctxinstance;
1976                 break;
1977             case CONTEXT_BLOCK:
1978                 $targetctxinstance = $blockctxinstance;
1979                 break;
1980         }
1981         $this->assertNotFalse($targetctxinstance);
1983         // Check the context instances.
1984         $instanceexists = context_instance::record_exists($targetctxinstance->get('id'));
1985         if ($override) {
1986             // If overridden, context instances on this context level would have been deleted.
1987             $this->assertFalse($instanceexists);
1989             // Check forum context instance.
1990             $forumctxexists = context_instance::record_exists($forumctxinstance->get('id'));
1991             if ($contextlevel != CONTEXT_MODULE || $foractivity) {
1992                 // The forum context instance won't be affected in this test if:
1993                 // - The overridden defaults are not for context modules.
1994                 // - Only the defaults for assign have been set.
1995                 $this->assertTrue($forumctxexists);
1996             } else {
1997                 // If we're overriding for the whole course module context level,
1998                 // then this forum context instance will be deleted as well.
1999                 $this->assertFalse($forumctxexists);
2000             }
2001         } else {
2002             // Otherwise, the context instance record remains.
2003             $this->assertTrue($instanceexists);
2004         }
2006         // Check defaults.
2007         list($defaultpurpose, $defaultcategory) = data_registry::get_defaults($contextlevel, $activity);
2008         if (!$inheritpurpose) {
2009             $this->assertEquals($purposeid, $defaultpurpose);
2010         }
2011         if (!$inheritcategory) {
2012             $this->assertEquals($categoryid, $defaultcategory);
2013         }
2014     }
2016     /**
2017      * Setup the basics with the specified retention period.
2018      *
2019      * @param   string  $system Retention policy for the system.
2020      * @param   string  $user Retention policy for users.
2021      * @param   string  $course Retention policy for courses.
2022      * @param   string  $activity Retention policy for activities.
2023      */
2024     protected function setup_basics(string $system, string $user, string $course = null, string $activity = null) : \stdClass {
2025         $this->resetAfterTest();
2027         $purposes = (object) [
2028             'system' => $this->create_and_set_purpose_for_contextlevel($system, CONTEXT_SYSTEM),
2029             'user' => $this->create_and_set_purpose_for_contextlevel($user, CONTEXT_USER),
2030         ];
2032         if (null !== $course) {
2033             $purposes->course = $this->create_and_set_purpose_for_contextlevel($course, CONTEXT_COURSE);
2034         }
2036         if (null !== $activity) {
2037             $purposes->activity = $this->create_and_set_purpose_for_contextlevel($activity, CONTEXT_MODULE);
2038         }
2040         return $purposes;
2041     }
2043     /**
2044      * Create a retention period and set it for the specified context level.
2045      *
2046      * @param   string  $retention
2047      * @param   int     $contextlevel
2048      */
2049     protected function create_and_set_purpose_for_contextlevel(string $retention, int $contextlevel) {
2050         $purpose = new purpose(0, (object) [
2051             'name' => 'Test purpose ' . rand(1, 1000),
2052             'retentionperiod' => $retention,
2053             'lawfulbases' => 'gdpr_art_6_1_a',
2054         ]);
2055         $purpose->create();
2057         $cat = new category(0, (object) ['name' => 'Test category']);
2058         $cat->create();
2060         if ($contextlevel <= CONTEXT_USER) {
2061             $record = (object) [
2062                 'purposeid'     => $purpose->get('id'),
2063                 'categoryid'    => $cat->get('id'),
2064                 'contextlevel'  => $contextlevel,
2065             ];
2066             api::set_contextlevel($record);
2067         } else {
2068             list($purposevar, ) = data_registry::var_names_from_context(
2069                     \context_helper::get_class_for_level(CONTEXT_COURSE)
2070                 );
2071             set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy');
2072         }
2074         return (object) [
2075             'purpose' => $purpose,
2076             'category' => $cat,
2077         ];
2078     }
2080     /**
2081      * Ensure that the find_ongoing_request_types_for_users only returns requests which are active.
2082      */
2083     public function test_find_ongoing_request_types_for_users() {
2084         $this->resetAfterTest();
2086         // Create users and their requests:.
2087         // - u1 has no requests of any type.
2088         // - u2 has one rejected export request.
2089         // - u3 has one rejected other request.
2090         // - u4 has one rejected delete request.
2091         // - u5 has one active and one rejected export request.
2092         // - u6 has one active and one rejected other request.
2093         // - u7 has one active and one rejected delete request.
2094         // - u8 has one active export, and one active delete request.
2095         $u1 = $this->getDataGenerator()->create_user();
2096         $u1expect = (object) [];
2098         $u2 = $this->getDataGenerator()->create_user();
2099         $this->create_request_with_type_and_status($u2->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_REJECTED);
2100         $u2expect = (object) [];
2102         $u3 = $this->getDataGenerator()->create_user();
2103         $this->create_request_with_type_and_status($u3->id, api::DATAREQUEST_TYPE_OTHERS, api::DATAREQUEST_STATUS_REJECTED);
2104         $u3expect = (object) [];
2106         $u4 = $this->getDataGenerator()->create_user();
2107         $this->create_request_with_type_and_status($u4->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_REJECTED);
2108         $u4expect = (object) [];
2110         $u5 = $this->getDataGenerator()->create_user();
2111         $this->create_request_with_type_and_status($u5->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_REJECTED);
2112         $this->create_request_with_type_and_status($u5->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_APPROVED);
2113         $u5expect = (object) [
2114             api::DATAREQUEST_TYPE_EXPORT => true,
2115         ];
2117         $u6 = $this->getDataGenerator()->create_user();
2118         $this->create_request_with_type_and_status($u6->id, api::DATAREQUEST_TYPE_OTHERS, api::DATAREQUEST_STATUS_REJECTED);
2119         $this->create_request_with_type_and_status($u6->id, api::DATAREQUEST_TYPE_OTHERS, api::DATAREQUEST_STATUS_APPROVED);
2120         $u6expect = (object) [
2121             api::DATAREQUEST_TYPE_OTHERS => true,
2122         ];
2124         $u7 = $this->getDataGenerator()->create_user();
2125         $this->create_request_with_type_and_status($u7->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_REJECTED);
2126         $this->create_request_with_type_and_status($u7->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_APPROVED);
2127         $u7expect = (object) [
2128             api::DATAREQUEST_TYPE_DELETE => true,
2129         ];
2131         $u8 = $this->getDataGenerator()->create_user();
2132         $this->create_request_with_type_and_status($u8->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_APPROVED);
2133         $this->create_request_with_type_and_status($u8->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_APPROVED);
2134         $u8expect = (object) [
2135             api::DATAREQUEST_TYPE_EXPORT => true,
2136             api::DATAREQUEST_TYPE_DELETE => true,
2137         ];
2139         // Test with no users specified.
2140         $result = api::find_ongoing_request_types_for_users([]);
2141         $this->assertEquals([], $result);
2143         // Fetch a subset of the users.
2144         $result = api::find_ongoing_request_types_for_users([$u3->id, $u4->id, $u5->id]);
2145         $this->assertEquals([
2146                 $u3->id => $u3expect,
2147                 $u4->id => $u4expect,
2148                 $u5->id => $u5expect,
2149             ], $result);
2151         // Fetch the empty user.
2152         $result = api::find_ongoing_request_types_for_users([$u1->id]);
2153         $this->assertEquals([
2154                 $u1->id => $u1expect,
2155             ], $result);
2157         // Fetch all.
2158         $result = api::find_ongoing_request_types_for_users(
2159             [$u1->id, $u2->id, $u3->id, $u4->id, $u5->id, $u6->id, $u7->id, $u8->id]);
2160         $this->assertEquals([
2161                 $u1->id => $u1expect,
2162                 $u2->id => $u2expect,
2163                 $u3->id => $u3expect,
2164                 $u4->id => $u4expect,
2165                 $u5->id => $u5expect,
2166                 $u6->id => $u6expect,
2167                 $u7->id => $u7expect,
2168                 $u8->id => $u8expect,
2169             ], $result);
2170     }
2172     /**
2173      * Create  a new data request for the user with the type and status specified.
2174      *
2175      * @param   int     $userid
2176      * @param   int     $type
2177      * @param   int     $status
2178      * @return  \tool_dataprivacy\data_request
2179      */
2180     protected function create_request_with_type_and_status(int $userid, int $type, int $status) : \tool_dataprivacy\data_request {
2181         $request = new \tool_dataprivacy\data_request(0, (object) [
2182             'userid' => $userid,
2183             'type' => $type,
2184             'status' => $status,
2185         ]);
2187         $request->save();
2189         return $request;
2190     }
2192     /**
2193      * Test user cannot create data deletion request for themselves if they don't have
2194      * "tool/dataprivacy:requestdelete" capability.
2195      *
2196      * @throws coding_exception
2197      */
2198     public function test_can_create_data_deletion_request_for_self_no() {
2199         $this->resetAfterTest();
2200         $userid = $this->getDataGenerator()->create_user()->id;
2201         $roleid = $this->getDataGenerator()->create_role();
2202         assign_capability('tool/dataprivacy:requestdelete', CAP_PROHIBIT, $roleid, context_user::instance($userid));
2203         role_assign($roleid, $userid, context_user::instance($userid));
2204         $this->setUser($userid);
2205         $this->assertFalse(api::can_create_data_deletion_request_for_self());
2206     }
2208     /**
2209      * Test primary admin cannot create data deletion request for themselves
2210      */
2211     public function test_can_create_data_deletion_request_for_self_primary_admin() {
2212         $this->resetAfterTest();
2213         $this->setAdminUser();
2214         $this->assertFalse(api::can_create_data_deletion_request_for_self());
2215     }
2217     /**
2218      * Test secondary admin can create data deletion request for themselves
2219      */
2220     public function test_can_create_data_deletion_request_for_self_secondary_admin() {
2221         $this->resetAfterTest();
2223         $admin1 = $this->getDataGenerator()->create_user();
2224         $admin2 = $this->getDataGenerator()->create_user();
2226         // The primary admin is the one listed first in the 'siteadmins' config.
2227         set_config('siteadmins', implode(',', [$admin1->id, $admin2->id]));
2229         // Set the current user as the second admin (non-primary).
2230         $this->setUser($admin2);
2232         $this->assertTrue(api::can_create_data_deletion_request_for_self());
2233     }
2235     /**
2236      * Test user can create data deletion request for themselves if they have
2237      * "tool/dataprivacy:requestdelete" capability.
2238      *
2239      * @throws coding_exception
2240      */
2241     public function test_can_create_data_deletion_request_for_self_yes() {
2242         $this->resetAfterTest();
2243         $userid = $this->getDataGenerator()->create_user()->id;
2244         $this->setUser($userid);
2245         $this->assertTrue(api::can_create_data_deletion_request_for_self());
2246     }
2248     /**
2249      * Test user cannot create data deletion request for another user if they
2250      * don't have "tool/dataprivacy:requestdeleteforotheruser" capability.
2251      *
2252      * @throws coding_exception
2253      * @throws dml_exception
2254      */
2255     public function test_can_create_data_deletion_request_for_other_no() {
2256         $this->resetAfterTest();
2257         $userid = $this->getDataGenerator()->create_user()->id;
2258         $this->setUser($userid);
2259         $this->assertFalse(api::can_create_data_deletion_request_for_other());
2260     }
2262     /**
2263      * Test user can create data deletion request for another user if they
2264      * don't have "tool/dataprivacy:requestdeleteforotheruser" capability.
2265      *
2266      * @throws coding_exception
2267      */
2268     public function test_can_create_data_deletion_request_for_other_yes() {
2269         $this->resetAfterTest();
2270         $userid = $this->getDataGenerator()->create_user()->id;
2271         $roleid = $this->getDataGenerator()->create_role();
2272         $contextsystem = context_system::instance();
2273         assign_capability('tool/dataprivacy:requestdeleteforotheruser', CAP_ALLOW, $roleid, $contextsystem);
2274         role_assign($roleid, $userid, $contextsystem);
2275         $this->setUser($userid);
2276         $this->assertTrue(api::can_create_data_deletion_request_for_other($userid));
2277     }
2279     /**
2280      * Check parents can create data deletion request for their children (unless the child is the primary admin),
2281      * but not other users.
2282      *
2283      * @throws coding_exception
2284      * @throws dml_exception
2285      */
2286     public function test_can_create_data_deletion_request_for_children() {
2287         $this->resetAfterTest();
2289         $parent = $this->getDataGenerator()->create_user();
2290         $child = $this->getDataGenerator()->create_user();
2291         $otheruser = $this->getDataGenerator()->create_user();
2293         $contextsystem = \context_system::instance();
2294         $parentrole = $this->getDataGenerator()->create_role();
2295         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW,
2296             $parentrole, $contextsystem);
2297         assign_capability('tool/dataprivacy:makedatadeletionrequestsforchildren', CAP_ALLOW,
2298             $parentrole, $contextsystem);
2299         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
2301         $this->setUser($parent);
2302         $this->assertTrue(api::can_create_data_deletion_request_for_children($child->id));
2303         $this->assertFalse(api::can_create_data_deletion_request_for_children($otheruser->id));
2305         // Now make child the primary admin, confirm parent can't make deletion request.
2306         set_config('siteadmins', $child->id);
2307         $this->assertFalse(api::can_create_data_deletion_request_for_children($child->id));
2308     }
2310     /**
2311      * Data provider function for testing \tool_dataprivacy\api::queue_data_request_task().
2312      *
2313      * @return array
2314      */
2315     public function queue_data_request_task_provider() {
2316         return [
2317             'With user ID provided' => [true],
2318             'Without user ID provided' => [false],
2319         ];
2320     }
2322     /**
2323      * Test for \tool_dataprivacy\api::queue_data_request_task().
2324      *
2325      * @dataProvider queue_data_request_task_provider
2326      * @param bool $withuserid
2327      */
2328     public function test_queue_data_request_task(bool $withuserid) {
2329         $this->resetAfterTest();
2331         $this->setAdminUser();
2333         if ($withuserid) {
2334             $user = $this->getDataGenerator()->create_user();
2335             api::queue_data_request_task(1, $user->id);
2336             $expecteduserid = $user->id;
2337         } else {
2338             api::queue_data_request_task(1);
2339             $expecteduserid = null;
2340         }
2342         // Test number of queued data request tasks.
2343         $datarequesttasks = manager::get_adhoc_tasks(process_data_request_task::class);
2344         $this->assertCount(1, $datarequesttasks);
2345         $requesttask = reset($datarequesttasks);
2346         $this->assertEquals($expecteduserid, $requesttask->get_userid());
2347     }
2349     /**
2350      * Data provider for test_is_automatic_request_approval_on().
2351      */
2352     public function automatic_request_approval_setting_provider() {
2353         return [
2354             'Data export, not set' => [
2355                 'automaticdataexportapproval', api::DATAREQUEST_TYPE_EXPORT, null, false
2356             ],
2357             'Data export, turned on' => [
2358                 'automaticdataexportapproval', api::DATAREQUEST_TYPE_EXPORT, true, true
2359             ],
2360             'Data export, turned off' => [
2361                 'automaticdataexportapproval', api::DATAREQUEST_TYPE_EXPORT, false, false
2362             ],
2363             'Data deletion, not set' => [
2364                 'automaticdatadeletionapproval', api::DATAREQUEST_TYPE_DELETE, null, false
2365             ],
2366             'Data deletion, turned on' => [
2367                 'automaticdatadeletionapproval', api::DATAREQUEST_TYPE_DELETE, true, true
2368             ],
2369             'Data deletion, turned off' => [
2370                 'automaticdatadeletionapproval', api::DATAREQUEST_TYPE_DELETE, false, false
2371             ],
2372         ];
2373     }
2375     /**
2376      * Test for \tool_dataprivacy\api::is_automatic_request_approval_on().
2377      *
2378      * @dataProvider automatic_request_approval_setting_provider
2379      * @param string $setting The automatic approval setting.
2380      * @param int $type The data request type.
2381      * @param bool $value The setting's value.
2382      * @param bool $expected The expected result.
2383      */
2384     public function test_is_automatic_request_approval_on($setting, $type, $value, $expected) {
2385         $this->resetAfterTest();
2387         if ($value !== null) {
2388             set_config($setting, $value, 'tool_dataprivacy');
2389         }
2391         $this->assertEquals($expected, api::is_automatic_request_approval_on($type));
2392     }