3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
19 * Block Class and Functions
21 * This file defines the {@link block_manager} class,
25 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 defined('MOODLE_INTERNAL') ||
die();
32 * Default names for the block regions in the standard theme.
34 define('BLOCK_POS_LEFT', 'side-pre');
35 define('BLOCK_POS_RIGHT', 'side-post');
38 define('BUI_CONTEXTS_FRONTPAGE_ONLY', 0);
39 define('BUI_CONTEXTS_FRONTPAGE_SUBS', 1);
40 define('BUI_CONTEXTS_ENTIRE_SITE', 2);
42 define('BUI_CONTEXTS_CURRENT', 0);
43 define('BUI_CONTEXTS_CURRENT_SUBS', 1);
45 // Position of "Add block" control, to be used in theme config as a value for $THEME->addblockposition:
46 // - default: as a fake block that is displayed in editing mode
47 // - flatnav: "Add block" item in the flat navigation drawer in editing mode
48 // - custom: none of the above, theme will take care of displaying the control.
49 define('BLOCK_ADDBLOCK_POSITION_DEFAULT', 0);
50 define('BLOCK_ADDBLOCK_POSITION_FLATNAV', 1);
51 define('BLOCK_ADDBLOCK_POSITION_CUSTOM', -1);
54 * Exception thrown when someone tried to do something with a block that does
55 * not exist on a page.
57 * @copyright 2009 Tim Hunt
58 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
61 class block_not_on_page_exception
extends moodle_exception
{
64 * @param int $instanceid the block instance id of the block that was looked for.
65 * @param object $page the current page.
67 public function __construct($instanceid, $page) {
69 $a->instanceid
= $instanceid;
70 $a->url
= $page->url
->out();
71 parent
::__construct('blockdoesnotexistonpage', '', $page->url
->out(), $a);
76 * This class keeps track of the block that should appear on a moodle_page.
78 * The page to work with as passed to the constructor.
80 * @copyright 2009 Tim Hunt
81 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
86 * The UI normally only shows block weights between -MAX_WEIGHT and MAX_WEIGHT,
87 * although other weights are valid.
89 const MAX_WEIGHT
= 10;
91 /// Field declarations =========================================================
94 * the moodle_page we are managing blocks for.
99 /** @var array region name => 1.*/
100 protected $regions = array();
102 /** @var string the region where new blocks are added.*/
103 protected $defaultregion = null;
105 /** @var array will be $DB->get_records('blocks') */
106 protected $allblocks = null;
109 * @var array blocks that this user can add to this page. Will be a subset
110 * of $allblocks, but with array keys block->name. Access this via the
111 * {@link get_addable_blocks()} method to ensure it is lazy-loaded.
113 protected $addableblocks = null;
116 * Will be an array region-name => array(db rows loaded in load_blocks);
119 protected $birecordsbyregion = null;
122 * array region-name => array(block objects); populated as necessary by
123 * the ensure_instances_exist method.
126 protected $blockinstances = array();
129 * array region-name => array(block_contents objects) what actually needs to
130 * be displayed in each region.
133 protected $visibleblockcontent = array();
136 * array region-name => array(block_contents objects) extra block-like things
137 * to be displayed in each region, before the real blocks.
140 protected $extracontent = array();
143 * Used by the block move id, to track whether a block is currently being moved.
145 * When you click on the move icon of a block, first the page needs to reload with
146 * extra UI for choosing a new position for a particular block. In that situation
147 * this field holds the id of the block being moved.
151 protected $movingblock = null;
154 * Show only fake blocks
156 protected $fakeblocksonly = false;
158 /// Constructor ================================================================
162 * @param object $page the moodle_page object object we are managing the blocks for,
163 * or a reasonable faxilimily. (See the comment at the top of this class
164 * and {@link http://en.wikipedia.org/wiki/Duck_typing})
166 public function __construct($page) {
170 /// Getter methods =============================================================
173 * Get an array of all region names on this page where a block may appear
175 * @return array the internal names of the regions on this page where block may appear.
177 public function get_regions() {
178 if (is_null($this->defaultregion
)) {
179 $this->page
->initialise_theme_and_output();
181 return array_keys($this->regions
);
185 * Get the region name of the region blocks are added to by default
187 * @return string the internal names of the region where new blocks are added
188 * by default, and where any blocks from an unrecognised region are shown.
189 * (Imagine that blocks were added with one theme selected, then you switched
190 * to a theme with different block positions.)
192 public function get_default_region() {
193 $this->page
->initialise_theme_and_output();
194 return $this->defaultregion
;
198 * The list of block types that may be added to this page.
200 * @return array block name => record from block table.
202 public function get_addable_blocks() {
203 $this->check_is_loaded();
205 if (!is_null($this->addableblocks
)) {
206 return $this->addableblocks
;
210 $this->addableblocks
= array();
212 $allblocks = blocks_get_record();
213 if (empty($allblocks)) {
214 return $this->addableblocks
;
217 $undeletableblocks = self
::get_undeletable_block_types();
218 $unaddablebythemeblocks = $this->get_unaddable_by_theme_block_types();
219 $requiredbythemeblocks = $this->get_required_by_theme_block_types();
220 $pageformat = $this->page
->pagetype
;
221 foreach($allblocks as $block) {
222 if (!$bi = block_instance($block->name
)) {
225 if ($block->visible
&& !in_array($block->name
, $undeletableblocks) &&
226 !in_array($block->name
, $requiredbythemeblocks) &&
227 !in_array($block->name
, $unaddablebythemeblocks) &&
228 $bi->can_block_be_added($this->page
) &&
229 ($bi->instance_allow_multiple() ||
!$this->is_block_present($block->name
)) &&
230 blocks_name_allowed_in_format($block->name
, $pageformat) &&
231 $bi->user_can_addto($this->page
)) {
232 $block->title
= $bi->get_title();
233 $this->addableblocks
[$block->name
] = $block;
237 core_collator
::asort_objects_by_property($this->addableblocks
, 'title');
238 return $this->addableblocks
;
242 * Given a block name, find out of any of them are currently present in the page
244 * @param string $blockname - the basic name of a block (eg "navigation")
245 * @return boolean - is there one of these blocks in the current page?
247 public function is_block_present($blockname) {
248 if (empty($this->blockinstances
)) {
252 $requiredbythemeblocks = $this->get_required_by_theme_block_types();
253 foreach ($this->blockinstances
as $region) {
254 foreach ($region as $instance) {
255 if (empty($instance->instance
->blockname
)) {
258 if ($instance->instance
->blockname
== $blockname) {
259 if ($instance->instance
->requiredbytheme
) {
260 if (!in_array($blockname, $requiredbythemeblocks)) {
272 * Find out if a block type is known by the system
274 * @param string $blockname the name of the type of block.
275 * @param boolean $includeinvisible if false (default) only check 'visible' blocks, that is, blocks enabled by the admin.
276 * @return boolean true if this block in installed.
278 public function is_known_block_type($blockname, $includeinvisible = false) {
279 $blocks = $this->get_installed_blocks();
280 foreach ($blocks as $block) {
281 if ($block->name
== $blockname && ($includeinvisible ||
$block->visible
)) {
289 * Find out if a region exists on a page
291 * @param string $region a region name
292 * @return boolean true if this region exists on this page.
294 public function is_known_region($region) {
295 if (empty($region)) {
298 return array_key_exists($region, $this->regions
);
302 * Get an array of all blocks within a given region
304 * @param string $region a block region that exists on this page.
305 * @return array of block instances.
307 public function get_blocks_for_region($region) {
308 $this->check_is_loaded();
309 $this->ensure_instances_exist($region);
310 return $this->blockinstances
[$region];
314 * Returns an array of block content objects that exist in a region
316 * @param string $region a block region that exists on this page.
317 * @return array of block block_contents objects for all the blocks in a region.
319 public function get_content_for_region($region, $output) {
320 $this->check_is_loaded();
321 $this->ensure_content_created($region, $output);
322 return $this->visibleblockcontent
[$region];
326 * Returns an array of block content objects for all the existings regions
328 * @param renderer_base $output the rendered to use
329 * @return array of block block_contents objects for all the blocks in all regions.
332 public function get_content_for_all_regions($output) {
334 $this->check_is_loaded();
336 foreach ($this->regions
as $region => $val) {
337 $this->ensure_content_created($region, $output);
338 $contents[$region] = $this->visibleblockcontent
[$region];
344 * Helper method used by get_content_for_region.
345 * @param string $region region name
346 * @param float $weight weight. May be fractional, since you may want to move a block
347 * between ones with weight 2 and 3, say ($weight would be 2.5).
348 * @return string URL for moving block $this->movingblock to this position.
350 protected function get_move_target_url($region, $weight) {
351 return new moodle_url($this->page
->url
, array('bui_moveid' => $this->movingblock
,
352 'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()));
356 * Determine whether a region contains anything. (Either any real blocks, or
357 * the add new block UI.)
359 * (You may wonder why the $output parameter is required. Unfortunately,
360 * because of the way that blocks work, the only reliable way to find out
361 * if a block will be visible is to get the content for output, and to
362 * get the content, you need a renderer. Fortunately, this is not a
363 * performance problem, because we cache the output that is generated, and
364 * in almost every case where we call region_has_content, we are about to
365 * output the blocks anyway, so we are not doing wasted effort.)
367 * @param string $region a block region that exists on this page.
368 * @param core_renderer $output a core_renderer. normally the global $OUTPUT.
369 * @return boolean Whether there is anything in this region.
371 public function region_has_content($region, $output) {
373 if (!$this->is_known_region($region)) {
376 $this->check_is_loaded();
377 $this->ensure_content_created($region, $output);
378 // if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks()) {
379 // Mark Nielsen's patch - part 1
380 if ($this->page
->user_is_editing() && $this->page
->user_can_edit_blocks() && $this->movingblock
) {
381 // If editing is on, we need all the block regions visible, for the
385 return !empty($this->visibleblockcontent
[$region]) ||
!empty($this->extracontent
[$region]);
389 * Determine whether a region contains any fake blocks.
391 * (Fake blocks are typically added to the extracontent array per region)
393 * @param string $region a block region that exists on this page.
394 * @return boolean Whether there are fake blocks in this region.
396 public function region_has_fakeblocks($region): bool {
397 return !empty($this->extracontent
[$region]);
401 * Get an array of all of the installed blocks.
403 * @return array contents of the block table.
405 public function get_installed_blocks() {
407 if (is_null($this->allblocks
)) {
408 $this->allblocks
= $DB->get_records('block');
410 return $this->allblocks
;
414 * @return array names of block types that must exist on every page with this theme.
416 public function get_required_by_theme_block_types() {
417 $requiredbythemeblocks = false;
418 if (isset($this->page
->theme
->requiredblocks
)) {
419 $requiredbythemeblocks = $this->page
->theme
->requiredblocks
;
422 if ($requiredbythemeblocks === false) {
423 return array('navigation', 'settings');
424 } else if ($requiredbythemeblocks === '') {
426 } else if (is_string($requiredbythemeblocks)) {
427 return explode(',', $requiredbythemeblocks);
429 return $requiredbythemeblocks;
434 * It returns the list of blocks that can't be displayed in the "Add a block" list.
435 * This information is taken from the unaddableblocks theme setting.
437 * @return array A list with the blocks that won't be displayed in the "Add a block" list.
439 public function get_unaddable_by_theme_block_types(): array {
440 $unaddablebythemeblocks = [];
441 if (isset($this->page
->theme
->settings
->unaddableblocks
) && !empty($this->page
->theme
->settings
->unaddableblocks
)) {
442 $unaddablebythemeblocks = array_map('trim', explode(',', $this->page
->theme
->settings
->unaddableblocks
));
445 return $unaddablebythemeblocks;
449 * Make this block type undeletable and unaddable.
451 * @param mixed $blockidorname string or int
453 public static function protect_block($blockidorname) {
456 $syscontext = context_system
::instance();
458 require_capability('moodle/site:config', $syscontext);
461 if (is_int($blockidorname)) {
462 $block = $DB->get_record('block', array('id' => $blockidorname), 'id, name', MUST_EXIST
);
464 $block = $DB->get_record('block', array('name' => $blockidorname), 'id, name', MUST_EXIST
);
466 $undeletableblocktypes = self
::get_undeletable_block_types();
467 if (!in_array($block->name
, $undeletableblocktypes)) {
468 $undeletableblocktypes[] = $block->name
;
469 set_config('undeletableblocktypes', implode(',', $undeletableblocktypes));
470 add_to_config_log('block_protect', "0", "1", $block->name
);
475 * Make this block type deletable and addable.
477 * @param mixed $blockidorname string or int
479 public static function unprotect_block($blockidorname) {
482 $syscontext = context_system
::instance();
484 require_capability('moodle/site:config', $syscontext);
487 if (is_int($blockidorname)) {
488 $block = $DB->get_record('block', array('id' => $blockidorname), 'id, name', MUST_EXIST
);
490 $block = $DB->get_record('block', array('name' => $blockidorname), 'id, name', MUST_EXIST
);
492 $undeletableblocktypes = self
::get_undeletable_block_types();
493 if (in_array($block->name
, $undeletableblocktypes)) {
494 $undeletableblocktypes = array_diff($undeletableblocktypes, array($block->name
));
495 set_config('undeletableblocktypes', implode(',', $undeletableblocktypes));
496 add_to_config_log('block_protect', "1", "0", $block->name
);
502 * Get the list of "protected" blocks via admin block manager ui.
504 * @return array names of block types that cannot be added or deleted. E.g. array('navigation','settings').
506 public static function get_undeletable_block_types() {
508 $undeletableblocks = false;
509 if (isset($CFG->undeletableblocktypes
)) {
510 $undeletableblocks = $CFG->undeletableblocktypes
;
513 if (empty($undeletableblocks)) {
515 } else if (is_string($undeletableblocks)) {
516 return explode(',', $undeletableblocks);
518 return $undeletableblocks;
522 /// Setter methods =============================================================
525 * Add a region to a page
527 * @param string $region add a named region where blocks may appear on the current page.
528 * This is an internal name, like 'side-pre', not a string to display in the UI.
529 * @param bool $custom True if this is a custom block region, being added by the page rather than the theme layout.
531 public function add_region($region, $custom = true) {
533 $this->check_not_yet_loaded();
535 if (array_key_exists($region, $this->regions
)) {
536 // This here is EXACTLY why we should not be adding block regions into a page. It should
537 // ALWAYS be done in a theme layout.
538 debugging('A custom region conflicts with a block region in the theme.', DEBUG_DEVELOPER
);
540 // We need to register this custom region against the page type being used.
541 // This allows us to check, when performing block actions, that unrecognised regions can be worked with.
542 $type = $this->page
->pagetype
;
543 if (!isset($SESSION->custom_block_regions
)) {
544 $SESSION->custom_block_regions
= array($type => array($region));
545 } else if (!isset($SESSION->custom_block_regions
[$type])) {
546 $SESSION->custom_block_regions
[$type] = array($region);
547 } else if (!in_array($region, $SESSION->custom_block_regions
[$type])) {
548 $SESSION->custom_block_regions
[$type][] = $region;
551 $this->regions
[$region] = 1;
553 // Checking the actual property instead of calling get_default_region as it ends up in a recursive call.
554 if (empty($this->defaultregion
)) {
555 $this->set_default_region($region);
560 * Add an array of regions
563 * @param array $regions this utility method calls add_region for each array element.
565 public function add_regions($regions, $custom = true) {
566 foreach ($regions as $region) {
567 $this->add_region($region, $custom);
572 * Finds custom block regions associated with a page type and registers them with this block manager.
574 * @param string $pagetype
576 public function add_custom_regions_for_pagetype($pagetype) {
578 if (isset($SESSION->custom_block_regions
[$pagetype])) {
579 foreach ($SESSION->custom_block_regions
[$pagetype] as $customregion) {
580 $this->add_region($customregion, false);
586 * Set the default region for new blocks on the page
588 * @param string $defaultregion the internal names of the region where new
589 * blocks should be added by default, and where any blocks from an
590 * unrecognised region are shown.
592 public function set_default_region($defaultregion) {
593 $this->check_not_yet_loaded();
594 if ($defaultregion) {
595 $this->check_region_is_known($defaultregion);
597 $this->defaultregion
= $defaultregion;
601 * Add something that looks like a block, but which isn't an actual block_instance,
604 * @param block_contents $bc the content of the block-like thing.
605 * @param string $region a block region that exists on this page.
607 public function add_fake_block($bc, $region) {
608 $this->page
->initialise_theme_and_output();
609 if (!$this->is_known_region($region)) {
610 $region = $this->get_default_region();
612 if (array_key_exists($region, $this->visibleblockcontent
)) {
613 throw new coding_exception('block_manager has already prepared the blocks in region ' .
614 $region . 'for output. It is too late to add a fake block.');
616 if (!isset($bc->attributes
['data-block'])) {
617 $bc->attributes
['data-block'] = '_fake';
619 $bc->attributes
['class'] .= ' block_fake';
620 $this->extracontent
[$region][] = $bc;
624 * Checks to see whether all of the blocks within the given region are docked
626 * @see region_uses_dock
627 * @param string $region
628 * @return bool True if all of the blocks within that region are docked
630 * Return false as from MDL-64506
632 public function region_completely_docked($region, $output) {
637 * Checks to see whether any of the blocks within the given regions are docked
639 * @see region_completely_docked
640 * @param array|string $regions array of regions (or single region)
641 * @return bool True if any of the blocks within that region are docked
643 * Return false as from MDL-64506
645 public function region_uses_dock($regions, $output) {
649 /// Actions ====================================================================
652 * This method actually loads the blocks for our page from the database.
654 * @param boolean|null $includeinvisible
655 * null (default) - load hidden blocks if $this->page->user_is_editing();
656 * true - load hidden blocks.
657 * false - don't load hidden blocks.
659 public function load_blocks($includeinvisible = null) {
662 if (!is_null($this->birecordsbyregion
)) {
667 if ($CFG->version
< 2009050619) {
668 // Upgrade/install not complete. Don't try too show any blocks.
669 $this->birecordsbyregion
= array();
673 // Ensure we have been initialised.
674 if (is_null($this->defaultregion
)) {
675 $this->page
->initialise_theme_and_output();
676 // If there are still no block regions, then there are no blocks on this page.
677 if (empty($this->regions
)) {
678 $this->birecordsbyregion
= array();
683 // Check if we need to load normal blocks
684 if ($this->fakeblocksonly
) {
685 $this->birecordsbyregion
= $this->prepare_per_region_arrays();
689 // Exclude auto created blocks if they are not undeletable in this theme.
690 $requiredbytheme = $this->get_required_by_theme_block_types();
691 $requiredbythemecheck = '';
692 $requiredbythemeparams = array();
693 $requiredbythemenotparams = array();
694 if (!empty($requiredbytheme)) {
695 list($testsql, $requiredbythemeparams) = $DB->get_in_or_equal($requiredbytheme, SQL_PARAMS_NAMED
, 'requiredbytheme');
696 list($testnotsql, $requiredbythemenotparams) = $DB->get_in_or_equal($requiredbytheme, SQL_PARAMS_NAMED
,
697 'notrequiredbytheme', false);
698 $requiredbythemecheck = 'AND ((bi.blockname ' . $testsql . ' AND bi.requiredbytheme = 1) OR ' .
699 ' (bi.blockname ' . $testnotsql . ' AND bi.requiredbytheme = 0))';
701 $requiredbythemecheck = 'AND (bi.requiredbytheme = 0)';
704 if (is_null($includeinvisible)) {
705 $includeinvisible = $this->page
->user_is_editing();
707 if ($includeinvisible) {
710 $visiblecheck = 'AND (bp.visible = 1 OR bp.visible IS NULL) AND (bs.visible = 1 OR bs.visible IS NULL)';
713 $context = $this->page
->context
;
714 $contexttest = 'bi.parentcontextid IN (:contextid2, :contextid3)';
715 $parentcontextparams = array();
716 $parentcontextids = $context->get_parent_context_ids();
717 if ($parentcontextids) {
718 list($parentcontexttest, $parentcontextparams) =
719 $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED
, 'parentcontext');
720 $contexttest = "($contexttest OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontexttest))";
723 $pagetypepatterns = matching_page_type_patterns($this->page
->pagetype
);
724 list($pagetypepatterntest, $pagetypepatternparams) =
725 $DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED
, 'pagetypepatterntest');
727 $ccselect = ', ' . context_helper
::get_preload_record_columns_sql('ctx');
728 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = bi.id AND ctx.contextlevel = :contextlevel)";
730 $systemcontext = context_system
::instance();
732 'contextlevel' => CONTEXT_BLOCK
,
733 'subpage1' => $this->page
->subpage
,
734 'subpage2' => $this->page
->subpage
,
735 'subpage3' => $this->page
->subpage
,
736 'contextid1' => $context->id
,
737 'contextid2' => $context->id
,
738 'contextid3' => $systemcontext->id
,
739 'contextid4' => $systemcontext->id
,
740 'pagetype' => $this->page
->pagetype
,
741 'pagetype2' => $this->page
->pagetype
,
743 if ($this->page
->subpage
=== '') {
744 $params['subpage1'] = '';
745 $params['subpage2'] = '';
746 $params['subpage3'] = '';
750 COALESCE(bp.id, bs.id) AS blockpositionid,
753 bi.showinsubcontexts,
759 COALESCE(bp.visible, bs.visible, 1) AS visible,
760 COALESCE(bp.region, bs.region, bi.defaultregion) AS region,
761 COALESCE(bp.weight, bs.weight, bi.defaultweight) AS weight,
765 FROM {block_instances} bi
766 JOIN {block} b ON bi.blockname = b.name
767 LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id
768 AND bp.contextid = :contextid1
769 AND bp.pagetype = :pagetype
770 AND bp.subpage = :subpage1
771 LEFT JOIN {block_positions} bs ON bs.blockinstanceid = bi.id
772 AND bs.contextid = :contextid4
773 AND bs.pagetype = :pagetype2
774 AND bs.subpage = :subpage3
779 AND bi.pagetypepattern $pagetypepatterntest
780 AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)
783 $requiredbythemecheck
786 COALESCE(bp.region, bs.region, bi.defaultregion),
787 COALESCE(bp.weight, bs.weight, bi.defaultweight),
790 $allparams = $params +
$parentcontextparams +
$pagetypepatternparams +
$requiredbythemeparams +
$requiredbythemenotparams;
791 $blockinstances = $DB->get_recordset_sql($sql, $allparams);
793 $this->birecordsbyregion
= $this->prepare_per_region_arrays();
795 foreach ($blockinstances as $bi) {
796 context_helper
::preload_from_record($bi);
797 if ($this->is_known_region($bi->region
)) {
798 $this->birecordsbyregion
[$bi->region
][] = $bi;
803 $blockinstances->close();
805 // Pages don't necessarily have a defaultregion. The one time this can
806 // happen is when there are no theme block regions, but the script itself
807 // has a block region in the main content area.
808 if (!empty($this->defaultregion
)) {
809 $this->birecordsbyregion
[$this->defaultregion
] =
810 array_merge($this->birecordsbyregion
[$this->defaultregion
], $unknown);
815 * Add a block to the current page, or related pages. The block is added to
816 * context $this->page->contextid. If $pagetypepattern $subpagepattern
818 * @param string $blockname The type of block to add.
819 * @param string $region the block region on this page to add the block to.
820 * @param integer $weight determines the order where this block appears in the region.
821 * @param boolean $showinsubcontexts whether this block appears in subcontexts, or just the current context.
822 * @param string|null $pagetypepattern which page types this block should appear on. Defaults to just the current page type.
823 * @param string|null $subpagepattern which subpage this block should appear on. NULL = any (the default), otherwise only the specified subpage.
826 public function add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern = NULL, $subpagepattern = NULL) {
828 // Allow invisible blocks because this is used when adding default page blocks, which
829 // might include invisible ones if the user makes some default blocks invisible
830 $this->check_known_block_type($blockname, true);
831 $this->check_region_is_known($region);
833 if (empty($pagetypepattern)) {
834 $pagetypepattern = $this->page
->pagetype
;
837 $blockinstance = new stdClass
;
838 $blockinstance->blockname
= $blockname;
839 $blockinstance->parentcontextid
= $this->page
->context
->id
;
840 $blockinstance->showinsubcontexts
= !empty($showinsubcontexts);
841 $blockinstance->pagetypepattern
= $pagetypepattern;
842 $blockinstance->subpagepattern
= $subpagepattern;
843 $blockinstance->defaultregion
= $region;
844 $blockinstance->defaultweight
= $weight;
845 $blockinstance->configdata
= '';
846 $blockinstance->timecreated
= time();
847 $blockinstance->timemodified
= $blockinstance->timecreated
;
848 $blockinstance->id
= $DB->insert_record('block_instances', $blockinstance);
850 // Ensure the block context is created.
851 context_block
::instance($blockinstance->id
);
853 // If the new instance was created, allow it to do additional setup
854 if ($block = block_instance($blockname, $blockinstance)) {
855 $block->instance_create();
858 if (!is_null($this->birecordsbyregion
)) {
859 // If blocks were already loaded on this page, reload them.
860 $this->birecordsbyregion
= null;
861 $this->load_blocks();
867 * When passed a block name create a new instance of the block in the specified region.
869 * @param string $blockname Name of the block to add.
870 * @param null|string $blockregion If defined add the new block to the specified region.
871 * @return ?block_base
873 public function add_block_at_end_of_default_region($blockname, $blockregion = null) {
874 if (empty($this->birecordsbyregion
)) {
875 // No blocks or block regions exist yet.
879 if ($blockregion === null) {
880 $defaulregion = $this->get_default_region();
882 $defaulregion = $blockregion;
885 $lastcurrentblock = end($this->birecordsbyregion
[$defaulregion]);
886 if ($lastcurrentblock) {
887 $weight = $lastcurrentblock->weight +
1;
892 if ($this->page
->subpage
) {
893 $subpage = $this->page
->subpage
;
898 // Special case. Course view page type include the course format, but we
899 // want to add the block non-format-specifically.
900 $pagetypepattern = $this->page
->pagetype
;
901 if (strpos($pagetypepattern, 'course-view') === 0) {
902 $pagetypepattern = 'course-view-*';
905 // We should end using this for ALL the blocks, making always the 1st option
906 // the default one to be used. Until then, this is one hack to avoid the
907 // 'pagetypewarning' message on blocks initial edition (MDL-27829) caused by
908 // non-existing $pagetypepattern set. This way at least we guarantee one "valid"
909 // (the FIRST $pagetypepattern will be set)
911 // We are applying it to all blocks created in mod pages for now and only if the
912 // default pagetype is not one of the available options
913 if (preg_match('/^mod-.*-/', $pagetypepattern)) {
914 $pagetypelist = generate_page_type_patterns($this->page
->pagetype
, null, $this->page
->context
);
915 // Only go for the first if the pagetype is not a valid option
916 if (is_array($pagetypelist) && !array_key_exists($pagetypepattern, $pagetypelist)) {
917 $pagetypepattern = key($pagetypelist);
920 // Surely other pages like course-report will need this too, they just are not important
921 // enough now. This will be decided in the coming days. (MDL-27829, MDL-28150)
923 return $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);
927 * Convenience method, calls add_block repeatedly for all the blocks in $blocks. Optionally, a starting weight
928 * can be used to decide the starting point that blocks are added in the region, the weight is passed to {@link add_block}
929 * and incremented by the position of the block in the $blocks array
931 * @param array $blocks array with array keys the region names, and values an array of block names.
932 * @param string $pagetypepattern optional. Passed to {@link add_block()}
933 * @param string $subpagepattern optional. Passed to {@link add_block()}
934 * @param boolean $showinsubcontexts optional. Passed to {@link add_block()}
935 * @param integer $weight optional. Determines the starting point that the blocks are added in the region.
937 public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {
938 $initialweight = $weight;
939 $this->add_regions(array_keys($blocks), false);
940 foreach ($blocks as $region => $regionblocks) {
941 foreach ($regionblocks as $offset => $blockname) {
942 $weight = $initialweight +
$offset;
943 $this->add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern, $subpagepattern);
949 * Move a block to a new position on this page.
951 * If this block cannot appear on any other pages, then we change defaultposition/weight
952 * in the block_instances table. Otherwise we just set the position on this page.
954 * @param $blockinstanceid the block instance id.
955 * @param $newregion the new region name.
956 * @param $newweight the new weight.
958 public function reposition_block($blockinstanceid, $newregion, $newweight) {
961 $this->check_region_is_known($newregion);
962 $inst = $this->find_instance($blockinstanceid);
964 $bi = $inst->instance
;
965 if ($bi->weight
== $bi->defaultweight
&& $bi->region
== $bi->defaultregion
&&
966 !$bi->showinsubcontexts
&& strpos($bi->pagetypepattern
, '*') === false &&
967 (!$this->page
->subpage ||
$bi->subpagepattern
)) {
969 // Set default position
970 $newbi = new stdClass
;
971 $newbi->id
= $bi->id
;
972 $newbi->defaultregion
= $newregion;
973 $newbi->defaultweight
= $newweight;
974 $newbi->timemodified
= time();
975 $DB->update_record('block_instances', $newbi);
977 if ($bi->blockpositionid
) {
979 $bp->id
= $bi->blockpositionid
;
980 $bp->region
= $newregion;
981 $bp->weight
= $newweight;
982 $DB->update_record('block_positions', $bp);
986 // Just set position on this page.
988 $bp->region
= $newregion;
989 $bp->weight
= $newweight;
991 if ($bi->blockpositionid
) {
992 $bp->id
= $bi->blockpositionid
;
993 $DB->update_record('block_positions', $bp);
996 $bp->blockinstanceid
= $bi->id
;
997 $bp->contextid
= $this->page
->context
->id
;
998 $bp->pagetype
= $this->page
->pagetype
;
999 if ($this->page
->subpage
) {
1000 $bp->subpage
= $this->page
->subpage
;
1004 $bp->visible
= $bi->visible
;
1005 $DB->insert_record('block_positions', $bp);
1011 * Find a given block by its instance id
1013 * @param integer $instanceid
1014 * @return block_base
1016 public function find_instance($instanceid) {
1017 foreach ($this->regions
as $region => $notused) {
1018 $this->ensure_instances_exist($region);
1019 foreach($this->blockinstances
[$region] as $instance) {
1020 if ($instance->instance
->id
== $instanceid) {
1025 throw new block_not_on_page_exception($instanceid, $this->page
);
1028 /// Inner workings =============================================================
1031 * Check whether the page blocks have been loaded yet
1033 * @return void Throws coding exception if already loaded
1035 protected function check_not_yet_loaded() {
1036 if (!is_null($this->birecordsbyregion
)) {
1037 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.');
1042 * Check whether the page blocks have been loaded yet
1044 * Nearly identical to the above function {@link check_not_yet_loaded()} except different message
1046 * @return void Throws coding exception if already loaded
1048 protected function check_is_loaded() {
1049 if (is_null($this->birecordsbyregion
)) {
1050 throw new coding_exception('block_manager has not yet loaded the blocks, to it is too soon to request the information you asked for.');
1055 * Check if a block type is known and usable
1057 * @param string $blockname The block type name to search for
1058 * @param bool $includeinvisible Include disabled block types in the initial pass
1059 * @return void Coding Exception thrown if unknown or not enabled
1061 protected function check_known_block_type($blockname, $includeinvisible = false) {
1062 if (!$this->is_known_block_type($blockname, $includeinvisible)) {
1063 if ($this->is_known_block_type($blockname, true)) {
1064 throw new coding_exception('Unknown block type ' . $blockname);
1066 throw new coding_exception('Block type ' . $blockname . ' has been disabled by the administrator.');
1072 * Check if a region is known by its name
1074 * @param string $region
1075 * @return void Coding Exception thrown if the region is not known
1077 protected function check_region_is_known($region) {
1078 if (!$this->is_known_region($region)) {
1079 throw new coding_exception('Trying to reference an unknown block region ' . $region);
1084 * Returns an array of region names as keys and nested arrays for values
1086 * @return array an array where the array keys are the region names, and the array
1087 * values are empty arrays.
1089 protected function prepare_per_region_arrays() {
1091 foreach ($this->regions
as $region => $notused) {
1092 $result[$region] = array();
1098 * Create a set of new block instance from a record array
1100 * @param array $birecords An array of block instance records
1101 * @return array An array of instantiated block_instance objects
1103 protected function create_block_instances($birecords) {
1105 foreach ($birecords as $record) {
1106 if ($blockobject = block_instance($record->blockname
, $record, $this->page
)) {
1107 $results[] = $blockobject;
1114 * Create all the block instances for all the blocks that were loaded by
1115 * load_blocks. This is used, for example, to ensure that all blocks get a
1116 * chance to initialise themselves via the {@link block_base::specialize()}
1117 * method, before any output is done.
1119 * It is also used to create any blocks that are "requiredbytheme" by the current theme.
1120 * These blocks that are auto-created have requiredbytheme set on the block instance
1121 * so they are only visible on themes that require them.
1123 public function create_all_block_instances() {
1126 // If there are any un-removable blocks that were not created - force them.
1127 $requiredbytheme = $this->get_required_by_theme_block_types();
1128 if (!$this->fakeblocksonly
) {
1129 foreach ($requiredbytheme as $forced) {
1130 if (empty($forced)) {
1134 foreach ($this->get_regions() as $region) {
1135 foreach($this->birecordsbyregion
[$region] as $instance) {
1136 if ($instance->blockname
== $forced) {
1142 $this->add_block_required_by_theme($forced);
1149 // Some blocks were missing. Lets do it again.
1150 $this->birecordsbyregion
= null;
1151 $this->load_blocks();
1153 foreach ($this->get_regions() as $region) {
1154 $this->ensure_instances_exist($region);
1160 * Add a block that is required by the current theme but has not been
1161 * created yet. This is a special type of block that only shows in themes that
1162 * require it (by listing it in undeletable_block_types).
1164 * @param string $blockname the name of the block type.
1166 protected function add_block_required_by_theme($blockname) {
1169 if (empty($this->birecordsbyregion
)) {
1170 // No blocks or block regions exist yet.
1174 // Never auto create blocks when we are showing fake blocks only.
1175 if ($this->fakeblocksonly
) {
1179 // Never add a duplicate block required by theme.
1180 if ($DB->record_exists('block_instances', array('blockname' => $blockname, 'requiredbytheme' => 1))) {
1184 $systemcontext = context_system
::instance();
1185 $defaultregion = $this->get_default_region();
1186 // Add a special system wide block instance only for themes that require it.
1187 $blockinstance = new stdClass
;
1188 $blockinstance->blockname
= $blockname;
1189 $blockinstance->parentcontextid
= $systemcontext->id
;
1190 $blockinstance->showinsubcontexts
= true;
1191 $blockinstance->requiredbytheme
= true;
1192 $blockinstance->pagetypepattern
= '*';
1193 $blockinstance->subpagepattern
= null;
1194 $blockinstance->defaultregion
= $defaultregion;
1195 $blockinstance->defaultweight
= 0;
1196 $blockinstance->configdata
= '';
1197 $blockinstance->timecreated
= time();
1198 $blockinstance->timemodified
= $blockinstance->timecreated
;
1199 $blockinstance->id
= $DB->insert_record('block_instances', $blockinstance);
1201 // Ensure the block context is created.
1202 context_block
::instance($blockinstance->id
);
1204 // If the new instance was created, allow it to do additional setup.
1205 if ($block = block_instance($blockname, $blockinstance)) {
1206 $block->instance_create();
1211 * Return an array of content objects from a set of block instances
1213 * @param array $instances An array of block instances
1214 * @param renderer_base The renderer to use.
1215 * @param string $region the region name.
1216 * @return array An array of block_content (and possibly block_move_target) objects.
1218 protected function create_block_contents($instances, $output, $region) {
1223 if ($this->movingblock
) {
1224 $first = reset($instances);
1226 $lastweight = $first->instance
->weight
- 2;
1230 foreach ($instances as $instance) {
1231 $content = $instance->get_content_for_output($output);
1232 if (empty($content)) {
1236 if ($this->movingblock
&& $lastweight != $instance->instance
->weight
&&
1237 $content->blockinstanceid
!= $this->movingblock
&& $lastblock != $this->movingblock
) {
1238 $results[] = new block_move_target($this->get_move_target_url($region, ($lastweight +
$instance->instance
->weight
)/2));
1241 if ($content->blockinstanceid
== $this->movingblock
) {
1242 $content->add_class('beingmoved');
1243 $content->annotation
.= get_string('movingthisblockcancel', 'block',
1244 html_writer
::link($this->page
->url
, get_string('cancel')));
1247 $results[] = $content;
1248 $lastweight = $instance->instance
->weight
;
1249 $lastblock = $instance->instance
->id
;
1252 if ($this->movingblock
&& $lastblock != $this->movingblock
) {
1253 $results[] = new block_move_target($this->get_move_target_url($region, $lastweight +
1));
1259 * Ensure block instances exist for a given region
1261 * @param string $region Check for bi's with the instance with this name
1263 protected function ensure_instances_exist($region) {
1264 $this->check_region_is_known($region);
1265 if (!array_key_exists($region, $this->blockinstances
)) {
1266 $this->blockinstances
[$region] =
1267 $this->create_block_instances($this->birecordsbyregion
[$region]);
1272 * Ensure that there is some content within the given region
1274 * @param string $region The name of the region to check
1276 public function ensure_content_created($region, $output) {
1277 $this->ensure_instances_exist($region);
1279 if (!has_capability('moodle/block:view', $this->page
->context
) ) {
1280 $this->visibleblockcontent
[$region] = [];
1284 if (!array_key_exists($region, $this->visibleblockcontent
)) {
1285 $contents = array();
1286 if (array_key_exists($region, $this->extracontent
)) {
1287 $contents = $this->extracontent
[$region];
1289 $contents = array_merge($contents, $this->create_block_contents($this->blockinstances
[$region], $output, $region));
1290 if (($region == $this->defaultregion
) && (!isset($this->page
->theme
->addblockposition
) ||
1291 $this->page
->theme
->addblockposition
== BLOCK_ADDBLOCK_POSITION_DEFAULT
)) {
1292 $addblockui = block_add_block_ui($this->page
, $output);
1294 $contents[] = $addblockui;
1297 $this->visibleblockcontent
[$region] = $contents;
1301 /// Process actions from the URL ===============================================
1304 * Get the appropriate list of editing icons for a block. This is used
1305 * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}.
1307 * @param block_base $block
1308 * @return array an array in the format for {@link block_contents::$controls}
1310 public function edit_controls($block) {
1313 $controls = array();
1314 $actionurl = $this->page
->url
->out(false, array('sesskey'=> sesskey()));
1315 $blocktitle = $block->title
;
1316 if (empty($blocktitle)) {
1317 $blocktitle = $block->arialabel
;
1320 if ($this->page
->user_can_edit_blocks()) {
1322 $str = new lang_string('moveblock', 'block', $blocktitle);
1323 $controls[] = new action_menu_link_primary(
1324 new moodle_url($actionurl, array('bui_moveid' => $block->instance
->id
)),
1325 new pix_icon('t/move', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1327 array('class' => 'editing_move')
1332 if ($this->page
->user_can_edit_blocks() ||
$block->user_can_edit()) {
1333 // Edit config icon - always show - needed for positioning UI.
1334 $str = new lang_string('configureblock', 'block', $blocktitle);
1335 $editactionurl = new moodle_url($actionurl, ['bui_editid' => $block->instance
->id
]);
1336 $editactionurl->remove_params(['sesskey']);
1338 // Handle editing block on admin index page, prevent the page redirecting before block action can begin.
1339 if ($editactionurl->compare(new moodle_url('/admin/index.php'), URL_MATCH_BASE
)) {
1340 $editactionurl->param('cache', 1);
1343 $controls[] = new action_menu_link_secondary(
1345 new pix_icon('t/edit', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1348 'class' => 'editing_edit',
1349 'data-action' => 'editblock',
1350 'data-blockid' => $block->instance
->id
,
1351 'data-blockform' => self
::get_block_edit_form_class($block->name()),
1352 'data-header' => $str,
1358 if ($this->page
->user_can_edit_blocks() && $block->instance_can_be_hidden()) {
1360 if ($block->instance
->visible
) {
1361 $str = new lang_string('hideblock', 'block', $blocktitle);
1362 $url = new moodle_url($actionurl, array('bui_hideid' => $block->instance
->id
));
1363 $icon = new pix_icon('t/hide', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1364 $attributes = array('class' => 'editing_hide');
1366 $str = new lang_string('showblock', 'block', $blocktitle);
1367 $url = new moodle_url($actionurl, array('bui_showid' => $block->instance
->id
));
1368 $icon = new pix_icon('t/show', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1369 $attributes = array('class' => 'editing_show');
1371 $controls[] = new action_menu_link_secondary($url, $icon, $str, $attributes);
1375 if (get_assignable_roles($block->context
, ROLENAME_SHORT
)) {
1376 $rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid' => $block->context
->id
,
1377 'returnurl' => $this->page
->url
->out_as_local_url()));
1378 $str = new lang_string('assignrolesinblock', 'block', $blocktitle);
1379 $controls[] = new action_menu_link_secondary(
1381 new pix_icon('i/assignroles', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1382 $str, array('class' => 'editing_assignroles')
1387 if (has_capability('moodle/role:review', $block->context
) or get_overridable_roles($block->context
)) {
1388 $rolesurl = new moodle_url('/admin/roles/permissions.php', array('contextid' => $block->context
->id
,
1389 'returnurl' => $this->page
->url
->out_as_local_url()));
1390 $str = get_string('permissions', 'role');
1391 $controls[] = new action_menu_link_secondary(
1393 new pix_icon('i/permissions', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1394 $str, array('class' => 'editing_permissions')
1398 // Change permissions.
1399 if (has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:assign'), $block->context
)) {
1400 $rolesurl = new moodle_url('/admin/roles/check.php', array('contextid' => $block->context
->id
,
1401 'returnurl' => $this->page
->url
->out_as_local_url()));
1402 $str = get_string('checkpermissions', 'role');
1403 $controls[] = new action_menu_link_secondary(
1405 new pix_icon('i/checkpermissions', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1406 $str, array('class' => 'editing_checkroles')
1410 if ($this->user_can_delete_block($block)) {
1412 $str = new lang_string('deleteblock', 'block', $blocktitle);
1413 $deleteactionurl = new moodle_url($actionurl, ['bui_deleteid' => $block->instance
->id
]);
1414 $deleteactionurl->remove_params(['sesskey']);
1416 // Handle deleting block on admin index page, prevent the page redirecting before block action can begin.
1417 if ($deleteactionurl->compare(new moodle_url('/admin/index.php'), URL_MATCH_BASE
)) {
1418 $deleteactionurl->param('cache', 1);
1421 $deleteconfirmationurl = new moodle_url($actionurl, [
1422 'bui_deleteid' => $block->instance
->id
,
1424 'sesskey' => sesskey(),
1426 $blocktitle = $block->get_title();
1428 $controls[] = new action_menu_link_secondary(
1430 new pix_icon('t/delete', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1433 'class' => 'editing_delete',
1434 'data-modal' => 'confirmation',
1435 'data-modal-title-str' => json_encode(['deletecheck_modal', 'block']),
1436 'data-modal-content-str' => json_encode(['deleteblockcheck', 'block', $blocktitle]),
1437 'data-modal-yes-button-str' => json_encode(['delete', 'core']),
1438 'data-modal-toast' => 'true',
1439 'data-modal-toast-confirmation-str' => json_encode(['deleteblockinprogress', 'block', $blocktitle]),
1440 'data-modal-destination' => $deleteconfirmationurl->out(false),
1445 if (!empty($CFG->contextlocking
) && has_capability('moodle/site:managecontextlocks', $block->context
)) {
1446 $parentcontext = $block->context
->get_parent_context();
1447 if (empty($parentcontext) ||
empty($parentcontext->locked
)) {
1448 if ($block->context
->locked
) {
1449 $lockicon = 'i/unlock';
1450 $lockstring = get_string('managecontextunlock', 'admin');
1452 $lockicon = 'i/lock';
1453 $lockstring = get_string('managecontextlock', 'admin');
1455 $controls[] = new action_menu_link_secondary(
1459 'id' => $block->context
->id
,
1462 new pix_icon($lockicon, $lockstring, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1464 ['class' => 'editing_lock']
1473 * @param block_base $block a block that appears on this page.
1474 * @return boolean boolean whether the currently logged in user is allowed to delete this block.
1476 protected function user_can_delete_block($block) {
1477 return $this->page
->user_can_edit_blocks() && $block->user_can_edit() &&
1478 $block->user_can_addto($this->page
) &&
1479 !in_array($block->instance
->blockname
, self
::get_undeletable_block_types()) &&
1480 !in_array($block->instance
->blockname
, $this->get_required_by_theme_block_types());
1484 * Process any block actions that were specified in the URL.
1486 * @return boolean true if anything was done. False if not.
1488 public function process_url_actions() {
1489 if (!$this->page
->user_is_editing()) {
1492 return $this->process_url_add() ||
$this->process_url_delete() ||
1493 $this->process_url_show_hide() ||
$this->process_url_edit() ||
1494 $this->process_url_move();
1498 * Handle adding a block.
1499 * @return boolean true if anything was done. False if not.
1501 public function process_url_add() {
1502 global $CFG, $PAGE, $OUTPUT;
1504 $blocktype = optional_param('bui_addblock', null, PARAM_PLUGIN
);
1505 $blockregion = optional_param('bui_blockregion', null, PARAM_TEXT
);
1507 if ($blocktype === null) {
1513 if (!$this->page
->user_can_edit_blocks()) {
1514 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('addblock'));
1517 $addableblocks = $this->get_addable_blocks();
1519 if ($blocktype === '') {
1520 // Display add block selection.
1521 $addpage = new moodle_page();
1522 $addpage->set_pagelayout('admin');
1523 $addpage->blocks
->show_only_fake_blocks(true);
1524 $addpage->set_course($this->page
->course
);
1525 $addpage->set_context($this->page
->context
);
1526 if ($this->page
->cm
) {
1527 $addpage->set_cm($this->page
->cm
);
1530 $addpagebase = str_replace($CFG->wwwroot
. '/', '/', $this->page
->url
->out_omit_querystring());
1531 $addpageparams = $this->page
->url
->params();
1532 $addpage->set_url($addpagebase, $addpageparams);
1533 $addpage->set_block_actions_done();
1534 // At this point we are going to display the block selector, overwrite global $PAGE ready for this.
1536 // Some functions use $OUTPUT so we need to replace that too.
1537 $OUTPUT = $addpage->get_renderer('core');
1540 $straddblock = get_string('addblock');
1542 $PAGE->navbar
->add($straddblock);
1543 $PAGE->set_title($straddblock);
1544 $PAGE->set_heading($site->fullname
);
1545 echo $OUTPUT->header();
1546 echo $OUTPUT->heading($straddblock);
1548 if (!$addableblocks) {
1549 echo $OUTPUT->box(get_string('noblockstoaddhere'));
1550 echo $OUTPUT->container($OUTPUT->action_link($addpage->url
, get_string('back')), 'mx-3 mb-1');
1552 $url = new moodle_url($addpage->url
, array('sesskey' => sesskey()));
1553 echo $OUTPUT->render_from_template('core/add_block_body',
1554 ['blocks' => array_values($addableblocks),
1555 'url' => '?' . $url->get_query_string(false)]);
1556 echo $OUTPUT->container($OUTPUT->action_link($addpage->url
, get_string('cancel')), 'mx-3 mb-1');
1559 echo $OUTPUT->footer();
1560 // Make sure that nothing else happens after we have displayed this form.
1564 if (!array_key_exists($blocktype, $addableblocks)) {
1565 throw new moodle_exception('cannotaddthisblocktype', '', $this->page
->url
->out(), $blocktype);
1568 $this->add_block_at_end_of_default_region($blocktype, $blockregion);
1570 // If the page URL was a guess, it will contain the bui_... param, so we must make sure it is not there.
1571 $this->page
->ensure_param_not_in_url('bui_addblock');
1577 * Handle deleting a block.
1578 * @return boolean true if anything was done. False if not.
1580 public function process_url_delete() {
1581 global $CFG, $PAGE, $OUTPUT;
1583 $blockid = optional_param('bui_deleteid', null, PARAM_INT
);
1584 $confirmdelete = optional_param('bui_confirm', null, PARAM_INT
);
1590 $block = $this->page
->blocks
->find_instance($blockid);
1591 if (!$this->user_can_delete_block($block)) {
1592 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('deleteablock'));
1595 if (!$confirmdelete) {
1596 $deletepage = new moodle_page();
1597 $deletepage->set_pagelayout('admin');
1598 $deletepage->blocks
->show_only_fake_blocks(true);
1599 $deletepage->set_course($this->page
->course
);
1600 $deletepage->set_context($this->page
->context
);
1601 if ($this->page
->cm
) {
1602 $deletepage->set_cm($this->page
->cm
);
1605 $deleteurlbase = str_replace($CFG->wwwroot
. '/', '/', $this->page
->url
->out_omit_querystring());
1606 $deleteurlparams = $this->page
->url
->params();
1607 $deletepage->set_url($deleteurlbase, $deleteurlparams);
1608 $deletepage->set_block_actions_done();
1609 $deletepage->set_secondarynav($this->get_secondarynav($block));
1610 // At this point we are either going to redirect, or display the form, so
1611 // overwrite global $PAGE ready for this. (Formslib refers to it.)
1612 $PAGE = $deletepage;
1613 //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that too
1614 $output = $deletepage->get_renderer('core');
1618 $blocktitle = $block->get_title();
1619 $strdeletecheck = get_string('deletecheck', 'block', $blocktitle);
1620 $message = get_string('deleteblockcheck', 'block', $blocktitle);
1622 // If the block is being shown in sub contexts display a warning.
1623 if ($block->instance
->showinsubcontexts
== 1) {
1624 $parentcontext = context
::instance_by_id($block->instance
->parentcontextid
);
1625 $systemcontext = context_system
::instance();
1626 $messagestring = new stdClass();
1627 $messagestring->location
= $parentcontext->get_context_name();
1629 // Checking for blocks that may have visibility on the front page and pages added on that.
1630 if ($parentcontext->id
!= $systemcontext->id
&& is_inside_frontpage($parentcontext)) {
1631 $messagestring->pagetype
= get_string('showonfrontpageandsubs', 'block');
1633 $pagetypes = generate_page_type_patterns($this->page
->pagetype
, $parentcontext);
1634 $messagestring->pagetype
= $block->instance
->pagetypepattern
;
1635 if (isset($pagetypes[$block->instance
->pagetypepattern
])) {
1636 $messagestring->pagetype
= $pagetypes[$block->instance
->pagetypepattern
];
1640 $message = get_string('deleteblockwarning', 'block', $messagestring);
1643 $PAGE->navbar
->add($strdeletecheck);
1644 $PAGE->set_title($blocktitle . ': ' . $strdeletecheck);
1645 $PAGE->set_heading($site->fullname
);
1646 echo $OUTPUT->header();
1647 $confirmurl = new moodle_url($deletepage->url
, array('sesskey' => sesskey(), 'bui_deleteid' => $block->instance
->id
, 'bui_confirm' => 1));
1648 $cancelurl = new moodle_url($deletepage->url
);
1649 $yesbutton = new single_button($confirmurl, get_string('yes'));
1650 $nobutton = new single_button($cancelurl, get_string('no'));
1651 echo $OUTPUT->confirm($message, $yesbutton, $nobutton);
1652 echo $OUTPUT->footer();
1653 // Make sure that nothing else happens after we have displayed this form.
1658 blocks_delete_instance($block->instance
);
1659 // bui_deleteid and bui_confirm should not be in the PAGE url.
1660 $this->page
->ensure_param_not_in_url('bui_deleteid');
1661 $this->page
->ensure_param_not_in_url('bui_confirm');
1667 * Returns the name of the class for block editing and makes sure it is autoloaded
1669 * @param string $blockname name of the block plugin (without block_ prefix)
1672 public static function get_block_edit_form_class(string $blockname): string {
1674 require_once("$CFG->dirroot/blocks/moodleblock.class.php");
1675 $blockname = clean_param($blockname, PARAM_PLUGIN
);
1676 $formfile = $CFG->dirroot
. '/blocks/' . $blockname . '/edit_form.php';
1677 if (is_readable($formfile)) {
1678 require_once($CFG->dirroot
. '/blocks/edit_form.php');
1679 require_once($formfile);
1680 $classname = 'block_' . $blockname . '_edit_form';
1681 if (!class_exists($classname)) {
1682 $classname = 'block_edit_form';
1685 require_once($CFG->dirroot
. '/blocks/edit_form.php');
1686 $classname = 'block_edit_form';
1692 * Handle showing or hiding a block.
1693 * @return boolean true if anything was done. False if not.
1695 public function process_url_show_hide() {
1696 if ($blockid = optional_param('bui_hideid', null, PARAM_INT
)) {
1698 } else if ($blockid = optional_param('bui_showid', null, PARAM_INT
)) {
1706 $block = $this->page
->blocks
->find_instance($blockid);
1708 if (!$this->page
->user_can_edit_blocks()) {
1709 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('hideshowblocks'));
1710 } else if (!$block->instance_can_be_hidden()) {
1714 blocks_set_visibility($block->instance
, $this->page
, $newvisibility);
1716 // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1717 $this->page
->ensure_param_not_in_url('bui_hideid');
1718 $this->page
->ensure_param_not_in_url('bui_showid');
1724 * Convenience function to check whether a block is implementing a secondary nav class and return it
1725 * initialised to the calling function
1727 * @todo MDL-74939 Remove support for old 'local\views\secondary' class location
1728 * @param block_base $block
1729 * @return \core\navigation\views\secondary
1731 protected function get_secondarynav(block_base
$block): \core\navigation\views\secondary
{
1732 $class = "core_block\\navigation\\views\\secondary";
1733 if (class_exists("block_{$block->name()}\\navigation\\views\\secondary")) {
1734 $class = "block_{$block->name()}\\navigation\\views\\secondary";
1735 } else if (class_exists("block_{$block->name()}\\local\\views\\secondary")) {
1736 // For backwards compatibility, support the old location for this class (it was in a
1737 // 'local' namespace which shouldn't be used for core APIs).
1738 debugging("The class block_{$block->name()}\\local\\views\\secondary uses a deprecated " .
1739 "namespace. Please move it to block_{$block->name()}\\navigation\\views\\secondary.",
1741 $class = "block_{$block->name()}\\local\\views\\secondary";
1743 $secondarynav = new $class($this->page
);
1744 $secondarynav->initialise();
1745 return $secondarynav;
1749 * Handle showing/processing the submission from the block editing form.
1750 * @return boolean true if the form was submitted and the new config saved. Does not
1751 * return if the editing form was displayed. False otherwise.
1753 public function process_url_edit() {
1754 global $CFG, $DB, $PAGE, $OUTPUT;
1756 $blockid = optional_param('bui_editid', null, PARAM_INT
);
1761 require_once($CFG->dirroot
. '/blocks/edit_form.php');
1763 $block = $this->find_instance($blockid);
1765 if (!$block->user_can_edit() && !$this->page
->user_can_edit_blocks()) {
1766 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('editblock'));
1769 $editpage = new moodle_page();
1770 $editpage->set_pagelayout('admin');
1771 $editpage->blocks
->show_only_fake_blocks(true);
1772 $editpage->set_course($this->page
->course
);
1773 //$editpage->set_context($block->context);
1774 $editpage->set_context($this->page
->context
);
1775 $editpage->set_secondarynav($this->get_secondarynav($block));
1777 if ($this->page
->cm
) {
1778 $editpage->set_cm($this->page
->cm
);
1780 $editurlbase = str_replace($CFG->wwwroot
. '/', '/', $this->page
->url
->out_omit_querystring());
1781 $editurlparams = $this->page
->url
->params();
1782 $editurlparams['bui_editid'] = $blockid;
1783 $editpage->set_url($editurlbase, $editurlparams);
1784 $editpage->set_block_actions_done();
1785 // At this point we are either going to redirect, or display the form, so
1786 // overwrite global $PAGE ready for this. (Formslib refers to it.)
1788 //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that to
1789 $output = $editpage->get_renderer('core');
1792 $classname = self
::get_block_edit_form_class($block->name());
1793 /** @var block_edit_form $mform */
1794 $mform = new $classname($editpage->url
->out(false), ['page' => $this->page
, 'block' => $block, 'actionbuttons' => true]);
1795 $mform->set_data($block->instance
);
1797 if ($mform->is_cancelled()) {
1798 redirect($this->page
->url
);
1800 } else if ($data = $mform->get_data()) {
1802 $this->save_block_data($block, $data);
1803 redirect($this->page
->url
);
1806 $strheading = get_string('blockconfiga', 'moodle', $block->get_title());
1807 $editpage->set_title($strheading);
1808 $editpage->set_heading($strheading);
1809 $bits = explode('-', $this->page
->pagetype
);
1810 if ($bits[0] == 'tag' && !empty($this->page
->subpage
)) {
1811 // better navbar for tag pages
1812 $editpage->navbar
->add(get_string('tags'), new moodle_url('/tag/'));
1813 $tag = core_tag_tag
::get($this->page
->subpage
);
1814 // tag search page doesn't have subpageid
1816 $editpage->navbar
->add($tag->get_display_name(), $tag->get_view_url());
1819 $editpage->navbar
->add($block->get_title());
1820 $editpage->navbar
->add(get_string('configuration'));
1821 echo $output->header();
1823 echo $output->footer();
1829 * Updates block configuration in the database
1831 * @param block_base $block
1832 * @param stdClass $data data from the block edit form
1835 public function save_block_data(block_base
$block, stdClass
$data): void
{
1839 $bi->id
= $block->instance
->id
;
1841 // This may get overwritten by the special case handling below.
1842 $bi->pagetypepattern
= $data->bui_pagetypepattern
;
1843 $bi->showinsubcontexts
= (bool) $data->bui_contexts
;
1844 if (empty($data->bui_subpagepattern
) ||
$data->bui_subpagepattern
== '%@NULL@%') {
1845 $bi->subpagepattern
= null;
1847 $bi->subpagepattern
= $data->bui_subpagepattern
;
1850 $systemcontext = context_system
::instance();
1851 $frontpagecontext = context_course
::instance(SITEID
);
1852 $parentcontext = context
::instance_by_id($data->bui_parentcontextid
);
1854 // Updating stickiness and contexts. See MDL-21375 for details.
1855 if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination.
1857 // Explicitly set the default context.
1858 $bi->parentcontextid
= $parentcontext->id
;
1860 if ($data->bui_editingatfrontpage
) { // The block is being edited on the front page.
1862 // The interface here is a special case because the pagetype pattern is
1863 // totally derived from the context menu. Here are the excpetions. MDL-30340 .
1865 switch ($data->bui_contexts
) {
1866 case BUI_CONTEXTS_ENTIRE_SITE
:
1867 // The user wants to show the block across the entire site.
1868 $bi->parentcontextid
= $systemcontext->id
;
1869 $bi->showinsubcontexts
= true;
1870 $bi->pagetypepattern
= '*';
1872 case BUI_CONTEXTS_FRONTPAGE_SUBS
:
1873 // The user wants the block shown on the front page and all subcontexts.
1874 $bi->parentcontextid
= $frontpagecontext->id
;
1875 $bi->showinsubcontexts
= true;
1876 $bi->pagetypepattern
= '*';
1878 case BUI_CONTEXTS_FRONTPAGE_ONLY
:
1879 // The user want to show the front page on the frontpage only.
1880 $bi->parentcontextid
= $frontpagecontext->id
;
1881 $bi->showinsubcontexts
= false;
1882 $bi->pagetypepattern
= 'site-index';
1883 // This is the only relevant page type anyway but we'll set it explicitly just
1884 // in case the front page grows site-index-* subpages of its own later.
1890 $bits = explode('-', $bi->pagetypepattern
);
1891 // Hacks for some contexts.
1892 if (($parentcontext->contextlevel
== CONTEXT_COURSE
) && ($parentcontext->instanceid
!= SITEID
)) {
1893 // For course context
1894 // is page type pattern is mod-*, change showinsubcontext to 1.
1895 if ($bits[0] == 'mod' ||
$bi->pagetypepattern
== '*') {
1896 $bi->showinsubcontexts
= 1;
1898 $bi->showinsubcontexts
= 0;
1900 } else if ($parentcontext->contextlevel
== CONTEXT_USER
) {
1901 // For user context subpagepattern should be null.
1902 if ($bits[0] == 'user' ||
$bits[0] == 'my') {
1903 // We don't need subpagepattern in usercontext.
1904 $bi->subpagepattern
= null;
1908 $bi->defaultregion
= $data->bui_defaultregion
;
1909 $bi->defaultweight
= $data->bui_defaultweight
;
1910 $bi->timemodified
= time();
1911 $DB->update_record('block_instances', $bi);
1913 if (!empty($block->config
)) {
1914 $config = clone($block->config
);
1916 $config = new stdClass
;
1918 foreach ($data as $configfield => $value) {
1919 if (strpos($configfield, 'config_') !== 0) {
1922 $field = substr($configfield, 7);
1923 $config->$field = $value;
1925 $block->instance_config_save($config);
1928 $bp->visible
= $data->bui_visible
;
1929 $bp->region
= $data->bui_region
;
1930 $bp->weight
= $data->bui_weight
;
1931 $needbprecord = !$data->bui_visible ||
$data->bui_region
!= $data->bui_defaultregion ||
1932 $data->bui_weight
!= $data->bui_defaultweight
;
1934 if ($block->instance
->blockpositionid
&& !$needbprecord) {
1935 $DB->delete_records('block_positions', array('id' => $block->instance
->blockpositionid
));
1937 } else if ($block->instance
->blockpositionid
&& $needbprecord) {
1938 $bp->id
= $block->instance
->blockpositionid
;
1939 $DB->update_record('block_positions', $bp);
1941 } else if ($needbprecord) {
1942 $bp->blockinstanceid
= $block->instance
->id
;
1943 $bp->contextid
= $this->page
->context
->id
;
1944 $bp->pagetype
= $this->page
->pagetype
;
1945 if ($this->page
->subpage
) {
1946 $bp->subpage
= $this->page
->subpage
;
1950 $DB->insert_record('block_positions', $bp);
1955 * Handle showing/processing the submission from the block editing form.
1956 * @return boolean true if the form was submitted and the new config saved. Does not
1957 * return if the editing form was displayed. False otherwise.
1959 public function process_url_move() {
1960 global $CFG, $DB, $PAGE;
1962 $blockid = optional_param('bui_moveid', null, PARAM_INT
);
1969 $block = $this->find_instance($blockid);
1971 if (!$this->page
->user_can_edit_blocks()) {
1972 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('editblock'));
1975 $newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT
);
1976 $newweight = optional_param('bui_newweight', null, PARAM_FLOAT
);
1977 if (!$newregion ||
is_null($newweight)) {
1978 // Don't have a valid target position yet, must be just starting the move.
1979 $this->movingblock
= $blockid;
1980 $this->page
->ensure_param_not_in_url('bui_moveid');
1984 if (!$this->is_known_region($newregion)) {
1985 throw new moodle_exception('unknownblockregion', '', $this->page
->url
, $newregion);
1988 // Move this block. This may involve moving other nearby blocks.
1989 $blocks = $this->birecordsbyregion
[$newregion];
1991 $maxweight = self
::MAX_WEIGHT
;
1992 $minweight = -self
::MAX_WEIGHT
;
1994 // Initialise the used weights and spareweights array with the default values
1995 $spareweights = array();
1996 $usedweights = array();
1997 for ($i = $minweight; $i <= $maxweight; $i++
) {
1998 $spareweights[$i] = $i;
1999 $usedweights[$i] = array();
2002 // Check each block and sort out where we have used weights
2003 foreach ($blocks as $bi) {
2004 if ($bi->weight
> $maxweight) {
2005 // If this statement is true then the blocks weight is more than the
2006 // current maximum. To ensure that we can get the best block position
2007 // we will initialise elements within the usedweights and spareweights
2008 // arrays between the blocks weight (which will then be the new max) and
2010 $parseweight = $bi->weight
;
2011 while (!array_key_exists($parseweight, $usedweights)) {
2012 $usedweights[$parseweight] = array();
2013 $spareweights[$parseweight] = $parseweight;
2016 $maxweight = $bi->weight
;
2017 } else if ($bi->weight
< $minweight) {
2018 // As above except this time the blocks weight is LESS than the
2019 // the current minimum, so we will initialise the array from the
2020 // blocks weight (new minimum) to the current minimum
2021 $parseweight = $bi->weight
;
2022 while (!array_key_exists($parseweight, $usedweights)) {
2023 $usedweights[$parseweight] = array();
2024 $spareweights[$parseweight] = $parseweight;
2027 $minweight = $bi->weight
;
2029 if ($bi->id
!= $block->instance
->id
) {
2030 unset($spareweights[$bi->weight
]);
2031 $usedweights[$bi->weight
][] = $bi->id
;
2035 // First we find the nearest gap in the list of weights.
2036 $bestdistance = max(abs($newweight - self
::MAX_WEIGHT
), abs($newweight + self
::MAX_WEIGHT
)) +
1;
2038 foreach ($spareweights as $spareweight) {
2039 if (abs($newweight - $spareweight) < $bestdistance) {
2040 $bestdistance = abs($newweight - $spareweight);
2041 $bestgap = $spareweight;
2045 // If there is no gap, we have to go outside -self::MAX_WEIGHT .. self::MAX_WEIGHT.
2046 if (is_null($bestgap)) {
2047 $bestgap = self
::MAX_WEIGHT +
1;
2048 while (!empty($usedweights[$bestgap])) {
2053 // Now we know the gap we are aiming for, so move all the blocks along.
2054 if ($bestgap < $newweight) {
2055 $newweight = floor($newweight);
2056 for ($weight = $bestgap +
1; $weight <= $newweight; $weight++
) {
2057 if (array_key_exists($weight, $usedweights)) {
2058 foreach ($usedweights[$weight] as $biid) {
2059 $this->reposition_block($biid, $newregion, $weight - 1);
2063 $this->reposition_block($block->instance
->id
, $newregion, $newweight);
2065 $newweight = ceil($newweight);
2066 for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {
2067 if (array_key_exists($weight, $usedweights)) {
2068 foreach ($usedweights[$weight] as $biid) {
2069 $this->reposition_block($biid, $newregion, $weight +
1);
2073 $this->reposition_block($block->instance
->id
, $newregion, $newweight);
2076 $this->page
->ensure_param_not_in_url('bui_moveid');
2077 $this->page
->ensure_param_not_in_url('bui_newregion');
2078 $this->page
->ensure_param_not_in_url('bui_newweight');
2083 * Turns the display of normal blocks either on or off.
2085 * @param bool $setting
2087 public function show_only_fake_blocks($setting = true) {
2088 $this->fakeblocksonly
= $setting;
2092 /// Helper functions for working with block classes ============================
2095 * Call a class method (one that does not require a block instance) on a block class.
2097 * @param string $blockname the name of the block.
2098 * @param string $method the method name.
2099 * @param array $param parameters to pass to the method.
2100 * @return mixed whatever the method returns.
2102 function block_method_result($blockname, $method, $param = NULL) {
2103 if(!block_load_class($blockname)) {
2106 return call_user_func(array('block_'.$blockname, $method), $param);
2110 * Returns a new instance of the specified block instance id.
2112 * @param int $blockinstanceid
2113 * @return block_base the requested block instance.
2115 function block_instance_by_id($blockinstanceid) {
2118 $blockinstance = $DB->get_record('block_instances', ['id' => $blockinstanceid]);
2119 $instance = block_instance($blockinstance->blockname
, $blockinstance);
2124 * Creates a new instance of the specified block class.
2126 * @param string $blockname the name of the block.
2127 * @param stdClass $instance block_instances DB table row (optional).
2128 * @param moodle_page $page the page this block is appearing on.
2129 * @return block_base the requested block instance.
2131 function block_instance($blockname, $instance = NULL, $page = NULL) {
2132 if(!block_load_class($blockname)) {
2135 $classname = 'block_'.$blockname;
2136 /** @var block_base $retval */
2137 $retval = new $classname;
2138 if($instance !== NULL) {
2139 if (is_null($page)) {
2143 $retval->_load_instance($instance, $page);
2149 * Load the block class for a particular type of block.
2151 * @param string $blockname the name of the block.
2152 * @return boolean success or failure.
2154 function block_load_class($blockname) {
2157 if(empty($blockname)) {
2161 $classname = 'block_'.$blockname;
2163 if(class_exists($classname)) {
2167 $blockpath = $CFG->dirroot
.'/blocks/'.$blockname.'/block_'.$blockname.'.php';
2169 if (file_exists($blockpath)) {
2170 require_once($CFG->dirroot
.'/blocks/moodleblock.class.php');
2171 include_once($blockpath);
2173 //debugging("$blockname code does not exist in $blockpath", DEBUG_DEVELOPER);
2177 return class_exists($classname);
2181 * Given a specific page type, return all the page type patterns that might
2184 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
2185 * @return array an array of all the page type patterns that might match this page type.
2187 function matching_page_type_patterns($pagetype) {
2188 $patterns = array($pagetype);
2189 $bits = explode('-', $pagetype);
2190 if (count($bits) == 3 && $bits[0] == 'mod') {
2191 if ($bits[2] == 'view') {
2192 $patterns[] = 'mod-*-view';
2193 } else if ($bits[2] == 'index') {
2194 $patterns[] = 'mod-*-index';
2197 while (count($bits) > 0) {
2198 $patterns[] = implode('-', $bits) . '-*';
2206 * Give an specific pattern, return all the page type patterns that would also match it.
2208 * @param string $pattern the pattern, e.g. 'mod-forum-*' or 'mod-quiz-view'.
2209 * @return array of all the page type patterns matching.
2211 function matching_page_type_patterns_from_pattern($pattern) {
2212 $patterns = array($pattern);
2213 if ($pattern === '*') {
2217 // Only keep the part before the star because we will append -* to all the bits.
2218 $star = strpos($pattern, '-*');
2219 if ($star !== false) {
2220 $pattern = substr($pattern, 0, $star);
2223 $patterns = array_merge($patterns, matching_page_type_patterns($pattern));
2224 $patterns = array_unique($patterns);
2230 * Given a specific page type, parent context and currect context, return all the page type patterns
2231 * that might be used by this block.
2233 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
2234 * @param stdClass $parentcontext Block's parent context
2235 * @param stdClass $currentcontext Current context of block
2236 * @return array an array of all the page type patterns that might match this page type.
2238 function generate_page_type_patterns($pagetype, $parentcontext = null, $currentcontext = null) {
2239 global $CFG; // Required for includes bellow.
2241 $bits = explode('-', $pagetype);
2243 $core = core_component
::get_core_subsystems();
2244 $plugins = core_component
::get_plugin_types();
2246 //progressively strip pieces off the page type looking for a match
2247 $componentarray = null;
2248 for ($i = count($bits); $i > 0; $i--) {
2249 $possiblecomponentarray = array_slice($bits, 0, $i);
2250 $possiblecomponent = implode('', $possiblecomponentarray);
2252 // Check to see if the component is a core component
2253 if (array_key_exists($possiblecomponent, $core) && !empty($core[$possiblecomponent])) {
2254 $libfile = $core[$possiblecomponent].'/lib.php';
2255 if (file_exists($libfile)) {
2256 require_once($libfile);
2257 $function = $possiblecomponent.'_page_type_list';
2258 if (function_exists($function)) {
2259 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
2266 //check the plugin directory and look for a callback
2267 if (array_key_exists($possiblecomponent, $plugins) && !empty($plugins[$possiblecomponent])) {
2269 //We've found a plugin type. Look for a plugin name by getting the next section of page type
2270 if (count($bits) > $i) {
2271 $pluginname = $bits[$i];
2272 $directory = core_component
::get_plugin_directory($possiblecomponent, $pluginname);
2273 if (!empty($directory)){
2274 $libfile = $directory.'/lib.php';
2275 if (file_exists($libfile)) {
2276 require_once($libfile);
2277 $function = $possiblecomponent.'_'.$pluginname.'_page_type_list';
2278 if (!function_exists($function)) {
2279 $function = $pluginname.'_page_type_list';
2281 if (function_exists($function)) {
2282 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
2290 //we'll only get to here if we still don't have any patterns
2291 //the plugin type may have a callback
2292 $directory = $plugins[$possiblecomponent];
2293 $libfile = $directory.'/lib.php';
2294 if (file_exists($libfile)) {
2295 require_once($libfile);
2296 $function = $possiblecomponent.'_page_type_list';
2297 if (function_exists($function)) {
2298 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
2306 if (empty($patterns)) {
2307 $patterns = default_page_type_list($pagetype, $parentcontext, $currentcontext);
2310 // Ensure that the * pattern is always available if editing block 'at distance', so
2311 // we always can 'bring back' it to the original context. MDL-30340
2312 if ((!isset($currentcontext) or !isset($parentcontext) or $currentcontext->id
!= $parentcontext->id
) && !isset($patterns['*'])) {
2313 // TODO: We could change the string here, showing its 'bring back' meaning
2314 $patterns['*'] = get_string('page-x', 'pagetype');
2321 * Generates a default page type list when a more appropriate callback cannot be decided upon.
2323 * @param string $pagetype
2324 * @param stdClass $parentcontext
2325 * @param stdClass $currentcontext
2328 function default_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
2329 // Generate page type patterns based on current page type if
2330 // callbacks haven't been defined
2331 $patterns = array($pagetype => $pagetype);
2332 $bits = explode('-', $pagetype);
2333 while (count($bits) > 0) {
2334 $pattern = implode('-', $bits) . '-*';
2335 $pagetypestringname = 'page-'.str_replace('*', 'x', $pattern);
2336 // guessing page type description
2337 if (get_string_manager()->string_exists($pagetypestringname, 'pagetype')) {
2338 $patterns[$pattern] = get_string($pagetypestringname, 'pagetype');
2340 $patterns[$pattern] = $pattern;
2344 $patterns['*'] = get_string('page-x', 'pagetype');
2349 * Generates the page type list for the my moodle page
2351 * @param string $pagetype
2352 * @param stdClass $parentcontext
2353 * @param stdClass $currentcontext
2356 function my_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
2357 return array('my-index' => get_string('page-my-index', 'pagetype'));
2361 * Generates the page type list for a module by either locating and using the modules callback
2362 * or by generating a default list.
2364 * @param string $pagetype
2365 * @param stdClass $parentcontext
2366 * @param stdClass $currentcontext
2369 function mod_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
2370 $patterns = plugin_page_type_list($pagetype, $parentcontext, $currentcontext);
2371 if (empty($patterns)) {
2372 // if modules don't have callbacks
2373 // generate two default page type patterns for modules only
2374 $bits = explode('-', $pagetype);
2375 $patterns = array($pagetype => $pagetype);
2376 if ($bits[2] == 'view') {
2377 $patterns['mod-*-view'] = get_string('page-mod-x-view', 'pagetype');
2378 } else if ($bits[2] == 'index') {
2379 $patterns['mod-*-index'] = get_string('page-mod-x-index', 'pagetype');
2384 /// Functions update the blocks if required by the request parameters ==========
2387 * Return a {@link block_contents} representing the add a new block UI, if
2388 * this user is allowed to see it.
2390 * @return block_contents an appropriate block_contents, or null if the user
2391 * cannot add any blocks here.
2393 function block_add_block_ui($page, $output) {
2394 global $CFG, $OUTPUT;
2395 if (!$page->user_is_editing() ||
!$page->user_can_edit_blocks()) {
2399 $bc = new block_contents();
2400 $bc->title
= get_string('addblock');
2401 $bc->add_class('block_adminblock');
2402 $bc->attributes
['data-block'] = 'adminblock';
2404 $missingblocks = $page->blocks
->get_addable_blocks();
2405 if (empty($missingblocks)) {
2406 $bc->content
= get_string('noblockstoaddhere');
2411 foreach ($missingblocks as $block) {
2412 $menu[$block->name
] = $block->title
;
2415 $actionurl = new moodle_url($page->url
, array('sesskey'=>sesskey()));
2416 $select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');
2417 $select->set_label(get_string('addblock'), array('class'=>'accesshide'));
2418 $bc->content
= $OUTPUT->render($select);
2423 * Actually delete from the database any blocks that are currently on this page,
2424 * but which should not be there according to blocks_name_allowed_in_format.
2426 * @todo Write/Fix this function. Currently returns immediately
2429 function blocks_remove_inappropriate($course) {
2433 $blockmanager = blocks_get_by_page($page);
2435 if (empty($blockmanager)) {
2439 if (($pageformat = $page->pagetype) == NULL) {
2443 foreach($blockmanager as $region) {
2444 foreach($region as $instance) {
2445 $block = blocks_get_record($instance->blockid);
2446 if(!blocks_name_allowed_in_format($block->name, $pageformat)) {
2447 blocks_delete_instance($instance->instance);
2454 * Check that a given name is in a permittable format
2456 * @param string $name
2457 * @param string $pageformat
2460 function blocks_name_allowed_in_format($name, $pageformat) {
2463 if (!$bi = block_instance($name)) {
2467 $formats = $bi->applicable_formats();
2471 foreach ($formats as $format => $allowed) {
2472 $formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';
2473 $depth = substr_count($format, '-');
2474 if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {
2479 if ($accept === NULL) {
2480 $accept = !empty($formats['all']);
2486 * Delete a block, and associated data.
2488 * @param object $instance a row from the block_instances table
2489 * @param bool $nolongerused legacy parameter. Not used, but kept for backwards compatibility.
2490 * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.
2492 function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {
2495 // Allow plugins to use this block before we completely delete it.
2496 if ($pluginsfunction = get_plugins_with_function('pre_block_delete')) {
2497 foreach ($pluginsfunction as $plugintype => $plugins) {
2498 foreach ($plugins as $pluginfunction) {
2499 $pluginfunction($instance);
2504 if ($block = block_instance($instance->blockname
, $instance)) {
2505 $block->instance_delete();
2507 context_helper
::delete_instance(CONTEXT_BLOCK
, $instance->id
);
2509 if (!$skipblockstables) {
2510 $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id
));
2511 $DB->delete_records('block_instances', array('id' => $instance->id
));
2512 $DB->delete_records_list('user_preferences', 'name', array('block'.$instance->id
.'hidden','docked_block_instance_'.$instance->id
));
2517 * Delete multiple blocks at once.
2519 * @param array $instanceids A list of block instance ID.
2521 function blocks_delete_instances($instanceids) {
2525 $count = count($instanceids);
2526 $chunks = [$instanceids];
2527 if ($count > $limit) {
2528 $chunks = array_chunk($instanceids, $limit);
2531 // Perform deletion for each chunk.
2532 foreach ($chunks as $chunk) {
2533 $instances = $DB->get_recordset_list('block_instances', 'id', $chunk);
2534 foreach ($instances as $instance) {
2535 blocks_delete_instance($instance, false, true);
2537 $instances->close();
2539 $DB->delete_records_list('block_positions', 'blockinstanceid', $chunk);
2540 $DB->delete_records_list('block_instances', 'id', $chunk);
2542 $preferences = array();
2543 foreach ($chunk as $instanceid) {
2544 $preferences[] = 'block' . $instanceid . 'hidden';
2545 $preferences[] = 'docked_block_instance_' . $instanceid;
2547 $DB->delete_records_list('user_preferences', 'name', $preferences);
2552 * Delete all the blocks that belong to a particular context.
2554 * @param int $contextid the context id.
2556 function blocks_delete_all_for_context($contextid) {
2558 $instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));
2559 foreach ($instances as $instance) {
2560 blocks_delete_instance($instance, true);
2562 $instances->close();
2563 $DB->delete_records('block_instances', array('parentcontextid' => $contextid));
2564 $DB->delete_records('block_positions', array('contextid' => $contextid));
2568 * Set a block to be visible or hidden on a particular page.
2570 * @param object $instance a row from the block_instances, preferably LEFT JOINed with the
2571 * block_positions table as return by block_manager.
2572 * @param moodle_page $page the back to set the visibility with respect to.
2573 * @param integer $newvisibility 1 for visible, 0 for hidden.
2575 function blocks_set_visibility($instance, $page, $newvisibility) {
2577 if (!empty($instance->blockpositionid
)) {
2578 // Already have local information on this page.
2579 $DB->set_field('block_positions', 'visible', $newvisibility, array('id' => $instance->blockpositionid
));
2583 // Create a new block_positions record.
2585 $bp->blockinstanceid
= $instance->id
;
2586 $bp->contextid
= $page->context
->id
;
2587 $bp->pagetype
= $page->pagetype
;
2588 if ($page->subpage
) {
2589 $bp->subpage
= $page->subpage
;
2591 $bp->visible
= $newvisibility;
2592 $bp->region
= $instance->defaultregion
;
2593 $bp->weight
= $instance->defaultweight
;
2594 $DB->insert_record('block_positions', $bp);
2598 * Get the block record for a particular blockid - that is, a particular type os block.
2600 * @param $int blockid block type id. If null, an array of all block types is returned.
2601 * @param bool $notusedanymore No longer used.
2602 * @return array|object row from block table, or all rows.
2604 function blocks_get_record($blockid = NULL, $notusedanymore = false) {
2606 $blocks = $PAGE->blocks
->get_installed_blocks();
2607 if ($blockid === NULL) {
2609 } else if (isset($blocks[$blockid])) {
2610 return $blocks[$blockid];
2617 * Find a given block by its blockid within a provide array
2619 * @param int $blockid
2620 * @param array $blocksarray
2621 * @return bool|object Instance if found else false
2623 function blocks_find_block($blockid, $blocksarray) {
2624 if (empty($blocksarray)) {
2627 foreach($blocksarray as $blockgroup) {
2628 if (empty($blockgroup)) {
2631 foreach($blockgroup as $instance) {
2632 if($instance->blockid
== $blockid) {
2640 // Functions for programatically adding default blocks to pages ================
2643 * Parse a list of default blocks. See config-dist for a description of the format.
2645 * @param string $blocksstr Determines the starting point that the blocks are added in the region.
2646 * @return array the parsed list of default blocks
2648 function blocks_parse_default_blocks_list($blocksstr) {
2650 $bits = explode(':', $blocksstr);
2651 if (!empty($bits)) {
2652 $leftbits = trim(array_shift($bits));
2653 if ($leftbits != '') {
2654 $blocks[BLOCK_POS_LEFT
] = explode(',', $leftbits);
2657 if (!empty($bits)) {
2658 $rightbits = trim(array_shift($bits));
2659 if ($rightbits != '') {
2660 $blocks[BLOCK_POS_RIGHT
] = explode(',', $rightbits);
2667 * @return array the blocks that should be added to the site course by default.
2669 function blocks_get_default_site_course_blocks() {
2672 if (isset($CFG->defaultblocks_site
)) {
2673 return blocks_parse_default_blocks_list($CFG->defaultblocks_site
);
2676 BLOCK_POS_LEFT
=> array(),
2677 BLOCK_POS_RIGHT
=> array()
2683 * Add the default blocks to a course.
2685 * @param object $course a course object.
2687 function blocks_add_default_course_blocks($course) {
2690 if (isset($CFG->defaultblocks_override
)) {
2691 $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override
);
2693 } else if ($course->id
== SITEID
) {
2694 $blocknames = blocks_get_default_site_course_blocks();
2696 } else if (isset($CFG->{'defaultblocks_' . $course->format
})) {
2697 $blocknames = blocks_parse_default_blocks_list($CFG->{'defaultblocks_' . $course->format
});
2700 require_once($CFG->dirroot
. '/course/lib.php');
2701 $blocknames = course_get_format($course)->get_default_blocks();
2705 if ($course->id
== SITEID
) {
2706 $pagetypepattern = 'site-index';
2708 $pagetypepattern = 'course-view-*';
2710 $page = new moodle_page();
2711 $page->set_course($course);
2712 $page->blocks
->add_blocks($blocknames, $pagetypepattern);
2716 * Add the default system-context blocks. E.g. the admin tree.
2718 function blocks_add_default_system_blocks() {
2721 $page = new moodle_page();
2722 $page->set_context(context_system
::instance());
2723 // We don't add blocks required by the theme, they will be auto-created.
2724 $page->blocks
->add_blocks(array(BLOCK_POS_LEFT
=> array('admin_bookmarks')), 'admin-*', null, null, 2);
2726 if ($defaultmypage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => 1))) {
2727 $subpagepattern = $defaultmypage->id
;
2729 $subpagepattern = null;
2732 if ($defaultmycoursespage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__courses', 'private' => 0))) {
2733 $mycoursesubpagepattern = $defaultmycoursespage->id
;
2735 $mycoursesubpagepattern = null;
2738 $page->blocks
->add_blocks([
2739 BLOCK_POS_RIGHT
=> [
2740 'recentlyaccesseditems',
2750 $page->blocks
->add_blocks([
2755 $mycoursesubpagepattern