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