Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

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