Merge branch 'MDL-58957-master' of https://github.com/sammarshallou/moodle

This commit is contained in:
Dan Poltawski 2017-07-10 11:15:00 +01:00
commit 1a54672fe6
22 changed files with 1271 additions and 11 deletions

View File

@ -1411,7 +1411,8 @@ class backup_block_instance_structure_step extends backup_structure_step {
$block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
'subpagepattern', 'defaultregion', 'defaultweight', 'configdata'));
'subpagepattern', 'defaultregion', 'defaultweight', 'configdata',
'timecreated', 'timemodified'));
$positions = new backup_nested_element('block_positions');

View File

@ -3937,6 +3937,14 @@ class restore_block_instance_structure_step extends restore_structure_step {
$data->configdata = base64_encode(serialize((object)$configdata));
}
// Set timecreated, timemodified if not included (older backup).
if (empty($data->timecreated)) {
$data->timecreated = time();
}
if (empty($data->timemodified)) {
$data->timemodified = $data->timecreated;
}
// Create the block instance
$newitemid = $DB->insert_record('block_instances', $data);
// Save the mapping (with restorefiles support)

View File

@ -464,9 +464,10 @@ class core_backup_moodle2_testcase extends advanced_testcase {
*
* @param stdClass $course Course object to backup
* @param int $newdate If non-zero, specifies custom date for new course
* @param callable|null $inbetween If specified, function that is called before restore
* @return int ID of newly restored course
*/
protected function backup_and_restore($course, $newdate = 0) {
protected function backup_and_restore($course, $newdate = 0, $inbetween = null) {
global $USER, $CFG;
// Turn off file logging, otherwise it can't delete the file (Windows).
@ -481,6 +482,10 @@ class core_backup_moodle2_testcase extends advanced_testcase {
$bc->execute_plan();
$bc->destroy();
if ($inbetween) {
$inbetween($backupid);
}
// Do restore to new course with default settings.
$newcourseid = restore_dbops::create_new_course(
$course->fullname, $course->shortname . '_2', $course->category);
@ -802,4 +807,64 @@ class core_backup_moodle2_testcase extends advanced_testcase {
$enrolment = reset($enrolments);
$this->assertEquals('self', $enrolment->enrol);
}
/**
* Test the block instance time fields (timecreated, timemodified) through a backup and restore.
*/
public function test_block_instance_times_backup() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$generator = $this->getDataGenerator();
// Create course and add HTML block.
$course = $generator->create_course();
$context = context_course::instance($course->id);
$page = new moodle_page();
$page->set_context($context);
$page->set_course($course);
$page->set_pagelayout('standard');
$page->set_pagetype('course-view');
$page->blocks->load_blocks();
$page->blocks->add_block_at_end_of_default_region('html');
// Update (hack in database) timemodified and timecreated to specific values for testing.
$blockdata = $DB->get_record('block_instances',
['blockname' => 'html', 'parentcontextid' => $context->id]);
$originalblockid = $blockdata->id;
$blockdata->timecreated = 12345;
$blockdata->timemodified = 67890;
$DB->update_record('block_instances', $blockdata);
// Do backup and restore.
$newcourseid = $this->backup_and_restore($course);
// Confirm that values were transferred correctly into HTML block on new course.
$newcontext = context_course::instance($newcourseid);
$blockdata = $DB->get_record('block_instances',
['blockname' => 'html', 'parentcontextid' => $newcontext->id]);
$this->assertEquals(12345, $blockdata->timecreated);
$this->assertEquals(67890, $blockdata->timemodified);
// Simulate what happens with an older backup that doesn't have those fields, by removing
// them from the backup before doing a restore.
$before = time();
$newcourseid = $this->backup_and_restore($course, 0, function($backupid) use($originalblockid) {
global $CFG;
$path = $CFG->dataroot . '/temp/backup/' . $backupid . '/course/blocks/html_' .
$originalblockid . '/block.xml';
$xml = file_get_contents($path);
$xml = preg_replace('~<timecreated>.*?</timemodified>~s', '', $xml);
file_put_contents($path, $xml);
});
$after = time();
// The fields not specified should default to current time.
$newcontext = context_course::instance($newcourseid);
$blockdata = $DB->get_record('block_instances',
['blockname' => 'html', 'parentcontextid' => $newcontext->id]);
$this->assertTrue($before <= $blockdata->timecreated && $after >= $blockdata->timecreated);
$this->assertTrue($before <= $blockdata->timemodified && $after >= $blockdata->timemodified);
}
}

View File

@ -0,0 +1,91 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Search area for block_html blocks
*
* @package block_html
* @copyright 2017 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace block_html\search;
use core_search\moodle_recordset;
defined('MOODLE_INTERNAL') || die();
/**
* Search area for block_html blocks
*
* @package block_html
* @copyright 2017 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class content extends \core_search\base_block {
public function get_document($record, $options = array()) {
// Create empty document.
$doc = \core_search\document_factory::instance($record->id,
$this->componentname, $this->areaname);
// Get stdclass object with data from DB.
$data = unserialize(base64_decode($record->configdata));
// Get content.
$content = content_to_text($data->text, $data->format);
$doc->set('content', $content);
if (isset($data->title)) {
// If there is a title, use it as title.
$doc->set('title', content_to_text($data->title, false));
} else {
// If there is no title, use the content text again.
$doc->set('title', shorten_text($content));
}
// Set standard fields.
$doc->set('contextid', $record->contextid);
$doc->set('type', \core_search\manager::TYPE_TEXT);
$doc->set('courseid', $record->courseid);
$doc->set('modified', $record->timemodified);
$doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
// Mark document new if appropriate.
if (isset($options['lastindexedtime']) &&
($options['lastindexedtime'] < $record->timecreated)) {
// If the document was created after the last index time, it must be new.
$doc->set_is_new(true);
}
return $doc;
}
public function uses_file_indexing() {
return true;
}
public function attach_files($document) {
$fs = get_file_storage();
$context = \context::instance_by_id($document->get('contextid'));
$files = $fs->get_area_files($context->id, 'block_html', 'content');
foreach ($files as $file) {
$document->add_stored_file($file);
}
}
}

View File

@ -33,3 +33,4 @@ $string['html:myaddinstance'] = 'Add a new HTML block to Dashboard';
$string['leaveblanktohide'] = 'leave blank to hide the title';
$string['newhtmlblock'] = '(new HTML block)';
$string['pluginname'] = 'HTML';
$string['search:content'] = 'HTML block content';

View File

@ -104,7 +104,8 @@ function block_html_global_db_replace($search, $replace) {
$config = unserialize(base64_decode($instance->configdata));
if (isset($config->text) and is_string($config->text)) {
$config->text = str_replace($search, $replace, $config->text);
$DB->set_field('block_instances', 'configdata', base64_encode(serialize($config)), array('id' => $instance->id));
$DB->update_record('block_instances', ['id' => $instance->id,
'configdata' => base64_encode(serialize($config)), 'timemodified' => time()]);
}
}
$instances->close();

View File

@ -0,0 +1,191 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit test for search indexing.
*
* @package block_html
* @copyright 2017 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace block_html;
defined('MOODLE_INTERNAL') || die();
/**
* Unit test for search indexing.
*
* @package block_html
* @copyright 2017 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class search_content_testcase extends \advanced_testcase {
/**
* Creates an HTML block on a course.
*
* @param \stdClass $course Course object
* @return \block_html Block instance object
*/
protected function create_block($course) {
$page = self::construct_page($course);
$page->blocks->add_block_at_end_of_default_region('html');
// Load the block.
$page = self::construct_page($course);
$page->blocks->load_blocks();
$blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
$block = end($blocks);
return $block;
}
/**
* Constructs a page object for the test course.
*
* @param \stdClass $course Moodle course object
* @return \moodle_page Page object representing course view
*/
protected static function construct_page($course) {
$context = \context_course::instance($course->id);
$page = new \moodle_page();
$page->set_context($context);
$page->set_course($course);
$page->set_pagelayout('standard');
$page->set_pagetype('course-view');
$page->blocks->load_blocks();
return $page;
}
/**
* Tests all functionality in the search area.
*/
public function test_search_area() {
global $CFG, $USER, $DB;
require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
$this->resetAfterTest();
$this->setAdminUser();
// Create course and add HTML block.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$before = time();
$block = $this->create_block($course);
// Change block settings to add some text and a file.
$itemid = file_get_unused_draft_itemid();
$fs = get_file_storage();
$usercontext = \context_user::instance($USER->id);
$fs->create_file_from_string(['component' => 'user', 'filearea' => 'draft',
'contextid' => $usercontext->id, 'itemid' => $itemid, 'filepath' => '/',
'filename' => 'file.txt'], 'File content');
$data = (object)['title' => 'Block title', 'text' => ['text' => 'Block text',
'itemid' => $itemid, 'format' => FORMAT_HTML]];
$block->instance_config_save($data);
$after = time();
// Set up fake search engine so we can create documents.
\testable_core_search::instance();
// Do indexing query.
$area = new \block_html\search\content();
$this->assertEquals('html', $area->get_block_name());
$rs = $area->get_recordset_by_timestamp();
$count = 0;
foreach ($rs as $record) {
$count++;
$this->assertEquals($course->id, $record->courseid);
// Check context is correct.
$blockcontext = \context::instance_by_id($record->contextid);
$this->assertInstanceOf('\context_block', $blockcontext);
$coursecontext = $blockcontext->get_parent_context();
$this->assertEquals($course->id, $coursecontext->instanceid);
// Check created and modified times are correct.
$this->assertTrue($record->timecreated >= $before && $record->timecreated <= $after);
$this->assertTrue($record->timemodified >= $before && $record->timemodified <= $after);
// Get config data.
$data = unserialize(base64_decode($record->configdata));
$this->assertEquals('Block title', $data->title);
$this->assertEquals('Block text', $data->text);
$this->assertEquals(FORMAT_HTML, $data->format);
// Check the get_document function 'new' flag.
$doc = $area->get_document($record, ['lastindexedtime' => 1]);
$this->assertTrue($doc->get_is_new());
$doc = $area->get_document($record, ['lastindexedtime' => time() + 1]);
$this->assertFalse($doc->get_is_new());
// Check the attach_files function results in correct list of associated files.
$this->assertCount(0, $doc->get_files());
$area->attach_files($doc);
$files = $doc->get_files();
$this->assertCount(2, $files);
foreach ($files as $file) {
if ($file->is_directory()) {
continue;
}
$this->assertEquals('file.txt', $file->get_filename());
$this->assertEquals('File content', $file->get_content());
}
// Check the document fields are all as expected.
$this->assertEquals('Block title', $doc->get('title'));
$this->assertEquals('Block text', $doc->get('content'));
$this->assertEquals($blockcontext->id, $doc->get('contextid'));
$this->assertEquals(\core_search\manager::TYPE_TEXT, $doc->get('type'));
$this->assertEquals($course->id, $doc->get('courseid'));
$this->assertEquals($record->timemodified, $doc->get('modified'));
$this->assertEquals(\core_search\manager::NO_OWNER_ID, $doc->get('owneruserid'));
// Also check getting the doc url and context url.
$url = new \moodle_url('/course/view.php', ['id' => $course->id], 'inst' . $record->id);
$this->assertTrue($url->compare($area->get_doc_url($doc)));
$this->assertTrue($url->compare($area->get_context_url($doc)));
}
$rs->close();
// Should only be one HTML block systemwide.
$this->assertEquals(1, $count);
// If we run the query starting from 1 second after now, there should be no results.
$rs = $area->get_recordset_by_timestamp($after + 1);
$count = 0;
foreach ($rs as $record) {
$count++;
}
$rs->close();
$this->assertEquals(0, $count);
// Create another block, but this time leave it empty (no data set). Hack the time though.
$block = $this->create_block($course);
$DB->set_field('block_instances', 'timemodified',
$after + 10, ['id' => $block->instance->id]);
$rs = $area->get_recordset_by_timestamp($after + 10);
$count = 0;
foreach ($rs as $record) {
// Because there is no configdata we don't index it.
$count++;
}
$rs->close();
$this->assertEquals(0, $count);
}
}

View File

@ -474,8 +474,8 @@ class block_base {
*/
function instance_config_save($data, $nolongerused = false) {
global $DB;
$DB->set_field('block_instances', 'configdata', base64_encode(serialize($data)),
array('id' => $this->instance->id));
$DB->update_record('block_instances', ['id' => $this->instance->id,
'configdata' => base64_encode(serialize($data)), 'timemodified' => time()]);
}
/**

View File

@ -1,6 +1,15 @@
This files describes API changes in /blocks/* - activity modules,
information provided here is intended especially for developers.
=== 3.4 ===
* 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 ===
* block_manager::get_required_by_theme_block_types() is no longer static.

View File

@ -833,6 +833,8 @@ class block_manager {
$blockinstance->defaultregion = $region;
$blockinstance->defaultweight = $weight;
$blockinstance->configdata = '';
$blockinstance->timecreated = time();
$blockinstance->timemodified = $blockinstance->timecreated;
$blockinstance->id = $DB->insert_record('block_instances', $blockinstance);
// Ensure the block context is created.
@ -940,6 +942,7 @@ class block_manager {
$newbi->id = $bi->id;
$newbi->defaultregion = $newregion;
$newbi->defaultweight = $newweight;
$newbi->timemodified = time();
$DB->update_record('block_instances', $newbi);
if ($bi->blockpositionid) {
@ -1162,6 +1165,8 @@ class block_manager {
$blockinstance->defaultregion = $defaultregion;
$blockinstance->defaultweight = 0;
$blockinstance->configdata = '';
$blockinstance->timecreated = time();
$blockinstance->timemodified = $blockinstance->timecreated;
$blockinstance->id = $DB->insert_record('block_instances', $blockinstance);
// Ensure the block context is created.
@ -1723,6 +1728,7 @@ class block_manager {
$bi->defaultregion = $data->bui_defaultregion;
$bi->defaultweight = $data->bui_defaultweight;
$bi->timemodified = time();
$DB->update_record('block_instances', $bi);
if (!empty($block->config)) {

View File

@ -2493,6 +2493,8 @@
<FIELD NAME="defaultregion" TYPE="char" LENGTH="16" NOTNULL="true" SEQUENCE="false" COMMENT="Which block region this block should appear in on each page, in the absence of a specific position in the block_positions table."/>
<FIELD NAME="defaultweight" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Used to order the blocks within a block region. Again, may be overridden by the block_positions table for a specific page where this block appears."/>
<FIELD NAME="configdata" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="A serialized blob of configuration data for this block instance."/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time at which this block instance was originally created"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time at which block instance was last modified."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
@ -2500,6 +2502,7 @@
</KEYS>
<INDEXES>
<INDEX NAME="parentcontextid-showinsubcontexts-pagetypepattern-subpagepattern" UNIQUE="false" FIELDS="parentcontextid, showinsubcontexts, pagetypepattern, subpagepattern"/>
<INDEX NAME="timemodified" UNIQUE="false" FIELDS="timemodified"/>
</INDEXES>
</TABLE>
<TABLE NAME="block_positions" COMMENT="Stores the position of a sticky block_instance on a another page than the one where it was added.">

View File

@ -2902,5 +2902,58 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2017071000.00);
}
if ($oldversion < 2017071001.00) {
// Define field timemodified to be added to block_instances.
$table = new xmldb_table('block_instances');
$field = new xmldb_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, null,
null, null, 'configdata');
// Conditionally launch add field timemodified.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
// Set field to current time.
$DB->set_field('block_instances', 'timemodified', time());
// Changing nullability of field timemodified on table block_instances to not null.
$field = new xmldb_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL,
null, null, 'configdata');
// Launch change of nullability for field timemodified.
$dbman->change_field_notnull($table, $field);
// Define index timemodified (not unique) to be added to block_instances.
$index = new xmldb_index('timemodified', XMLDB_INDEX_NOTUNIQUE, array('timemodified'));
// Conditionally launch add index timemodified.
if (!$dbman->index_exists($table, $index)) {
$dbman->add_index($table, $index);
}
}
// Define field timecreated to be added to block_instances.
$field = new xmldb_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, null,
null, null, 'configdata');
// Conditionally launch add field timecreated.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
// Set field to current time.
$DB->set_field('block_instances', 'timecreated', time());
// Changing nullability of field timecreated on table block_instances to not null.
$field = new xmldb_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL,
null, null, 'configdata');
// Launch change of nullability for field timecreated.
$dbman->change_field_notnull($table, $field);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2017071001.00);
}
return true;
}

View File

@ -122,6 +122,13 @@ abstract class testing_block_generator extends component_generator_base {
$this->preprocess_record($record, $options);
$record = $this->prepare_record($record);
if (empty($record->timecreated)) {
$record->timecreated = time();
}
if (empty($record->timemodified)) {
$record->timemodified = time();
}
$id = $DB->insert_record('block_instances', $record);
context_block::instance($id);

View File

@ -622,6 +622,72 @@ class core_blocklib_testcase extends advanced_testcase {
}
}
/**
* Test the block instance time fields (timecreated, timemodified).
*/
public function test_block_instance_times() {
global $DB;
$this->purge_blocks();
// Set up fixture.
$regionname = 'a-region';
$blockname = 'html';
$context = context_system::instance();
list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname),
$context, 'page-type');
// Add block to page.
$before = time();
$blockmanager->add_block($blockname, $regionname, 0, false);
$after = time();
// Check database table to ensure it contains created/modified times.
$blockdata = $DB->get_record('block_instances', ['blockname' => 'html']);
$this->assertTrue($blockdata->timemodified >= $before && $blockdata->timemodified <= $after);
$this->assertTrue($blockdata->timecreated >= $before && $blockdata->timecreated <= $after);
// Get block from manager.
$blockmanager->load_blocks();
$blocks = $blockmanager->get_blocks_for_region($regionname);
$block = reset($blocks);
// Wait until at least the next second.
while (time() === $after) {
usleep(100000);
}
// Update block settings.
$this->setAdminUser();
$data = (object)['text' => ['text' => 'New text', 'itemid' => 0, 'format' => FORMAT_HTML]];
$before = time();
$block->instance_config_save($data);
$after = time();
// Check time modified updated, but time created didn't.
$newblockdata = $DB->get_record('block_instances', ['blockname' => 'html']);
$this->assertTrue(
$newblockdata->timemodified >= $before &&
$newblockdata->timemodified <= $after &&
$newblockdata->timemodified > $blockdata->timemodified);
$this->assertEquals($blockdata->timecreated, $newblockdata->timecreated);
// Also try repositioning the block.
while (time() === $after) {
usleep(100000);
}
$before = time();
$blockmanager->reposition_block($blockdata->id, $regionname, 10);
$after = time();
$blockdata = $newblockdata;
$newblockdata = $DB->get_record('block_instances', ['blockname' => 'html']);
$this->assertTrue(
$newblockdata->timemodified >= $before &&
$newblockdata->timemodified <= $after &&
$newblockdata->timemodified > $blockdata->timemodified);
$this->assertEquals($blockdata->timecreated, $newblockdata->timecreated);
}
}
/**

View File

@ -86,6 +86,8 @@ function my_copy_page($userid, $private=MY_PAGE_PRIVATE, $pagetype='my-index') {
unset($instance->id);
$instance->parentcontextid = $usercontext->id;
$instance->subpagepattern = $page->id;
$instance->timecreated = time();
$instance->timemodified = $instance->timecreated;
$instance->id = $DB->insert_record('block_instances', $instance);
$newblockinstanceids[$originalid] = $instance->id;
$blockcontext = context_block::instance($instance->id); // Just creates the context record

View File

@ -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);

View File

@ -0,0 +1,243 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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();
}
}

View File

@ -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;
}

View File

@ -0,0 +1,311 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}

View File

@ -0,0 +1,40 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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');
}
}

View File

@ -254,6 +254,105 @@ class search_manager_testcase extends advanced_testcase {
$this->assertEquals(array($course1ctx->id => $course1ctx->id), $contexts[$this->mycoursesareaid]);
}
/**
* Tests the block support in get_search_user_accesses.
*
* @return void
*/
public function test_search_user_accesses_blocks() {
$this->resetAfterTest();
$this->setAdminUser();
// Create course and add HTML block.
$generator = $this->getDataGenerator();
$course1 = $generator->create_course();
$context1 = \context_course::instance($course1->id);
$page = new \moodle_page();
$page->set_context($context1);
$page->set_course($course1);
$page->set_pagelayout('standard');
$page->set_pagetype('course-view');
$page->blocks->load_blocks();
$page->blocks->add_block_at_end_of_default_region('html');
// Create another course with HTML blocks only in some weird page or a module page (not
// yet supported, so both these blocks will be ignored).
$course2 = $generator->create_course();
$context2 = \context_course::instance($course2->id);
$page = new \moodle_page();
$page->set_context($context2);
$page->set_course($course2);
$page->set_pagelayout('standard');
$page->set_pagetype('bogus-page');
$page->blocks->load_blocks();
$page->blocks->add_block_at_end_of_default_region('html');
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
$forumcontext = context_module::instance($forum->cmid);
$page = new \moodle_page();
$page->set_context($forumcontext);
$page->set_course($course2);
$page->set_pagelayout('standard');
$page->set_pagetype('mod-forum-view');
$page->blocks->load_blocks();
$page->blocks->add_block_at_end_of_default_region('html');
// The third course has 2 HTML blocks.
$course3 = $generator->create_course();
$context3 = \context_course::instance($course3->id);
$page = new \moodle_page();
$page->set_context($context3);
$page->set_course($course3);
$page->set_pagelayout('standard');
$page->set_pagetype('course-view');
$page->blocks->load_blocks();
$page->blocks->add_block_at_end_of_default_region('html');
$page->blocks->add_block_at_end_of_default_region('html');
// Student 1 belongs to all 3 courses.
$student1 = $generator->create_user();
$generator->enrol_user($student1->id, $course1->id, 'student');
$generator->enrol_user($student1->id, $course2->id, 'student');
$generator->enrol_user($student1->id, $course3->id, 'student');
// Student 2 belongs only to course 2.
$student2 = $generator->create_user();
$generator->enrol_user($student2->id, $course2->id, 'student');
// And the third student is only in course 3.
$student3 = $generator->create_user();
$generator->enrol_user($student3->id, $course3->id, 'student');
$search = testable_core_search::instance();
$search->add_core_search_areas();
// Admin gets 'true' result to function regardless of blocks.
$this->setAdminUser();
$this->assertTrue($search->get_areas_user_accesses());
// Student 1 gets all 3 block contexts.
$this->setUser($student1);
$contexts = $search->get_areas_user_accesses();
$this->assertArrayHasKey('block_html-content', $contexts);
$this->assertCount(3, $contexts['block_html-content']);
// Student 2 does not get any blocks.
$this->setUser($student2);
$contexts = $search->get_areas_user_accesses();
$this->assertArrayNotHasKey('block_html-content', $contexts);
// Student 3 gets only two of them.
$this->setUser($student3);
$contexts = $search->get_areas_user_accesses();
$this->assertArrayHasKey('block_html-content', $contexts);
$this->assertCount(2, $contexts['block_html-content']);
// A course limited search for student 1 is the same as the student 3 search.
$this->setUser($student1);
$limitedcontexts = $search->get_areas_user_accesses([$course3->id]);
$this->assertEquals($contexts['block_html-content'], $limitedcontexts['block_html-content']);
}
/**
* test_is_search_area
*

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2017071000.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2017071001.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.