MDL-63496 tool_dataprivacy: Respect expiry with protected flag
[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\contextlist_context;
28 use tool_dataprivacy\context_instance;
29 use tool_dataprivacy\api;
30 use tool_dataprivacy\data_registry;
31 use tool_dataprivacy\expired_context;
32 use tool_dataprivacy\data_request;
33 use tool_dataprivacy\purpose;
34 use tool_dataprivacy\category;
35 use tool_dataprivacy\local\helper;
36 use tool_dataprivacy\task\initiate_data_request_task;
37 use tool_dataprivacy\task\process_data_request_task;
39 defined('MOODLE_INTERNAL') || die();
40 global $CFG;
42 /**
43  * API tests.
44  *
45  * @package    tool_dataprivacy
46  * @copyright  2018 Jun Pataleta
47  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48  */
49 class tool_dataprivacy_api_testcase extends advanced_testcase {
51     /**
52      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
53      * tested with the default context.
54      */
55     public function test_check_can_manage_data_registry_admin() {
56         $this->resetAfterTest();
58         $this->setAdminUser();
59         // Technically this actually returns void, but assertNull will suffice to avoid a pointless test.
60         $this->assertNull(api::check_can_manage_data_registry());
61     }
63     /**
64      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
65      * tested with the default context.
66      */
67     public function test_check_can_manage_data_registry_without_cap_default() {
68         $this->resetAfterTest();
70         $user = $this->getDataGenerator()->create_user();
71         $this->setUser($user);
73         $this->expectException(required_capability_exception::class);
74         api::check_can_manage_data_registry();
75     }
77     /**
78      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
79      * tested with the default context.
80      */
81     public function test_check_can_manage_data_registry_without_cap_system() {
82         $this->resetAfterTest();
84         $user = $this->getDataGenerator()->create_user();
85         $this->setUser($user);
87         $this->expectException(required_capability_exception::class);
88         api::check_can_manage_data_registry(\context_system::instance()->id);
89     }
91     /**
92      * Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
93      * tested with the default context.
94      */
95     public function test_check_can_manage_data_registry_without_cap_own_user() {
96         $this->resetAfterTest();
98         $user = $this->getDataGenerator()->create_user();
99         $this->setUser($user);
101         $this->expectException(required_capability_exception::class);
102         api::check_can_manage_data_registry(\context_user::instance($user->id)->id);
103     }
105     /**
106      * Test for api::update_request_status().
107      */
108     public function test_update_request_status() {
109         $this->resetAfterTest();
111         $generator = new testing_data_generator();
112         $s1 = $generator->create_user();
113         $this->setUser($s1);
115         // Create the sample data request.
116         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
118         $requestid = $datarequest->get('id');
120         // Update with a comment.
121         $comment = 'This is an example of a comment';
122         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, $comment);
123         $this->assertTrue($result);
124         $datarequest = new data_request($requestid);
125         $this->assertStringEndsWith($comment, $datarequest->get('dpocomment'));
127         // Update with a comment which will be trimmed.
128         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, '  ');
129         $this->assertTrue($result);
130         $datarequest = new data_request($requestid);
131         $this->assertStringEndsWith($comment, $datarequest->get('dpocomment'));
133         // Update with a comment.
134         $secondcomment = '  - More comments -  ';
135         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, $secondcomment);
136         $this->assertTrue($result);
137         $datarequest = new data_request($requestid);
138         $this->assertRegExp("/.*{$comment}.*{$secondcomment}/s", $datarequest->get('dpocomment'));
140         // Update with a valid status.
141         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_DOWNLOAD_READY);
142         $this->assertTrue($result);
144         // Fetch the request record again.
145         $datarequest = new data_request($requestid);
146         $this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $datarequest->get('status'));
148         // Update with an invalid status.
149         $this->expectException(invalid_persistent_exception::class);
150         api::update_request_status($requestid, -1);
151     }
153     /**
154      * Test for api::get_site_dpos() when there are no users with the DPO role.
155      */
156     public function test_get_site_dpos_no_dpos() {
157         $this->resetAfterTest();
159         $admin = get_admin();
161         $dpos = api::get_site_dpos();
162         $this->assertCount(1, $dpos);
163         $dpo = reset($dpos);
164         $this->assertEquals($admin->id, $dpo->id);
165     }
167     /**
168      * Test for api::get_site_dpos() when there are no users with the DPO role.
169      */
170     public function test_get_site_dpos() {
171         global $DB;
173         $this->resetAfterTest();
175         $generator = new testing_data_generator();
176         $u1 = $generator->create_user();
177         $u2 = $generator->create_user();
179         $context = context_system::instance();
181         // Give the manager role with the capability to manage data requests.
182         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
183         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
184         // Assign u1 as a manager.
185         role_assign($managerroleid, $u1->id, $context->id);
187         // Give the editing teacher role with the capability to manage data requests.
188         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
189         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
190         // Assign u1 as an editing teacher as well.
191         role_assign($editingteacherroleid, $u1->id, $context->id);
192         // Assign u2 as an editing teacher.
193         role_assign($editingteacherroleid, $u2->id, $context->id);
195         // Only map the manager role to the DPO role.
196         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
198         $dpos = api::get_site_dpos();
199         $this->assertCount(1, $dpos);
200         $dpo = reset($dpos);
201         $this->assertEquals($u1->id, $dpo->id);
202     }
204     /**
205      * Test for \tool_dataprivacy\api::get_assigned_privacy_officer_roles().
206      */
207     public function test_get_assigned_privacy_officer_roles() {
208         global $DB;
210         $this->resetAfterTest();
212         // Erroneously set the manager roles as the PO, even if it doesn't have the managedatarequests capability yet.
213         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
214         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
215         // Get the assigned PO roles when nothing has been set yet.
216         $roleids = api::get_assigned_privacy_officer_roles();
217         // Confirm that the returned list is empty.
218         $this->assertEmpty($roleids);
220         $context = context_system::instance();
222         // Give the manager role with the capability to manage data requests.
223         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
225         // Give the editing teacher role with the capability to manage data requests.
226         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
227         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
229         // Get the non-editing teacher role ID.
230         $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
232         // Erroneously map the manager and the non-editing teacher roles to the PO role.
233         $badconfig = $managerroleid . ',' . $teacherroleid;
234         set_config('dporoles', $badconfig, 'tool_dataprivacy');
236         // Get the assigned PO roles.
237         $roleids = api::get_assigned_privacy_officer_roles();
239         // There should only be one PO role.
240         $this->assertCount(1, $roleids);
241         // Confirm it contains the manager role.
242         $this->assertContains($managerroleid, $roleids);
243         // And it does not contain the editing teacher role.
244         $this->assertNotContains($editingteacherroleid, $roleids);
245     }
247     /**
248      * Test for api::approve_data_request().
249      */
250     public function test_approve_data_request() {
251         global $DB;
253         $this->resetAfterTest();
255         $generator = new testing_data_generator();
256         $s1 = $generator->create_user();
257         $u1 = $generator->create_user();
259         $context = context_system::instance();
261         // Manager role.
262         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
263         // Give the manager role with the capability to manage data requests.
264         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
265         // Assign u1 as a manager.
266         role_assign($managerroleid, $u1->id, $context->id);
268         // Map the manager role to the DPO role.
269         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
271         // Create the sample data request.
272         $this->setUser($s1);
273         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
274         $requestid = $datarequest->get('id');
276         // Make this ready for approval.
277         api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
279         $this->setUser($u1);
280         $result = api::approve_data_request($requestid);
281         $this->assertTrue($result);
282         $datarequest = new data_request($requestid);
283         $this->assertEquals($u1->id, $datarequest->get('dpo'));
284         $this->assertEquals(api::DATAREQUEST_STATUS_APPROVED, $datarequest->get('status'));
286         // Test adhoc task creation.
287         $adhoctasks = manager::get_adhoc_tasks(process_data_request_task::class);
288         $this->assertCount(1, $adhoctasks);
289     }
291     /**
292      * Test for api::approve_data_request() with the request not yet waiting for approval.
293      */
294     public function test_approve_data_request_not_yet_ready() {
295         global $DB;
297         $this->resetAfterTest();
299         $generator = new testing_data_generator();
300         $s1 = $generator->create_user();
301         $u1 = $generator->create_user();
303         $context = context_system::instance();
305         // Manager role.
306         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
307         // Give the manager role with the capability to manage data requests.
308         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
309         // Assign u1 as a manager.
310         role_assign($managerroleid, $u1->id, $context->id);
312         // Map the manager role to the DPO role.
313         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
315         // Create the sample data request.
316         $this->setUser($s1);
317         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
318         $requestid = $datarequest->get('id');
320         $this->setUser($u1);
321         $this->expectException(moodle_exception::class);
322         api::approve_data_request($requestid);
323     }
325     /**
326      * Test for api::approve_data_request() when called by a user who doesn't have the DPO role.
327      */
328     public function test_approve_data_request_non_dpo_user() {
329         $this->resetAfterTest();
331         $generator = new testing_data_generator();
332         $student = $generator->create_user();
333         $teacher = $generator->create_user();
335         // Create the sample data request.
336         $this->setUser($student);
337         $datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
339         $requestid = $datarequest->get('id');
340     }
342     /**
343      * Test for api::can_contact_dpo()
344      */
345     public function test_can_contact_dpo() {
346         $this->resetAfterTest();
348         // Default ('contactdataprotectionofficer' is disabled by default).
349         $this->assertFalse(api::can_contact_dpo());
351         // Enable.
352         set_config('contactdataprotectionofficer', 1, 'tool_dataprivacy');
353         $this->assertTrue(api::can_contact_dpo());
355         // Disable again.
356         set_config('contactdataprotectionofficer', 0, 'tool_dataprivacy');
357         $this->assertFalse(api::can_contact_dpo());
358     }
360     /**
361      * Test for api::can_manage_data_requests()
362      */
363     public function test_can_manage_data_requests() {
364         global $DB;
366         $this->resetAfterTest();
368         // No configured site DPOs yet.
369         $admin = get_admin();
370         $this->assertTrue(api::can_manage_data_requests($admin->id));
372         $generator = new testing_data_generator();
373         $dpo = $generator->create_user();
374         $nondpocapable = $generator->create_user();
375         $nondpoincapable = $generator->create_user();
377         $context = context_system::instance();
379         // Manager role.
380         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
381         // Give the manager role with the capability to manage data requests.
382         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
383         // Assign u1 as a manager.
384         role_assign($managerroleid, $dpo->id, $context->id);
386         // Editing teacher role.
387         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
388         // Give the editing teacher role with the capability to manage data requests.
389         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
390         // Assign u2 as an editing teacher.
391         role_assign($editingteacherroleid, $nondpocapable->id, $context->id);
393         // Map only the manager role to the DPO role.
394         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
396         // User with capability and has DPO role.
397         $this->assertTrue(api::can_manage_data_requests($dpo->id));
398         // User with capability but has no DPO role.
399         $this->assertFalse(api::can_manage_data_requests($nondpocapable->id));
400         // User without the capability and has no DPO role.
401         $this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
402     }
404     /**
405      * Test that a user who has no capability to make any data requests for children cannot create data requests for any
406      * other user.
407      */
408     public function test_can_create_data_request_for_user_no() {
409         $this->resetAfterTest();
411         $parent = $this->getDataGenerator()->create_user();
412         $otheruser = $this->getDataGenerator()->create_user();
414         $this->setUser($parent);
415         $this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
416     }
418     /**
419      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
420      * for any other user.
421      */
422     public function test_can_create_data_request_for_user_some() {
423         $this->resetAfterTest();
425         $parent = $this->getDataGenerator()->create_user();
426         $child = $this->getDataGenerator()->create_user();
427         $otheruser = $this->getDataGenerator()->create_user();
429         $systemcontext = \context_system::instance();
430         $parentrole = $this->getDataGenerator()->create_role();
431         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
432         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
434         $this->setUser($parent);
435         $this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
436     }
438     /**
439      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
440      * for any other user.
441      */
442     public function test_can_create_data_request_for_user_own_child() {
443         $this->resetAfterTest();
445         $parent = $this->getDataGenerator()->create_user();
446         $child = $this->getDataGenerator()->create_user();
448         $systemcontext = \context_system::instance();
449         $parentrole = $this->getDataGenerator()->create_role();
450         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
451         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
453         $this->setUser($parent);
454         $this->assertTrue(api::can_create_data_request_for_user($child->id));
455     }
457     /**
458      * Test that a user who has no capability to make any data requests for children cannot create data requests for any
459      * other user.
460      */
461     public function test_require_can_create_data_request_for_user_no() {
462         $this->resetAfterTest();
464         $parent = $this->getDataGenerator()->create_user();
465         $otheruser = $this->getDataGenerator()->create_user();
467         $this->setUser($parent);
468         $this->expectException('required_capability_exception');
469         api::require_can_create_data_request_for_user($otheruser->id);
470     }
472     /**
473      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
474      * for any other user.
475      */
476     public function test_require_can_create_data_request_for_user_some() {
477         $this->resetAfterTest();
479         $parent = $this->getDataGenerator()->create_user();
480         $child = $this->getDataGenerator()->create_user();
481         $otheruser = $this->getDataGenerator()->create_user();
483         $systemcontext = \context_system::instance();
484         $parentrole = $this->getDataGenerator()->create_role();
485         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
486         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
488         $this->setUser($parent);
489         $this->expectException('required_capability_exception');
490         api::require_can_create_data_request_for_user($otheruser->id);
491     }
493     /**
494      * Test that a user who has the capability to make any data requests for one other user cannot create data requests
495      * for any other user.
496      */
497     public function test_require_can_create_data_request_for_user_own_child() {
498         $this->resetAfterTest();
500         $parent = $this->getDataGenerator()->create_user();
501         $child = $this->getDataGenerator()->create_user();
503         $systemcontext = \context_system::instance();
504         $parentrole = $this->getDataGenerator()->create_role();
505         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
506         role_assign($parentrole, $parent->id, \context_user::instance($child->id));
508         $this->setUser($parent);
509         $this->assertTrue(api::require_can_create_data_request_for_user($child->id));
510     }
512     /**
513      * Test for api::can_download_data_request_for_user()
514      */
515     public function test_can_download_data_request_for_user() {
516         $this->resetAfterTest();
518         $generator = $this->getDataGenerator();
520         // Three victims.
521         $victim1 = $generator->create_user();
522         $victim2 = $generator->create_user();
523         $victim3 = $generator->create_user();
525         // Assign a user as victim 1's parent.
526         $systemcontext = \context_system::instance();
527         $parentrole = $generator->create_role();
528         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
529         $parent = $generator->create_user();
530         role_assign($parentrole, $parent->id, \context_user::instance($victim1->id));
532         // Assign another user as data access wonder woman.
533         $wonderrole = $generator->create_role();
534         assign_capability('tool/dataprivacy:downloadallrequests', CAP_ALLOW, $wonderrole, $systemcontext);
535         $staff = $generator->create_user();
536         role_assign($wonderrole, $staff->id, $systemcontext);
538         // Finally, victim 3 has been naughty; stop them accessing their own data.
539         $naughtyrole = $generator->create_role();
540         assign_capability('tool/dataprivacy:downloadownrequest', CAP_PROHIBIT, $naughtyrole, $systemcontext);
541         role_assign($naughtyrole, $victim3->id, $systemcontext);
543         // Victims 1 and 2 can access their own data, regardless of who requested it.
544         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $victim1->id));
545         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $victim2->id));
547         // Victim 3 cannot access his own data.
548         $this->assertFalse(api::can_download_data_request_for_user($victim3->id, $victim3->id, $victim3->id));
550         // Victims 1 and 2 cannot access another victim's data.
551         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $victim1->id, $victim1->id));
552         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $victim2->id));
554         // Staff can access everyone's data.
555         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $staff->id));
556         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $staff->id));
557         $this->assertTrue(api::can_download_data_request_for_user($victim3->id, $staff->id, $staff->id));
559         // Parent can access victim 1's data only if they requested it.
560         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $parent->id, $parent->id));
561         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $parent->id));
562         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $parent->id, $parent->id));
563     }
565     /**
566      * Test for api::create_data_request()
567      */
568     public function test_create_data_request() {
569         $this->resetAfterTest();
571         $generator = new testing_data_generator();
572         $user = $generator->create_user();
573         $comment = 'sample comment';
575         // Login as user.
576         $this->setUser($user->id);
578         // Test data request creation.
579         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
580         $this->assertEquals($user->id, $datarequest->get('userid'));
581         $this->assertEquals($user->id, $datarequest->get('requestedby'));
582         $this->assertEquals(0, $datarequest->get('dpo'));
583         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
584         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
585         $this->assertEquals($comment, $datarequest->get('comments'));
587         // Test adhoc task creation.
588         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
589         $this->assertCount(1, $adhoctasks);
590     }
592     /**
593      * Test for api::create_data_request() made by DPO.
594      */
595     public function test_create_data_request_by_dpo() {
596         global $USER;
598         $this->resetAfterTest();
600         $generator = new testing_data_generator();
601         $user = $generator->create_user();
602         $comment = 'sample comment';
604         // Login as DPO (Admin is DPO by default).
605         $this->setAdminUser();
607         // Test data request creation.
608         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
609         $this->assertEquals($user->id, $datarequest->get('userid'));
610         $this->assertEquals($USER->id, $datarequest->get('requestedby'));
611         $this->assertEquals($USER->id, $datarequest->get('dpo'));
612         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
613         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
614         $this->assertEquals($comment, $datarequest->get('comments'));
616         // Test adhoc task creation.
617         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
618         $this->assertCount(1, $adhoctasks);
619     }
621     /**
622      * Test for api::create_data_request() made by a parent.
623      */
624     public function test_create_data_request_by_parent() {
625         global $DB;
627         $this->resetAfterTest();
629         $generator = new testing_data_generator();
630         $user = $generator->create_user();
631         $parent = $generator->create_user();
632         $comment = 'sample comment';
634         // Get the teacher role pretend it's the parent roles ;).
635         $systemcontext = context_system::instance();
636         $usercontext = context_user::instance($user->id);
637         $parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
638         // Give the manager role with the capability to manage data requests.
639         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
640         // Assign the parent to user.
641         role_assign($parentroleid, $parent->id, $usercontext->id);
643         // Login as the user's parent.
644         $this->setUser($parent);
646         // Test data request creation.
647         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
648         $this->assertEquals($user->id, $datarequest->get('userid'));
649         $this->assertEquals($parent->id, $datarequest->get('requestedby'));
650         $this->assertEquals(0, $datarequest->get('dpo'));
651         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
652         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
653         $this->assertEquals($comment, $datarequest->get('comments'));
655         // Test adhoc task creation.
656         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
657         $this->assertCount(1, $adhoctasks);
658     }
660     /**
661      * Test for api::deny_data_request()
662      */
663     public function test_deny_data_request() {
664         $this->resetAfterTest();
666         $generator = new testing_data_generator();
667         $user = $generator->create_user();
668         $comment = 'sample comment';
670         // Login as user.
671         $this->setUser($user->id);
673         // Test data request creation.
674         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
676         // Login as the admin (default DPO when no one is set).
677         $this->setAdminUser();
679         // Make this ready for approval.
680         api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
682         // Deny the data request.
683         $result = api::deny_data_request($datarequest->get('id'));
684         $this->assertTrue($result);
685     }
687     /**
688      * Data provider for \tool_dataprivacy_api_testcase::test_get_data_requests().
689      *
690      * @return array
691      */
692     public function get_data_requests_provider() {
693         $completeonly = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DOWNLOAD_READY, api::DATAREQUEST_STATUS_DELETED];
694         $completeandcancelled = array_merge($completeonly, [api::DATAREQUEST_STATUS_CANCELLED]);
696         return [
697             // Own data requests.
698             ['user', false, $completeonly],
699             // Non-DPO fetching all requets.
700             ['user', true, $completeonly],
701             // Admin fetching all completed and cancelled requests.
702             ['dpo', true, $completeandcancelled],
703             // Admin fetching all completed requests.
704             ['dpo', true, $completeonly],
705             // Guest fetching all requests.
706             ['guest', true, $completeonly],
707         ];
708     }
710     /**
711      * Test for api::get_data_requests()
712      *
713      * @dataProvider get_data_requests_provider
714      * @param string $usertype The type of the user logging in.
715      * @param boolean $fetchall Whether to fetch all records.
716      * @param int[] $statuses Status filters.
717      */
718     public function test_get_data_requests($usertype, $fetchall, $statuses) {
719         $this->resetAfterTest();
721         $generator = new testing_data_generator();
722         $user1 = $generator->create_user();
723         $user2 = $generator->create_user();
724         $user3 = $generator->create_user();
725         $user4 = $generator->create_user();
726         $user5 = $generator->create_user();
727         $users = [$user1, $user2, $user3, $user4, $user5];
729         switch ($usertype) {
730             case 'user':
731                 $loggeduser = $user1;
732                 break;
733             case 'dpo':
734                 $loggeduser = get_admin();
735                 break;
736             case 'guest':
737                 $loggeduser = guest_user();
738                 break;
739         }
741         $comment = 'Data %s request comment by user %d';
742         $exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
743         $deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
744         // Make a data requests for the users.
745         foreach ($users as $user) {
746             $this->setUser($user);
747             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $exportstring, $user->id));
748             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $deletionstring, $user->id));
749         }
751         // Log in as the target user.
752         $this->setUser($loggeduser);
753         // Get records count based on the filters.
754         $userid = $loggeduser->id;
755         if ($fetchall) {
756             $userid = 0;
757         }
758         $count = api::get_data_requests_count($userid);
759         if (api::is_site_dpo($loggeduser->id)) {
760             // DPOs should see all the requests.
761             $this->assertEquals(count($users) * 2, $count);
762         } else {
763             if (empty($userid)) {
764                 // There should be no data requests for this user available.
765                 $this->assertEquals(0, $count);
766             } else {
767                 // There should be only one (request with pending status).
768                 $this->assertEquals(2, $count);
769             }
770         }
771         // Get data requests.
772         $requests = api::get_data_requests($userid);
773         // The number of requests should match the count.
774         $this->assertCount($count, $requests);
776         // Test filtering by status.
777         if ($count && !empty($statuses)) {
778             $filteredcount = api::get_data_requests_count($userid, $statuses);
779             // There should be none as they are all pending.
780             $this->assertEquals(0, $filteredcount);
781             $filteredrequests = api::get_data_requests($userid, $statuses);
782             $this->assertCount($filteredcount, $filteredrequests);
784             $statuscounts = [];
785             foreach ($statuses as $stat) {
786                 $statuscounts[$stat] = 0;
787             }
788             $numstatus = count($statuses);
789             // Get all requests with status filter and update statuses, randomly.
790             foreach ($requests as $request) {
791                 if (rand(0, 1)) {
792                     continue;
793                 }
795                 if ($numstatus > 1) {
796                     $index = rand(0, $numstatus - 1);
797                     $status = $statuses[$index];
798                 } else {
799                     $status = reset($statuses);
800                 }
801                 $statuscounts[$status]++;
802                 api::update_request_status($request->get('id'), $status);
803             }
804             $total = array_sum($statuscounts);
805             $filteredcount = api::get_data_requests_count($userid, $statuses);
806             $this->assertEquals($total, $filteredcount);
807             $filteredrequests = api::get_data_requests($userid, $statuses);
808             $this->assertCount($filteredcount, $filteredrequests);
809             // Confirm the filtered requests match the status filter(s).
810             foreach ($filteredrequests as $request) {
811                 $this->assertContains($request->get('status'), $statuses);
812             }
814             if ($numstatus > 1) {
815                 // Fetch by individual status to check the numbers match.
816                 foreach ($statuses as $status) {
817                     $filteredcount = api::get_data_requests_count($userid, [$status]);
818                     $this->assertEquals($statuscounts[$status], $filteredcount);
819                     $filteredrequests = api::get_data_requests($userid, [$status]);
820                     $this->assertCount($filteredcount, $filteredrequests);
821                 }
822             }
823         }
824     }
826     /**
827      * Data provider for test_has_ongoing_request.
828      */
829     public function status_provider() {
830         return [
831             [api::DATAREQUEST_STATUS_PENDING, true],
832             [api::DATAREQUEST_STATUS_PREPROCESSING, true],
833             [api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
834             [api::DATAREQUEST_STATUS_APPROVED, true],
835             [api::DATAREQUEST_STATUS_PROCESSING, true],
836             [api::DATAREQUEST_STATUS_COMPLETE, false],
837             [api::DATAREQUEST_STATUS_CANCELLED, false],
838             [api::DATAREQUEST_STATUS_REJECTED, false],
839             [api::DATAREQUEST_STATUS_DOWNLOAD_READY, false],
840             [api::DATAREQUEST_STATUS_EXPIRED, false],
841             [api::DATAREQUEST_STATUS_DELETED, false],
842         ];
843     }
845     /**
846      * Test for api::has_ongoing_request()
847      *
848      * @dataProvider status_provider
849      * @param int $status The request status.
850      * @param bool $expected The expected result.
851      */
852     public function test_has_ongoing_request($status, $expected) {
853         $this->resetAfterTest();
855         $generator = new testing_data_generator();
856         $user1 = $generator->create_user();
858         // Make a data request as user 1.
859         $this->setUser($user1);
860         $request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
861         // Set the status.
862         api::update_request_status($request->get('id'), $status);
864         // Check if this request is ongoing.
865         $result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
866         $this->assertEquals($expected, $result);
867     }
869     /**
870      * Test for api::is_active()
871      *
872      * @dataProvider status_provider
873      * @param int $status The request status
874      * @param bool $expected The expected result
875      */
876     public function test_is_active($status, $expected) {
877         // Check if this request is ongoing.
878         $result = api::is_active($status);
879         $this->assertEquals($expected, $result);
880     }
882     /**
883      * Test for api::is_site_dpo()
884      */
885     public function test_is_site_dpo() {
886         global $DB;
888         $this->resetAfterTest();
890         // No configured site DPOs yet.
891         $admin = get_admin();
892         $this->assertTrue(api::is_site_dpo($admin->id));
894         $generator = new testing_data_generator();
895         $dpo = $generator->create_user();
896         $nondpo = $generator->create_user();
898         $context = context_system::instance();
900         // Manager role.
901         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
902         // Give the manager role with the capability to manage data requests.
903         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
904         // Assign u1 as a manager.
905         role_assign($managerroleid, $dpo->id, $context->id);
907         // Map only the manager role to the DPO role.
908         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
910         // User is a DPO.
911         $this->assertTrue(api::is_site_dpo($dpo->id));
912         // User is not a DPO.
913         $this->assertFalse(api::is_site_dpo($nondpo->id));
914     }
916     /**
917      * Data provider function for test_notify_dpo
918      *
919      * @return array
920      */
921     public function notify_dpo_provider() {
922         return [
923             [false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
924             [false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
925             [false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
926             [true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
927         ];
928     }
930     /**
931      * Test for api::notify_dpo()
932      *
933      * @dataProvider notify_dpo_provider
934      * @param bool $byadmin Whether the admin requests data on behalf of the user
935      * @param int $type The request type
936      * @param string $typestringid The request lang string identifier
937      * @param string $comments The requestor's message to the DPO.
938      */
939     public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
940         $this->resetAfterTest();
942         $generator = new testing_data_generator();
943         $user1 = $generator->create_user();
944         // Let's just use admin as DPO (It's the default if not set).
945         $dpo = get_admin();
946         if ($byadmin) {
947             $this->setAdminUser();
948             $requestedby = $dpo;
949         } else {
950             $this->setUser($user1);
951             $requestedby = $user1;
952         }
954         // Make a data request for user 1.
955         $request = api::create_data_request($user1->id, $type, $comments);
957         $sink = $this->redirectMessages();
958         $messageid = api::notify_dpo($dpo, $request);
959         $this->assertNotFalse($messageid);
960         $messages = $sink->get_messages();
961         $this->assertCount(1, $messages);
962         $message = reset($messages);
964         // Check some of the message properties.
965         $this->assertEquals($requestedby->id, $message->useridfrom);
966         $this->assertEquals($dpo->id, $message->useridto);
967         $typestring = get_string($typestringid, 'tool_dataprivacy');
968         $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
969         $this->assertEquals($subject, $message->subject);
970         $this->assertEquals('tool_dataprivacy', $message->component);
971         $this->assertEquals('contactdataprotectionofficer', $message->eventtype);
972         $this->assertContains(fullname($dpo), $message->fullmessage);
973         $this->assertContains(fullname($user1), $message->fullmessage);
974     }
976     /**
977      * Test data purposes CRUD actions.
978      *
979      * @return null
980      */
981     public function test_purpose_crud() {
982         $this->resetAfterTest();
984         $this->setAdminUser();
986         // Add.
987         $purpose = api::create_purpose((object)[
988             'name' => 'bbb',
989             'description' => '<b>yeah</b>',
990             'descriptionformat' => 1,
991             'retentionperiod' => 'PT1M',
992             'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
993         ]);
994         $this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
995         $this->assertEquals('bbb', $purpose->get('name'));
996         $this->assertEquals('PT1M', $purpose->get('retentionperiod'));
997         $this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
999         // Update.
1000         $purpose->set('retentionperiod', 'PT2M');
1001         $purpose = api::update_purpose($purpose->to_record());
1002         $this->assertEquals('PT2M', $purpose->get('retentionperiod'));
1004         // Retrieve.
1005         $purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1006         $purposes = api::get_purposes();
1007         $this->assertCount(2, $purposes);
1008         $this->assertEquals('aaa', $purposes[0]->get('name'));
1009         $this->assertEquals('bbb', $purposes[1]->get('name'));
1011         // Delete.
1012         api::delete_purpose($purposes[0]->get('id'));
1013         $this->assertCount(1, api::get_purposes());
1014         api::delete_purpose($purposes[1]->get('id'));
1015         $this->assertCount(0, api::get_purposes());
1016     }
1018     /**
1019      * Test data categories CRUD actions.
1020      *
1021      * @return null
1022      */
1023     public function test_category_crud() {
1024         $this->resetAfterTest();
1026         $this->setAdminUser();
1028         // Add.
1029         $category = api::create_category((object)[
1030             'name' => 'bbb',
1031             'description' => '<b>yeah</b>',
1032             'descriptionformat' => 1
1033         ]);
1034         $this->assertInstanceOf('\tool_dataprivacy\category', $category);
1035         $this->assertEquals('bbb', $category->get('name'));
1037         // Update.
1038         $category->set('name', 'bcd');
1039         $category = api::update_category($category->to_record());
1040         $this->assertEquals('bcd', $category->get('name'));
1042         // Retrieve.
1043         $category = api::create_category((object)['name' => 'aaa']);
1044         $categories = api::get_categories();
1045         $this->assertCount(2, $categories);
1046         $this->assertEquals('aaa', $categories[0]->get('name'));
1047         $this->assertEquals('bcd', $categories[1]->get('name'));
1049         // Delete.
1050         api::delete_category($categories[0]->get('id'));
1051         $this->assertCount(1, api::get_categories());
1052         api::delete_category($categories[1]->get('id'));
1053         $this->assertCount(0, api::get_categories());
1054     }
1056     /**
1057      * Test context instances.
1058      *
1059      * @return null
1060      */
1061     public function test_context_instances() {
1062         global $DB;
1064         $this->resetAfterTest();
1066         $this->setAdminUser();
1068         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1070         $coursecontext1 = \context_course::instance($courses[0]->id);
1071         $coursecontext2 = \context_course::instance($courses[1]->id);
1073         $record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
1074             'categoryid' => $categories[0]->get('id')];
1075         $contextinstance1 = api::set_context_instance($record1);
1077         $record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
1078             'categoryid' => $categories[1]->get('id')];
1079         $contextinstance2 = api::set_context_instance($record2);
1081         $this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
1083         api::unset_context_instance($contextinstance1);
1084         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1086         $update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
1087             'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
1088         $contextinstance2 = api::set_context_instance($update);
1089         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1090     }
1092     /**
1093      * Test contextlevel.
1094      *
1095      * @return null
1096      */
1097     public function test_contextlevel() {
1098         global $DB;
1100         $this->resetAfterTest();
1102         $this->setAdminUser();
1103         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1105         $record = (object)[
1106             'purposeid' => $purposes[0]->get('id'),
1107             'categoryid' => $categories[0]->get('id'),
1108             'contextlevel' => CONTEXT_SYSTEM,
1109         ];
1110         $contextlevel = api::set_contextlevel($record);
1111         $this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
1112         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1113         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1114         $this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
1116         // Now update it.
1117         $record->purposeid = $purposes[1]->get('id');
1118         $contextlevel = api::set_contextlevel($record);
1119         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1120         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1121         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
1123         $record->contextlevel = CONTEXT_USER;
1124         $contextlevel = api::set_contextlevel($record);
1125         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
1126     }
1128     /**
1129      * Test effective context levels purpose and category defaults.
1130      *
1131      * @return null
1132      */
1133     public function test_effective_contextlevel_defaults() {
1134         $this->setAdminUser();
1136         $this->resetAfterTest();
1138         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1140         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1141         $this->assertEquals(false, $purposeid);
1142         $this->assertEquals(false, $categoryid);
1144         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1145             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1146         );
1147         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1149         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1150         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1151         $this->assertEquals(false, $categoryid);
1153         // Course inherits from system if not defined.
1154         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1155         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1156         $this->assertEquals(false, $categoryid);
1158         // Course defined values should have preference.
1159         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1160             \context_helper::get_class_for_level(CONTEXT_COURSE)
1161         );
1162         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1163         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1165         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1166         $this->assertEquals($purposes[1]->get('id'), $purposeid);
1167         $this->assertEquals($categories[0]->get('id'), $categoryid);
1169         // Context level defaults are also allowed to be set to 'inherit'.
1170         set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
1172         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1173         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1174         $this->assertEquals($categories[0]->get('id'), $categoryid);
1176         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_MODULE);
1177         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1178         $this->assertEquals($categories[0]->get('id'), $categoryid);
1179     }
1181     public function test_get_effective_contextlevel_category() {
1182         // Before setup, get_effective_contextlevel_purpose will return false.
1183         $this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
1184     }
1186     /**
1187      * Test effective contextlevel return.
1188      */
1189     public function test_effective_contextlevel() {
1190         $this->setAdminUser();
1192         $this->resetAfterTest();
1194         // Before setup, get_effective_contextlevel_purpose will return false.
1195         $this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
1197         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1199         // Set the system context level to purpose 1.
1200         $record = (object)[
1201             'contextlevel' => CONTEXT_SYSTEM,
1202             'purposeid' => $purposes[1]->get('id'),
1203             'categoryid' => $categories[1]->get('id'),
1204         ];
1205         api::set_contextlevel($record);
1207         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM);
1208         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1210         // Value 'not set' will get the default value for the context level. For context level defaults
1211         // both 'not set' and 'inherit' result in inherit, so the parent context (system) default
1212         // will be retrieved.
1213         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1214         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1216         // The behaviour forcing an inherit from context system should result in the same effective
1217         // purpose.
1218         $record->purposeid = context_instance::INHERIT;
1219         $record->contextlevel = CONTEXT_USER;
1220         api::set_contextlevel($record);
1221         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1222         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1224         $record->purposeid = $purposes[2]->get('id');
1225         $record->contextlevel = CONTEXT_USER;
1226         api::set_contextlevel($record);
1228         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1229         $this->assertEquals($purposes[2]->get('id'), $purpose->get('id'));
1231         // Only system and user allowed.
1232         $this->expectException(coding_exception::class);
1233         $record->contextlevel = CONTEXT_COURSE;
1234         $record->purposeid = $purposes[1]->get('id');
1235         api::set_contextlevel($record);
1236     }
1238     /**
1239      * Test effective context purposes and categories.
1240      *
1241      * @return null
1242      */
1243     public function test_effective_context() {
1244         $this->resetAfterTest();
1246         $this->setAdminUser();
1248         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1250         // Define system defaults (all context levels below will inherit).
1251         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1252             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1253         );
1254         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1255         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1257         // Define course defaults.
1258         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1259             \context_helper::get_class_for_level(CONTEXT_COURSE)
1260         );
1261         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1262         set_config($categoryvar, $categories[1]->get('id'), 'tool_dataprivacy');
1264         $course0context = \context_course::instance($courses[0]->id);
1265         $course1context = \context_course::instance($courses[1]->id);
1266         $mod0context = \context_module::instance($modules[0]->cmid);
1267         $mod1context = \context_module::instance($modules[1]->cmid);
1269         // Set course instance values.
1270         $record = (object)[
1271             'contextid' => $course0context->id,
1272             'purposeid' => $purposes[1]->get('id'),
1273             'categoryid' => $categories[2]->get('id'),
1274         ];
1275         api::set_context_instance($record);
1276         $category = api::get_effective_context_category($course0context);
1277         $this->assertEquals($record->categoryid, $category->get('id'));
1279         // Module instances get the context level default if nothing specified.
1280         $category = api::get_effective_context_category($mod0context);
1281         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1283         // Module instances get the parent context category if they inherit.
1284         $record->contextid = $mod0context->id;
1285         $record->categoryid = context_instance::INHERIT;
1286         api::set_context_instance($record);
1287         $category = api::get_effective_context_category($mod0context);
1288         $this->assertEquals($categories[2]->get('id'), $category->get('id'));
1290         // The $forcedvalue param allows us to override the actual value (method php-docs for more info).
1291         $category = api::get_effective_context_category($mod0context, $categories[1]->get('id'));
1292         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1293         $category = api::get_effective_context_category($mod0context, $categories[0]->get('id'));
1294         $this->assertEquals($categories[0]->get('id'), $category->get('id'));
1296         // Module instances get the parent context category if they inherit; in
1297         // this case the parent context category is not set so it should use the
1298         // context level default (see 'Define course defaults' above).
1299         $record->contextid = $mod1context->id;
1300         $record->categoryid = context_instance::INHERIT;
1301         api::set_context_instance($record);
1302         $category = api::get_effective_context_category($mod1context);
1303         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1305         // User instances use the value set at user context level instead of the user default.
1307         // User defaults to cat 0 and user context level to 1.
1308         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1309             \context_helper::get_class_for_level(CONTEXT_USER)
1310         );
1311         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1312         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1313         $usercontextlevel = (object)[
1314             'contextlevel' => CONTEXT_USER,
1315             'purposeid' => $purposes[1]->get('id'),
1316             'categoryid' => $categories[1]->get('id'),
1317         ];
1318         api::set_contextlevel($usercontextlevel);
1320         $newuser = $this->getDataGenerator()->create_user();
1321         $usercontext = \context_user::instance($newuser->id);
1322         $category = api::get_effective_context_category($usercontext);
1323         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1324     }
1326     /**
1327      * Creates test purposes and categories.
1328      *
1329      * @return null
1330      */
1331     protected function add_purposes_and_categories() {
1332         $this->resetAfterTest();
1334         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1335         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
1336         $purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
1338         $cat1 = api::create_category((object)['name' => 'a']);
1339         $cat2 = api::create_category((object)['name' => 'b']);
1340         $cat3 = api::create_category((object)['name' => 'c']);
1342         $course1 = $this->getDataGenerator()->create_course();
1343         $course2 = $this->getDataGenerator()->create_course();
1345         $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
1346         $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
1348         return [
1349             [$purpose1, $purpose2, $purpose3],
1350             [$cat1, $cat2, $cat3],
1351             [$course1, $course2],
1352             [$module1, $module2]
1353         ];
1354     }
1356     /**
1357      * Test that delete requests filter out protected purpose contexts.
1358      */
1359     public function test_add_request_contexts_with_status_delete() {
1360         $this->resetAfterTest();
1362         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
1363         $contextids = $data->list->get_contextids();
1365         $this->assertCount(1, $contextids);
1366         $this->assertEquals($data->contexts->unprotected, $contextids);
1367     }
1369     /**
1370      * Test that export requests don't filter out protected purpose contexts.
1371      */
1372     public function test_add_request_contexts_with_status_export() {
1373         $this->resetAfterTest();
1375         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
1376         $contextids = $data->list->get_contextids();
1378         $this->assertCount(2, $contextids);
1379         $this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
1380     }
1382     /**
1383      * Test that delete requests do not filter out protected purpose contexts if they are already expired.
1384      */
1385     public function test_add_request_contexts_with_status_delete_course_expired_protected() {
1386         global $DB;
1388         $this->resetAfterTest();
1390         $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
1391         $purposes->course->set('protected', 1)->save();
1393         $user = $this->getDataGenerator()->create_user();
1394         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
1395         $coursecontext = \context_course::instance($course->id);
1397         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1399         $collection = new \core_privacy\local\request\contextlist_collection($user->id);
1400         $contextlist = new \core_privacy\local\request\contextlist();
1401         $contextlist->set_component('tool_dataprivacy');
1402         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx1)', ['ctx1' => $coursecontext->id]);
1403         $collection->add_contextlist($contextlist);
1405         $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
1407         $purposes->course->set('protected', 1)->save();
1408         api::add_request_contexts_with_status($collection, $request->get('id'), contextlist_context::STATUS_APPROVED);
1410         $requests = contextlist_context::get_records();
1411         $this->assertCount(1, $requests);
1412     }
1414     /**
1415      * Test that delete requests does filter out protected purpose contexts which are not expired.
1416      */
1417     public function test_add_request_contexts_with_status_delete_course_unexpired_protected() {
1418         global $DB;
1420         $this->resetAfterTest();
1422         $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
1423         $purposes->course->set('protected', 1)->save();
1425         $user = $this->getDataGenerator()->create_user();
1426         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
1427         $coursecontext = \context_course::instance($course->id);
1429         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1431         $collection = new \core_privacy\local\request\contextlist_collection($user->id);
1432         $contextlist = new \core_privacy\local\request\contextlist();
1433         $contextlist->set_component('tool_dataprivacy');
1434         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx1)', ['ctx1' => $coursecontext->id]);
1435         $collection->add_contextlist($contextlist);
1437         $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
1439         $purposes->course->set('protected', 1)->save();
1440         api::add_request_contexts_with_status($collection, $request->get('id'), contextlist_context::STATUS_APPROVED);
1442         $requests = contextlist_context::get_records();
1443         $this->assertCount(0, $requests);
1444     }
1446     /**
1447      * Test that delete requests do not filter out unexpired contexts if they are not protected.
1448      */
1449     public function test_add_request_contexts_with_status_delete_course_unexpired_unprotected() {
1450         global $DB;
1452         $this->resetAfterTest();
1454         $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
1455         $purposes->course->set('protected', 1)->save();
1457         $user = $this->getDataGenerator()->create_user();
1458         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
1459         $coursecontext = \context_course::instance($course->id);
1461         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1463         $collection = new \core_privacy\local\request\contextlist_collection($user->id);
1464         $contextlist = new \core_privacy\local\request\contextlist();
1465         $contextlist->set_component('tool_dataprivacy');
1466         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx1)', ['ctx1' => $coursecontext->id]);
1467         $collection->add_contextlist($contextlist);
1469         $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
1471         $purposes->course->set('protected', 0)->save();
1472         api::add_request_contexts_with_status($collection, $request->get('id'), contextlist_context::STATUS_APPROVED);
1474         $requests = contextlist_context::get_records();
1475         $this->assertCount(1, $requests);
1476     }
1478     /**
1479      * Test that delete requests do not filter out protected purpose contexts if they are already expired.
1480      */
1481     public function test_get_approved_contextlist_collection_for_request_delete_course_expired_protected() {
1482         $this->resetAfterTest();
1484         $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
1485         $purposes->course->set('protected', 1)->save();
1487         $user = $this->getDataGenerator()->create_user();
1488         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
1489         $coursecontext = \context_course::instance($course->id);
1491         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1493         // Create the request, with its contextlist and context.
1494         $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
1495         $contextlist = new \tool_dataprivacy\contextlist(0, (object) ['component' => 'tool_dataprivacy']);
1496         $contextlist->save();
1498         $clcontext = new \tool_dataprivacy\contextlist_context(0, (object) [
1499                 'contextid' => $coursecontext->id,
1500                 'status' => contextlist_context::STATUS_APPROVED,
1501                 'contextlistid' => $contextlist->get('id'),
1502             ]);
1503         $clcontext->save();
1505         $rcl = new \tool_dataprivacy\request_contextlist(0, (object) [
1506                 'requestid' => $request->get('id'),
1507                 'contextlistid' => $contextlist->get('id'),
1508             ]);
1509         $rcl->save();
1511         $purposes->course->set('protected', 1)->save();
1512         $collection = api::get_approved_contextlist_collection_for_request($request);
1514         $this->assertCount(1, $collection);
1516         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1517         $this->assertCount(1, $list);
1518     }
1520     /**
1521      * Test that delete requests does filter out protected purpose contexts which are not expired.
1522      */
1523     public function test_get_approved_contextlist_collection_for_request_delete_course_unexpired_protected() {
1524         $this->resetAfterTest();
1526         $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
1527         $purposes->course->set('protected', 1)->save();
1529         $user = $this->getDataGenerator()->create_user();
1530         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
1531         $coursecontext = \context_course::instance($course->id);
1533         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1535         // Create the request, with its contextlist and context.
1536         $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
1537         $contextlist = new \tool_dataprivacy\contextlist(0, (object) ['component' => 'tool_dataprivacy']);
1538         $contextlist->save();
1540         $clcontext = new \tool_dataprivacy\contextlist_context(0, (object) [
1541                 'contextid' => $coursecontext->id,
1542                 'status' => contextlist_context::STATUS_APPROVED,
1543                 'contextlistid' => $contextlist->get('id'),
1544             ]);
1545         $clcontext->save();
1547         $rcl = new \tool_dataprivacy\request_contextlist(0, (object) [
1548                 'requestid' => $request->get('id'),
1549                 'contextlistid' => $contextlist->get('id'),
1550             ]);
1551         $rcl->save();
1553         $purposes->course->set('protected', 1)->save();
1554         $collection = api::get_approved_contextlist_collection_for_request($request);
1556         $this->assertCount(0, $collection);
1558         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1559         $this->assertEmpty($list);
1560     }
1562     /**
1563      * Test that delete requests do not filter out unexpired contexts if they are not protected.
1564      */
1565     public function test_get_approved_contextlist_collection_for_request_delete_course_unexpired_unprotected() {
1566         $this->resetAfterTest();
1568         $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
1569         $purposes->course->set('protected', 1)->save();
1571         $user = $this->getDataGenerator()->create_user();
1572         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
1573         $coursecontext = \context_course::instance($course->id);
1575         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1577         // Create the request, with its contextlist and context.
1578         $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
1579         $contextlist = new \tool_dataprivacy\contextlist(0, (object) ['component' => 'tool_dataprivacy']);
1580         $contextlist->save();
1582         $clcontext = new \tool_dataprivacy\contextlist_context(0, (object) [
1583                 'contextid' => $coursecontext->id,
1584                 'status' => contextlist_context::STATUS_APPROVED,
1585                 'contextlistid' => $contextlist->get('id'),
1586             ]);
1587         $clcontext->save();
1589         $rcl = new \tool_dataprivacy\request_contextlist(0, (object) [
1590                 'requestid' => $request->get('id'),
1591                 'contextlistid' => $contextlist->get('id'),
1592             ]);
1593         $rcl->save();
1595         $purposes->course->set('protected', 0)->save();
1596         $collection = api::get_approved_contextlist_collection_for_request($request);
1598         $this->assertCount(1, $collection);
1600         $list = $collection->get_contextlist_for_component('tool_dataprivacy');
1601         $this->assertCount(1, $list);
1602     }
1604     /**
1605      * Data provider for \tool_dataprivacy_api_testcase::test_set_context_defaults
1606      */
1607     public function set_context_defaults_provider() {
1608         $contextlevels = [
1609             [CONTEXT_COURSECAT],
1610             [CONTEXT_COURSE],
1611             [CONTEXT_MODULE],
1612             [CONTEXT_BLOCK],
1613         ];
1614         $paramsets = [
1615             [true, true, false, false], // Inherit category and purpose, Not for activity, Don't override.
1616             [true, false, false, false], // Inherit category but not purpose, Not for activity, Don't override.
1617             [false, true, false, false], // Inherit purpose but not category, Not for activity, Don't override.
1618             [false, false, false, false], // Don't inherit both category and purpose, Not for activity, Don't override.
1619             [false, false, false, true], // Don't inherit both category and purpose, Not for activity, Override instances.
1620         ];
1621         $data = [];
1622         foreach ($contextlevels as $level) {
1623             foreach ($paramsets as $set) {
1624                 $data[] = array_merge($level, $set);
1625             }
1626             if ($level == CONTEXT_MODULE) {
1627                 // Add a combination where defaults for activity is being set.
1628                 $data[] = [CONTEXT_MODULE, false, false, true, false];
1629                 $data[] = [CONTEXT_MODULE, false, false, true, true];
1630             }
1631         }
1632         return $data;
1633     }
1635     /**
1636      * Test for \tool_dataprivacy\api::set_context_defaults()
1637      *
1638      * @dataProvider set_context_defaults_provider
1639      * @param int $contextlevel The context level
1640      * @param bool $inheritcategory Whether to set category value as INHERIT.
1641      * @param bool $inheritpurpose Whether to set purpose value as INHERIT.
1642      * @param bool $foractivity Whether to set defaults for an activity.
1643      * @param bool $override Whether to override instances.
1644      */
1645     public function test_set_context_defaults($contextlevel, $inheritcategory, $inheritpurpose, $foractivity, $override) {
1646         $this->resetAfterTest();
1648         $generator = $this->getDataGenerator();
1650         // Generate course cat, course, block, assignment, forum instances.
1651         $coursecat = $generator->create_category();
1652         $course = $generator->create_course(['category' => $coursecat->id]);
1653         $block = $generator->create_block('online_users');
1654         $assign = $generator->create_module('assign', ['course' => $course->id]);
1655         $forum = $generator->create_module('forum', ['course' => $course->id]);
1657         $coursecatcontext = context_coursecat::instance($coursecat->id);
1658         $coursecontext = context_course::instance($course->id);
1659         $blockcontext = context_block::instance($block->id);
1661         list($course, $assigncm) = get_course_and_cm_from_instance($assign->id, 'assign');
1662         list($course, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1663         $assigncontext = context_module::instance($assigncm->id);
1664         $forumcontext = context_module::instance($forumcm->id);
1666         // Generate purposes and categories.
1667         $category1 = api::create_category((object)['name' => 'Test category 1']);
1668         $category2 = api::create_category((object)['name' => 'Test category 2']);
1669         $purpose1 = api::create_purpose((object)[
1670             'name' => 'Test purpose 1', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1671         ]);
1672         $purpose2 = api::create_purpose((object)[
1673             'name' => 'Test purpose 2', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1674         ]);
1676         // Assign purposes and categories to contexts.
1677         $coursecatctxinstance = api::set_context_instance((object) [
1678             'contextid' => $coursecatcontext->id,
1679             'purposeid' => $purpose1->get('id'),
1680             'categoryid' => $category1->get('id'),
1681         ]);
1682         $coursectxinstance = api::set_context_instance((object) [
1683             'contextid' => $coursecontext->id,
1684             'purposeid' => $purpose1->get('id'),
1685             'categoryid' => $category1->get('id'),
1686         ]);
1687         $blockctxinstance = api::set_context_instance((object) [
1688             'contextid' => $blockcontext->id,
1689             'purposeid' => $purpose1->get('id'),
1690             'categoryid' => $category1->get('id'),
1691         ]);
1692         $assignctxinstance = api::set_context_instance((object) [
1693             'contextid' => $assigncontext->id,
1694             'purposeid' => $purpose1->get('id'),
1695             'categoryid' => $category1->get('id'),
1696         ]);
1697         $forumctxinstance = api::set_context_instance((object) [
1698             'contextid' => $forumcontext->id,
1699             'purposeid' => $purpose1->get('id'),
1700             'categoryid' => $category1->get('id'),
1701         ]);
1703         $categoryid = $inheritcategory ? context_instance::INHERIT : $category2->get('id');
1704         $purposeid = $inheritpurpose ? context_instance::INHERIT : $purpose2->get('id');
1705         $activity = '';
1706         if ($contextlevel == CONTEXT_MODULE && $foractivity) {
1707             $activity = 'assign';
1708         }
1709         $result = api::set_context_defaults($contextlevel, $categoryid, $purposeid, $activity, $override);
1710         $this->assertTrue($result);
1712         $targetctxinstance = false;
1713         switch ($contextlevel) {
1714             case CONTEXT_COURSECAT:
1715                 $targetctxinstance = $coursecatctxinstance;
1716                 break;
1717             case CONTEXT_COURSE:
1718                 $targetctxinstance = $coursectxinstance;
1719                 break;
1720             case CONTEXT_MODULE:
1721                 $targetctxinstance = $assignctxinstance;
1722                 break;
1723             case CONTEXT_BLOCK:
1724                 $targetctxinstance = $blockctxinstance;
1725                 break;
1726         }
1727         $this->assertNotFalse($targetctxinstance);
1729         // Check the context instances.
1730         $instanceexists = context_instance::record_exists($targetctxinstance->get('id'));
1731         if ($override) {
1732             // If overridden, context instances on this context level would have been deleted.
1733             $this->assertFalse($instanceexists);
1735             // Check forum context instance.
1736             $forumctxexists = context_instance::record_exists($forumctxinstance->get('id'));
1737             if ($contextlevel != CONTEXT_MODULE || $foractivity) {
1738                 // The forum context instance won't be affected in this test if:
1739                 // - The overridden defaults are not for context modules.
1740                 // - Only the defaults for assign have been set.
1741                 $this->assertTrue($forumctxexists);
1742             } else {
1743                 // If we're overriding for the whole course module context level,
1744                 // then this forum context instance will be deleted as well.
1745                 $this->assertFalse($forumctxexists);
1746             }
1747         } else {
1748             // Otherwise, the context instance record remains.
1749             $this->assertTrue($instanceexists);
1750         }
1752         // Check defaults.
1753         list($defaultpurpose, $defaultcategory) = data_registry::get_defaults($contextlevel, $activity);
1754         if (!$inheritpurpose) {
1755             $this->assertEquals($purposeid, $defaultpurpose);
1756         }
1757         if (!$inheritcategory) {
1758             $this->assertEquals($categoryid, $defaultcategory);
1759         }
1760     }
1762     /**
1763      * Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
1764      *
1765      * @param       int $type The type of request to create
1766      * @return      \stdClass
1767      */
1768     protected function setup_test_add_request_contexts_with_status($type) {
1769         $this->resetAfterTest();
1771         $this->setAdminUser();
1773         // User under test.
1774         $s1 = $this->getDataGenerator()->create_user();
1776         // Create three sample contexts.
1777         // 1 which should not be returned; and
1778         // 1 which will be returned and is not protected; and
1779         // 1 which will be returned and is protected.
1781         $c1 = $this->getDataGenerator()->create_course();
1782         $c2 = $this->getDataGenerator()->create_course();
1783         $c3 = $this->getDataGenerator()->create_course();
1785         $ctx1 = \context_course::instance($c1->id);
1786         $ctx2 = \context_course::instance($c2->id);
1787         $ctx3 = \context_course::instance($c3->id);
1789         $unprotected = api::create_purpose((object)[
1790             'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1791         $protected = api::create_purpose((object) [
1792             'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
1794         $cat1 = api::create_category((object)['name' => 'a']);
1796         // Set the defaults.
1797         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1798             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1799         );
1800         set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
1801         set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
1803         $contextinstance1 = api::set_context_instance((object) [
1804                 'contextid' => $ctx1->id,
1805                 'purposeid' => $unprotected->get('id'),
1806                 'categoryid' => $cat1->get('id'),
1807             ]);
1809         $contextinstance2 = api::set_context_instance((object) [
1810                 'contextid' => $ctx2->id,
1811                 'purposeid' => $unprotected->get('id'),
1812                 'categoryid' => $cat1->get('id'),
1813             ]);
1815         $contextinstance3 = api::set_context_instance((object) [
1816                 'contextid' => $ctx3->id,
1817                 'purposeid' => $protected->get('id'),
1818                 'categoryid' => $cat1->get('id'),
1819             ]);
1821         $collection = new \core_privacy\local\request\contextlist_collection($s1->id);
1822         $contextlist = new \core_privacy\local\request\contextlist();
1823         $contextlist->set_component('tool_dataprivacy');
1824         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
1825                 'ctx2' => $ctx2->id,
1826                 'ctx3' => $ctx3->id,
1827             ]);
1829         $collection->add_contextlist($contextlist);
1831         // Create the sample data request.
1832         $datarequest = api::create_data_request($s1->id, $type);
1833         $requestid = $datarequest->get('id');
1835         // Add the full collection with contexts 2, and 3.
1836         api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
1838         // Mark it as approved.
1839         api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
1841         // Fetch the list.
1842         $approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
1844         return (object) [
1845             'contexts' => (object) [
1846                 'unused' => [
1847                     $ctx1->id,
1848                 ],
1849                 'used' => [
1850                     $ctx2->id,
1851                     $ctx3->id,
1852                 ],
1853                 'unprotected' => [
1854                     $ctx2->id,
1855                 ],
1856                 'protected' => [
1857                     $ctx3->id,
1858                 ],
1859             ],
1860             'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
1861         ];
1862     }
1864     /**
1865      * Setup the basics with the specified retention period.
1866      *
1867      * @param   string  $system Retention policy for the system.
1868      * @param   string  $user Retention policy for users.
1869      * @param   string  $course Retention policy for courses.
1870      * @param   string  $activity Retention policy for activities.
1871      */
1872     protected function setup_basics(string $system, string $user, string $course = null, string $activity = null) : \stdClass {
1873         $this->resetAfterTest();
1875         $purposes = (object) [
1876             'system' => $this->create_and_set_purpose_for_contextlevel($system, CONTEXT_SYSTEM),
1877             'user' => $this->create_and_set_purpose_for_contextlevel($user, CONTEXT_USER),
1878         ];
1880         if (null !== $course) {
1881             $purposes->course = $this->create_and_set_purpose_for_contextlevel($course, CONTEXT_COURSE);
1882         }
1884         if (null !== $activity) {
1885             $purposes->activity = $this->create_and_set_purpose_for_contextlevel($activity, CONTEXT_MODULE);
1886         }
1888         return $purposes;
1889     }
1891     /**
1892      * Create a retention period and set it for the specified context level.
1893      *
1894      * @param   string  $retention
1895      * @param   int     $contextlevel
1896      * @return  purpose
1897      */
1898     protected function create_and_set_purpose_for_contextlevel(string $retention, int $contextlevel) : purpose {
1899         $purpose = new purpose(0, (object) [
1900             'name' => 'Test purpose ' . rand(1, 1000),
1901             'retentionperiod' => $retention,
1902             'lawfulbases' => 'gdpr_art_6_1_a',
1903         ]);
1904         $purpose->create();
1906         $cat = new category(0, (object) ['name' => 'Test category']);
1907         $cat->create();
1909         if ($contextlevel <= CONTEXT_USER) {
1910             $record = (object) [
1911                 'purposeid'     => $purpose->get('id'),
1912                 'categoryid'    => $cat->get('id'),
1913                 'contextlevel'  => $contextlevel,
1914             ];
1915             api::set_contextlevel($record);
1916         } else {
1917             list($purposevar, ) = data_registry::var_names_from_context(
1918                     \context_helper::get_class_for_level(CONTEXT_COURSE)
1919                 );
1920             set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy');
1921         }
1923         return $purpose;
1924     }