Rev 1 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?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/>./*** Block Class and Functions** This file defines the {@link block_manager} class,** @package core* @subpackage block* @copyright 1999 onwards Martin Dougiamas http://dougiamas.com* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/defined('MOODLE_INTERNAL') || die();/**#@+* Default names for the block regions in the standard theme.*/define('BLOCK_POS_LEFT', 'side-pre');define('BLOCK_POS_RIGHT', 'side-post');/**#@-*/define('BUI_CONTEXTS_FRONTPAGE_ONLY', 0);define('BUI_CONTEXTS_FRONTPAGE_SUBS', 1);define('BUI_CONTEXTS_ENTIRE_SITE', 2);define('BUI_CONTEXTS_CURRENT', 0);define('BUI_CONTEXTS_CURRENT_SUBS', 1);// Position of "Add block" control, to be used in theme config as a value for $THEME->addblockposition:// - default: as a fake block that is displayed in editing mode// - flatnav: "Add block" item in the flat navigation drawer in editing mode// - custom: none of the above, theme will take care of displaying the control.define('BLOCK_ADDBLOCK_POSITION_DEFAULT', 0);define('BLOCK_ADDBLOCK_POSITION_FLATNAV', 1);define('BLOCK_ADDBLOCK_POSITION_CUSTOM', -1);/*** Exception thrown when someone tried to do something with a block that does* not exist on a page.** @copyright 2009 Tim Hunt* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later* @since Moodle 2.0*/class block_not_on_page_exception extends moodle_exception {/*** Constructor* @param int $instanceid the block instance id of the block that was looked for.* @param object $page the current page.*/public function __construct($instanceid, $page) {$a = new stdClass;$a->instanceid = $instanceid;$a->url = $page->url->out();parent::__construct('blockdoesnotexistonpage', '', $page->url->out(), $a);}}/*** This class keeps track of the block that should appear on a moodle_page.** The page to work with as passed to the constructor.** @copyright 2009 Tim Hunt* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later* @since Moodle 2.0*/class block_manager {/*** The UI normally only shows block weights between -MAX_WEIGHT and MAX_WEIGHT,* although other weights are valid.*/const MAX_WEIGHT = 10;/// Field declarations =========================================================/*** the moodle_page we are managing blocks for.* @var moodle_page*/protected $page;/** @var array region name => 1.*/protected $regions = array();/** @var string the region where new blocks are added.*/protected $defaultregion = null;/** @var array will be $DB->get_records('blocks') */protected $allblocks = null;/*** @var array blocks that this user can add to this page. Will be a subset* of $allblocks, but with array keys block->name. Access this via the* {@link get_addable_blocks()} method to ensure it is lazy-loaded.*/protected $addableblocks = null;/*** Will be an array region-name => array(db rows loaded in load_blocks);* @var array*/protected $birecordsbyregion = null;/*** array region-name => array(block objects); populated as necessary by* the ensure_instances_exist method.* @var array*/protected $blockinstances = array();/*** array region-name => array(block_contents objects) what actually needs to* be displayed in each region.* @var array*/protected $visibleblockcontent = array();/*** array region-name => array(block_contents objects) extra block-like things* to be displayed in each region, before the real blocks.* @var array*/protected $extracontent = array();/*** Used by the block move id, to track whether a block is currently being moved.** When you click on the move icon of a block, first the page needs to reload with* extra UI for choosing a new position for a particular block. In that situation* this field holds the id of the block being moved.** @var integer|null*/protected $movingblock = null;/*** Show only fake blocks*/protected $fakeblocksonly = false;/// 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 class* and {@link http://en.wikipedia.org/wiki/Duck_typing})*/public function __construct($page) {$this->page = $page;}/// Getter methods =============================================================/*** Get an array of all region names on this page where a block may appear** @return array the internal names of the regions on this page where block may appear.*/public function get_regions() {if (is_null($this->defaultregion)) {$this->page->initialise_theme_and_output();}return array_keys($this->regions);}/*** Get the region name of the region blocks are added to by default** @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() {$this->page->initialise_theme_and_output();return $this->defaultregion;}/*** The list of block types that may be added to this page.** @return array block name => record from block table.*/public function get_addable_blocks() {$this->check_is_loaded();if (!is_null($this->addableblocks)) {return $this->addableblocks;}// Lazy load.$this->addableblocks = array();$allblocks = blocks_get_record();if (empty($allblocks)) {return $this->addableblocks;}$undeletableblocks = self::get_undeletable_block_types();$unaddablebythemeblocks = $this->get_unaddable_by_theme_block_types();$requiredbythemeblocks = $this->get_required_by_theme_block_types();$pageformat = $this->page->pagetype;foreach($allblocks as $block) {if (!$bi = block_instance($block->name)) {continue;}if ($block->visible && !in_array($block->name, $undeletableblocks) &&!in_array($block->name, $requiredbythemeblocks) &&!in_array($block->name, $unaddablebythemeblocks) &&$bi->can_block_be_added($this->page) &&($bi->instance_allow_multiple() || !$this->is_block_present($block->name)) &&blocks_name_allowed_in_format($block->name, $pageformat) &&$bi->user_can_addto($this->page)) {$block->title = $bi->get_title();$this->addableblocks[$block->name] = $block;}}core_collator::asort_objects_by_property($this->addableblocks, 'title');return $this->addableblocks;}/*** Given a block name, find out of any of them are currently present in the page* @param string $blockname - the basic name of a block (eg "navigation")* @return boolean - is there one of these blocks in the current page?*/public function is_block_present($blockname) {if (empty($this->blockinstances)) {return false;}$requiredbythemeblocks = $this->get_required_by_theme_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($blockname, $requiredbythemeblocks)) {continue;}}return true;}}}return false;}/*** Find out if a block type is known by the system** @param string $blockname the name of the type of block.* @param boolean $includeinvisible if false (default) only check 'visible' blocks, that is, blocks enabled by the admin.* @return boolean true if this block in installed.*/public function is_known_block_type($blockname, $includeinvisible = false) {$blocks = $this->get_installed_blocks();foreach ($blocks as $block) {if ($block->name == $blockname && ($includeinvisible || $block->visible)) {return true;}}return false;}/*** Find out if a region exists on a page** @param string $region a region name* @return boolean true if this region exists on this page.*/public function is_known_region($region) {if (empty($region)) {return false;}return array_key_exists($region, $this->regions);}/*** Get an array of all blocks within a given region** @param string $region a block region that exists on this page.* @return array of block instances.*/public function get_blocks_for_region($region) {$this->check_is_loaded();$this->ensure_instances_exist($region);return $this->blockinstances[$region];}/*** Returns an array of block content objects that exist in a region** @param string $region a block region that exists on this page.* @return array of block block_contents objects for all the blocks in a region.*/public function get_content_for_region($region, $output) {$this->check_is_loaded();$this->ensure_content_created($region, $output);return $this->visibleblockcontent[$region];}/*** Returns an array of block content objects for all the existings regions** @param renderer_base $output the rendered to use* @return array of block block_contents objects for all the blocks in all regions.* @since Moodle 3.3*/public function get_content_for_all_regions($output) {$contents = array();$this->check_is_loaded();foreach ($this->regions as $region => $val) {$this->ensure_content_created($region, $output);$contents[$region] = $this->visibleblockcontent[$region];}return $contents;}/*** Helper method used by get_content_for_region.* @param string $region region name* @param float $weight weight. May be fractional, since you may want to move a block* between ones with weight 2 and 3, say ($weight would be 2.5).* @return moodle_url URL for moving block $this->movingblock to this position.*/protected function get_move_target_url($region, $weight) {return new moodle_url($this->page->url, array('bui_moveid' => $this->movingblock,'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()));}/*** Determine whether a region contains anything. (Either any real blocks, or* the add new block UI.)** (You may wonder why the $output parameter is required. Unfortunately,* because of the way that blocks work, the only reliable way to find out* if a block will be visible is to get the content for output, and to* get the content, you need a renderer. Fortunately, this is not a* performance problem, because we cache the output that is generated, and* in almost every case where we call region_has_content, we are about to* output the blocks anyway, so we are not doing wasted effort.)** @param string $region a block region that exists on this page.* @param core_renderer $output a core_renderer. normally the global $OUTPUT.* @return boolean Whether there is anything in this region.*/public function region_has_content($region, $output) {if (!$this->is_known_region($region)) {return false;}$this->check_is_loaded();$this->ensure_content_created($region, $output);// if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks()) {// Mark Nielsen's patch - part 1if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks() && $this->movingblock) {// If editing is on, we need all the block regions visible, for the// move blocks UI.return true;}return !empty($this->visibleblockcontent[$region]) || !empty($this->extracontent[$region]);}/*** Determine whether a region contains any fake blocks.** (Fake blocks are typically added to the extracontent array per region)** @param string $region a block region that exists on this page.* @return boolean Whether there are fake blocks in this region.*/public function region_has_fakeblocks($region): bool {return !empty($this->extracontent[$region]);}/*** Get an array of all of the installed blocks.** @return array contents of the block table.*/public function get_installed_blocks() {global $DB;if (is_null($this->allblocks)) {$this->allblocks = $DB->get_records('block');}return $this->allblocks;}/*** @return array names of block types that must exist on every page with this theme.*/public function get_required_by_theme_block_types() {$requiredbythemeblocks = false;if (isset($this->page->theme->requiredblocks)) {$requiredbythemeblocks = $this->page->theme->requiredblocks;}if ($requiredbythemeblocks === false) {return array('navigation', 'settings');} else if ($requiredbythemeblocks === '') {return array();} else if (is_string($requiredbythemeblocks)) {return explode(',', $requiredbythemeblocks);} else {return $requiredbythemeblocks;}}/*** It returns the list of blocks that can't be displayed in the "Add a block" list.* This information is taken from the unaddableblocks theme setting.** @return array A list with the blocks that won't be displayed in the "Add a block" list.*/public function get_unaddable_by_theme_block_types(): array {$unaddablebythemeblocks = [];if (isset($this->page->theme->settings->unaddableblocks) && !empty($this->page->theme->settings->unaddableblocks)) {$unaddablebythemeblocks = array_map('trim', explode(',', $this->page->theme->settings->unaddableblocks));}return $unaddablebythemeblocks;}/*** Make this block type undeletable and unaddable.** @param mixed $blockidorname string or int*/public static function protect_block($blockidorname) {global $DB;$syscontext = context_system::instance();require_capability('moodle/site:config', $syscontext);$block = false;if (is_int($blockidorname)) {$block = $DB->get_record('block', array('id' => $blockidorname), 'id, name', MUST_EXIST);} else {$block = $DB->get_record('block', array('name' => $blockidorname), 'id, name', MUST_EXIST);}$undeletableblocktypes = self::get_undeletable_block_types();if (!in_array($block->name, $undeletableblocktypes)) {$undeletableblocktypes[] = $block->name;set_config('undeletableblocktypes', implode(',', $undeletableblocktypes));add_to_config_log('block_protect', "0", "1", $block->name);}}/*** Make this block type deletable and addable.** @param mixed $blockidorname string or int*/public static function unprotect_block($blockidorname) {global $DB;$syscontext = context_system::instance();require_capability('moodle/site:config', $syscontext);$block = false;if (is_int($blockidorname)) {$block = $DB->get_record('block', array('id' => $blockidorname), 'id, name', MUST_EXIST);} else {$block = $DB->get_record('block', array('name' => $blockidorname), 'id, name', MUST_EXIST);}$undeletableblocktypes = self::get_undeletable_block_types();if (in_array($block->name, $undeletableblocktypes)) {$undeletableblocktypes = array_diff($undeletableblocktypes, array($block->name));set_config('undeletableblocktypes', implode(',', $undeletableblocktypes));add_to_config_log('block_protect', "1", "0", $block->name);}}/*** Get the list of "protected" blocks via admin block manager ui.** @return array names of block types that cannot be added or deleted. E.g. array('navigation','settings').*/public static function get_undeletable_block_types() {global $CFG;$undeletableblocks = false;if (isset($CFG->undeletableblocktypes)) {$undeletableblocks = $CFG->undeletableblocktypes;}if (empty($undeletableblocks)) {return array();} else if (is_string($undeletableblocks)) {return explode(',', $undeletableblocks);} else {return $undeletableblocks;}}/// Setter methods =============================================================/*** Add a region to a page** @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.* @param bool $custom True if this is a custom block region, being added by the page rather than the theme layout.*/public function add_region($region, $custom = true) {global $SESSION;$this->check_not_yet_loaded();if ($custom) {if (array_key_exists($region, $this->regions)) {// This here is EXACTLY why we should not be adding block regions into a page. It should// ALWAYS be done in a theme layout.debugging('A custom region conflicts with a block region in the theme.', DEBUG_DEVELOPER);}// We need to register this custom region against the page type being used.// This allows us to check, when performing block actions, that unrecognised regions can be worked with.$type = $this->page->pagetype;if (!isset($SESSION->custom_block_regions)) {$SESSION->custom_block_regions = array($type => array($region));} else if (!isset($SESSION->custom_block_regions[$type])) {$SESSION->custom_block_regions[$type] = array($region);} else if (!in_array($region, $SESSION->custom_block_regions[$type])) {$SESSION->custom_block_regions[$type][] = $region;}}$this->regions[$region] = 1;// Checking the actual property instead of calling get_default_region as it ends up in a recursive call.if (empty($this->defaultregion)) {$this->set_default_region($region);}}/*** Add an array of regions* @see add_region()** @param array $regions this utility method calls add_region for each array element.*/public function add_regions($regions, $custom = true) {foreach ($regions as $region) {$this->add_region($region, $custom);}}/*** Finds custom block regions associated with a page type and registers them with this block manager.** @param string $pagetype*/public function add_custom_regions_for_pagetype($pagetype) {global $SESSION;if (isset($SESSION->custom_block_regions[$pagetype])) {foreach ($SESSION->custom_block_regions[$pagetype] as $customregion) {$this->add_region($customregion, false);}}}/*** Set the default region for new blocks on the page** @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 ($defaultregion) {$this->check_region_is_known($defaultregion);}$this->defaultregion = $defaultregion;}/*** Add something that looks like a block, but which isn't an actual block_instance,* to this page.** @param block_contents $bc the content of the block-like thing.* @param string $region a block region that exists on this page.*/public function add_fake_block($bc, $region) {$this->page->initialise_theme_and_output();if (!$this->is_known_region($region)) {$region = $this->get_default_region();}if (array_key_exists($region, $this->visibleblockcontent)) {throw new coding_exception('block_manager has already prepared the blocks in region ' .$region . 'for output. It is too late to add a fake block.');}if (!isset($bc->attributes['data-block'])) {$bc->attributes['data-block'] = '_fake';}$bc->attributes['class'] .= ' block_fake';$this->extracontent[$region][] = $bc;}/*** Checks to see whether all of the blocks within the given region are docked** @see region_uses_dock* @param string $region* @return bool True if all of the blocks within that region are docked** Return false as from MDL-64506*/public function region_completely_docked($region, $output) {return false;}/*** Checks to see whether any of the blocks within the given regions are docked** @see region_completely_docked* @param array|string $regions array of regions (or single region)* @return bool True if any of the blocks within that region are docked** Return false as from MDL-64506*/public function region_uses_dock($regions, $output) {return false;}/// Actions ====================================================================/*** This method actually loads the blocks for our page from the database.** @param boolean|null $includeinvisible* null (default) - load hidden blocks if $this->page->user_is_editing();* true - load hidden blocks.* false - don't load hidden blocks.*/public function load_blocks($includeinvisible = null) {global $DB, $CFG;if (!is_null($this->birecordsbyregion)) {// Already done.return;}if ($CFG->version < 2009050619) {// Upgrade/install not complete. Don't try too show any blocks.$this->birecordsbyregion = array();return;}// Ensure we have been initialised.if (is_null($this->defaultregion)) {$this->page->initialise_theme_and_output();// If there are still no block regions, then there are no blocks on this page.if (empty($this->regions)) {$this->birecordsbyregion = array();return;}}// Check if we need to load normal blocksif ($this->fakeblocksonly) {$this->birecordsbyregion = $this->prepare_per_region_arrays();return;}// Exclude auto created blocks if they are not undeletable in this theme.$requiredbytheme = $this->get_required_by_theme_block_types();$requiredbythemecheck = '';$requiredbythemeparams = array();$requiredbythemenotparams = array();if (!empty($requiredbytheme)) {list($testsql, $requiredbythemeparams) = $DB->get_in_or_equal($requiredbytheme, SQL_PARAMS_NAMED, 'requiredbytheme');list($testnotsql, $requiredbythemenotparams) = $DB->get_in_or_equal($requiredbytheme, SQL_PARAMS_NAMED,'notrequiredbytheme', false);$requiredbythemecheck = 'AND ((bi.blockname ' . $testsql . ' AND bi.requiredbytheme = 1) OR ' .' (bi.blockname ' . $testnotsql . ' AND bi.requiredbytheme = 0))';} else {$requiredbythemecheck = 'AND (bi.requiredbytheme = 0)';}if (is_null($includeinvisible)) {$includeinvisible = $this->page->user_is_editing();}if ($includeinvisible) {$visiblecheck = '';} else {$visiblecheck = 'AND (bp.visible = 1 OR bp.visible IS NULL) AND (bs.visible = 1 OR bs.visible IS NULL)';}$context = $this->page->context;$contexttest = 'bi.parentcontextid IN (:contextid2, :contextid3)';$parentcontextparams = array();$parentcontextids = $context->get_parent_context_ids();if ($parentcontextids) {list($parentcontexttest, $parentcontextparams) =$DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED, 'parentcontext');$contexttest = "($contexttest OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontexttest))";}$pagetypepatterns = matching_page_type_patterns($this->page->pagetype);list($pagetypepatterntest, $pagetypepatternparams) =$DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED, 'pagetypepatterntest');$ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');$ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = bi.id AND ctx.contextlevel = :contextlevel)";$systemcontext = context_system::instance();$params = array('contextlevel' => CONTEXT_BLOCK,'subpage1' => $this->page->subpage,'subpage2' => $this->page->subpage,'subpage3' => $this->page->subpage,'contextid1' => $context->id,'contextid2' => $context->id,'contextid3' => $systemcontext->id,'contextid4' => $systemcontext->id,'pagetype' => $this->page->pagetype,'pagetype2' => $this->page->pagetype,);if ($this->page->subpage === '') {$params['subpage1'] = '';$params['subpage2'] = '';$params['subpage3'] = '';}$sql = "SELECTbi.id,COALESCE(bp.id, bs.id) AS blockpositionid,bi.blockname,bi.parentcontextid,bi.showinsubcontexts,bi.pagetypepattern,bi.requiredbytheme,bi.subpagepattern,bi.defaultregion,bi.defaultweight,COALESCE(bp.visible, bs.visible, 1) AS visible,COALESCE(bp.region, bs.region, bi.defaultregion) AS region,COALESCE(bp.weight, bs.weight, bi.defaultweight) AS weight,bi.configdata$ccselectFROM {block_instances} biJOIN {block} b ON bi.blockname = b.nameLEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.idAND bp.contextid = :contextid1AND bp.pagetype = :pagetypeAND bp.subpage = :subpage1LEFT JOIN {block_positions} bs ON bs.blockinstanceid = bi.idAND bs.contextid = :contextid4AND bs.pagetype = :pagetype2AND bs.subpage = :subpage3$ccjoinWHERE$contexttestAND bi.pagetypepattern $pagetypepatterntestAND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)$visiblecheckAND b.visible = 1$requiredbythemecheckORDER BYCOALESCE(bp.region, bs.region, bi.defaultregion),COALESCE(bp.weight, bs.weight, bi.defaultweight),bi.id";$allparams = $params + $parentcontextparams + $pagetypepatternparams + $requiredbythemeparams + $requiredbythemenotparams;$blockinstances = $DB->get_recordset_sql($sql, $allparams);$this->birecordsbyregion = $this->prepare_per_region_arrays();$unknown = array();foreach ($blockinstances as $bi) {context_helper::preload_from_record($bi);if ($this->is_known_region($bi->region)) {$this->birecordsbyregion[$bi->region][] = $bi;} else {$unknown[] = $bi;}}$blockinstances->close();// Pages don't necessarily have a defaultregion. The one time this can// happen is when there are no theme block regions, but the script itself// has a block region in the main content area.if (!empty($this->defaultregion)) {$this->birecordsbyregion[$this->defaultregion] =array_merge($this->birecordsbyregion[$this->defaultregion], $unknown);}}/*** Add a block to the current page, or related pages. The block is added to* context $this->page->contextid. If $pagetypepattern $subpagepattern** @param string $blockname The type of block to add.* @param string $region the block region on this page to add the block to.* @param integer $weight determines the order where this block appears in the region.* @param boolean $showinsubcontexts whether this block appears in subcontexts, or just the current context.* @param string|null $pagetypepattern which page types this block should appear on. Defaults to just the current page type.* @param string|null $subpagepattern which subpage this block should appear on. NULL = any (the default), otherwise only the specified subpage.* @return block_base*/public function add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern = NULL, $subpagepattern = NULL) {global $DB;// Allow invisible blocks because this is used when adding default page blocks, which// might include invisible ones if the user makes some default blocks invisible$this->check_known_block_type($blockname, true);$this->check_region_is_known($region);if (empty($pagetypepattern)) {$pagetypepattern = $this->page->pagetype;}$blockinstance = new stdClass;$blockinstance->blockname = $blockname;$blockinstance->parentcontextid = $this->page->context->id;$blockinstance->showinsubcontexts = !empty($showinsubcontexts);$blockinstance->pagetypepattern = $pagetypepattern;$blockinstance->subpagepattern = $subpagepattern;$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.context_block::instance($blockinstance->id);// If the new instance was created, allow it to do additional setupif ($block = block_instance($blockname, $blockinstance)) {$block->instance_create();}if (!is_null($this->birecordsbyregion)) {// If blocks were already loaded on this page, reload them.$this->birecordsbyregion = null;$this->load_blocks();}return $block;}/*** When passed a block name create a new instance of the block in the specified region.** @param string $blockname Name of the block to add.* @param null|string $blockregion If defined add the new block to the specified region.* @return ?block_base*/public function add_block_at_end_of_default_region($blockname, $blockregion = null) {if (empty($this->birecordsbyregion)) {// No blocks or block regions exist yet.return null;}if ($blockregion === null) {$defaulregion = $this->get_default_region();} else {$defaulregion = $blockregion;}$lastcurrentblock = end($this->birecordsbyregion[$defaulregion]);if ($lastcurrentblock) {$weight = $lastcurrentblock->weight + 1;} else {$weight = 0;}if ($this->page->subpage) {$subpage = $this->page->subpage;} else {$subpage = null;}// Special case. Course view page type include the course format, but we// want to add the block non-format-specifically.$pagetypepattern = $this->page->pagetype;if (strpos($pagetypepattern, 'course-view') === 0) {$pagetypepattern = 'course-view-*';}// We should end using this for ALL the blocks, making always the 1st option// the default one to be used. Until then, this is one hack to avoid the// 'pagetypewarning' message on blocks initial edition (MDL-27829) caused by// non-existing $pagetypepattern set. This way at least we guarantee one "valid"// (the FIRST $pagetypepattern will be set)// We are applying it to all blocks created in mod pages for now and only if the// default pagetype is not one of the available optionsif (preg_match('/^mod-.*-/', $pagetypepattern)) {$pagetypelist = generate_page_type_patterns($this->page->pagetype, null, $this->page->context);// Only go for the first if the pagetype is not a valid optionif (is_array($pagetypelist) && !array_key_exists($pagetypepattern, $pagetypelist)) {$pagetypepattern = key($pagetypelist);}}// Surely other pages like course-report will need this too, they just are not important// enough now. This will be decided in the coming days. (MDL-27829, MDL-28150)return $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);}/*** Convenience method, calls add_block repeatedly for all the blocks in $blocks. Optionally, a starting weight* can be used to decide the starting point that blocks are added in the region, the weight is passed to {@link add_block}* and incremented by the position of the block in the $blocks array** @param array $blocks array with array keys the region names, and values an array of block names.* @param string|null $pagetypepattern optional. Passed to {@see self::add_block()}* @param string|null $subpagepattern optional. Passed to {@see self::add_block()}* @param bool $showinsubcontexts optional. Passed to {@see self::add_block()}* @param int $weight optional. Determines the starting point that the blocks are added in the region.*/public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {$initialweight = $weight;$this->add_regions(array_keys($blocks), false);foreach ($blocks as $region => $regionblocks) {foreach ($regionblocks as $offset => $blockname) {$weight = $initialweight + $offset;$this->add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern, $subpagepattern);}}}/*** Move a block to a new position on this page.** If this block cannot appear on any other pages, then we change defaultposition/weight* in the block_instances table. Otherwise we just set the position on this page.** @param $blockinstanceid the block instance id.* @param $newregion the new region name.* @param $newweight the new weight.*/public function reposition_block($blockinstanceid, $newregion, $newweight) {global $DB;$this->check_region_is_known($newregion);$inst = $this->find_instance($blockinstanceid);$bi = $inst->instance;if ($bi->weight == $bi->defaultweight && $bi->region == $bi->defaultregion &&!$bi->showinsubcontexts && strpos($bi->pagetypepattern, '*') === false &&(!$this->page->subpage || $bi->subpagepattern)) {// Set default position$newbi = new stdClass;$newbi->id = $bi->id;$newbi->defaultregion = $newregion;$newbi->defaultweight = $newweight;$newbi->timemodified = time();$DB->update_record('block_instances', $newbi);if ($bi->blockpositionid) {$bp = new stdClass;$bp->id = $bi->blockpositionid;$bp->region = $newregion;$bp->weight = $newweight;$DB->update_record('block_positions', $bp);}} else {// Just set position on this page.$bp = new stdClass;$bp->region = $newregion;$bp->weight = $newweight;if ($bi->blockpositionid) {$bp->id = $bi->blockpositionid;$DB->update_record('block_positions', $bp);} else {$bp->blockinstanceid = $bi->id;$bp->contextid = $this->page->context->id;$bp->pagetype = $this->page->pagetype;if ($this->page->subpage) {$bp->subpage = $this->page->subpage;} else {$bp->subpage = '';}$bp->visible = $bi->visible;$DB->insert_record('block_positions', $bp);}}}/*** Find a given block by its instance id** @param integer $instanceid* @return block_base*/public function find_instance($instanceid) {foreach ($this->regions as $region => $notused) {$this->ensure_instances_exist($region);foreach($this->blockinstances[$region] as $instance) {if ($instance->instance->id == $instanceid) {return $instance;}}}throw new block_not_on_page_exception($instanceid, $this->page);}/// Inner workings =============================================================/*** Check whether the page blocks have been loaded yet** @return void Throws coding exception if already loaded*/protected function check_not_yet_loaded() {if (!is_null($this->birecordsbyregion)) {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.');}}/*** Check whether the page blocks have been loaded yet** Nearly identical to the above function {@link check_not_yet_loaded()} except different message** @return void Throws coding exception if already loaded*/protected function check_is_loaded() {if (is_null($this->birecordsbyregion)) {throw new coding_exception('block_manager has not yet loaded the blocks, to it is too soon to request the information you asked for.');}}/*** Check if a block type is known and usable** @param string $blockname The block type name to search for* @param bool $includeinvisible Include disabled block types in the initial pass* @return void Coding Exception thrown if unknown or not enabled*/protected function check_known_block_type($blockname, $includeinvisible = false) {if (!$this->is_known_block_type($blockname, $includeinvisible)) {if ($this->is_known_block_type($blockname, true)) {throw new coding_exception('Unknown block type ' . $blockname);} else {throw new coding_exception('Block type ' . $blockname . ' has been disabled by the administrator.');}}}/*** Check if a region is known by its name** @param string $region* @return void Coding Exception thrown if the region is not known*/protected function check_region_is_known($region) {if (!$this->is_known_region($region)) {throw new coding_exception('Trying to reference an unknown block region ' . $region);}}/*** Returns an array of region names as keys and nested arrays for values** @return array an array where the array keys are the region names, and the array* values are empty arrays.*/protected function prepare_per_region_arrays() {$result = array();foreach ($this->regions as $region => $notused) {$result[$region] = array();}return $result;}/*** Create a set of new block instance from a record array** @param array $birecords An array of block instance records* @return array An array of instantiated block_instance objects*/protected function create_block_instances($birecords) {$results = array();foreach ($birecords as $record) {if ($blockobject = block_instance($record->blockname, $record, $this->page)) {$results[] = $blockobject;}}return $results;}/*** Create all the block instances for all the blocks that were loaded by* 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 "requiredbytheme" 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() {$missing = false;// If there are any un-removable blocks that were not created - force them.$requiredbytheme = $this->get_required_by_theme_block_types();if (!$this->fakeblocksonly) {foreach ($requiredbytheme as $forced) {if (empty($forced)) {continue;}$found = false;foreach ($this->get_regions() as $region) {foreach($this->birecordsbyregion[$region] as $instance) {if ($instance->blockname == $forced) {$found = true;}}}if (!$found) {$this->add_block_required_by_theme($forced);$missing = true;}}}if ($missing) {// Some blocks were missing. Lets do it again.$this->birecordsbyregion = null;$this->load_blocks();}foreach ($this->get_regions() as $region) {$this->ensure_instances_exist($region);}}/*** 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;}// Never auto create blocks when we are showing fake blocks only.if ($this->fakeblocksonly) {return;}// Never add a duplicate block required by theme.if ($DB->record_exists('block_instances', array('blockname' => $blockname, 'requiredbytheme' => 1))) {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->timecreated = time();$blockinstance->timemodified = $blockinstance->timecreated;$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** @param array $instances An array of block instances* @param renderer_base The renderer to use.* @param string $region the region name.* @return array An array of block_content (and possibly block_move_target) objects.*/protected function create_block_contents($instances, $output, $region) {$results = array();$lastweight = 0;$lastblock = 0;if ($this->movingblock) {$first = reset($instances);if ($first) {$lastweight = $first->instance->weight - 2;}}foreach ($instances as $instance) {$content = $instance->get_content_for_output($output);if (empty($content)) {continue;}if ($this->movingblock && $lastweight != $instance->instance->weight &&$content->blockinstanceid != $this->movingblock && $lastblock != $this->movingblock) {$results[] = new block_move_target($this->get_move_target_url($region, ($lastweight + $instance->instance->weight)/2));}if ($content->blockinstanceid == $this->movingblock) {$content->add_class('beingmoved');$content->annotation .= get_string('movingthisblockcancel', 'block',html_writer::link($this->page->url, get_string('cancel')));}$results[] = $content;$lastweight = $instance->instance->weight;$lastblock = $instance->instance->id;}if ($this->movingblock && $lastblock != $this->movingblock) {$results[] = new block_move_target($this->get_move_target_url($region, $lastweight + 1));}return $results;}/*** Ensure block instances exist for a given region** @param string $region Check for bi's with the instance with this name*/protected function ensure_instances_exist($region) {$this->check_region_is_known($region);if (!array_key_exists($region, $this->blockinstances)) {$this->blockinstances[$region] =$this->create_block_instances($this->birecordsbyregion[$region]);}}/*** Ensure that there is some content within the given region** @param string $region The name of the region to check*/public function ensure_content_created($region, $output) {$this->ensure_instances_exist($region);if (!has_capability('moodle/block:view', $this->page->context) ) {$this->visibleblockcontent[$region] = [];return;}if (!array_key_exists($region, $this->visibleblockcontent)) {$contents = array();if (array_key_exists($region, $this->extracontent)) {$contents = $this->extracontent[$region];}$contents = array_merge($contents, $this->create_block_contents($this->blockinstances[$region], $output, $region));if (($region == $this->defaultregion) && (!isset($this->page->theme->addblockposition) ||$this->page->theme->addblockposition == BLOCK_ADDBLOCK_POSITION_DEFAULT)) {$addblockui = block_add_block_ui($this->page, $output);if ($addblockui) {$contents[] = $addblockui;}}$this->visibleblockcontent[$region] = $contents;}}/// Process actions from the URL ===============================================/*** Get the appropriate list of editing icons for a block. This is used* to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}.** @param block_base $block* @return array an array in the format for {@link block_contents::$controls}*/public function edit_controls($block) {global $CFG;$controls = array();$actionurl = $this->page->url->out(false, array('sesskey'=> sesskey()));$blocktitle = $block->title;if (empty($blocktitle)) {$blocktitle = $block->arialabel;}if ($this->page->user_can_edit_blocks()) {// Move icon.$str = new lang_string('moveblock', 'block', $blocktitle);$controls[] = new action_menu_link_primary(new moodle_url($actionurl, array('bui_moveid' => $block->instance->id)),new pix_icon('t/move', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),$str,array('class' => 'editing_move'));}if ($this->page->user_can_edit_blocks() || $block->user_can_edit()) {// Edit config icon - always show - needed for positioning UI.$str = new lang_string('configureblock', 'block', $blocktitle);$editactionurl = new moodle_url($actionurl, ['bui_editid' => $block->instance->id]);$editactionurl->remove_params(['sesskey']);// Handle editing block on admin index page, prevent the page redirecting before block action can begin.if ($editactionurl->compare(new moodle_url('/admin/index.php'), URL_MATCH_BASE)) {$editactionurl->param('cache', 1);}$controls[] = new action_menu_link_secondary($editactionurl,new pix_icon('t/edit', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),$str,['class' => 'editing_edit','data-action' => 'editblock','data-blockid' => $block->instance->id,'data-blockform' => self::get_block_edit_form_class($block->name()),'data-header' => $str,]);}if ($this->page->user_can_edit_blocks() && $block->instance_can_be_hidden()) {// Show/hide icon.if ($block->instance->visible) {$str = new lang_string('hideblock', 'block', $blocktitle);$url = new moodle_url($actionurl, array('bui_hideid' => $block->instance->id));$icon = new pix_icon('t/hide', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));$attributes = array('class' => 'editing_hide');} else {$str = new lang_string('showblock', 'block', $blocktitle);$url = new moodle_url($actionurl, array('bui_showid' => $block->instance->id));$icon = new pix_icon('t/show', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));$attributes = array('class' => 'editing_show');}$controls[] = new action_menu_link_secondary($url, $icon, $str, $attributes);}// Assign roles.if (get_assignable_roles($block->context, ROLENAME_SHORT)) {$rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid' => $block->context->id,'returnurl' => $this->page->url->out_as_local_url()));$str = new lang_string('assignrolesinblock', 'block', $blocktitle);$controls[] = new action_menu_link_secondary($rolesurl,new pix_icon('i/assignroles', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),$str, array('class' => 'editing_assignroles'));}// Permissions.if (has_capability('moodle/role:review', $block->context) or get_overridable_roles($block->context)) {$rolesurl = new moodle_url('/admin/roles/permissions.php', array('contextid' => $block->context->id,'returnurl' => $this->page->url->out_as_local_url()));$str = get_string('permissions', 'role');$controls[] = new action_menu_link_secondary($rolesurl,new pix_icon('i/permissions', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),$str, array('class' => 'editing_permissions'));}// Change permissions.if (has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:assign'), $block->context)) {$rolesurl = new moodle_url('/admin/roles/check.php', array('contextid' => $block->context->id,'returnurl' => $this->page->url->out_as_local_url()));$str = get_string('checkpermissions', 'role');$controls[] = new action_menu_link_secondary($rolesurl,new pix_icon('i/checkpermissions', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),$str, array('class' => 'editing_checkroles'));}if ($this->user_can_delete_block($block)) {// Delete icon.$str = new lang_string('deleteblock', 'block', $blocktitle);$deleteactionurl = new moodle_url($actionurl, ['bui_deleteid' => $block->instance->id]);$deleteactionurl->remove_params(['sesskey']);// Handle deleting block on admin index page, prevent the page redirecting before block action can begin.if ($deleteactionurl->compare(new moodle_url('/admin/index.php'), URL_MATCH_BASE)) {$deleteactionurl->param('cache', 1);}$deleteconfirmationurl = new moodle_url($actionurl, ['bui_deleteid' => $block->instance->id,'bui_confirm' => 1,'sesskey' => sesskey(),]);$blocktitle = $block->get_title();$controls[] = new action_menu_link_secondary($deleteactionurl,new pix_icon('t/delete', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),$str,['class' => 'editing_delete','data-modal' => 'confirmation','data-modal-title-str' => json_encode(['deletecheck_modal', 'block']),'data-modal-content-str' => json_encode(['deleteblockcheck', 'block', $blocktitle]),'data-modal-yes-button-str' => json_encode(['delete', 'core']),'data-modal-toast' => 'true','data-modal-toast-confirmation-str' => json_encode(['deleteblockinprogress', 'block', $blocktitle]),'data-modal-destination' => $deleteconfirmationurl->out(false),]);}if (!empty($CFG->contextlocking) && has_capability('moodle/site:managecontextlocks', $block->context)) {$parentcontext = $block->context->get_parent_context();if (empty($parentcontext) || empty($parentcontext->locked)) {if ($block->context->locked) {$lockicon = 'i/unlock';$lockstring = get_string('managecontextunlock', 'admin');} else {$lockicon = 'i/lock';$lockstring = get_string('managecontextlock', 'admin');}$controls[] = new action_menu_link_secondary(new moodle_url('/admin/lock.php',['id' => $block->context->id,]),new pix_icon($lockicon, $lockstring, 'moodle', array('class' => 'iconsmall', 'title' => '')),$lockstring,['class' => 'editing_lock']);}}return $controls;}/*** @param block_base $block a block that appears on this page.* @return boolean boolean whether the currently logged in user is allowed to delete this block.*/protected function user_can_delete_block($block) {return $this->page->user_can_edit_blocks() && $block->user_can_edit() &&$block->user_can_addto($this->page) &&!in_array($block->instance->blockname, self::get_undeletable_block_types()) &&!in_array($block->instance->blockname, $this->get_required_by_theme_block_types());}/*** Process any block actions that were specified in the URL.** @return boolean true if anything was done. False if not.*/public function process_url_actions() {if (!$this->page->user_is_editing()) {return false;}return $this->process_url_add() || $this->process_url_delete() ||$this->process_url_show_hide() || $this->process_url_edit() ||$this->process_url_move();}/*** Handle adding a block.* @return boolean true if anything was done. False if not.*/public function process_url_add() {global $CFG, $PAGE, $OUTPUT;$blocktype = optional_param('bui_addblock', null, PARAM_PLUGIN);$blockregion = optional_param('bui_blockregion', null, PARAM_TEXT);if ($blocktype === null) {return false;}require_sesskey();if (!$this->page->user_can_edit_blocks()) {throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('addblock'));}$addableblocks = $this->get_addable_blocks();if ($blocktype === '') {// Display add block selection.$addpage = new moodle_page();$addpage->set_pagelayout('admin');$addpage->blocks->show_only_fake_blocks(true);$addpage->set_course($this->page->course);$addpage->set_context($this->page->context);if ($this->page->cm) {$addpage->set_cm($this->page->cm);}$addpagebase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());$addpageparams = $this->page->url->params();$addpage->set_url($addpagebase, $addpageparams);$addpage->set_block_actions_done();// At this point we are going to display the block selector, overwrite global $PAGE ready for this.$PAGE = $addpage;// Some functions use $OUTPUT so we need to replace that too./** @var core_renderer $OUTPUT */$OUTPUT = $addpage->get_renderer('core');$site = get_site();$straddblock = get_string('addblock');$PAGE->navbar->add($straddblock);$PAGE->set_title($straddblock);$PAGE->set_heading($site->fullname);echo $OUTPUT->header();echo $OUTPUT->heading($straddblock);if (!$addableblocks) {echo $OUTPUT->box(get_string('noblockstoaddhere'));echo $OUTPUT->container($OUTPUT->action_link($addpage->url, get_string('back')), 'mx-3 mb-1');} else {$url = new moodle_url($addpage->url, array('sesskey' => sesskey()));echo $OUTPUT->render_from_template('core/add_block_body',['blocks' => array_values($addableblocks),'url' => '?' . $url->get_query_string(false)]);echo $OUTPUT->container($OUTPUT->action_link($addpage->url, get_string('cancel')), 'mx-3 mb-1');}echo $OUTPUT->footer();// Make sure that nothing else happens after we have displayed this form.exit;}if (!array_key_exists($blocktype, $addableblocks)) {throw new moodle_exception('cannotaddthisblocktype', '', $this->page->url->out(), $blocktype);}$this->add_block_at_end_of_default_region($blocktype, $blockregion);// If the page URL was a guess, it will contain the bui_... param, so we must make sure it is not there.$this->page->ensure_param_not_in_url('bui_addblock');return true;}/*** Handle deleting a block.* @return boolean true if anything was done. False if not.*/public function process_url_delete() {global $CFG, $PAGE, $OUTPUT;$blockid = optional_param('bui_deleteid', null, PARAM_INT);$confirmdelete = optional_param('bui_confirm', null, PARAM_INT);if (!$blockid) {return false;}$block = $this->page->blocks->find_instance($blockid);if (!$this->user_can_delete_block($block)) {throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('deleteablock'));}if (!$confirmdelete) {$deletepage = new moodle_page();$deletepage->set_pagelayout('admin');$deletepage->blocks->show_only_fake_blocks(true);$deletepage->set_course($this->page->course);$deletepage->set_context($this->page->context);if ($this->page->cm) {$deletepage->set_cm($this->page->cm);}$deleteurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());$deleteurlparams = $this->page->url->params();$deletepage->set_url($deleteurlbase, $deleteurlparams);$deletepage->set_block_actions_done();$deletepage->set_secondarynav($this->get_secondarynav($block));// At this point we are either going to redirect, or display the form, so// overwrite global $PAGE ready for this. (Formslib refers to it.)$PAGE = $deletepage;//some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that too/** @var core_renderer $output */$output = $deletepage->get_renderer('core');$OUTPUT = $output;$site = get_site();$blocktitle = $block->get_title();$strdeletecheck = get_string('deletecheck', 'block', $blocktitle);$message = get_string('deleteblockcheck', 'block', $blocktitle);// If the block is being shown in sub contexts display a warning.if ($block->instance->showinsubcontexts == 1) {$parentcontext = context::instance_by_id($block->instance->parentcontextid);$systemcontext = context_system::instance();$messagestring = new stdClass();$messagestring->location = $parentcontext->get_context_name();// Checking for blocks that may have visibility on the front page and pages added on that.if ($parentcontext->id != $systemcontext->id && is_inside_frontpage($parentcontext)) {$messagestring->pagetype = get_string('showonfrontpageandsubs', 'block');} else {$pagetypes = generate_page_type_patterns($this->page->pagetype, $parentcontext);$messagestring->pagetype = $block->instance->pagetypepattern;if (isset($pagetypes[$block->instance->pagetypepattern])) {$messagestring->pagetype = $pagetypes[$block->instance->pagetypepattern];}}$message = get_string('deleteblockwarning', 'block', $messagestring);}$PAGE->navbar->add($strdeletecheck);$PAGE->set_title($blocktitle . ': ' . $strdeletecheck);$PAGE->set_heading($site->fullname);echo $OUTPUT->header();$confirmurl = new moodle_url($deletepage->url, array('sesskey' => sesskey(), 'bui_deleteid' => $block->instance->id, 'bui_confirm' => 1));$cancelurl = new moodle_url($deletepage->url);$yesbutton = new single_button($confirmurl, get_string('yes'));$nobutton = new single_button($cancelurl, get_string('no'));echo $OUTPUT->confirm($message, $yesbutton, $nobutton);echo $OUTPUT->footer();// Make sure that nothing else happens after we have displayed this form.exit;} else {require_sesskey();blocks_delete_instance($block->instance);// bui_deleteid and bui_confirm should not be in the PAGE url.$this->page->ensure_param_not_in_url('bui_deleteid');$this->page->ensure_param_not_in_url('bui_confirm');return true;}}/*** Returns the name of the class for block editing and makes sure it is autoloaded** @param string $blockname name of the block plugin (without block_ prefix)* @return string*/public static function get_block_edit_form_class(string $blockname): string {global $CFG;require_once("$CFG->dirroot/blocks/moodleblock.class.php");$blockname = clean_param($blockname, PARAM_PLUGIN);$formfile = $CFG->dirroot . '/blocks/' . $blockname . '/edit_form.php';if (is_readable($formfile)) {require_once($CFG->dirroot . '/blocks/edit_form.php');require_once($formfile);$classname = 'block_' . $blockname . '_edit_form';if (!class_exists($classname)) {$classname = 'block_edit_form';}} else {require_once($CFG->dirroot . '/blocks/edit_form.php');$classname = 'block_edit_form';}return $classname;}/*** Handle showing or hiding a block.* @return boolean true if anything was done. False if not.*/public function process_url_show_hide() {if ($blockid = optional_param('bui_hideid', null, PARAM_INT)) {$newvisibility = 0;} else if ($blockid = optional_param('bui_showid', null, PARAM_INT)) {$newvisibility = 1;} else {return false;}require_sesskey();$block = $this->page->blocks->find_instance($blockid);if (!$this->page->user_can_edit_blocks()) {throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('hideshowblocks'));} else if (!$block->instance_can_be_hidden()) {return false;}blocks_set_visibility($block->instance, $this->page, $newvisibility);// If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.$this->page->ensure_param_not_in_url('bui_hideid');$this->page->ensure_param_not_in_url('bui_showid');return true;}/*** Convenience function to check whether a block is implementing a secondary nav class and return it* initialised to the calling function** @todo MDL-74939 Remove support for old 'local\views\secondary' class location* @param block_base $block* @return \core\navigation\views\secondary*/protected function get_secondarynav(block_base $block): \core\navigation\views\secondary {$class = "core_block\\navigation\\views\\secondary";if (class_exists("block_{$block->name()}\\navigation\\views\\secondary")) {$class = "block_{$block->name()}\\navigation\\views\\secondary";} else if (class_exists("block_{$block->name()}\\local\\views\\secondary")) {// For backwards compatibility, support the old location for this class (it was in a// 'local' namespace which shouldn't be used for core APIs).debugging("The class block_{$block->name()}\\local\\views\\secondary uses a deprecated " ."namespace. Please move it to block_{$block->name()}\\navigation\\views\\secondary.",DEBUG_DEVELOPER);$class = "block_{$block->name()}\\local\\views\\secondary";}$secondarynav = new $class($this->page);$secondarynav->initialise();return $secondarynav;}/*** Handle showing/processing the submission from the block editing form.* @return boolean true if the form was submitted and the new config saved. Does not* return if the editing form was displayed. False otherwise.*/public function process_url_edit() {global $CFG, $DB, $PAGE, $OUTPUT;$blockid = optional_param('bui_editid', null, PARAM_INT);if (!$blockid) {return false;}require_once($CFG->dirroot . '/blocks/edit_form.php');$block = $this->find_instance($blockid);if (!$block->user_can_edit() && !$this->page->user_can_edit_blocks()) {throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));}$editpage = new moodle_page();$editpage->set_pagelayout('admin');$editpage->blocks->show_only_fake_blocks(true);$editpage->set_course($this->page->course);//$editpage->set_context($block->context);$editpage->set_context($this->page->context);$editpage->set_secondarynav($this->get_secondarynav($block));if ($this->page->cm) {$editpage->set_cm($this->page->cm);}$editurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());$editurlparams = $this->page->url->params();$editurlparams['bui_editid'] = $blockid;$editpage->set_url($editurlbase, $editurlparams);$editpage->set_block_actions_done();// At this point we are either going to redirect, or display the form, so// overwrite global $PAGE ready for this. (Formslib refers to it.)$PAGE = $editpage;//some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that to$output = $editpage->get_renderer('core');$OUTPUT = $output;$classname = self::get_block_edit_form_class($block->name());/** @var block_edit_form $mform */$mform = new $classname($editpage->url->out(false), ['page' => $this->page, 'block' => $block, 'actionbuttons' => true]);$mform->set_data($block->instance);if ($mform->is_cancelled()) {redirect($this->page->url);} else if ($data = $mform->get_data()) {$this->save_block_data($block, $data);redirect($this->page->url);} else {$strheading = get_string('blockconfiga', 'moodle', $block->get_title());$editpage->set_title($strheading);$editpage->set_heading($strheading);$bits = explode('-', $this->page->pagetype);if ($bits[0] == 'tag' && !empty($this->page->subpage)) {// better navbar for tag pages$editpage->navbar->add(get_string('tags'), new moodle_url('/tag/'));$tag = core_tag_tag::get($this->page->subpage);// tag search page doesn't have subpageidif ($tag) {$editpage->navbar->add($tag->get_display_name(), $tag->get_view_url());}}$editpage->navbar->add($block->get_title());$editpage->navbar->add(get_string('configuration'));echo $output->header();$mform->display();echo $output->footer();exit;}}/*** Updates block configuration in the database** @param block_base $block* @param stdClass $data data from the block edit form* @return void*/public function save_block_data(block_base $block, stdClass $data): void {global $DB;$bi = new stdClass;$bi->id = $block->instance->id;// This may get overwritten by the special case handling below.$bi->pagetypepattern = $data->bui_pagetypepattern;$bi->showinsubcontexts = (bool) $data->bui_contexts;if (empty($data->bui_subpagepattern) || $data->bui_subpagepattern == '%@NULL@%') {$bi->subpagepattern = null;} else {$bi->subpagepattern = $data->bui_subpagepattern;}$systemcontext = context_system::instance();$frontpagecontext = context_course::instance(SITEID);$parentcontext = context::instance_by_id($data->bui_parentcontextid);// Updating stickiness and contexts. See MDL-21375 for details.if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination.// Explicitly set the default context.$bi->parentcontextid = $parentcontext->id;if ($data->bui_editingatfrontpage) { // The block is being edited on the front page.// The interface here is a special case because the pagetype pattern is// totally derived from the context menu. Here are the excpetions. MDL-30340 .switch ($data->bui_contexts) {case BUI_CONTEXTS_ENTIRE_SITE:// The user wants to show the block across the entire site.$bi->parentcontextid = $systemcontext->id;$bi->showinsubcontexts = true;$bi->pagetypepattern = '*';break;case BUI_CONTEXTS_FRONTPAGE_SUBS:// The user wants the block shown on the front page and all subcontexts.$bi->parentcontextid = $frontpagecontext->id;$bi->showinsubcontexts = true;$bi->pagetypepattern = '*';break;case BUI_CONTEXTS_FRONTPAGE_ONLY:// The user want to show the front page on the frontpage only.$bi->parentcontextid = $frontpagecontext->id;$bi->showinsubcontexts = false;$bi->pagetypepattern = 'site-index';// This is the only relevant page type anyway but we'll set it explicitly just// in case the front page grows site-index-* subpages of its own later.break;}}}$bits = explode('-', $bi->pagetypepattern);// Hacks for some contexts.if (($parentcontext->contextlevel == CONTEXT_COURSE) && ($parentcontext->instanceid != SITEID)) {// For course context// is page type pattern is mod-*, change showinsubcontext to 1.if ($bits[0] == 'mod' || $bi->pagetypepattern == '*') {$bi->showinsubcontexts = 1;} else {$bi->showinsubcontexts = 0;}} else if ($parentcontext->contextlevel == CONTEXT_USER) {// For user context subpagepattern should be null.if ($bits[0] == 'user' || $bits[0] == 'my') {// We don't need subpagepattern in usercontext.$bi->subpagepattern = null;}}$bi->defaultregion = $data->bui_defaultregion;$bi->defaultweight = $data->bui_defaultweight;$bi->timemodified = time();$DB->update_record('block_instances', $bi);if (!empty($block->config)) {$config = clone($block->config);} else {$config = new stdClass;}foreach ($data as $configfield => $value) {if (strpos($configfield, 'config_') !== 0) {continue;}$field = substr($configfield, 7);$config->$field = $value;}$block->instance_config_save($config);$bp = new stdClass;$bp->visible = $data->bui_visible;$bp->region = $data->bui_region;$bp->weight = $data->bui_weight;$needbprecord = !$data->bui_visible || $data->bui_region != $data->bui_defaultregion ||$data->bui_weight != $data->bui_defaultweight;if ($block->instance->blockpositionid && !$needbprecord) {$DB->delete_records('block_positions', array('id' => $block->instance->blockpositionid));} else if ($block->instance->blockpositionid && $needbprecord) {$bp->id = $block->instance->blockpositionid;$DB->update_record('block_positions', $bp);} else if ($needbprecord) {$bp->blockinstanceid = $block->instance->id;$bp->contextid = $this->page->context->id;$bp->pagetype = $this->page->pagetype;if ($this->page->subpage) {$bp->subpage = $this->page->subpage;} else {$bp->subpage = '';}$DB->insert_record('block_positions', $bp);}}/*** Handle showing/processing the submission from the block editing form.* @return boolean true if the form was submitted and the new config saved. Does not* return if the editing form was displayed. False otherwise.*/public function process_url_move() {global $CFG, $DB, $PAGE;$blockid = optional_param('bui_moveid', null, PARAM_INT);if (!$blockid) {return false;}require_sesskey();$block = $this->find_instance($blockid);if (!$this->page->user_can_edit_blocks()) {throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));}$newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT);$newweight = optional_param('bui_newweight', null, PARAM_FLOAT);if (!$newregion || is_null($newweight)) {// Don't have a valid target position yet, must be just starting the move.$this->movingblock = $blockid;$this->page->ensure_param_not_in_url('bui_moveid');return false;}if (!$this->is_known_region($newregion)) {throw new moodle_exception('unknownblockregion', '', $this->page->url, $newregion);}// Move this block. This may involve moving other nearby blocks.$blocks = $this->birecordsbyregion[$newregion];$maxweight = self::MAX_WEIGHT;$minweight = -self::MAX_WEIGHT;// Initialise the used weights and spareweights array with the default values$spareweights = array();$usedweights = array();for ($i = $minweight; $i <= $maxweight; $i++) {$spareweights[$i] = $i;$usedweights[$i] = array();}// Check each block and sort out where we have used weightsforeach ($blocks as $bi) {if ($bi->weight > $maxweight) {// If this statement is true then the blocks weight is more than the// current maximum. To ensure that we can get the best block position// we will initialise elements within the usedweights and spareweights// arrays between the blocks weight (which will then be the new max) and// the current max$parseweight = $bi->weight;while (!array_key_exists($parseweight, $usedweights)) {$usedweights[$parseweight] = array();$spareweights[$parseweight] = $parseweight;$parseweight--;}$maxweight = $bi->weight;} else if ($bi->weight < $minweight) {// As above except this time the blocks weight is LESS than the// the current minimum, so we will initialise the array from the// blocks weight (new minimum) to the current minimum$parseweight = $bi->weight;while (!array_key_exists($parseweight, $usedweights)) {$usedweights[$parseweight] = array();$spareweights[$parseweight] = $parseweight;$parseweight++;}$minweight = $bi->weight;}if ($bi->id != $block->instance->id) {unset($spareweights[$bi->weight]);$usedweights[$bi->weight][] = $bi->id;}}// First we find the nearest gap in the list of weights.$bestdistance = max(abs($newweight - self::MAX_WEIGHT), abs($newweight + self::MAX_WEIGHT)) + 1;$bestgap = null;foreach ($spareweights as $spareweight) {if (abs($newweight - $spareweight) < $bestdistance) {$bestdistance = abs($newweight - $spareweight);$bestgap = $spareweight;}}// If there is no gap, we have to go outside -self::MAX_WEIGHT .. self::MAX_WEIGHT.if (is_null($bestgap)) {$bestgap = self::MAX_WEIGHT + 1;while (!empty($usedweights[$bestgap])) {$bestgap++;}}// Now we know the gap we are aiming for, so move all the blocks along.if ($bestgap < $newweight) {$newweight = floor($newweight);for ($weight = $bestgap + 1; $weight <= $newweight; $weight++) {if (array_key_exists($weight, $usedweights)) {foreach ($usedweights[$weight] as $biid) {$this->reposition_block($biid, $newregion, $weight - 1);}}}$this->reposition_block($block->instance->id, $newregion, $newweight);} else {$newweight = ceil($newweight);for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {if (array_key_exists($weight, $usedweights)) {foreach ($usedweights[$weight] as $biid) {$this->reposition_block($biid, $newregion, $weight + 1);}}}$this->reposition_block($block->instance->id, $newregion, $newweight);}$this->page->ensure_param_not_in_url('bui_moveid');$this->page->ensure_param_not_in_url('bui_newregion');$this->page->ensure_param_not_in_url('bui_newweight');return true;}/*** Turns the display of normal blocks either on or off.** @param bool $setting*/public function show_only_fake_blocks($setting = true) {$this->fakeblocksonly = $setting;}}/// Helper functions for working with block classes ============================/*** Call a class method (one that does not require a block instance) on a block class.** @param string $blockname the name of the block.* @param string $method the method name.* @param array $param parameters to pass to the method.* @return mixed whatever the method returns.*/function block_method_result($blockname, $method, $param = NULL) {if(!block_load_class($blockname)) {return NULL;}return call_user_func(array('block_'.$blockname, $method), $param);}/*** Returns a new instance of the specified block instance id.** @param int $blockinstanceid* @return block_base the requested block instance.*/function block_instance_by_id($blockinstanceid) {global $DB;$blockinstance = $DB->get_record('block_instances', ['id' => $blockinstanceid]);$instance = block_instance($blockinstance->blockname, $blockinstance);return $instance;}/*** Creates a new instance of the specified block class.** @param string $blockname the name of the block.* @param stdClass $instance block_instances DB table row (optional).* @param moodle_page $page the page this block is appearing on.* @return block_base|false the requested block instance.*/function block_instance($blockname, $instance = NULL, $page = NULL) {if(!block_load_class($blockname)) {return false;}$classname = 'block_'.$blockname;/** @var block_base $retval */$retval = new $classname;if($instance !== NULL) {if (is_null($page)) {global $PAGE;$page = $PAGE;}$retval->_load_instance($instance, $page);}return $retval;}/*** Load the block class for a particular type of block.** @param string $blockname the name of the block.* @return boolean success or failure.*/function block_load_class($blockname) {global $CFG;$blocknameclean = clean_param($blockname, PARAM_PLUGIN);if (empty($blockname) || empty($blocknameclean)) {return false;}$classname = 'block_'.$blockname;if(class_exists($classname)) {return true;}$blockpath = $CFG->dirroot.'/blocks/'.$blockname.'/block_'.$blockname.'.php';if (file_exists($blockpath)) {require_once($CFG->dirroot.'/blocks/moodleblock.class.php');include_once($blockpath);}else{//debugging("$blockname code does not exist in $blockpath", DEBUG_DEVELOPER);return false;}return class_exists($classname);}/*** Given a specific page type, return all the page type patterns that might* match it.** @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.* @return array an array of all the page type patterns that might match this page type.*/function matching_page_type_patterns($pagetype) {$patterns = array($pagetype);$bits = explode('-', $pagetype);if (count($bits) == 3 && $bits[0] == 'mod') {if ($bits[2] == 'view') {$patterns[] = 'mod-*-view';} else if ($bits[2] == 'index') {$patterns[] = 'mod-*-index';}}while (count($bits) > 0) {$patterns[] = implode('-', $bits) . '-*';array_pop($bits);}$patterns[] = '*';return $patterns;}/*** Give an specific pattern, return all the page type patterns that would also match it.** @param string $pattern the pattern, e.g. 'mod-forum-*' or 'mod-quiz-view'.* @return array of all the page type patterns matching.*/function matching_page_type_patterns_from_pattern($pattern) {$patterns = array($pattern);if ($pattern === '*') {return $patterns;}// Only keep the part before the star because we will append -* to all the bits.$star = strpos($pattern, '-*');if ($star !== false) {$pattern = substr($pattern, 0, $star);}$patterns = array_merge($patterns, matching_page_type_patterns($pattern));$patterns = array_unique($patterns);return $patterns;}/*** Given a specific page type, parent context and currect context, return all the page type patterns* that might be used by this block.** @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.* @param stdClass $parentcontext Block's parent context* @param stdClass $currentcontext Current context of block* @return array an array of all the page type patterns that might match this page type.*/function generate_page_type_patterns($pagetype, $parentcontext = null, $currentcontext = null) {global $CFG; // Required for includes bellow.$bits = explode('-', $pagetype);$core = core_component::get_core_subsystems();$plugins = core_component::get_plugin_types();//progressively strip pieces off the page type looking for a match$componentarray = null;for ($i = count($bits); $i > 0; $i--) {$possiblecomponentarray = array_slice($bits, 0, $i);$possiblecomponent = implode('', $possiblecomponentarray);// Check to see if the component is a core componentif (array_key_exists($possiblecomponent, $core) && !empty($core[$possiblecomponent])) {$libfile = $core[$possiblecomponent].'/lib.php';if (file_exists($libfile)) {require_once($libfile);$function = $possiblecomponent.'_page_type_list';if (function_exists($function)) {if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {break;}}}}//check the plugin directory and look for a callbackif (array_key_exists($possiblecomponent, $plugins) && !empty($plugins[$possiblecomponent])) {//We've found a plugin type. Look for a plugin name by getting the next section of page typeif (count($bits) > $i) {$pluginname = $bits[$i];$directory = core_component::get_plugin_directory($possiblecomponent, $pluginname);if (!empty($directory)){$libfile = $directory.'/lib.php';if (file_exists($libfile)) {require_once($libfile);$function = $possiblecomponent.'_'.$pluginname.'_page_type_list';if (!function_exists($function)) {$function = $pluginname.'_page_type_list';}if (function_exists($function)) {if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {break;}}}}}//we'll only get to here if we still don't have any patterns//the plugin type may have a callback$directory = $plugins[$possiblecomponent];$libfile = $directory.'/lib.php';if (file_exists($libfile)) {require_once($libfile);$function = $possiblecomponent.'_page_type_list';if (function_exists($function)) {if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {break;}}}}}if (empty($patterns)) {$patterns = default_page_type_list($pagetype, $parentcontext, $currentcontext);}// Ensure that the * pattern is always available if editing block 'at distance', so// we always can 'bring back' it to the original context. MDL-30340if ((!isset($currentcontext) or !isset($parentcontext) or $currentcontext->id != $parentcontext->id) && !isset($patterns['*'])) {// TODO: We could change the string here, showing its 'bring back' meaning$patterns['*'] = get_string('page-x', 'pagetype');}return $patterns;}/*** Generates a default page type list when a more appropriate callback cannot be decided upon.** @param string $pagetype* @param stdClass $parentcontext* @param stdClass $currentcontext* @return array*/function default_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {// Generate page type patterns based on current page type if// callbacks haven't been defined$patterns = array($pagetype => $pagetype);$bits = explode('-', $pagetype);while (count($bits) > 0) {$pattern = implode('-', $bits) . '-*';$pagetypestringname = 'page-'.str_replace('*', 'x', $pattern);// guessing page type descriptionif (get_string_manager()->string_exists($pagetypestringname, 'pagetype')) {$patterns[$pattern] = get_string($pagetypestringname, 'pagetype');} else {$patterns[$pattern] = $pattern;}array_pop($bits);}$patterns['*'] = get_string('page-x', 'pagetype');return $patterns;}/*** Generates the page type list for the my moodle page** @param string $pagetype* @param stdClass $parentcontext* @param stdClass $currentcontext* @return array*/function my_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {return array('my-index' => get_string('page-my-index', 'pagetype'));}/*** Generates the page type list for a module by either locating and using the modules callback* or by generating a default list.** @param string $pagetype* @param stdClass $parentcontext* @param stdClass $currentcontext* @return array*/function mod_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {$patterns = plugin_page_type_list($pagetype, $parentcontext, $currentcontext);if (empty($patterns)) {// if modules don't have callbacks// generate two default page type patterns for modules only$bits = explode('-', $pagetype);$patterns = array($pagetype => $pagetype);if ($bits[2] == 'view') {$patterns['mod-*-view'] = get_string('page-mod-x-view', 'pagetype');} else if ($bits[2] == 'index') {$patterns['mod-*-index'] = get_string('page-mod-x-index', 'pagetype');}}return $patterns;}/// Functions update the blocks if required by the request parameters ==========/*** Return a {@link block_contents} representing the add a new block UI, if* this user is allowed to see it.** @return ?block_contents an appropriate block_contents, or null if the user* cannot add any blocks here.*/function block_add_block_ui($page, $output) {global $CFG, $OUTPUT;if (!$page->user_is_editing() || !$page->user_can_edit_blocks()) {return null;}$bc = new block_contents();$bc->title = get_string('addblock');$bc->add_class('block_adminblock');$bc->attributes['data-block'] = 'adminblock';$missingblocks = $page->blocks->get_addable_blocks();if (empty($missingblocks)) {$bc->content = get_string('noblockstoaddhere');return $bc;}$menu = array();foreach ($missingblocks as $block) {$menu[$block->name] = $block->title;}$actionurl = new moodle_url($page->url, array('sesskey'=>sesskey()));$select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');$select->set_label(get_string('addblock'), array('class'=>'accesshide'));$bc->content = $OUTPUT->render($select);return $bc;}/*** Actually delete from the database any blocks that are currently on this page,* but which should not be there according to blocks_name_allowed_in_format.** @todo Write/Fix this function. Currently returns immediately* @param $course*/function blocks_remove_inappropriate($course) {// TODOreturn;/*$blockmanager = blocks_get_by_page($page);if (empty($blockmanager)) {return;}if (($pageformat = $page->pagetype) == NULL) {return;}foreach($blockmanager as $region) {foreach($region as $instance) {$block = blocks_get_record($instance->blockid);if(!blocks_name_allowed_in_format($block->name, $pageformat)) {blocks_delete_instance($instance->instance);}}}*/}/*** Check that a given name is in a permittable format** @param string $name* @param string $pageformat* @return bool*/function blocks_name_allowed_in_format($name, $pageformat) {$accept = NULL;$maxdepth = -1;if (!$bi = block_instance($name)) {return false;}$formats = $bi->applicable_formats();if (!$formats) {$formats = array();}foreach ($formats as $format => $allowed) {$formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';$depth = substr_count($format, '-');if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {$maxdepth = $depth;$accept = $allowed;}}if ($accept === NULL) {$accept = !empty($formats['all']);}return $accept;}/*** Delete a block, and associated data.** @param object $instance a row from the block_instances table* @param bool $nolongerused legacy parameter. Not used, but kept for backwards compatibility.* @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.*/function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {global $DB;// Allow plugins to use this block before we completely delete it.if ($pluginsfunction = get_plugins_with_function('pre_block_delete')) {foreach ($pluginsfunction as $plugintype => $plugins) {foreach ($plugins as $pluginfunction) {$pluginfunction($instance);}}}if ($block = block_instance($instance->blockname, $instance)) {$block->instance_delete();}context_helper::delete_instance(CONTEXT_BLOCK, $instance->id);if (!$skipblockstables) {$DB->delete_records('block_positions', array('blockinstanceid' => $instance->id));$DB->delete_records('block_instances', array('id' => $instance->id));$DB->delete_records_list('user_preferences', 'name', array('block'.$instance->id.'hidden','docked_block_instance_'.$instance->id));}}/*** Delete multiple blocks at once.** @param array $instanceids A list of block instance ID.*/function blocks_delete_instances($instanceids) {global $DB;$limit = 1000;$count = count($instanceids);$chunks = [$instanceids];if ($count > $limit) {$chunks = array_chunk($instanceids, $limit);}// Perform deletion for each chunk.foreach ($chunks as $chunk) {$instances = $DB->get_recordset_list('block_instances', 'id', $chunk);foreach ($instances as $instance) {blocks_delete_instance($instance, false, true);}$instances->close();$DB->delete_records_list('block_positions', 'blockinstanceid', $chunk);$DB->delete_records_list('block_instances', 'id', $chunk);$preferences = array();foreach ($chunk as $instanceid) {$preferences[] = 'block' . $instanceid . 'hidden';$preferences[] = 'docked_block_instance_' . $instanceid;}$DB->delete_records_list('user_preferences', 'name', $preferences);}}/*** Delete all the blocks that belong to a particular context.** @param int $contextid the context id.*/function blocks_delete_all_for_context($contextid) {global $DB;$instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));foreach ($instances as $instance) {blocks_delete_instance($instance, true);}$instances->close();$DB->delete_records('block_instances', array('parentcontextid' => $contextid));$DB->delete_records('block_positions', array('contextid' => $contextid));}/*** Set a block to be visible or hidden on a particular page.** @param object $instance a row from the block_instances, preferably LEFT JOINed with the* block_positions table as return by block_manager.* @param moodle_page $page the back to set the visibility with respect to.* @param integer $newvisibility 1 for visible, 0 for hidden.*/function blocks_set_visibility($instance, $page, $newvisibility) {global $DB;if (!empty($instance->blockpositionid)) {// Already have local information on this page.$DB->set_field('block_positions', 'visible', $newvisibility, array('id' => $instance->blockpositionid));return;}// Create a new block_positions record.$bp = new stdClass;$bp->blockinstanceid = $instance->id;$bp->contextid = $page->context->id;$bp->pagetype = $page->pagetype;if ($page->subpage) {$bp->subpage = $page->subpage;}$bp->visible = $newvisibility;$bp->region = $instance->defaultregion;$bp->weight = $instance->defaultweight;$DB->insert_record('block_positions', $bp);}/*** Get the block record for a particular blockid - that is, a particular type os block.** @param $int blockid block type id. If null, an array of all block types is returned.* @param bool $notusedanymore No longer used.* @return array|object|false row from block table, or all rows.*/function blocks_get_record($blockid = NULL, $notusedanymore = false) {global $PAGE;$blocks = $PAGE->blocks->get_installed_blocks();if ($blockid === NULL) {return $blocks;} else if (isset($blocks[$blockid])) {return $blocks[$blockid];} else {return false;}}/*** Find a given block by its blockid within a provide array** @param int $blockid* @param array $blocksarray* @return bool|object Instance if found else false*/function blocks_find_block($blockid, $blocksarray) {if (empty($blocksarray)) {return false;}foreach($blocksarray as $blockgroup) {if (empty($blockgroup)) {continue;}foreach($blockgroup as $instance) {if($instance->blockid == $blockid) {return $instance;}}}return false;}// Functions for programatically adding default blocks to pages ================/*** Parse a list of default blocks. See config-dist for a description of the format.** @param string $blocksstr Determines the starting point that the blocks are added in the region.* @return array the parsed list of default blocks*/function blocks_parse_default_blocks_list($blocksstr) {$blocks = array();$bits = explode(':', $blocksstr);if (!empty($bits)) {$leftbits = trim(array_shift($bits));if ($leftbits != '') {$blocks[BLOCK_POS_LEFT] = explode(',', $leftbits);}}if (!empty($bits)) {$rightbits = trim(array_shift($bits));if ($rightbits != '') {$blocks[BLOCK_POS_RIGHT] = explode(',', $rightbits);}}return $blocks;}/*** @return array the blocks that should be added to the site course by default.*/function blocks_get_default_site_course_blocks() {global $CFG;if (isset($CFG->defaultblocks_site)) {return blocks_parse_default_blocks_list($CFG->defaultblocks_site);} else {return array(BLOCK_POS_LEFT => array(),BLOCK_POS_RIGHT => array());}}/*** Add the default blocks to a course.** @param object $course a course object.*/function blocks_add_default_course_blocks($course) {global $CFG;if (isset($CFG->defaultblocks_override)) {$blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override);} else if ($course->id == SITEID) {$blocknames = blocks_get_default_site_course_blocks();} else if (isset($CFG->{'defaultblocks_' . $course->format})) {$blocknames = blocks_parse_default_blocks_list($CFG->{'defaultblocks_' . $course->format});} else {require_once($CFG->dirroot. '/course/lib.php');$blocknames = course_get_format($course)->get_default_blocks();}if ($course->id == SITEID) {$pagetypepattern = 'site-index';} else {$pagetypepattern = 'course-view-*';}$page = new moodle_page();$page->set_course($course);$page->blocks->add_blocks($blocknames, $pagetypepattern);}/*** Add the default system-context blocks. E.g. the admin tree.*/function blocks_add_default_system_blocks() {global $DB;$page = new moodle_page();$page->set_context(context_system::instance());// We don't add blocks required by the theme, they will be auto-created.$page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('admin_bookmarks')), 'admin-*', null, null, 2);if ($defaultmypage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => 1))) {$subpagepattern = $defaultmypage->id;} else {$subpagepattern = null;}if ($defaultmycoursespage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__courses', 'private' => 0))) {$mycoursesubpagepattern = $defaultmycoursespage->id;} else {$mycoursesubpagepattern = null;}$page->blocks->add_blocks([BLOCK_POS_RIGHT => ['recentlyaccesseditems',],'content' => ['timeline','calendar_month',]],'my-index',$subpagepattern);$page->blocks->add_blocks(['content' => ['myoverview']],'my-index',$mycoursesubpagepattern);}