Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.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  // Note: This namespace is not technically correct, but we have to make it different to the tests for lib/upgradelib.php
  18  // and this is more correct than alternatives.
  19  namespace core\db;
  20  
  21  /**
  22   * Unit tests for the lib/db/upgradelib.php library.
  23   *
  24   * @package   core
  25   * @category  phpunit
  26   * @copyright 2022 Andrew Lyons <andrew@thelyons.family>
  27   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   */
  29  class upgradelib_test extends \advanced_testcase {
  30  
  31      /**
  32       * Shared setup for the testcase.
  33       */
  34      public function setUp(): void {
  35          global $CFG;
  36  
  37          require_once("{$CFG->libdir}/db/upgradelib.php");
  38          require_once("{$CFG->dirroot}/my/lib.php");
  39      }
  40  
  41      /**
  42       * Ensure that the upgrade_block_set_defaultregion function performs as expected.
  43       *
  44       * Only targetted blocks and pages should be affected.
  45       *
  46       * @covers ::upgrade_block_set_defaultregion
  47       */
  48      public function test_upgrade_block_set_defaultregion(): void {
  49          global $DB;
  50  
  51          $this->resetAfterTest();
  52  
  53          // Ensure that only the targetted blocks are affected.
  54  
  55          // Create a my-index entry for the Dashboard.
  56          $dashboardid = $DB->insert_record('my_pages', (object) [
  57              'name' => '__default',
  58              'private' => MY_PAGE_PRIVATE,
  59          ]);
  60  
  61          // Create a page for the my-courses page.
  62          $mycoursesid = $DB->insert_record('my_pages', (object) [
  63              'name' => '__courses',
  64              'private' => MY_PAGE_PRIVATE,
  65          ]);
  66  
  67          $unchanged = [];
  68          $changed = [];
  69  
  70          // Create several blocks of different types.
  71          // These are not linked to the my-index page above, so should not be modified.
  72          $unchanged[] = $this->getDataGenerator()->create_block('online_users', [
  73              'defaultregion' => 'left-side',
  74          ]);
  75          $unchanged[] = $this->getDataGenerator()->create_block('myoverview', [
  76              'defaultregion' => 'left-side',
  77          ]);
  78          $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [
  79              'defaultregion' => 'left-side',
  80          ]);
  81  
  82          // These are on the my-index above, but are not the block being updated.
  83          $unchanged[] = $this->getDataGenerator()->create_block('online_users', [
  84              'pagetypepattern' => 'my-index',
  85              'subpagepattern' => $dashboardid,
  86              'defaultregion' => 'left-side',
  87          ]);
  88          $unchanged[] = $this->getDataGenerator()->create_block('myoverview', [
  89              'pagetypepattern' => 'my-index',
  90              'subpagepattern' => $dashboardid,
  91              'defaultregion' => 'left-side',
  92          ]);
  93  
  94          // This is on a my-index page, and is the affected block, but is on the mycourses page, not the dashboard.
  95          $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [
  96              'pagetypepattern' => 'my-index',
  97              'subpagepattern' => $mycoursesid,
  98              'defaultregion' => 'left-side',
  99          ]);
 100  
 101          // This is on the default dashboard, and is the affected block, but not a my-index page.
 102          $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [
 103              'pagetypepattern' => 'not-my-index',
 104              'subpagepattern' => $dashboardid,
 105              'defaultregion' => 'left-side',
 106          ]);
 107  
 108          // This is the match which should be changed.
 109          $changed[] = $this->getDataGenerator()->create_block('calendar_month', [
 110              'pagetypepattern' => 'my-index',
 111              'subpagepattern' => $dashboardid,
 112              'defaultregion' => 'left-side',
 113          ]);
 114  
 115          // Perform the operation.
 116          // Target all calendar_month blocks matching 'my-index' and update them to the 'content' region where they
 117          // belong to the user dashboard ('pagename' == '__default').
 118          upgrade_block_set_defaultregion('calendar_month', '__default', 'my-index', 'content');
 119  
 120          // Ensure that the relevant blocks remain unchanged.
 121          foreach ($unchanged as $original) {
 122              $block = $DB->get_record('block_instances', ['id' => $original->id]);
 123              $this->assertEquals($original, $block);
 124          }
 125  
 126          // Ensure that only the expected blocks were changed.
 127          foreach ($changed as $original) {
 128              $block = $DB->get_record('block_instances', ['id' => $original->id]);
 129              $this->assertNotEquals($original, $block);
 130  
 131              // Only the defaultregion should be updated to content. No other changes are expected.
 132              $expected = (object) $original;
 133              $expected->defaultregion = 'content';
 134              $this->assertEquals($expected, $block);
 135          }
 136      }
 137  
 138      /**
 139       * Ensure that the upgrade_block_set_defaultregion function performs as expected.
 140       *
 141       * Missing block entries will be created.
 142       *
 143       * @covers ::upgrade_block_set_defaultregion
 144       */
 145      public function test_upgrade_block_set_defaultregion_create_missing(): void {
 146          global $DB;
 147  
 148          $this->resetAfterTest();
 149  
 150          // Ensure that only the targetted blocks are affected.
 151  
 152          $dashboards = [];
 153          $mycourses = [];
 154          // Create dashboard pages for a number of users.
 155          while (count($dashboards) < 10) {
 156              $user = $this->getDataGenerator()->create_user();
 157              $dashboards[] = $DB->insert_record('my_pages', (object) [
 158                  'userid' => $user->id,
 159                  'name' => '__default',
 160                  'private' => MY_PAGE_PRIVATE,
 161              ]);
 162  
 163              $mycourses[] = $DB->insert_record('my_pages', (object) [
 164                  'userid' => $user->id,
 165                  'name' => '__courses',
 166                  'private' => MY_PAGE_PRIVATE,
 167              ]);
 168          }
 169  
 170          // Enusre that there are no blocks initially.
 171          foreach ($dashboards as $dashboardid) {
 172              $this->assertEquals(0, $DB->count_records('block_instances', [
 173                  'subpagepattern' => $dashboardid,
 174              ]));
 175          }
 176  
 177          // Perform the operation.
 178          // Target all calendar_month blocks matching 'my-index' and update them to the 'content' region where they
 179          // belong to the user dashboard ('pagename' == '__default').
 180          // Any dashboards which are missing the block will have it created by the operation.
 181          upgrade_block_set_defaultregion('calendar_month', '__default', 'my-index', 'content');
 182  
 183          // Each of the dashboards should now have a block instance of the calendar_month block in the 'content' region
 184          // on 'my-index' only.
 185          foreach ($dashboards as $dashboardid) {
 186              // Only one block should have been created.
 187              $blocks = $DB->get_records('block_instances', [
 188                  'subpagepattern' => $dashboardid,
 189              ]);
 190              $this->assertCount(1, $blocks);
 191  
 192              $theblock = reset($blocks);
 193              $this->assertEquals('calendar_month', $theblock->blockname);
 194              $this->assertEquals('content', $theblock->defaultregion);
 195              $this->assertEquals('my-index', $theblock->pagetypepattern);
 196  
 197              // Fetch the user details.
 198              $dashboard = $DB->get_record('my_pages', ['id' => $dashboardid]);
 199              $usercontext = \context_user::instance($dashboard->userid);
 200  
 201              $this->assertEquals($usercontext->id, $theblock->parentcontextid);
 202          }
 203  
 204          // Enusre that there are no blocks on the mycourses page.
 205          foreach ($mycourses as $pageid) {
 206              $this->assertEquals(0, $DB->count_records('block_instances', [
 207                  'subpagepattern' => $pageid,
 208              ]));
 209          }
 210      }
 211  
 212      /**
 213       * Ensure that the upgrade_block_delete_instances function performs as expected.
 214       *
 215       * Missing block entries will be created.
 216       *
 217       * @covers ::upgrade_block_delete_instances
 218       */
 219      public function test_upgrade_block_delete_instances(): void {
 220          global $DB;
 221  
 222          $this->resetAfterTest();
 223  
 224          $DB->delete_records('block_instances');
 225  
 226          // Ensure that only the targetted blocks are affected.
 227  
 228          // Get the my-index entry for the Dashboard.
 229          $dashboardid = $DB->get_record('my_pages', [
 230              'userid' => null,
 231              'name' => '__default',
 232              'private' => MY_PAGE_PRIVATE,
 233          ], 'id')->id;
 234  
 235          // Get the page for the my-courses page.
 236          $mycoursesid = $DB->get_record('my_pages', [
 237              'name' => MY_PAGE_COURSES,
 238          ], 'id')->id;
 239  
 240          $dashboards = [];
 241          $unchanged = [];
 242          $unchangedcontexts = [];
 243          $unchangedpreferences = [];
 244          $deleted = [];
 245          $deletedcontexts = [];
 246          $deletedpreferences = [];
 247  
 248          // Create several blocks of different types.
 249          // These are not linked to the my page above, so should not be modified.
 250          $unchanged[] = $this->getDataGenerator()->create_block('online_users', [
 251              'defaultregion' => 'left-side',
 252          ]);
 253          $unchanged[] = $this->getDataGenerator()->create_block('myoverview', [
 254              'defaultregion' => 'left-side',
 255          ]);
 256          $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [
 257              'defaultregion' => 'left-side',
 258          ]);
 259  
 260          // These are on the my-index above, but are not the block being updated.
 261          $unchanged[] = $this->getDataGenerator()->create_block('online_users', [
 262              'pagetypepattern' => 'my-index',
 263              'subpagepattern' => $dashboardid,
 264              'defaultregion' => 'left-side',
 265          ]);
 266          $unchanged[] = $this->getDataGenerator()->create_block('myoverview', [
 267              'pagetypepattern' => 'my-index',
 268              'subpagepattern' => $dashboardid,
 269              'defaultregion' => 'left-side',
 270          ]);
 271  
 272          // This is on a my-index page, and is the affected block, but is on the mycourses page, not the dashboard.
 273          $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [
 274              'pagetypepattern' => 'my-index',
 275              'subpagepattern' => $mycoursesid,
 276              'defaultregion' => 'left-side',
 277          ]);
 278  
 279          // This is on the default dashboard, and is the affected block, but not a my-index page.
 280          $unchanged[] = $this->getDataGenerator()->create_block('calendar_month', [
 281              'pagetypepattern' => 'not-my-index',
 282              'subpagepattern' => $dashboardid,
 283              'defaultregion' => 'left-side',
 284          ]);
 285  
 286          // This is the match which should be changed.
 287          $deleted[] = $this->getDataGenerator()->create_block('calendar_month', [
 288              'pagetypepattern' => 'my-index',
 289              'subpagepattern' => $dashboardid,
 290              'defaultregion' => 'left-side',
 291          ]);
 292  
 293          // Create blocks for users with preferences now.
 294          while (count($dashboards) < 10) {
 295              $userunchangedblocks = [];
 296              $userdeletedblocks = [];
 297  
 298              $user = $this->getDataGenerator()->create_user();
 299              $userdashboardid = $DB->insert_record('my_pages', (object) [
 300                  'userid' => $user->id,
 301                  'name' => '__default',
 302                  'private' => MY_PAGE_PRIVATE,
 303              ]);
 304              $dashboards[] = $userdashboardid;
 305  
 306              $usermycoursesid = $DB->insert_record('my_pages', (object) [
 307                  'userid' => $user->id,
 308                  'name' => '__courses',
 309                  'private' => MY_PAGE_PRIVATE,
 310              ]);
 311  
 312              // These are on the my-index above, but are not the block being updated.
 313              $userunchangedblocks[] = $this->getDataGenerator()->create_block('online_users', [
 314                  'pagetypepattern' => 'my-index',
 315                  'subpagepattern' => $userdashboardid,
 316                  'defaultregion' => 'left-side',
 317              ]);
 318              $userunchangedblocks[] = $this->getDataGenerator()->create_block('myoverview', [
 319                  'pagetypepattern' => 'my-index',
 320                  'subpagepattern' => $userdashboardid,
 321                  'defaultregion' => 'left-side',
 322              ]);
 323  
 324              // This is on a my-index page, and is the affected block, but is on the mycourses page, not the dashboard.
 325              $userunchangedblocks[] = $this->getDataGenerator()->create_block('calendar_month', [
 326                  'pagetypepattern' => 'my-index',
 327                  'subpagepattern' => $usermycoursesid,
 328                  'defaultregion' => 'left-side',
 329              ]);
 330  
 331              // This is on the default dashboard, and is the affected block, but not a my-index page.
 332              $userunchangedblocks[] = $this->getDataGenerator()->create_block('calendar_month', [
 333                  'pagetypepattern' => 'not-my-index',
 334                  'subpagepattern' => $userdashboardid,
 335                  'defaultregion' => 'left-side',
 336              ]);
 337  
 338              // This is the match which should be changed.
 339              $userdeletedblocks[] = $this->getDataGenerator()->create_block('calendar_month', [
 340                  'pagetypepattern' => 'my-index',
 341                  'subpagepattern' => $userdashboardid,
 342                  'defaultregion' => 'left-side',
 343              ]);
 344  
 345              $unchanged += $userunchangedblocks;
 346              $deleted += $userdeletedblocks;
 347  
 348              foreach ($userunchangedblocks as $block) {
 349                  // Create user preferences for these blocks.
 350                  set_user_preference("block{$block->id}hidden", 1, $user);
 351                  set_user_preference("docked_block_instance_{$block->id}", 1, $user);
 352                  $unchangedpreferences[] = $block->id;
 353              }
 354  
 355              foreach ($userdeletedblocks as $block) {
 356                  // Create user preferences for these blocks.
 357                  set_user_preference("block{$block->id}hidden", 1, $user);
 358                  set_user_preference("docked_block_instance_{$block->id}", 1, $user);
 359                  $deletedpreferences[] = $block->id;
 360              }
 361          }
 362  
 363          // Create missing contexts.
 364          \context_helper::create_instances(CONTEXT_BLOCK);
 365  
 366          // Ensure that other related test data is present.
 367          $systemcontext = \context_system::instance();
 368          foreach ($unchanged as $block) {
 369              // Get contexts.
 370              $unchangedcontexts[] = \context_block::instance($block->id);
 371  
 372              // Create a block position.
 373              $DB->insert_record('block_positions', [
 374                  'blockinstanceid' => $block->id,
 375                  'contextid' => $systemcontext->id,
 376                  'pagetype' => 'course-view-topics',
 377                  'region' => 'site-post',
 378                  'weight' => 1,
 379                  'visible' => 1,
 380              ]);
 381          }
 382  
 383          foreach ($deleted as $block) {
 384              // Get contexts.
 385              $deletedcontexts[] = \context_block::instance($block->id);
 386  
 387              // Create a block position.
 388              $DB->insert_record('block_positions', [
 389                  'blockinstanceid' => $block->id,
 390                  'contextid' => $systemcontext->id,
 391                  'pagetype' => 'course-view-topics',
 392                  'region' => 'site-post',
 393                  'weight' => 1,
 394                  'visible' => 1,
 395              ]);
 396          }
 397  
 398          // Perform the operation.
 399          // Target all calendar_month blocks matching 'my-index' and update them to the 'content' region where they
 400          // belong to the user dashboard ('pagename' == '__default').
 401          upgrade_block_delete_instances('calendar_month', '__default', 'my-index');
 402  
 403          // Ensure that the relevant blocks remain unchanged.
 404          foreach ($unchanged as $original) {
 405              $block = $DB->get_record('block_instances', ['id' => $original->id]);
 406              $this->assertEquals($original, $block);
 407  
 408              // Ensure that the block positions remain.
 409              $this->assertEquals(1, $DB->count_records('block_positions', ['blockinstanceid' => $original->id]));
 410          }
 411  
 412          foreach ($unchangedcontexts as $context) {
 413              // Ensure that the context still exists.
 414              $this->assertEquals(1, $DB->count_records('context', ['id' => $context->id]));
 415          }
 416  
 417          foreach ($unchangedpreferences as $blockid) {
 418              // Ensure that the context still exists.
 419              $this->assertEquals(1, $DB->count_records('user_preferences', ['name' => "block{$blockid}hidden"]));
 420              $this->assertEquals(1, $DB->count_records('user_preferences', [
 421                  'name' => "docked_block_instance_{$blockid}",
 422              ]));
 423          }
 424  
 425          // Ensure that only the expected blocks were changed.
 426          foreach ($deleted as $original) {
 427              $this->assertCount(0, $DB->get_records('block_instances', ['id' => $original->id]));
 428  
 429              // Ensure that the block positions was removed.
 430              $this->assertEquals(0, $DB->count_records('block_positions', ['blockinstanceid' => $original->id]));
 431          }
 432  
 433          foreach ($deletedcontexts as $context) {
 434              // Ensure that the context still exists.
 435              $this->assertEquals(0, $DB->count_records('context', ['id' => $context->id]));
 436          }
 437  
 438          foreach ($deletedpreferences as $blockid) {
 439              // Ensure that the context still exists.
 440              $this->assertEquals(0, $DB->count_records('user_preferences', ['name' => "block{$blockid}hidden"]));
 441              $this->assertEquals(0, $DB->count_records('user_preferences', [
 442                  'name' => "docked_block_instance_{$blockid}",
 443              ]));
 444          }
 445      }
 446  
 447      /**
 448       * Ensrue that the upgrade_block_set_my_user_parent_context function performs as expected.
 449       *
 450       * @covers ::upgrade_block_set_my_user_parent_context
 451       */
 452      public function test_upgrade_block_set_my_user_parent_context(): void {
 453          global $DB;
 454  
 455          $this->resetAfterTest();
 456          $this->preventResetByRollback();
 457  
 458          $systemcontext = \context_system::instance();
 459  
 460          $dashboards = [];
 461          $otherblocknames = [
 462              'online_users',
 463              'myoverview',
 464              'calendar_month',
 465          ];
 466          $affectedblockname = 'timeline';
 467  
 468          // Create dashboard pages for a number of users.
 469          while (count($dashboards) < 10) {
 470              $user = $this->getDataGenerator()->create_user();
 471              $dashboard = $DB->insert_record('my_pages', (object) [
 472                  'userid' => $user->id,
 473                  'name' => '__default',
 474                  'private' => MY_PAGE_PRIVATE,
 475              ]);
 476              $dashboards[] = $dashboard;
 477  
 478              $mycourse = $DB->insert_record('my_pages', (object) [
 479                  'userid' => $user->id,
 480                  'name' => '__courses',
 481                  'private' => MY_PAGE_PRIVATE,
 482              ]);
 483  
 484              // These are on the my-index above, but are not the block being updated.
 485              foreach ($otherblocknames as $blockname) {
 486                  $unchanged[] = $this->getDataGenerator()->create_block($blockname, [
 487                      'parentcontextid' => $systemcontext->id,
 488                      'pagetypepattern' => 'my-index',
 489                      'subpagepattern' => $dashboard,
 490                  ]);
 491              }
 492  
 493              // This is on a my-index page, and is the affected block, but is on the mycourses page, not the dashboard.
 494              $unchanged[] = $this->getDataGenerator()->create_block($affectedblockname, [
 495                  'parentcontextid' => $systemcontext->id,
 496                  'pagetypepattern' => 'my-index',
 497                  'subpagepattern' => $mycourse,
 498              ]);
 499  
 500              // This is on the default dashboard, and is the affected block, but not a my-index page.
 501              $unchanged[] = $this->getDataGenerator()->create_block($affectedblockname, [
 502                  'parentcontextid' => $systemcontext->id,
 503                  'pagetypepattern' => 'not-my-index',
 504                  'subpagepattern' => $dashboard,
 505              ]);
 506  
 507              // This is the match which should be changed.
 508              $changed[] = $this->getDataGenerator()->create_block($affectedblockname, [
 509                  'parentcontextid' => $systemcontext->id,
 510                  'pagetypepattern' => 'my-index',
 511                  'subpagepattern' => $dashboard,
 512              ]);
 513          }
 514  
 515          // Perform the operation.
 516          // Target all affected blocks matching 'my-index' and correct the context to the relevant user's contexct.
 517          // Only the '__default' dashboard on the 'my-index' my_page should be affected.
 518          upgrade_block_set_my_user_parent_context($affectedblockname, '__default', 'my-index');
 519  
 520          // Ensure that the relevant blocks remain unchanged.
 521          foreach ($unchanged as $original) {
 522              $block = $DB->get_record('block_instances', ['id' => $original->id]);
 523              $this->assertEquals($original, $block);
 524          }
 525  
 526          // Ensure that only the expected blocks were changed.
 527          foreach ($changed as $original) {
 528              $block = $DB->get_record('block_instances', ['id' => $original->id]);
 529              $this->assertNotEquals($original, $block);
 530  
 531              // Fetch the my page and user details.
 532              $dashboard = $DB->get_record('my_pages', ['id' => $original->subpagepattern]);
 533              $usercontext = \context_user::instance($dashboard->userid);
 534  
 535              // Only the contextid should be updated to the relevant user's context.
 536              // No other changes are expected.
 537              $expected = (object) $original;
 538              $expected->parentcontextid = $usercontext->id;
 539              $this->assertEquals($expected, $block);
 540          }
 541      }
 542  }