Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
   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   * Unit tests for the block_html implementation of the privacy API.
  19   *
  20   * @package    block_html
  21   * @category   test
  22   * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  use \core_privacy\local\request\writer;
  29  use \core_privacy\local\request\approved_contextlist;
  30  use \core_privacy\local\request\approved_userlist;
  31  use \block_html\privacy\provider;
  32  
  33  /**
  34   * Unit tests for the block_html implementation of the privacy API.
  35   *
  36   * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class block_html_privacy_testcase extends \core_privacy\tests\provider_testcase {
  40      /**
  41       * Get the list of standard format options for comparison.
  42       *
  43       * @return \stdClass
  44       */
  45      protected function get_format_options() {
  46          return (object) [
  47              'overflowdiv' => true,
  48              'noclean' => true,
  49          ];
  50      }
  51  
  52      /**
  53       * Creates an HTML block on a user.
  54       *
  55       * @param   string  $title
  56       * @param   string  $body
  57       * @param   string  $format
  58       * @return  \block_instance
  59       */
  60      protected function create_user_block($title, $body, $format) {
  61          global $USER;
  62  
  63          $configdata = (object) [
  64              'title' => $title,
  65              'text' => [
  66                  'itemid' => 19,
  67                  'text' => $body,
  68                  'format' => $format,
  69              ],
  70          ];
  71  
  72          $this->create_block($this->construct_user_page($USER));
  73          $block = $this->get_last_block_on_page($this->construct_user_page($USER));
  74          $block = block_instance('html', $block->instance);
  75          $block->instance_config_save((object) $configdata);
  76  
  77          return $block;
  78      }
  79  
  80      /**
  81       * Creates an HTML block on a course.
  82       *
  83       * @param   \stdClass $course
  84       * @param   string  $title
  85       * @param   string  $body
  86       * @param   string  $format
  87       * @return  \block_instance
  88       */
  89      protected function create_course_block($course, $title, $body, $format) {
  90          global $USER;
  91  
  92          $configdata = (object) [
  93              'title' => $title,
  94              'text' => [
  95                  'itemid' => 19,
  96                  'text' => $body,
  97                  'format' => $format,
  98              ],
  99          ];
 100  
 101          $this->create_block($this->construct_course_page($course));
 102          $block = $this->get_last_block_on_page($this->construct_course_page($course));
 103          $block = block_instance('html', $block->instance);
 104          $block->instance_config_save((object) $configdata);
 105  
 106          return $block;
 107      }
 108  
 109      /**
 110       * Creates an HTML block on a page.
 111       *
 112       * @param \page $page Page
 113       */
 114      protected function create_block($page) {
 115          $page->blocks->add_block_at_end_of_default_region('html');
 116      }
 117  
 118      /**
 119       * Get the last block on the page.
 120       *
 121       * @param \page $page Page
 122       * @return \block_html Block instance object
 123       */
 124      protected function get_last_block_on_page($page) {
 125          $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
 126          $block = end($blocks);
 127  
 128          return $block;
 129      }
 130  
 131      /**
 132       * Constructs a Page object for the User Dashboard.
 133       *
 134       * @param   \stdClass       $user User to create Dashboard for.
 135       * @return  \moodle_page
 136       */
 137      protected function construct_user_page(\stdClass $user) {
 138          $page = new \moodle_page();
 139          $page->set_context(\context_user::instance($user->id));
 140          $page->set_pagelayout('mydashboard');
 141          $page->set_pagetype('my-index');
 142          $page->blocks->load_blocks();
 143          return $page;
 144      }
 145  
 146      /**
 147       * Constructs a Page object for the User Dashboard.
 148       *
 149       * @param   \stdClass       $course Course to create Dashboard for.
 150       * @return  \moodle_page
 151       */
 152      protected function construct_course_page(\stdClass $course) {
 153          $page = new \moodle_page();
 154          $page->set_context(\context_course::instance($course->id));
 155          $page->set_pagelayout('standard');
 156          $page->set_pagetype('course-view');
 157          $page->set_course($course);
 158          $page->blocks->load_blocks();
 159          return $page;
 160      }
 161  
 162      /**
 163       * Test that a block on the dashboard is exported.
 164       */
 165      public function test_user_block() {
 166          $this->resetAfterTest();
 167  
 168          $title = 'Example title';
 169          $content = 'Example content';
 170          $format = FORMAT_PLAIN;
 171  
 172          // Test setup.
 173          $user = $this->getDataGenerator()->create_user();
 174          $this->setUser($user);
 175          $block = $this->create_user_block($title, $content, $format);
 176          $context = \context_block::instance($block->instance->id);
 177  
 178          // Get the contexts.
 179          $contextlist = provider::get_contexts_for_userid($user->id);
 180  
 181          // Only the user context should be returned.
 182          $this->assertCount(1, $contextlist);
 183          $this->assertEquals($context, $contextlist->current());
 184  
 185          // Export the data.
 186          $this->export_context_data_for_user($user->id, $context, 'block_html');
 187          $writer = \core_privacy\local\request\writer::with_context($context);
 188          $this->assertTrue($writer->has_any_data());
 189  
 190          // Check the data.
 191          $data = $writer->get_data([]);
 192          $this->assertInstanceOf('stdClass', $data);
 193          $this->assertEquals($title, $data->title);
 194          $this->assertEquals(format_text($content, $format, $this->get_format_options()), $data->content);
 195  
 196          // Delete the context.
 197          provider::delete_data_for_all_users_in_context($context);
 198  
 199          // Re-fetch the contexts - it should no longer be returned.
 200          $contextlist = provider::get_contexts_for_userid($user->id);
 201          $this->assertCount(0, $contextlist);
 202      }
 203  
 204      /**
 205       * Test that a block on the dashboard which is not configured is _not_ exported.
 206       */
 207      public function test_user_block_unconfigured() {
 208          global $DB;
 209  
 210          $this->resetAfterTest();
 211  
 212          $title = 'Example title';
 213          $content = 'Example content';
 214          $format = FORMAT_PLAIN;
 215  
 216          // Test setup.
 217          $user = $this->getDataGenerator()->create_user();
 218          $this->setUser($user);
 219          $block = $this->create_user_block($title, $content, $format);
 220          $block->instance->configdata = '';
 221          $DB->update_record('block_instances', $block->instance);
 222          $block = block_instance('html', $block->instance);
 223  
 224          $context = \context_block::instance($block->instance->id);
 225  
 226          // Get the contexts.
 227          $contextlist = provider::get_contexts_for_userid($user->id);
 228  
 229          // Only the user context should be returned.
 230          $this->assertCount(1, $contextlist);
 231          $this->assertEquals($context, $contextlist->current());
 232  
 233          // Export the data.
 234          $this->export_context_data_for_user($user->id, $context, 'block_html');
 235          $writer = \core_privacy\local\request\writer::with_context($context);
 236          $this->assertFalse($writer->has_any_data());
 237      }
 238  
 239      /**
 240       * Test that a block on the dashboard is exported.
 241       */
 242      public function test_user_multiple_blocks_exported() {
 243          $this->resetAfterTest();
 244  
 245          $title = 'Example title';
 246          $content = 'Example content';
 247          $format = FORMAT_PLAIN;
 248  
 249          // Test setup.
 250          $blocks = [];
 251          $contexts = [];
 252          $user = $this->getDataGenerator()->create_user();
 253          $this->setUser($user);
 254  
 255          $block = $this->create_user_block($title, $content, $format);
 256          $context = \context_block::instance($block->instance->id);
 257          $contexts[$context->id] = $context;
 258  
 259          $block = $this->create_user_block($title, $content, $format);
 260          $context = \context_block::instance($block->instance->id);
 261          $contexts[$context->id] = $context;
 262  
 263          // Get the contexts.
 264          $contextlist = provider::get_contexts_for_userid($user->id);
 265  
 266          // There are now two blocks on the user context.
 267          $this->assertCount(2, $contextlist);
 268          foreach ($contextlist as $context) {
 269              $this->assertTrue(isset($contexts[$context->id]));
 270          }
 271  
 272          // Turn them into an approved_contextlist.
 273          $approvedlist = new approved_contextlist($user, 'block_html', $contextlist->get_contextids());
 274  
 275          // Delete using delete_data_for_user.
 276          provider::delete_data_for_user($approvedlist);
 277  
 278          // Re-fetch the contexts - it should no longer be returned.
 279          $contextlist = provider::get_contexts_for_userid($user->id);
 280          $this->assertCount(0, $contextlist);
 281      }
 282  
 283      /**
 284       * Test that a block on the dashboard is not exported.
 285       */
 286      public function test_course_blocks_not_exported() {
 287          $this->resetAfterTest();
 288  
 289          $title = 'Example title';
 290          $content = 'Example content';
 291          $format = FORMAT_PLAIN;
 292  
 293          // Test setup.
 294          $user = $this->getDataGenerator()->create_user();
 295          $course = $this->getDataGenerator()->create_course();
 296          $this->setUser($user);
 297  
 298          $block = $this->create_course_block($course, $title, $content, $format);
 299          $context = \context_block::instance($block->instance->id);
 300  
 301          // Get the contexts.
 302          $contextlist = provider::get_contexts_for_userid($user->id);
 303  
 304          // No blocks should be returned.
 305          $this->assertCount(0, $contextlist);
 306      }
 307  
 308      /**
 309       * Test that a block on the dashboard is exported.
 310       */
 311      public function test_mixed_multiple_blocks_exported() {
 312          $this->resetAfterTest();
 313  
 314          $title = 'Example title';
 315          $content = 'Example content';
 316          $format = FORMAT_PLAIN;
 317  
 318          // Test setup.
 319          $contexts = [];
 320  
 321          $user = $this->getDataGenerator()->create_user();
 322          $course = $this->getDataGenerator()->create_course();
 323          $this->setUser($user);
 324  
 325          $block = $this->create_course_block($course, $title, $content, $format);
 326          $context = \context_block::instance($block->instance->id);
 327  
 328          $block = $this->create_user_block($title, $content, $format);
 329          $context = \context_block::instance($block->instance->id);
 330          $contexts[$context->id] = $context;
 331  
 332          $block = $this->create_user_block($title, $content, $format);
 333          $context = \context_block::instance($block->instance->id);
 334          $contexts[$context->id] = $context;
 335  
 336          // Get the contexts.
 337          $contextlist = provider::get_contexts_for_userid($user->id);
 338  
 339          // There are now two blocks on the user context.
 340          $this->assertCount(2, $contextlist);
 341          foreach ($contextlist as $context) {
 342              $this->assertTrue(isset($contexts[$context->id]));
 343          }
 344      }
 345  
 346      /**
 347       * Test that only users with a user context HTML block are fetched.
 348       */
 349      public function test_get_users_in_context() {
 350          $this->resetAfterTest();
 351  
 352          $component = 'block_html';
 353          $title = 'Block title';
 354          $content = 'Block content';
 355          $blockformat = FORMAT_PLAIN;
 356  
 357          // Create a user with a user context HTML block.
 358          $user1 = $this->getDataGenerator()->create_user();
 359          $this->setUser($user1);
 360  
 361          $userblock = $this->create_user_block($title, $content, $blockformat);
 362          $usercontext = \context_block::instance($userblock->instance->id);
 363  
 364          // Create a user with a course context HTML block.
 365          $user2 = $this->getDataGenerator()->create_user();
 366          $this->setUser($user2);
 367  
 368          $course = $this->getDataGenerator()->create_course();
 369          $courseblock = $this->create_course_block($course, $title, $content, $blockformat);
 370          $coursecontext = \context_block::instance($courseblock->instance->id);
 371  
 372          // Ensure only the user with a user context HTML block is returned.
 373          $userlist = new \core_privacy\local\request\userlist($usercontext, $component);
 374          \block_html\privacy\provider::get_users_in_context($userlist);
 375  
 376          $this->assertCount(1, $userlist);
 377  
 378          $expected = [$user1->id];
 379          $actual = $userlist->get_userids();
 380  
 381          $this->assertEquals($expected, $actual);
 382  
 383          // Ensure passing the course context returns no users.
 384          $userlist = new \core_privacy\local\request\userlist($coursecontext, $component);
 385          \mod_forum\privacy\provider::get_users_in_context($userlist);
 386          $this->assertEmpty($userlist);
 387      }
 388  
 389      /**
 390       * Test that data for users in approved userlist is deleted.
 391       */
 392      public function test_delete_data_for_users() {
 393          $this->resetAfterTest();
 394  
 395          $component = 'block_html';
 396          $title = 'Block title';
 397          $content = 'Block content';
 398          $blockformat = FORMAT_PLAIN;
 399  
 400          // Create 2 user swith a user context HTML blocks.
 401          $user1 = $this->getDataGenerator()->create_user();
 402          $this->setUser($user1);
 403  
 404          $block1 = $this->create_user_block($title, $content, $blockformat);
 405          $context1 = \context_block::instance($block1->instance->id);
 406  
 407          $user2 = $this->getDataGenerator()->create_user();
 408          $this->setUser($user2);
 409          $block2 = $this->create_user_block($title, $content, $blockformat);
 410          $context2 = \context_block::instance($block2->instance->id);
 411  
 412          // Create and populate the userlists.
 413          $userlist1 = new \core_privacy\local\request\userlist($context1, $component);
 414          \block_html\privacy\provider::get_users_in_context($userlist1);
 415          $userlist2 = new \core_privacy\local\request\userlist($context2, $component);
 416          \block_html\privacy\provider::get_users_in_context($userlist2);
 417  
 418          // Ensure both members are included.
 419          $this->assertCount(1, $userlist1);
 420          $this->assertCount(1, $userlist2);
 421  
 422          // Convert $userlist1 into an approved_contextlist.
 423          $approvedlist = new approved_userlist($context1, 'block_html', $userlist1->get_userids());
 424  
 425          // Delete using delete_data_for_user.
 426          provider::delete_data_for_users($approvedlist);
 427  
 428          // Re-fetch users in the contexts - only the first one should now be empty.
 429          $userlist1 = new \core_privacy\local\request\userlist($context1, $component);
 430          \block_html\privacy\provider::get_users_in_context($userlist1);
 431          $this->assertCount(0, $userlist1);
 432  
 433          $userlist2 = new \core_privacy\local\request\userlist($context2, $component);
 434          \block_html\privacy\provider::get_users_in_context($userlist2);
 435          $this->assertCount(1, $userlist2);
 436      }
 437  }