Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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