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 * @deprecated since Moodle 2.0. No longer used.
34 define('BLOCK_MOVE_LEFT', 0x01);
35 define('BLOCK_MOVE_RIGHT', 0x02);
36 define('BLOCK_MOVE_UP', 0x04);
37 define('BLOCK_MOVE_DOWN', 0x08);
38 define('BLOCK_CONFIGURE', 0x10);
42 * Default names for the block regions in the standard theme.
44 define('BLOCK_POS_LEFT', 'side-pre');
45 define('BLOCK_POS_RIGHT', 'side-post');
49 * @deprecated since Moodle 2.0. No longer used.
51 define('BLOCKS_PINNED_TRUE',0);
52 define('BLOCKS_PINNED_FALSE',1);
53 define('BLOCKS_PINNED_BOTH',2);
56 define('BUI_CONTEXTS_FRONTPAGE_ONLY', 0);
57 define('BUI_CONTEXTS_FRONTPAGE_SUBS', 1);
58 define('BUI_CONTEXTS_ENTIRE_SITE', 2);
60 define('BUI_CONTEXTS_CURRENT', 0);
61 define('BUI_CONTEXTS_CURRENT_SUBS', 1);
64 * Exception thrown when someone tried to do something with a block that does
65 * not exist on a page.
67 * @copyright 2009 Tim Hunt
68 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
71 class block_not_on_page_exception
extends moodle_exception
{
74 * @param int $instanceid the block instance id of the block that was looked for.
75 * @param object $page the current page.
77 public function __construct($instanceid, $page) {
79 $a->instanceid
= $instanceid;
80 $a->url
= $page->url
->out();
81 parent
::__construct('blockdoesnotexistonpage', '', $page->url
->out(), $a);
86 * This class keeps track of the block that should appear on a moodle_page.
88 * The page to work with as passed to the constructor.
90 * @copyright 2009 Tim Hunt
91 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
96 * The UI normally only shows block weights between -MAX_WEIGHT and MAX_WEIGHT,
97 * although other weights are valid.
99 const MAX_WEIGHT
= 10;
101 /// Field declarations =========================================================
104 * the moodle_page we are managing blocks for.
109 /** @var array region name => 1.*/
110 protected $regions = array();
112 /** @var string the region where new blocks are added.*/
113 protected $defaultregion = null;
115 /** @var array will be $DB->get_records('blocks') */
116 protected $allblocks = null;
119 * @var array blocks that this user can add to this page. Will be a subset
120 * of $allblocks, but with array keys block->name. Access this via the
121 * {@link get_addable_blocks()} method to ensure it is lazy-loaded.
123 protected $addableblocks = null;
126 * Will be an array region-name => array(db rows loaded in load_blocks);
129 protected $birecordsbyregion = null;
132 * array region-name => array(block objects); populated as necessary by
133 * the ensure_instances_exist method.
136 protected $blockinstances = array();
139 * array region-name => array(block_contents objects) what actually needs to
140 * be displayed in each region.
143 protected $visibleblockcontent = array();
146 * array region-name => array(block_contents objects) extra block-like things
147 * to be displayed in each region, before the real blocks.
150 protected $extracontent = array();
153 * Used by the block move id, to track whether a block is currently being moved.
155 * When you click on the move icon of a block, first the page needs to reload with
156 * extra UI for choosing a new position for a particular block. In that situation
157 * this field holds the id of the block being moved.
161 protected $movingblock = null;
164 * Show only fake blocks
166 protected $fakeblocksonly = false;
168 /// Constructor ================================================================
172 * @param object $page the moodle_page object object we are managing the blocks for,
173 * or a reasonable faxilimily. (See the comment at the top of this class
174 * and {@link http://en.wikipedia.org/wiki/Duck_typing})
176 public function __construct($page) {
180 /// Getter methods =============================================================
183 * Get an array of all region names on this page where a block may appear
185 * @return array the internal names of the regions on this page where block may appear.
187 public function get_regions() {
188 if (is_null($this->defaultregion
)) {
189 $this->page
->initialise_theme_and_output();
191 return array_keys($this->regions
);
195 * Get the region name of the region blocks are added to by default
197 * @return string the internal names of the region where new blocks are added
198 * by default, and where any blocks from an unrecognised region are shown.
199 * (Imagine that blocks were added with one theme selected, then you switched
200 * to a theme with different block positions.)
202 public function get_default_region() {
203 $this->page
->initialise_theme_and_output();
204 return $this->defaultregion
;
208 * The list of block types that may be added to this page.
210 * @return array block name => record from block table.
212 public function get_addable_blocks() {
213 $this->check_is_loaded();
215 if (!is_null($this->addableblocks
)) {
216 return $this->addableblocks
;
220 $this->addableblocks
= array();
222 $allblocks = blocks_get_record();
223 if (empty($allblocks)) {
224 return $this->addableblocks
;
227 $unaddableblocks = self
::get_undeletable_block_types();
228 $pageformat = $this->page
->pagetype
;
229 foreach($allblocks as $block) {
230 if (!$bi = block_instance($block->name
)) {
233 if ($block->visible
&& !in_array($block->name
, $unaddableblocks) &&
234 ($bi->instance_allow_multiple() ||
!$this->is_block_present($block->name
)) &&
235 blocks_name_allowed_in_format($block->name
, $pageformat) &&
236 $bi->user_can_addto($this->page
)) {
237 $this->addableblocks
[$block->name
] = $block;
241 return $this->addableblocks
;
245 * Given a block name, find out of any of them are currently present in the page
247 * @param string $blockname - the basic name of a block (eg "navigation")
248 * @return boolean - is there one of these blocks in the current page?
250 public function is_block_present($blockname) {
251 if (empty($this->blockinstances
)) {
255 foreach ($this->blockinstances
as $region) {
256 foreach ($region as $instance) {
257 if (empty($instance->instance
->blockname
)) {
260 if ($instance->instance
->blockname
== $blockname) {
269 * Find out if a block type is known by the system
271 * @param string $blockname the name of the type of block.
272 * @param boolean $includeinvisible if false (default) only check 'visible' blocks, that is, blocks enabled by the admin.
273 * @return boolean true if this block in installed.
275 public function is_known_block_type($blockname, $includeinvisible = false) {
276 $blocks = $this->get_installed_blocks();
277 foreach ($blocks as $block) {
278 if ($block->name
== $blockname && ($includeinvisible ||
$block->visible
)) {
286 * Find out if a region exists on a page
288 * @param string $region a region name
289 * @return boolean true if this region exists on this page.
291 public function is_known_region($region) {
292 return array_key_exists($region, $this->regions
);
296 * Get an array of all blocks within a given region
298 * @param string $region a block region that exists on this page.
299 * @return array of block instances.
301 public function get_blocks_for_region($region) {
302 $this->check_is_loaded();
303 $this->ensure_instances_exist($region);
304 return $this->blockinstances
[$region];
308 * Returns an array of block content objects that exist in a region
310 * @param string $region a block region that exists on this page.
311 * @return array of block block_contents objects for all the blocks in a region.
313 public function get_content_for_region($region, $output) {
314 $this->check_is_loaded();
315 $this->ensure_content_created($region, $output);
316 return $this->visibleblockcontent
[$region];
320 * Helper method used by get_content_for_region.
321 * @param string $region region name
322 * @param float $weight weight. May be fractional, since you may want to move a block
323 * between ones with weight 2 and 3, say ($weight would be 2.5).
324 * @return string URL for moving block $this->movingblock to this position.
326 protected function get_move_target_url($region, $weight) {
327 return new moodle_url($this->page
->url
, array('bui_moveid' => $this->movingblock
,
328 'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()));
332 * Determine whether a region contains anything. (Either any real blocks, or
333 * the add new block UI.)
335 * (You may wonder why the $output parameter is required. Unfortunately,
336 * because of the way that blocks work, the only reliable way to find out
337 * if a block will be visible is to get the content for output, and to
338 * get the content, you need a renderer. Fortunately, this is not a
339 * performance problem, because we cache the output that is generated, and
340 * in almost every case where we call region_has_content, we are about to
341 * output the blocks anyway, so we are not doing wasted effort.)
343 * @param string $region a block region that exists on this page.
344 * @param object $output a core_renderer. normally the global $OUTPUT.
345 * @return boolean Whether there is anything in this region.
347 public function region_has_content($region, $output) {
349 if (!$this->is_known_region($region)) {
352 $this->check_is_loaded();
353 $this->ensure_content_created($region, $output);
354 // if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks()) {
355 // Mark Nielsen's patch - part 1
356 if ($this->page
->user_is_editing() && $this->page
->user_can_edit_blocks() && $this->movingblock
) {
357 // If editing is on, we need all the block regions visible, for the
361 return !empty($this->visibleblockcontent
[$region]) ||
!empty($this->extracontent
[$region]);
365 * Get an array of all of the installed blocks.
367 * @return array contents of the block table.
369 public function get_installed_blocks() {
371 if (is_null($this->allblocks
)) {
372 $this->allblocks
= $DB->get_records('block');
374 return $this->allblocks
;
378 * @return array names of block types that cannot be added or deleted. E.g. array('navigation','settings').
380 public static function get_undeletable_block_types() {
383 if (!isset($CFG->undeletableblocktypes
) ||
(!is_array($CFG->undeletableblocktypes
) && !is_string($CFG->undeletableblocktypes
))) {
384 return array('navigation','settings');
385 } else if (is_string($CFG->undeletableblocktypes
)) {
386 return explode(',', $CFG->undeletableblocktypes
);
388 return $CFG->undeletableblocktypes
;
392 /// Setter methods =============================================================
395 * Add a region to a page
397 * @param string $region add a named region where blocks may appear on the
398 * current page. This is an internal name, like 'side-pre', not a string to
401 public function add_region($region) {
402 $this->check_not_yet_loaded();
403 $this->regions
[$region] = 1;
407 * Add an array of regions
410 * @param array $regions this utility method calls add_region for each array element.
412 public function add_regions($regions) {
413 foreach ($regions as $region) {
414 $this->add_region($region);
419 * Set the default region for new blocks on the page
421 * @param string $defaultregion the internal names of the region where new
422 * blocks should be added by default, and where any blocks from an
423 * unrecognised region are shown.
425 public function set_default_region($defaultregion) {
426 $this->check_not_yet_loaded();
427 if ($defaultregion) {
428 $this->check_region_is_known($defaultregion);
430 $this->defaultregion
= $defaultregion;
434 * Add something that looks like a block, but which isn't an actual block_instance,
437 * @param block_contents $bc the content of the block-like thing.
438 * @param string $region a block region that exists on this page.
440 public function add_fake_block($bc, $region) {
441 $this->page
->initialise_theme_and_output();
442 if (!$this->is_known_region($region)) {
443 $region = $this->get_default_region();
445 if (array_key_exists($region, $this->visibleblockcontent
)) {
446 throw new coding_exception('block_manager has already prepared the blocks in region ' .
447 $region . 'for output. It is too late to add a fake block.');
449 $this->extracontent
[$region][] = $bc;
453 * When the block_manager class was created, the {@link add_fake_block()}
454 * was called add_pretend_block, which is inconsisted with
455 * {@link show_only_fake_blocks()}. To fix this inconsistency, this method
456 * was renamed to add_fake_block. Please update your code.
457 * @param block_contents $bc the content of the block-like thing.
458 * @param string $region a block region that exists on this page.
460 public function add_pretend_block($bc, $region) {
461 debugging(DEBUG_DEVELOPER
, 'add_pretend_block has been renamed to add_fake_block. Please rename the method call in your code.');
462 $this->add_fake_block($bc, $region);
466 * Checks to see whether all of the blocks within the given region are docked
468 * @see region_uses_dock
469 * @param string $region
470 * @return bool True if all of the blocks within that region are docked
472 public function region_completely_docked($region, $output) {
474 // If theme doesn't allow docking or allowblockstodock is not set, then return.
475 if (!$this->page
->theme
->enable_dock ||
empty($CFG->allowblockstodock
)) {
479 // Do not dock the region when the user attemps to move a block.
480 if ($this->movingblock
) {
484 $this->check_is_loaded();
485 $this->ensure_content_created($region, $output);
486 foreach($this->visibleblockcontent
[$region] as $instance) {
487 if (!empty($instance->content
) && !get_user_preferences('docked_block_instance_'.$instance->blockinstanceid
, 0)) {
495 * Checks to see whether any of the blocks within the given regions are docked
497 * @see region_completely_docked
498 * @param array|string $regions array of regions (or single region)
499 * @return bool True if any of the blocks within that region are docked
501 public function region_uses_dock($regions, $output) {
502 if (!$this->page
->theme
->enable_dock
) {
505 $this->check_is_loaded();
506 foreach((array)$regions as $region) {
507 $this->ensure_content_created($region, $output);
508 foreach($this->visibleblockcontent
[$region] as $instance) {
509 if(!empty($instance->content
) && get_user_preferences('docked_block_instance_'.$instance->blockinstanceid
, 0)) {
517 /// Actions ====================================================================
520 * This method actually loads the blocks for our page from the database.
522 * @param boolean|null $includeinvisible
523 * null (default) - load hidden blocks if $this->page->user_is_editing();
524 * true - load hidden blocks.
525 * false - don't load hidden blocks.
527 public function load_blocks($includeinvisible = null) {
530 if (!is_null($this->birecordsbyregion
)) {
535 if ($CFG->version
< 2009050619) {
536 // Upgrade/install not complete. Don't try too show any blocks.
537 $this->birecordsbyregion
= array();
541 // Ensure we have been initialised.
542 if (is_null($this->defaultregion
)) {
543 $this->page
->initialise_theme_and_output();
544 // If there are still no block regions, then there are no blocks on this page.
545 if (empty($this->regions
)) {
546 $this->birecordsbyregion
= array();
551 // Check if we need to load normal blocks
552 if ($this->fakeblocksonly
) {
553 $this->birecordsbyregion
= $this->prepare_per_region_arrays();
557 if (is_null($includeinvisible)) {
558 $includeinvisible = $this->page
->user_is_editing();
560 if ($includeinvisible) {
563 $visiblecheck = 'AND (bp.visible = 1 OR bp.visible IS NULL)';
566 $context = $this->page
->context
;
567 $contexttest = 'bi.parentcontextid = :contextid2';
568 $parentcontextparams = array();
569 $parentcontextids = get_parent_contexts($context);
570 if ($parentcontextids) {
571 list($parentcontexttest, $parentcontextparams) =
572 $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED
, 'parentcontext');
573 $contexttest = "($contexttest OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontexttest))";
576 $pagetypepatterns = matching_page_type_patterns($this->page
->pagetype
);
577 list($pagetypepatterntest, $pagetypepatternparams) =
578 $DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED
, 'pagetypepatterntest');
580 list($ccselect, $ccjoin) = context_instance_preload_sql('bi.id', CONTEXT_BLOCK
, 'ctx');
583 'subpage1' => $this->page
->subpage
,
584 'subpage2' => $this->page
->subpage
,
585 'contextid1' => $context->id
,
586 'contextid2' => $context->id
,
587 'pagetype' => $this->page
->pagetype
,
589 if ($this->page
->subpage
=== '') {
590 $params['subpage1'] = $DB->sql_empty();
591 $params['subpage2'] = $DB->sql_empty();
595 bp.id AS blockpositionid,
598 bi.showinsubcontexts,
603 COALESCE(bp.visible, 1) AS visible,
604 COALESCE(bp.region, bi.defaultregion) AS region,
605 COALESCE(bp.weight, bi.defaultweight) AS weight,
609 FROM {block_instances} bi
610 JOIN {block} b ON bi.blockname = b.name
611 LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id
612 AND bp.contextid = :contextid1
613 AND bp.pagetype = :pagetype
614 AND bp.subpage = :subpage1
619 AND bi.pagetypepattern $pagetypepatterntest
620 AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)
625 COALESCE(bp.region, bi.defaultregion),
626 COALESCE(bp.weight, bi.defaultweight),
628 $blockinstances = $DB->get_recordset_sql($sql, $params +
$parentcontextparams +
$pagetypepatternparams);
630 $this->birecordsbyregion
= $this->prepare_per_region_arrays();
632 foreach ($blockinstances as $bi) {
633 context_instance_preload($bi);
634 if ($this->is_known_region($bi->region
)) {
635 $this->birecordsbyregion
[$bi->region
][] = $bi;
641 // Pages don't necessarily have a defaultregion. The one time this can
642 // happen is when there are no theme block regions, but the script itself
643 // has a block region in the main content area.
644 if (!empty($this->defaultregion
)) {
645 $this->birecordsbyregion
[$this->defaultregion
] =
646 array_merge($this->birecordsbyregion
[$this->defaultregion
], $unknown);
651 * Add a block to the current page, or related pages. The block is added to
652 * context $this->page->contextid. If $pagetypepattern $subpagepattern
654 * @param string $blockname The type of block to add.
655 * @param string $region the block region on this page to add the block to.
656 * @param integer $weight determines the order where this block appears in the region.
657 * @param boolean $showinsubcontexts whether this block appears in subcontexts, or just the current context.
658 * @param string|null $pagetypepattern which page types this block should appear on. Defaults to just the current page type.
659 * @param string|null $subpagepattern which subpage this block should appear on. NULL = any (the default), otherwise only the specified subpage.
661 public function add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern = NULL, $subpagepattern = NULL) {
663 // Allow invisible blocks because this is used when adding default page blocks, which
664 // might include invisible ones if the user makes some default blocks invisible
665 $this->check_known_block_type($blockname, true);
666 $this->check_region_is_known($region);
668 if (empty($pagetypepattern)) {
669 $pagetypepattern = $this->page
->pagetype
;
672 $blockinstance = new stdClass
;
673 $blockinstance->blockname
= $blockname;
674 $blockinstance->parentcontextid
= $this->page
->context
->id
;
675 $blockinstance->showinsubcontexts
= !empty($showinsubcontexts);
676 $blockinstance->pagetypepattern
= $pagetypepattern;
677 $blockinstance->subpagepattern
= $subpagepattern;
678 $blockinstance->defaultregion
= $region;
679 $blockinstance->defaultweight
= $weight;
680 $blockinstance->configdata
= '';
681 $blockinstance->id
= $DB->insert_record('block_instances', $blockinstance);
683 // Ensure the block context is created.
684 context_block
::instance($blockinstance->id
);
686 // If the new instance was created, allow it to do additional setup
687 if ($block = block_instance($blockname, $blockinstance)) {
688 $block->instance_create();
692 public function add_block_at_end_of_default_region($blockname) {
693 $defaulregion = $this->get_default_region();
695 $lastcurrentblock = end($this->birecordsbyregion
[$defaulregion]);
696 if ($lastcurrentblock) {
697 $weight = $lastcurrentblock->weight +
1;
702 if ($this->page
->subpage
) {
703 $subpage = $this->page
->subpage
;
708 // Special case. Course view page type include the course format, but we
709 // want to add the block non-format-specifically.
710 $pagetypepattern = $this->page
->pagetype
;
711 if (strpos($pagetypepattern, 'course-view') === 0) {
712 $pagetypepattern = 'course-view-*';
715 // We should end using this for ALL the blocks, making always the 1st option
716 // the default one to be used. Until then, this is one hack to avoid the
717 // 'pagetypewarning' message on blocks initial edition (MDL-27829) caused by
718 // non-existing $pagetypepattern set. This way at least we guarantee one "valid"
719 // (the FIRST $pagetypepattern will be set)
721 // We are applying it to all blocks created in mod pages for now and only if the
722 // default pagetype is not one of the available options
723 if (preg_match('/^mod-.*-/', $pagetypepattern)) {
724 $pagetypelist = generate_page_type_patterns($this->page
->pagetype
, null, $this->page
->context
);
725 // Only go for the first if the pagetype is not a valid option
726 if (is_array($pagetypelist) && !array_key_exists($pagetypepattern, $pagetypelist)) {
727 $pagetypepattern = key($pagetypelist);
730 // Surely other pages like course-report will need this too, they just are not important
731 // enough now. This will be decided in the coming days. (MDL-27829, MDL-28150)
733 $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);
737 * Convenience method, calls add_block repeatedly for all the blocks in $blocks.
739 * @param array $blocks array with array keys the region names, and values an array of block names.
740 * @param string $pagetypepattern optional. Passed to @see add_block()
741 * @param string $subpagepattern optional. Passed to @see add_block()
743 public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {
744 $this->add_regions(array_keys($blocks));
745 foreach ($blocks as $region => $regionblocks) {
747 foreach ($regionblocks as $blockname) {
748 $this->add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern, $subpagepattern);
755 * Move a block to a new position on this page.
757 * If this block cannot appear on any other pages, then we change defaultposition/weight
758 * in the block_instances table. Otherwise we just set the position on this page.
760 * @param $blockinstanceid the block instance id.
761 * @param $newregion the new region name.
762 * @param $newweight the new weight.
764 public function reposition_block($blockinstanceid, $newregion, $newweight) {
767 $this->check_region_is_known($newregion);
768 $inst = $this->find_instance($blockinstanceid);
770 $bi = $inst->instance
;
771 if ($bi->weight
== $bi->defaultweight
&& $bi->region
== $bi->defaultregion
&&
772 !$bi->showinsubcontexts
&& strpos($bi->pagetypepattern
, '*') === false &&
773 (!$this->page
->subpage ||
$bi->subpagepattern
)) {
775 // Set default position
776 $newbi = new stdClass
;
777 $newbi->id
= $bi->id
;
778 $newbi->defaultregion
= $newregion;
779 $newbi->defaultweight
= $newweight;
780 $DB->update_record('block_instances', $newbi);
782 if ($bi->blockpositionid
) {
784 $bp->id
= $bi->blockpositionid
;
785 $bp->region
= $newregion;
786 $bp->weight
= $newweight;
787 $DB->update_record('block_positions', $bp);
791 // Just set position on this page.
793 $bp->region
= $newregion;
794 $bp->weight
= $newweight;
796 if ($bi->blockpositionid
) {
797 $bp->id
= $bi->blockpositionid
;
798 $DB->update_record('block_positions', $bp);
801 $bp->blockinstanceid
= $bi->id
;
802 $bp->contextid
= $this->page
->context
->id
;
803 $bp->pagetype
= $this->page
->pagetype
;
804 if ($this->page
->subpage
) {
805 $bp->subpage
= $this->page
->subpage
;
809 $bp->visible
= $bi->visible
;
810 $DB->insert_record('block_positions', $bp);
816 * Find a given block by its instance id
818 * @param integer $instanceid
821 public function find_instance($instanceid) {
822 foreach ($this->regions
as $region => $notused) {
823 $this->ensure_instances_exist($region);
824 foreach($this->blockinstances
[$region] as $instance) {
825 if ($instance->instance
->id
== $instanceid) {
830 throw new block_not_on_page_exception($instanceid, $this->page
);
833 /// Inner workings =============================================================
836 * Check whether the page blocks have been loaded yet
838 * @return void Throws coding exception if already loaded
840 protected function check_not_yet_loaded() {
841 if (!is_null($this->birecordsbyregion
)) {
842 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.');
847 * Check whether the page blocks have been loaded yet
849 * Nearly identical to the above function {@link check_not_yet_loaded()} except different message
851 * @return void Throws coding exception if already loaded
853 protected function check_is_loaded() {
854 if (is_null($this->birecordsbyregion
)) {
855 throw new coding_exception('block_manager has not yet loaded the blocks, to it is too soon to request the information you asked for.');
860 * Check if a block type is known and usable
862 * @param string $blockname The block type name to search for
863 * @param bool $includeinvisible Include disabled block types in the initial pass
864 * @return void Coding Exception thrown if unknown or not enabled
866 protected function check_known_block_type($blockname, $includeinvisible = false) {
867 if (!$this->is_known_block_type($blockname, $includeinvisible)) {
868 if ($this->is_known_block_type($blockname, true)) {
869 throw new coding_exception('Unknown block type ' . $blockname);
871 throw new coding_exception('Block type ' . $blockname . ' has been disabled by the administrator.');
877 * Check if a region is known by its name
879 * @param string $region
880 * @return void Coding Exception thrown if the region is not known
882 protected function check_region_is_known($region) {
883 if (!$this->is_known_region($region)) {
884 throw new coding_exception('Trying to reference an unknown block region ' . $region);
889 * Returns an array of region names as keys and nested arrays for values
891 * @return array an array where the array keys are the region names, and the array
892 * values are empty arrays.
894 protected function prepare_per_region_arrays() {
896 foreach ($this->regions
as $region => $notused) {
897 $result[$region] = array();
903 * Create a set of new block instance from a record array
905 * @param array $birecords An array of block instance records
906 * @return array An array of instantiated block_instance objects
908 protected function create_block_instances($birecords) {
910 foreach ($birecords as $record) {
911 if ($blockobject = block_instance($record->blockname
, $record, $this->page
)) {
912 $results[] = $blockobject;
919 * Create all the block instances for all the blocks that were loaded by
920 * load_blocks. This is used, for example, to ensure that all blocks get a
921 * chance to initialise themselves via the {@link block_base::specialize()}
922 * method, before any output is done.
924 public function create_all_block_instances() {
925 foreach ($this->get_regions() as $region) {
926 $this->ensure_instances_exist($region);
931 * Return an array of content objects from a set of block instances
933 * @param array $instances An array of block instances
934 * @param renderer_base The renderer to use.
935 * @param string $region the region name.
936 * @return array An array of block_content (and possibly block_move_target) objects.
938 protected function create_block_contents($instances, $output, $region) {
943 if ($this->movingblock
) {
944 $first = reset($instances);
946 $lastweight = $first->instance
->weight
- 2;
949 $strmoveblockhere = get_string('moveblockhere', 'block');
952 foreach ($instances as $instance) {
953 $content = $instance->get_content_for_output($output);
954 if (empty($content)) {
958 if ($this->movingblock
&& $lastweight != $instance->instance
->weight
&&
959 $content->blockinstanceid
!= $this->movingblock
&& $lastblock != $this->movingblock
) {
960 $results[] = new block_move_target($strmoveblockhere, $this->get_move_target_url($region, ($lastweight +
$instance->instance
->weight
)/2));
963 if ($content->blockinstanceid
== $this->movingblock
) {
964 $content->add_class('beingmoved');
965 $content->annotation
.= get_string('movingthisblockcancel', 'block',
966 html_writer
::link($this->page
->url
, get_string('cancel')));
969 $results[] = $content;
970 $lastweight = $instance->instance
->weight
;
971 $lastblock = $instance->instance
->id
;
974 if ($this->movingblock
&& $lastblock != $this->movingblock
) {
975 $results[] = new block_move_target($strmoveblockhere, $this->get_move_target_url($region, $lastweight +
1));
981 * Ensure block instances exist for a given region
983 * @param string $region Check for bi's with the instance with this name
985 protected function ensure_instances_exist($region) {
986 $this->check_region_is_known($region);
987 if (!array_key_exists($region, $this->blockinstances
)) {
988 $this->blockinstances
[$region] =
989 $this->create_block_instances($this->birecordsbyregion
[$region]);
994 * Ensure that there is some content within the given region
996 * @param string $region The name of the region to check
998 protected function ensure_content_created($region, $output) {
999 $this->ensure_instances_exist($region);
1000 if (!array_key_exists($region, $this->visibleblockcontent
)) {
1001 $contents = array();
1002 if (array_key_exists($region, $this->extracontent
)) {
1003 $contents = $this->extracontent
[$region];
1005 $contents = array_merge($contents, $this->create_block_contents($this->blockinstances
[$region], $output, $region));
1006 if ($region == $this->defaultregion
) {
1007 $addblockui = block_add_block_ui($this->page
, $output);
1009 $contents[] = $addblockui;
1012 $this->visibleblockcontent
[$region] = $contents;
1016 /// Process actions from the URL ===============================================
1019 * Get the appropriate list of editing icons for a block. This is used
1020 * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}.
1022 * @param $output The core_renderer to use when generating the output. (Need to get icon paths.)
1023 * @return an array in the format for {@link block_contents::$controls}
1025 public function edit_controls($block) {
1028 $controls = array();
1029 $actionurl = $this->page
->url
->out(false, array('sesskey'=> sesskey()));
1031 if ($this->page
->user_can_edit_blocks()) {
1033 $controls[] = array('url' => $actionurl . '&bui_moveid=' . $block->instance
->id
,
1034 'icon' => 't/move', 'caption' => get_string('moveblock', 'block', $block->title
),
1035 'class' => 'editing_move');
1038 if ($this->page
->user_can_edit_blocks() ||
$block->user_can_edit()) {
1039 // Edit config icon - always show - needed for positioning UI.
1040 $controls[] = array('url' => $actionurl . '&bui_editid=' . $block->instance
->id
,
1041 'icon' => 't/edit', 'caption' => get_string('configureblock', 'block', $block->title
),
1042 'class' => 'editing_edit');
1045 if ($this->user_can_delete_block($block)) {
1047 $controls[] = array('url' => $actionurl . '&bui_deleteid=' . $block->instance
->id
,
1048 'icon' => 't/delete', 'caption' => get_string('deleteblock', 'block', $block->title
),
1049 'class' => 'editing_delete');
1052 if ($this->page
->user_can_edit_blocks() && $block->instance_can_be_hidden()) {
1054 if ($block->instance
->visible
) {
1055 $controls[] = array('url' => $actionurl . '&bui_hideid=' . $block->instance
->id
,
1056 'icon' => 't/hide', 'caption' => get_string('hideblock', 'block', $block->title
),
1057 'class' => 'editing_hide');
1059 $controls[] = array('url' => $actionurl . '&bui_showid=' . $block->instance
->id
,
1060 'icon' => 't/show', 'caption' => get_string('showblock', 'block', $block->title
),
1061 'class' => 'editing_show');
1065 // Assign roles icon.
1066 if (has_capability('moodle/role:assign', $block->context
)) {
1067 //TODO: please note it is sloppy to pass urls through page parameters!!
1068 // it is shortened because some web servers (e.g. IIS by default) give
1069 // a 'security' error if you try to pass a full URL as a GET parameter in another URL.
1070 $return = $this->page
->url
->out(false);
1071 $return = str_replace($CFG->wwwroot
. '/', '', $return);
1073 $controls[] = array('url' => $CFG->wwwroot
. '/' . $CFG->admin
.
1074 '/roles/assign.php?contextid=' . $block->context
->id
. '&returnurl=' . urlencode($return),
1075 'icon' => 't/assignroles', 'caption' => get_string('assignrolesinblock', 'block', $block->title
),
1076 'class' => 'editing_roles');
1083 * @param block_base $block a block that appears on this page.
1084 * @return boolean boolean whether the currently logged in user is allowed to delete this block.
1086 protected function user_can_delete_block($block) {
1087 return $this->page
->user_can_edit_blocks() && $block->user_can_edit() &&
1088 $block->user_can_addto($this->page
) &&
1089 !in_array($block->instance
->blockname
, self
::get_undeletable_block_types());
1093 * Process any block actions that were specified in the URL.
1095 * @return boolean true if anything was done. False if not.
1097 public function process_url_actions() {
1098 if (!$this->page
->user_is_editing()) {
1101 return $this->process_url_add() ||
$this->process_url_delete() ||
1102 $this->process_url_show_hide() ||
$this->process_url_edit() ||
1103 $this->process_url_move();
1107 * Handle adding a block.
1108 * @return boolean true if anything was done. False if not.
1110 public function process_url_add() {
1111 $blocktype = optional_param('bui_addblock', null, PARAM_PLUGIN
);
1118 if (!$this->page
->user_can_edit_blocks()) {
1119 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('addblock'));
1122 if (!array_key_exists($blocktype, $this->get_addable_blocks())) {
1123 throw new moodle_exception('cannotaddthisblocktype', '', $this->page
->url
->out(), $blocktype);
1126 $this->add_block_at_end_of_default_region($blocktype);
1128 // If the page URL was a guess, it will contain the bui_... param, so we must make sure it is not there.
1129 $this->page
->ensure_param_not_in_url('bui_addblock');
1135 * Handle deleting a block.
1136 * @return boolean true if anything was done. False if not.
1138 public function process_url_delete() {
1139 global $CFG, $PAGE, $OUTPUT;
1141 $blockid = optional_param('bui_deleteid', null, PARAM_INT
);
1142 $confirmdelete = optional_param('bui_confirm', null, PARAM_INT
);
1149 $block = $this->page
->blocks
->find_instance($blockid);
1150 if (!$this->user_can_delete_block($block)) {
1151 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('deleteablock'));
1154 if (!$confirmdelete) {
1155 $deletepage = new moodle_page();
1156 $deletepage->set_pagelayout('admin');
1157 $deletepage->set_course($this->page
->course
);
1158 $deletepage->set_context($this->page
->context
);
1159 if ($this->page
->cm
) {
1160 $deletepage->set_cm($this->page
->cm
);
1163 $deleteurlbase = str_replace($CFG->wwwroot
. '/', '/', $this->page
->url
->out_omit_querystring());
1164 $deleteurlparams = $this->page
->url
->params();
1165 $deletepage->set_url($deleteurlbase, $deleteurlparams);
1166 $deletepage->set_block_actions_done();
1167 // At this point we are either going to redirect, or display the form, so
1168 // overwrite global $PAGE ready for this. (Formslib refers to it.)
1169 $PAGE = $deletepage;
1170 //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that too
1171 $output = $deletepage->get_renderer('core');
1175 $blocktitle = $block->get_title();
1176 $strdeletecheck = get_string('deletecheck', 'block', $blocktitle);
1177 $message = get_string('deleteblockcheck', 'block', $blocktitle);
1179 $PAGE->navbar
->add($strdeletecheck);
1180 $PAGE->set_title($blocktitle . ': ' . $strdeletecheck);
1181 $PAGE->set_heading($site->fullname
);
1182 echo $OUTPUT->header();
1183 $confirmurl = new moodle_url($deletepage->url
, array('sesskey' => sesskey(), 'bui_deleteid' => $block->instance
->id
, 'bui_confirm' => 1));
1184 $cancelurl = new moodle_url($deletepage->url
);
1185 $yesbutton = new single_button($confirmurl, get_string('yes'));
1186 $nobutton = new single_button($cancelurl, get_string('no'));
1187 echo $OUTPUT->confirm($message, $yesbutton, $nobutton);
1188 echo $OUTPUT->footer();
1189 // Make sure that nothing else happens after we have displayed this form.
1192 blocks_delete_instance($block->instance
);
1193 // bui_deleteid and bui_confirm should not be in the PAGE url.
1194 $this->page
->ensure_param_not_in_url('bui_deleteid');
1195 $this->page
->ensure_param_not_in_url('bui_confirm');
1201 * Handle showing or hiding a block.
1202 * @return boolean true if anything was done. False if not.
1204 public function process_url_show_hide() {
1205 if ($blockid = optional_param('bui_hideid', null, PARAM_INT
)) {
1207 } else if ($blockid = optional_param('bui_showid', null, PARAM_INT
)) {
1215 $block = $this->page
->blocks
->find_instance($blockid);
1217 if (!$this->page
->user_can_edit_blocks()) {
1218 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('hideshowblocks'));
1219 } else if (!$block->instance_can_be_hidden()) {
1223 blocks_set_visibility($block->instance
, $this->page
, $newvisibility);
1225 // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1226 $this->page
->ensure_param_not_in_url('bui_hideid');
1227 $this->page
->ensure_param_not_in_url('bui_showid');
1233 * Handle showing/processing the submission from the block editing form.
1234 * @return boolean true if the form was submitted and the new config saved. Does not
1235 * return if the editing form was displayed. False otherwise.
1237 public function process_url_edit() {
1238 global $CFG, $DB, $PAGE, $OUTPUT;
1240 $blockid = optional_param('bui_editid', null, PARAM_INT
);
1246 require_once($CFG->dirroot
. '/blocks/edit_form.php');
1248 $block = $this->find_instance($blockid);
1250 if (!$block->user_can_edit() && !$this->page
->user_can_edit_blocks()) {
1251 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('editblock'));
1254 $editpage = new moodle_page();
1255 $editpage->set_pagelayout('admin');
1256 $editpage->set_course($this->page
->course
);
1257 //$editpage->set_context($block->context);
1258 $editpage->set_context($this->page
->context
);
1259 if ($this->page
->cm
) {
1260 $editpage->set_cm($this->page
->cm
);
1262 $editurlbase = str_replace($CFG->wwwroot
. '/', '/', $this->page
->url
->out_omit_querystring());
1263 $editurlparams = $this->page
->url
->params();
1264 $editurlparams['bui_editid'] = $blockid;
1265 $editpage->set_url($editurlbase, $editurlparams);
1266 $editpage->set_block_actions_done();
1267 // At this point we are either going to redirect, or display the form, so
1268 // overwrite global $PAGE ready for this. (Formslib refers to it.)
1270 //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that to
1271 $output = $editpage->get_renderer('core');
1274 $formfile = $CFG->dirroot
. '/blocks/' . $block->name() . '/edit_form.php';
1275 if (is_readable($formfile)) {
1276 require_once($formfile);
1277 $classname = 'block_' . $block->name() . '_edit_form';
1278 if (!class_exists($classname)) {
1279 $classname = 'block_edit_form';
1282 $classname = 'block_edit_form';
1285 $mform = new $classname($editpage->url
, $block, $this->page
);
1286 $mform->set_data($block->instance
);
1288 if ($mform->is_cancelled()) {
1289 redirect($this->page
->url
);
1291 } else if ($data = $mform->get_data()) {
1293 $bi->id
= $block->instance
->id
;
1295 // This may get overwritten by the special case handling below.
1296 $bi->pagetypepattern
= $data->bui_pagetypepattern
;
1297 $bi->showinsubcontexts
= (bool) $data->bui_contexts
;
1298 if (empty($data->bui_subpagepattern
) ||
$data->bui_subpagepattern
== '%@NULL@%') {
1299 $bi->subpagepattern
= null;
1301 $bi->subpagepattern
= $data->bui_subpagepattern
;
1304 $systemcontext = context_system
::instance();
1305 $frontpagecontext = context_course
::instance(SITEID
);
1306 $parentcontext = context
::instance_by_id($data->bui_parentcontextid
);
1308 // Updating stickiness and contexts. See MDL-21375 for details.
1309 if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination
1311 // Explicitly set the default context
1312 $bi->parentcontextid
= $parentcontext->id
;
1314 if ($data->bui_editingatfrontpage
) { // The block is being edited on the front page
1316 // The interface here is a special case because the pagetype pattern is
1317 // totally derived from the context menu. Here are the excpetions. MDL-30340
1319 switch ($data->bui_contexts
) {
1320 case BUI_CONTEXTS_ENTIRE_SITE
:
1321 // The user wants to show the block across the entire site
1322 $bi->parentcontextid
= $systemcontext->id
;
1323 $bi->showinsubcontexts
= true;
1324 $bi->pagetypepattern
= '*';
1326 case BUI_CONTEXTS_FRONTPAGE_SUBS
:
1327 // The user wants the block shown on the front page and all subcontexts
1328 $bi->parentcontextid
= $frontpagecontext->id
;
1329 $bi->showinsubcontexts
= true;
1330 $bi->pagetypepattern
= '*';
1332 case BUI_CONTEXTS_FRONTPAGE_ONLY
:
1333 // The user want to show the front page on the frontpage only
1334 $bi->parentcontextid
= $frontpagecontext->id
;
1335 $bi->showinsubcontexts
= false;
1336 $bi->pagetypepattern
= 'site-index';
1337 // This is the only relevant page type anyway but we'll set it explicitly just
1338 // in case the front page grows site-index-* subpages of its own later
1344 $bits = explode('-', $bi->pagetypepattern
);
1345 // hacks for some contexts
1346 if (($parentcontext->contextlevel
== CONTEXT_COURSE
) && ($parentcontext->instanceid
!= SITEID
)) {
1347 // For course context
1348 // is page type pattern is mod-*, change showinsubcontext to 1
1349 if ($bits[0] == 'mod' ||
$bi->pagetypepattern
== '*') {
1350 $bi->showinsubcontexts
= 1;
1352 $bi->showinsubcontexts
= 0;
1354 } else if ($parentcontext->contextlevel
== CONTEXT_USER
) {
1356 // subpagepattern should be null
1357 if ($bits[0] == 'user' or $bits[0] == 'my') {
1358 // we don't need subpagepattern in usercontext
1359 $bi->subpagepattern
= null;
1363 $bi->defaultregion
= $data->bui_defaultregion
;
1364 $bi->defaultweight
= $data->bui_defaultweight
;
1365 $DB->update_record('block_instances', $bi);
1367 if (!empty($block->config
)) {
1368 $config = clone($block->config
);
1370 $config = new stdClass
;
1372 foreach ($data as $configfield => $value) {
1373 if (strpos($configfield, 'config_') !== 0) {
1376 $field = substr($configfield, 7);
1377 $config->$field = $value;
1379 $block->instance_config_save($config);
1382 $bp->visible
= $data->bui_visible
;
1383 $bp->region
= $data->bui_region
;
1384 $bp->weight
= $data->bui_weight
;
1385 $needbprecord = !$data->bui_visible ||
$data->bui_region
!= $data->bui_defaultregion ||
1386 $data->bui_weight
!= $data->bui_defaultweight
;
1388 if ($block->instance
->blockpositionid
&& !$needbprecord) {
1389 $DB->delete_records('block_positions', array('id' => $block->instance
->blockpositionid
));
1391 } else if ($block->instance
->blockpositionid
&& $needbprecord) {
1392 $bp->id
= $block->instance
->blockpositionid
;
1393 $DB->update_record('block_positions', $bp);
1395 } else if ($needbprecord) {
1396 $bp->blockinstanceid
= $block->instance
->id
;
1397 $bp->contextid
= $this->page
->context
->id
;
1398 $bp->pagetype
= $this->page
->pagetype
;
1399 if ($this->page
->subpage
) {
1400 $bp->subpage
= $this->page
->subpage
;
1404 $DB->insert_record('block_positions', $bp);
1407 redirect($this->page
->url
);
1410 $strheading = get_string('blockconfiga', 'moodle', $block->get_title());
1411 $editpage->set_title($strheading);
1412 $editpage->set_heading($strheading);
1413 $bits = explode('-', $this->page
->pagetype
);
1414 if ($bits[0] == 'tag' && !empty($this->page
->subpage
)) {
1415 // better navbar for tag pages
1416 $editpage->navbar
->add(get_string('tags'), new moodle_url('/tag/'));
1417 $tag = tag_get('id', $this->page
->subpage
, '*');
1418 // tag search page doesn't have subpageid
1420 $editpage->navbar
->add($tag->name
, new moodle_url('/tag/index.php', array('id'=>$tag->id
)));
1423 $editpage->navbar
->add($block->get_title());
1424 $editpage->navbar
->add(get_string('configuration'));
1425 echo $output->header();
1426 echo $output->heading($strheading, 2);
1428 echo $output->footer();
1434 * Handle showing/processing the submission from the block editing form.
1435 * @return boolean true if the form was submitted and the new config saved. Does not
1436 * return if the editing form was displayed. False otherwise.
1438 public function process_url_move() {
1439 global $CFG, $DB, $PAGE;
1441 $blockid = optional_param('bui_moveid', null, PARAM_INT
);
1448 $block = $this->find_instance($blockid);
1450 if (!$this->page
->user_can_edit_blocks()) {
1451 throw new moodle_exception('nopermissions', '', $this->page
->url
->out(), get_string('editblock'));
1454 $newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT
);
1455 $newweight = optional_param('bui_newweight', null, PARAM_FLOAT
);
1456 if (!$newregion ||
is_null($newweight)) {
1457 // Don't have a valid target position yet, must be just starting the move.
1458 $this->movingblock
= $blockid;
1459 $this->page
->ensure_param_not_in_url('bui_moveid');
1463 if (!$this->is_known_region($newregion)) {
1464 throw new moodle_exception('unknownblockregion', '', $this->page
->url
, $newregion);
1467 // Move this block. This may involve moving other nearby blocks.
1468 $blocks = $this->birecordsbyregion
[$newregion];
1470 $maxweight = self
::MAX_WEIGHT
;
1471 $minweight = -self
::MAX_WEIGHT
;
1473 // Initialise the used weights and spareweights array with the default values
1474 $spareweights = array();
1475 $usedweights = array();
1476 for ($i = $minweight; $i <= $maxweight; $i++
) {
1477 $spareweights[$i] = $i;
1478 $usedweights[$i] = array();
1481 // Check each block and sort out where we have used weights
1482 foreach ($blocks as $bi) {
1483 if ($bi->weight
> $maxweight) {
1484 // If this statement is true then the blocks weight is more than the
1485 // current maximum. To ensure that we can get the best block position
1486 // we will initialise elements within the usedweights and spareweights
1487 // arrays between the blocks weight (which will then be the new max) and
1489 $parseweight = $bi->weight
;
1490 while (!array_key_exists($parseweight, $usedweights)) {
1491 $usedweights[$parseweight] = array();
1492 $spareweights[$parseweight] = $parseweight;
1495 $maxweight = $bi->weight
;
1496 } else if ($bi->weight
< $minweight) {
1497 // As above except this time the blocks weight is LESS than the
1498 // the current minimum, so we will initialise the array from the
1499 // blocks weight (new minimum) to the current minimum
1500 $parseweight = $bi->weight
;
1501 while (!array_key_exists($parseweight, $usedweights)) {
1502 $usedweights[$parseweight] = array();
1503 $spareweights[$parseweight] = $parseweight;
1506 $minweight = $bi->weight
;
1508 if ($bi->id
!= $block->instance
->id
) {
1509 unset($spareweights[$bi->weight
]);
1510 $usedweights[$bi->weight
][] = $bi->id
;
1514 // First we find the nearest gap in the list of weights.
1515 $bestdistance = max(abs($newweight - self
::MAX_WEIGHT
), abs($newweight + self
::MAX_WEIGHT
)) +
1;
1517 foreach ($spareweights as $spareweight) {
1518 if (abs($newweight - $spareweight) < $bestdistance) {
1519 $bestdistance = abs($newweight - $spareweight);
1520 $bestgap = $spareweight;
1524 // If there is no gap, we have to go outside -self::MAX_WEIGHT .. self::MAX_WEIGHT.
1525 if (is_null($bestgap)) {
1526 $bestgap = self
::MAX_WEIGHT +
1;
1527 while (!empty($usedweights[$bestgap])) {
1532 // Now we know the gap we are aiming for, so move all the blocks along.
1533 if ($bestgap < $newweight) {
1534 $newweight = floor($newweight);
1535 for ($weight = $bestgap +
1; $weight <= $newweight; $weight++
) {
1536 foreach ($usedweights[$weight] as $biid) {
1537 $this->reposition_block($biid, $newregion, $weight - 1);
1540 $this->reposition_block($block->instance
->id
, $newregion, $newweight);
1542 $newweight = ceil($newweight);
1543 for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {
1544 if (array_key_exists($weight, $usedweights)) {
1545 foreach ($usedweights[$weight] as $biid) {
1546 $this->reposition_block($biid, $newregion, $weight +
1);
1550 $this->reposition_block($block->instance
->id
, $newregion, $newweight);
1553 $this->page
->ensure_param_not_in_url('bui_moveid');
1554 $this->page
->ensure_param_not_in_url('bui_newregion');
1555 $this->page
->ensure_param_not_in_url('bui_newweight');
1560 * Turns the display of normal blocks either on or off.
1562 * @param bool $setting
1564 public function show_only_fake_blocks($setting = true) {
1565 $this->fakeblocksonly
= $setting;
1569 /// Helper functions for working with block classes ============================
1572 * Call a class method (one that does not require a block instance) on a block class.
1574 * @param string $blockname the name of the block.
1575 * @param string $method the method name.
1576 * @param array $param parameters to pass to the method.
1577 * @return mixed whatever the method returns.
1579 function block_method_result($blockname, $method, $param = NULL) {
1580 if(!block_load_class($blockname)) {
1583 return call_user_func(array('block_'.$blockname, $method), $param);
1587 * Creates a new instance of the specified block class.
1589 * @param string $blockname the name of the block.
1590 * @param $instance block_instances DB table row (optional).
1591 * @param moodle_page $page the page this block is appearing on.
1592 * @return block_base the requested block instance.
1594 function block_instance($blockname, $instance = NULL, $page = NULL) {
1595 if(!block_load_class($blockname)) {
1598 $classname = 'block_'.$blockname;
1599 $retval = new $classname;
1600 if($instance !== NULL) {
1601 if (is_null($page)) {
1605 $retval->_load_instance($instance, $page);
1611 * Load the block class for a particular type of block.
1613 * @param string $blockname the name of the block.
1614 * @return boolean success or failure.
1616 function block_load_class($blockname) {
1619 if(empty($blockname)) {
1623 $classname = 'block_'.$blockname;
1625 if(class_exists($classname)) {
1629 $blockpath = $CFG->dirroot
.'/blocks/'.$blockname.'/block_'.$blockname.'.php';
1631 if (file_exists($blockpath)) {
1632 require_once($CFG->dirroot
.'/blocks/moodleblock.class.php');
1633 include_once($blockpath);
1635 //debugging("$blockname code does not exist in $blockpath", DEBUG_DEVELOPER);
1639 return class_exists($classname);
1643 * Given a specific page type, return all the page type patterns that might
1646 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1647 * @return array an array of all the page type patterns that might match this page type.
1649 function matching_page_type_patterns($pagetype) {
1650 $patterns = array($pagetype);
1651 $bits = explode('-', $pagetype);
1652 if (count($bits) == 3 && $bits[0] == 'mod') {
1653 if ($bits[2] == 'view') {
1654 $patterns[] = 'mod-*-view';
1655 } else if ($bits[2] == 'index') {
1656 $patterns[] = 'mod-*-index';
1659 while (count($bits) > 0) {
1660 $patterns[] = implode('-', $bits) . '-*';
1668 * Given a specific page type, parent context and currect context, return all the page type patterns
1669 * that might be used by this block.
1671 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1672 * @param stdClass $parentcontext Block's parent context
1673 * @param stdClass $currentcontext Current context of block
1674 * @return array an array of all the page type patterns that might match this page type.
1676 function generate_page_type_patterns($pagetype, $parentcontext = null, $currentcontext = null) {
1679 $bits = explode('-', $pagetype);
1681 $core = get_core_subsystems();
1682 $plugins = get_plugin_types();
1684 //progressively strip pieces off the page type looking for a match
1685 $componentarray = null;
1686 for ($i = count($bits); $i > 0; $i--) {
1687 $possiblecomponentarray = array_slice($bits, 0, $i);
1688 $possiblecomponent = implode('', $possiblecomponentarray);
1690 // Check to see if the component is a core component
1691 if (array_key_exists($possiblecomponent, $core) && !empty($core[$possiblecomponent])) {
1692 $libfile = $CFG->dirroot
.'/'.$core[$possiblecomponent].'/lib.php';
1693 if (file_exists($libfile)) {
1694 require_once($libfile);
1695 $function = $possiblecomponent.'_page_type_list';
1696 if (function_exists($function)) {
1697 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1704 //check the plugin directory and look for a callback
1705 if (array_key_exists($possiblecomponent, $plugins) && !empty($plugins[$possiblecomponent])) {
1707 //We've found a plugin type. Look for a plugin name by getting the next section of page type
1708 if (count($bits) > $i) {
1709 $pluginname = $bits[$i];
1710 $directory = get_plugin_directory($possiblecomponent, $pluginname);
1711 if (!empty($directory)){
1712 $libfile = $directory.'/lib.php';
1713 if (file_exists($libfile)) {
1714 require_once($libfile);
1715 $function = $possiblecomponent.'_'.$pluginname.'_page_type_list';
1716 if (!function_exists($function)) {
1717 $function = $pluginname.'_page_type_list';
1719 if (function_exists($function)) {
1720 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1728 //we'll only get to here if we still don't have any patterns
1729 //the plugin type may have a callback
1730 $directory = get_plugin_directory($possiblecomponent, null);
1731 if (!empty($directory)){
1732 $libfile = $directory.'/lib.php';
1733 if (file_exists($libfile)) {
1734 require_once($libfile);
1735 $function = $possiblecomponent.'_page_type_list';
1736 if (function_exists($function)) {
1737 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1746 if (empty($patterns)) {
1747 $patterns = default_page_type_list($pagetype, $parentcontext, $currentcontext);
1750 // Ensure that the * pattern is always available if editing block 'at distance', so
1751 // we always can 'bring back' it to the original context. MDL-30340
1752 if ((!isset($currentcontext) or !isset($parentcontext) or $currentcontext->id
!= $parentcontext->id
) && !isset($patterns['*'])) {
1753 // TODO: We could change the string here, showing its 'bring back' meaning
1754 $patterns['*'] = get_string('page-x', 'pagetype');
1761 * Generates a default page type list when a more appropriate callback cannot be decided upon.
1763 * @param string $pagetype
1764 * @param stdClass $parentcontext
1765 * @param stdClass $currentcontext
1768 function default_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1769 // Generate page type patterns based on current page type if
1770 // callbacks haven't been defined
1771 $patterns = array($pagetype => $pagetype);
1772 $bits = explode('-', $pagetype);
1773 while (count($bits) > 0) {
1774 $pattern = implode('-', $bits) . '-*';
1775 $pagetypestringname = 'page-'.str_replace('*', 'x', $pattern);
1776 // guessing page type description
1777 if (get_string_manager()->string_exists($pagetypestringname, 'pagetype')) {
1778 $patterns[$pattern] = get_string($pagetypestringname, 'pagetype');
1780 $patterns[$pattern] = $pattern;
1784 $patterns['*'] = get_string('page-x', 'pagetype');
1789 * Generates the page type list for the my moodle page
1791 * @param string $pagetype
1792 * @param stdClass $parentcontext
1793 * @param stdClass $currentcontext
1796 function my_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1797 return array('my-index' => get_string('page-my-index', 'pagetype'));
1801 * Generates the page type list for a module by either locating and using the modules callback
1802 * or by generating a default list.
1804 * @param string $pagetype
1805 * @param stdClass $parentcontext
1806 * @param stdClass $currentcontext
1809 function mod_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1810 $patterns = plugin_page_type_list($pagetype, $parentcontext, $currentcontext);
1811 if (empty($patterns)) {
1812 // if modules don't have callbacks
1813 // generate two default page type patterns for modules only
1814 $bits = explode('-', $pagetype);
1815 $patterns = array($pagetype => $pagetype);
1816 if ($bits[2] == 'view') {
1817 $patterns['mod-*-view'] = get_string('page-mod-x-view', 'pagetype');
1818 } else if ($bits[2] == 'index') {
1819 $patterns['mod-*-index'] = get_string('page-mod-x-index', 'pagetype');
1824 /// Functions update the blocks if required by the request parameters ==========
1827 * Return a {@link block_contents} representing the add a new block UI, if
1828 * this user is allowed to see it.
1830 * @return block_contents an appropriate block_contents, or null if the user
1831 * cannot add any blocks here.
1833 function block_add_block_ui($page, $output) {
1834 global $CFG, $OUTPUT;
1835 if (!$page->user_is_editing() ||
!$page->user_can_edit_blocks()) {
1839 $bc = new block_contents();
1840 $bc->title
= get_string('addblock');
1841 $bc->add_class('block_adminblock');
1843 $missingblocks = $page->blocks
->get_addable_blocks();
1844 if (empty($missingblocks)) {
1845 $bc->content
= get_string('noblockstoaddhere');
1850 foreach ($missingblocks as $block) {
1851 $blockobject = block_instance($block->name
);
1852 if ($blockobject !== false && $blockobject->user_can_addto($page)) {
1853 $menu[$block->name
] = $blockobject->get_title();
1856 collatorlib
::asort($menu);
1858 $actionurl = new moodle_url($page->url
, array('sesskey'=>sesskey()));
1859 $select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');
1860 $select->set_label(get_string('addblock'), array('class'=>'accesshide'));
1861 $bc->content
= $OUTPUT->render($select);
1865 // Functions that have been deprecated by block_manager =======================
1868 * @deprecated since Moodle 2.0 - use $page->blocks->get_addable_blocks();
1870 * This function returns an array with the IDs of any blocks that you can add to your page.
1871 * Parameters are passed by reference for speed; they are not modified at all.
1873 * @param $page the page object.
1874 * @param $blockmanager Not used.
1875 * @return array of block type ids.
1877 function blocks_get_missing(&$page, &$blockmanager) {
1878 debugging('blocks_get_missing is deprecated. Please use $page->blocks->get_addable_blocks() instead.', DEBUG_DEVELOPER
);
1879 $blocks = $page->blocks
->get_addable_blocks();
1881 foreach ($blocks as $block) {
1882 $ids[] = $block->id
;
1888 * Actually delete from the database any blocks that are currently on this page,
1889 * but which should not be there according to blocks_name_allowed_in_format.
1891 * @todo Write/Fix this function. Currently returns immediately
1894 function blocks_remove_inappropriate($course) {
1898 $blockmanager = blocks_get_by_page($page);
1900 if (empty($blockmanager)) {
1904 if (($pageformat = $page->pagetype) == NULL) {
1908 foreach($blockmanager as $region) {
1909 foreach($region as $instance) {
1910 $block = blocks_get_record($instance->blockid);
1911 if(!blocks_name_allowed_in_format($block->name, $pageformat)) {
1912 blocks_delete_instance($instance->instance);
1919 * Check that a given name is in a permittable format
1921 * @param string $name
1922 * @param string $pageformat
1925 function blocks_name_allowed_in_format($name, $pageformat) {
1928 if (!$bi = block_instance($name)) {
1932 $formats = $bi->applicable_formats();
1936 foreach ($formats as $format => $allowed) {
1937 $formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';
1938 $depth = substr_count($format, '-');
1939 if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {
1944 if ($accept === NULL) {
1945 $accept = !empty($formats['all']);
1951 * Delete a block, and associated data.
1953 * @param object $instance a row from the block_instances table
1954 * @param bool $nolongerused legacy parameter. Not used, but kept for backwards compatibility.
1955 * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.
1957 function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {
1960 if ($block = block_instance($instance->blockname
, $instance)) {
1961 $block->instance_delete();
1963 delete_context(CONTEXT_BLOCK
, $instance->id
);
1965 if (!$skipblockstables) {
1966 $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id
));
1967 $DB->delete_records('block_instances', array('id' => $instance->id
));
1968 $DB->delete_records_list('user_preferences', 'name', array('block'.$instance->id
.'hidden','docked_block_instance_'.$instance->id
));
1973 * Delete all the blocks that belong to a particular context.
1975 * @param int $contextid the context id.
1977 function blocks_delete_all_for_context($contextid) {
1979 $instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));
1980 foreach ($instances as $instance) {
1981 blocks_delete_instance($instance, true);
1983 $instances->close();
1984 $DB->delete_records('block_instances', array('parentcontextid' => $contextid));
1985 $DB->delete_records('block_positions', array('contextid' => $contextid));
1989 * Set a block to be visible or hidden on a particular page.
1991 * @param object $instance a row from the block_instances, preferably LEFT JOINed with the
1992 * block_positions table as return by block_manager.
1993 * @param moodle_page $page the back to set the visibility with respect to.
1994 * @param integer $newvisibility 1 for visible, 0 for hidden.
1996 function blocks_set_visibility($instance, $page, $newvisibility) {
1998 if (!empty($instance->blockpositionid
)) {
1999 // Already have local information on this page.
2000 $DB->set_field('block_positions', 'visible', $newvisibility, array('id' => $instance->blockpositionid
));
2004 // Create a new block_positions record.
2006 $bp->blockinstanceid
= $instance->id
;
2007 $bp->contextid
= $page->context
->id
;
2008 $bp->pagetype
= $page->pagetype
;
2009 if ($page->subpage
) {
2010 $bp->subpage
= $page->subpage
;
2012 $bp->visible
= $newvisibility;
2013 $bp->region
= $instance->defaultregion
;
2014 $bp->weight
= $instance->defaultweight
;
2015 $DB->insert_record('block_positions', $bp);
2019 * @deprecated since 2.0
2020 * Delete all the blocks from a particular page.
2022 * @param string $pagetype the page type.
2023 * @param integer $pageid the page id.
2024 * @return bool success or failure.
2026 function blocks_delete_all_on_page($pagetype, $pageid) {
2029 debugging('Call to deprecated function blocks_delete_all_on_page. ' .
2030 'This function cannot work any more. Doing nothing. ' .
2031 'Please update your code to use a block_manager method $PAGE->blocks->....', DEBUG_DEVELOPER
);
2036 * Get the block record for a particular blockid - that is, a particular type os block.
2038 * @param $int blockid block type id. If null, an array of all block types is returned.
2039 * @param bool $notusedanymore No longer used.
2040 * @return array|object row from block table, or all rows.
2042 function blocks_get_record($blockid = NULL, $notusedanymore = false) {
2044 $blocks = $PAGE->blocks
->get_installed_blocks();
2045 if ($blockid === NULL) {
2047 } else if (isset($blocks[$blockid])) {
2048 return $blocks[$blockid];
2055 * Find a given block by its blockid within a provide array
2057 * @param int $blockid
2058 * @param array $blocksarray
2059 * @return bool|object Instance if found else false
2061 function blocks_find_block($blockid, $blocksarray) {
2062 if (empty($blocksarray)) {
2065 foreach($blocksarray as $blockgroup) {
2066 if (empty($blockgroup)) {
2069 foreach($blockgroup as $instance) {
2070 if($instance->blockid
== $blockid) {
2078 // Functions for programatically adding default blocks to pages ================
2081 * Parse a list of default blocks. See config-dist for a description of the format.
2083 * @param string $blocksstr
2086 function blocks_parse_default_blocks_list($blocksstr) {
2088 $bits = explode(':', $blocksstr);
2089 if (!empty($bits)) {
2090 $leftbits = trim(array_shift($bits));
2091 if ($leftbits != '') {
2092 $blocks[BLOCK_POS_LEFT
] = explode(',', $leftbits);
2095 if (!empty($bits)) {
2096 $rightbits =trim(array_shift($bits));
2097 if ($rightbits != '') {
2098 $blocks[BLOCK_POS_RIGHT
] = explode(',', $rightbits);
2105 * @return array the blocks that should be added to the site course by default.
2107 function blocks_get_default_site_course_blocks() {
2110 if (!empty($CFG->defaultblocks_site
)) {
2111 return blocks_parse_default_blocks_list($CFG->defaultblocks_site
);
2114 BLOCK_POS_LEFT
=> array('site_main_menu'),
2115 BLOCK_POS_RIGHT
=> array('course_summary', 'calendar_month')
2121 * Add the default blocks to a course.
2123 * @param object $course a course object.
2125 function blocks_add_default_course_blocks($course) {
2128 if (!empty($CFG->defaultblocks_override
)) {
2129 $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override
);
2131 } else if ($course->id
== SITEID
) {
2132 $blocknames = blocks_get_default_site_course_blocks();
2134 } else if (!empty($CFG->{'defaultblocks_' . $course->format
})) {
2135 $blocknames = blocks_parse_default_blocks_list($CFG->{'defaultblocks_' . $course->format
});
2138 $blocknames = course_get_format($course)->get_default_blocks();
2142 if ($course->id
== SITEID
) {
2143 $pagetypepattern = 'site-index';
2145 $pagetypepattern = 'course-view-*';
2147 $page = new moodle_page();
2148 $page->set_course($course);
2149 $page->blocks
->add_blocks($blocknames, $pagetypepattern);
2153 * Add the default system-context blocks. E.g. the admin tree.
2155 function blocks_add_default_system_blocks() {
2158 $page = new moodle_page();
2159 $page->set_context(context_system
::instance());
2160 $page->blocks
->add_blocks(array(BLOCK_POS_LEFT
=> array('navigation', 'settings')), '*', null, true);
2161 $page->blocks
->add_blocks(array(BLOCK_POS_LEFT
=> array('admin_bookmarks')), 'admin-*', null, null, 2);
2163 if ($defaultmypage = $DB->get_record('my_pages', array('userid'=>null, 'name'=>'__default', 'private'=>1))) {
2164 $subpagepattern = $defaultmypage->id
;
2166 $subpagepattern = null;
2169 $page->blocks
->add_blocks(array(BLOCK_POS_RIGHT
=> array('private_files', 'online_users'), 'content' => array('course_overview')), 'my-index', $subpagepattern, false);