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.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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  namespace core_block;
  18  
  19  use core_block_external;
  20  use externallib_advanced_testcase;
  21  
  22  defined('MOODLE_INTERNAL') || die();
  23  
  24  global $CFG;
  25  
  26  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  27  require_once($CFG->dirroot . '/my/lib.php');
  28  
  29  /**
  30   * External block functions unit tests
  31   *
  32   * @package    core_block
  33   * @category   external
  34   * @copyright  2015 Juan Leyva <juan@moodle.com>
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   * @since      Moodle 3.0
  37   */
  38  class externallib_test extends externallib_advanced_testcase {
  39  
  40      /**
  41       * Test get_course_blocks
  42       */
  43      public function test_get_course_blocks() {
  44          global $DB, $FULLME;
  45  
  46          $this->resetAfterTest(true);
  47  
  48          $user = $this->getDataGenerator()->create_user();
  49          $course = $this->getDataGenerator()->create_course();
  50          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
  51          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
  52  
  53          $page = new \moodle_page();
  54          $page->set_context(\context_course::instance($course->id));
  55          $page->set_pagelayout('course');
  56          $course->format = course_get_format($course)->get_format();
  57          $page->set_pagetype('course-view-' . $course->format);
  58          $page->blocks->load_blocks();
  59          $newblock = 'calendar_upcoming';
  60          $page->blocks->add_block_at_end_of_default_region($newblock);
  61          $this->setUser($user);
  62  
  63          // Check for the new block.
  64          $result = core_block_external::get_course_blocks($course->id);
  65          // We need to execute the return values cleaning process to simulate the web service server.
  66          $result = \external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result);
  67  
  68          // Expect the new block.
  69          $this->assertCount(1, $result['blocks']);
  70          $this->assertEquals($newblock, $result['blocks'][0]['name']);
  71      }
  72  
  73      /**
  74       * Test get_course_blocks on site home
  75       */
  76      public function test_get_course_blocks_site_home() {
  77          global $DB, $FULLME;
  78  
  79          $this->resetAfterTest(true);
  80  
  81          $user = $this->getDataGenerator()->create_user();
  82  
  83          $page = new \moodle_page();
  84          $page->set_context(\context_course::instance(SITEID));
  85          $page->set_pagelayout('frontpage');
  86          $page->set_pagetype('site-index');
  87          $page->blocks->load_blocks();
  88          $newblock = 'calendar_upcoming';
  89          $page->blocks->add_block_at_end_of_default_region($newblock);
  90          $this->setUser($user);
  91  
  92          // Check for the new block.
  93          $result = core_block_external::get_course_blocks(SITEID);
  94          // We need to execute the return values cleaning process to simulate the web service server.
  95          $result = \external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result);
  96  
  97          // Expect the new block.
  98          $this->assertCount(1, $result['blocks']);
  99          $this->assertEquals($newblock, $result['blocks'][0]['name']);
 100      }
 101  
 102      /**
 103       * Test get_course_blocks
 104       */
 105      public function test_get_course_blocks_overrides() {
 106          global $DB, $CFG, $FULLME;
 107  
 108          $this->resetAfterTest(true);
 109  
 110          $CFG->defaultblocks_override = 'search_forums,course_list:calendar_upcoming,recent_activity';
 111  
 112          $user = $this->getDataGenerator()->create_user();
 113          $course = $this->getDataGenerator()->create_course();
 114          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 115          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
 116  
 117          $this->setUser($user);
 118  
 119          // Try default blocks.
 120          $result = core_block_external::get_course_blocks($course->id);
 121          // We need to execute the return values cleaning process to simulate the web service server.
 122          $result = \external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result);
 123  
 124          // Expect 4 default blocks.
 125          $this->assertCount(4, $result['blocks']);
 126  
 127          $expectedblocks = array('navigation', 'settings', 'search_forums', 'course_list',
 128                                  'calendar_upcoming', 'recent_activity');
 129          foreach ($result['blocks'] as $block) {
 130              if (!in_array($block['name'], $expectedblocks)) {
 131                  $this->fail("Unexpected block found: " . $block['name']);
 132              }
 133          }
 134  
 135      }
 136  
 137      /**
 138       * Test get_course_blocks contents
 139       */
 140      public function test_get_course_blocks_contents() {
 141          global $DB, $FULLME;
 142  
 143          $this->resetAfterTest(true);
 144  
 145          $user = $this->getDataGenerator()->create_user();
 146          $course = $this->getDataGenerator()->create_course();
 147          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 148          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
 149          $coursecontext = \context_course::instance($course->id);
 150  
 151          // Create a HTML block.
 152          $title = 'Some course info';
 153          $body = 'Some course info<br /><p>Some contents</p>';
 154          $bodyformat = FORMAT_MOODLE;
 155          $page = new \moodle_page();
 156          $page->set_context($coursecontext);
 157          $page->set_pagelayout('course');
 158          $course->format = course_get_format($course)->get_format();
 159          $page->set_pagetype('course-view-' . $course->format);
 160          $page->blocks->load_blocks();
 161          $newblock = 'html';
 162          $page->blocks->add_block_at_end_of_default_region($newblock);
 163  
 164          $this->setUser($user);
 165          // Re-create the page.
 166          $page = new \moodle_page();
 167          $page->set_context($coursecontext);
 168          $page->set_pagelayout('course');
 169          $course->format = course_get_format($course)->get_format();
 170          $page->set_pagetype('course-view-' . $course->format);
 171          $page->blocks->load_blocks();
 172          $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
 173          $block = end($blocks);
 174          $block = block_instance('html', $block->instance);
 175          $nonscalar = [
 176              'something' => true,
 177          ];
 178          $configdata = (object) [
 179              'title' => $title,
 180              'text' => [
 181                  'itemid' => 0,
 182                  'text' => $body,
 183                  'format' => $bodyformat,
 184              ],
 185              'nonscalar' => $nonscalar
 186          ];
 187          $block->instance_config_save((object) $configdata);
 188          $filename = 'img.png';
 189          $filerecord = array(
 190              'contextid' => \context_block::instance($block->instance->id)->id,
 191              'component' => 'block_html',
 192              'filearea' => 'content',
 193              'itemid' => 0,
 194              'filepath' => '/',
 195              'filename' => $filename,
 196          );
 197          // Create an area to upload the file.
 198          $fs = get_file_storage();
 199          // Create a file from the string that we made earlier.
 200          $file = $fs->create_file_from_string($filerecord, 'some fake content (should be an image).');
 201  
 202          // Check for the new block.
 203          $result = core_block_external::get_course_blocks($course->id, true);
 204          // We need to execute the return values cleaning process to simulate the web service server.
 205          $result = \external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result);
 206  
 207          // Expect the new block.
 208          $this->assertCount(1, $result['blocks']);
 209          $this->assertEquals($title, $result['blocks'][0]['contents']['title']);
 210          $this->assertEquals($body, $result['blocks'][0]['contents']['content']);
 211          $this->assertEquals(FORMAT_HTML, $result['blocks'][0]['contents']['contentformat']);    // Format change for external.
 212          $this->assertEquals('', $result['blocks'][0]['contents']['footer']);
 213          $this->assertCount(1, $result['blocks'][0]['contents']['files']);
 214          $this->assertEquals($newblock, $result['blocks'][0]['name']);
 215          $configcounts = 0;
 216          foreach ($result['blocks'][0]['configs'] as $config) {
 217              if ($config['type'] = 'plugin' && $config['name'] == 'allowcssclasses' && $config['value'] == json_encode('0')) {
 218                  $configcounts++;
 219              } else if ($config['type'] = 'instance' && $config['name'] == 'text' && $config['value'] == json_encode($body)) {
 220                  $configcounts++;
 221              } else if ($config['type'] = 'instance' && $config['name'] == 'title' && $config['value'] == json_encode($title)) {
 222                  $configcounts++;
 223              } else if ($config['type'] = 'instance' && $config['name'] == 'format' && $config['value'] == json_encode('0')) {
 224                  $configcounts++;
 225              } else if ($config['type'] = 'instance' && $config['name'] == 'nonscalar' &&
 226                      $config['value'] == json_encode($nonscalar)) {
 227                  $configcounts++;
 228              }
 229          }
 230          $this->assertEquals(5, $configcounts);
 231      }
 232  
 233      /**
 234       * Test get_course_blocks contents with mathjax.
 235       */
 236      public function test_get_course_blocks_contents_with_mathjax() {
 237          global $DB, $CFG;
 238  
 239          require_once($CFG->dirroot . '/lib/externallib.php');
 240  
 241          $this->resetAfterTest(true);
 242  
 243          // Enable MathJax filter in content and headings.
 244          $this->configure_filters([
 245              ['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true],
 246          ]);
 247  
 248          // Create a few stuff to test with.
 249          $user = $this->getDataGenerator()->create_user();
 250          $course = $this->getDataGenerator()->create_course();
 251          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 252          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
 253          $coursecontext = \context_course::instance($course->id);
 254  
 255          // Create a HTML block.
 256          $title = 'My block $$(a+b)=2$$';
 257          $body = 'My block contents $$(a+b)=2$$';
 258          $bodyformat = FORMAT_MOODLE;
 259          $page = new \moodle_page();
 260          $page->set_context($coursecontext);
 261          $page->set_pagelayout('course');
 262          $course->format = course_get_format($course)->get_format();
 263          $page->set_pagetype('course-view-' . $course->format);
 264          $page->blocks->load_blocks();
 265          $newblock = 'html';
 266          $page->blocks->add_block_at_end_of_default_region($newblock);
 267  
 268          $this->setUser($user);
 269          // Re-create the page.
 270          $page = new \moodle_page();
 271          $page->set_context($coursecontext);
 272          $page->set_pagelayout('course');
 273          $course->format = course_get_format($course)->get_format();
 274          $page->set_pagetype('course-view-' . $course->format);
 275          $page->blocks->load_blocks();
 276          $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
 277          $block = end($blocks);
 278          $block = block_instance('html', $block->instance);
 279          $nonscalar = [
 280              'something' => true,
 281          ];
 282          $configdata = (object) [
 283              'title' => $title,
 284              'text' => [
 285                  'itemid' => 0,
 286                  'text' => $body,
 287                  'format' => $bodyformat,
 288              ],
 289              'nonscalar' => $nonscalar
 290          ];
 291          $block->instance_config_save((object) $configdata);
 292  
 293          // Check for the new block.
 294          $result = core_block_external::get_course_blocks($course->id, true);
 295          $result = \external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result);
 296  
 297          // Format the original data.
 298          $sitecontext = \context_system::instance();
 299          $title = external_format_string($title, $coursecontext->id);
 300          list($body, $bodyformat) = external_format_text($body, $bodyformat, $coursecontext->id, 'block_html', 'content');
 301  
 302          // Check that the block data is formatted.
 303          $this->assertCount(1, $result['blocks']);
 304          $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">',
 305                  $result['blocks'][0]['contents']['title']);
 306          $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">',
 307                  $result['blocks'][0]['contents']['content']);
 308          $this->assertEquals($title, $result['blocks'][0]['contents']['title']);
 309          $this->assertEquals($body, $result['blocks'][0]['contents']['content']);
 310      }
 311  
 312      /**
 313       * Test user get default dashboard blocks.
 314       */
 315      public function test_get_dashboard_blocks_default_dashboard() {
 316          global $PAGE, $DB;
 317          $this->resetAfterTest(true);
 318  
 319          $user = $this->getDataGenerator()->create_user();
 320          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 321  
 322          // Force a setting change to check the returned blocks settings.
 323          set_config('displaycategories', 0, 'block_myoverview');
 324  
 325          $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true));
 326          // Get the expected default blocks.
 327          $alldefaultblocksordered = $DB->get_records_menu(
 328              'block_instances',
 329              array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id),
 330              'defaultregion, defaultweight ASC',
 331              'id, blockname'
 332          );
 333  
 334          $this->setUser($user);
 335  
 336          // Check for the default blocks.
 337          $result = core_block_external::get_dashboard_blocks($user->id);
 338          // We need to execute the return values cleaning process to simulate the web service server.
 339          $result = \external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result);
 340          // Expect all default blocks defined in blocks_add_default_system_blocks().
 341          $this->assertCount(count($alldefaultblocksordered), $result['blocks']);
 342          $returnedblocks = array();
 343          foreach ($result['blocks'] as $block) {
 344              // Check all the returned blocks are in the expected blocks array.
 345              $this->assertContains($block['name'], $alldefaultblocksordered);
 346              $returnedblocks[] = $block['name'];
 347              // Check the configuration returned for this default block.
 348              if ($block['name'] == 'myoverview') {
 349                  // Convert config to associative array to avoid DB sorting randomness.
 350                  $config = array_column($block['configs'], null, 'name');
 351                  $this->assertArrayHasKey('displaycategories', $config);
 352                  $this->assertEquals(json_encode('0'), $config['displaycategories']['value']);
 353                  $this->assertEquals('plugin', $config['displaycategories']['type']);
 354              }
 355          }
 356  
 357          // Check that we received the blocks in the expected order.
 358          $this->assertEquals(array_values($alldefaultblocksordered), $returnedblocks);
 359      }
 360  
 361      /**
 362       * Test user get default dashboard blocks including a sticky block.
 363       */
 364      public function test_get_dashboard_blocks_default_dashboard_including_sticky_block() {
 365          global $PAGE, $DB;
 366          $this->resetAfterTest(true);
 367  
 368          $user = $this->getDataGenerator()->create_user();
 369          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 370  
 371          $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true));
 372          // Get the expected default blocks.
 373          $alldefaultblocks = $DB->get_records_menu(
 374              'block_instances', array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id),
 375              '',
 376              'id, blockname'
 377          );
 378  
 379          // Now, add a sticky block.
 380          $page = new \moodle_page();
 381          $page->set_context(\context_system::instance());
 382          $page->set_pagetype('my-index');
 383          $page->set_url(new \moodle_url('/'));
 384          $page->blocks->add_region('side-pre');
 385          $page->blocks->load_blocks();
 386          $page->blocks->add_block('myprofile', 'side-pre', 0, true, '*');
 387  
 388          $this->setUser($user);
 389  
 390          // Check for the default blocks plus the sticky.
 391          $result = core_block_external::get_dashboard_blocks($user->id);
 392          // We need to execute the return values cleaning process to simulate the web service server.
 393          $result = \external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result);
 394          // Expect all default blocks defined in blocks_add_default_system_blocks() plus sticky one.
 395          $this->assertCount(count($alldefaultblocks) + 1, $result['blocks']);
 396          $found = false;
 397          foreach ($result['blocks'] as $block) {
 398              if ($block['name'] == 'myprofile') {
 399                  $this->assertEquals('side-pre', $block['region']);
 400                  $found = true;
 401                  continue;
 402              }
 403              // Check that the block is in the expected blocks array.
 404              $this->assertContains($block['name'], $alldefaultblocks);
 405          }
 406          $this->assertTrue($found);
 407      }
 408  
 409      /**
 410       * Test admin get user's custom dashboard blocks.
 411       */
 412      public function test_get_dashboard_blocks_custom_user_dashboard() {
 413          global $PAGE, $DB;
 414          $this->resetAfterTest(true);
 415  
 416          $user = $this->getDataGenerator()->create_user();
 417          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 418  
 419          $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true));
 420          // Get the expected default blocks.
 421          $alldefaultblocks = $DB->get_records_menu(
 422              'block_instances',
 423              array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id),
 424              '',
 425              'id, blockname'
 426          );
 427  
 428          // Add a custom block.
 429          $page = new \moodle_page();
 430          $page->set_context(\context_user::instance($user->id));
 431          $page->set_pagelayout('mydashboard');
 432          $page->set_pagetype('my-index');
 433          $page->blocks->add_region('content');
 434          $currentpage = my_get_page($user->id, MY_PAGE_PRIVATE);
 435          $page->set_subpage($currentpage->id);
 436          $page->blocks->load_blocks();
 437          $page->blocks->add_block('myprofile', 'content', 0, false);
 438  
 439          $this->setAdminUser();
 440  
 441          // Check for the new block as admin for a user.
 442          $result = core_block_external::get_dashboard_blocks($user->id);
 443          // We need to execute the return values cleaning process to simulate the web service server.
 444          $result = \external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result);
 445          // Expect all default blocks defined in blocks_add_default_system_blocks() plus the one we added.
 446          $this->assertCount(count($alldefaultblocks) + 1, $result['blocks']);
 447          $found = false;
 448          foreach ($result['blocks'] as $block) {
 449              if ($block['name'] == 'myprofile') {
 450                  $this->assertEquals('content', $block['region']);
 451                  $found = true;
 452                  continue;
 453              }
 454              // Check that the block is in the expected blocks array.
 455              $this->assertContains($block['name'], $alldefaultblocks);
 456          }
 457          $this->assertTrue($found);
 458      }
 459  
 460      /**
 461       * Test user tries to get other user blocks not having permission.
 462       */
 463      public function test_get_dashboard_blocks_other_user_missing_permissions() {
 464          $this->resetAfterTest(true);
 465  
 466          $user1 = $this->getDataGenerator()->create_user();
 467          $user2 = $this->getDataGenerator()->create_user();
 468  
 469          $this->setUser($user1);
 470  
 471          $this->expectException('moodle_exception');
 472          core_block_external::get_dashboard_blocks($user2->id);
 473      }
 474  
 475      /**
 476       * Test user get default dashboard blocks for my courses page.
 477       */
 478      public function test_get_dashboard_blocks_my_courses() {
 479          global $PAGE, $DB;
 480          $this->resetAfterTest(true);
 481  
 482          $user = $this->getDataGenerator()->create_user();
 483          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 484  
 485          // Force a setting change to check the returned blocks settings.
 486          set_config('displaycategories', 0, 'block_myoverview');
 487  
 488          $systempage = $DB->get_record('my_pages', ['userid' => null, 'name' => MY_PAGE_COURSES, 'private' => false]);
 489          // Get the expected default blocks.
 490          $alldefaultblocksordered = $DB->get_records_menu(
 491              'block_instances',
 492              ['pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id],
 493              'defaultregion, defaultweight ASC',
 494              'id, blockname'
 495          );
 496  
 497          $this->setUser($user);
 498  
 499          // Check for the default blocks.
 500          $result = core_block_external::get_dashboard_blocks($user->id, false, MY_PAGE_COURSES);
 501          // We need to execute the return values cleaning process to simulate the web service server.
 502          $result = \external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result);
 503          // Expect all default blocks defined in blocks_add_default_system_blocks().
 504          $this->assertCount(count($alldefaultblocksordered), $result['blocks']);
 505          $returnedblocks = [];
 506          foreach ($result['blocks'] as $block) {
 507              // Check all the returned blocks are in the expected blocks array.
 508              $this->assertContains($block['name'], $alldefaultblocksordered);
 509              $returnedblocks[] = $block['name'];
 510              // Check the configuration returned for this default block.
 511              if ($block['name'] == 'myoverview') {
 512                  // Convert config to associative array to avoid DB sorting randomness.
 513                  $config = array_column($block['configs'], null, 'name');
 514                  $this->assertArrayHasKey('displaycategories', $config);
 515                  $this->assertEquals(json_encode('0'), $config['displaycategories']['value']);
 516                  $this->assertEquals('plugin', $config['displaycategories']['type']);
 517              }
 518          }
 519  
 520          // Check that we received the blocks in the expected order.
 521          $this->assertEquals(array_values($alldefaultblocksordered), $returnedblocks);
 522      }
 523  
 524      /**
 525       * Test user passing the wrong page type and getting an exception.
 526       */
 527      public function test_get_dashboard_blocks_incorrect_page() {
 528          global $PAGE;
 529          $this->resetAfterTest(true);
 530  
 531          $user = $this->getDataGenerator()->create_user();
 532          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 533  
 534          $this->setUser($user);
 535  
 536          $this->expectException('moodle_exception');
 537          // Check for the default blocks with a fake page, no need to assign as it'll throw.
 538          core_block_external::get_dashboard_blocks($user->id, false, 'fakepage');
 539  
 540      }
 541  }