Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 and 403]

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