From a96faa49a855c79c7a0bbc8610c7f5282a471ced Mon Sep 17 00:00:00 2001 From: sam marshall Date: Tue, 16 May 2017 17:26:35 +0100 Subject: [PATCH] MDL-58957 Global search: Add block support to search manager --- blocks/upgrade.txt | 3 + search/classes/base.php | 2 +- search/classes/base_block.php | 243 +++++++++++++++++ search/classes/manager.php | 69 ++++- search/tests/base_block_test.php | 311 ++++++++++++++++++++++ search/tests/fixtures/mock_block_area.php | 40 +++ 6 files changed, 664 insertions(+), 4 deletions(-) create mode 100644 search/classes/base_block.php create mode 100644 search/tests/base_block_test.php create mode 100644 search/tests/fixtures/mock_block_area.php diff --git a/blocks/upgrade.txt b/blocks/upgrade.txt index f34b35d9950..6b8fc564aea 100644 --- a/blocks/upgrade.txt +++ b/blocks/upgrade.txt @@ -6,6 +6,9 @@ information provided here is intended especially for developers. * The block_instances table now contains fields timecreated and timemodified. If third-party code creates or updates these rows (without using the standard API), it should be modified to set these fields as appropriate. +* Blocks can now be included in Moodle global search, with some limitations (at present, the search + works only for blocks located directly on course pages or site home page). See the HTML block for + an example. === 3.3 === diff --git a/search/classes/base.php b/search/classes/base.php index 9b7180601c3..d9f51b4a62e 100644 --- a/search/classes/base.php +++ b/search/classes/base.php @@ -270,7 +270,7 @@ abstract class base { * Can the current user see the document. * * @param int $id The internal search area entity id. - * @return bool True if the user can see it, false otherwise + * @return int manager:ACCESS_xx constant */ abstract public function check_access($id); diff --git a/search/classes/base_block.php b/search/classes/base_block.php new file mode 100644 index 00000000000..91e3caf94fa --- /dev/null +++ b/search/classes/base_block.php @@ -0,0 +1,243 @@ +. + +/** + * Search area base class for blocks. + * + * Note: Only blocks within courses are supported. + * + * @package core_search + * @copyright 2017 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_search; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Search area base class for blocks. + * + * Note: Only blocks within courses are supported. + * + * @package core_search + * @copyright 2017 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class base_block extends base { + /** @var string Cache name used for block instances */ + const CACHE_INSTANCES = 'base_block_instances'; + + /** + * The context levels the search area is working on. + * + * This can be overwriten by the search area if it works at multiple + * levels. + * + * @var array + */ + protected static $levels = [CONTEXT_BLOCK]; + + /** + * Gets the block name only. + * + * @return string Block name e.g. 'html' + */ + public function get_block_name() { + // Remove 'block_' text. + return substr($this->get_component_name(), 6); + } + + /** + * Returns restrictions on which block_instances rows to return. By default, excludes rows + * that have empty configdata. + * + * @return string SQL restriction (or multiple restrictions joined by AND), empty if none + */ + protected function get_indexing_restrictions() { + return "bi.configdata != ''"; + } + + /** + * Gets recordset of all records modified since given time. + * + * See base class for detailed requirements. This implementation includes the key fields + * from block_instances. + * + * This can be overridden to do something totally different if the block's data is stored in + * other tables. + * + * If there are certain instances of the block which should not be included in the search index + * then you can override get_indexing_restrictions; by default this excludes rows with empty + * configdata. + * + * @param int $modifiedfrom Modified from time (>= this) + */ + public function get_recordset_by_timestamp($modifiedfrom = 0) { + global $DB; + $restrictions = $this->get_indexing_restrictions(); + if ($restrictions) { + $restrictions = 'AND ' . $restrictions; + } + // Query for all entries in block_instances for this type of block, which were modified + // since the given date. Also find the course or module where the block is located. + // (Although this query supports both module and course context, currently only two page + // types are supported, which will both be at course context. The module support is present + // in case of extension to other page types later.) + return $DB->get_recordset_sql(" + SELECT bi.id, bi.timemodified, bi.timecreated, bi.configdata, + c.id AS courseid, x.id AS contextid + FROM {block_instances} bi + JOIN {context} x ON x.instanceid = bi.id AND x.contextlevel = ? + JOIN {context} parent ON parent.id = bi.parentcontextid + LEFT JOIN {course_modules} cm ON cm.id = parent.instanceid AND parent.contextlevel = ? + JOIN {course} c ON c.id = cm.course + OR (c.id = parent.instanceid AND parent.contextlevel = ?) + WHERE bi.timemodified >= ? + AND bi.blockname = ? + AND (parent.contextlevel = ? AND (bi.pagetypepattern LIKE 'course-view-%' + OR bi.pagetypepattern IN ('site-index', 'course-*', '*'))) + $restrictions + ORDER BY bi.timemodified ASC", + [CONTEXT_BLOCK, CONTEXT_MODULE, CONTEXT_COURSE, $modifiedfrom, + $this->get_block_name(), CONTEXT_COURSE]); + } + + public function get_doc_url(\core_search\document $doc) { + // Load block instance and find cmid if there is one. + $blockinstanceid = preg_replace('~^.*-~', '', $doc->get('id')); + $instance = $this->get_block_instance($blockinstanceid); + $courseid = $doc->get('courseid'); + $anchor = 'inst' . $blockinstanceid; + + // Check if the block is at course or module level. + if ($instance->cmid) { + // No module-level page types are supported at present so the search system won't return + // them. But let's put some example code here to indicate how it could work. + debugging('Unexpected module-level page type for block ' . $blockinstanceid . ': ' . + $instance->pagetypepattern, DEBUG_DEVELOPER); + $modinfo = get_fast_modinfo($courseid); + $cm = $modinfo->get_cm($instance->cmid); + return new \moodle_url($cm->url, null, $anchor); + } else { + // The block is at course level. Let's check the page type, although in practice we + // currently only support the course main page. + if ($instance->pagetypepattern === '*' || $instance->pagetypepattern === 'course-*' || + preg_match('~^course-view-(.*)$~', $instance->pagetypepattern)) { + return new \moodle_url('/course/view.php', ['id' => $courseid], $anchor); + } else if ($instance->pagetypepattern === 'site-index') { + return new \moodle_url('/', [], $anchor); + } else { + debugging('Unexpected page type for block ' . $blockinstanceid . ': ' . + $instance->pagetypepattern, DEBUG_DEVELOPER); + return new \moodle_url('/course/view.php', ['id' => $courseid], $anchor); + } + } + } + + public function get_context_url(\core_search\document $doc) { + return $this->get_doc_url($doc); + } + + /** + * Checks access for a document in this search area. + * + * If you override this function for a block, you should call this base class version first + * as it will check that the block is still visible to users in a supported location. + * + * @param int $id Document id + * @return int manager:ACCESS_xx constant + */ + public function check_access($id) { + $instance = $this->get_block_instance($id, IGNORE_MISSING); + if (!$instance) { + // This generally won't happen because if the block has been deleted then we won't have + // included its context in the search area list, but just in case. + return manager::ACCESS_DELETED; + } + + // Check block has not been moved to an unsupported area since it was indexed. (At the + // moment, only blocks within site and course context are supported, also only certain + // page types.) + if (!$instance->courseid || + !self::is_supported_page_type_at_course_context($instance->pagetypepattern)) { + return manager::ACCESS_DELETED; + } + + // Note we do not need to check if the block was hidden or if the user has access to the + // context, because those checks are included in the list of search contexts user can access + // that is calculated in manager.php every time they do a query. + return manager::ACCESS_GRANTED; + } + + /** + * Checks if a page type is supported for blocks when at course (or also site) context. This + * function should be consistent with the SQL in get_recordset_by_timestamp. + * + * @param string $pagetype Page type + * @return bool True if supported + */ + protected static function is_supported_page_type_at_course_context($pagetype) { + if (in_array($pagetype, ['site-index', 'course-*', '*'])) { + return true; + } + if (preg_match('~^course-view-~', $pagetype)) { + return true; + } + return false; + } + + /** + * Gets a block instance with given id. + * + * Returns the fields id, pagetypepattern, subpagepattern from block_instances and also the + * cmid (if parent context is an activity module). + * + * @param int $id ID of block instance + * @param int $strictness MUST_EXIST or IGNORE_MISSING + * @return false|mixed Block instance data (may be false if strictness is IGNORE_MISSING) + */ + protected function get_block_instance($id, $strictness = MUST_EXIST) { + global $DB; + + $cache = \cache::make_from_params(\cache_store::MODE_REQUEST, 'core_search', + self::CACHE_INSTANCES, [], ['simplekeys' => true]); + $id = (int)$id; + $instance = $cache->get($id); + if (!$instance) { + $instance = $DB->get_record_sql(" + SELECT bi.id, bi.pagetypepattern, bi.subpagepattern, + c.id AS courseid, cm.id AS cmid + FROM {block_instances} bi + JOIN {context} parent ON parent.id = bi.parentcontextid + LEFT JOIN {course} c ON c.id = parent.instanceid AND parent.contextlevel = ? + LEFT JOIN {course_modules} cm ON cm.id = parent.instanceid AND parent.contextlevel = ? + WHERE bi.id = ?", + [CONTEXT_COURSE, CONTEXT_MODULE, $id], $strictness); + $cache->set($id, $instance); + } + return $instance; + } + + /** + * Clears static cache. This function can be removed (with calls to it in the test script + * replaced with cache_helper::purge_all) if MDL-59427 is fixed. + */ + public static function clear_static() { + \cache::make_from_params(\cache_store::MODE_REQUEST, 'core_search', + self::CACHE_INSTANCES, [], ['simplekeys' => true])->purge(); + } +} diff --git a/search/classes/manager.php b/search/classes/manager.php index cef7900d1d9..59041951251 100644 --- a/search/classes/manager.php +++ b/search/classes/manager.php @@ -294,6 +294,8 @@ class manager { static::$enabledsearchareas = null; static::$allsearchareas = null; static::$instance = null; + + base_block::clear_static(); } /** @@ -331,7 +333,7 @@ class manager { * @return bool|array Indexed by area identifier (component + area name). Returns true if the user can see everything. */ protected function get_areas_user_accesses($limitcourseids = false) { - global $CFG, $USER; + global $DB, $USER; // All results for admins. Eventually we could add a new capability for managers. if (is_siteadmin()) { @@ -380,19 +382,23 @@ class manager { $courses[SITEID] = get_course(SITEID); } + // Keep a list of included course context ids (needed for the block calculation below). + $coursecontextids = []; + foreach ($courses as $course) { if (!empty($limitcourseids) && !in_array($course->id, $limitcourseids)) { // Skip non-included courses. continue; } + $coursecontext = \context_course::instance($course->id); + $coursecontextids[] = $coursecontext->id; + // Info about the course modules. $modinfo = get_fast_modinfo($course); if (!empty($areasbylevel[CONTEXT_COURSE])) { // Add the course contexts the user can view. - - $coursecontext = \context_course::instance($course->id); foreach ($areasbylevel[CONTEXT_COURSE] as $areaid => $searchclass) { if ($course->visible || has_capability('moodle/course:viewhiddencourses', $coursecontext)) { $areascontexts[$areaid][$coursecontext->id] = $coursecontext->id; @@ -418,6 +424,63 @@ class manager { } } + // Add all supported block contexts, in a single query for performance. + if (!empty($areasbylevel[CONTEXT_BLOCK])) { + // Get list of all block types we care about. + $blocklist = []; + foreach ($areasbylevel[CONTEXT_BLOCK] as $areaid => $searchclass) { + $blocklist[$searchclass->get_block_name()] = true; + } + list ($blocknamesql, $blocknameparams) = $DB->get_in_or_equal(array_keys($blocklist)); + + // Get list of course contexts. + list ($contextsql, $contextparams) = $DB->get_in_or_equal($coursecontextids); + + // Query all blocks that are within an included course, and are set to be visible, and + // in a supported page type (basically just course view). This query could be + // extended (or a second query added) to support blocks that are within a module + // context as well, and we could add more page types if required. + $blockrecs = $DB->get_records_sql(" + SELECT x.*, bi.blockname AS blockname, bi.id AS blockinstanceid + FROM {block_instances} bi + JOIN {context} x ON x.instanceid = bi.id AND x.contextlevel = ? + LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id + AND bp.contextid = bi.parentcontextid + AND bp.pagetype LIKE 'course-view-%' + AND bp.subpage = '' + AND bp.visible = 0 + WHERE bi.parentcontextid $contextsql + AND bi.blockname $blocknamesql + AND bi.subpagepattern IS NULL + AND (bi.pagetypepattern = 'site-index' + OR bi.pagetypepattern LIKE 'course-view-%' + OR bi.pagetypepattern = 'course-*' + OR bi.pagetypepattern = '*') + AND bp.id IS NULL", + array_merge([CONTEXT_BLOCK], $contextparams, $blocknameparams)); + $blockcontextsbyname = []; + foreach ($blockrecs as $blockrec) { + if (empty($blockcontextsbyname[$blockrec->blockname])) { + $blockcontextsbyname[$blockrec->blockname] = []; + } + \context_helper::preload_from_record($blockrec); + $blockcontextsbyname[$blockrec->blockname][] = \context_block::instance( + $blockrec->blockinstanceid); + } + + // Add the block contexts the user can view. + foreach ($areasbylevel[CONTEXT_BLOCK] as $areaid => $searchclass) { + if (empty($blockcontextsbyname[$searchclass->get_block_name()])) { + continue; + } + foreach ($blockcontextsbyname[$searchclass->get_block_name()] as $context) { + if (has_capability('moodle/block:view', $context)) { + $areascontexts[$areaid][$context->id] = $context->id; + } + } + } + } + return $areascontexts; } diff --git a/search/tests/base_block_test.php b/search/tests/base_block_test.php new file mode 100644 index 00000000000..87e30184a40 --- /dev/null +++ b/search/tests/base_block_test.php @@ -0,0 +1,311 @@ +. + +/** + * Unit tests for the base_block class. + * + * @package core_search + * @copyright 2017 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once(__DIR__ . '/fixtures/testable_core_search.php'); +require_once(__DIR__ . '/fixtures/mock_block_area.php'); + +/** + * Unit tests for the base_block class. + * + * @package core_search + * @copyright 2017 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class base_block_testcase extends advanced_testcase { + /** + * Tests getting the name out of the class name. + */ + public function test_get_block_name() { + $area = new \block_mockblock\search\area(); + $this->assertEquals('mockblock', $area->get_block_name()); + } + + /** + * Tests getting the recordset. + */ + public function test_get_recordset_by_timestamp() { + global $DB; + + $this->resetAfterTest(); + + // Create course and activity module. + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $coursecontext = \context_course::instance($course->id); + $page = $generator->create_module('page', ['course' => $course->id]); + $pagecontext = \context_module::instance($page->cmid); + + // Add blocks by hacking table (because it's not a real block type). + + // 1. Block on course page. + $configdata = base64_encode(serialize(new \stdClass())); + $instance = (object)['blockname' => 'mockblock', 'parentcontextid' => $coursecontext->id, + 'showinsubcontexts' => 0, 'pagetypepattern' => 'course-view-*', + 'defaultweight' => 0, 'timecreated' => 1, 'timemodified' => 1, + 'configdata' => $configdata]; + $block1id = $DB->insert_record('block_instances', $instance); + $block1context = \context_block::instance($block1id); + + // 2. Block on activity page. + $instance->parentcontextid = $pagecontext->id; + $instance->pagetypepattern = 'mod-page-view'; + $instance->timemodified = 2; + $block2id = $DB->insert_record('block_instances', $instance); + \context_block::instance($block2id); + + // 3. Block on site context. + $sitecourse = get_site(); + $sitecontext = \context_course::instance($sitecourse->id); + $instance->parentcontextid = $sitecontext->id; + $instance->pagetypepattern = 'site-index'; + $instance->timemodified = 3; + $block3id = $DB->insert_record('block_instances', $instance); + $block3context = \context_block::instance($block3id); + + // 4. Block on course page but no data. + $instance->parentcontextid = $coursecontext->id; + $instance->pagetypepattern = 'course-view-*'; + unset($instance->configdata); + $instance->timemodified = 4; + $block4id = $DB->insert_record('block_instances', $instance); + \context_block::instance($block4id); + + // 5. Block on course page but not this block. + $instance->blockname = 'mockotherblock'; + $instance->configdata = $configdata; + $instance->timemodified = 5; + $block5id = $DB->insert_record('block_instances', $instance); + \context_block::instance($block5id); + + // 6. Block on course page with '*' page type. + $instance->blockname = 'mockblock'; + $instance->pagetypepattern = '*'; + $instance->timemodified = 6; + $block6id = $DB->insert_record('block_instances', $instance); + \context_block::instance($block6id); + + // 7. Block on course page with 'course-*' page type. + $instance->pagetypepattern = 'course-*'; + $instance->timemodified = 7; + $block7id = $DB->insert_record('block_instances', $instance); + \context_block::instance($block7id); + + // Get all the blocks. + $area = new block_mockblock\search\area(); + $rs = $area->get_recordset_by_timestamp(); + $results = []; + foreach ($rs as $rec) { + $results[] = $rec; + } + $rs->close(); + + // Only blocks 1, 3, 6, and 7 should be returned. Check all the fields for the first two. + $this->assertCount(4, $results); + + $this->assertEquals($block1id, $results[0]->id); + $this->assertEquals(1, $results[0]->timemodified); + $this->assertEquals(1, $results[0]->timecreated); + $this->assertEquals($configdata, $results[0]->configdata); + $this->assertEquals($course->id, $results[0]->courseid); + $this->assertEquals($block1context->id, $results[0]->contextid); + + $this->assertEquals($block3id, $results[1]->id); + $this->assertEquals(3, $results[1]->timemodified); + $this->assertEquals(1, $results[1]->timecreated); + $this->assertEquals($configdata, $results[1]->configdata); + $this->assertEquals($sitecourse->id, $results[1]->courseid); + $this->assertEquals($block3context->id, $results[1]->contextid); + + // For the later ones, just check it got the right ones! + $this->assertEquals($block6id, $results[2]->id); + $this->assertEquals($block7id, $results[3]->id); + + // Repeat with a time restriction. + $rs = $area->get_recordset_by_timestamp(2); + $results = []; + foreach ($rs as $rec) { + $results[] = $rec; + } + $rs->close(); + + // Only block 3, 6, and 7 are returned. + $this->assertCount(3, $results); + $this->assertEquals($block3id, $results[0]->id); + $this->assertEquals($block6id, $results[1]->id); + $this->assertEquals($block7id, $results[2]->id); + } + + /** + * Tests the get_doc_url function. + */ + public function test_get_doc_url() { + global $DB; + + $this->resetAfterTest(); + + // Create course and activity module. + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $coursecontext = \context_course::instance($course->id); + $page = $generator->create_module('page', ['course' => $course->id]); + $pagecontext = \context_module::instance($page->cmid); + + // Create block on course page. + $configdata = base64_encode(serialize(new \stdClass())); + $instance = (object)['blockname' => 'mockblock', 'parentcontextid' => $coursecontext->id, + 'showinsubcontexts' => 0, 'pagetypepattern' => 'course-view-*', 'defaultweight' => 0, + 'timecreated' => 1, 'timemodified' => 1, 'configdata' => $configdata]; + $blockid = $DB->insert_record('block_instances', $instance); + + // Get document URL. + $area = new block_mockblock\search\area(); + $doc = $this->get_doc($course->id, $blockid); + $expected = new moodle_url('/course/view.php', ['id' => $course->id], 'inst' . $blockid); + $this->assertEquals($expected, $area->get_doc_url($doc)); + $this->assertEquals($expected, $area->get_context_url($doc)); + + // Repeat with block on site page. + $sitecourse = get_site(); + $sitecontext = \context_course::instance($sitecourse->id); + $instance->pagetypepattern = 'site-index'; + $instance->parentcontextid = $sitecontext->id; + $block2id = $DB->insert_record('block_instances', $instance); + + // Get document URL. + $doc2 = $this->get_doc($course->id, $block2id); + $expected = new moodle_url('/', [], 'inst' . $block2id); + $this->assertEquals($expected, $area->get_doc_url($doc2)); + $this->assertEquals($expected, $area->get_context_url($doc2)); + + // Repeat with block on module page (this cannot happen yet because the search query will + // only include course context blocks, but let's check it works for the future). + $instance->pagetypepattern = 'mod-page-view'; + $instance->parentcontextid = $pagecontext->id; + $block3id = $DB->insert_record('block_instances', $instance); + + // Get and check document URL, ignoring debugging message for unsupported page type. + $debugmessage = 'Unexpected module-level page type for block ' . $block3id . + ': mod-page-view'; + $doc3 = $this->get_doc($course->id, $block3id); + $this->assertDebuggingCalledCount(2, [$debugmessage, $debugmessage]); + + $expected = new moodle_url('/mod/page/view.php', ['id' => $page->cmid], 'inst' . $block3id); + $this->assertEquals($expected, $area->get_doc_url($doc3)); + $this->assertDebuggingCalled($debugmessage); + $this->assertEquals($expected, $area->get_context_url($doc3)); + $this->assertDebuggingCalled($debugmessage); + + // Repeat with another block on course page but '*' pages. + $instance->pagetypepattern = '*'; + $instance->parentcontextid = $coursecontext->id; + $block4id = $DB->insert_record('block_instances', $instance); + + // Get document URL. + $doc = $this->get_doc($course->id, $block4id); + $expected = new moodle_url('/course/view.php', ['id' => $course->id], 'inst' . $block4id); + $this->assertEquals($expected, $area->get_doc_url($doc)); + $this->assertEquals($expected, $area->get_context_url($doc)); + + // And same thing but 'course-*' pages. + $instance->pagetypepattern = 'course-*'; + $block5id = $DB->insert_record('block_instances', $instance); + + // Get document URL. + $doc = $this->get_doc($course->id, $block5id); + $expected = new moodle_url('/course/view.php', ['id' => $course->id], 'inst' . $block5id); + $this->assertEquals($expected, $area->get_doc_url($doc)); + $this->assertEquals($expected, $area->get_context_url($doc)); + } + + /** + * Tests the check_access function. + */ + public function test_check_access() { + global $DB; + + $this->resetAfterTest(); + + // Create course and activity module. + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $coursecontext = \context_course::instance($course->id); + $page = $generator->create_module('page', ['course' => $course->id]); + $pagecontext = \context_module::instance($page->cmid); + + // Create block on course page. + $configdata = base64_encode(serialize(new \stdClass())); + $instance = (object)['blockname' => 'mockblock', 'parentcontextid' => $coursecontext->id, + 'showinsubcontexts' => 0, 'pagetypepattern' => 'course-view-*', 'defaultweight' => 0, + 'timecreated' => 1, 'timemodified' => 1, 'configdata' => $configdata]; + $blockid = $DB->insert_record('block_instances', $instance); + + // Check access for block that exists. + $area = new block_mockblock\search\area(); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $area->check_access($blockid)); + + // Check access for nonexistent block. + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $area->check_access($blockid + 1)); + + // Check if block is not in a course context any longer. + $DB->set_field('block_instances', 'parentcontextid', $pagecontext->id, ['id' => $blockid]); + \core_search\base_block::clear_static(); + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $area->check_access($blockid)); + + // Or what if it is in a course context but has supported vs. unsupported page type. + $DB->set_field('block_instances', 'parentcontextid', $coursecontext->id, ['id' => $blockid]); + + $DB->set_field('block_instances', 'pagetypepattern', 'course-*', ['id' => $blockid]); + \core_search\base_block::clear_static(); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $area->check_access($blockid)); + + $DB->set_field('block_instances', 'pagetypepattern', '*', ['id' => $blockid]); + \core_search\base_block::clear_static(); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $area->check_access($blockid)); + + $DB->set_field('block_instances', 'pagetypepattern', 'course-view-frogs', ['id' => $blockid]); + \core_search\base_block::clear_static(); + $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $area->check_access($blockid)); + + $DB->set_field('block_instances', 'pagetypepattern', 'anythingelse', ['id' => $blockid]); + \core_search\base_block::clear_static(); + $this->assertEquals(\core_search\manager::ACCESS_DELETED, $area->check_access($blockid)); + } + + /** + * Gets a search document object from the fake search area. + * + * @param int $courseid Course id in document + * @param int $blockinstanceid Block instance id in document + * @return \core_search\document Document object + */ + protected function get_doc($courseid, $blockinstanceid) { + $engine = testable_core_search::instance()->get_engine(); + $area = new block_mockblock\search\area(); + $docdata = ['id' => $blockinstanceid, 'courseid' => $courseid, + 'areaid' => $area->get_area_id(), 'itemid' => 0]; + return $engine->to_document($area, $docdata); + } +} diff --git a/search/tests/fixtures/mock_block_area.php b/search/tests/fixtures/mock_block_area.php new file mode 100644 index 00000000000..60ce02d36f3 --- /dev/null +++ b/search/tests/fixtures/mock_block_area.php @@ -0,0 +1,40 @@ +. + +/** + * Test block area. + * + * @package core_search + * @copyright 2017 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_mockblock\search; + +defined('MOODLE_INTERNAL') || die; + +/** + * Test block area. + * + * @package core_search + * @copyright 2017 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class area extends \core_search\base_block { + public function get_document($record, $options = array()) { + throw new \coding_exception('Not implemented'); + } +}