Merge branch 'MDL-63102-master' of git://github.com/peterRd/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\local\helper;
33 use tool_dataprivacy\task\initiate_data_request_task;
34 use tool_dataprivacy\task\process_data_request_task;
36 defined('MOODLE_INTERNAL') || die();
37 global $CFG;
39 /**
40  * API tests.
41  *
42  * @package    tool_dataprivacy
43  * @copyright  2018 Jun Pataleta
44  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45  */
46 class tool_dataprivacy_api_testcase extends advanced_testcase {
48     /**
49      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
50      * tested with the default context.
51      */
52     public function test_check_can_manage_data_registry_admin() {
53         $this->resetAfterTest();
55         $this->setAdminUser();
56         // Technically this actually returns void, but assertNull will suffice to avoid a pointless test.
57         $this->assertNull(api::check_can_manage_data_registry());
58     }
60     /**
61      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
62      * tested with the default context.
63      */
64     public function test_check_can_manage_data_registry_without_cap_default() {
65         $this->resetAfterTest();
67         $user = $this->getDataGenerator()->create_user();
68         $this->setUser($user);
70         $this->expectException(required_capability_exception::class);
71         api::check_can_manage_data_registry();
72     }
74     /**
75      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
76      * tested with the default context.
77      */
78     public function test_check_can_manage_data_registry_without_cap_system() {
79         $this->resetAfterTest();
81         $user = $this->getDataGenerator()->create_user();
82         $this->setUser($user);
84         $this->expectException(required_capability_exception::class);
85         api::check_can_manage_data_registry(\context_system::instance()->id);
86     }
88     /**
89      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
90      * tested with the default context.
91      */
92     public function test_check_can_manage_data_registry_without_cap_own_user() {
93         $this->resetAfterTest();
95         $user = $this->getDataGenerator()->create_user();
96         $this->setUser($user);
98         $this->expectException(required_capability_exception::class);
99         api::check_can_manage_data_registry(\context_user::instance($user->id)->id);
100     }
102     /**
103      * Test for api::update_request_status().
104      */
105     public function test_update_request_status() {
106         $this->resetAfterTest();
108         $generator = new testing_data_generator();
109         $s1 = $generator->create_user();
110         $this->setUser($s1);
112         // Create the sample data request.
113         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
115         $requestid = $datarequest->get('id');
117         // Update with a comment.
118         $comment = 'This is an example of a comment';
119         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, $comment);
120         $this->assertTrue($result);
121         $datarequest = new data_request($requestid);
122         $this->assertStringEndsWith($comment, $datarequest->get('dpocomment'));
124         // Update with a comment which will be trimmed.
125         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, '  ');
126         $this->assertTrue($result);
127         $datarequest = new data_request($requestid);
128         $this->assertStringEndsWith($comment, $datarequest->get('dpocomment'));
130         // Update with a comment.
131         $secondcomment = '  - More comments -  ';
132         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, $secondcomment);
133         $this->assertTrue($result);
134         $datarequest = new data_request($requestid);
135         $this->assertRegExp("/.*{$comment}.*{$secondcomment}/s", $datarequest->get('dpocomment'));
137         // Update with a valid status.
138         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_DOWNLOAD_READY);
139         $this->assertTrue($result);
141         // Fetch the request record again.
142         $datarequest = new data_request($requestid);
143         $this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $datarequest->get('status'));
145         // Update with an invalid status.
146         $this->expectException(invalid_persistent_exception::class);
147         api::update_request_status($requestid, -1);
148     }
150     /**
151      * Test for api::get_site_dpos() when there are no users with the DPO role.
152      */
153     public function test_get_site_dpos_no_dpos() {
154         $this->resetAfterTest();
156         $admin = get_admin();
158         $dpos = api::get_site_dpos();
159         $this->assertCount(1, $dpos);
160         $dpo = reset($dpos);
161         $this->assertEquals($admin->id, $dpo->id);
162     }
164     /**
165      * Test for api::get_site_dpos() when there are no users with the DPO role.
166      */
167     public function test_get_site_dpos() {
168         global $DB;
170         $this->resetAfterTest();
172         $generator = new testing_data_generator();
173         $u1 = $generator->create_user();
174         $u2 = $generator->create_user();
176         $context = context_system::instance();
178         // Give the manager role with the capability to manage data requests.
179         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
180         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
181         // Assign u1 as a manager.
182         role_assign($managerroleid, $u1->id, $context->id);
184         // Give the editing teacher role with the capability to manage data requests.
185         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
186         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
187         // Assign u1 as an editing teacher as well.
188         role_assign($editingteacherroleid, $u1->id, $context->id);
189         // Assign u2 as an editing teacher.
190         role_assign($editingteacherroleid, $u2->id, $context->id);
192         // Only map the manager role to the DPO role.
193         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
195         $dpos = api::get_site_dpos();
196         $this->assertCount(1, $dpos);
197         $dpo = reset($dpos);
198         $this->assertEquals($u1->id, $dpo->id);
199     }
201     /**
202      * Test for \tool_dataprivacy\api::get_assigned_privacy_officer_roles().
203      */
204     public function test_get_assigned_privacy_officer_roles() {
205         global $DB;
207         $this->resetAfterTest();
209         // Erroneously set the manager roles as the PO, even if it doesn't have the managedatarequests capability yet.
210         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
211         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
212         // Get the assigned PO roles when nothing has been set yet.
213         $roleids = api::get_assigned_privacy_officer_roles();
214         // Confirm that the returned list is empty.
215         $this->assertEmpty($roleids);
217         $context = context_system::instance();
219         // Give the manager role with the capability to manage data requests.
220         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
222         // Give the editing teacher role with the capability to manage data requests.
223         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
224         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
226         // Get the non-editing teacher role ID.
227         $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
229         // Erroneously map the manager and the non-editing teacher roles to the PO role.
230         $badconfig = $managerroleid . ',' . $teacherroleid;
231         set_config('dporoles', $badconfig, 'tool_dataprivacy');
233         // Get the assigned PO roles.
234         $roleids = api::get_assigned_privacy_officer_roles();
236         // There should only be one PO role.
237         $this->assertCount(1, $roleids);
238         // Confirm it contains the manager role.
239         $this->assertContains($managerroleid, $roleids);
240         // And it does not contain the editing teacher role.
241         $this->assertNotContains($editingteacherroleid, $roleids);
242     }
244     /**
245      * Test for api::approve_data_request().
246      */
247     public function test_approve_data_request() {
248         global $DB;
250         $this->resetAfterTest();
252         $generator = new testing_data_generator();
253         $s1 = $generator->create_user();
254         $u1 = $generator->create_user();
256         $context = context_system::instance();
258         // Manager role.
259         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
260         // Give the manager role with the capability to manage data requests.
261         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
262         // Assign u1 as a manager.
263         role_assign($managerroleid, $u1->id, $context->id);
265         // Map the manager role to the DPO role.
266         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
268         // Create the sample data request.
269         $this->setUser($s1);
270         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
271         $requestid = $datarequest->get('id');
273         // Make this ready for approval.
274         api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
276         $this->setUser($u1);
277         $result = api::approve_data_request($requestid);
278         $this->assertTrue($result);
279         $datarequest = new data_request($requestid);
280         $this->assertEquals($u1->id, $datarequest->get('dpo'));
281         $this->assertEquals(api::DATAREQUEST_STATUS_APPROVED, $datarequest->get('status'));
283         // Test adhoc task creation.
284         $adhoctasks = manager::get_adhoc_tasks(process_data_request_task::class);
285         $this->assertCount(1, $adhoctasks);
286     }
288     /**
289      * Test for api::approve_data_request() with the request not yet waiting for approval.
290      */
291     public function test_approve_data_request_not_yet_ready() {
292         global $DB;
294         $this->resetAfterTest();
296         $generator = new testing_data_generator();
297         $s1 = $generator->create_user();
298         $u1 = $generator->create_user();
300         $context = context_system::instance();
302         // Manager role.
303         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
304         // Give the manager role with the capability to manage data requests.
305         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
306         // Assign u1 as a manager.
307         role_assign($managerroleid, $u1->id, $context->id);
309         // Map the manager role to the DPO role.
310         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
312         // Create the sample data request.
313         $this->setUser($s1);
314         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
315         $requestid = $datarequest->get('id');
317         $this->setUser($u1);
318         $this->expectException(moodle_exception::class);
319         api::approve_data_request($requestid);
320     }
322     /**
323      * Test for api::approve_data_request() when called by a user who doesn't have the DPO role.
324      */
325     public function test_approve_data_request_non_dpo_user() {
326         $this->resetAfterTest();
328         $generator = new testing_data_generator();
329         $student = $generator->create_user();
330         $teacher = $generator->create_user();
332         // Create the sample data request.
333         $this->setUser($student);
334         $datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
336         $requestid = $datarequest->get('id');
337     }
339     /**
340      * Test for api::can_contact_dpo()
341      */
342     public function test_can_contact_dpo() {
343         $this->resetAfterTest();
345         // Default ('contactdataprotectionofficer' is disabled by default).
346         $this->assertFalse(api::can_contact_dpo());
348         // Enable.
349         set_config('contactdataprotectionofficer', 1, 'tool_dataprivacy');
350         $this->assertTrue(api::can_contact_dpo());
352         // Disable again.
353         set_config('contactdataprotectionofficer', 0, 'tool_dataprivacy');
354         $this->assertFalse(api::can_contact_dpo());
355     }
357     /**
358      * Test for api::can_manage_data_requests()
359      */
360     public function test_can_manage_data_requests() {
361         global $DB;
363         $this->resetAfterTest();
365         // No configured site DPOs yet.
366         $admin = get_admin();
367         $this->assertTrue(api::can_manage_data_requests($admin->id));
369         $generator = new testing_data_generator();
370         $dpo = $generator->create_user();
371         $nondpocapable = $generator->create_user();
372         $nondpoincapable = $generator->create_user();
374         $context = context_system::instance();
376         // Manager role.
377         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
378         // Give the manager role with the capability to manage data requests.
379         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
380         // Assign u1 as a manager.
381         role_assign($managerroleid, $dpo->id, $context->id);
383         // Editing teacher role.
384         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
385         // Give the editing teacher role with the capability to manage data requests.
386         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
387         // Assign u2 as an editing teacher.
388         role_assign($editingteacherroleid, $nondpocapable->id, $context->id);
390         // Map only the manager role to the DPO role.
391         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
393         // User with capability and has DPO role.
394         $this->assertTrue(api::can_manage_data_requests($dpo->id));
395         // User with capability but has no DPO role.
396         $this->assertFalse(api::can_manage_data_requests($nondpocapable->id));
397         // User without the capability and has no DPO role.
398         $this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
399     }
401     /**
402      * Test that a user who has no capability to make any data requests for children cannot create data requests for any
403      * other user.
404      */
405     public function test_can_create_data_request_for_user_no() {
406         $this->resetAfterTest();
408         $parent = $this->getDataGenerator()->create_user();
409         $otheruser = $this->getDataGenerator()->create_user();
411         $this->setUser($parent);
412         $this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
413     }
415     /**
416      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
417      * for any other user.
418      */
419     public function test_can_create_data_request_for_user_some() {
420         $this->resetAfterTest();
422         $parent = $this->getDataGenerator()->create_user();
423         $child = $this->getDataGenerator()->create_user();
424         $otheruser = $this->getDataGenerator()->create_user();
426         $systemcontext = \context_system::instance();
427         $parentrole = $this->getDataGenerator()->create_role();
428         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
429         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
431         $this->setUser($parent);
432         $this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
433     }
435     /**
436      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
437      * for any other user.
438      */
439     public function test_can_create_data_request_for_user_own_child() {
440         $this->resetAfterTest();
442         $parent = $this->getDataGenerator()->create_user();
443         $child = $this->getDataGenerator()->create_user();
445         $systemcontext = \context_system::instance();
446         $parentrole = $this->getDataGenerator()->create_role();
447         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
448         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
450         $this->setUser($parent);
451         $this->assertTrue(api::can_create_data_request_for_user($child->id));
452     }
454     /**
455      * Test that a user who has no capability to make any data requests for children cannot create data requests for any
456      * other user.
457      */
458     public function test_require_can_create_data_request_for_user_no() {
459         $this->resetAfterTest();
461         $parent = $this->getDataGenerator()->create_user();
462         $otheruser = $this->getDataGenerator()->create_user();
464         $this->setUser($parent);
465         $this->expectException('required_capability_exception');
466         api::require_can_create_data_request_for_user($otheruser->id);
467     }
469     /**
470      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
471      * for any other user.
472      */
473     public function test_require_can_create_data_request_for_user_some() {
474         $this->resetAfterTest();
476         $parent = $this->getDataGenerator()->create_user();
477         $child = $this->getDataGenerator()->create_user();
478         $otheruser = $this->getDataGenerator()->create_user();
480         $systemcontext = \context_system::instance();
481         $parentrole = $this->getDataGenerator()->create_role();
482         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
483         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
485         $this->setUser($parent);
486         $this->expectException('required_capability_exception');
487         api::require_can_create_data_request_for_user($otheruser->id);
488     }
490     /**
491      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
492      * for any other user.
493      */
494     public function test_require_can_create_data_request_for_user_own_child() {
495         $this->resetAfterTest();
497         $parent = $this->getDataGenerator()->create_user();
498         $child = $this->getDataGenerator()->create_user();
500         $systemcontext = \context_system::instance();
501         $parentrole = $this->getDataGenerator()->create_role();
502         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
503         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
505         $this->setUser($parent);
506         $this->assertTrue(api::require_can_create_data_request_for_user($child->id));
507     }
509     /**
510      * Test for api::can_download_data_request_for_user()
511      */
512     public function test_can_download_data_request_for_user() {
513         $this->resetAfterTest();
515         $generator = $this->getDataGenerator();
517         // Three victims.
518         $victim1 = $generator->create_user();
519         $victim2 = $generator->create_user();
520         $victim3 = $generator->create_user();
522         // Assign a user as victim 1's parent.
523         $systemcontext = \context_system::instance();
524         $parentrole = $generator->create_role();
525         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
526         $parent = $generator->create_user();
527         role_assign($parentrole, $parent->id, \context_user::instance($victim1->id));
529         // Assign another user as data access wonder woman.
530         $wonderrole = $generator->create_role();
531         assign_capability('tool/dataprivacy:downloadallrequests', CAP_ALLOW, $wonderrole, $systemcontext);
532         $staff = $generator->create_user();
533         role_assign($wonderrole, $staff->id, $systemcontext);
535         // Finally, victim 3 has been naughty; stop them accessing their own data.
536         $naughtyrole = $generator->create_role();
537         assign_capability('tool/dataprivacy:downloadownrequest', CAP_PROHIBIT, $naughtyrole, $systemcontext);
538         role_assign($naughtyrole, $victim3->id, $systemcontext);
540         // Victims 1 and 2 can access their own data, regardless of who requested it.
541         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $victim1->id));
542         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $victim2->id));
544         // Victim 3 cannot access his own data.
545         $this->assertFalse(api::can_download_data_request_for_user($victim3->id, $victim3->id, $victim3->id));
547         // Victims 1 and 2 cannot access another victim's data.
548         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $victim1->id, $victim1->id));
549         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $victim2->id));
551         // Staff can access everyone's data.
552         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $staff->id));
553         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $staff->id));
554         $this->assertTrue(api::can_download_data_request_for_user($victim3->id, $staff->id, $staff->id));
556         // Parent can access victim 1's data only if they requested it.
557         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $parent->id, $parent->id));
558         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $parent->id));
559         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $parent->id, $parent->id));
560     }
562     /**
563      * Test for api::create_data_request()
564      */
565     public function test_create_data_request() {
566         $this->resetAfterTest();
568         $generator = new testing_data_generator();
569         $user = $generator->create_user();
570         $comment = 'sample comment';
572         // Login as user.
573         $this->setUser($user->id);
575         // Test data request creation.
576         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
577         $this->assertEquals($user->id, $datarequest->get('userid'));
578         $this->assertEquals($user->id, $datarequest->get('requestedby'));
579         $this->assertEquals(0, $datarequest->get('dpo'));
580         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
581         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
582         $this->assertEquals($comment, $datarequest->get('comments'));
584         // Test adhoc task creation.
585         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
586         $this->assertCount(1, $adhoctasks);
587     }
589     /**
590      * Test for api::create_data_request() made by DPO.
591      */
592     public function test_create_data_request_by_dpo() {
593         global $USER;
595         $this->resetAfterTest();
597         $generator = new testing_data_generator();
598         $user = $generator->create_user();
599         $comment = 'sample comment';
601         // Login as DPO (Admin is DPO by default).
602         $this->setAdminUser();
604         // Test data request creation.
605         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
606         $this->assertEquals($user->id, $datarequest->get('userid'));
607         $this->assertEquals($USER->id, $datarequest->get('requestedby'));
608         $this->assertEquals($USER->id, $datarequest->get('dpo'));
609         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
610         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
611         $this->assertEquals($comment, $datarequest->get('comments'));
613         // Test adhoc task creation.
614         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
615         $this->assertCount(1, $adhoctasks);
616     }
618     /**
619      * Test for api::create_data_request() made by a parent.
620      */
621     public function test_create_data_request_by_parent() {
622         global $DB;
624         $this->resetAfterTest();
626         $generator = new testing_data_generator();
627         $user = $generator->create_user();
628         $parent = $generator->create_user();
629         $comment = 'sample comment';
631         // Get the teacher role pretend it's the parent roles ;).
632         $systemcontext = context_system::instance();
633         $usercontext = context_user::instance($user->id);
634         $parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
635         // Give the manager role with the capability to manage data requests.
636         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
637         // Assign the parent to user.
638         role_assign($parentroleid, $parent->id, $usercontext->id);
640         // Login as the user's parent.
641         $this->setUser($parent);
643         // Test data request creation.
644         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
645         $this->assertEquals($user->id, $datarequest->get('userid'));
646         $this->assertEquals($parent->id, $datarequest->get('requestedby'));
647         $this->assertEquals(0, $datarequest->get('dpo'));
648         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
649         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
650         $this->assertEquals($comment, $datarequest->get('comments'));
652         // Test adhoc task creation.
653         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
654         $this->assertCount(1, $adhoctasks);
655     }
657     /**
658      * Test for api::deny_data_request()
659      */
660     public function test_deny_data_request() {
661         $this->resetAfterTest();
663         $generator = new testing_data_generator();
664         $user = $generator->create_user();
665         $comment = 'sample comment';
667         // Login as user.
668         $this->setUser($user->id);
670         // Test data request creation.
671         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
673         // Login as the admin (default DPO when no one is set).
674         $this->setAdminUser();
676         // Make this ready for approval.
677         api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
679         // Deny the data request.
680         $result = api::deny_data_request($datarequest->get('id'));
681         $this->assertTrue($result);
682     }
684     /**
685      * Data provider for \tool_dataprivacy_api_testcase::test_get_data_requests().
686      *
687      * @return array
688      */
689     public function get_data_requests_provider() {
690         $completeonly = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DOWNLOAD_READY, api::DATAREQUEST_STATUS_DELETED];
691         $completeandcancelled = array_merge($completeonly, [api::DATAREQUEST_STATUS_CANCELLED]);
693         return [
694             // Own data requests.
695             ['user', false, $completeonly],
696             // Non-DPO fetching all requets.
697             ['user', true, $completeonly],
698             // Admin fetching all completed and cancelled requests.
699             ['dpo', true, $completeandcancelled],
700             // Admin fetching all completed requests.
701             ['dpo', true, $completeonly],
702             // Guest fetching all requests.
703             ['guest', true, $completeonly],
704         ];
705     }
707     /**
708      * Test for api::get_data_requests()
709      *
710      * @dataProvider get_data_requests_provider
711      * @param string $usertype The type of the user logging in.
712      * @param boolean $fetchall Whether to fetch all records.
713      * @param int[] $statuses Status filters.
714      */
715     public function test_get_data_requests($usertype, $fetchall, $statuses) {
716         $this->resetAfterTest();
718         $generator = new testing_data_generator();
719         $user1 = $generator->create_user();
720         $user2 = $generator->create_user();
721         $user3 = $generator->create_user();
722         $user4 = $generator->create_user();
723         $user5 = $generator->create_user();
724         $users = [$user1, $user2, $user3, $user4, $user5];
726         switch ($usertype) {
727             case 'user':
728                 $loggeduser = $user1;
729                 break;
730             case 'dpo':
731                 $loggeduser = get_admin();
732                 break;
733             case 'guest':
734                 $loggeduser = guest_user();
735                 break;
736         }
738         $comment = 'Data %s request comment by user %d';
739         $exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
740         $deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
741         // Make a data requests for the users.
742         foreach ($users as $user) {
743             $this->setUser($user);
744             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $exportstring, $user->id));
745             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $deletionstring, $user->id));
746         }
748         // Log in as the target user.
749         $this->setUser($loggeduser);
750         // Get records count based on the filters.
751         $userid = $loggeduser->id;
752         if ($fetchall) {
753             $userid = 0;
754         }
755         $count = api::get_data_requests_count($userid);
756         if (api::is_site_dpo($loggeduser->id)) {
757             // DPOs should see all the requests.
758             $this->assertEquals(count($users) * 2, $count);
759         } else {
760             if (empty($userid)) {
761                 // There should be no data requests for this user available.
762                 $this->assertEquals(0, $count);
763             } else {
764                 // There should be only one (request with pending status).
765                 $this->assertEquals(2, $count);
766             }
767         }
768         // Get data requests.
769         $requests = api::get_data_requests($userid);
770         // The number of requests should match the count.
771         $this->assertCount($count, $requests);
773         // Test filtering by status.
774         if ($count && !empty($statuses)) {
775             $filteredcount = api::get_data_requests_count($userid, $statuses);
776             // There should be none as they are all pending.
777             $this->assertEquals(0, $filteredcount);
778             $filteredrequests = api::get_data_requests($userid, $statuses);
779             $this->assertCount($filteredcount, $filteredrequests);
781             $statuscounts = [];
782             foreach ($statuses as $stat) {
783                 $statuscounts[$stat] = 0;
784             }
785             $numstatus = count($statuses);
786             // Get all requests with status filter and update statuses, randomly.
787             foreach ($requests as $request) {
788                 if (rand(0, 1)) {
789                     continue;
790                 }
792                 if ($numstatus > 1) {
793                     $index = rand(0, $numstatus - 1);
794                     $status = $statuses[$index];
795                 } else {
796                     $status = reset($statuses);
797                 }
798                 $statuscounts[$status]++;
799                 api::update_request_status($request->get('id'), $status);
800             }
801             $total = array_sum($statuscounts);
802             $filteredcount = api::get_data_requests_count($userid, $statuses);
803             $this->assertEquals($total, $filteredcount);
804             $filteredrequests = api::get_data_requests($userid, $statuses);
805             $this->assertCount($filteredcount, $filteredrequests);
806             // Confirm the filtered requests match the status filter(s).
807             foreach ($filteredrequests as $request) {
808                 $this->assertContains($request->get('status'), $statuses);
809             }
811             if ($numstatus > 1) {
812                 // Fetch by individual status to check the numbers match.
813                 foreach ($statuses as $status) {
814                     $filteredcount = api::get_data_requests_count($userid, [$status]);
815                     $this->assertEquals($statuscounts[$status], $filteredcount);
816                     $filteredrequests = api::get_data_requests($userid, [$status]);
817                     $this->assertCount($filteredcount, $filteredrequests);
818                 }
819             }
820         }
821     }
823     /**
824      * Data provider for test_has_ongoing_request.
825      */
826     public function status_provider() {
827         return [
828             [api::DATAREQUEST_STATUS_PENDING, true],
829             [api::DATAREQUEST_STATUS_PREPROCESSING, true],
830             [api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
831             [api::DATAREQUEST_STATUS_APPROVED, true],
832             [api::DATAREQUEST_STATUS_PROCESSING, true],
833             [api::DATAREQUEST_STATUS_COMPLETE, false],
834             [api::DATAREQUEST_STATUS_CANCELLED, false],
835             [api::DATAREQUEST_STATUS_REJECTED, false],
836             [api::DATAREQUEST_STATUS_DOWNLOAD_READY, false],
837             [api::DATAREQUEST_STATUS_EXPIRED, false],
838             [api::DATAREQUEST_STATUS_DELETED, false],
839         ];
840     }
842     /**
843      * Test for api::has_ongoing_request()
844      *
845      * @dataProvider status_provider
846      * @param int $status The request status.
847      * @param bool $expected The expected result.
848      */
849     public function test_has_ongoing_request($status, $expected) {
850         $this->resetAfterTest();
852         $generator = new testing_data_generator();
853         $user1 = $generator->create_user();
855         // Make a data request as user 1.
856         $this->setUser($user1);
857         $request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
858         // Set the status.
859         api::update_request_status($request->get('id'), $status);
861         // Check if this request is ongoing.
862         $result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
863         $this->assertEquals($expected, $result);
864     }
866     /**
867      * Test for api::is_active()
868      *
869      * @dataProvider status_provider
870      * @param int $status The request status
871      * @param bool $expected The expected result
872      */
873     public function test_is_active($status, $expected) {
874         // Check if this request is ongoing.
875         $result = api::is_active($status);
876         $this->assertEquals($expected, $result);
877     }
879     /**
880      * Test for api::is_site_dpo()
881      */
882     public function test_is_site_dpo() {
883         global $DB;
885         $this->resetAfterTest();
887         // No configured site DPOs yet.
888         $admin = get_admin();
889         $this->assertTrue(api::is_site_dpo($admin->id));
891         $generator = new testing_data_generator();
892         $dpo = $generator->create_user();
893         $nondpo = $generator->create_user();
895         $context = context_system::instance();
897         // Manager role.
898         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
899         // Give the manager role with the capability to manage data requests.
900         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
901         // Assign u1 as a manager.
902         role_assign($managerroleid, $dpo->id, $context->id);
904         // Map only the manager role to the DPO role.
905         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
907         // User is a DPO.
908         $this->assertTrue(api::is_site_dpo($dpo->id));
909         // User is not a DPO.
910         $this->assertFalse(api::is_site_dpo($nondpo->id));
911     }
913     /**
914      * Data provider function for test_notify_dpo
915      *
916      * @return array
917      */
918     public function notify_dpo_provider() {
919         return [
920             [false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
921             [false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
922             [false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
923             [true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
924         ];
925     }
927     /**
928      * Test for api::notify_dpo()
929      *
930      * @dataProvider notify_dpo_provider
931      * @param bool $byadmin Whether the admin requests data on behalf of the user
932      * @param int $type The request type
933      * @param string $typestringid The request lang string identifier
934      * @param string $comments The requestor's message to the DPO.
935      */
936     public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
937         $this->resetAfterTest();
939         $generator = new testing_data_generator();
940         $user1 = $generator->create_user();
941         // Let's just use admin as DPO (It's the default if not set).
942         $dpo = get_admin();
943         if ($byadmin) {
944             $this->setAdminUser();
945             $requestedby = $dpo;
946         } else {
947             $this->setUser($user1);
948             $requestedby = $user1;
949         }
951         // Make a data request for user 1.
952         $request = api::create_data_request($user1->id, $type, $comments);
954         $sink = $this->redirectMessages();
955         $messageid = api::notify_dpo($dpo, $request);
956         $this->assertNotFalse($messageid);
957         $messages = $sink->get_messages();
958         $this->assertCount(1, $messages);
959         $message = reset($messages);
961         // Check some of the message properties.
962         $this->assertEquals($requestedby->id, $message->useridfrom);
963         $this->assertEquals($dpo->id, $message->useridto);
964         $typestring = get_string($typestringid, 'tool_dataprivacy');
965         $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
966         $this->assertEquals($subject, $message->subject);
967         $this->assertEquals('tool_dataprivacy', $message->component);
968         $this->assertEquals('contactdataprotectionofficer', $message->eventtype);
969         $this->assertContains(fullname($dpo), $message->fullmessage);
970         $this->assertContains(fullname($user1), $message->fullmessage);
971     }
973     /**
974      * Test data purposes CRUD actions.
975      *
976      * @return null
977      */
978     public function test_purpose_crud() {
979         $this->resetAfterTest();
981         $this->setAdminUser();
983         // Add.
984         $purpose = api::create_purpose((object)[
985             'name' => 'bbb',
986             'description' => '<b>yeah</b>',
987             'descriptionformat' => 1,
988             'retentionperiod' => 'PT1M',
989             'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
990         ]);
991         $this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
992         $this->assertEquals('bbb', $purpose->get('name'));
993         $this->assertEquals('PT1M', $purpose->get('retentionperiod'));
994         $this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
996         // Update.
997         $purpose->set('retentionperiod', 'PT2M');
998         $purpose = api::update_purpose($purpose->to_record());
999         $this->assertEquals('PT2M', $purpose->get('retentionperiod'));
1001         // Retrieve.
1002         $purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1003         $purposes = api::get_purposes();
1004         $this->assertCount(2, $purposes);
1005         $this->assertEquals('aaa', $purposes[0]->get('name'));
1006         $this->assertEquals('bbb', $purposes[1]->get('name'));
1008         // Delete.
1009         api::delete_purpose($purposes[0]->get('id'));
1010         $this->assertCount(1, api::get_purposes());
1011         api::delete_purpose($purposes[1]->get('id'));
1012         $this->assertCount(0, api::get_purposes());
1013     }
1015     /**
1016      * Test data categories CRUD actions.
1017      *
1018      * @return null
1019      */
1020     public function test_category_crud() {
1021         $this->resetAfterTest();
1023         $this->setAdminUser();
1025         // Add.
1026         $category = api::create_category((object)[
1027             'name' => 'bbb',
1028             'description' => '<b>yeah</b>',
1029             'descriptionformat' => 1
1030         ]);
1031         $this->assertInstanceOf('\tool_dataprivacy\category', $category);
1032         $this->assertEquals('bbb', $category->get('name'));
1034         // Update.
1035         $category->set('name', 'bcd');
1036         $category = api::update_category($category->to_record());
1037         $this->assertEquals('bcd', $category->get('name'));
1039         // Retrieve.
1040         $category = api::create_category((object)['name' => 'aaa']);
1041         $categories = api::get_categories();
1042         $this->assertCount(2, $categories);
1043         $this->assertEquals('aaa', $categories[0]->get('name'));
1044         $this->assertEquals('bcd', $categories[1]->get('name'));
1046         // Delete.
1047         api::delete_category($categories[0]->get('id'));
1048         $this->assertCount(1, api::get_categories());
1049         api::delete_category($categories[1]->get('id'));
1050         $this->assertCount(0, api::get_categories());
1051     }
1053     /**
1054      * Test context instances.
1055      *
1056      * @return null
1057      */
1058     public function test_context_instances() {
1059         global $DB;
1061         $this->resetAfterTest();
1063         $this->setAdminUser();
1065         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1067         $coursecontext1 = \context_course::instance($courses[0]->id);
1068         $coursecontext2 = \context_course::instance($courses[1]->id);
1070         $record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
1071             'categoryid' => $categories[0]->get('id')];
1072         $contextinstance1 = api::set_context_instance($record1);
1074         $record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
1075             'categoryid' => $categories[1]->get('id')];
1076         $contextinstance2 = api::set_context_instance($record2);
1078         $this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
1080         api::unset_context_instance($contextinstance1);
1081         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1083         $update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
1084             'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
1085         $contextinstance2 = api::set_context_instance($update);
1086         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1087     }
1089     /**
1090      * Test contextlevel.
1091      *
1092      * @return null
1093      */
1094     public function test_contextlevel() {
1095         global $DB;
1097         $this->resetAfterTest();
1099         $this->setAdminUser();
1100         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1102         $record = (object)[
1103             'purposeid' => $purposes[0]->get('id'),
1104             'categoryid' => $categories[0]->get('id'),
1105             'contextlevel' => CONTEXT_SYSTEM,
1106         ];
1107         $contextlevel = api::set_contextlevel($record);
1108         $this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
1109         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1110         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1111         $this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
1113         // Now update it.
1114         $record->purposeid = $purposes[1]->get('id');
1115         $contextlevel = api::set_contextlevel($record);
1116         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1117         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1118         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
1120         $record->contextlevel = CONTEXT_USER;
1121         $contextlevel = api::set_contextlevel($record);
1122         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
1123     }
1125     /**
1126      * Test effective context levels purpose and category defaults.
1127      *
1128      * @return null
1129      */
1130     public function test_effective_contextlevel_defaults() {
1131         $this->setAdminUser();
1133         $this->resetAfterTest();
1135         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1137         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1138         $this->assertEquals(false, $purposeid);
1139         $this->assertEquals(false, $categoryid);
1141         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1142             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1143         );
1144         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1146         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1147         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1148         $this->assertEquals(false, $categoryid);
1150         // Course inherits from system if not defined.
1151         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1152         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1153         $this->assertEquals(false, $categoryid);
1155         // Course defined values should have preference.
1156         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1157             \context_helper::get_class_for_level(CONTEXT_COURSE)
1158         );
1159         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1160         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1162         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1163         $this->assertEquals($purposes[1]->get('id'), $purposeid);
1164         $this->assertEquals($categories[0]->get('id'), $categoryid);
1166         // Context level defaults are also allowed to be set to 'inherit'.
1167         set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
1169         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1170         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1171         $this->assertEquals($categories[0]->get('id'), $categoryid);
1173         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_MODULE);
1174         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1175         $this->assertEquals($categories[0]->get('id'), $categoryid);
1176     }
1178     public function test_get_effective_contextlevel_category() {
1179         // Before setup, get_effective_contextlevel_purpose will return false.
1180         $this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
1181     }
1183     /**
1184      * Test effective contextlevel return.
1185      */
1186     public function test_effective_contextlevel() {
1187         $this->setAdminUser();
1189         $this->resetAfterTest();
1191         // Before setup, get_effective_contextlevel_purpose will return false.
1192         $this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
1194         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1196         // Set the system context level to purpose 1.
1197         $record = (object)[
1198             'contextlevel' => CONTEXT_SYSTEM,
1199             'purposeid' => $purposes[1]->get('id'),
1200             'categoryid' => $categories[1]->get('id'),
1201         ];
1202         api::set_contextlevel($record);
1204         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM);
1205         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1207         // Value 'not set' will get the default value for the context level. For context level defaults
1208         // both 'not set' and 'inherit' result in inherit, so the parent context (system) default
1209         // will be retrieved.
1210         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1211         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1213         // The behaviour forcing an inherit from context system should result in the same effective
1214         // purpose.
1215         $record->purposeid = context_instance::INHERIT;
1216         $record->contextlevel = CONTEXT_USER;
1217         api::set_contextlevel($record);
1218         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1219         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1221         $record->purposeid = $purposes[2]->get('id');
1222         $record->contextlevel = CONTEXT_USER;
1223         api::set_contextlevel($record);
1225         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1226         $this->assertEquals($purposes[2]->get('id'), $purpose->get('id'));
1228         // Only system and user allowed.
1229         $this->expectException(coding_exception::class);
1230         $record->contextlevel = CONTEXT_COURSE;
1231         $record->purposeid = $purposes[1]->get('id');
1232         api::set_contextlevel($record);
1233     }
1235     /**
1236      * Test effective context purposes and categories.
1237      *
1238      * @return null
1239      */
1240     public function test_effective_context() {
1241         $this->resetAfterTest();
1243         $this->setAdminUser();
1245         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1247         // Define system defaults (all context levels below will inherit).
1248         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1249             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1250         );
1251         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1252         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1254         // Define course defaults.
1255         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1256             \context_helper::get_class_for_level(CONTEXT_COURSE)
1257         );
1258         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1259         set_config($categoryvar, $categories[1]->get('id'), 'tool_dataprivacy');
1261         $course0context = \context_course::instance($courses[0]->id);
1262         $course1context = \context_course::instance($courses[1]->id);
1263         $mod0context = \context_module::instance($modules[0]->cmid);
1264         $mod1context = \context_module::instance($modules[1]->cmid);
1266         // Set course instance values.
1267         $record = (object)[
1268             'contextid' => $course0context->id,
1269             'purposeid' => $purposes[1]->get('id'),
1270             'categoryid' => $categories[2]->get('id'),
1271         ];
1272         api::set_context_instance($record);
1273         $category = api::get_effective_context_category($course0context);
1274         $this->assertEquals($record->categoryid, $category->get('id'));
1276         // Module instances get the context level default if nothing specified.
1277         $category = api::get_effective_context_category($mod0context);
1278         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1280         // Module instances get the parent context category if they inherit.
1281         $record->contextid = $mod0context->id;
1282         $record->categoryid = context_instance::INHERIT;
1283         api::set_context_instance($record);
1284         $category = api::get_effective_context_category($mod0context);
1285         $this->assertEquals($categories[2]->get('id'), $category->get('id'));
1287         // The $forcedvalue param allows us to override the actual value (method php-docs for more info).
1288         $category = api::get_effective_context_category($mod0context, $categories[1]->get('id'));
1289         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1290         $category = api::get_effective_context_category($mod0context, $categories[0]->get('id'));
1291         $this->assertEquals($categories[0]->get('id'), $category->get('id'));
1293         // Module instances get the parent context category if they inherit; in
1294         // this case the parent context category is not set so it should use the
1295         // context level default (see 'Define course defaults' above).
1296         $record->contextid = $mod1context->id;
1297         $record->categoryid = context_instance::INHERIT;
1298         api::set_context_instance($record);
1299         $category = api::get_effective_context_category($mod1context);
1300         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1302         // User instances use the value set at user context level instead of the user default.
1304         // User defaults to cat 0 and user context level to 1.
1305         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1306             \context_helper::get_class_for_level(CONTEXT_USER)
1307         );
1308         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1309         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1310         $usercontextlevel = (object)[
1311             'contextlevel' => CONTEXT_USER,
1312             'purposeid' => $purposes[1]->get('id'),
1313             'categoryid' => $categories[1]->get('id'),
1314         ];
1315         api::set_contextlevel($usercontextlevel);
1317         $newuser = $this->getDataGenerator()->create_user();
1318         $usercontext = \context_user::instance($newuser->id);
1319         $category = api::get_effective_context_category($usercontext);
1320         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1321     }
1323     /**
1324      * Creates test purposes and categories.
1325      *
1326      * @return null
1327      */
1328     protected function add_purposes_and_categories() {
1329         $this->resetAfterTest();
1331         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1332         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
1333         $purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
1335         $cat1 = api::create_category((object)['name' => 'a']);
1336         $cat2 = api::create_category((object)['name' => 'b']);
1337         $cat3 = api::create_category((object)['name' => 'c']);
1339         $course1 = $this->getDataGenerator()->create_course();
1340         $course2 = $this->getDataGenerator()->create_course();
1342         $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
1343         $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
1345         return [
1346             [$purpose1, $purpose2, $purpose3],
1347             [$cat1, $cat2, $cat3],
1348             [$course1, $course2],
1349             [$module1, $module2]
1350         ];
1351     }
1353     /**
1354      * Test that delete requests filter out protected purpose contexts.
1355      */
1356     public function test_add_request_contexts_with_status_delete() {
1357         $this->resetAfterTest();
1359         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
1360         $contextids = $data->list->get_contextids();
1362         $this->assertCount(1, $contextids);
1363         $this->assertEquals($data->contexts->unprotected, $contextids);
1364     }
1366     /**
1367      * Test that export requests don't filter out protected purpose contexts.
1368      */
1369     public function test_add_request_contexts_with_status_export() {
1370         $this->resetAfterTest();
1372         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
1373         $contextids = $data->list->get_contextids();
1375         $this->assertCount(2, $contextids);
1376         $this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
1377     }
1379     /**
1380      * Data provider for \tool_dataprivacy_api_testcase::test_set_context_defaults
1381      */
1382     public function set_context_defaults_provider() {
1383         $contextlevels = [
1384             [CONTEXT_COURSECAT],
1385             [CONTEXT_COURSE],
1386             [CONTEXT_MODULE],
1387             [CONTEXT_BLOCK],
1388         ];
1389         $paramsets = [
1390             [true, true, false, false], // Inherit category and purpose, Not for activity, Don't override.
1391             [true, false, false, false], // Inherit category but not purpose, Not for activity, Don't override.
1392             [false, true, false, false], // Inherit purpose but not category, Not for activity, Don't override.
1393             [false, false, false, false], // Don't inherit both category and purpose, Not for activity, Don't override.
1394             [false, false, false, true], // Don't inherit both category and purpose, Not for activity, Override instances.
1395         ];
1396         $data = [];
1397         foreach ($contextlevels as $level) {
1398             foreach ($paramsets as $set) {
1399                 $data[] = array_merge($level, $set);
1400             }
1401             if ($level == CONTEXT_MODULE) {
1402                 // Add a combination where defaults for activity is being set.
1403                 $data[] = [CONTEXT_MODULE, false, false, true, false];
1404                 $data[] = [CONTEXT_MODULE, false, false, true, true];
1405             }
1406         }
1407         return $data;
1408     }
1410     /**
1411      * Test for \tool_dataprivacy\api::set_context_defaults()
1412      *
1413      * @dataProvider set_context_defaults_provider
1414      * @param int $contextlevel The context level
1415      * @param bool $inheritcategory Whether to set category value as INHERIT.
1416      * @param bool $inheritpurpose Whether to set purpose value as INHERIT.
1417      * @param bool $foractivity Whether to set defaults for an activity.
1418      * @param bool $override Whether to override instances.
1419      */
1420     public function test_set_context_defaults($contextlevel, $inheritcategory, $inheritpurpose, $foractivity, $override) {
1421         $this->resetAfterTest();
1423         $generator = $this->getDataGenerator();
1425         // Generate course cat, course, block, assignment, forum instances.
1426         $coursecat = $generator->create_category();
1427         $course = $generator->create_course(['category' => $coursecat->id]);
1428         $block = $generator->create_block('online_users');
1429         $assign = $generator->create_module('assign', ['course' => $course->id]);
1430         $forum = $generator->create_module('forum', ['course' => $course->id]);
1432         $coursecatcontext = context_coursecat::instance($coursecat->id);
1433         $coursecontext = context_course::instance($course->id);
1434         $blockcontext = context_block::instance($block->id);
1436         list($course, $assigncm) = get_course_and_cm_from_instance($assign->id, 'assign');
1437         list($course, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1438         $assigncontext = context_module::instance($assigncm->id);
1439         $forumcontext = context_module::instance($forumcm->id);
1441         // Generate purposes and categories.
1442         $category1 = api::create_category((object)['name' => 'Test category 1']);
1443         $category2 = api::create_category((object)['name' => 'Test category 2']);
1444         $purpose1 = api::create_purpose((object)[
1445             'name' => 'Test purpose 1', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1446         ]);
1447         $purpose2 = api::create_purpose((object)[
1448             'name' => 'Test purpose 2', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1449         ]);
1451         // Assign purposes and categories to contexts.
1452         $coursecatctxinstance = api::set_context_instance((object) [
1453             'contextid' => $coursecatcontext->id,
1454             'purposeid' => $purpose1->get('id'),
1455             'categoryid' => $category1->get('id'),
1456         ]);
1457         $coursectxinstance = api::set_context_instance((object) [
1458             'contextid' => $coursecontext->id,
1459             'purposeid' => $purpose1->get('id'),
1460             'categoryid' => $category1->get('id'),
1461         ]);
1462         $blockctxinstance = api::set_context_instance((object) [
1463             'contextid' => $blockcontext->id,
1464             'purposeid' => $purpose1->get('id'),
1465             'categoryid' => $category1->get('id'),
1466         ]);
1467         $assignctxinstance = api::set_context_instance((object) [
1468             'contextid' => $assigncontext->id,
1469             'purposeid' => $purpose1->get('id'),
1470             'categoryid' => $category1->get('id'),
1471         ]);
1472         $forumctxinstance = api::set_context_instance((object) [
1473             'contextid' => $forumcontext->id,
1474             'purposeid' => $purpose1->get('id'),
1475             'categoryid' => $category1->get('id'),
1476         ]);
1478         $categoryid = $inheritcategory ? context_instance::INHERIT : $category2->get('id');
1479         $purposeid = $inheritpurpose ? context_instance::INHERIT : $purpose2->get('id');
1480         $activity = '';
1481         if ($contextlevel == CONTEXT_MODULE && $foractivity) {
1482             $activity = 'assign';
1483         }
1484         $result = api::set_context_defaults($contextlevel, $categoryid, $purposeid, $activity, $override);
1485         $this->assertTrue($result);
1487         $targetctxinstance = false;
1488         switch ($contextlevel) {
1489             case CONTEXT_COURSECAT:
1490                 $targetctxinstance = $coursecatctxinstance;
1491                 break;
1492             case CONTEXT_COURSE:
1493                 $targetctxinstance = $coursectxinstance;
1494                 break;
1495             case CONTEXT_MODULE:
1496                 $targetctxinstance = $assignctxinstance;
1497                 break;
1498             case CONTEXT_BLOCK:
1499                 $targetctxinstance = $blockctxinstance;
1500                 break;
1501         }
1502         $this->assertNotFalse($targetctxinstance);
1504         // Check the context instances.
1505         $instanceexists = context_instance::record_exists($targetctxinstance->get('id'));
1506         if ($override) {
1507             // If overridden, context instances on this context level would have been deleted.
1508             $this->assertFalse($instanceexists);
1510             // Check forum context instance.
1511             $forumctxexists = context_instance::record_exists($forumctxinstance->get('id'));
1512             if ($contextlevel != CONTEXT_MODULE || $foractivity) {
1513                 // The forum context instance won't be affected in this test if:
1514                 // - The overridden defaults are not for context modules.
1515                 // - Only the defaults for assign have been set.
1516                 $this->assertTrue($forumctxexists);
1517             } else {
1518                 // If we're overriding for the whole course module context level,
1519                 // then this forum context instance will be deleted as well.
1520                 $this->assertFalse($forumctxexists);
1521             }
1522         } else {
1523             // Otherwise, the context instance record remains.
1524             $this->assertTrue($instanceexists);
1525         }
1527         // Check defaults.
1528         list($defaultpurpose, $defaultcategory) = data_registry::get_defaults($contextlevel, $activity);
1529         if (!$inheritpurpose) {
1530             $this->assertEquals($purposeid, $defaultpurpose);
1531         }
1532         if (!$inheritcategory) {
1533             $this->assertEquals($categoryid, $defaultcategory);
1534         }
1535     }
1537     /**
1538      * Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
1539      *
1540      * @param       int $type The type of request to create
1541      * @return      \stdClass
1542      */
1543     protected function setup_test_add_request_contexts_with_status($type) {
1544         $this->resetAfterTest();
1546         $this->setAdminUser();
1548         // User under test.
1549         $s1 = $this->getDataGenerator()->create_user();
1551         // Create three sample contexts.
1552         // 1 which should not be returned; and
1553         // 1 which will be returned and is not protected; and
1554         // 1 which will be returned and is protected.
1556         $c1 = $this->getDataGenerator()->create_course();
1557         $c2 = $this->getDataGenerator()->create_course();
1558         $c3 = $this->getDataGenerator()->create_course();
1560         $ctx1 = \context_course::instance($c1->id);
1561         $ctx2 = \context_course::instance($c2->id);
1562         $ctx3 = \context_course::instance($c3->id);
1564         $unprotected = api::create_purpose((object)[
1565             'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1566         $protected = api::create_purpose((object) [
1567             'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
1569         $cat1 = api::create_category((object)['name' => 'a']);
1571         // Set the defaults.
1572         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1573             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1574         );
1575         set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
1576         set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
1578         $contextinstance1 = api::set_context_instance((object) [
1579                 'contextid' => $ctx1->id,
1580                 'purposeid' => $unprotected->get('id'),
1581                 'categoryid' => $cat1->get('id'),
1582             ]);
1584         $contextinstance2 = api::set_context_instance((object) [
1585                 'contextid' => $ctx2->id,
1586                 'purposeid' => $unprotected->get('id'),
1587                 'categoryid' => $cat1->get('id'),
1588             ]);
1590         $contextinstance3 = api::set_context_instance((object) [
1591                 'contextid' => $ctx3->id,
1592                 'purposeid' => $protected->get('id'),
1593                 'categoryid' => $cat1->get('id'),
1594             ]);
1596         $collection = new \core_privacy\local\request\contextlist_collection($s1->id);
1597         $contextlist = new \core_privacy\local\request\contextlist();
1598         $contextlist->set_component('tool_dataprivacy');
1599         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
1600                 'ctx2' => $ctx2->id,
1601                 'ctx3' => $ctx3->id,
1602             ]);
1604         $collection->add_contextlist($contextlist);
1606         // Create the sample data request.
1607         $datarequest = api::create_data_request($s1->id, $type);
1608         $requestid = $datarequest->get('id');
1610         // Add the full collection with contexts 2, and 3.
1611         api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
1613         // Mark it as approved.
1614         api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
1616         // Fetch the list.
1617         $approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
1619         return (object) [
1620             'contexts' => (object) [
1621                 'unused' => [
1622                     $ctx1->id,
1623                 ],
1624                 'used' => [
1625                     $ctx2->id,
1626                     $ctx3->id,
1627                 ],
1628                 'unprotected' => [
1629                     $ctx2->id,
1630                 ],
1631                 'protected' => [
1632                     $ctx3->id,
1633                 ],
1634             ],
1635             'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
1636         ];
1637     }