See Release Notes
Long Term Support Release
<?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 string URL for moving block $this->movingblock to this position.> * @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 1 if ($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 blocks if ($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 = "SELECT bi.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 $ccselect FROM {block_instances} bi JOIN {block} b ON bi.blockname = b.name LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id AND bp.contextid = :contextid1 AND bp.pagetype = :pagetype AND bp.subpage = :subpage1 LEFT JOIN {block_positions} bs ON bs.blockinstanceid = bi.id AND bs.contextid = :contextid4 AND bs.pagetype = :pagetype2 AND bs.subpage = :subpage3 $ccjoin WHERE $contexttest AND bi.pagetypepattern $pagetypepatterntest AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2) $visiblecheck AND b.visible = 1 $requiredbythemecheck ORDER BY COALESCE(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 setup if ($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; * When passed a block name create a new instance of the block in the specified region. > $this->load_blocks(); * > } * @param string $blockname Name of the block to add. > return $block;* @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;> 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 options if (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 option if (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)< $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);> 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 $pagetypepattern optional. Passed to {@link add_block()} * @param string $subpagepattern optional. Passed to {@link add_block()} * @param boolean $showinsubcontexts optional. Passed to {@link add_block()} * @param integer $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 $output The core_renderer to use when generating the output. (Need to get icon paths.) < * @return an array in the format for {@link block_contents::$controls}> * @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,< array('class' => 'editing_edit')> [ > '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 * Handle showing or hiding a block. > * * @return boolean true if anything was done. False if not. > * @param string $blockname name of the block plugin (without block_ prefix) */ > * @return string public function process_url_show_hide() { > */ if ($blockid = optional_param('bui_hideid', null, PARAM_INT)) { > public static function get_block_edit_form_class(string $blockname): string { $newvisibility = 0; > global $CFG; } else if ($blockid = optional_param('bui_showid', null, PARAM_INT)) { > require_once("$CFG->dirroot/blocks/moodleblock.class.php"); $newvisibility = 1; > $blockname = clean_param($blockname, PARAM_PLUGIN); } else { > $formfile = $CFG->dirroot . '/blocks/' . $blockname . '/edit_form.php'; return false; > if (is_readable($formfile)) { } > require_once($CFG->dirroot . '/blocks/edit_form.php'); > require_once($formfile); require_sesskey(); > $classname = 'block_' . $blockname . '_edit_form'; > if (!class_exists($classname)) { $block = $this->page->blocks->find_instance($blockid); > $classname = 'block_edit_form'; > } if (!$this->page->user_can_edit_blocks()) { > } else { throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('hideshowblocks')); > require_once($CFG->dirroot . '/blocks/edit_form.php'); } else if (!$block->instance_can_be_hidden()) { > $classname = 'block_edit_form'; return false; > } } > return $classname; > } 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;< $formfile = $CFG->dirroot . '/blocks/' . $block->name() . '/edit_form.php'; < if (is_readable($formfile)) { < require_once($formfile); < $classname = 'block_' . $block->name() . '_edit_form'; < if (!class_exists($classname)) { < $classname = 'block_edit_form'; < } < } else { < $classname = 'block_edit_form'; < } < < $mform = new $classname($editpage->url, $block, $this->page);> $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()) {> $bi = new stdClass; > $this->save_block_data($block, $data); $bi->id = $block->instance->id; > redirect($this->page->url); > // This may get overwritten by the special case handling below. > } else { $bi->pagetypepattern = $data->bui_pagetypepattern; > $strheading = get_string('blockconfiga', 'moodle', $block->get_title()); $bi->showinsubcontexts = (bool) $data->bui_contexts; > $editpage->set_title($strheading); if (empty($data->bui_subpagepattern) || $data->bui_subpagepattern == '%@NULL@%') { > $editpage->set_heading($strheading); $bi->subpagepattern = null; > $bits = explode('-', $this->page->pagetype); } else { > if ($bits[0] == 'tag' && !empty($this->page->subpage)) { $bi->subpagepattern = $data->bui_subpagepattern; > // better navbar for tag pages } > $editpage->navbar->add(get_string('tags'), new moodle_url('/tag/')); > $tag = core_tag_tag::get($this->page->subpage); $systemcontext = context_system::instance(); > // tag search page doesn't have subpageid $frontpagecontext = context_course::instance(SITEID); > if ($tag) { $parentcontext = context::instance_by_id($data->bui_parentcontextid); > $editpage->navbar->add($tag->get_display_name(), $tag->get_view_url()); > } // Updating stickiness and contexts. See MDL-21375 for details. > } if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination > $editpage->navbar->add($block->get_title()); > $editpage->navbar->add(get_string('configuration')); // Explicitly set the default context > echo $output->header(); $bi->parentcontextid = $parentcontext->id; > $mform->display(); > echo $output->footer(); if ($data->bui_editingatfrontpage) { // The block is being edited on the front page > exit; > } // 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) { > * Updates block configuration in the database case BUI_CONTEXTS_ENTIRE_SITE: > * // The user wants to show the block across the entire site > * @param block_base $block $bi->parentcontextid = $systemcontext->id; > * @param stdClass $data data from the block edit form $bi->showinsubcontexts = true; > * @return void $bi->pagetypepattern = '*'; > */ break; > public function save_block_data(block_base $block, stdClass $data): void { case BUI_CONTEXTS_FRONTPAGE_SUBS: > global $DB; // The user wants the block shown on the front page and all subcontexts >< if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination> if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination.< // Explicitly set the default context> // Explicitly set the default context.< if ($data->bui_editingatfrontpage) { // The block is being edited on the front page> if ($data->bui_editingatfrontpage) { // The block is being edited on the front page.< // totally derived from the context menu. Here are the excpetions. MDL-30340> // totally derived from the context menu. Here are the excpetions. MDL-30340 .< // The user wants to show the block across the entire site> // The user wants to show the block across the entire site.< // The user wants the block shown on the front page and all subcontexts> // The user wants the block shown on the front page and all subcontexts.< // The user want to show the front page on the frontpage only> // The user want to show the front page on the frontpage only.$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> // in case the front page grows site-index-* subpages of its own later.break; } } } $bits = explode('-', $bi->pagetypepattern);< // hacks for some contexts> // 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> // 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' or $bits[0] == 'my') { < // we don't need subpagepattern in usercontext> // 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); }< < 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 subpageid < if ($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; < }} /** * 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 weights foreach ($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 $instance block_instances DB table row (optional).> * @param stdClass $instance block_instances DB table row (optional).* @param moodle_page $page the page this block is appearing on. * @return block_base 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; if(empty($blockname)) { 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 component if (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 callback if (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 type if (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-30340 if ((!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 description if (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) { // TODO return; /* $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 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 ); }