Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
   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   * Privacy tests for core_course.
  18   *
  19   * @package    core_course
  20   * @category   test
  21   * @copyright  2018 Adrian Greeve <adrian@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  global $CFG;
  28  require_once($CFG->dirroot . '/completion/tests/fixtures/completion_creation.php');
  29  
  30  use \core_privacy\local\request\transform;
  31  
  32  /**
  33   * Unit tests for course/classes/privacy/policy
  34   *
  35   * @copyright  2018 Adrian Greeve <adrian@moodle.com>
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class core_course_privacy_testcase extends \core_privacy\tests\provider_testcase {
  39  
  40      use completion_creation;
  41  
  42      /**
  43       * Test getting the appropriate context for the userid. This should only ever
  44       * return the user context for the user id supplied.
  45       */
  46      public function test_get_contexts_for_userid() {
  47          $this->resetAfterTest();
  48  
  49          $user1 = $this->getDataGenerator()->create_user();
  50          $user2 = $this->getDataGenerator()->create_user();
  51  
  52          // Make sure contexts are not being returned for user1.
  53          $contextlist = \core_course\privacy\provider::get_contexts_for_userid($user1->id);
  54          $this->assertCount(0, $contextlist->get_contextids());
  55  
  56          // Make sure contexts are not being returned for user2.
  57          $contextlist = \core_course\privacy\provider::get_contexts_for_userid($user2->id);
  58          $this->assertCount(0, $contextlist->get_contextids());
  59  
  60          // Create course completion data for user1.
  61          $this->create_course_completion();
  62          $this->complete_course($user1);
  63  
  64          // Make sure the course context is being returned for user1.
  65          $contextlist = \core_course\privacy\provider::get_contexts_for_userid($user1->id);
  66          $expected = [$this->coursecontext->id];
  67          $actual = $contextlist->get_contextids();
  68          $this->assertCount(1, $actual);
  69          $this->assertEquals($expected, $actual);
  70  
  71          // Make sure contexts are still not being returned for user2.
  72          $contextlist = \core_course\privacy\provider::get_contexts_for_userid($user2->id);
  73          $this->assertCount(0, $contextlist->get_contextids());
  74  
  75          // User2 has a favourite course.
  76          $user2context = \context_user::instance($user2->id);
  77          $ufservice = \core_favourites\service_factory::get_service_for_user_context($user2context);
  78          $ufservice->create_favourite('core_course', 'courses', $this->coursecontext->instanceid,
  79              $this->coursecontext);
  80  
  81          // Make sure the course context is being returned for user2.
  82          $contextlist = \core_course\privacy\provider::get_contexts_for_userid($user2->id);
  83          $expected = [$this->coursecontext->id];
  84          $actual = $contextlist->get_contextids();
  85          $this->assertCount(1, $actual);
  86          $this->assertEquals($expected, $actual);
  87      }
  88  
  89      /**
  90       * Test fetching users within a context.
  91       */
  92      public function test_get_users_in_context() {
  93          $this->resetAfterTest();
  94          $component = 'core_course';
  95  
  96          $user1 = $this->getDataGenerator()->create_user();
  97          $user2 = $this->getDataGenerator()->create_user();
  98          $user3 = $this->getDataGenerator()->create_user();
  99          $user4 = $this->getDataGenerator()->create_user();
 100  
 101          // User1 and user2 complete course.
 102          $this->create_course_completion();
 103          $this->complete_course($user1);
 104          $this->complete_course($user2);
 105  
 106          // User3 is enrolled but has not completed course.
 107          $this->getDataGenerator()->enrol_user($user3->id, $this->course->id, 'student');
 108  
 109          // User4 has a favourited course.
 110          $systemcontext = \context_system::instance();
 111          $user4ctx = \context_user::instance($user4->id);
 112          $ufservice = \core_favourites\service_factory::get_service_for_user_context($user4ctx);
 113          $ufservice->create_favourite('core_course', 'courses', $this->coursecontext->instanceid,
 114                  $this->coursecontext);
 115  
 116          // Ensure only users that have course completion or favourites are returned.
 117          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, $component);
 118          \core_course\privacy\provider::get_users_in_context($userlist);
 119          $expected = [
 120              $user1->id,
 121              $user2->id,
 122              $user4->id
 123          ];
 124          $actual = $userlist->get_userids();
 125          sort($expected);
 126          sort($actual);
 127          $this->assertCount(3, $actual);
 128          $this->assertEquals($expected, $actual);
 129  
 130          // Ensure that users are not being returned in other contexts than the course context.
 131          $userlist = new \core_privacy\local\request\userlist($systemcontext, $component);
 132          \core_course\privacy\provider::get_users_in_context($userlist);
 133          $actual = $userlist->get_userids();
 134          $this->assertCount(0, $actual);
 135      }
 136  
 137      /**
 138       * Test that user data is exported.
 139       */
 140      public function test_export_user_data() {
 141          $this->resetAfterTest();
 142  
 143          $user = $this->getDataGenerator()->create_user();
 144          $this->create_course_completion();
 145          $this->complete_course($user);
 146          $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'core_course',
 147                  [$this->coursecontext->id]);
 148          $writer = \core_privacy\local\request\writer::with_context($this->coursecontext);
 149          \core_course\privacy\provider::export_user_data($approvedlist);
 150          $completiondata = $writer->get_data([get_string('privacy:completionpath', 'course')]);
 151          $this->assertEquals('In progress', $completiondata->status);
 152          $this->assertCount(2, $completiondata->criteria);
 153  
 154          // User has a favourite course.
 155          $usercontext = \context_user::instance($user->id);
 156          $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
 157          $favourite = $ufservice->create_favourite('core_course', 'courses',
 158                  $this->coursecontext->instanceid, $this->coursecontext);
 159  
 160          // Ensure that user's favourites data in the course context is being exported.
 161          $writer = \core_privacy\local\request\writer::with_context($this->coursecontext);
 162          \core_course\privacy\provider::export_user_data($approvedlist);
 163          $favouritedata = $writer->get_data([get_string('privacy:favouritespath', 'course')]);
 164  
 165          $this->assertEquals(transform::yesno(true), $favouritedata->starred);
 166          $this->assertEquals('', $favouritedata->ordering);
 167          $this->assertEquals(transform::datetime($favourite->timecreated), $favouritedata->timecreated);
 168          $this->assertEquals(transform::datetime($favourite->timemodified), $favouritedata->timemodified);
 169      }
 170  
 171      /**
 172       * Verify that if a module context is included in the contextlist_collection and its parent course is not, the
 173       * export_context_data() call picks this up, and that the contextual course information is included.
 174       */
 175      public function test_export_context_data_module_context_only() {
 176          $this->resetAfterTest();
 177  
 178          // Create a course and a single module.
 179          $course1 = $this->getDataGenerator()->create_course(['fullname' => 'Course 1', 'shortname' => 'C1']);
 180          $context1 = context_course::instance($course1->id);
 181          $modassign = $this->getDataGenerator()->create_module('assign', ['course' => $course1->id, 'name' => 'assign test 1']);
 182          $assigncontext = context_module::instance($modassign->cmid);
 183  
 184          // Now, let's assume during user info export, only the coursemodule context is returned in the contextlist_collection.
 185          $user = $this->getDataGenerator()->create_user();
 186          $collection = new \core_privacy\local\request\contextlist_collection($user->id);
 187          $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'mod_assign', [$assigncontext->id]);
 188          $collection->add_contextlist($approvedlist);
 189  
 190          // Now, verify that core_course will detect this, and add relevant contextual information.
 191          \core_course\privacy\provider::export_context_data($collection);
 192          $writer = \core_privacy\local\request\writer::with_context($context1);
 193          $this->assertTrue($writer->has_any_data());
 194          $writerdata = $writer->get_data();
 195          $this->assertObjectHasAttribute('fullname', $writerdata);
 196          $this->assertObjectHasAttribute('shortname', $writerdata);
 197          $this->assertObjectHasAttribute('idnumber', $writerdata);
 198          $this->assertObjectHasAttribute('summary', $writerdata);
 199      }
 200  
 201      /**
 202       * Verify that if a module context and its parent course context are both included in the contextlist_collection, that course
 203       * contextual information is present in the export.
 204       */
 205      public function test_export_context_data_course_and_module_contexts() {
 206          $this->resetAfterTest();
 207  
 208          // Create a course and a single module.
 209          $course1 = $this->getDataGenerator()->create_course(['fullname' => 'Course 1', 'shortname' => 'C1', 'format' => 'site']);
 210          $context1 = context_course::instance($course1->id);
 211          $modassign = $this->getDataGenerator()->create_module('assign', ['course' => $course1->id, 'name' => 'assign test 1']);
 212          $assigncontext = context_module::instance($modassign->cmid);
 213  
 214          // Now, assume during user info export, that both module and course contexts are returned in the contextlist_collection.
 215          $user = $this->getDataGenerator()->create_user();
 216          $collection = new \core_privacy\local\request\contextlist_collection($user->id);
 217          $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'mod_assign', [$assigncontext->id]);
 218          $approvedlist2 = new \core_privacy\local\request\approved_contextlist($user, 'core_course', [$context1->id]);
 219          $collection->add_contextlist($approvedlist);
 220          $collection->add_contextlist($approvedlist2);
 221  
 222          // Now, verify that core_course still adds relevant contextual information, even for courses which are explicitly listed in
 223          // the contextlist_collection.
 224          \core_course\privacy\provider::export_context_data($collection);
 225          $writer = \core_privacy\local\request\writer::with_context($context1);
 226          $this->assertTrue($writer->has_any_data());
 227          $writerdata = $writer->get_data();
 228          $this->assertObjectHasAttribute('fullname', $writerdata);
 229          $this->assertObjectHasAttribute('shortname', $writerdata);
 230          $this->assertObjectHasAttribute('idnumber', $writerdata);
 231          $this->assertObjectHasAttribute('summary', $writerdata);
 232      }
 233  
 234      /**
 235       * Test deleting all user data for one context.
 236       */
 237      public function test_delete_data_for_all_users_in_context() {
 238          global $DB;
 239  
 240          $this->resetAfterTest();
 241  
 242          $user1 = $this->getDataGenerator()->create_user();
 243          $user2 = $this->getDataGenerator()->create_user();
 244          $this->create_course_completion();
 245  
 246          $systemcontext = \context_system::instance();
 247          $user1ctx = \context_user::instance($user1->id);
 248          $user2ctx = \context_user::instance($user2->id);
 249          // User1 and user2 have a favourite course.
 250          $ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1ctx);
 251          $ufservice1->create_favourite('core_course', 'courses', $this->coursecontext->instanceid,
 252                  $this->coursecontext);
 253          $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2ctx);
 254          $ufservice2->create_favourite('core_course', 'courses', $this->coursecontext->instanceid,
 255                  $this->coursecontext);
 256  
 257          // Ensure only users that have course favourites are returned in the course context (user1 and user2).
 258          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 259          \core_course\privacy\provider::get_users_in_context($userlist);
 260          $actual = $userlist->get_userids();
 261          $this->assertCount(2, $actual);
 262  
 263          // Ensure the users does not have a course completion data.
 264          $records = $DB->get_records('course_modules_completion');
 265          $this->assertCount(0, $records);
 266          $records = $DB->get_records('course_completion_crit_compl');
 267          $this->assertCount(0, $records);
 268  
 269          // Create course completions for user1 and users.
 270          $this->complete_course($user1);
 271          $this->complete_course($user2);
 272          $records = $DB->get_records('course_modules_completion');
 273          $this->assertCount(2, $records);
 274          $records = $DB->get_records('course_completion_crit_compl');
 275          $this->assertCount(2, $records);
 276  
 277          // Delete data for all users in a context different than the course context (system context).
 278          \core_course\privacy\provider::delete_data_for_all_users_in_context($systemcontext);
 279  
 280          // Ensure the data in the course context has not been deleted.
 281          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 282          \core_course\privacy\provider::get_users_in_context($userlist);
 283          $actual = $userlist->get_userids();
 284          $this->assertCount(2, $actual);
 285  
 286          // Delete data for all users in the course context.
 287          \core_course\privacy\provider::delete_data_for_all_users_in_context($this->coursecontext);
 288  
 289          // Ensure the completion data has been removed in the course context.
 290          $records = $DB->get_records('course_modules_completion');
 291          $this->assertCount(0, $records);
 292          $records = $DB->get_records('course_completion_crit_compl');
 293          $this->assertCount(0, $records);
 294  
 295          // Ensure that users are not returned after the deletion in the course context.
 296          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 297          \core_course\privacy\provider::get_users_in_context($userlist);
 298          $actual = $userlist->get_userids();
 299          $this->assertCount(0, $actual);
 300      }
 301  
 302      /**
 303       * Test deleting data for only one user.
 304       */
 305      public function test_delete_data_for_user() {
 306          $this->resetAfterTest();
 307  
 308          $user1 = $this->getDataGenerator()->create_user();
 309          $user2 = $this->getDataGenerator()->create_user();
 310          $user3 = $this->getDataGenerator()->create_user();
 311  
 312          // Create course completion for user1.
 313          $this->create_course_completion();
 314          $this->complete_course($user1);
 315  
 316          // Ensure user1 is returned in the course context.
 317          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 318          \core_course\privacy\provider::get_users_in_context($userlist);
 319          $actual = $userlist->get_userids();
 320          $expected = [$user1->id];
 321          $this->assertCount(1, $actual);
 322          $this->assertEquals($expected, $actual);
 323  
 324          // User2 and user3 have a favourite course.
 325          $systemcontext = \context_system::instance();
 326          $user2ctx = \context_user::instance($user2->id);
 327          $user3ctx = \context_user::instance($user3->id);
 328          $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2ctx);
 329          $ufservice2->create_favourite('core_course', 'courses', $this->coursecontext->instanceid,
 330                  $this->coursecontext);
 331          $ufservice3 = \core_favourites\service_factory::get_service_for_user_context($user3ctx);
 332          $ufservice3->create_favourite('core_course', 'courses', $this->coursecontext->instanceid,
 333                  $this->coursecontext);
 334  
 335          // Ensure user1, user2 and user3 are returned in the course context.
 336          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 337          \core_course\privacy\provider::get_users_in_context($userlist);
 338          $actual = $userlist->get_userids();
 339          $expected = [
 340              $user1->id,
 341              $user2->id,
 342              $user3->id
 343          ];
 344          sort($expected);
 345          sort($actual);
 346          $this->assertCount(3, $actual);
 347          $this->assertEquals($expected, $actual);
 348  
 349          // Delete user1's data in the course context.
 350          $approvedlist = new \core_privacy\local\request\approved_contextlist($user1, 'core_course',
 351                  [$this->coursecontext->id]);
 352          \core_course\privacy\provider::delete_data_for_user($approvedlist);
 353  
 354          // Ensure user1's data is deleted and only user2 and user3 are returned in the course context.
 355          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 356          \core_course\privacy\provider::get_users_in_context($userlist);
 357          $actual = $userlist->get_userids();
 358          $expected = [
 359              $user2->id,
 360              $user3->id
 361          ];
 362          sort($expected);
 363          sort($actual);
 364          $this->assertEquals($expected, $actual);
 365  
 366          // Delete user2's data in a context different than the course context (system context).
 367          $approvedlist = new \core_privacy\local\request\approved_contextlist($user2, 'core_course',
 368                  [$systemcontext->id]);
 369          \core_course\privacy\provider::delete_data_for_user($approvedlist);
 370  
 371          // Ensure user2 and user3 are still returned in the course context.
 372          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 373          \core_course\privacy\provider::get_users_in_context($userlist);
 374          $actual = $userlist->get_userids();
 375          $expected = [
 376              $user2->id,
 377              $user3->id
 378          ];
 379          sort($expected);
 380          sort($actual);
 381          $this->assertEquals($expected, $actual);
 382  
 383          // Delete user2's data in the course context.
 384          $approvedlist = new \core_privacy\local\request\approved_contextlist($user2, 'core_course',
 385                  [$this->coursecontext->id]);
 386          \core_course\privacy\provider::delete_data_for_user($approvedlist);
 387  
 388          // Ensure user2's is deleted and user3 is still returned in the course context.
 389          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 390          \core_course\privacy\provider::get_users_in_context($userlist);
 391          $actual = $userlist->get_userids();
 392          $expected = [
 393              $user3->id
 394          ];
 395          $this->assertEquals($expected, $actual);
 396      }
 397  
 398      /**
 399       * Test deleting data within a context for an approved userlist.
 400       */
 401      public function test_delete_data_for_users() {
 402          $this->resetAfterTest();
 403  
 404          $component = 'core_course';
 405          $user1 = $this->getDataGenerator()->create_user();
 406          $user2 = $this->getDataGenerator()->create_user();
 407          $user3 = $this->getDataGenerator()->create_user();
 408  
 409          $this->create_course_completion();
 410          $this->complete_course($user1);
 411          $this->complete_course($user2);
 412  
 413          // Ensure user1, user2 are returned in the course context.
 414          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 415          \core_course\privacy\provider::get_users_in_context($userlist);
 416          $actual = $userlist->get_userids();
 417          $expected = [
 418              $user1->id,
 419              $user2->id
 420          ];
 421          sort($expected);
 422          sort($actual);
 423          $this->assertCount(2, $actual);
 424          $this->assertEquals($expected, $actual);
 425  
 426          $systemcontext = \context_system::instance();
 427          // User3 has a favourite course.
 428          $user3ctx = \context_user::instance($user3->id);
 429          $ufservice = \core_favourites\service_factory::get_service_for_user_context($user3ctx);
 430          $ufservice->create_favourite('core_course', 'courses', $this->coursecontext->instanceid,
 431                  $this->coursecontext);
 432  
 433          // Ensure user1, user2 and user3 are now returned in the course context.
 434          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 435          \core_course\privacy\provider::get_users_in_context($userlist);
 436          $actual = $userlist->get_userids();
 437          $expected = [
 438              $user1->id,
 439              $user2->id,
 440              $user3->id
 441          ];
 442          sort($expected);
 443          sort($actual);
 444          $this->assertCount(3, $actual);
 445          $this->assertEquals($expected, $actual);
 446  
 447          // Delete data for user1 and user3 in the course context.
 448          $approveduserids = [$user1->id, $user3->id];
 449          $approvedlist = new \core_privacy\local\request\approved_userlist($this->coursecontext, $component, $approveduserids);
 450          \core_course\privacy\provider::delete_data_for_users($approvedlist);
 451  
 452          // Ensure user1 and user3 are deleted and user2 is still returned in the course context.
 453          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 454          \core_course\privacy\provider::get_users_in_context($userlist);
 455          $actual = $userlist->get_userids();
 456          $expected = [$user2->id];
 457          $this->assertCount(1, $actual);
 458          $this->assertEquals($expected, $actual);
 459  
 460          // Try to delete user2's data in a context different than course (system context).
 461          $approveduserids = [$user2->id];
 462          $approvedlist = new \core_privacy\local\request\approved_userlist($systemcontext, $component, $approveduserids);
 463          \core_course\privacy\provider::delete_data_for_users($approvedlist);
 464  
 465          // Ensure user2 is still returned in the course context.
 466          $userlist = new \core_privacy\local\request\userlist($this->coursecontext, 'core_course');
 467          \core_course\privacy\provider::get_users_in_context($userlist);
 468          $actual = $userlist->get_userids();
 469          $expected = [
 470              $user2->id
 471          ];
 472          $this->assertCount(1, $actual);
 473          $this->assertEquals($expected, $actual);
 474      }
 475  }