diff --git a/config-dist.php b/config-dist.php index 6f2c092cc31..8e66e42b3d6 100644 --- a/config-dist.php +++ b/config-dist.php @@ -167,6 +167,13 @@ $CFG->admin = 'admin'; // These blocks are used when no other default setting is found. // $CFG->defaultblocks = 'participants,activity_modules,search_forums,admin,course_list:news_items,calendar_upcoming,recent_activity'; // +// You can specify a different class to be created for the $PAGE global, and to +// compute which blocks appear on each page. However, I cannot think of any good +// reason why you would need to change that. It just felt wrong to hard-code the +// the class name. You are stronly advised not to use these to settings unless +// you are absolutely sure you know what you are doing. +// $CFG->moodlepageclass = 'moodle_page'; +// $CFG->blockmanagerclass = 'block_manager'; // // Seconds for files to remain in caches. Decrease this if you are worried // about students being served outdated versions of uploaded files. diff --git a/lib/blocklib.php b/lib/blocklib.php index cf8120b0131..13e01ad593b 100644 --- a/lib/blocklib.php +++ b/lib/blocklib.php @@ -45,6 +45,109 @@ define('BLOCKS_PINNED_BOTH',2); require_once($CFG->libdir.'/pagelib.php'); +/** + * This class keeps track of the block that should appear on a moodle_page. + * The page to work with as passed to the constructor. + * The only fields of moodle_page that is uses are ->context, ->pagetype and + * ->subpage, so instead of passing a full moodle_page object, you may also + * pass a stdClass object with those three fields. These field values are read + * only at the point that the load_blocks() method is called. It is the caller's + * responsibility to ensure that those fields do not subsequently change. + */ +class block_manager { + /**#@+ Tracks the where we are in the generation of the page. */ + const STATE_BLOCKS_NOT_LOADED = 0; + const STATE_BLOCKS_LOADED = 1; + /**#@-*/ + +/// Field declarations ========================================================= + + protected $loaded = self::STATE_BLOCKS_NOT_LOADED; + + protected $page; + + protected $regions = array(); + + protected $defaultregion; + +/// Constructor ================================================================ + + /** + * Constructor. + * @param object $page the moodle_page object object we are managing the blocks for, + * or a reasonable faxilimily. (See the comment at the top of this classe + * and http://en.wikipedia.org/wiki/Duck_typing) + */ + public function __construct($page) { + $this->page = $page; + } + +/// Getter methods ============================================================= + + /** + * @return array the internal names of the regions on this page where block may appear. + */ + public function get_regions() { + return array_keys($this->regions); + } + + /** + * @return string the internal names of the region where new blocks are added + * by default, and where any blocks from an unrecognised region are shown. + * (Imagine that blocks were added with one theme selected, then you switched + * to a theme with different block positions.) + */ + public function get_default_region() { + return $this->defaultregion; + } + +/// Setter methods ============================================================= + + /** + * @param string $region add a named region where blocks may appear on the + * current page. This is an internal name, like 'side-pre', not a string to + * display in the UI. + */ + public function add_region($region) { + $this->check_not_yet_loaded(); + $this->regions[$region] = 1; + } + + /** + * @param array $regions this utility method calls add_region for each array element. + */ + public function add_regions($regions) { + foreach ($regions as $region) { + $this->add_region($region); + } + } + + /** + * @param string $defaultregion the internal names of the region where new + * blocks should be added by default, and where any blocks from an + * unrecognised region are shown. + */ + public function set_default_region($defaultregion) { + $this->check_not_yet_loaded(); + if (!array_key_exists($defaultregion, $this->regions)) { + throw new coding_exception('Trying to set an unknown block region as the default.'); + } + $this->defaultregion = $defaultregion; + } + +/// Inner workings ============================================================= + + protected function check_not_yet_loaded() { + if ($this->loaded) { + throw new coding_exception('block_manager has already loaded the blocks, to it is too late to change things that might affect which blocks are visible.'); + } + } + + protected function mark_loaded() { + $this->loaded = self::STATE_BLOCKS_LOADED; + } +} + //This function retrieves a method-defined property of a class WITHOUT instantiating an object function block_method_result($blockname, $method, $param = NULL) { if(!block_load_class($blockname)) { @@ -329,7 +432,7 @@ function blocks_print_group(&$page, &$pageblocks, $position) { $managecourseblocks = has_capability('moodle/site:manageblocks', $coursecontext); $editmymoodle = $page->pagetype == PAGE_MY_MOODLE && has_capability('moodle/my:manageblocks', $coursecontext); - if ($page->blocks->get_default_position() == $position && + if ($page->blocks->get_default_region() == $position && $page->user_is_editing() && ($managecourseblocks || $editmymoodle || $myownblogpage || defined('ADMIN_STICKYBLOCKS'))) { @@ -639,7 +742,7 @@ function blocks_execute_action($page, &$pageblocks, $blockaction, $instanceorid, break; } - $newpos = $page->blocks->get_default_position(); + $newpos = $page->blocks->get_default_region(); if (!empty($pinned)) { $sql = "SELECT 1, MAX(weight) + 1 AS nextfree FROM {block_pinned_old} @@ -835,7 +938,7 @@ function blocks_get_pinned($page) { $blocks = $DB->get_records_select('block_pinned_old', $select, $params, 'position, weight'); - $positions = $page->blocks->get_positions(); + $positions = $page->blocks->get_regions(); $arr = array(); foreach($positions as $key => $position) { @@ -897,7 +1000,7 @@ function blocks_get_by_page($page) { $blocks = $DB->get_records_select('block_instance_old', "pageid = ? AND ? LIKE (" . $DB->sql_concat('pagetype', "'%'") . ")", array($page->get_id(), $page->pagetype), 'position, weight'); - $positions = $page->blocks->get_positions(); + $positions = $page->blocks->get_regions(); $arr = array(); foreach($positions as $key => $position) { $arr[$position] = array(); @@ -986,7 +1089,7 @@ function blocks_repopulate_page($page) { $blocknames = $page->blocks_get_default(); } - $positions = $page->blocks->get_positions(); + $positions = $page->blocks->get_regions(); $posblocks = explode(':', $blocknames); // Now one array holds the names of the positions, and the other one holds the blocks diff --git a/lib/pagelib.php b/lib/pagelib.php index 0fdb40dd5ed..c2551e9f29e 100644 --- a/lib/pagelib.php +++ b/lib/pagelib.php @@ -268,8 +268,14 @@ class moodle_page { * @return blocks_manager the blocks manager object for this page. */ public function get_blocks() { + global $CFG; if (is_null($this->_blocks)) { - $this->_blocks = new blocks_manager(); + if (!empty($CFG->blockmanagerclass)) { + $classname = $CFG->blockmanagerclass; + } else { + $classname = 'block_manager'; + } + $this->_blocks = new $classname($this); } return $this->_blocks; } @@ -745,21 +751,21 @@ class moodle_page { } /** - * @deprecated since Moodle 2.0 - use $PAGE->blocks->get_positions() instead + * @deprecated since Moodle 2.0 - use $PAGE->blocks->get_regions() instead * @return string the places on this page where blocks can go. */ function blocks_get_positions() { - debugging('Call to deprecated method moodle_page::blocks_get_positions. Use $PAGE->blocks->get_positions() instead.'); - return $this->blocks->get_positions(); + debugging('Call to deprecated method moodle_page::blocks_get_positions. Use $PAGE->blocks->get_regions() instead.'); + return $this->blocks->get_regions(); } /** - * @deprecated since Moodle 2.0 - use $PAGE->blocks->get_default_position() instead + * @deprecated since Moodle 2.0 - use $PAGE->blocks->get_default_region() instead * @return string the default place for blocks on this page. */ function blocks_default_position() { - debugging('Call to deprecated method moodle_page::blocks_default_position. Use $PAGE->blocks->get_default_position() instead.'); - return $this->blocks->get_default_position(); + debugging('Call to deprecated method moodle_page::blocks_default_position. Use $PAGE->blocks->get_default_region() instead.'); + return $this->blocks->get_default_region(); } /** @@ -856,17 +862,6 @@ class moodle_page { } } -/** Stub implementation of the blocks_manager, to stop things from breaking too badly. */ -class blocks_manager { - public function get_positions() { - return array(BLOCK_POS_LEFT, BLOCK_POS_RIGHT); - } - - public function get_default_position() { - return BLOCK_POS_RIGHT; - } -} - /** * @deprecated since Moodle 2.0 * Not needed any more. diff --git a/lib/setup.php b/lib/setup.php index 63790b75c94..26dd952115d 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -275,7 +275,12 @@ global $SCRIPT; } /// Create the $PAGE global. - $PAGE = new moodle_page(); + if (!empty($CFG->moodlepageclass)) { + $classname = $CFG->moodlepageclass; + } else { + $classname = 'moodle_page'; + } + $PAGE = new $classname(); /// Set error reporting back to normal if ($originaldatabasedebug == -1) { diff --git a/lib/simpletest/testblocklib_blockmanager.php b/lib/simpletest/testblocklib_blockmanager.php new file mode 100644 index 00000000000..5cb08c55c0f --- /dev/null +++ b/lib/simpletest/testblocklib_blockmanager.php @@ -0,0 +1,130 @@ +libdir . '/pagelib.php'); +require_once($CFG->libdir . '/blocklib.php'); + +/** Test-specific subclass to make some protected things public. */ +class testable_block_manager extends block_manager { + public function mark_loaded() { + parent::mark_loaded(); + } +} + +/** + * Test functions that don't need to touch the database. + */ +class moodle_page_test extends UnitTestCase { + protected $testpage; + protected $blockmanager; + + public function setUp() { + $this->testpage = new moodle_page(); + $this->blockmanager = new testable_block_manager($this->testpage); + } + + public function tearDown() { + $this->testpage = NULL; + $this->blockmanager = NULL; + } + + public function test_no_regions_initially() { + // Exercise SUT & Validate + $this->assertEqual(array(), $this->blockmanager->get_regions()); + } + + public function test_add_region() { + // Exercise SUT. + $this->blockmanager->add_region('a-region-name'); + // Validate + $this->assertEqual(array('a-region-name'), $this->blockmanager->get_regions()); + } + + public function test_add_regions() { + // Set up fixture. + $regions = array('a-region', 'another-region'); + // Exercise SUT. + $this->blockmanager->add_regions($regions); + // Validate + $this->assert(new ArraysHaveSameValuesExpectation($regions), $this->blockmanager->get_regions()); + } + + public function test_add_region_twice() { + // Exercise SUT. + $this->blockmanager->add_region('a-region-name'); + $this->blockmanager->add_region('another-region'); + // Validate + $this->assert(new ArraysHaveSameValuesExpectation(array('a-region-name', 'another-region')), + $this->blockmanager->get_regions()); + } + + public function test_cannot_add_region_after_loaded() { + // Set up fixture. + $this->blockmanager->mark_loaded(); + // Set expectation + $this->expectException(); + // Exercise SUT. + $this->blockmanager->add_region('too-late'); + } + + public function test_set_default_region() { + // Set up fixture. + $this->blockmanager->add_region('a-region-name'); + // Exercise SUT. + $this->blockmanager->set_default_region('a-region-name'); + // Validate + $this->assertEqual('a-region-name', $this->blockmanager->get_default_region()); + } + + public function test_cannot_set_unknown_region_as_default() { + // Set expectation + $this->expectException(); + // Exercise SUT. + $this->blockmanager->set_default_region('a-region-name'); + } + + public function test_cannot_change_default_region_after_loaded() { + // Set up fixture. + $this->blockmanager->mark_loaded(); + // Set expectation + $this->expectException(); + // Exercise SUT. + $this->blockmanager->set_default_region('too-late'); + } + +} + +?>