Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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  /**
  18   * Unit tests for the base_block class.
  19   *
  20   * @package core_search
  21   * @copyright 2017 The Open University
  22   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once (__DIR__ . '/fixtures/testable_core_search.php');
  28  require_once (__DIR__ . '/fixtures/mock_block_area.php');
  29  
  30  /**
  31   * Unit tests for the base_block class.
  32   *
  33   * @package core_search
  34   * @copyright 2017 The Open University
  35   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class base_block_testcase extends advanced_testcase {
  38      /**
  39       * Tests getting the name out of the class name.
  40       */
  41      public function test_get_block_name() {
  42          $area = new \block_mockblock\search\area();
  43          $this->assertEquals('mockblock', $area->get_block_name());
  44      }
  45  
  46      /**
  47       * Tests getting the recordset.
  48       */
  49      public function test_get_document_recordset() {
  50          global $DB, $USER;
  51  
  52          $this->resetAfterTest();
  53          $this->setAdminUser();
  54  
  55          // Create course and activity module.
  56          $generator = $this->getDataGenerator();
  57          $course = $generator->create_course();
  58          $coursecontext = \context_course::instance($course->id);
  59          $page = $generator->create_module('page', ['course' => $course->id]);
  60          $pagecontext = \context_module::instance($page->cmid);
  61  
  62          // Create another 2 courses (in same category and in a new category).
  63          $cat1context = \context_coursecat::instance($course->category);
  64          $course2 = $generator->create_course();
  65          $course2context = \context_course::instance($course2->id);
  66          $cat2 = $generator->create_category();
  67          $cat2context = \context_coursecat::instance($cat2->id);
  68          $course3 = $generator->create_course(['category' => $cat2->id]);
  69          $course3context = \context_course::instance($course3->id);
  70  
  71          // Add blocks by hacking table (because it's not a real block type).
  72  
  73          // 1. Block on course page.
  74          $configdata = base64_encode(serialize((object) ['example' => 'content']));
  75          $instance = (object)['blockname' => 'mockblock', 'parentcontextid' => $coursecontext->id,
  76                  'showinsubcontexts' => 0, 'pagetypepattern' => 'course-view-*',
  77                  'defaultweight' => 0, 'timecreated' => 1, 'timemodified' => 1,
  78                  'configdata' => $configdata];
  79          $block1id = $DB->insert_record('block_instances', $instance);
  80          $block1context = \context_block::instance($block1id);
  81  
  82          // 2. Block on activity page.
  83          $instance->parentcontextid = $pagecontext->id;
  84          $instance->pagetypepattern = 'mod-page-view';
  85          $instance->timemodified = 2;
  86          $block2id = $DB->insert_record('block_instances', $instance);
  87          \context_block::instance($block2id);
  88  
  89          // 3. Block on site context.
  90          $sitecourse = get_site();
  91          $sitecontext = \context_course::instance($sitecourse->id);
  92          $instance->parentcontextid = $sitecontext->id;
  93          $instance->pagetypepattern = 'site-index';
  94          $instance->timemodified = 3;
  95          $block3id = $DB->insert_record('block_instances', $instance);
  96          $block3context = \context_block::instance($block3id);
  97  
  98          // 4. Block on course page but no data.
  99          $instance->parentcontextid = $coursecontext->id;
 100          $instance->pagetypepattern = 'course-view-*';
 101          unset($instance->configdata);
 102          $instance->timemodified = 4;
 103          $block4id = $DB->insert_record('block_instances', $instance);
 104          \context_block::instance($block4id);
 105  
 106          // 5. Block on course page but not this block.
 107          $instance->blockname = 'mockotherblock';
 108          $instance->configdata = $configdata;
 109          $instance->timemodified = 5;
 110          $block5id = $DB->insert_record('block_instances', $instance);
 111          \context_block::instance($block5id);
 112  
 113          // 6. Block on course page with '*' page type.
 114          $instance->blockname = 'mockblock';
 115          $instance->pagetypepattern = '*';
 116          $instance->timemodified = 6;
 117          $block6id = $DB->insert_record('block_instances', $instance);
 118          \context_block::instance($block6id);
 119  
 120          // 7. Block on course page with 'course-*' page type.
 121          $instance->pagetypepattern = 'course-*';
 122          $instance->timemodified = 7;
 123          $block7id = $DB->insert_record('block_instances', $instance);
 124          \context_block::instance($block7id);
 125  
 126          // 8. Block on course 2.
 127          $instance->parentcontextid = $course2context->id;
 128          $instance->timemodified = 8;
 129          $block8id = $DB->insert_record('block_instances', $instance);
 130          \context_block::instance($block8id);
 131  
 132          // 9. Block on course 3.
 133          $instance->parentcontextid = $course3context->id;
 134          $instance->timemodified = 9;
 135          $block9id = $DB->insert_record('block_instances', $instance);
 136          \context_block::instance($block9id);
 137  
 138          // Get all the blocks.
 139          $area = new block_mockblock\search\area();
 140          $results = self::recordset_to_indexed_array($area->get_document_recordset());
 141  
 142          // Only blocks 1, 3, 6, 7, 8, 9 should be returned. Check all the fields for the first two.
 143          $this->assertCount(6, $results);
 144  
 145          $this->assertEquals($block1id, $results[0]->id);
 146          $this->assertEquals(1, $results[0]->timemodified);
 147          $this->assertEquals(1, $results[0]->timecreated);
 148          $this->assertEquals($configdata, $results[0]->configdata);
 149          $this->assertEquals($course->id, $results[0]->courseid);
 150          $this->assertEquals($block1context->id, $results[0]->contextid);
 151  
 152          $this->assertEquals($block3id, $results[1]->id);
 153          $this->assertEquals(3, $results[1]->timemodified);
 154          $this->assertEquals(1, $results[1]->timecreated);
 155          $this->assertEquals($configdata, $results[1]->configdata);
 156          $this->assertEquals($sitecourse->id, $results[1]->courseid);
 157          $this->assertEquals($block3context->id, $results[1]->contextid);
 158  
 159          // For the later ones, just check it got the right ones!
 160          $this->assertEquals($block6id, $results[2]->id);
 161          $this->assertEquals($block7id, $results[3]->id);
 162          $this->assertEquals($block8id, $results[4]->id);
 163          $this->assertEquals($block9id, $results[5]->id);
 164  
 165          // Repeat with a time restriction.
 166          $results = self::recordset_to_indexed_array($area->get_document_recordset(2));
 167  
 168          // Only block 3, 6, 7, 8, and 9 are returned.
 169          $this->assertEquals([$block3id, $block6id, $block7id, $block8id, $block9id],
 170                  self::records_to_ids($results));
 171  
 172          // Now use context restrictions. First, the whole site (no change).
 173          $results = self::recordset_to_indexed_array($area->get_document_recordset(
 174                  0, context_system::instance()));
 175          $this->assertEquals([$block1id, $block3id, $block6id, $block7id, $block8id, $block9id],
 176                  self::records_to_ids($results));
 177  
 178          // Course page only (leave out the one on site page and other courses).
 179          $results = self::recordset_to_indexed_array($area->get_document_recordset(
 180                  0, $coursecontext));
 181          $this->assertEquals([$block1id, $block6id, $block7id],
 182                  self::records_to_ids($results));
 183  
 184          // Other course page only.
 185          $results = self::recordset_to_indexed_array($area->get_document_recordset(
 186                  0, $course2context));
 187          $this->assertEquals([$block8id], self::records_to_ids($results));
 188  
 189          // Activity module only (no results).
 190          $results = self::recordset_to_indexed_array($area->get_document_recordset(
 191                  0, $pagecontext));
 192          $this->assertCount(0, $results);
 193  
 194          // Specific block context.
 195          $results = self::recordset_to_indexed_array($area->get_document_recordset(
 196                  0, $block3context));
 197          $this->assertEquals([$block3id], self::records_to_ids($results));
 198  
 199          // User context (no results).
 200          $usercontext = context_user::instance($USER->id);
 201          $results = self::recordset_to_indexed_array($area->get_document_recordset(
 202                  0, $usercontext));
 203          $this->assertCount(0, $results);
 204  
 205          // Category 1 context (courses 1 and 2).
 206          $results = self::recordset_to_indexed_array($area->get_document_recordset(
 207                  0, $cat1context));
 208          $this->assertEquals([$block1id, $block6id, $block7id, $block8id],
 209                  self::records_to_ids($results));
 210  
 211          // Category 2 context (course 3).
 212          $results = self::recordset_to_indexed_array($area->get_document_recordset(
 213                  0, $cat2context));
 214          $this->assertEquals([$block9id], self::records_to_ids($results));
 215  
 216          // Combine context restriction (category 1) with timemodified.
 217          $results = self::recordset_to_indexed_array($area->get_document_recordset(
 218                  7, $cat1context));
 219          $this->assertEquals([$block7id, $block8id], self::records_to_ids($results));
 220      }
 221  
 222      /**
 223       * Utility function to convert recordset to array for testing.
 224       *
 225       * @param moodle_recordset $rs Recordset to convert
 226       * @return array Array indexed by number (0, 1, 2, ...)
 227       */
 228      protected static function recordset_to_indexed_array(moodle_recordset $rs) {
 229          $results = [];
 230          foreach ($rs as $rec) {
 231              $results[] = $rec;
 232          }
 233          $rs->close();
 234          return $results;
 235      }
 236  
 237      /**
 238       * Utility function to convert records to array of IDs.
 239       *
 240       * @param array $recs Records which should have an 'id' field
 241       * @return array Array of ids
 242       */
 243      protected static function records_to_ids(array $recs) {
 244          $ids = [];
 245          foreach ($recs as $rec) {
 246              $ids[] = $rec->id;
 247          }
 248          return $ids;
 249      }
 250  
 251      /**
 252       * Tests the get_doc_url function.
 253       */
 254      public function test_get_doc_url() {
 255          global $DB;
 256  
 257          $this->resetAfterTest();
 258  
 259          // Create course and activity module.
 260          $generator = $this->getDataGenerator();
 261          $course = $generator->create_course();
 262          $coursecontext = \context_course::instance($course->id);
 263          $page = $generator->create_module('page', ['course' => $course->id]);
 264          $pagecontext = \context_module::instance($page->cmid);
 265  
 266          // Create block on course page.
 267          $configdata = base64_encode(serialize(new \stdClass()));
 268          $instance = (object)['blockname' => 'mockblock', 'parentcontextid' => $coursecontext->id,
 269                  'showinsubcontexts' => 0, 'pagetypepattern' => 'course-view-*', 'defaultweight' => 0,
 270                  'timecreated' => 1, 'timemodified' => 1, 'configdata' => $configdata];
 271          $blockid = $DB->insert_record('block_instances', $instance);
 272  
 273          // Get document URL.
 274          $area = new block_mockblock\search\area();
 275          $doc = $this->get_doc($course->id, $blockid);
 276          $expected = new moodle_url('/course/view.php', ['id' => $course->id], 'inst' . $blockid);
 277          $this->assertEquals($expected, $area->get_doc_url($doc));
 278          $this->assertEquals($expected, $area->get_context_url($doc));
 279  
 280          // Repeat with block on site page.
 281          $sitecourse = get_site();
 282          $sitecontext = \context_course::instance($sitecourse->id);
 283          $instance->pagetypepattern = 'site-index';
 284          $instance->parentcontextid = $sitecontext->id;
 285          $block2id = $DB->insert_record('block_instances', $instance);
 286  
 287          // Get document URL.
 288          $doc2 = $this->get_doc($course->id, $block2id);
 289          $expected = new moodle_url('/', ['redirect' => 0], 'inst' . $block2id);
 290          $this->assertEquals($expected, $area->get_doc_url($doc2));
 291          $this->assertEquals($expected, $area->get_context_url($doc2));
 292  
 293          // Repeat with block on module page (this cannot happen yet because the search query will
 294          // only include course context blocks, but let's check it works for the future).
 295          $instance->pagetypepattern = 'mod-page-view';
 296          $instance->parentcontextid = $pagecontext->id;
 297          $block3id = $DB->insert_record('block_instances', $instance);
 298  
 299          // Get and check document URL, ignoring debugging message for unsupported page type.
 300          $debugmessage = 'Unexpected module-level page type for block ' . $block3id .
 301                  ': mod-page-view';
 302          $doc3 = $this->get_doc($course->id, $block3id);
 303          $this->assertDebuggingCalledCount(2, [$debugmessage, $debugmessage]);
 304  
 305          $expected = new moodle_url('/mod/page/view.php', ['id' => $page->cmid], 'inst' . $block3id);
 306          $this->assertEquals($expected, $area->get_doc_url($doc3));
 307          $this->assertDebuggingCalled($debugmessage);
 308          $this->assertEquals($expected, $area->get_context_url($doc3));
 309          $this->assertDebuggingCalled($debugmessage);
 310  
 311          // Repeat with another block on course page but '*' pages.
 312          $instance->pagetypepattern = '*';
 313          $instance->parentcontextid = $coursecontext->id;
 314          $block4id = $DB->insert_record('block_instances', $instance);
 315  
 316          // Get document URL.
 317          $doc = $this->get_doc($course->id, $block4id);
 318          $expected = new moodle_url('/course/view.php', ['id' => $course->id], 'inst' . $block4id);
 319          $this->assertEquals($expected, $area->get_doc_url($doc));
 320          $this->assertEquals($expected, $area->get_context_url($doc));
 321  
 322          // And same thing but 'course-*' pages.
 323          $instance->pagetypepattern = 'course-*';
 324          $block5id = $DB->insert_record('block_instances', $instance);
 325  
 326          // Get document URL.
 327          $doc = $this->get_doc($course->id, $block5id);
 328          $expected = new moodle_url('/course/view.php', ['id' => $course->id], 'inst' . $block5id);
 329          $this->assertEquals($expected, $area->get_doc_url($doc));
 330          $this->assertEquals($expected, $area->get_context_url($doc));
 331      }
 332  
 333      /**
 334       * Tests the check_access function.
 335       */
 336      public function test_check_access() {
 337          global $DB;
 338  
 339          $this->resetAfterTest();
 340  
 341          // Create course and activity module.
 342          $generator = $this->getDataGenerator();
 343          $course = $generator->create_course();
 344          $coursecontext = \context_course::instance($course->id);
 345          $page = $generator->create_module('page', ['course' => $course->id]);
 346          $pagecontext = \context_module::instance($page->cmid);
 347  
 348          // Create block on course page.
 349          $configdata = base64_encode(serialize(new \stdClass()));
 350          $instance = (object)['blockname' => 'mockblock', 'parentcontextid' => $coursecontext->id,
 351                  'showinsubcontexts' => 0, 'pagetypepattern' => 'course-view-*', 'defaultweight' => 0,
 352                  'timecreated' => 1, 'timemodified' => 1, 'configdata' => $configdata];
 353          $blockid = $DB->insert_record('block_instances', $instance);
 354  
 355          // Check access for block that exists.
 356          $area = new block_mockblock\search\area();
 357          $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $area->check_access($blockid));
 358  
 359          // Check access for nonexistent block.
 360          $this->assertEquals(\core_search\manager::ACCESS_DELETED, $area->check_access($blockid + 1));
 361  
 362          // Check if block is not in a course context any longer.
 363          $DB->set_field('block_instances', 'parentcontextid', $pagecontext->id, ['id' => $blockid]);
 364          \core_search\base_block::clear_static();
 365          $this->assertEquals(\core_search\manager::ACCESS_DELETED, $area->check_access($blockid));
 366  
 367          // Or what if it is in a course context but has supported vs. unsupported page type.
 368          $DB->set_field('block_instances', 'parentcontextid', $coursecontext->id, ['id' => $blockid]);
 369  
 370          $DB->set_field('block_instances', 'pagetypepattern', 'course-*', ['id' => $blockid]);
 371          \core_search\base_block::clear_static();
 372          $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $area->check_access($blockid));
 373  
 374          $DB->set_field('block_instances', 'pagetypepattern', '*', ['id' => $blockid]);
 375          \core_search\base_block::clear_static();
 376          $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $area->check_access($blockid));
 377  
 378          $DB->set_field('block_instances', 'pagetypepattern', 'course-view-frogs', ['id' => $blockid]);
 379          \core_search\base_block::clear_static();
 380          $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $area->check_access($blockid));
 381  
 382          $DB->set_field('block_instances', 'pagetypepattern', 'anythingelse', ['id' => $blockid]);
 383          \core_search\base_block::clear_static();
 384          $this->assertEquals(\core_search\manager::ACCESS_DELETED, $area->check_access($blockid));
 385      }
 386  
 387      /**
 388       * Tests the block version of get_contexts_to_reindex, which is supposed to return all the
 389       * block contexts in order of date added.
 390       */
 391      public function test_get_contexts_to_reindex() {
 392          global $DB;
 393  
 394          $this->resetAfterTest();
 395  
 396          // Create course and activity module.
 397          $generator = $this->getDataGenerator();
 398          $course = $generator->create_course();
 399          $coursecontext = \context_course::instance($course->id);
 400          $page = $generator->create_module('page', ['course' => $course->id]);
 401          $pagecontext = \context_module::instance($page->cmid);
 402  
 403          // Create blocks on course page, with time modified non-sequential.
 404          $configdata = base64_encode(serialize(new \stdClass()));
 405          $instance = (object)['blockname' => 'mockblock', 'parentcontextid' => $coursecontext->id,
 406                  'showinsubcontexts' => 0, 'pagetypepattern' => 'course-view-*', 'defaultweight' => 0,
 407                  'timecreated' => 1, 'timemodified' => 100, 'configdata' => $configdata];
 408          $blockid1 = $DB->insert_record('block_instances', $instance);
 409          $context1 = \context_block::instance($blockid1);
 410          $instance->timemodified = 120;
 411          $blockid2 = $DB->insert_record('block_instances', $instance);
 412          $context2 = \context_block::instance($blockid2);
 413          $instance->timemodified = 110;
 414          $blockid3 = $DB->insert_record('block_instances', $instance);
 415          $context3 = \context_block::instance($blockid3);
 416  
 417          // Create another block on the activity page (not included).
 418          $instance->parentcontextid = $pagecontext->id;
 419          $blockid4 = $DB->insert_record('block_instances', $instance);
 420          \context_block::instance($blockid4);
 421  
 422          // Check list of contexts.
 423          $area = new block_mockblock\search\area();
 424          $contexts = iterator_to_array($area->get_contexts_to_reindex(), false);
 425          $expected = [
 426              $context2,
 427              $context3,
 428              $context1
 429          ];
 430          $this->assertEquals($expected, $contexts);
 431      }
 432  
 433      /**
 434       * Gets a search document object from the fake search area.
 435       *
 436       * @param int $courseid Course id in document
 437       * @param int $blockinstanceid Block instance id in document
 438       * @return \core_search\document Document object
 439       */
 440      protected function get_doc($courseid, $blockinstanceid) {
 441          $engine = testable_core_search::instance()->get_engine();
 442          $area = new block_mockblock\search\area();
 443          $docdata = ['id' => $blockinstanceid, 'courseid' => $courseid,
 444                  'areaid' => $area->get_area_id(), 'itemid' => 0];
 445          return $engine->to_document($area, $docdata);
 446      }
 447  
 448      /**
 449       * Test document icon.
 450       */
 451      public function test_get_doc_icon() {
 452          $baseblock = $this->getMockBuilder('\core_search\base_block')
 453              ->disableOriginalConstructor()
 454              ->getMockForAbstractClass();
 455  
 456          $document = $this->getMockBuilder('\core_search\document')
 457              ->disableOriginalConstructor()
 458              ->getMock();
 459  
 460          $result = $baseblock->get_doc_icon($document);
 461  
 462          $this->assertEquals('e/anchor', $result->get_name());
 463          $this->assertEquals('moodle', $result->get_component());
 464      }
 465  }