Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is 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  /**
  18   * Privacy provider tests.
  19   *
  20   * @package    block_comments
  21   * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  namespace block_comments\privacy;
  25  
  26  use core_privacy\local\metadata\collection;
  27  use block_comments\privacy\provider;
  28  use core_privacy\local\request\approved_userlist;
  29  
  30  defined('MOODLE_INTERNAL') || die();
  31  
  32  /**
  33   * Privacy provider test for block_comments.
  34   *
  35   * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class provider_test extends \core_privacy\tests\provider_testcase {
  39  
  40      /** @var stdClass A student who is only enrolled in course1. */
  41      protected $student1;
  42  
  43      /** @var stdClass A student who is only enrolled in course2. */
  44      protected $student2;
  45  
  46      /** @var stdClass A student who is enrolled in both course1 and course2. */
  47      protected $student12;
  48  
  49      /** @var stdClass A test course. */
  50      protected $course1;
  51  
  52      /** @var stdClass A test course. */
  53      protected $course2;
  54  
  55      protected function setUp(): void {
  56          global $DB;
  57  
  58          $this->resetAfterTest();
  59          $this->setAdminUser();
  60  
  61          // Create courses.
  62          $generator = $this->getDataGenerator();
  63          $this->course1 = $generator->create_course();
  64          $this->course2 = $generator->create_course();
  65  
  66          // Create and enrol students.
  67          $this->student1 = $generator->create_user();
  68          $this->student2 = $generator->create_user();
  69          $this->student12 = $generator->create_user();
  70  
  71          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
  72          $generator->enrol_user($this->student1->id,  $this->course1->id, $studentrole->id);
  73          $generator->enrol_user($this->student2->id,  $this->course2->id, $studentrole->id);
  74          $generator->enrol_user($this->student12->id,  $this->course1->id, $studentrole->id);
  75          $generator->enrol_user($this->student12->id,  $this->course2->id, $studentrole->id);
  76  
  77          // Comment block on course pages.
  78          $block = $this->add_comments_block_in_context(\context_course::instance($this->course1->id));
  79          $block = $this->add_comments_block_in_context(\context_course::instance($this->course2->id));
  80      }
  81  
  82      /**
  83       * Posts a comment on a given context.
  84       *
  85       * @param string $text The comment's text.
  86       * @param context $context The context on which we want to put the comment.
  87       */
  88      protected function add_comment($text, \context $context) {
  89          $args = new \stdClass;
  90          $args->context = $context;
  91          $args->area = 'page_comments';
  92          $args->itemid = 0;
  93          $args->component = 'block_comments';
  94          $args->linktext = get_string('showcomments');
  95          $args->notoggle = true;
  96          $args->autostart = true;
  97          $args->displaycancel = false;
  98          $comment = new \comment($args);
  99  
 100          $comment->add($text);
 101      }
 102  
 103      /**
 104       * Creates a comments block on a context.
 105       *
 106       * @param context $context The context on which we want to put the block.
 107       * @return block_base The created block instance.
 108       * @throws coding_exception
 109       */
 110      protected function add_comments_block_in_context(\context $context) {
 111          global $DB;
 112  
 113          $course = null;
 114  
 115          $page = new \moodle_page();
 116          $page->set_context($context);
 117  
 118          switch ($context->contextlevel) {
 119              case CONTEXT_SYSTEM:
 120                  $page->set_pagelayout('frontpage');
 121                  $page->set_pagetype('site-index');
 122                  break;
 123              case CONTEXT_COURSE:
 124                  $page->set_pagelayout('standard');
 125                  $page->set_pagetype('course-view');
 126                  $course = $DB->get_record('course', ['id' => $context->instanceid]);
 127                  $page->set_course($course);
 128                  break;
 129              case CONTEXT_MODULE:
 130                  $page->set_pagelayout('standard');
 131                  $mod = $DB->get_field_sql("SELECT m.name
 132                                               FROM {modules} m
 133                                               JOIN {course_modules} cm on cm.module = m.id
 134                                              WHERE cm.id = ?", [$context->instanceid]);
 135                  $page->set_pagetype("mod-$mod-view");
 136                  break;
 137              case CONTEXT_USER:
 138                  $page->set_pagelayout('mydashboard');
 139                  $page->set_pagetype('my-index');
 140                  break;
 141              default:
 142                  throw new coding_exception('Unsupported context for test');
 143          }
 144  
 145          $page->blocks->load_blocks();
 146  
 147          $page->blocks->add_block_at_end_of_default_region('comments');
 148  
 149          // We need to use another page object as load_blocks() only loads the blocks once.
 150          $page2 = new \moodle_page();
 151          $page2->set_context($page->context);
 152          $page2->set_pagelayout($page->pagelayout);
 153          $page2->set_pagetype($page->pagetype);
 154          if ($course) {
 155              $page2->set_course($course);
 156          }
 157  
 158          $page->blocks->load_blocks();
 159          $page2->blocks->load_blocks();
 160          $blocks = $page2->blocks->get_blocks_for_region($page2->blocks->get_default_region());
 161          $block = end($blocks);
 162  
 163          $block = block_instance('comments', $block->instance);
 164  
 165          return $block;
 166      }
 167  
 168      /**
 169       * Test for provider::get_metadata().
 170       */
 171      public function test_get_metadata() {
 172          $collection = new collection('block_comments');
 173          $newcollection = provider::get_metadata($collection);
 174          $itemcollection = $newcollection->get_collection();
 175          $this->assertCount(1, $itemcollection);
 176  
 177          $link = reset($itemcollection);
 178  
 179          $this->assertEquals('core_comment', $link->get_name());
 180          $this->assertEmpty($link->get_privacy_fields());
 181          $this->assertEquals('privacy:metadata:core_comment', $link->get_summary());
 182      }
 183  
 184      /**
 185       * Test for provider::get_contexts_for_userid() when user had not posted any comments..
 186       */
 187      public function test_get_contexts_for_userid_no_comment() {
 188          $this->setUser($this->student1);
 189          $coursecontext1 = \context_course::instance($this->course1->id);
 190          $this->add_comment('New comment', $coursecontext1);
 191  
 192          $this->setUser($this->student2);
 193          $contextlist = provider::get_contexts_for_userid($this->student2->id);
 194          $this->assertCount(0, $contextlist);
 195      }
 196  
 197      /**
 198       * Test for provider::get_contexts_for_userid().
 199       */
 200      public function test_get_contexts_for_userid() {
 201          $coursecontext1 = \context_course::instance($this->course1->id);
 202          $coursecontext2 = \context_course::instance($this->course2->id);
 203  
 204          $this->setUser($this->student12);
 205          $this->add_comment('New comment', $coursecontext1);
 206          $this->add_comment('New comment', $coursecontext1);
 207          $this->add_comment('New comment', $coursecontext2);
 208  
 209          $contextlist = provider::get_contexts_for_userid($this->student12->id);
 210          $this->assertCount(2, $contextlist);
 211  
 212          $contextids = $contextlist->get_contextids();
 213          $this->assertEqualsCanonicalizing([$coursecontext1->id, $coursecontext2->id], $contextids);
 214      }
 215  
 216      /**
 217       * Test for provider::export_user_data() when the user has not posted any comments.
 218       */
 219      public function test_export_for_context_no_comment() {
 220          $coursecontext1 = \context_course::instance($this->course1->id);
 221          $coursecontext2 = \context_course::instance($this->course2->id);
 222  
 223          $this->setUser($this->student1);
 224          $this->add_comment('New comment', $coursecontext1);
 225  
 226          $this->setUser($this->student2);
 227  
 228          $this->setUser($this->student2);
 229          $this->export_context_data_for_user($this->student2->id, $coursecontext2, 'block_comments');
 230          $writer = \core_privacy\local\request\writer::with_context($coursecontext2);
 231          $this->assertFalse($writer->has_any_data());
 232  
 233      }
 234  
 235      /**
 236       * Test for provider::export_user_data().
 237       */
 238      public function test_export_for_context() {
 239          $coursecontext1 = \context_course::instance($this->course1->id);
 240          $coursecontext2 = \context_course::instance($this->course2->id);
 241  
 242          $this->setUser($this->student12);
 243          $this->add_comment('New comment', $coursecontext1);
 244          $this->add_comment('New comment', $coursecontext1);
 245          $this->add_comment('New comment', $coursecontext2);
 246  
 247          // Export all of the data for the context.
 248          $this->export_context_data_for_user($this->student12->id, $coursecontext1, 'block_comments');
 249          $writer = \core_privacy\local\request\writer::with_context($coursecontext1);
 250          $this->assertTrue($writer->has_any_data());
 251      }
 252  
 253      /**
 254       * Test for provider::delete_data_for_all_users_in_context().
 255       */
 256      public function test_delete_data_for_all_users_in_context() {
 257          global $DB;
 258  
 259          $coursecontext1 = \context_course::instance($this->course1->id);
 260          $coursecontext2 = \context_course::instance($this->course2->id);
 261  
 262          $this->setUser($this->student1);
 263          $this->add_comment('New comment', $coursecontext1);
 264  
 265          $this->setUser($this->student2);
 266          $this->add_comment('New comment', $coursecontext2);
 267  
 268          $this->setUser($this->student12);
 269          $this->add_comment('New comment', $coursecontext1);
 270          $this->add_comment('New comment', $coursecontext1);
 271          $this->add_comment('New comment', $coursecontext2);
 272  
 273          // Before deletion, we should have 3 comments in $coursecontext1 and 2 comments in $coursecontext2.
 274          $this->assertEquals(
 275                  3,
 276                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext1->id])
 277          );
 278          $this->assertEquals(
 279                  2,
 280                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext2->id])
 281          );
 282  
 283          // Delete data based on context.
 284          provider::delete_data_for_all_users_in_context($coursecontext1);
 285  
 286          // After deletion, the comments for $coursecontext1 should have been deleted.
 287          $this->assertEquals(
 288                  0,
 289                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext1->id])
 290          );
 291          $this->assertEquals(
 292                  2,
 293                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext2->id])
 294          );
 295      }
 296  
 297      /**
 298       * Test for provider::delete_data_for_all_users_in_context() when there are also comments from other plugins.
 299       */
 300      public function test_delete_data_for_all_users_in_context_with_comments_from_other_plugins() {
 301          global $DB;
 302  
 303          $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
 304          $instance = $assigngenerator->create_instance(['course' => $this->course1]);
 305          $cm = get_coursemodule_from_instance('assign', $instance->id);
 306          $assigncontext = \context_module::instance($cm->id);
 307          $assign = new \assign($assigncontext, $cm, $this->course1);
 308  
 309          // Add a comments block in the assignment page.
 310          $this->add_comments_block_in_context($assigncontext);
 311  
 312          $submission = $assign->get_user_submission($this->student1->id, true);
 313  
 314          $options = new \stdClass();
 315          $options->area = 'submission_comments';
 316          $options->course = $assign->get_course();
 317          $options->context = $assigncontext;
 318          $options->itemid = $submission->id;
 319          $options->component = 'assignsubmission_comments';
 320          $options->showcount = true;
 321          $options->displaycancel = true;
 322  
 323          $comment = new \comment($options);
 324          $comment->set_post_permission(true);
 325  
 326          $this->setUser($this->student1);
 327          $comment->add('Comment from student 1');
 328  
 329          $this->add_comment('New comment', $assigncontext);
 330  
 331          $this->setUser($this->student2);
 332          $this->add_comment('New comment', $assigncontext);
 333  
 334          // Before deletion, we should have 3 comments in $assigncontext.
 335          // One comment is for the assignment submission and 2 are for the comments block.
 336          $this->assertEquals(
 337                  3,
 338                  $DB->count_records('comments', ['contextid' => $assigncontext->id])
 339          );
 340          $this->assertEquals(
 341                  2,
 342                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $assigncontext->id])
 343          );
 344  
 345          provider::delete_data_for_all_users_in_context($assigncontext);
 346  
 347          // After deletion, the comments for $assigncontext in the comment block should have been deleted,
 348          // but the assignment submission comment should be left.
 349          $this->assertEquals(
 350                  1,
 351                  $DB->count_records('comments', ['contextid' => $assigncontext->id])
 352          );
 353          $this->assertEquals(
 354                  0,
 355                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $assigncontext->id])
 356          );
 357      }
 358  
 359      /**
 360       * Test for provider::delete_data_for_user().
 361       */
 362      public function test_delete_data_for_user() {
 363          global $DB;
 364  
 365          $coursecontext1 = \context_course::instance($this->course1->id);
 366          $coursecontext2 = \context_course::instance($this->course2->id);
 367  
 368          $this->setUser($this->student1);
 369          $this->add_comment('New comment', $coursecontext1);
 370  
 371          $this->setUser($this->student2);
 372          $this->add_comment('New comment', $coursecontext2);
 373  
 374          $this->setUser($this->student12);
 375          $this->add_comment('New comment', $coursecontext1);
 376          $this->add_comment('New comment', $coursecontext1);
 377          $this->add_comment('New comment', $coursecontext2);
 378  
 379          // Before deletion, we should have 3 comments in $coursecontext1 and 2 comments in $coursecontext2,
 380          // and 3 comments by student12 in $coursecontext1 and $coursecontext2 combined.
 381          $this->assertEquals(
 382                  3,
 383                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext1->id])
 384          );
 385          $this->assertEquals(
 386                  2,
 387                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext2->id])
 388          );
 389          $this->assertEquals(
 390                  3,
 391                  $DB->count_records('comments', ['component' => 'block_comments', 'userid' => $this->student12->id])
 392          );
 393  
 394          $contextlist = new \core_privacy\local\request\approved_contextlist($this->student12, 'block_comments',
 395                  [$coursecontext1->id, $coursecontext2->id]);
 396          provider::delete_data_for_user($contextlist);
 397  
 398          // After deletion, the comments for the student12 should have been deleted.
 399          $this->assertEquals(
 400                  1,
 401                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext1->id])
 402          );
 403          $this->assertEquals(
 404                  1,
 405                  $DB->count_records('comments', ['component' => 'block_comments', 'contextid' => $coursecontext2->id])
 406          );
 407          $this->assertEquals(
 408                  0,
 409                  $DB->count_records('comments', ['component' => 'block_comments', 'userid' => $this->student12->id])
 410          );
 411      }
 412  
 413      /**
 414       * Test for provider::delete_data_for_user() when there are also comments from other plugins.
 415       */
 416      public function test_delete_data_for_user_with_comments_from_other_plugins() {
 417          global $DB;
 418  
 419          $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
 420          $instance = $assigngenerator->create_instance(['course' => $this->course1]);
 421          $cm = get_coursemodule_from_instance('assign', $instance->id);
 422          $assigncontext = \context_module::instance($cm->id);
 423          $assign = new \assign($assigncontext, $cm, $this->course1);
 424  
 425          // Add a comments block in the assignment page.
 426          $this->add_comments_block_in_context($assigncontext);
 427  
 428          $submission = $assign->get_user_submission($this->student1->id, true);
 429  
 430          $options = new \stdClass();
 431          $options->area = 'submission_comments';
 432          $options->course = $assign->get_course();
 433          $options->context = $assigncontext;
 434          $options->itemid = $submission->id;
 435          $options->component = 'assignsubmission_comments';
 436          $options->showcount = true;
 437          $options->displaycancel = true;
 438  
 439          $comment = new \comment($options);
 440          $comment->set_post_permission(true);
 441  
 442          $this->setUser($this->student1);
 443          $comment->add('Comment from student 1');
 444  
 445          $this->add_comment('New comment', $assigncontext);
 446          $this->add_comment('New comment', $assigncontext);
 447  
 448          // Before deletion, we should have 3 comments in $assigncontext.
 449          // one comment is for the assignment submission and 2 are for the comments block.
 450          $this->assertEquals(
 451                  3,
 452                  $DB->count_records('comments', ['contextid' => $assigncontext->id])
 453          );
 454  
 455          $contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'block_comments',
 456                  [$assigncontext->id]);
 457          provider::delete_data_for_user($contextlist);
 458  
 459          // After deletion, the comments for the student1 in the comment block should have been deleted,
 460          // but the assignment submission comment should be left.
 461          $this->assertEquals(
 462                  1,
 463                  $DB->count_records('comments', ['contextid' => $assigncontext->id])
 464          );
 465          $this->assertEquals(
 466                  0,
 467                  $DB->count_records('comments', ['component' => 'block_comments', 'userid' => $this->student1->id])
 468          );
 469      }
 470  
 471      /**
 472       * Test that only users within a course context are fetched.
 473       * @group qtesttt
 474       */
 475      public function test_get_users_in_context() {
 476          $component = 'block_comments';
 477  
 478          $coursecontext1 = \context_course::instance($this->course1->id);
 479          $coursecontext2 = \context_course::instance($this->course2->id);
 480  
 481          $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component);
 482          provider::get_users_in_context($userlist1);
 483          $this->assertCount(0, $userlist1);
 484  
 485          $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component);
 486          provider::get_users_in_context($userlist2);
 487          $this->assertCount(0, $userlist2);
 488  
 489          $this->setUser($this->student12);
 490          $this->add_comment('New comment', $coursecontext1);
 491          $this->add_comment('New comment', $coursecontext2);
 492          $this->setUser($this->student1);
 493          $this->add_comment('New comment', $coursecontext1);
 494  
 495          // The list of users should contain user12 and user1.
 496          provider::get_users_in_context($userlist1);
 497          $this->assertCount(2, $userlist1);
 498          $this->assertTrue(in_array($this->student1->id, $userlist1->get_userids()));
 499          $this->assertTrue(in_array($this->student12->id, $userlist1->get_userids()));
 500  
 501          // The list of users should contain user12.
 502          provider::get_users_in_context($userlist2);
 503          $this->assertCount(1, $userlist2);
 504          $expected = [$this->student12->id];
 505          $actual = $userlist2->get_userids();
 506          $this->assertEquals($expected, $actual);
 507      }
 508  
 509      /**
 510       * Test that data for users in approved userlist is deleted.
 511       */
 512      public function test_delete_data_for_users() {
 513          $component = 'block_comments';
 514  
 515          $coursecontext1 = \context_course::instance($this->course1->id);
 516          $coursecontext2 = \context_course::instance($this->course2->id);
 517  
 518          $this->setUser($this->student12);
 519          $this->add_comment('New comment', $coursecontext1);
 520          $this->add_comment('New comment', $coursecontext2);
 521          $this->setUser($this->student1);
 522          $this->add_comment('New comment', $coursecontext1);
 523  
 524          $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component);
 525          provider::get_users_in_context($userlist1);
 526          $this->assertCount(2, $userlist1);
 527  
 528          $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component);
 529          provider::get_users_in_context($userlist2);
 530          $this->assertCount(1, $userlist2);
 531  
 532          // Convert $userlist1 into an approved_contextlist.
 533          $approvedlist1 = new approved_userlist($coursecontext1, $component, $userlist1->get_userids());
 534          // Delete using delete_data_for_user.
 535          provider::delete_data_for_users($approvedlist1);
 536  
 537          // Re-fetch users in coursecontext1.
 538          $userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component);
 539          provider::get_users_in_context($userlist1);
 540          // The user data in coursecontext1 should be deleted.
 541          $this->assertCount(0, $userlist1);
 542  
 543          // Re-fetch users in coursecontext2.
 544          $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component);
 545          provider::get_users_in_context($userlist2);
 546          // The user data in coursecontext2 should be still present.
 547          $this->assertCount(1, $userlist2);
 548      }
 549  
 550      /**
 551       * Test for provider::delete_data_for_user() when there are also comments from other plugins.
 552       */
 553      public function test_delete_data_for_users_with_comments_from_other_plugins() {
 554          $component = 'block_comments';
 555  
 556          $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
 557          $instance = $assigngenerator->create_instance(['course' => $this->course1]);
 558          $cm = get_coursemodule_from_instance('assign', $instance->id);
 559          $assigncontext = \context_module::instance($cm->id);
 560          $assign = new \assign($assigncontext, $cm, $this->course1);
 561  
 562          // Add a comments block in the assignment page.
 563          $this->add_comments_block_in_context($assigncontext);
 564  
 565          $submission = $assign->get_user_submission($this->student1->id, true);
 566  
 567          $options = new \stdClass();
 568          $options->area = 'submission_comments';
 569          $options->course = $assign->get_course();
 570          $options->context = $assigncontext;
 571          $options->itemid = $submission->id;
 572          $options->component = 'assignsubmission_comments';
 573          $options->showcount = true;
 574          $options->displaycancel = true;
 575  
 576          $comment = new \comment($options);
 577          $comment->set_post_permission(true);
 578  
 579          $this->setUser($this->student1);
 580          $comment->add('Comment from student 1');
 581  
 582          $this->add_comment('New comment', $assigncontext);
 583          $this->add_comment('New comment', $assigncontext);
 584  
 585          $userlist1 = new \core_privacy\local\request\userlist($assigncontext, $component);
 586          provider::get_users_in_context($userlist1);
 587          $this->assertCount(1, $userlist1);
 588  
 589          // Convert $userlist1 into an approved_contextlist.
 590          $approvedlist = new approved_userlist($assigncontext, $component, $userlist1->get_userids());
 591          // Delete using delete_data_for_user.
 592          provider::delete_data_for_users($approvedlist);
 593  
 594          // Re-fetch users in assigncontext.
 595          $userlist1 = new \core_privacy\local\request\userlist($assigncontext, $component);
 596          provider::get_users_in_context($userlist1);
 597          // The user data in assigncontext should be deleted.
 598          $this->assertCount(0, $userlist1);
 599      }
 600  }