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 400 and 401]

   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   * Tests for the moodle_page class.
  19   *
  20   * @package   core
  21   * @category  test
  22   * @copyright 2009 Tim Hunt
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace core;
  27  
  28  use moodle_page;
  29  
  30  defined('MOODLE_INTERNAL') || die();
  31  
  32  global $CFG;
  33  require_once($CFG->libdir . '/pagelib.php');
  34  require_once($CFG->libdir . '/blocklib.php');
  35  
  36  /**
  37   * Tests for the moodle_page class.
  38   *
  39   * @package   core
  40   * @category  test
  41   * @copyright 2009 Tim Hunt
  42   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  43   * @coversDefaultClass \moodle_page
  44   */
  45  class moodle_page_test extends \advanced_testcase {
  46  
  47      /**
  48       * @var testable_moodle_page
  49       */
  50      protected $testpage;
  51  
  52      public function setUp(): void {
  53          parent::setUp();
  54          $this->resetAfterTest();
  55          $this->testpage = new testable_moodle_page();
  56      }
  57  
  58      public function test_course_returns_site_before_set() {
  59          global $SITE;
  60          // Validated.
  61          $this->assertSame($SITE, $this->testpage->course);
  62      }
  63  
  64      public function test_setting_course_works() {
  65          // Setup fixture.
  66          $course = $this->getDataGenerator()->create_course();
  67          $this->testpage->set_context(\context_system::instance()); // Avoid trying to set the context.
  68          // Exercise SUT.
  69          $this->testpage->set_course($course);
  70          // Validated.
  71          $this->assertEquals($course, $this->testpage->course);
  72      }
  73  
  74      public function test_global_course_and_page_course_are_same_with_global_page() {
  75          global $COURSE, $PAGE;
  76          // Setup fixture.
  77          $course = $this->getDataGenerator()->create_course();
  78          $this->testpage->set_context(\context_system::instance()); // Avoid trying to set the context.
  79          $PAGE = $this->testpage;
  80          // Exercise SUT.
  81          $this->testpage->set_course($course);
  82          // Validated.
  83          $this->assertSame($COURSE, $this->testpage->course);
  84      }
  85  
  86      public function test_global_course_not_changed_with_non_global_page() {
  87          global $COURSE;
  88          $originalcourse = $COURSE;
  89          // Setup fixture.
  90          $course = $this->getDataGenerator()->create_course();
  91          $this->testpage->set_context(\context_system::instance()); // Avoid trying to set the context.
  92          // Exercise SUT.
  93          $this->testpage->set_course($course);
  94          // Validated.
  95          $this->assertSame($originalcourse, $COURSE);
  96      }
  97  
  98      public function test_cannot_set_course_once_theme_set() {
  99          // Setup fixture.
 100          $this->testpage->force_theme(\theme_config::DEFAULT_THEME);
 101          $course = $this->getDataGenerator()->create_course();
 102  
 103          // Exercise SUT.
 104          $this->expectException(\coding_exception::class);
 105          $this->testpage->set_course($course);
 106      }
 107  
 108      public function test_cannot_set_category_once_theme_set() {
 109          // Setup fixture.
 110          $this->testpage->force_theme(\theme_config::DEFAULT_THEME);
 111  
 112          // Exercise SUT.
 113          $this->expectException(\coding_exception::class);
 114          $this->testpage->set_category_by_id(123);
 115      }
 116  
 117      public function test_cannot_set_category_once_course_set() {
 118          // Setup fixture.
 119          $course = $this->getDataGenerator()->create_course();
 120          $this->testpage->set_context(\context_system::instance()); // Avoid trying to set the context.
 121          $this->testpage->set_course($course);
 122  
 123          // Exercise SUT.
 124          $this->expectException(\coding_exception::class);
 125          $this->testpage->set_category_by_id(123);
 126      }
 127  
 128      public function test_categories_array_empty_for_front_page() {
 129          global $SITE;
 130          // Setup fixture.
 131          $this->testpage->set_context(\context_system::instance()); // Avoid trying to set the context.
 132          $this->testpage->set_course($SITE);
 133          // Exercise SUT and validate.
 134          $this->assertEquals(array(), $this->testpage->categories);
 135      }
 136  
 137      public function test_set_state_normal_path() {
 138          $course = $this->getDataGenerator()->create_course();
 139          $this->testpage->set_context(\context_system::instance());
 140          $this->testpage->set_course($course);
 141  
 142          $this->assertEquals(\moodle_page::STATE_BEFORE_HEADER, $this->testpage->state);
 143  
 144          $this->testpage->set_state(\moodle_page::STATE_PRINTING_HEADER);
 145          $this->assertEquals(\moodle_page::STATE_PRINTING_HEADER, $this->testpage->state);
 146  
 147          $this->testpage->set_state(\moodle_page::STATE_IN_BODY);
 148          $this->assertEquals(\moodle_page::STATE_IN_BODY, $this->testpage->state);
 149  
 150          $this->testpage->set_state(\moodle_page::STATE_DONE);
 151          $this->assertEquals(\moodle_page::STATE_DONE, $this->testpage->state);
 152      }
 153  
 154      public function test_set_state_cannot_skip_one() {
 155          // Exercise SUT.
 156          $this->expectException(\coding_exception::class);
 157          $this->testpage->set_state(\moodle_page::STATE_IN_BODY);
 158      }
 159  
 160      public function test_header_printed_false_initially() {
 161          // Validated.
 162          $this->assertFalse($this->testpage->headerprinted);
 163      }
 164  
 165      public function test_header_printed_becomes_true() {
 166          $course = $this->getDataGenerator()->create_course();
 167          $this->testpage->set_context(\context_system::instance());
 168          $this->testpage->set_course($course);
 169  
 170          // Exercise SUT.
 171          $this->testpage->set_state(\moodle_page::STATE_PRINTING_HEADER);
 172          $this->testpage->set_state(\moodle_page::STATE_IN_BODY);
 173          // Validated.
 174          $this->assertTrue($this->testpage->headerprinted);
 175      }
 176  
 177      public function test_set_context() {
 178          // Setup fixture.
 179          $course = $this->getDataGenerator()->create_course();
 180          $context = \context_course::instance($course->id);
 181          // Exercise SUT.
 182          $this->testpage->set_context($context);
 183          // Validated.
 184          $this->assertSame($context, $this->testpage->context);
 185      }
 186  
 187      public function test_pagetype_defaults_to_script() {
 188          global $SCRIPT;
 189          // Exercise SUT and validate.
 190          $SCRIPT = '/index.php';
 191          $this->testpage->initialise_default_pagetype();
 192          $this->assertSame('site-index', $this->testpage->pagetype);
 193      }
 194  
 195      public function test_set_pagetype() {
 196          // Exercise SUT.
 197          $this->testpage->set_pagetype('a-page-type');
 198          // Validated.
 199          $this->assertSame('a-page-type', $this->testpage->pagetype);
 200      }
 201  
 202      public function test_initialise_default_pagetype() {
 203          // Exercise SUT.
 204          $this->testpage->initialise_default_pagetype('admin/tool/unittest/index.php');
 205          // Validated.
 206          $this->assertSame('admin-tool-unittest-index', $this->testpage->pagetype);
 207      }
 208  
 209      public function test_initialise_default_pagetype_fp() {
 210          // Exercise SUT.
 211          $this->testpage->initialise_default_pagetype('index.php');
 212          // Validated.
 213          $this->assertSame('site-index', $this->testpage->pagetype);
 214      }
 215  
 216      public function test_get_body_classes_empty() {
 217          // Validated.
 218          $this->assertSame('', $this->testpage->bodyclasses);
 219      }
 220  
 221      public function test_get_body_classes_single() {
 222          // Exercise SUT.
 223          $this->testpage->add_body_class('aclassname');
 224          // Validated.
 225          $this->assertSame('aclassname', $this->testpage->bodyclasses);
 226      }
 227  
 228      public function test_get_body_classes() {
 229          // Exercise SUT.
 230          $this->testpage->add_body_classes(array('aclassname', 'anotherclassname'));
 231          // Validated.
 232          $this->assertSame('aclassname anotherclassname', $this->testpage->bodyclasses);
 233      }
 234  
 235      public function test_url_to_class_name() {
 236          $this->assertSame('example-com', $this->testpage->url_to_class_name('http://example.com'));
 237          $this->assertSame('example-com--80', $this->testpage->url_to_class_name('http://example.com:80'));
 238          $this->assertSame('example-com--moodle', $this->testpage->url_to_class_name('https://example.com/moodle'));
 239          $this->assertSame('example-com--8080--nested-moodle', $this->testpage->url_to_class_name('https://example.com:8080/nested/moodle'));
 240      }
 241  
 242      public function test_set_docs_path() {
 243          // Exercise SUT.
 244          $this->testpage->set_docs_path('a/file/path');
 245          // Validated.
 246          $this->assertSame('a/file/path', $this->testpage->docspath);
 247      }
 248  
 249      public function test_docs_path_defaults_from_pagetype() {
 250          // Exercise SUT.
 251          $this->testpage->set_pagetype('a-page-type');
 252          // Validated.
 253          $this->assertSame('a/page/type', $this->testpage->docspath);
 254      }
 255  
 256      public function test_set_url_root() {
 257          global $CFG;
 258          // Exercise SUT.
 259          $this->testpage->set_url('/');
 260          // Validated.
 261          $this->assertSame($CFG->wwwroot . '/', $this->testpage->url->out());
 262      }
 263  
 264      public function test_set_url_one_param() {
 265          global $CFG;
 266          // Exercise SUT.
 267          $this->testpage->set_url('/mod/quiz/attempt.php', array('attempt' => 123));
 268          // Validated.
 269          $this->assertSame($CFG->wwwroot . '/mod/quiz/attempt.php?attempt=123', $this->testpage->url->out());
 270      }
 271  
 272      public function test_set_url_two_params() {
 273          global $CFG;
 274          // Exercise SUT.
 275          $this->testpage->set_url('/mod/quiz/attempt.php', array('attempt' => 123, 'page' => 7));
 276          // Validated.
 277          $this->assertSame($CFG->wwwroot . '/mod/quiz/attempt.php?attempt=123&amp;page=7', $this->testpage->url->out());
 278      }
 279  
 280      public function test_set_url_using_moodle_url() {
 281          global $CFG;
 282          // Fixture setup.
 283          $url = new \moodle_url('/mod/workshop/allocation.php', array('cmid' => 29, 'method' => 'manual'));
 284          // Exercise SUT.
 285          $this->testpage->set_url($url);
 286          // Validated.
 287          $this->assertSame($CFG->wwwroot . '/mod/workshop/allocation.php?cmid=29&amp;method=manual', $this->testpage->url->out());
 288      }
 289  
 290      public function test_set_url_sets_page_type() {
 291          // Exercise SUT.
 292          $this->testpage->set_url('/mod/quiz/attempt.php', array('attempt' => 123, 'page' => 7));
 293          // Validated.
 294          $this->assertSame('mod-quiz-attempt', $this->testpage->pagetype);
 295      }
 296  
 297      public function test_set_url_does_not_change_explicit_page_type() {
 298          // Setup fixture.
 299          $this->testpage->set_pagetype('a-page-type');
 300          // Exercise SUT.
 301          $this->testpage->set_url('/mod/quiz/attempt.php', array('attempt' => 123, 'page' => 7));
 302          // Validated.
 303          $this->assertSame('a-page-type', $this->testpage->pagetype);
 304      }
 305  
 306      public function test_set_subpage() {
 307          // Exercise SUT.
 308          $this->testpage->set_subpage('somestring');
 309          // Validated.
 310          $this->assertSame('somestring', $this->testpage->subpage);
 311      }
 312  
 313      public function test_set_heading() {
 314          // Exercise SUT.
 315          $this->testpage->set_heading('a heading');
 316          // Validated.
 317          $this->assertSame('a heading', $this->testpage->heading);
 318  
 319          // By default formatting is applied and tags are removed.
 320          $this->testpage->set_heading('a heading <a href="#">edit</a><p>');
 321          $this->assertSame('a heading edit', $this->testpage->heading);
 322  
 323          // Without formatting the tags are preserved but cleaned.
 324          $this->testpage->set_heading('a heading <a href="#">edit</a><p>', false);
 325          $this->assertSame('a heading <a href="#">edit</a><p></p>', $this->testpage->heading);
 326      }
 327  
 328      /**
 329       * Data provider for {@see test_set_title}.
 330       *
 331       * @return array
 332       */
 333      public function set_title_provider(): array {
 334          return [
 335              'Do not append the site name' => [
 336                  'shortname', false, '', false
 337              ],
 338              'Site not yet installed not configured defaults to site shortname' => [
 339                  null, true, 'shortname'
 340              ],
 341              '$CFG->sitenameintitle not configured defaults to site shortname' => [
 342                  null, true, 'shortname'
 343              ],
 344              '$CFG->sitenameintitle set to shortname' => [
 345                  'shortname', true, 'shortname'
 346              ],
 347              '$CFG->sitenameintitle set to fullname' => [
 348                  'fullname', true, 'fullname'
 349              ],
 350          ];
 351      }
 352  
 353      /**
 354       * Test for set_title
 355       *
 356       * @dataProvider set_title_provider
 357       * @param string|null $config The config value for $CFG->sitenameintitle.
 358       * @param bool $appendsitename The $appendsitename parameter
 359       * @param string $expected The expected site name to be appended to the title.
 360       * @param bool $sitenameset To simulate the absence of the site name being set in the site.
 361       * @return void
 362       * @covers ::set_title
 363       */
 364      public function test_set_title(?string $config, bool $appendsitename, string $expected, bool $sitenameset = true): void {
 365          global $CFG, $SITE;
 366  
 367          if ($config !== null) {
 368              $CFG->sitenameintitle = $config;
 369          }
 370  
 371          $title = "A title";
 372          if ($appendsitename) {
 373              if ($sitenameset) {
 374                  $expectedtitle = $title . moodle_page::TITLE_SEPARATOR . $SITE->{$expected};
 375              } else {
 376                  // Simulate site fullname and shortname being empty for any reason.
 377                  $SITE->fullname = null;
 378                  $SITE->shortname = null;
 379                  $expectedtitle = $title . moodle_page::TITLE_SEPARATOR . 'Moodle';
 380              }
 381          } else {
 382              $expectedtitle = $title;
 383          }
 384  
 385          $this->testpage->set_title($title, $appendsitename);
 386          // Validated.
 387          $this->assertSame($expectedtitle, $this->testpage->title);
 388      }
 389  
 390      public function test_default_pagelayout() {
 391          // Exercise SUT and Validate.
 392          $this->assertSame('base', $this->testpage->pagelayout);
 393      }
 394  
 395      public function test_set_pagelayout() {
 396          // Exercise SUT.
 397          $this->testpage->set_pagelayout('type');
 398          // Validated.
 399          $this->assertSame('type', $this->testpage->pagelayout);
 400      }
 401  
 402      public function test_setting_course_sets_context() {
 403          // Setup fixture.
 404          $course = $this->getDataGenerator()->create_course();
 405          $context = \context_course::instance($course->id);
 406  
 407          // Exercise SUT.
 408          $this->testpage->set_course($course);
 409  
 410          // Validated.
 411          $this->assertSame($context, $this->testpage->context);
 412      }
 413  
 414      public function test_set_category_top_level() {
 415          global $DB;
 416          // Setup fixture.
 417          $cat = $this->getDataGenerator()->create_category();
 418          $catdbrecord = $DB->get_record('course_categories', array('id' => $cat->id));
 419          // Exercise SUT.
 420          $this->testpage->set_category_by_id($cat->id);
 421          // Validated.
 422          $this->assertEquals($catdbrecord, $this->testpage->category);
 423          $this->assertSame(\context_coursecat::instance($cat->id), $this->testpage->context);
 424      }
 425  
 426      public function test_set_nested_categories() {
 427          global $DB;
 428          // Setup fixture.
 429          $topcat = $this->getDataGenerator()->create_category();
 430          $topcatdbrecord = $DB->get_record('course_categories', array('id' => $topcat->id));
 431          $subcat = $this->getDataGenerator()->create_category(array('parent'=>$topcat->id));
 432          $subcatdbrecord = $DB->get_record('course_categories', array('id' => $subcat->id));
 433          // Exercise SUT.
 434          $this->testpage->set_category_by_id($subcat->id);
 435          // Validated.
 436          $categories = $this->testpage->categories;
 437          $this->assertCount(2, $categories);
 438          $this->assertEquals($topcatdbrecord, array_pop($categories));
 439          $this->assertEquals($subcatdbrecord, array_pop($categories));
 440      }
 441  
 442      public function test_cm_null_initially() {
 443          // Validated.
 444          $this->assertNull($this->testpage->cm);
 445      }
 446  
 447      public function test_set_cm() {
 448          // Setup fixture.
 449          $course = $this->getDataGenerator()->create_course();
 450          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 451          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 452          // Exercise SUT.
 453          $this->testpage->set_cm($cm);
 454          // Validated.
 455          $this->assertEquals($cm->id, $this->testpage->cm->id);
 456      }
 457  
 458      public function test_cannot_set_activity_record_before_cm() {
 459          // Setup fixture.
 460          $course = $this->getDataGenerator()->create_course();
 461          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 462          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 463          // Exercise SUT.
 464          $this->expectException(\coding_exception::class);
 465          $this->testpage->set_activity_record($forum);
 466      }
 467  
 468      public function test_setting_cm_sets_context() {
 469          // Setup fixture.
 470          $course = $this->getDataGenerator()->create_course();
 471          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 472          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 473          // Exercise SUT.
 474          $this->testpage->set_cm($cm);
 475          // Validated.
 476          $this->assertSame(\context_module::instance($cm->id), $this->testpage->context);
 477      }
 478  
 479      public function test_activity_record_loaded_if_not_set() {
 480          // Setup fixture.
 481          $course = $this->getDataGenerator()->create_course();
 482          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 483          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 484          // Exercise SUT.
 485          $this->testpage->set_cm($cm);
 486          // Validated.
 487          unset($forum->cmid);
 488          $this->assertEquals($forum, $this->testpage->activityrecord);
 489      }
 490  
 491      public function test_set_activity_record() {
 492          // Setup fixture.
 493          $course = $this->getDataGenerator()->create_course();
 494          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 495          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 496          $this->testpage->set_cm($cm);
 497          // Exercise SUT.
 498          $this->testpage->set_activity_record($forum);
 499          // Validated.
 500          unset($forum->cmid);
 501          $this->assertEquals($forum, $this->testpage->activityrecord);
 502      }
 503  
 504      public function test_cannot_set_inconsistent_activity_record_course() {
 505          // Setup fixture.
 506          $course = $this->getDataGenerator()->create_course();
 507          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 508          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 509          $this->testpage->set_cm($cm);
 510          // Exercise SUT.
 511          $forum->course = 13;
 512          $this->expectException(\coding_exception::class);
 513          $this->testpage->set_activity_record($forum);
 514      }
 515  
 516      public function test_cannot_set_inconsistent_activity_record_instance() {
 517          // Setup fixture.
 518          $course = $this->getDataGenerator()->create_course();
 519          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 520          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 521          $this->testpage->set_cm($cm);
 522          // Exercise SUT.
 523          $forum->id = 13;
 524          $this->expectException(\coding_exception::class);
 525          $this->testpage->set_activity_record($forum);
 526      }
 527  
 528      public function test_setting_cm_sets_course() {
 529          // Setup fixture.
 530          $course = $this->getDataGenerator()->create_course();
 531          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 532          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 533          // Exercise SUT.
 534          $this->testpage->set_cm($cm);
 535          // Validated.
 536          $this->assertEquals($course->id, $this->testpage->course->id);
 537      }
 538  
 539      public function test_set_cm_with_course_and_activity_no_db() {
 540          // Setup fixture.
 541          $course = $this->getDataGenerator()->create_course();
 542          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 543          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 544          // This only works without db if we already have modinfo cache
 545          // Exercise SUT.
 546          $this->testpage->set_cm($cm, $course, $forum);
 547          // Validated.
 548          $this->assertEquals($cm->id, $this->testpage->cm->id);
 549          $this->assertEquals($course->id, $this->testpage->course->id);
 550          unset($forum->cmid);
 551          $this->assertEquals($forum, $this->testpage->activityrecord);
 552      }
 553  
 554      public function test_cannot_set_cm_with_inconsistent_course() {
 555          // Setup fixture.
 556          $course = $this->getDataGenerator()->create_course();
 557          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 558          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 559          // Exercise SUT.
 560          $cm->course = 13;
 561          $this->expectException(\coding_exception::class);
 562          $this->testpage->set_cm($cm, $course);
 563      }
 564  
 565      public function test_get_activity_name() {
 566          // Setup fixture.
 567          $course = $this->getDataGenerator()->create_course();
 568          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 569          $cm = get_coursemodule_from_id('forum', $forum->cmid);
 570          // Exercise SUT.
 571          $this->testpage->set_cm($cm, $course, $forum);
 572          // Validated.
 573          $this->assertSame('forum', $this->testpage->activityname);
 574      }
 575  
 576      public function test_user_is_editing_on() {
 577          // We are relying on the fact that unit tests are always run by admin, to
 578          // ensure the user_allows_editing call returns true.
 579  
 580          // Setup fixture.
 581          global $USER;
 582  
 583          $this->testpage->set_context(\context_system::instance());
 584          $this->setAdminUser();
 585  
 586          $USER->editing = true;
 587          // Validated.
 588          $this->assertTrue($this->testpage->user_is_editing());
 589      }
 590  
 591      public function test_user_is_editing_off() {
 592          // We are relying on the fact that unit tests are always run by admin, to
 593          // ensure the user_allows_editing call returns true.
 594  
 595          // Setup fixture.
 596          global $USER;
 597  
 598          $this->testpage->set_context(\context_system::instance());
 599          $this->setAdminUser();
 600  
 601          $USER->editing = false;
 602          // Validated.
 603          $this->assertFalse($this->testpage->user_is_editing());
 604      }
 605  
 606      public function test_default_editing_capabilities() {
 607          $this->testpage->set_context(\context_system::instance());
 608          $this->setAdminUser();
 609  
 610          // Validated.
 611          $this->assertEquals(array('moodle/site:manageblocks'), $this->testpage->all_editing_caps());
 612      }
 613  
 614      public function test_other_block_editing_cap() {
 615          $this->testpage->set_context(\context_system::instance());
 616          $this->setAdminUser();
 617  
 618          // Exercise SUT.
 619          $this->testpage->set_blocks_editing_capability('moodle/my:manageblocks');
 620          // Validated.
 621          $this->assertEquals(array('moodle/my:manageblocks'), $this->testpage->all_editing_caps());
 622      }
 623  
 624      public function test_other_editing_cap() {
 625          $this->testpage->set_context(\context_system::instance());
 626          $this->setAdminUser();
 627  
 628          // Exercise SUT.
 629          $this->testpage->set_other_editing_capability('moodle/course:manageactivities');
 630          // Validated.
 631          $actualcaps = $this->testpage->all_editing_caps();
 632          $expectedcaps = array('moodle/course:manageactivities', 'moodle/site:manageblocks');
 633          $this->assertEquals(array_values($expectedcaps), array_values($actualcaps));
 634      }
 635  
 636      public function test_other_editing_caps() {
 637          $this->testpage->set_context(\context_system::instance());
 638          $this->setAdminUser();
 639  
 640          // Exercise SUT.
 641          $this->testpage->set_other_editing_capability(array('moodle/course:manageactivities', 'moodle/site:other'));
 642          // Validated.
 643          $actualcaps = $this->testpage->all_editing_caps();
 644          $expectedcaps = array('moodle/course:manageactivities', 'moodle/site:other', 'moodle/site:manageblocks');
 645          $this->assertEquals(array_values($expectedcaps), array_values($actualcaps));
 646      }
 647  
 648      /**
 649       * Test getting a renderer.
 650       */
 651      public function test_get_renderer() {
 652          global $OUTPUT, $PAGE;
 653          $oldoutput = $OUTPUT;
 654          $oldpage = $PAGE;
 655          $PAGE = $this->testpage;
 656  
 657          $this->testpage->set_pagelayout('standard');
 658          $this->assertEquals('standard', $this->testpage->pagelayout);
 659          // Initialise theme and output for the next tests.
 660          $this->testpage->initialise_theme_and_output();
 661          // Check the generated $OUTPUT object is a core renderer.
 662          $this->assertInstanceOf('core_renderer', $OUTPUT);
 663          // Check we can get a core renderer if we explicitly request one (no component).
 664          $this->assertInstanceOf('core_renderer', $this->testpage->get_renderer('core'));
 665          // Check we get a CLI renderer if we request a maintenance renderer. The CLI target should take precedence.
 666          $this->assertInstanceOf('core_renderer_cli',
 667              $this->testpage->get_renderer('core', null, RENDERER_TARGET_MAINTENANCE));
 668  
 669          // Check we can get a coures renderer if we explicitly request one (valid component).
 670          $this->assertInstanceOf('core_course_renderer', $this->testpage->get_renderer('core', 'course'));
 671  
 672          // Check a properly invalid component.
 673          try {
 674              $this->testpage->get_renderer('core', 'monkeys');
 675              $this->fail('Request for renderer with invalid component didn\'t throw expected exception.');
 676          } catch (\coding_exception $exception) {
 677              $this->assertEquals('monkeys', $exception->debuginfo);
 678          }
 679  
 680          $PAGE = $oldpage;
 681          $OUTPUT = $oldoutput;
 682      }
 683  
 684      /**
 685       * Tests getting a renderer with a maintenance layout.
 686       *
 687       * This layout has special hacks in place in order to deliver a "maintenance" renderer.
 688       */
 689      public function test_get_renderer_maintenance() {
 690          global $OUTPUT, $PAGE;
 691          $oldoutput = $OUTPUT;
 692          $oldpage = $PAGE;
 693          $PAGE = $this->testpage;
 694  
 695          $this->testpage->set_pagelayout('maintenance');
 696          $this->assertEquals('maintenance', $this->testpage->pagelayout);
 697          // Initialise theme and output for the next tests.
 698          $this->testpage->initialise_theme_and_output();
 699          // Check the generated $OUTPUT object is a core cli renderer.
 700          // It shouldn't be maintenance because there the cli target should take greater precedence.
 701          $this->assertInstanceOf('core_renderer_cli', $OUTPUT);
 702          // Check we can get a core renderer if we explicitly request one (no component).
 703          $this->assertInstanceOf('core_renderer', $this->testpage->get_renderer('core'));
 704          // Check we get a CLI renderer if we request a maintenance renderer. The CLI target should take precedence.
 705          $this->assertInstanceOf('core_renderer_cli',
 706              $this->testpage->get_renderer('core', null, RENDERER_TARGET_MAINTENANCE));
 707          // Check we can get a coures renderer if we explicitly request one (valid component).
 708          $this->assertInstanceOf('core_course_renderer', $this->testpage->get_renderer('core', 'course'));
 709  
 710          try {
 711              $this->testpage->get_renderer('core', 'monkeys');
 712              $this->fail('Request for renderer with invalid component didn\'t throw expected exception.');
 713          } catch (\coding_exception $exception) {
 714              $this->assertEquals('monkeys', $exception->debuginfo);
 715          }
 716  
 717          $PAGE = $oldpage;
 718          $OUTPUT = $oldoutput;
 719      }
 720  
 721      public function test_render_to_cli() {
 722          global $OUTPUT;
 723  
 724          $footer = $OUTPUT->footer();
 725          $this->assertEmpty($footer, 'cli output does not have a footer.');
 726      }
 727  
 728      /**
 729       * Validate the theme value depending on the user theme and cohorts.
 730       *
 731       * @dataProvider get_user_theme_provider
 732       */
 733      public function test_cohort_get_user_theme($usertheme, $sitetheme, $cohortthemes, $expected) {
 734          global $DB, $PAGE, $USER;
 735  
 736          $this->resetAfterTest();
 737  
 738          // Enable cohort themes.
 739          set_config('allowuserthemes', 1);
 740          set_config('allowcohortthemes', 1);
 741  
 742          $systemctx = \context_system::instance();
 743  
 744          set_config('theme', $sitetheme);
 745          // Create user.
 746          $user = $this->getDataGenerator()->create_user(array('theme' => $usertheme));
 747  
 748          // Create cohorts and add user as member.
 749          $cohorts = array();
 750          foreach ($cohortthemes as $cohorttheme) {
 751              $cohort = $this->getDataGenerator()->create_cohort(array('contextid' => $systemctx->id, 'name' => 'Cohort',
 752                  'idnumber' => '', 'description' => '', 'theme' => $cohorttheme));
 753              $cohorts[] = $cohort;
 754              cohort_add_member($cohort->id, $user->id);
 755          }
 756  
 757          // Get the theme and compare to the expected.
 758          $this->setUser($user);
 759  
 760          // Initialise user theme.
 761          $USER = get_complete_user_data('id', $user->id);
 762  
 763          // Initialise site theme.
 764          $PAGE->reset_theme_and_output();
 765          $PAGE->initialise_theme_and_output();
 766          $result = $PAGE->theme->name;
 767          $this->assertEquals($expected, $result);
 768      }
 769  
 770      /**
 771       * Some user cases for validating the expected theme depending on the cohorts, site and user values.
 772       *
 773       * The result is an array of:
 774       *     'User case description' => [
 775       *      'usertheme' => '', // User theme.
 776       *      'sitetheme' => '', // Site theme.
 777       *      'cohorts' => [],   // Cohort themes.
 778       *      'expected' => '',  // Expected value returned by cohort_get_user_cohort_theme.
 779       *    ]
 780       *
 781       * @return array
 782       */
 783      public function get_user_theme_provider() {
 784          return [
 785              'User not a member of any cohort' => [
 786                  'usertheme' => '',
 787                  'sitetheme' => 'boost',
 788                  'cohorts' => [],
 789                  'expected' => 'boost',
 790              ],
 791              'User member of one cohort which has a theme set' => [
 792                  'usertheme' => '',
 793                  'sitetheme' => 'boost',
 794                  'cohorts' => [
 795                      'classic',
 796                  ],
 797                  'expected' => 'classic',
 798              ],
 799              'User member of one cohort which has a theme set, and one without a theme' => [
 800                  'usertheme' => '',
 801                  'sitetheme' => 'boost',
 802                  'cohorts' => [
 803                      'classic',
 804                      '',
 805                  ],
 806                  'expected' => 'classic',
 807              ],
 808              'User member of one cohort which has a theme set, and one with a different theme' => [
 809                  'usertheme' => '',
 810                  'sitetheme' => 'boost',
 811                  'cohorts' => [
 812                      'classic',
 813                      'someother',
 814                  ],
 815                  'expected' => 'boost',
 816              ],
 817              'User with a theme but not a member of any cohort' => [
 818                  'usertheme' => 'classic',
 819                  'sitetheme' => 'boost',
 820                  'cohorts' => [],
 821                  'expected' => 'classic',
 822              ],
 823              'User with a theme and member of one cohort which has a theme set' => [
 824                  'usertheme' => 'classic',
 825                  'sitetheme' => 'boost',
 826                  'cohorts' => [
 827                      'boost',
 828                  ],
 829                  'expected' => 'classic',
 830              ],
 831          ];
 832      }
 833  
 834      /**
 835       * Tests user_can_edit_blocks() returns the expected response.
 836       * @covers ::user_can_edit_blocks()
 837       */
 838      public function test_user_can_edit_blocks() {
 839          global $DB;
 840  
 841          $systemcontext = \context_system::instance();
 842          $this->testpage->set_context($systemcontext);
 843  
 844          $user = $this->getDataGenerator()->create_user();
 845          $role = $DB->get_record('role', ['shortname' => 'teacher']);
 846          role_assign($role->id, $user->id, $systemcontext->id);
 847          $this->setUser($user);
 848  
 849          // Confirm expected response (false) when user does not have access to edit blocks.
 850          $capability = $this->testpage->all_editing_caps()[0];
 851          assign_capability($capability, CAP_PROHIBIT, $role->id, $systemcontext, true);
 852          $this->assertFalse($this->testpage->user_can_edit_blocks());
 853  
 854          // Give capability and confirm expected response (true) now user has access to edit blocks.
 855          assign_capability($capability, CAP_ALLOW, $role->id, $systemcontext, true);
 856          $this->assertTrue($this->testpage->user_can_edit_blocks());
 857      }
 858  
 859      /**
 860       * Tests that calling force_lock_all_blocks() will cause user_can_edit_blocks() to return false, regardless of capabilities.
 861       * @covers ::force_lock_all_blocks()
 862       */
 863      public function test_force_lock_all_blocks() {
 864          $this->testpage->set_context(\context_system::instance());
 865          $this->setAdminUser();
 866  
 867          // Confirm admin user has access to edit blocks.
 868          $this->assertTrue($this->testpage->user_can_edit_blocks());
 869  
 870          // Force lock and confirm user can no longer edit, despite having the capability.
 871          $this->testpage->force_lock_all_blocks();
 872          $this->assertFalse($this->testpage->user_can_edit_blocks());
 873      }
 874  }
 875  
 876  /**
 877   * Test-specific subclass to make some protected things public.
 878   */
 879  class testable_moodle_page extends moodle_page {
 880      public function initialise_default_pagetype($script = null) {
 881          parent::initialise_default_pagetype($script);
 882      }
 883      public function url_to_class_name($url) {
 884          return parent::url_to_class_name($url);
 885      }
 886      public function all_editing_caps() {
 887          return parent::all_editing_caps();
 888      }
 889  }