Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [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 = \core_external\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 = \core_external\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 = \core_external\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 = \core_external\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          $this->resetAfterTest(true);
 240  
 241          // Enable MathJax filter in content and headings.
 242          $this->configure_filters([
 243              ['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true],
 244          ]);
 245  
 246          // Create a few stuff to test with.
 247          $user = $this->getDataGenerator()->create_user();
 248          $course = $this->getDataGenerator()->create_course();
 249          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 250          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
 251          $coursecontext = \context_course::instance($course->id);
 252  
 253          // Create a HTML block.
 254          $title = 'My block $$(a+b)=2$$';
 255          $body = 'My block contents $$(a+b)=2$$';
 256          $bodyformat = FORMAT_MOODLE;
 257          $page = new \moodle_page();
 258          $page->set_context($coursecontext);
 259          $page->set_pagelayout('course');
 260          $course->format = course_get_format($course)->get_format();
 261          $page->set_pagetype('course-view-' . $course->format);
 262          $page->blocks->load_blocks();
 263          $newblock = 'html';
 264          $page->blocks->add_block_at_end_of_default_region($newblock);
 265  
 266          $this->setUser($user);
 267          // Re-create the page.
 268          $page = new \moodle_page();
 269          $page->set_context($coursecontext);
 270          $page->set_pagelayout('course');
 271          $course->format = course_get_format($course)->get_format();
 272          $page->set_pagetype('course-view-' . $course->format);
 273          $page->blocks->load_blocks();
 274          $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
 275          $block = end($blocks);
 276          $block = block_instance('html', $block->instance);
 277          $nonscalar = [
 278              'something' => true,
 279          ];
 280          $configdata = (object) [
 281              'title' => $title,
 282              'text' => [
 283                  'itemid' => 0,
 284                  'text' => $body,
 285                  'format' => $bodyformat,
 286              ],
 287              'nonscalar' => $nonscalar
 288          ];
 289          $block->instance_config_save((object) $configdata);
 290  
 291          // Check for the new block.
 292          $result = core_block_external::get_course_blocks($course->id, true);
 293          $result = \core_external\external_api::clean_returnvalue(core_block_external::get_course_blocks_returns(), $result);
 294  
 295          // Format the original data.
 296          $sitecontext = \context_system::instance();
 297          $title = \core_external\util::format_string($title, $coursecontext->id);
 298          list($body, $bodyformat) = \core_external\util::format_text($body, $bodyformat, $coursecontext, 'block_html', 'content');
 299  
 300          // Check that the block data is formatted.
 301          $this->assertCount(1, $result['blocks']);
 302          $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">',
 303                  $result['blocks'][0]['contents']['title']);
 304          $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">',
 305                  $result['blocks'][0]['contents']['content']);
 306          $this->assertEquals($title, $result['blocks'][0]['contents']['title']);
 307          $this->assertEquals($body, $result['blocks'][0]['contents']['content']);
 308      }
 309  
 310      /**
 311       * Test user get default dashboard blocks.
 312       */
 313      public function test_get_dashboard_blocks_default_dashboard() {
 314          global $PAGE, $DB;
 315          $this->resetAfterTest(true);
 316  
 317          $user = $this->getDataGenerator()->create_user();
 318          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 319  
 320          // Force a setting change to check the returned blocks settings.
 321          set_config('displaycategories', 0, 'block_myoverview');
 322  
 323          $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true));
 324          // Get the expected default blocks.
 325          $alldefaultblocksordered = $DB->get_records_menu(
 326              'block_instances',
 327              array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id),
 328              'defaultregion, defaultweight ASC',
 329              'id, blockname'
 330          );
 331  
 332          $this->setUser($user);
 333  
 334          // Check for the default blocks.
 335          $result = core_block_external::get_dashboard_blocks($user->id);
 336          // We need to execute the return values cleaning process to simulate the web service server.
 337          $result = \core_external\external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result);
 338          // Expect all default blocks defined in blocks_add_default_system_blocks().
 339          $this->assertCount(count($alldefaultblocksordered), $result['blocks']);
 340          $returnedblocks = array();
 341          foreach ($result['blocks'] as $block) {
 342              // Check all the returned blocks are in the expected blocks array.
 343              $this->assertContains($block['name'], $alldefaultblocksordered);
 344              $returnedblocks[] = $block['name'];
 345              // Check the configuration returned for this default block.
 346              if ($block['name'] == 'myoverview') {
 347                  // Convert config to associative array to avoid DB sorting randomness.
 348                  $config = array_column($block['configs'], null, 'name');
 349                  $this->assertArrayHasKey('displaycategories', $config);
 350                  $this->assertEquals(json_encode('0'), $config['displaycategories']['value']);
 351                  $this->assertEquals('plugin', $config['displaycategories']['type']);
 352              }
 353          }
 354  
 355          // Check that we received the blocks in the expected order.
 356          $this->assertEquals(array_values($alldefaultblocksordered), $returnedblocks);
 357      }
 358  
 359      /**
 360       * Test user get default dashboard blocks including a sticky block.
 361       */
 362      public function test_get_dashboard_blocks_default_dashboard_including_sticky_block() {
 363          global $PAGE, $DB;
 364          $this->resetAfterTest(true);
 365  
 366          $user = $this->getDataGenerator()->create_user();
 367          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 368  
 369          $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true));
 370          // Get the expected default blocks.
 371          $alldefaultblocks = $DB->get_records_menu(
 372              'block_instances', array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id),
 373              '',
 374              'id, blockname'
 375          );
 376  
 377          // Now, add a sticky block.
 378          $page = new \moodle_page();
 379          $page->set_context(\context_system::instance());
 380          $page->set_pagetype('my-index');
 381          $page->set_url(new \moodle_url('/'));
 382          $page->blocks->add_region('side-pre');
 383          $page->blocks->load_blocks();
 384          $page->blocks->add_block('myprofile', 'side-pre', 0, true, '*');
 385  
 386          $this->setUser($user);
 387  
 388          // Check for the default blocks plus the sticky.
 389          $result = core_block_external::get_dashboard_blocks($user->id);
 390          // We need to execute the return values cleaning process to simulate the web service server.
 391          $result = \core_external\external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result);
 392          // Expect all default blocks defined in blocks_add_default_system_blocks() plus sticky one.
 393          $this->assertCount(count($alldefaultblocks) + 1, $result['blocks']);
 394          $found = false;
 395          foreach ($result['blocks'] as $block) {
 396              if ($block['name'] == 'myprofile') {
 397                  $this->assertEquals('side-pre', $block['region']);
 398                  $found = true;
 399                  continue;
 400              }
 401              // Check that the block is in the expected blocks array.
 402              $this->assertContains($block['name'], $alldefaultblocks);
 403          }
 404          $this->assertTrue($found);
 405      }
 406  
 407      /**
 408       * Test admin get user's custom dashboard blocks.
 409       */
 410      public function test_get_dashboard_blocks_custom_user_dashboard() {
 411          global $PAGE, $DB;
 412          $this->resetAfterTest(true);
 413  
 414          $user = $this->getDataGenerator()->create_user();
 415          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 416  
 417          $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true));
 418          // Get the expected default blocks.
 419          $alldefaultblocks = $DB->get_records_menu(
 420              'block_instances',
 421              array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id),
 422              '',
 423              'id, blockname'
 424          );
 425  
 426          // Add a custom block.
 427          $page = new \moodle_page();
 428          $page->set_context(\context_user::instance($user->id));
 429          $page->set_pagelayout('mydashboard');
 430          $page->set_pagetype('my-index');
 431          $page->blocks->add_region('content');
 432          $currentpage = my_get_page($user->id, MY_PAGE_PRIVATE);
 433          $page->set_subpage($currentpage->id);
 434          $page->blocks->load_blocks();
 435          $page->blocks->add_block('myprofile', 'content', 0, false);
 436  
 437          $this->setAdminUser();
 438  
 439          // Check for the new block as admin for a user.
 440          $result = core_block_external::get_dashboard_blocks($user->id);
 441          // We need to execute the return values cleaning process to simulate the web service server.
 442          $result = \core_external\external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result);
 443          // Expect all default blocks defined in blocks_add_default_system_blocks() plus the one we added.
 444          $this->assertCount(count($alldefaultblocks) + 1, $result['blocks']);
 445          $found = false;
 446          foreach ($result['blocks'] as $block) {
 447              if ($block['name'] == 'myprofile') {
 448                  $this->assertEquals('content', $block['region']);
 449                  $found = true;
 450                  continue;
 451              }
 452              // Check that the block is in the expected blocks array.
 453              $this->assertContains($block['name'], $alldefaultblocks);
 454          }
 455          $this->assertTrue($found);
 456      }
 457  
 458      /**
 459       * Test user tries to get other user blocks not having permission.
 460       */
 461      public function test_get_dashboard_blocks_other_user_missing_permissions() {
 462          $this->resetAfterTest(true);
 463  
 464          $user1 = $this->getDataGenerator()->create_user();
 465          $user2 = $this->getDataGenerator()->create_user();
 466  
 467          $this->setUser($user1);
 468  
 469          $this->expectException('moodle_exception');
 470          core_block_external::get_dashboard_blocks($user2->id);
 471      }
 472  
 473      /**
 474       * Test user get default dashboard blocks for my courses page.
 475       */
 476      public function test_get_dashboard_blocks_my_courses() {
 477          global $PAGE, $DB;
 478          $this->resetAfterTest(true);
 479  
 480          $user = $this->getDataGenerator()->create_user();
 481          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 482  
 483          // Force a setting change to check the returned blocks settings.
 484          set_config('displaycategories', 0, 'block_myoverview');
 485  
 486          $systempage = $DB->get_record('my_pages', ['userid' => null, 'name' => MY_PAGE_COURSES, 'private' => false]);
 487          // Get the expected default blocks.
 488          $alldefaultblocksordered = $DB->get_records_menu(
 489              'block_instances',
 490              ['pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id],
 491              'defaultregion, defaultweight ASC',
 492              'id, blockname'
 493          );
 494  
 495          $this->setUser($user);
 496  
 497          // Check for the default blocks.
 498          $result = core_block_external::get_dashboard_blocks($user->id, false, MY_PAGE_COURSES);
 499          // We need to execute the return values cleaning process to simulate the web service server.
 500          $result = \core_external\external_api::clean_returnvalue(core_block_external::get_dashboard_blocks_returns(), $result);
 501          // Expect all default blocks defined in blocks_add_default_system_blocks().
 502          $this->assertCount(count($alldefaultblocksordered), $result['blocks']);
 503          $returnedblocks = [];
 504          foreach ($result['blocks'] as $block) {
 505              // Check all the returned blocks are in the expected blocks array.
 506              $this->assertContains($block['name'], $alldefaultblocksordered);
 507              $returnedblocks[] = $block['name'];
 508              // Check the configuration returned for this default block.
 509              if ($block['name'] == 'myoverview') {
 510                  // Convert config to associative array to avoid DB sorting randomness.
 511                  $config = array_column($block['configs'], null, 'name');
 512                  $this->assertArrayHasKey('displaycategories', $config);
 513                  $this->assertEquals(json_encode('0'), $config['displaycategories']['value']);
 514                  $this->assertEquals('plugin', $config['displaycategories']['type']);
 515              }
 516          }
 517  
 518          // Check that we received the blocks in the expected order.
 519          $this->assertEquals(array_values($alldefaultblocksordered), $returnedblocks);
 520      }
 521  
 522      /**
 523       * Test user passing the wrong page type and getting an exception.
 524       */
 525      public function test_get_dashboard_blocks_incorrect_page() {
 526          global $PAGE;
 527          $this->resetAfterTest(true);
 528  
 529          $user = $this->getDataGenerator()->create_user();
 530          $PAGE->set_url('/my/index.php');    // Need this because some internal API calls require the $PAGE url to be set.
 531  
 532          $this->setUser($user);
 533  
 534          $this->expectException('moodle_exception');
 535          // Check for the default blocks with a fake page, no need to assign as it'll throw.
 536          core_block_external::get_dashboard_blocks($user->id, false, 'fakepage');
 537  
 538      }
 539  }