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);
46 * Exception thrown when someone tried to do something with a block that does
47 * not exist on a page.
49 * @copyright 2009 Tim Hunt
50 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
53 class block_not_on_page_exception
extends moodle_exception
{
56 * @param int $instanceid the block instance id of the block that was looked for.
57 * @param object $page the current page.
59 public function __construct($instanceid, $page) {
61 $a->instanceid
= $instanceid;
62 $a->url
= $page->url
->out();
63 parent
::__construct('blockdoesnotexistonpage', '', $page->url
->out(), $a);
68 * This class keeps track of the block that should appear on a moodle_page.
70 * The page to work with as passed to the constructor.
72 * @copyright 2009 Tim Hunt
73 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
78 * The UI normally only shows block weights between -MAX_WEIGHT and MAX_WEIGHT,
79 * although other weights are valid.
81 const MAX_WEIGHT
= 10;
83 /// Field declarations =========================================================
86 * the moodle_page we are managing blocks for.
91 /** @var array region name => 1.*/
92 protected $regions = array();
94 /** @var string the region where new blocks are added.*/
95 protected $defaultregion = null;
97 /** @var array will be $DB->get_records('blocks') */
98 protected $allblocks = null;
101 * @var array blocks that this user can add to this page. Will be a subset
102 * of $allblocks, but with array keys block->name. Access this via the
103 * {@link get_addable_blocks()} method to ensure it is lazy-loaded.
105 protected $addableblocks = null;
108 * Will be an array region-name => array(db rows loaded in load_blocks);
111 protected $birecordsbyregion = null;
114 * array region-name => array(block objects); populated as necessary by
115 * the ensure_instances_exist method.
118 protected $blockinstances = array();
121 * array region-name => array(block_contents objects) what actually needs to
122 * be displayed in each region.
125 protected $visibleblockcontent = array();
128 * array region-name => array(block_contents objects) extra block-like things
129 * to be displayed in each region, before the real blocks.
132 protected $extracontent = array();
135 * Used by the block move id, to track whether a block is currently being moved.
137 * When you click on the move icon of a block, first the page needs to reload with
138 * extra UI for choosing a new position for a particular block. In that situation
139 * this field holds the id of the block being moved.
143 protected $movingblock = null;
146 * Show only fake blocks
148 protected $fakeblocksonly = false;
150 /// Constructor ================================================================
154 * @param object $page the moodle_page object object we are managing the blocks for,
155 * or a reasonable faxilimily. (See the comment at the top of this class
156 * and {@link http://en.wikipedia.org/wiki/Duck_typing})
158 public function __construct($page) {
162 /// Getter methods =============================================================
165 * Get an array of all region names on this page where a block may appear
167 * @return array the internal names of the regions on this page where block may appear.
169 public function get_regions() {
170 if (is_null($this->defaultregion
)) {
171 $this->page
->initialise_theme_and_output();
173 return array_keys($this->regions
);
177 * Get the region name of the region blocks are added to by default
179 * @return string the internal names of the region where new blocks are added
180 * by default, and where any blocks from an unrecognised region are shown.
181 * (Imagine that blocks were added with one theme selected, then you switched
182 * to a theme with different block positions.)
184 public function get_default_region() {
185 $this->page
->initialise_theme_and_output();
186 return $this->defaultregion
;
190 * The list of block types that may be added to this page.
192 * @return array block name => record from block table.
194 public function get_addable_blocks() {
195 $this->check_is_loaded();
197 if (!is_null($this->addableblocks
)) {
198 return $this->addableblocks
;
202 $this->addableblocks
= array();
204 $allblocks = blocks_get_record();
205 if (empty($allblocks)) {
206 return $this->addableblocks
;
209 $unaddableblocks = self
::get_undeletable_block_types();
210 $pageformat = $this->page
->pagetype
;
211 foreach($allblocks as $block) {
212 if (!$bi = block_instance($block->name
)) {
215 if ($block->visible
&& !in_array($block->name
, $unaddableblocks) &&
216 ($bi->instance_allow_multiple() ||
!$this->is_block_present($block->name
)) &&
217 blocks_name_allowed_in_format($block->name
, $pageformat) &&
218 $bi->user_can_addto($this->page
)) {
219 $this->addableblocks
[$block->name
] = $block;
223 return $this->addableblocks
;
227 * Given a block name, find out of any of them are currently present in the page
229 * @param string $blockname - the basic name of a block (eg "navigation")
230 * @return boolean - is there one of these blocks in the current page?
232 public function is_block_present($blockname) {
233 if (empty($this->blockinstances
)) {
237 foreach ($this->blockinstances
as $region) {
238 foreach ($region as $instance) {
239 if (empty($instance->instance
->blockname
)) {
242 if ($instance->instance
->blockname
== $blockname) {
251 * Find out if a block type is known by the system
253 * @param string $blockname the name of the type of block.
254 * @param boolean $includeinvisible if false (default) only check 'visible' blocks, that is, blocks enabled by the admin.
255 * @return boolean true if this block in installed.
257 public function is_known_block_type($blockname, $includeinvisible = false) {
258 $blocks = $this->get_installed_blocks();
259 foreach ($blocks as $block) {
260 if ($block->name
== $blockname && ($includeinvisible ||
$block->visible
)) {
268 * Find out if a region exists on a page
270 * @param string $region a region name
271 * @return boolean true if this region exists on this page.
273 public function is_known_region($region) {
274 if (empty($region)) {
277 return array_key_exists($region, $this->regions
);
281 * Get an array of all blocks within a given region
283 * @param string $region a block region that exists on this page.
284 * @return array of block instances.
286 public function get_blocks_for_region($region) {
287 $this->check_is_loaded();
288 $this->ensure_instances_exist($region);
289 return $this->blockinstances
[$region];
293 * Returns an array of block content objects that exist in a region
295 * @param string $region a block region that exists on this page.
296 * @return array of block block_contents objects for all the blocks in a region.
298 public function get_content_for_region($region, $output) {
299 $this->check_is_loaded();
300 $this->ensure_content_created($region, $output);
301 return $this->visibleblockcontent
[$region];
305 * Helper method used by get_content_for_region.
306 * @param string $region region name
307 * @param float $weight weight. May be fractional, since you may want to move a block
308 * between ones with weight 2 and 3, say ($weight would be 2.5).
309 * @return string URL for moving block $this->movingblock to this position.
311 protected function get_move_target_url($region, $weight) {
312 return new moodle_url($this->page
->url
, array('bui_moveid' => $this->movingblock
,
313 'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()));
317 * Determine whether a region contains anything. (Either any real blocks, or
318 * the add new block UI.)
320 * (You may wonder why the $output parameter is required. Unfortunately,
321 * because of the way that blocks work, the only reliable way to find out
322 * if a block will be visible is to get the content for output, and to
323 * get the content, you need a renderer. Fortunately, this is not a
324 * performance problem, because we cache the output that is generated, and
325 * in almost every case where we call region_has_content, we are about to
326 * output the blocks anyway, so we are not doing wasted effort.)
328 * @param string $region a block region that exists on this page.
329 * @param core_renderer $output a core_renderer. normally the global $OUTPUT.
330 * @return boolean Whether there is anything in this region.
332 public function region_has_content($region, $output) {
334 if (!$this->is_known_region($region)) {
337 $this->check_is_loaded();
338 $this->ensure_content_created($region, $output);
339 // if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks()) {
340 // Mark Nielsen's patch - part 1
341 if ($this->page
->user_is_editing() && $this->page
->user_can_edit_blocks() && $this->movingblock
) {
342 // If editing is on, we need all the block regions visible, for the
346 return !empty($this->visibleblockcontent
[$region]) ||
!empty($this->extracontent
[$region]);
350 * Get an array of all of the installed blocks.
352 * @return array contents of the block table.
354 public function get_installed_blocks() {
356 if (is_null($this->allblocks
)) {
357 $this->allblocks
= $DB->get_records('block');
359 return $this->allblocks
;
363 * @return array names of block types that cannot be added or deleted. E.g. array('navigation','settings').
365 public static function get_undeletable_block_types() {
367 $undeletableblocks = false;
368 if (isset($CFG->undeletableblocktypes
)) {
369 $undeletableblocks = $CFG->undeletableblocktypes
;
370 } else if (isset($PAGE->theme
->undeletableblocktypes
)) {
371 $undeletableblocks = $PAGE->theme
->undeletableblocktypes
;
374 if ($undeletableblocks === false) {
375 return array('navigation','settings');
376 } else if ($undeletableblocks === '') {
378 } else if (is_string($undeletableblocks)) {
379 return explode(',', $undeletableblocks);
381 return $undeletableblocks;
385 /// Setter methods =============================================================
388 * Add a region to a page
390 * @param string $region add a named region where blocks may appear on the current page.
391 * This is an internal name, like 'side-pre', not a string to display in the UI.
392 * @param bool $custom True if this is a custom block region, being added by the page rather than the theme layout.
394 public function add_region($region, $custom = true) {
396 $this->check_not_yet_loaded();
398 if (array_key_exists($region, $this->regions
)) {
399 // This here is EXACTLY why we should not be adding block regions into a page. It should
400 // ALWAYS be done in a theme layout.
401 debugging('A custom region conflicts with a block region in the theme.', DEBUG_DEVELOPER
);
403 // We need to register this custom region against the page type being used.
404 // This allows us to check, when performing block actions, that unrecognised regions can be worked with.
405 $type = $this->page
->pagetype
;
406 if (!isset($SESSION->custom_block_regions
)) {
407 $SESSION->custom_block_regions
= array($type => array($region));
408 } else if (!isset($SESSION->custom_block_regions
[$type])) {
409 $SESSION->custom_block_regions
[$type] = array($region);
410 } else if (!in_array($region, $SESSION->custom_block_regions
[$type])) {
411 $SESSION->custom_block_regions
[$type][] = $region;
414 $this->regions
[$region] = 1;
418 * Add an array of regions
421 * @param array $regions this utility method calls add_region for each array element.
423 public function add_regions($regions, $custom = true) {
424 foreach ($regions as $region) {
425 $this->add_region($region, $custom);
430 * Finds custom block regions associated with a page type and registers them with this block manager.
432 * @param string $pagetype
434 public function add_custom_regions_for_pagetype($pagetype) {
436 if (isset($SESSION->custom_block_regions
[$pagetype])) {
437 foreach ($SESSION->custom_block_regions
[$pagetype] as $customregion) {
438 $this->add_region($customregion, false);
444 * Set the default region for new blocks on the page
446 * @param string $defaultregion the internal names of the region where new
447 * blocks should be added by default, and where any blocks from an
448 * unrecognised region are shown.
450 public function set_default_region($defaultregion) {
451 $this->check_not_yet_loaded();
452 if ($defaultregion) {
453 $this->check_region_is_known($defaultregion);
455 $this->defaultregion
= $defaultregion;
459 * Add something that looks like a block, but which isn't an actual block_instance,
462 * @param block_contents $bc the content of the block-like thing.
463 * @param string $region a block region that exists on this page.
465 public function add_fake_block($bc, $region) {
466 $this->page
->initialise_theme_and_output();
467 if (!$this->is_known_region($region)) {
468 $region = $this->get_default_region();
470 if (array_key_exists($region, $this->visibleblockcontent
)) {
471 throw new coding_exception('block_manager has already prepared the blocks in region ' .
472 $region . 'for output. It is too late to add a fake block.');
474 if (!isset($bc->attributes
['data-block'])) {
475 $bc->attributes
['data-block'] = '_fake';
477 $bc->attributes
['class'] .= ' block_fake';
478 $this->extracontent
[$region][] = $bc;
482 * Checks to see whether all of the blocks within the given region are docked
484 * @see region_uses_dock
485 * @param string $region
486 * @return bool True if all of the blocks within that region are docked
488 public function region_completely_docked($region, $output) {
490 // If theme doesn't allow docking or allowblockstodock is not set, then return.
491 if (!$this->page
->theme
->enable_dock ||
empty($CFG->allowblockstodock
)) {
495 // Do not dock the region when the user attemps to move a block.
496 if ($this->movingblock
) {
500 // Block regions should not be docked during editing when all the blocks are hidden.
501 if ($this->page
->user_is_editing() && $this->page
->user_can_edit_blocks()) {
505 $this->check_is_loaded();
506 $this->ensure_content_created($region, $output);
507 if (!$this->region_has_content($region, $output)) {
508 // If the region has no content then nothing is docked at all of course.
511 foreach ($this->visibleblockcontent
[$region] as $instance) {
512 if (!get_user_preferences('docked_block_instance_'.$instance->blockinstanceid
, 0)) {
520 * Checks to see whether any of the blocks within the given regions are docked
522 * @see region_completely_docked
523 * @param array|string $regions array of regions (or single region)
524 * @return bool True if any of the blocks within that region are docked
526 public function region_uses_dock($regions, $output) {
527 if (!$this->page
->theme
->enable_dock
) {
530 $this->check_is_loaded();
531 foreach((array)$regions as $region) {
532 $this->ensure_content_created($region, $output);
533 foreach($this->visibleblockcontent
[$region] as $instance) {
534 if(!empty($instance->content
) && get_user_preferences('docked_block_instance_'.$instance->blockinstanceid
, 0)) {
542 /// Actions ====================================================================
545 * This method actually loads the blocks for our page from the database.
547 * @param boolean|null $includeinvisible
548 * null (default) - load hidden blocks if $this->page->user_is_editing();
549 * true - load hidden blocks.
550 * false - don't load hidden blocks.
552 public function load_blocks($includeinvisible = null) {
555 if (!is_null($this->birecordsbyregion
)) {
560 if ($CFG->version
< 2009050619) {
561 // Upgrade/install not complete. Don't try too show any blocks.
562 $this->birecordsbyregion
= array();
566 // Ensure we have been initialised.
567 if (is_null($this->defaultregion
)) {
568 $this->page
->initialise_theme_and_output();
569 // If there are still no block regions, then there are no blocks on this page.
570 if (empty($this->regions
)) {
571 $this->birecordsbyregion
= array();
576 // Check if we need to load normal blocks
577 if ($this->fakeblocksonly
) {
578 $this->birecordsbyregion
= $this->prepare_per_region_arrays();
582 if (is_null($includeinvisible)) {
583 $includeinvisible = $this->page
->user_is_editing();
585 if ($includeinvisible) {
588 $visiblecheck = 'AND (bp.visible = 1 OR bp.visible IS NULL)';
591 $context = $this->page
->context
;
592 $contexttest = 'bi.parentcontextid IN (:contextid2, :contextid3)';
593 $parentcontextparams = array();
594 $parentcontextids = $context->get_parent_context_ids();
595 if ($parentcontextids) {
596 list($parentcontexttest, $parentcontextparams) =
597 $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED
, 'parentcontext');
598 $contexttest = "($contexttest OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontexttest))";
601 $pagetypepatterns = matching_page_type_patterns($this->page
->pagetype
);
602 list($pagetypepatterntest, $pagetypepatternparams) =
603 $DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED
, 'pagetypepatterntest');
605 $ccselect = ', ' . context_helper
::get_preload_record_columns_sql('ctx');
606 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = bi.id AND ctx.contextlevel = :contextlevel)";
608 $systemcontext = context_system
::instance();
610 'contextlevel' => CONTEXT_BLOCK
,
611 'subpage1' => $this->page
->subpage
,
612 'subpage2' => $this->page
->subpage
,
613 'contextid1' => $context->id
,
614 'contextid2' => $context->id
,
615 'contextid3' => $systemcontext->id
,
616 'pagetype' => $this->page
->pagetype
,
618 if ($this->page
->subpage
=== '') {
619 $params['subpage1'] = '';
620 $params['subpage2'] = '';
624 bp.id AS blockpositionid,
627 bi.showinsubcontexts,
632 COALESCE(bp.visible, 1) AS visible,
633 COALESCE(bp.region, bi.defaultregion) AS region,
634 COALESCE(bp.weight, bi.defaultweight) AS weight,
638 FROM {block_instances} bi
639 JOIN {block} b ON bi.blockname = b.name
640 LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id
641 AND bp.contextid = :contextid1
642 AND bp.pagetype = :pagetype
643 AND bp.subpage = :subpage1
648 AND bi.pagetypepattern $pagetypepatterntest
649 AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)
654 COALESCE(bp.region, bi.defaultregion),
655 COALESCE(bp.weight, bi.defaultweight),
657 $blockinstances = $DB->get_recordset_sql($sql, $params +
$parentcontextparams +
$pagetypepatternparams);
659 $this->birecordsbyregion
= $this->prepare_per_region_arrays();
661 foreach ($blockinstances as $bi) {
662 context_helper
::preload_from_record($bi);
663 if ($this->is_known_region($bi->region
)) {
664 $this->birecordsbyregion
[$bi->region
][] = $bi;
670 // Pages don't necessarily have a defaultregion. The one time this can
671 // happen is when there are no theme block regions, but the script itself
672 // has a block region in the main content area.
673 if (!empty($this->defaultregion
)) {
674 $this->birecordsbyregion
[$this->defaultregion
] =
675 array_merge($this->birecordsbyregion
[$this->defaultregion
], $unknown);
680 * Add a block to the current page, or related pages. The block is added to
681 * context $this->page->contextid. If $pagetypepattern $subpagepattern
683 * @param string $blockname The type of block to add.
684 * @param string $region the block region on this page to add the block to.
685 * @param integer $weight determines the order where this block appears in the region.
686 * @param boolean $showinsubcontexts whether this block appears in subcontexts, or just the current context.
687 * @param string|null $pagetypepattern which page types this block should appear on. Defaults to just the current page type.
688 * @param string|null $subpagepattern which subpage this block should appear on. NULL = any (the default), otherwise only the specified subpage.
690 public function add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern = NULL, $subpagepattern = NULL) {
692 // Allow invisible blocks because this is used when adding default page blocks, which
693 // might include invisible ones if the user makes some default blocks invisible
694 $this->check_known_block_type($blockname, true);
695 $this->check_region_is_known($region);
697 if (empty($pagetypepattern)) {
698 $pagetypepattern = $this->page
->pagetype
;
701 $blockinstance = new stdClass
;
702 $blockinstance->blockname
= $blockname;
703 $blockinstance->parentcontextid
= $this->page
->context
->id
;
704 $blockinstance->showinsubcontexts
= !empty($showinsubcontexts);
705 $blockinstance->pagetypepattern
= $pagetypepattern;
706 $blockinstance->subpagepattern
= $subpagepattern;
707 $blockinstance->defaultregion
= $region;
708 $blockinstance->defaultweight
= $weight;
709 $blockinstance->configdata
= '';
710 $blockinstance->id
= $DB->insert_record('block_instances', $blockinstance);
712 // Ensure the block context is created.
713 context_block
::instance($blockinstance->id
);
715 // If the new instance was created, allow it to do additional setup
716 if ($block = block_instance($blockname, $blockinstance)) {
717 $block->instance_create();
721 public function add_block_at_end_of_default_region($blockname) {
722 if (empty($this->birecordsbyregion
)) {
723 // No blocks or block regions exist yet.
726 $defaulregion = $this->get_default_region();
728 $lastcurrentblock = end($this->birecordsbyregion
[$defaulregion]);
729 if ($lastcurrentblock) {
730 $weight = $lastcurrentblock->weight +
1;
735 if ($this->page
->subpage
) {
736 $subpage = $this->page
->subpage
;
741 // Special case. Course view page type include the course format, but we
742 // want to add the block non-format-specifically.
743 $pagetypepattern = $this->page
->pagetype
;
744 if (strpos($pagetypepattern, 'course-view') === 0) {
745 $pagetypepattern = 'course-view-*';
748 // We should end using this for ALL the blocks, making always the 1st option
749 // the default one to be used. Until then, this is one hack to avoid the
750 // 'pagetypewarning' message on blocks initial edition (MDL-27829) caused by
751 // non-existing $pagetypepattern set. This way at least we guarantee one "valid"
752 // (the FIRST $pagetypepattern will be set)
754 // We are applying it to all blocks created in mod pages for now and only if the
755 // default pagetype is not one of the available options
756 if (preg_match('/^mod-.*-/', $pagetypepattern)) {
757 $pagetypelist = generate_page_type_patterns($this->page
->pagetype
, null, $this->page
->context
);
758 // Only go for the first if the pagetype is not a valid option
759 if (is_array($pagetypelist) && !array_key_exists($pagetypepattern, $pagetypelist)) {
760 $pagetypepattern = key($pagetypelist);
763 // Surely other pages like course-report will need this too, they just are not important
764 // enough now. This will be decided in the coming days. (MDL-27829, MDL-28150)
766 $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);
770 * Convenience method, calls add_block repeatedly for all the blocks in $blocks. Optionally, a starting weight
771 * can be used to decide the starting point that blocks are added in the region, the weight is passed to {@link add_block}
772 * and incremented by the position of the block in the $blocks array
774 * @param array $blocks array with array keys the region names, and values an array of block names.
775 * @param string $pagetypepattern optional. Passed to {@link add_block()}
776 * @param string $subpagepattern optional. Passed to {@link add_block()}
777 * @param boolean $showinsubcontexts optional. Passed to {@link add_block()}
778 * @param integer $weight optional. Determines the starting point that the blocks are added in the region.
780 public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {
781 $initialweight = $weight;
782 $this->add_regions(array_keys($blocks), false);
783 foreach ($blocks as $region => $regionblocks) {
784 foreach ($regionblocks as $offset => $blockname) {
785 $weight = $initialweight +
$offset;
786 $this->add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern, $subpagepattern);
792 * Move a block to a new position on this page.
794 * If this block cannot appear on any other pages, then we change defaultposition/weight
795 * in the block_instances table. Otherwise we just set the position on this page.
797 * @param $blockinstanceid the block instance id.
798 * @param $newregion the new region name.
799 * @param $newweight the new weight.
801 public function reposition_block($blockinstanceid, $newregion, $newweight) {
804 $this->check_region_is_known($newregion);
805 $inst = $this->find_instance($blockinstanceid);
807 $bi = $inst->instance
;
808 if ($bi->weight
== $bi->defaultweight
&& $bi->region
== $bi->defaultregion
&&
809 !$bi->showinsubcontexts
&& strpos($bi->pagetypepattern
, '*') === false &&
810 (!$this->page
->subpage ||
$bi->subpagepattern
)) {
812 // Set default position
813 $newbi = new stdClass
;
814 $newbi->id
= $bi->id
;
815 $newbi->defaultregion
= $newregion;
816 $newbi->defaultweight
= $newweight;
817 $DB->update_record('block_instances', $newbi);
819 if ($bi->blockpositionid
) {
821 $bp->id
= $bi->blockpositionid
;
822 $bp->region
= $newregion;
823 $bp->weight
= $newweight;
824 $DB->update_record('block_positions', $bp);
828 // Just set position on this page.
830 $bp->region
= $newregion;
831 $bp->weight
= $newweight;
833 if ($bi->blockpositionid
) {
834 $bp->id
= $bi->blockpositionid
;
835 $DB->update_record('block_positions', $bp);
838 $bp->blockinstanceid
= $bi->id
;
839 $bp->contextid
= $this->page
->context
->id
;
840 $bp->pagetype
= $this->page
->pagetype
;
841 if ($this->page
->subpage
) {
842 $bp->subpage
= $this->page
->subpage
;
846 $bp->visible
= $bi->visible
;
847 $DB->insert_record('block_positions', $bp);
853 * Find a given block by its instance id
855 * @param integer $instanceid
858 public function find_instance($instanceid) {
859 foreach ($this->regions
as $region => $notused) {
860 $this->ensure_instances_exist($region);
861 foreach($this->blockinstances
[$region] as $instance) {
862 if ($instance->instance
->id
== $instanceid) {
867 throw new block_not_on_page_exception($instanceid, $this->page
);
870 /// Inner workings =============================================================
873 * Check whether the page blocks have been loaded yet
875 * @return void Throws coding exception if already loaded
877 protected function check_not_yet_loaded() {
878 if (!is_null($this->birecordsbyregion
)) {
879 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.');
884 * Check whether the page blocks have been loaded yet
886 * Nearly identical to the above function {@link check_not_yet_loaded()} except different message
888 * @return void Throws coding exception if already loaded
890 protected function check_is_loaded() {
891 if (is_null($this->birecordsbyregion
)) {
892 throw new coding_exception('block_manager has not yet loaded the blocks, to it is too soon to request the information you asked for.');
897 * Check if a block type is known and usable
899 * @param string $blockname The block type name to search for
900 * @param bool $includeinvisible Include disabled block types in the initial pass
901 * @return void Coding Exception thrown if unknown or not enabled
903 protected function check_known_block_type($blockname, $includeinvisible = false) {
904 if (!$this->is_known_block_type($blockname, $includeinvisible)) {
905 if ($this->is_known_block_type($blockname, true)) {
906 throw new coding_exception('Unknown block type ' . $blockname);
908 throw new coding_exception('Block type ' . $blockname . ' has been disabled by the administrator.');
914 * Check if a region is known by its name
916 * @param string $region
917 * @return void Coding Exception thrown if the region is not known
919 protected function check_region_is_known($region) {
920 if (!$this->is_known_region($region)) {
921 throw new coding_exception('Trying to reference an unknown block region ' . $region);
926 * Returns an array of region names as keys and nested arrays for values
928 * @return array an array where the array keys are the region names, and the array
929 * values are empty arrays.
931 protected function prepare_per_region_arrays() {
933 foreach ($this->regions
as $region => $notused) {
934 $result[$region] = array();
940 * Create a set of new block instance from a record array
942 * @param array $birecords An array of block instance records
943 * @return array An array of instantiated block_instance objects
945 protected function create_block_instances($birecords) {
947 foreach ($birecords as $record) {
948 if ($blockobject = block_instance($record->blockname
, $record, $this->page
)) {
949 $results[] = $blockobject;
956 * Create all the block instances for all the blocks that were loaded by
957 * load_blocks. This is used, for example, to ensure that all blocks get a
958 * chance to initialise themselves via the {@link block_base::specialize()}
959 * method, before any output is done.
961 public function create_all_block_instances() {
965 // If there are any un-removable blocks that were not created - force them.
966 $undeletable = $this->get_undeletable_block_types();
967 foreach ($undeletable as $forced) {
968 if (empty($forced)) {
972 foreach ($this->get_regions() as $region) {
973 foreach($this->birecordsbyregion
[$region] as $instance) {
974 if ($instance->blockname
== $forced) {
980 $this->add_block_at_end_of_default_region($forced);
986 // Some blocks were missing. Lets do it again.
987 $this->birecordsbyregion
= null;
988 $this->load_blocks();
990 foreach ($this->get_regions() as $region) {
991 $this->ensure_instances_exist($region);
997 * Return an array of content objects from a set of block instances
999 * @param array $instances An array of block instances
1000 * @param renderer_base The renderer to use.
1001 * @param string $region the region name.
1002 * @return array An array of block_content (and possibly block_move_target) objects.
1004 protected function create_block_contents($instances, $output, $region) {
1009 if ($this->movingblock
) {
1010 $first = reset($instances);
1012 $lastweight = $first->instance
->weight
- 2;
1016 foreach ($instances as $instance) {
1017 $content = $instance->get_content_for_output($output);
1018 if (empty($content)) {
1022 if ($this->movingblock
&& $lastweight != $instance->instance
->weight
&&
1023 $content->blockinstanceid
!= $this->movingblock
&& $lastblock != $this->movingblock
) {
1024 $results[] = new block_move_target($this->get_move_target_url($region, ($lastweight +
$instance->instance
->weight
)/2));
1027 if ($content->blockinstanceid
== $this->movingblock
) {
1028 $content->add_class('beingmoved');
1029 $content->annotation
.= get_string('movingthisblockcancel', 'block',
1030 html_writer
::link($this->page
->url
, get_string('cancel')));
1033 $results[] = $content;
1034 $lastweight = $instance->instance
->weight
;
1035 $lastblock = $instance->instance
->id
;
1038 if ($this->movingblock
&& $lastblock != $this->movingblock
) {
1039 $results[] = new block_move_target($this->get_move_target_url($region, $lastweight +
1));
1045 * Ensure block instances exist for a given region
1047 * @param string $region Check for bi's with the instance with this name
1049 protected function ensure_instances_exist($region) {
1050 $this->check_region_is_known($region);
1051 if (!array_key_exists($region, $this->blockinstances
)) {
1052 $this->blockinstances
[$region] =
1053 $this->create_block_instances($this->birecordsbyregion
[$region]);
1058 * Ensure that there is some content within the given region
1060 * @param string $region The name of the region to check
1062 public function ensure_content_created($region, $output) {
1063 $this->ensure_instances_exist($region);
1064 if (!array_key_exists($region, $this->visibleblockcontent
)) {
1065 $contents = array();
1066 if (array_key_exists($region, $this->extracontent
)) {
1067 $contents = $this->extracontent
[$region];
1069 $contents = array_merge($contents, $this->create_block_contents($this->blockinstances
[$region], $output, $region));
1070 if ($region == $this->defaultregion
) {
1071 $addblockui = block_add_block_ui($this->page
, $output);
1073 $contents[] = $addblockui;
1076 $this->visibleblockcontent
[$region] = $contents;
1080 /// Process actions from the URL ===============================================
1083 * Get the appropriate list of editing icons for a block. This is used
1084 * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}.
1086 * @param $output The core_renderer to use when generating the output. (Need to get icon paths.)
1087 * @return an array in the format for {@link block_contents::$controls}
1089 public function edit_controls($block) {
1092 $controls = array();
1093 $actionurl = $this->page
->url
->out(false, array('sesskey'=> sesskey()));
1094 $blocktitle = $block->title
;
1095 if (empty($blocktitle)) {
1096 $blocktitle = $block->arialabel
;
1099 if ($this->page
->user_can_edit_blocks()) {
1101 $str = new lang_string('moveblock', 'block', $blocktitle);
1102 $controls[] = new action_menu_link_primary(
1103 new moodle_url($actionurl, array('bui_moveid' => $block->instance
->id
)),
1104 new pix_icon('t/move', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1106 array('class' => 'editing_move')
1111 if ($this->page
->user_can_edit_blocks() ||
$block->user_can_edit()) {
1112 // Edit config icon - always show - needed for positioning UI.
1113 $str = new lang_string('configureblock', 'block', $blocktitle);
1114 $controls[] = new action_menu_link_secondary(
1115 new moodle_url($actionurl, array('bui_editid' => $block->instance
->id
)),
1116 new pix_icon('t/edit', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1118 array('class' => 'editing_edit')
1123 if ($this->page
->user_can_edit_blocks() && $block->instance_can_be_hidden()) {
1125 if ($block->instance
->visible
) {
1126 $str = new lang_string('hideblock', 'block', $blocktitle);
1127 $url = new moodle_url($actionurl, array('bui_hideid' => $block->instance
->id
));
1128 $icon = new pix_icon('t/hide', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1129 $attributes = array('class' => 'editing_hide');
1131 $str = new lang_string('showblock', 'block', $blocktitle);
1132 $url = new moodle_url($actionurl, array('bui_showid' => $block->instance
->id
));
1133 $icon = new pix_icon('t/show', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1134 $attributes = array('class' => 'editing_show');
1136 $controls[] = new action_menu_link_secondary($url, $icon, $str, $attributes);
1139 // Display either "Assign roles" or "Permissions" or "Change permissions" icon (whichever first is available).
1142 if (get_assignable_roles($block->context
, ROLENAME_SHORT
)) {
1143 $rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid' => $block->context
->id
));
1144 $str = new lang_string('assignrolesinblock', 'block', $blocktitle);
1145 $icon = 'i/assignroles';
1146 } else if (has_capability('moodle/role:review', $block->context
) or get_overridable_roles($block->context
)) {
1147 $rolesurl = new moodle_url('/admin/roles/permissions.php', array('contextid' => $block->context
->id
));
1148 $str = get_string('permissions', 'role');
1149 $icon = 'i/permissions';
1150 } else if (has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:assign'), $block->context
)) {
1151 $rolesurl = new moodle_url('/admin/roles/check.php', array('contextid' => $block->context
->id
));
1152 $str = get_string('checkpermissions', 'role');
1153 $icon = 'i/checkpermissions';
1157 // TODO: please note it is sloppy to pass urls through page parameters!!
1158 // it is shortened because some web servers (e.g. IIS by default) give
1159 // a 'security' error if you try to pass a full URL as a GET parameter in another URL.
1160 $return = $this->page
->url
->out(false);
1161 $return = str_replace($CFG->wwwroot
. '/', '', $return);
1162 $rolesurl->param('returnurl', $return);
1164 $controls[] = new action_menu_link_secondary(
1166 new pix_icon($icon, $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1168 array('class' => 'editing_roles')
1172 if ($this->user_can_delete_block($block)) {
1174 $str = new lang_string('deleteblock', 'block', $blocktitle);
1175 $controls[] = new action_menu_link_secondary(
1176 new moodle_url($actionurl, array('bui_deleteid' => $block->instance
->id
)),
1177 new pix_icon('t/delete', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1179 array('class' => 'editing_delete')
1187 * @param block_base $block a block that appears on this page.
1188 * @return boolean boolean whether the currently logged in user is allowed to delete this block.
1190 protected function user_can_delete_block($block) {
1191 return $this->page
->user_can_edit_blocks() && $block->user_can_edit() &&
1192 $block->user_can_addto($this->page
) &&
1193 !in_array($block->instance
->blockname
, self
::get_undeletable_block_types());
1197 * Process any block actions that were specified in the URL.
1199 * @return boolean true if anything was done. False if not.
1201 public function process_url_actions() {
1202 if (!$this->page
->user_is_editing()) {
1205 return $this->process_url_add() ||
$this->process_url_delete() ||
1206 $this->process_url_show_hide() ||
$this->process_url_edit() ||
1207 $this->process_url_move();
1211 * Handle adding a block.
1212 * @return boolean true if anything was done. False if not.
1214 public function process_url_add() {
1215 $blocktype = optional_param('bui_addblock', null, PARAM_PLUGIN
);
1222 if (!$this->page
->user_can_edit_blocks()) {
1223 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('addblock'));
1226 if (!array_key_exists($blocktype, $this->get_addable_blocks())) {
1227 throw new moodle_exception('cannotaddthisblocktype', '', $this->page
->url
->out(), $blocktype);
1230 $this->add_block_at_end_of_default_region($blocktype);
1232 // If the page URL was a guess, it will contain the bui_... param, so we must make sure it is not there.
1233 $this->page
->ensure_param_not_in_url('bui_addblock');
1239 * Handle deleting a block.
1240 * @return boolean true if anything was done. False if not.
1242 public function process_url_delete() {
1243 global $CFG, $PAGE, $OUTPUT;
1245 $blockid = optional_param('bui_deleteid', null, PARAM_INT
);
1246 $confirmdelete = optional_param('bui_confirm', null, PARAM_INT
);
1253 $block = $this->page
->blocks
->find_instance($blockid);
1254 if (!$this->user_can_delete_block($block)) {
1255 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('deleteablock'));
1258 if (!$confirmdelete) {
1259 $deletepage = new moodle_page();
1260 $deletepage->set_pagelayout('admin');
1261 $deletepage->set_course($this->page
->course
);
1262 $deletepage->set_context($this->page
->context
);
1263 if ($this->page
->cm
) {
1264 $deletepage->set_cm($this->page
->cm
);
1267 $deleteurlbase = str_replace($CFG->wwwroot
. '/', '/', $this->page
->url
->out_omit_querystring());
1268 $deleteurlparams = $this->page
->url
->params();
1269 $deletepage->set_url($deleteurlbase, $deleteurlparams);
1270 $deletepage->set_block_actions_done();
1271 // At this point we are either going to redirect, or display the form, so
1272 // overwrite global $PAGE ready for this. (Formslib refers to it.)
1273 $PAGE = $deletepage;
1274 //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that too
1275 $output = $deletepage->get_renderer('core');
1279 $blocktitle = $block->get_title();
1280 $strdeletecheck = get_string('deletecheck', 'block', $blocktitle);
1281 $message = get_string('deleteblockcheck', 'block', $blocktitle);
1283 // If the block is being shown in sub contexts display a warning.
1284 if ($block->instance
->showinsubcontexts
== 1) {
1285 $parentcontext = context
::instance_by_id($block->instance
->parentcontextid
);
1286 $systemcontext = context_system
::instance();
1287 $messagestring = new stdClass();
1288 $messagestring->location
= $parentcontext->get_context_name();
1290 // Checking for blocks that may have visibility on the front page and pages added on that.
1291 if ($parentcontext->id
!= $systemcontext->id
&& is_inside_frontpage($parentcontext)) {
1292 $messagestring->pagetype
= get_string('showonfrontpageandsubs', 'block');
1294 $pagetypes = generate_page_type_patterns($this->page
->pagetype
, $parentcontext);
1295 $messagestring->pagetype
= $block->instance
->pagetypepattern
;
1296 if (isset($pagetypes[$block->instance
->pagetypepattern
])) {
1297 $messagestring->pagetype
= $pagetypes[$block->instance
->pagetypepattern
];
1301 $message = get_string('deleteblockwarning', 'block', $messagestring);
1304 $PAGE->navbar
->add($strdeletecheck);
1305 $PAGE->set_title($blocktitle . ': ' . $strdeletecheck);
1306 $PAGE->set_heading($site->fullname
);
1307 echo $OUTPUT->header();
1308 $confirmurl = new moodle_url($deletepage->url
, array('sesskey' => sesskey(), 'bui_deleteid' => $block->instance
->id
, 'bui_confirm' => 1));
1309 $cancelurl = new moodle_url($deletepage->url
);
1310 $yesbutton = new single_button($confirmurl, get_string('yes'));
1311 $nobutton = new single_button($cancelurl, get_string('no'));
1312 echo $OUTPUT->confirm($message, $yesbutton, $nobutton);
1313 echo $OUTPUT->footer();
1314 // Make sure that nothing else happens after we have displayed this form.
1317 blocks_delete_instance($block->instance
);
1318 // bui_deleteid and bui_confirm should not be in the PAGE url.
1319 $this->page
->ensure_param_not_in_url('bui_deleteid');
1320 $this->page
->ensure_param_not_in_url('bui_confirm');
1326 * Handle showing or hiding a block.
1327 * @return boolean true if anything was done. False if not.
1329 public function process_url_show_hide() {
1330 if ($blockid = optional_param('bui_hideid', null, PARAM_INT
)) {
1332 } else if ($blockid = optional_param('bui_showid', null, PARAM_INT
)) {
1340 $block = $this->page
->blocks
->find_instance($blockid);
1342 if (!$this->page
->user_can_edit_blocks()) {
1343 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('hideshowblocks'));
1344 } else if (!$block->instance_can_be_hidden()) {
1348 blocks_set_visibility($block->instance
, $this->page
, $newvisibility);
1350 // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1351 $this->page
->ensure_param_not_in_url('bui_hideid');
1352 $this->page
->ensure_param_not_in_url('bui_showid');
1358 * Handle showing/processing the submission from the block editing form.
1359 * @return boolean true if the form was submitted and the new config saved. Does not
1360 * return if the editing form was displayed. False otherwise.
1362 public function process_url_edit() {
1363 global $CFG, $DB, $PAGE, $OUTPUT;
1365 $blockid = optional_param('bui_editid', null, PARAM_INT
);
1371 require_once($CFG->dirroot
. '/blocks/edit_form.php');
1373 $block = $this->find_instance($blockid);
1375 if (!$block->user_can_edit() && !$this->page
->user_can_edit_blocks()) {
1376 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('editblock'));
1379 $editpage = new moodle_page();
1380 $editpage->set_pagelayout('admin');
1381 $editpage->set_course($this->page
->course
);
1382 //$editpage->set_context($block->context);
1383 $editpage->set_context($this->page
->context
);
1384 if ($this->page
->cm
) {
1385 $editpage->set_cm($this->page
->cm
);
1387 $editurlbase = str_replace($CFG->wwwroot
. '/', '/', $this->page
->url
->out_omit_querystring());
1388 $editurlparams = $this->page
->url
->params();
1389 $editurlparams['bui_editid'] = $blockid;
1390 $editpage->set_url($editurlbase, $editurlparams);
1391 $editpage->set_block_actions_done();
1392 // At this point we are either going to redirect, or display the form, so
1393 // overwrite global $PAGE ready for this. (Formslib refers to it.)
1395 //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that to
1396 $output = $editpage->get_renderer('core');
1399 $formfile = $CFG->dirroot
. '/blocks/' . $block->name() . '/edit_form.php';
1400 if (is_readable($formfile)) {
1401 require_once($formfile);
1402 $classname = 'block_' . $block->name() . '_edit_form';
1403 if (!class_exists($classname)) {
1404 $classname = 'block_edit_form';
1407 $classname = 'block_edit_form';
1410 $mform = new $classname($editpage->url
, $block, $this->page
);
1411 $mform->set_data($block->instance
);
1413 if ($mform->is_cancelled()) {
1414 redirect($this->page
->url
);
1416 } else if ($data = $mform->get_data()) {
1418 $bi->id
= $block->instance
->id
;
1420 // This may get overwritten by the special case handling below.
1421 $bi->pagetypepattern
= $data->bui_pagetypepattern
;
1422 $bi->showinsubcontexts
= (bool) $data->bui_contexts
;
1423 if (empty($data->bui_subpagepattern
) ||
$data->bui_subpagepattern
== '%@NULL@%') {
1424 $bi->subpagepattern
= null;
1426 $bi->subpagepattern
= $data->bui_subpagepattern
;
1429 $systemcontext = context_system
::instance();
1430 $frontpagecontext = context_course
::instance(SITEID
);
1431 $parentcontext = context
::instance_by_id($data->bui_parentcontextid
);
1433 // Updating stickiness and contexts. See MDL-21375 for details.
1434 if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination
1436 // Explicitly set the default context
1437 $bi->parentcontextid
= $parentcontext->id
;
1439 if ($data->bui_editingatfrontpage
) { // The block is being edited on the front page
1441 // The interface here is a special case because the pagetype pattern is
1442 // totally derived from the context menu. Here are the excpetions. MDL-30340
1444 switch ($data->bui_contexts
) {
1445 case BUI_CONTEXTS_ENTIRE_SITE
:
1446 // The user wants to show the block across the entire site
1447 $bi->parentcontextid
= $systemcontext->id
;
1448 $bi->showinsubcontexts
= true;
1449 $bi->pagetypepattern
= '*';
1451 case BUI_CONTEXTS_FRONTPAGE_SUBS
:
1452 // The user wants the block shown on the front page and all subcontexts
1453 $bi->parentcontextid
= $frontpagecontext->id
;
1454 $bi->showinsubcontexts
= true;
1455 $bi->pagetypepattern
= '*';
1457 case BUI_CONTEXTS_FRONTPAGE_ONLY
:
1458 // The user want to show the front page on the frontpage only
1459 $bi->parentcontextid
= $frontpagecontext->id
;
1460 $bi->showinsubcontexts
= false;
1461 $bi->pagetypepattern
= 'site-index';
1462 // This is the only relevant page type anyway but we'll set it explicitly just
1463 // in case the front page grows site-index-* subpages of its own later
1469 $bits = explode('-', $bi->pagetypepattern
);
1470 // hacks for some contexts
1471 if (($parentcontext->contextlevel
== CONTEXT_COURSE
) && ($parentcontext->instanceid
!= SITEID
)) {
1472 // For course context
1473 // is page type pattern is mod-*, change showinsubcontext to 1
1474 if ($bits[0] == 'mod' ||
$bi->pagetypepattern
== '*') {
1475 $bi->showinsubcontexts
= 1;
1477 $bi->showinsubcontexts
= 0;
1479 } else if ($parentcontext->contextlevel
== CONTEXT_USER
) {
1481 // subpagepattern should be null
1482 if ($bits[0] == 'user' or $bits[0] == 'my') {
1483 // we don't need subpagepattern in usercontext
1484 $bi->subpagepattern
= null;
1488 $bi->defaultregion
= $data->bui_defaultregion
;
1489 $bi->defaultweight
= $data->bui_defaultweight
;
1490 $DB->update_record('block_instances', $bi);
1492 if (!empty($block->config
)) {
1493 $config = clone($block->config
);
1495 $config = new stdClass
;
1497 foreach ($data as $configfield => $value) {
1498 if (strpos($configfield, 'config_') !== 0) {
1501 $field = substr($configfield, 7);
1502 $config->$field = $value;
1504 $block->instance_config_save($config);
1507 $bp->visible
= $data->bui_visible
;
1508 $bp->region
= $data->bui_region
;
1509 $bp->weight
= $data->bui_weight
;
1510 $needbprecord = !$data->bui_visible ||
$data->bui_region
!= $data->bui_defaultregion ||
1511 $data->bui_weight
!= $data->bui_defaultweight
;
1513 if ($block->instance
->blockpositionid
&& !$needbprecord) {
1514 $DB->delete_records('block_positions', array('id' => $block->instance
->blockpositionid
));
1516 } else if ($block->instance
->blockpositionid
&& $needbprecord) {
1517 $bp->id
= $block->instance
->blockpositionid
;
1518 $DB->update_record('block_positions', $bp);
1520 } else if ($needbprecord) {
1521 $bp->blockinstanceid
= $block->instance
->id
;
1522 $bp->contextid
= $this->page
->context
->id
;
1523 $bp->pagetype
= $this->page
->pagetype
;
1524 if ($this->page
->subpage
) {
1525 $bp->subpage
= $this->page
->subpage
;
1529 $DB->insert_record('block_positions', $bp);
1532 redirect($this->page
->url
);
1535 $strheading = get_string('blockconfiga', 'moodle', $block->get_title());
1536 $editpage->set_title($strheading);
1537 $editpage->set_heading($strheading);
1538 $bits = explode('-', $this->page
->pagetype
);
1539 if ($bits[0] == 'tag' && !empty($this->page
->subpage
)) {
1540 // better navbar for tag pages
1541 $editpage->navbar
->add(get_string('tags'), new moodle_url('/tag/'));
1542 $tag = core_tag_tag
::get($this->page
->subpage
);
1543 // tag search page doesn't have subpageid
1545 $editpage->navbar
->add($tag->get_display_name(), $tag->get_view_url());
1548 $editpage->navbar
->add($block->get_title());
1549 $editpage->navbar
->add(get_string('configuration'));
1550 echo $output->header();
1551 echo $output->heading($strheading, 2);
1553 echo $output->footer();
1559 * Handle showing/processing the submission from the block editing form.
1560 * @return boolean true if the form was submitted and the new config saved. Does not
1561 * return if the editing form was displayed. False otherwise.
1563 public function process_url_move() {
1564 global $CFG, $DB, $PAGE;
1566 $blockid = optional_param('bui_moveid', null, PARAM_INT
);
1573 $block = $this->find_instance($blockid);
1575 if (!$this->page
->user_can_edit_blocks()) {
1576 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('editblock'));
1579 $newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT
);
1580 $newweight = optional_param('bui_newweight', null, PARAM_FLOAT
);
1581 if (!$newregion ||
is_null($newweight)) {
1582 // Don't have a valid target position yet, must be just starting the move.
1583 $this->movingblock
= $blockid;
1584 $this->page
->ensure_param_not_in_url('bui_moveid');
1588 if (!$this->is_known_region($newregion)) {
1589 throw new moodle_exception('unknownblockregion', '', $this->page
->url
, $newregion);
1592 // Move this block. This may involve moving other nearby blocks.
1593 $blocks = $this->birecordsbyregion
[$newregion];
1595 $maxweight = self
::MAX_WEIGHT
;
1596 $minweight = -self
::MAX_WEIGHT
;
1598 // Initialise the used weights and spareweights array with the default values
1599 $spareweights = array();
1600 $usedweights = array();
1601 for ($i = $minweight; $i <= $maxweight; $i++
) {
1602 $spareweights[$i] = $i;
1603 $usedweights[$i] = array();
1606 // Check each block and sort out where we have used weights
1607 foreach ($blocks as $bi) {
1608 if ($bi->weight
> $maxweight) {
1609 // If this statement is true then the blocks weight is more than the
1610 // current maximum. To ensure that we can get the best block position
1611 // we will initialise elements within the usedweights and spareweights
1612 // arrays between the blocks weight (which will then be the new max) and
1614 $parseweight = $bi->weight
;
1615 while (!array_key_exists($parseweight, $usedweights)) {
1616 $usedweights[$parseweight] = array();
1617 $spareweights[$parseweight] = $parseweight;
1620 $maxweight = $bi->weight
;
1621 } else if ($bi->weight
< $minweight) {
1622 // As above except this time the blocks weight is LESS than the
1623 // the current minimum, so we will initialise the array from the
1624 // blocks weight (new minimum) to the current minimum
1625 $parseweight = $bi->weight
;
1626 while (!array_key_exists($parseweight, $usedweights)) {
1627 $usedweights[$parseweight] = array();
1628 $spareweights[$parseweight] = $parseweight;
1631 $minweight = $bi->weight
;
1633 if ($bi->id
!= $block->instance
->id
) {
1634 unset($spareweights[$bi->weight
]);
1635 $usedweights[$bi->weight
][] = $bi->id
;
1639 // First we find the nearest gap in the list of weights.
1640 $bestdistance = max(abs($newweight - self
::MAX_WEIGHT
), abs($newweight + self
::MAX_WEIGHT
)) +
1;
1642 foreach ($spareweights as $spareweight) {
1643 if (abs($newweight - $spareweight) < $bestdistance) {
1644 $bestdistance = abs($newweight - $spareweight);
1645 $bestgap = $spareweight;
1649 // If there is no gap, we have to go outside -self::MAX_WEIGHT .. self::MAX_WEIGHT.
1650 if (is_null($bestgap)) {
1651 $bestgap = self
::MAX_WEIGHT +
1;
1652 while (!empty($usedweights[$bestgap])) {
1657 // Now we know the gap we are aiming for, so move all the blocks along.
1658 if ($bestgap < $newweight) {
1659 $newweight = floor($newweight);
1660 for ($weight = $bestgap +
1; $weight <= $newweight; $weight++
) {
1661 if (array_key_exists($weight, $usedweights)) {
1662 foreach ($usedweights[$weight] as $biid) {
1663 $this->reposition_block($biid, $newregion, $weight - 1);
1667 $this->reposition_block($block->instance
->id
, $newregion, $newweight);
1669 $newweight = ceil($newweight);
1670 for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {
1671 if (array_key_exists($weight, $usedweights)) {
1672 foreach ($usedweights[$weight] as $biid) {
1673 $this->reposition_block($biid, $newregion, $weight +
1);
1677 $this->reposition_block($block->instance
->id
, $newregion, $newweight);
1680 $this->page
->ensure_param_not_in_url('bui_moveid');
1681 $this->page
->ensure_param_not_in_url('bui_newregion');
1682 $this->page
->ensure_param_not_in_url('bui_newweight');
1687 * Turns the display of normal blocks either on or off.
1689 * @param bool $setting
1691 public function show_only_fake_blocks($setting = true) {
1692 $this->fakeblocksonly
= $setting;
1696 /// Helper functions for working with block classes ============================
1699 * Call a class method (one that does not require a block instance) on a block class.
1701 * @param string $blockname the name of the block.
1702 * @param string $method the method name.
1703 * @param array $param parameters to pass to the method.
1704 * @return mixed whatever the method returns.
1706 function block_method_result($blockname, $method, $param = NULL) {
1707 if(!block_load_class($blockname)) {
1710 return call_user_func(array('block_'.$blockname, $method), $param);
1714 * Creates a new instance of the specified block class.
1716 * @param string $blockname the name of the block.
1717 * @param $instance block_instances DB table row (optional).
1718 * @param moodle_page $page the page this block is appearing on.
1719 * @return block_base the requested block instance.
1721 function block_instance($blockname, $instance = NULL, $page = NULL) {
1722 if(!block_load_class($blockname)) {
1725 $classname = 'block_'.$blockname;
1726 $retval = new $classname;
1727 if($instance !== NULL) {
1728 if (is_null($page)) {
1732 $retval->_load_instance($instance, $page);
1738 * Load the block class for a particular type of block.
1740 * @param string $blockname the name of the block.
1741 * @return boolean success or failure.
1743 function block_load_class($blockname) {
1746 if(empty($blockname)) {
1750 $classname = 'block_'.$blockname;
1752 if(class_exists($classname)) {
1756 $blockpath = $CFG->dirroot
.'/blocks/'.$blockname.'/block_'.$blockname.'.php';
1758 if (file_exists($blockpath)) {
1759 require_once($CFG->dirroot
.'/blocks/moodleblock.class.php');
1760 include_once($blockpath);
1762 //debugging("$blockname code does not exist in $blockpath", DEBUG_DEVELOPER);
1766 return class_exists($classname);
1770 * Given a specific page type, return all the page type patterns that might
1773 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1774 * @return array an array of all the page type patterns that might match this page type.
1776 function matching_page_type_patterns($pagetype) {
1777 $patterns = array($pagetype);
1778 $bits = explode('-', $pagetype);
1779 if (count($bits) == 3 && $bits[0] == 'mod') {
1780 if ($bits[2] == 'view') {
1781 $patterns[] = 'mod-*-view';
1782 } else if ($bits[2] == 'index') {
1783 $patterns[] = 'mod-*-index';
1786 while (count($bits) > 0) {
1787 $patterns[] = implode('-', $bits) . '-*';
1795 * Give an specific pattern, return all the page type patterns that would also match it.
1797 * @param string $pattern the pattern, e.g. 'mod-forum-*' or 'mod-quiz-view'.
1798 * @return array of all the page type patterns matching.
1800 function matching_page_type_patterns_from_pattern($pattern) {
1801 $patterns = array($pattern);
1802 if ($pattern === '*') {
1806 // Only keep the part before the star because we will append -* to all the bits.
1807 $star = strpos($pattern, '-*');
1808 if ($star !== false) {
1809 $pattern = substr($pattern, 0, $star);
1812 $patterns = array_merge($patterns, matching_page_type_patterns($pattern));
1813 $patterns = array_unique($patterns);
1819 * Given a specific page type, parent context and currect context, return all the page type patterns
1820 * that might be used by this block.
1822 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1823 * @param stdClass $parentcontext Block's parent context
1824 * @param stdClass $currentcontext Current context of block
1825 * @return array an array of all the page type patterns that might match this page type.
1827 function generate_page_type_patterns($pagetype, $parentcontext = null, $currentcontext = null) {
1828 global $CFG; // Required for includes bellow.
1830 $bits = explode('-', $pagetype);
1832 $core = core_component
::get_core_subsystems();
1833 $plugins = core_component
::get_plugin_types();
1835 //progressively strip pieces off the page type looking for a match
1836 $componentarray = null;
1837 for ($i = count($bits); $i > 0; $i--) {
1838 $possiblecomponentarray = array_slice($bits, 0, $i);
1839 $possiblecomponent = implode('', $possiblecomponentarray);
1841 // Check to see if the component is a core component
1842 if (array_key_exists($possiblecomponent, $core) && !empty($core[$possiblecomponent])) {
1843 $libfile = $core[$possiblecomponent].'/lib.php';
1844 if (file_exists($libfile)) {
1845 require_once($libfile);
1846 $function = $possiblecomponent.'_page_type_list';
1847 if (function_exists($function)) {
1848 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1855 //check the plugin directory and look for a callback
1856 if (array_key_exists($possiblecomponent, $plugins) && !empty($plugins[$possiblecomponent])) {
1858 //We've found a plugin type. Look for a plugin name by getting the next section of page type
1859 if (count($bits) > $i) {
1860 $pluginname = $bits[$i];
1861 $directory = core_component
::get_plugin_directory($possiblecomponent, $pluginname);
1862 if (!empty($directory)){
1863 $libfile = $directory.'/lib.php';
1864 if (file_exists($libfile)) {
1865 require_once($libfile);
1866 $function = $possiblecomponent.'_'.$pluginname.'_page_type_list';
1867 if (!function_exists($function)) {
1868 $function = $pluginname.'_page_type_list';
1870 if (function_exists($function)) {
1871 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1879 //we'll only get to here if we still don't have any patterns
1880 //the plugin type may have a callback
1881 $directory = $plugins[$possiblecomponent];
1882 $libfile = $directory.'/lib.php';
1883 if (file_exists($libfile)) {
1884 require_once($libfile);
1885 $function = $possiblecomponent.'_page_type_list';
1886 if (function_exists($function)) {
1887 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1895 if (empty($patterns)) {
1896 $patterns = default_page_type_list($pagetype, $parentcontext, $currentcontext);
1899 // Ensure that the * pattern is always available if editing block 'at distance', so
1900 // we always can 'bring back' it to the original context. MDL-30340
1901 if ((!isset($currentcontext) or !isset($parentcontext) or $currentcontext->id
!= $parentcontext->id
) && !isset($patterns['*'])) {
1902 // TODO: We could change the string here, showing its 'bring back' meaning
1903 $patterns['*'] = get_string('page-x', 'pagetype');
1910 * Generates a default page type list when a more appropriate callback cannot be decided upon.
1912 * @param string $pagetype
1913 * @param stdClass $parentcontext
1914 * @param stdClass $currentcontext
1917 function default_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1918 // Generate page type patterns based on current page type if
1919 // callbacks haven't been defined
1920 $patterns = array($pagetype => $pagetype);
1921 $bits = explode('-', $pagetype);
1922 while (count($bits) > 0) {
1923 $pattern = implode('-', $bits) . '-*';
1924 $pagetypestringname = 'page-'.str_replace('*', 'x', $pattern);
1925 // guessing page type description
1926 if (get_string_manager()->string_exists($pagetypestringname, 'pagetype')) {
1927 $patterns[$pattern] = get_string($pagetypestringname, 'pagetype');
1929 $patterns[$pattern] = $pattern;
1933 $patterns['*'] = get_string('page-x', 'pagetype');
1938 * Generates the page type list for the my moodle page
1940 * @param string $pagetype
1941 * @param stdClass $parentcontext
1942 * @param stdClass $currentcontext
1945 function my_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1946 return array('my-index' => get_string('page-my-index', 'pagetype'));
1950 * Generates the page type list for a module by either locating and using the modules callback
1951 * or by generating a default list.
1953 * @param string $pagetype
1954 * @param stdClass $parentcontext
1955 * @param stdClass $currentcontext
1958 function mod_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1959 $patterns = plugin_page_type_list($pagetype, $parentcontext, $currentcontext);
1960 if (empty($patterns)) {
1961 // if modules don't have callbacks
1962 // generate two default page type patterns for modules only
1963 $bits = explode('-', $pagetype);
1964 $patterns = array($pagetype => $pagetype);
1965 if ($bits[2] == 'view') {
1966 $patterns['mod-*-view'] = get_string('page-mod-x-view', 'pagetype');
1967 } else if ($bits[2] == 'index') {
1968 $patterns['mod-*-index'] = get_string('page-mod-x-index', 'pagetype');
1973 /// Functions update the blocks if required by the request parameters ==========
1976 * Return a {@link block_contents} representing the add a new block UI, if
1977 * this user is allowed to see it.
1979 * @return block_contents an appropriate block_contents, or null if the user
1980 * cannot add any blocks here.
1982 function block_add_block_ui($page, $output) {
1983 global $CFG, $OUTPUT;
1984 if (!$page->user_is_editing() ||
!$page->user_can_edit_blocks()) {
1988 $bc = new block_contents();
1989 $bc->title
= get_string('addblock');
1990 $bc->add_class('block_adminblock');
1991 $bc->attributes
['data-block'] = 'adminblock';
1993 $missingblocks = $page->blocks
->get_addable_blocks();
1994 if (empty($missingblocks)) {
1995 $bc->content
= get_string('noblockstoaddhere');
2000 foreach ($missingblocks as $block) {
2001 $blockobject = block_instance($block->name
);
2002 if ($blockobject !== false && $blockobject->user_can_addto($page)) {
2003 $menu[$block->name
] = $blockobject->get_title();
2006 core_collator
::asort($menu);
2008 $actionurl = new moodle_url($page->url
, array('sesskey'=>sesskey()));
2009 $select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');
2010 $select->set_label(get_string('addblock'), array('class'=>'accesshide'));
2011 $bc->content
= $OUTPUT->render($select);
2016 * Actually delete from the database any blocks that are currently on this page,
2017 * but which should not be there according to blocks_name_allowed_in_format.
2019 * @todo Write/Fix this function. Currently returns immediately
2022 function blocks_remove_inappropriate($course) {
2026 $blockmanager = blocks_get_by_page($page);
2028 if (empty($blockmanager)) {
2032 if (($pageformat = $page->pagetype) == NULL) {
2036 foreach($blockmanager as $region) {
2037 foreach($region as $instance) {
2038 $block = blocks_get_record($instance->blockid);
2039 if(!blocks_name_allowed_in_format($block->name, $pageformat)) {
2040 blocks_delete_instance($instance->instance);
2047 * Check that a given name is in a permittable format
2049 * @param string $name
2050 * @param string $pageformat
2053 function blocks_name_allowed_in_format($name, $pageformat) {
2056 if (!$bi = block_instance($name)) {
2060 $formats = $bi->applicable_formats();
2064 foreach ($formats as $format => $allowed) {
2065 $formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';
2066 $depth = substr_count($format, '-');
2067 if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {
2072 if ($accept === NULL) {
2073 $accept = !empty($formats['all']);
2079 * Delete a block, and associated data.
2081 * @param object $instance a row from the block_instances table
2082 * @param bool $nolongerused legacy parameter. Not used, but kept for backwards compatibility.
2083 * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.
2085 function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {
2088 // Allow plugins to use this block before we completely delete it.
2089 if ($pluginsfunction = get_plugins_with_function('pre_block_delete')) {
2090 foreach ($pluginsfunction as $plugintype => $plugins) {
2091 foreach ($plugins as $pluginfunction) {
2092 $pluginfunction($instance);
2097 if ($block = block_instance($instance->blockname
, $instance)) {
2098 $block->instance_delete();
2100 context_helper
::delete_instance(CONTEXT_BLOCK
, $instance->id
);
2102 if (!$skipblockstables) {
2103 $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id
));
2104 $DB->delete_records('block_instances', array('id' => $instance->id
));
2105 $DB->delete_records_list('user_preferences', 'name', array('block'.$instance->id
.'hidden','docked_block_instance_'.$instance->id
));
2110 * Delete multiple blocks at once.
2112 * @param array $instanceids A list of block instance ID.
2114 function blocks_delete_instances($instanceids) {
2118 $count = count($instanceids);
2119 $chunks = [$instanceids];
2120 if ($count > $limit) {
2121 $chunks = array_chunk($instanceids, $limit);
2124 // Perform deletion for each chunk.
2125 foreach ($chunks as $chunk) {
2126 $instances = $DB->get_recordset_list('block_instances', 'id', $chunk);
2127 foreach ($instances as $instance) {
2128 blocks_delete_instance($instance, false, true);
2130 $instances->close();
2132 $DB->delete_records_list('block_positions', 'blockinstanceid', $chunk);
2133 $DB->delete_records_list('block_instances', 'id', $chunk);
2135 $preferences = array();
2136 foreach ($chunk as $instanceid) {
2137 $preferences[] = 'block' . $instanceid . 'hidden';
2138 $preferences[] = 'docked_block_instance_' . $instanceid;
2140 $DB->delete_records_list('user_preferences', 'name', $preferences);
2145 * Delete all the blocks that belong to a particular context.
2147 * @param int $contextid the context id.
2149 function blocks_delete_all_for_context($contextid) {
2151 $instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));
2152 foreach ($instances as $instance) {
2153 blocks_delete_instance($instance, true);
2155 $instances->close();
2156 $DB->delete_records('block_instances', array('parentcontextid' => $contextid));
2157 $DB->delete_records('block_positions', array('contextid' => $contextid));
2161 * Set a block to be visible or hidden on a particular page.
2163 * @param object $instance a row from the block_instances, preferably LEFT JOINed with the
2164 * block_positions table as return by block_manager.
2165 * @param moodle_page $page the back to set the visibility with respect to.
2166 * @param integer $newvisibility 1 for visible, 0 for hidden.
2168 function blocks_set_visibility($instance, $page, $newvisibility) {
2170 if (!empty($instance->blockpositionid
)) {
2171 // Already have local information on this page.
2172 $DB->set_field('block_positions', 'visible', $newvisibility, array('id' => $instance->blockpositionid
));
2176 // Create a new block_positions record.
2178 $bp->blockinstanceid
= $instance->id
;
2179 $bp->contextid
= $page->context
->id
;
2180 $bp->pagetype
= $page->pagetype
;
2181 if ($page->subpage
) {
2182 $bp->subpage
= $page->subpage
;
2184 $bp->visible
= $newvisibility;
2185 $bp->region
= $instance->defaultregion
;
2186 $bp->weight
= $instance->defaultweight
;
2187 $DB->insert_record('block_positions', $bp);
2191 * Get the block record for a particular blockid - that is, a particular type os block.
2193 * @param $int blockid block type id. If null, an array of all block types is returned.
2194 * @param bool $notusedanymore No longer used.
2195 * @return array|object row from block table, or all rows.
2197 function blocks_get_record($blockid = NULL, $notusedanymore = false) {
2199 $blocks = $PAGE->blocks
->get_installed_blocks();
2200 if ($blockid === NULL) {
2202 } else if (isset($blocks[$blockid])) {
2203 return $blocks[$blockid];
2210 * Find a given block by its blockid within a provide array
2212 * @param int $blockid
2213 * @param array $blocksarray
2214 * @return bool|object Instance if found else false
2216 function blocks_find_block($blockid, $blocksarray) {
2217 if (empty($blocksarray)) {
2220 foreach($blocksarray as $blockgroup) {
2221 if (empty($blockgroup)) {
2224 foreach($blockgroup as $instance) {
2225 if($instance->blockid
== $blockid) {
2233 // Functions for programatically adding default blocks to pages ================
2236 * Parse a list of default blocks. See config-dist for a description of the format.
2238 * @param string $blocksstr Determines the starting point that the blocks are added in the region.
2239 * @return array the parsed list of default blocks
2241 function blocks_parse_default_blocks_list($blocksstr) {
2243 $bits = explode(':', $blocksstr);
2244 if (!empty($bits)) {
2245 $leftbits = trim(array_shift($bits));
2246 if ($leftbits != '') {
2247 $blocks[BLOCK_POS_LEFT
] = explode(',', $leftbits);
2250 if (!empty($bits)) {
2251 $rightbits = trim(array_shift($bits));
2252 if ($rightbits != '') {
2253 $blocks[BLOCK_POS_RIGHT
] = explode(',', $rightbits);
2260 * @return array the blocks that should be added to the site course by default.
2262 function blocks_get_default_site_course_blocks() {
2265 if (isset($CFG->defaultblocks_site
)) {
2266 return blocks_parse_default_blocks_list($CFG->defaultblocks_site
);
2269 BLOCK_POS_LEFT
=> array(),
2270 BLOCK_POS_RIGHT
=> array()
2276 * Add the default blocks to a course.
2278 * @param object $course a course object.
2280 function blocks_add_default_course_blocks($course) {
2283 if (isset($CFG->defaultblocks_override
)) {
2284 $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override
);
2286 } else if ($course->id
== SITEID
) {
2287 $blocknames = blocks_get_default_site_course_blocks();
2289 } else if (isset($CFG->{'defaultblocks_' . $course->format
})) {
2290 $blocknames = blocks_parse_default_blocks_list($CFG->{'defaultblocks_' . $course->format
});
2293 require_once($CFG->dirroot
. '/course/lib.php');
2294 $blocknames = course_get_format($course)->get_default_blocks();
2298 if ($course->id
== SITEID
) {
2299 $pagetypepattern = 'site-index';
2301 $pagetypepattern = 'course-view-*';
2303 $page = new moodle_page();
2304 $page->set_course($course);
2305 $page->blocks
->add_blocks($blocknames, $pagetypepattern);
2309 * Add the default system-context blocks. E.g. the admin tree.
2311 function blocks_add_default_system_blocks() {
2314 $page = new moodle_page();
2315 $page->set_context(context_system
::instance());
2316 $page->blocks
->add_blocks(array(BLOCK_POS_LEFT
=> block_manager
::get_undeletable_block_types()), '*', null, true);
2317 $page->blocks
->add_blocks(array(BLOCK_POS_LEFT
=> array('admin_bookmarks')), 'admin-*', null, null, 2);
2319 if ($defaultmypage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => 1))) {
2320 $subpagepattern = $defaultmypage->id
;
2322 $subpagepattern = null;
2325 $newblocks = array('private_files', 'online_users', 'badges', 'calendar_month', 'calendar_upcoming');
2326 $newcontent = array('lp', 'course_overview');
2327 $page->blocks
->add_blocks(array(BLOCK_POS_RIGHT
=> $newblocks, 'content' => $newcontent), 'my-index', $subpagepattern);