diff --git a/lib/blocklib.php b/lib/blocklib.php index 0cd4c4610a8..04740029c0b 100644 --- a/lib/blocklib.php +++ b/lib/blocklib.php @@ -234,12 +234,18 @@ class block_manager { return false; } + $undeletableblocks = self::get_undeletable_block_types(); foreach ($this->blockinstances as $region) { foreach ($region as $instance) { if (empty($instance->instance->blockname)) { continue; } if ($instance->instance->blockname == $blockname) { + if ($instance->instance->requiredbytheme) { + if (!in_array($block->name, $undeletableblocks)) { + continue; + } + } return true; } } @@ -579,6 +585,20 @@ class block_manager { return; } + // Exclude auto created blocks if they are not undeletable in this theme. + $undeletable = $this->get_undeletable_block_types(); + $undeletablecheck = ''; + $undeletableparams = array(); + $undeletablenotparams = array(); + if (!empty($undeletable)) { + list($testsql, $undeletableparams) = $DB->get_in_or_equal($undeletable, SQL_PARAMS_NAMED, 'undeletable'); + list($testnotsql, $undeletablenotparams) = $DB->get_in_or_equal($undeletable, SQL_PARAMS_NAMED, 'deletable', false); + $undeletablecheck = 'AND ((bi.blockname ' . $testsql . ' AND bi.requiredbytheme = 1) OR ' . + ' (bi.blockname ' . $testnotsql . ' AND bi.requiredbytheme = 0))'; + } else { + $undeletablecheck = 'AND (bi.requiredbytheme = 0)'; + } + if (is_null($includeinvisible)) { $includeinvisible = $this->page->user_is_editing(); } @@ -626,6 +646,7 @@ class block_manager { bi.parentcontextid, bi.showinsubcontexts, bi.pagetypepattern, + bi.requiredbytheme, bi.subpagepattern, bi.defaultregion, bi.defaultweight, @@ -649,12 +670,15 @@ class block_manager { AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2) $visiblecheck AND b.visible = 1 + $undeletablecheck ORDER BY COALESCE(bp.region, bi.defaultregion), COALESCE(bp.weight, bi.defaultweight), bi.id"; - $blockinstances = $DB->get_recordset_sql($sql, $params + $parentcontextparams + $pagetypepatternparams); + + $allparams = $params + $parentcontextparams + $pagetypepatternparams + $undeletableparams + $undeletablenotparams; + $blockinstances = $DB->get_recordset_sql($sql, $allparams); $this->birecordsbyregion = $this->prepare_per_region_arrays(); $unknown = array(); @@ -957,6 +981,10 @@ class block_manager { * load_blocks. This is used, for example, to ensure that all blocks get a * chance to initialise themselves via the {@link block_base::specialize()} * method, before any output is done. + * + * It is also used to create any blocks that are "undeletable" by the current theme. + * These blocks that are auto-created have requiredbytheme set on the block instance + * so they are only visible on themes that require them. */ public function create_all_block_instances() { global $PAGE; @@ -977,7 +1005,7 @@ class block_manager { } } if (!$found) { - $this->add_block_at_end_of_default_region($forced); + $this->add_block_required_by_theme($forced); $missing = true; } } @@ -993,6 +1021,45 @@ class block_manager { } + /** + * Add a block that is required by the current theme but has not been + * created yet. This is a special type of block that only shows in themes that + * require it (by listing it in undeletable_block_types). + * + * @param string $blockname the name of the block type. + */ + protected function add_block_required_by_theme($blockname) { + global $DB; + + if (empty($this->birecordsbyregion)) { + // No blocks or block regions exist yet. + return; + } + + $systemcontext = context_system::instance(); + $defaultregion = $this->get_default_region(); + // Add a special system wide block instance only for themes that require it. + $blockinstance = new stdClass; + $blockinstance->blockname = $blockname; + $blockinstance->parentcontextid = $systemcontext->id; + $blockinstance->showinsubcontexts = true; + $blockinstance->requiredbytheme = true; + $blockinstance->pagetypepattern = '*'; + $blockinstance->subpagepattern = null; + $blockinstance->defaultregion = $defaultregion; + $blockinstance->defaultweight = 0; + $blockinstance->configdata = ''; + $blockinstance->id = $DB->insert_record('block_instances', $blockinstance); + + // Ensure the block context is created. + context_block::instance($blockinstance->id); + + // If the new instance was created, allow it to do additional setup. + if ($block = block_instance($blockname, $blockinstance)) { + $block->instance_create(); + } + } + /** * Return an array of content objects from a set of block instances * diff --git a/lib/db/install.xml b/lib/db/install.xml old mode 100644 new mode 100755 index 39bbd58abc4..a26be81259d --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<XMLDB PATH="lib/db" VERSION="20160804" COMMENT="XMLDB file for core Moodle tables" +<XMLDB PATH="lib/db" VERSION="20161119" COMMENT="XMLDB file for core Moodle tables" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd" > @@ -2459,6 +2459,7 @@ <FIELD NAME="blockname" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false" COMMENT="The type of block this is. Foreign key, references block.name."/> <FIELD NAME="parentcontextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The context within which this block appears. Foreign key, references context.id."/> <FIELD NAME="showinsubcontexts" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false" COMMENT="If 1, this block appears on all matching pages in subcontexts of parentcontextid, as well in pages belonging to parentcontextid."/> + <FIELD NAME="requiredbytheme" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If 1, this block was created because it was required by the theme and did not exist."/> <FIELD NAME="pagetypepattern" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="The types of page this block appears on. Either an exact page type like mod-quiz-view, or a pattern like mod-quiz-* or course-view-*. Note that course-view-* will match course-view."/> <FIELD NAME="subpagepattern" TYPE="char" LENGTH="16" NOTNULL="false" SEQUENCE="false" COMMENT="Further restrictions on where this block appears. In some places, e.g. during a quiz or lesson attempt, different pages have different subpage ids. If this field is not null, the block only appears on that particular subpage."/> <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."/> diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 9d68867c165..cd9f0f06465 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2406,5 +2406,32 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2016110600.00); } + if ($oldversion < 2016112200.01) { + + // Define field requiredbytheme to be added to block_instances. + $table = new xmldb_table('block_instances'); + $field = new xmldb_field('requiredbytheme', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'showinsubcontexts'); + + // Conditionally launch add field requiredbytheme. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2016112200.01); + } + if ($oldversion < 2016112200.02) { + + // Change the existing site level admin and settings blocks to be requiredbytheme which means they won't show in boost. + $context = context_system::instance(); + $params = array('blockname' => 'settings', 'parentcontextid' => $context->id); + $DB->set_field('block_instances', 'requiredbytheme', 1, $params); + + $params = array('blockname' => 'navigation', 'parentcontextid' => $context->id); + $DB->set_field('block_instances', 'requiredbytheme', 1, $params); + // Main savepoint reached. + upgrade_main_savepoint(true, 2016112200.02); + } + return true; } diff --git a/lib/tests/blocklib_test.php b/lib/tests/blocklib_test.php index 533eeaa7770..a7be723750c 100644 --- a/lib/tests/blocklib_test.php +++ b/lib/tests/blocklib_test.php @@ -542,6 +542,58 @@ class core_blocklib_testcase extends advanced_testcase { context_block::instance($tokeep); // Would throw an exception if it was deleted. } + public function test_create_all_block_instances() { + global $CFG, $PAGE, $DB; + + $this->resetAfterTest(); + $regionname = 'side-pre'; + $context = context_system::instance(); + + $PAGE->reset_theme_and_output(); + $CFG->theme = 'boost'; + + list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname), + $context, 'page-type'); + $blockmanager->load_blocks(); + $blockmanager->create_all_block_instances(); + $blocks = $blockmanager->get_blocks_for_region($regionname); + $this->assertEmpty($blocks); + // There should be no blocks in the DB. + + $PAGE->reset_theme_and_output(); + // Change to a theme with undeletable blocks. + $CFG->theme = 'clean'; + + list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname), + $context, 'page-type'); + $blockmanager->load_blocks(); + $blockmanager->create_all_block_instances(); + $blocks = $blockmanager->get_blocks_for_region($regionname); + $this->assertCount(2, $blocks); + + $undeletable = block_manager::get_undeletable_block_types(); + foreach ($undeletable as $blockname) { + $instance = $DB->get_record('block_instances', array('blockname' => $blockname)); + $this->assertEquals(1, $instance->requiredbytheme); + } + + // Switch back and those auto blocks should not be returned. + $PAGE->reset_theme_and_output(); + $CFG->theme = 'boost'; + + list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname), + $context, 'page-type'); + $blockmanager->load_blocks(); + $blockmanager->create_all_block_instances(); + $blocks = $blockmanager->get_blocks_for_region($regionname); + $this->assertEmpty($blocks); + // But they should exist in the DB. + foreach ($undeletable as $blockname) { + $count = $DB->count_records('block_instances', array('blockname' => $blockname)); + $this->assertEquals(1, $count); + } + } + } /** diff --git a/version.php b/version.php index 17b2534a169..d4b078799ad 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2016112200.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2016112200.02; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.