MDL-41551 blocks: added tracking and recognition of custom block regions
[moodle.git] / lib / blocklib.php
CommitLineData
d4accfc0 1<?php
2
a19f419d 3// This file is part of Moodle - http://moodle.org/
4//
d4accfc0 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.
9//
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.
a19f419d 14//
d4accfc0 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/>.
e8c8cee9 17
18/**
d4accfc0 19 * Block Class and Functions
e8c8cee9 20 *
a19f419d 21 * This file defines the {@link block_manager} class,
d4a03c00 22 *
78bfb562
PS
23 * @package core
24 * @subpackage block
25 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
e8c8cee9 27 */
0f3fe4b6 28
78bfb562
PS
29defined('MOODLE_INTERNAL') || die();
30
13a0d3d3 31/**#@+
32 * @deprecated since Moodle 2.0. No longer used.
d4a03c00 33 */
0f3fe4b6 34define('BLOCK_MOVE_LEFT', 0x01);
35define('BLOCK_MOVE_RIGHT', 0x02);
36define('BLOCK_MOVE_UP', 0x04);
37define('BLOCK_MOVE_DOWN', 0x08);
9b4b78fd 38define('BLOCK_CONFIGURE', 0x10);
13a0d3d3 39/**#@-*/
0f3fe4b6 40
13a0d3d3 41/**#@+
42 * Default names for the block regions in the standard theme.
43 */
bb46a4fa 44define('BLOCK_POS_LEFT', 'side-pre');
45define('BLOCK_POS_RIGHT', 'side-post');
13a0d3d3 46/**#@-*/
0e9af917 47
13a0d3d3 48/**#@+
49 * @deprecated since Moodle 2.0. No longer used.
50 */
ee6055eb 51define('BLOCKS_PINNED_TRUE',0);
52define('BLOCKS_PINNED_FALSE',1);
53define('BLOCKS_PINNED_BOTH',2);
13a0d3d3 54/**#@-*/
ee6055eb 55
b1627a92
DC
56define('BUI_CONTEXTS_FRONTPAGE_ONLY', 0);
57define('BUI_CONTEXTS_FRONTPAGE_SUBS', 1);
58define('BUI_CONTEXTS_ENTIRE_SITE', 2);
59
60define('BUI_CONTEXTS_CURRENT', 0);
61define('BUI_CONTEXTS_CURRENT_SUBS', 1);
62
d4accfc0 63/**
d4a03c00 64 * Exception thrown when someone tried to do something with a block that does
65 * not exist on a page.
d4accfc0 66 *
d4a03c00 67 * @copyright 2009 Tim Hunt
68 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
69 * @since Moodle 2.0
d4accfc0 70 */
f4d38d20 71class block_not_on_page_exception extends moodle_exception {
d4accfc0 72 /**
847bed23 73 * Constructor
d4a03c00 74 * @param int $instanceid the block instance id of the block that was looked for.
75 * @param object $page the current page.
d4accfc0 76 */
f4d38d20 77 public function __construct($instanceid, $page) {
78 $a = new stdClass;
79 $a->instanceid = $instanceid;
2a3b0763 80 $a->url = $page->url->out();
81 parent::__construct('blockdoesnotexistonpage', '', $page->url->out(), $a);
f4d38d20 82 }
83}
84
86b5ea0f 85/**
86 * This class keeps track of the block that should appear on a moodle_page.
bb46a4fa 87 *
d4a03c00 88 * The page to work with as passed to the constructor.
1d00ec6a 89 *
d4a03c00 90 * @copyright 2009 Tim Hunt
91 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
92 * @since Moodle 2.0
86b5ea0f 93 */
d4a03c00 94class block_manager {
2cdb8d84 95 /**
96 * The UI normally only shows block weights between -MAX_WEIGHT and MAX_WEIGHT,
97 * although other weights are valid.
98 */
99 const MAX_WEIGHT = 10;
86b5ea0f 100
101/// Field declarations =========================================================
d4a03c00 102
d8ef60bd
SH
103 /**
104 * the moodle_page we are managing blocks for.
105 * @var moodle_page
106 */
86b5ea0f 107 protected $page;
d4a03c00 108
109 /** @var array region name => 1.*/
86b5ea0f 110 protected $regions = array();
d4a03c00 111
112 /** @var string the region where new blocks are added.*/
113 protected $defaultregion = null;
114
115 /** @var array will be $DB->get_records('blocks') */
116 protected $allblocks = null;
117
118 /**
119 * @var array blocks that this user can add to this page. Will be a subset
a2789e34 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.
d4a03c00 122 */
123 protected $addableblocks = null;
08eab897 124
bb46a4fa 125 /**
126 * Will be an array region-name => array(db rows loaded in load_blocks);
d4accfc0 127 * @var array
bb46a4fa 128 */
129 protected $birecordsbyregion = null;
130
131 /**
132 * array region-name => array(block objects); populated as necessary by
133 * the ensure_instances_exist method.
d4accfc0 134 * @var array
bb46a4fa 135 */
136 protected $blockinstances = array();
137
138 /**
847bed23 139 * array region-name => array(block_contents objects) what actually needs to
bb46a4fa 140 * be displayed in each region.
d4accfc0 141 * @var array
bb46a4fa 142 */
143 protected $visibleblockcontent = array();
08eab897 144
d4a03c00 145 /**
146 * array region-name => array(block_contents objects) extra block-like things
147 * to be displayed in each region, before the real blocks.
148 * @var array
149 */
150 protected $extracontent = array();
151
00a24d44 152 /**
847bed23 153 * Used by the block move id, to track whether a block is currently being moved.
00a24d44 154 *
847bed23
PS
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
00a24d44 157 * this field holds the id of the block being moved.
158 *
159 * @var integer|null
160 */
161 protected $movingblock = null;
162
56ed242b
SH
163 /**
164 * Show only fake blocks
165 */
166 protected $fakeblocksonly = false;
167
86b5ea0f 168/// Constructor ================================================================
169
170 /**
171 * Constructor.
172 * @param object $page the moodle_page object object we are managing the blocks for,
847bed23 173 * or a reasonable faxilimily. (See the comment at the top of this class
d4accfc0 174 * and {@link http://en.wikipedia.org/wiki/Duck_typing})
86b5ea0f 175 */
176 public function __construct($page) {
177 $this->page = $page;
178 }
179
180/// Getter methods =============================================================
181
182 /**
d4accfc0 183 * Get an array of all region names on this page where a block may appear
184 *
86b5ea0f 185 * @return array the internal names of the regions on this page where block may appear.
186 */
187 public function get_regions() {
78946b9b 188 if (is_null($this->defaultregion)) {
7d875874 189 $this->page->initialise_theme_and_output();
78946b9b 190 }
86b5ea0f 191 return array_keys($this->regions);
192 }
193
194 /**
d4accfc0 195 * Get the region name of the region blocks are added to by default
196 *
86b5ea0f 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.)
201 */
202 public function get_default_region() {
d4a03c00 203 $this->page->initialise_theme_and_output();
86b5ea0f 204 return $this->defaultregion;
205 }
206
08eab897 207 /**
208 * The list of block types that may be added to this page.
d4accfc0 209 *
727ae436 210 * @return array block name => record from block table.
08eab897 211 */
212 public function get_addable_blocks() {
213 $this->check_is_loaded();
214
215 if (!is_null($this->addableblocks)) {
216 return $this->addableblocks;
217 }
218
219 // Lazy load.
220 $this->addableblocks = array();
221
222 $allblocks = blocks_get_record();
223 if (empty($allblocks)) {
224 return $this->addableblocks;
225 }
226
1c76fc96 227 $unaddableblocks = self::get_undeletable_block_types();
bb46a4fa 228 $pageformat = $this->page->pagetype;
08eab897 229 foreach($allblocks as $block) {
f20edd52
PS
230 if (!$bi = block_instance($block->name)) {
231 continue;
232 }
1c76fc96 233 if ($block->visible && !in_array($block->name, $unaddableblocks) &&
f20edd52 234 ($bi->instance_allow_multiple() || !$this->is_block_present($block->name)) &&
a2789e34 235 blocks_name_allowed_in_format($block->name, $pageformat) &&
f20edd52 236 $bi->user_can_addto($this->page)) {
a2789e34 237 $this->addableblocks[$block->name] = $block;
08eab897 238 }
239 }
240
241 return $this->addableblocks;
242 }
243
d4accfc0 244 /**
464c7e70
MD
245 * Given a block name, find out of any of them are currently present in the page
246
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?
d4accfc0 249 */
464c7e70
MD
250 public function is_block_present($blockname) {
251 if (empty($this->blockinstances)) {
252 return false;
253 }
254
255 foreach ($this->blockinstances as $region) {
256 foreach ($region as $instance) {
257 if (empty($instance->instance->blockname)) {
258 continue;
259 }
260 if ($instance->instance->blockname == $blockname) {
261 return true;
262 }
263 }
264 }
265 return false;
08eab897 266 }
267
268 /**
d4accfc0 269 * Find out if a block type is known by the system
270 *
847bed23 271 * @param string $blockname the name of the type of block.
08eab897 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.
274 */
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)) {
279 return true;
280 }
281 }
282 return false;
283 }
284
285 /**
d4accfc0 286 * Find out if a region exists on a page
287 *
08eab897 288 * @param string $region a region name
847bed23 289 * @return boolean true if this region exists on this page.
08eab897 290 */
291 public function is_known_region($region) {
292 return array_key_exists($region, $this->regions);
293 }
294
295 /**
d4accfc0 296 * Get an array of all blocks within a given region
297 *
298 * @param string $region a block region that exists on this page.
08eab897 299 * @return array of block instances.
300 */
301 public function get_blocks_for_region($region) {
302 $this->check_is_loaded();
bb46a4fa 303 $this->ensure_instances_exist($region);
304 return $this->blockinstances[$region];
305 }
306
307 /**
d4accfc0 308 * Returns an array of block content objects that exist in a region
309 *
d4a03c00 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.
bb46a4fa 312 */
d4a03c00 313 public function get_content_for_region($region, $output) {
bb46a4fa 314 $this->check_is_loaded();
d4a03c00 315 $this->ensure_content_created($region, $output);
bb46a4fa 316 return $this->visibleblockcontent[$region];
08eab897 317 }
318
00a24d44 319 /**
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.
325 */
326 protected function get_move_target_url($region, $weight) {
dd72b308 327 return new moodle_url($this->page->url, array('bui_moveid' => $this->movingblock,
b9bc2019 328 'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()));
00a24d44 329 }
330
d4a03c00 331 /**
332 * Determine whether a region contains anything. (Either any real blocks, or
333 * the add new block UI.)
78d27a90 334 *
335 * (You may wonder why the $output parameter is required. Unfortunately,
847bed23 336 * because of the way that blocks work, the only reliable way to find out
78d27a90 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
847bed23 339 * performance problem, because we cache the output that is generated, and
78d27a90 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.)
342 *
d4a03c00 343 * @param string $region a block region that exists on this page.
06a72e01 344 * @param core_renderer $output a core_renderer. normally the global $OUTPUT.
d4a03c00 345 * @return boolean Whether there is anything in this region.
346 */
78d27a90 347 public function region_has_content($region, $output) {
4f0c2d00 348
d4a03c00 349 if (!$this->is_known_region($region)) {
350 return false;
351 }
352 $this->check_is_loaded();
78d27a90 353 $this->ensure_content_created($region, $output);
afc7026a
ME
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) {
d4a03c00 357 // If editing is on, we need all the block regions visible, for the
358 // move blocks UI.
359 return true;
360 }
78d27a90 361 return !empty($this->visibleblockcontent[$region]) || !empty($this->extracontent[$region]);
d4a03c00 362 }
363
08eab897 364 /**
d4accfc0 365 * Get an array of all of the installed blocks.
366 *
08eab897 367 * @return array contents of the block table.
368 */
369 public function get_installed_blocks() {
370 global $DB;
371 if (is_null($this->allblocks)) {
372 $this->allblocks = $DB->get_records('block');
373 }
374 return $this->allblocks;
375 }
376
1c76fc96
TH
377 /**
378 * @return array names of block types that cannot be added or deleted. E.g. array('navigation','settings').
379 */
380 public static function get_undeletable_block_types() {
cedf9763
DP
381 global $CFG;
382
1c76fc96
TH
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);
387 } else {
388 return $CFG->undeletableblocktypes;
389 }
390 }
391
86b5ea0f 392/// Setter methods =============================================================
393
394 /**
d4accfc0 395 * Add a region to a page
396 *
292dcf04
SH
397 * @param string $region add a named region where blocks may appear on the current page.
398 * This is an internal name, like 'side-pre', not a string to display in the UI.
399 * @param bool $custom True if this is a custom block region, being added by the page rather than the theme layout.
86b5ea0f 400 */
292dcf04
SH
401 public function add_region($region, $custom = true) {
402 global $SESSION;
86b5ea0f 403 $this->check_not_yet_loaded();
292dcf04
SH
404 if ($custom) {
405 if (array_key_exists($region, $this->regions)) {
406 // This here is EXACTLY why we should not be adding block regions into a page. It should
407 // ALWAYS be done in a theme layout.
408 debugging('A custom region conflicts with a block region in the theme.', DEBUG_DEVELOPER);
409 }
410 // We need to register this custom region against the page type being used.
411 // This allows us to check, when performing block actions, that unrecognised regions can be worked with.
412 $type = $this->page->pagetype;
413 if (!isset($SESSION->custom_block_regions)) {
414 $SESSION->custom_block_regions = array($type => array($region));
415 } else if (!isset($SESSION->custom_block_regions[$type])) {
416 $SESSION->custom_block_regions[$type] = array($region);
417 } else if (!in_array($region, $SESSION->custom_block_regions[$type])) {
418 $SESSION->custom_block_regions[$type][] = $region;
419 }
420 }
86b5ea0f 421 $this->regions[$region] = 1;
422 }
423
424 /**
d4accfc0 425 * Add an array of regions
426 * @see add_region()
427 *
86b5ea0f 428 * @param array $regions this utility method calls add_region for each array element.
429 */
292dcf04 430 public function add_regions($regions, $custom = true) {
86b5ea0f 431 foreach ($regions as $region) {
292dcf04
SH
432 $this->add_region($region, $custom);
433 }
434 }
435
436 /**
437 * Finds custom block regions associated with a page type and registers them with this block manager.
438 *
439 * @param string $pagetype
440 */
441 public function add_custom_regions_for_pagetype($pagetype) {
442 global $SESSION;
443 if (isset($SESSION->custom_block_regions[$pagetype])) {
444 foreach ($SESSION->custom_block_regions[$pagetype] as $customregion) {
445 $this->add_region($customregion, false);
446 }
86b5ea0f 447 }
448 }
449
450 /**
d4accfc0 451 * Set the default region for new blocks on the page
452 *
86b5ea0f 453 * @param string $defaultregion the internal names of the region where new
454 * blocks should be added by default, and where any blocks from an
455 * unrecognised region are shown.
456 */
457 public function set_default_region($defaultregion) {
458 $this->check_not_yet_loaded();
035b96a9 459 if ($defaultregion) {
460 $this->check_region_is_known($defaultregion);
461 }
86b5ea0f 462 $this->defaultregion = $defaultregion;
463 }
464
d4a03c00 465 /**
466 * Add something that looks like a block, but which isn't an actual block_instance,
467 * to this page.
468 *
d9c26e21 469 * @param block_contents $bc the content of the block-like thing.
d4a03c00 470 * @param string $region a block region that exists on this page.
471 */
d9c26e21 472 public function add_fake_block($bc, $region) {
d4a03c00 473 $this->page->initialise_theme_and_output();
bf2b43df
SH
474 if (!$this->is_known_region($region)) {
475 $region = $this->get_default_region();
476 }
d4a03c00 477 if (array_key_exists($region, $this->visibleblockcontent)) {
478 throw new coding_exception('block_manager has already prepared the blocks in region ' .
d9c26e21 479 $region . 'for output. It is too late to add a fake block.');
d4a03c00 480 }
a69a7e89
SH
481 if (!isset($bc->attributes['data-block'])) {
482 $bc->attributes['data-block'] = '_fake';
483 }
d4a03c00 484 $this->extracontent[$region][] = $bc;
485 }
486
d9c26e21
TH
487 /**
488 * When the block_manager class was created, the {@link add_fake_block()}
489 * was called add_pretend_block, which is inconsisted with
490 * {@link show_only_fake_blocks()}. To fix this inconsistency, this method
491 * was renamed to add_fake_block. Please update your code.
492 * @param block_contents $bc the content of the block-like thing.
493 * @param string $region a block region that exists on this page.
494 */
495 public function add_pretend_block($bc, $region) {
496 debugging(DEBUG_DEVELOPER, 'add_pretend_block has been renamed to add_fake_block. Please rename the method call in your code.');
497 $this->add_fake_block($bc, $region);
498 }
499
7e4617f7
SH
500 /**
501 * Checks to see whether all of the blocks within the given region are docked
502 *
4d6fd15c 503 * @see region_uses_dock
7e4617f7
SH
504 * @param string $region
505 * @return bool True if all of the blocks within that region are docked
506 */
507 public function region_completely_docked($region, $output) {
2bb1d1fb
RT
508 global $CFG;
509 // If theme doesn't allow docking or allowblockstodock is not set, then return.
510 if (!$this->page->theme->enable_dock || empty($CFG->allowblockstodock)) {
7e4617f7
SH
511 return false;
512 }
dbc623c4
MM
513
514 // Do not dock the region when the user attemps to move a block.
515 if ($this->movingblock) {
516 return false;
517 }
518
7e4617f7
SH
519 $this->check_is_loaded();
520 $this->ensure_content_created($region, $output);
521 foreach($this->visibleblockcontent[$region] as $instance) {
522 if (!empty($instance->content) && !get_user_preferences('docked_block_instance_'.$instance->blockinstanceid, 0)) {
523 return false;
524 }
525 }
526 return true;
527 }
528
4d6fd15c
SH
529 /**
530 * Checks to see whether any of the blocks within the given regions are docked
531 *
532 * @see region_completely_docked
533 * @param array|string $regions array of regions (or single region)
534 * @return bool True if any of the blocks within that region are docked
535 */
536 public function region_uses_dock($regions, $output) {
537 if (!$this->page->theme->enable_dock) {
538 return false;
539 }
540 $this->check_is_loaded();
541 foreach((array)$regions as $region) {
542 $this->ensure_content_created($region, $output);
543 foreach($this->visibleblockcontent[$region] as $instance) {
544 if(!empty($instance->content) && get_user_preferences('docked_block_instance_'.$instance->blockinstanceid, 0)) {
545 return true;
546 }
547 }
548 }
549 return false;
550 }
551
08eab897 552/// Actions ====================================================================
553
554 /**
555 * This method actually loads the blocks for our page from the database.
d4accfc0 556 *
ae42ff6f 557 * @param boolean|null $includeinvisible
558 * null (default) - load hidden blocks if $this->page->user_is_editing();
559 * true - load hidden blocks.
560 * false - don't load hidden blocks.
08eab897 561 */
ae42ff6f 562 public function load_blocks($includeinvisible = null) {
d19e8195 563 global $DB, $CFG;
7d875874 564
bb46a4fa 565 if (!is_null($this->birecordsbyregion)) {
566 // Already done.
567 return;
568 }
08eab897 569
d19e8195 570 if ($CFG->version < 2009050619) {
571 // Upgrade/install not complete. Don't try too show any blocks.
572 $this->birecordsbyregion = array();
573 return;
574 }
575
d4a03c00 576 // Ensure we have been initialised.
7d875874 577 if (is_null($this->defaultregion)) {
b7009474 578 $this->page->initialise_theme_and_output();
d4a03c00 579 // If there are still no block regions, then there are no blocks on this page.
580 if (empty($this->regions)) {
581 $this->birecordsbyregion = array();
582 return;
583 }
b7009474 584 }
585
56ed242b
SH
586 // Check if we need to load normal blocks
587 if ($this->fakeblocksonly) {
588 $this->birecordsbyregion = $this->prepare_per_region_arrays();
589 return;
590 }
591
08eab897 592 if (is_null($includeinvisible)) {
593 $includeinvisible = $this->page->user_is_editing();
594 }
595 if ($includeinvisible) {
08eab897 596 $visiblecheck = '';
ae42ff6f 597 } else {
598 $visiblecheck = 'AND (bp.visible = 1 OR bp.visible IS NULL)';
08eab897 599 }
600
601 $context = $this->page->context;
13a0d3d3 602 $contexttest = 'bi.parentcontextid = :contextid2';
08eab897 603 $parentcontextparams = array();
8e8891b7 604 $parentcontextids = $context->get_parent_context_ids();
08eab897 605 if ($parentcontextids) {
606 list($parentcontexttest, $parentcontextparams) =
cf717dc2 607 $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED, 'parentcontext');
13a0d3d3 608 $contexttest = "($contexttest OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontexttest))";
08eab897 609 }
610
1d13c75c 611 $pagetypepatterns = matching_page_type_patterns($this->page->pagetype);
08eab897 612 list($pagetypepatterntest, $pagetypepatternparams) =
cf717dc2 613 $DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED, 'pagetypepatterntest');
08eab897 614
2e4c0c91
FM
615 $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
616 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = bi.id AND ctx.contextlevel = :contextlevel)";
4f0c2d00 617
08eab897 618 $params = array(
2e4c0c91 619 'contextlevel' => CONTEXT_BLOCK,
08eab897 620 'subpage1' => $this->page->subpage,
621 'subpage2' => $this->page->subpage,
622 'contextid1' => $context->id,
623 'contextid2' => $context->id,
624 'pagetype' => $this->page->pagetype,
625 );
fd3932fe 626 if ($this->page->subpage === '') {
77a5c093
PS
627 $params['subpage1'] = '';
628 $params['subpage2'] = '';
fd3932fe 629 }
08eab897 630 $sql = "SELECT
631 bi.id,
d4a03c00 632 bp.id AS blockpositionid,
08eab897 633 bi.blockname,
13a0d3d3 634 bi.parentcontextid,
08eab897 635 bi.showinsubcontexts,
636 bi.pagetypepattern,
637 bi.subpagepattern,
ae42ff6f 638 bi.defaultregion,
639 bi.defaultweight,
bb46a4fa 640 COALESCE(bp.visible, 1) AS visible,
08eab897 641 COALESCE(bp.region, bi.defaultregion) AS region,
642 COALESCE(bp.weight, bi.defaultweight) AS weight,
4f0c2d00
PS
643 bi.configdata
644 $ccselect
08eab897 645
646 FROM {block_instances} bi
647 JOIN {block} b ON bi.blockname = b.name
648 LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id
649 AND bp.contextid = :contextid1
650 AND bp.pagetype = :pagetype
651 AND bp.subpage = :subpage1
4f0c2d00 652 $ccjoin
08eab897 653
654 WHERE
655 $contexttest
656 AND bi.pagetypepattern $pagetypepatterntest
657 AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)
658 $visiblecheck
659 AND b.visible = 1
660
661 ORDER BY
662 COALESCE(bp.region, bi.defaultregion),
663 COALESCE(bp.weight, bi.defaultweight),
664 bi.id";
665 $blockinstances = $DB->get_recordset_sql($sql, $params + $parentcontextparams + $pagetypepatternparams);
666
bb46a4fa 667 $this->birecordsbyregion = $this->prepare_per_region_arrays();
08eab897 668 $unknown = array();
08eab897 669 foreach ($blockinstances as $bi) {
db314f34 670 context_helper::preload_from_record($bi);
08eab897 671 if ($this->is_known_region($bi->region)) {
bb46a4fa 672 $this->birecordsbyregion[$bi->region][] = $bi;
08eab897 673 } else {
674 $unknown[] = $bi;
675 }
676 }
d4a03c00 677
678 // Pages don't necessarily have a defaultregion. The one time this can
679 // happen is when there are no theme block regions, but the script itself
680 // has a block region in the main content area.
681 if (!empty($this->defaultregion)) {
682 $this->birecordsbyregion[$this->defaultregion] =
683 array_merge($this->birecordsbyregion[$this->defaultregion], $unknown);
684 }
08eab897 685 }
686
687 /**
688 * Add a block to the current page, or related pages. The block is added to
689 * context $this->page->contextid. If $pagetypepattern $subpagepattern
d4accfc0 690 *
08eab897 691 * @param string $blockname The type of block to add.
692 * @param string $region the block region on this page to add the block to.
693 * @param integer $weight determines the order where this block appears in the region.
694 * @param boolean $showinsubcontexts whether this block appears in subcontexts, or just the current context.
695 * @param string|null $pagetypepattern which page types this block should appear on. Defaults to just the current page type.
696 * @param string|null $subpagepattern which subpage this block should appear on. NULL = any (the default), otherwise only the specified subpage.
697 */
698 public function add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern = NULL, $subpagepattern = NULL) {
699 global $DB;
51f96f5b
SM
700 // Allow invisible blocks because this is used when adding default page blocks, which
701 // might include invisible ones if the user makes some default blocks invisible
702 $this->check_known_block_type($blockname, true);
08eab897 703 $this->check_region_is_known($region);
704
705 if (empty($pagetypepattern)) {
706 $pagetypepattern = $this->page->pagetype;
707 }
708
709 $blockinstance = new stdClass;
710 $blockinstance->blockname = $blockname;
13a0d3d3 711 $blockinstance->parentcontextid = $this->page->context->id;
08eab897 712 $blockinstance->showinsubcontexts = !empty($showinsubcontexts);
713 $blockinstance->pagetypepattern = $pagetypepattern;
714 $blockinstance->subpagepattern = $subpagepattern;
715 $blockinstance->defaultregion = $region;
716 $blockinstance->defaultweight = $weight;
717 $blockinstance->configdata = '';
feed1900 718 $blockinstance->id = $DB->insert_record('block_instances', $blockinstance);
719
e92c286c 720 // Ensure the block context is created.
b0c6dc1c 721 context_block::instance($blockinstance->id);
e03c0c1d 722
feed1900 723 // If the new instance was created, allow it to do additional setup
e92c286c 724 if ($block = block_instance($blockname, $blockinstance)) {
feed1900 725 $block->instance_create();
726 }
08eab897 727 }
728
21d33bdf 729 public function add_block_at_end_of_default_region($blockname) {
730 $defaulregion = $this->get_default_region();
2a3b0763 731
21d33bdf 732 $lastcurrentblock = end($this->birecordsbyregion[$defaulregion]);
2a3b0763 733 if ($lastcurrentblock) {
734 $weight = $lastcurrentblock->weight + 1;
735 } else {
736 $weight = 0;
737 }
738
21d33bdf 739 if ($this->page->subpage) {
740 $subpage = $this->page->subpage;
741 } else {
742 $subpage = null;
743 }
a2789e34 744
745 // Special case. Course view page type include the course format, but we
746 // want to add the block non-format-specifically.
747 $pagetypepattern = $this->page->pagetype;
748 if (strpos($pagetypepattern, 'course-view') === 0) {
749 $pagetypepattern = 'course-view-*';
750 }
751
ddaa9147
EL
752 // We should end using this for ALL the blocks, making always the 1st option
753 // the default one to be used. Until then, this is one hack to avoid the
754 // 'pagetypewarning' message on blocks initial edition (MDL-27829) caused by
755 // non-existing $pagetypepattern set. This way at least we guarantee one "valid"
756 // (the FIRST $pagetypepattern will be set)
757
758 // We are applying it to all blocks created in mod pages for now and only if the
759 // default pagetype is not one of the available options
760 if (preg_match('/^mod-.*-/', $pagetypepattern)) {
761 $pagetypelist = generate_page_type_patterns($this->page->pagetype, null, $this->page->context);
762 // Only go for the first if the pagetype is not a valid option
763 if (is_array($pagetypelist) && !array_key_exists($pagetypepattern, $pagetypelist)) {
764 $pagetypepattern = key($pagetypelist);
765 }
766 }
767 // Surely other pages like course-report will need this too, they just are not important
768 // enough now. This will be decided in the coming days. (MDL-27829, MDL-28150)
769
2a3b0763 770 $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);
21d33bdf 771 }
772
9d1d606e 773 /**
774 * Convenience method, calls add_block repeatedly for all the blocks in $blocks.
d4accfc0 775 *
2a3b0763 776 * @param array $blocks array with array keys the region names, and values an array of block names.
9d1d606e 777 * @param string $pagetypepattern optional. Passed to @see add_block()
778 * @param string $subpagepattern optional. Passed to @see add_block()
779 */
7d2a0492 780 public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {
292dcf04 781 $this->add_regions(array_keys($blocks), false);
9d1d606e 782 foreach ($blocks as $region => $regionblocks) {
783 $weight = 0;
784 foreach ($regionblocks as $blockname) {
7d2a0492 785 $this->add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern, $subpagepattern);
9d1d606e 786 $weight += 1;
787 }
788 }
789 }
790
2cdb8d84 791 /**
792 * Move a block to a new position on this page.
793 *
794 * If this block cannot appear on any other pages, then we change defaultposition/weight
847bed23 795 * in the block_instances table. Otherwise we just set the position on this page.
2cdb8d84 796 *
797 * @param $blockinstanceid the block instance id.
798 * @param $newregion the new region name.
799 * @param $newweight the new weight.
800 */
801 public function reposition_block($blockinstanceid, $newregion, $newweight) {
802 global $DB;
803
804 $this->check_region_is_known($newregion);
805 $inst = $this->find_instance($blockinstanceid);
806
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)) {
811
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);
818
819 if ($bi->blockpositionid) {
820 $bp = new stdClass;
821 $bp->id = $bi->blockpositionid;
822 $bp->region = $newregion;
823 $bp->weight = $newweight;
824 $DB->update_record('block_positions', $bp);
825 }
826
827 } else {
828 // Just set position on this page.
829 $bp = new stdClass;
830 $bp->region = $newregion;
831 $bp->weight = $newweight;
832
833 if ($bi->blockpositionid) {
834 $bp->id = $bi->blockpositionid;
835 $DB->update_record('block_positions', $bp);
836
837 } else {
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;
843 } else {
844 $bp->subpage = '';
845 }
846 $bp->visible = $bi->visible;
847 $DB->insert_record('block_positions', $bp);
848 }
849 }
850 }
851
f4d38d20 852 /**
a19f419d 853 * Find a given block by its instance id
d4accfc0 854 *
f4d38d20 855 * @param integer $instanceid
1c76fc96 856 * @return block_base
f4d38d20 857 */
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) {
863 return $instance;
864 }
865 }
866 }
867 throw new block_not_on_page_exception($instanceid, $this->page);
868 }
869
86b5ea0f 870/// Inner workings =============================================================
871
d4accfc0 872 /**
873 * Check whether the page blocks have been loaded yet
874 *
875 * @return void Throws coding exception if already loaded
876 */
86b5ea0f 877 protected function check_not_yet_loaded() {
bb46a4fa 878 if (!is_null($this->birecordsbyregion)) {
86b5ea0f 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.');
880 }
881 }
882
d4accfc0 883 /**
884 * Check whether the page blocks have been loaded yet
885 *
886 * Nearly identical to the above function {@link check_not_yet_loaded()} except different message
887 *
888 * @return void Throws coding exception if already loaded
889 */
08eab897 890 protected function check_is_loaded() {
bb46a4fa 891 if (is_null($this->birecordsbyregion)) {
08eab897 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.');
893 }
894 }
895
d4accfc0 896 /**
897 * Check if a block type is known and usable
898 *
899 * @param string $blockname The block type name to search for
847bed23 900 * @param bool $includeinvisible Include disabled block types in the initial pass
d4accfc0 901 * @return void Coding Exception thrown if unknown or not enabled
902 */
08eab897 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);
907 } else {
908 throw new coding_exception('Block type ' . $blockname . ' has been disabled by the administrator.');
909 }
910 }
911 }
912
d4accfc0 913 /**
914 * Check if a region is known by its name
915 *
916 * @param string $region
917 * @return void Coding Exception thrown if the region is not known
918 */
08eab897 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);
922 }
86b5ea0f 923 }
bb46a4fa 924
925 /**
d4accfc0 926 * Returns an array of region names as keys and nested arrays for values
927 *
bb46a4fa 928 * @return array an array where the array keys are the region names, and the array
929 * values are empty arrays.
930 */
931 protected function prepare_per_region_arrays() {
932 $result = array();
933 foreach ($this->regions as $region => $notused) {
934 $result[$region] = array();
935 }
936 return $result;
937 }
938
d4accfc0 939 /**
940 * Create a set of new block instance from a record array
941 *
942 * @param array $birecords An array of block instance records
943 * @return array An array of instantiated block_instance objects
944 */
bb46a4fa 945 protected function create_block_instances($birecords) {
946 $results = array();
947 foreach ($birecords as $record) {
d836aa4b 948 if ($blockobject = block_instance($record->blockname, $record, $this->page)) {
949 $results[] = $blockobject;
950 }
bb46a4fa 951 }
952 return $results;
953 }
954
4578a5eb 955 /**
847bed23 956 * Create all the block instances for all the blocks that were loaded by
4578a5eb 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.
960 */
961 public function create_all_block_instances() {
962 foreach ($this->get_regions() as $region) {
963 $this->ensure_instances_exist($region);
964 }
965 }
966
d4accfc0 967 /**
00a24d44 968 * Return an array of content objects from a set of block instances
d4accfc0 969 *
970 * @param array $instances An array of block instances
78946b9b 971 * @param renderer_base The renderer to use.
00a24d44 972 * @param string $region the region name.
973 * @return array An array of block_content (and possibly block_move_target) objects.
d4accfc0 974 */
00a24d44 975 protected function create_block_contents($instances, $output, $region) {
bb46a4fa 976 $results = array();
00a24d44 977
978 $lastweight = 0;
979 $lastblock = 0;
980 if ($this->movingblock) {
981 $first = reset($instances);
982 if ($first) {
983 $lastweight = $first->instance->weight - 2;
984 }
00a24d44 985 }
986
bb46a4fa 987 foreach ($instances as $instance) {
d4a03c00 988 $content = $instance->get_content_for_output($output);
00a24d44 989 if (empty($content)) {
990 continue;
991 }
992
993 if ($this->movingblock && $lastweight != $instance->instance->weight &&
994 $content->blockinstanceid != $this->movingblock && $lastblock != $this->movingblock) {
6671fa73 995 $results[] = new block_move_target($this->get_move_target_url($region, ($lastweight + $instance->instance->weight)/2));
00a24d44 996 }
997
998 if ($content->blockinstanceid == $this->movingblock) {
999 $content->add_class('beingmoved');
1000 $content->annotation .= get_string('movingthisblockcancel', 'block',
75015e5f 1001 html_writer::link($this->page->url, get_string('cancel')));
bb46a4fa 1002 }
00a24d44 1003
1004 $results[] = $content;
1005 $lastweight = $instance->instance->weight;
1006 $lastblock = $instance->instance->id;
1007 }
1008
1009 if ($this->movingblock && $lastblock != $this->movingblock) {
6671fa73 1010 $results[] = new block_move_target($this->get_move_target_url($region, $lastweight + 1));
bb46a4fa 1011 }
1012 return $results;
1013 }
1014
d4accfc0 1015 /**
1016 * Ensure block instances exist for a given region
a19f419d 1017 *
d4accfc0 1018 * @param string $region Check for bi's with the instance with this name
1019 */
bb46a4fa 1020 protected function ensure_instances_exist($region) {
1021 $this->check_region_is_known($region);
1022 if (!array_key_exists($region, $this->blockinstances)) {
1023 $this->blockinstances[$region] =
1024 $this->create_block_instances($this->birecordsbyregion[$region]);
1025 }
1026 }
1027
d4accfc0 1028 /**
1029 * Ensure that there is some content within the given region
1030 *
1031 * @param string $region The name of the region to check
1032 */
59849f79 1033 public function ensure_content_created($region, $output) {
bb46a4fa 1034 $this->ensure_instances_exist($region);
1035 if (!array_key_exists($region, $this->visibleblockcontent)) {
d4a03c00 1036 $contents = array();
1037 if (array_key_exists($region, $this->extracontent)) {
1038 $contents = $this->extracontent[$region];
1039 }
00a24d44 1040 $contents = array_merge($contents, $this->create_block_contents($this->blockinstances[$region], $output, $region));
d4a03c00 1041 if ($region == $this->defaultregion) {
21d33bdf 1042 $addblockui = block_add_block_ui($this->page, $output);
d4a03c00 1043 if ($addblockui) {
1044 $contents[] = $addblockui;
1045 }
1046 }
1047 $this->visibleblockcontent[$region] = $contents;
bb46a4fa 1048 }
1049 }
a19f419d 1050
1051/// Process actions from the URL ===============================================
1052
00a24d44 1053 /**
1054 * Get the appropriate list of editing icons for a block. This is used
1055 * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}.
1056 *
1057 * @param $output The core_renderer to use when generating the output. (Need to get icon paths.)
1058 * @return an array in the format for {@link block_contents::$controls}
1059 */
1060 public function edit_controls($block) {
1061 global $CFG;
1062
1063 $controls = array();
b9bc2019 1064 $actionurl = $this->page->url->out(false, array('sesskey'=> sesskey()));
47332703
JF
1065 $blocktitle = $block->title;
1066 if (empty($blocktitle)) {
1067 $blocktitle = $block->arialabel;
1068 }
00a24d44 1069
6919b90f
SH
1070 if ($this->page->user_can_edit_blocks()) {
1071 // Move icon.
cf69a00a 1072 $str = new lang_string('moveblock', 'block', $blocktitle);
3665af78 1073 $controls[] = new action_menu_link_primary(
e282c679 1074 new moodle_url($actionurl, array('bui_moveid' => $block->instance->id)),
cf69a00a
SH
1075 new pix_icon('t/move', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1076 $str,
1077 array('class' => 'editing_move')
e282c679
SH
1078 );
1079
00a24d44 1080 }
1081
1082 if ($this->page->user_can_edit_blocks() || $block->user_can_edit()) {
1083 // Edit config icon - always show - needed for positioning UI.
cf69a00a 1084 $str = new lang_string('configureblock', 'block', $blocktitle);
803912ae 1085 $controls[] = new action_menu_link_secondary(
e282c679 1086 new moodle_url($actionurl, array('bui_editid' => $block->instance->id)),
cf69a00a
SH
1087 new pix_icon('t/edit', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1088 $str,
1089 array('class' => 'editing_edit')
e282c679
SH
1090 );
1091
00a24d44 1092 }
1093
6919b90f
SH
1094 if ($this->page->user_can_edit_blocks() && $block->instance_can_be_hidden()) {
1095 // Show/hide icon.
1096 if ($block->instance->visible) {
cf69a00a
SH
1097 $str = new lang_string('hideblock', 'block', $blocktitle);
1098 $url = new moodle_url($actionurl, array('bui_hideid' => $block->instance->id));
1099 $icon = new pix_icon('t/hide', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1100 $attributes = array('class' => 'editing_hide');
6919b90f 1101 } else {
cf69a00a
SH
1102 $str = new lang_string('showblock', 'block', $blocktitle);
1103 $url = new moodle_url($actionurl, array('bui_showid' => $block->instance->id));
1104 $icon = new pix_icon('t/show', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1105 $attributes = array('class' => 'editing_show');
6919b90f 1106 }
803912ae 1107 $controls[] = new action_menu_link_secondary($url, $icon, $str, $attributes);
6919b90f
SH
1108 }
1109
1110 // Assign roles icon.
1111 if (has_capability('moodle/role:assign', $block->context)) {
1112 //TODO: please note it is sloppy to pass urls through page parameters!!
1113 // it is shortened because some web servers (e.g. IIS by default) give
1114 // a 'security' error if you try to pass a full URL as a GET parameter in another URL.
1115 $return = $this->page->url->out(false);
1116 $return = str_replace($CFG->wwwroot . '/', '', $return);
1117
e282c679
SH
1118 $rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid'=>$block->context->id,
1119 'returnurl'=>$return));
cf69a00a
SH
1120 // Delete icon.
1121 $str = new lang_string('assignrolesinblock', 'block', $blocktitle);
3665af78 1122 $controls[] = new action_menu_link_secondary(
cf69a00a
SH
1123 $rolesurl,
1124 new pix_icon('t/assignroles', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1125 $str,
1126 array('class' => 'editing_roles')
1127 );
00a24d44 1128 }
803912ae
AN
1129
1130 if ($this->user_can_delete_block($block)) {
1131 // Delete icon.
1132 $str = new lang_string('deleteblock', 'block', $blocktitle);
1133 $controls[] = new action_menu_link_secondary(
1134 new moodle_url($actionurl, array('bui_deleteid' => $block->instance->id)),
1135 new pix_icon('t/delete', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1136 $str,
1137 array('class' => 'editing_delete')
1138 );
1139 }
1140
00a24d44 1141 return $controls;
1142 }
1143
1c76fc96
TH
1144 /**
1145 * @param block_base $block a block that appears on this page.
1146 * @return boolean boolean whether the currently logged in user is allowed to delete this block.
1147 */
1148 protected function user_can_delete_block($block) {
1149 return $this->page->user_can_edit_blocks() && $block->user_can_edit() &&
1150 $block->user_can_addto($this->page) &&
1151 !in_array($block->instance->blockname, self::get_undeletable_block_types());
1152 }
1153
a19f419d 1154 /**
1155 * Process any block actions that were specified in the URL.
1156 *
a19f419d 1157 * @return boolean true if anything was done. False if not.
1158 */
1159 public function process_url_actions() {
00a24d44 1160 if (!$this->page->user_is_editing()) {
1161 return false;
1162 }
a19f419d 1163 return $this->process_url_add() || $this->process_url_delete() ||
00a24d44 1164 $this->process_url_show_hide() || $this->process_url_edit() ||
1165 $this->process_url_move();
a19f419d 1166 }
1167
1168 /**
1169 * Handle adding a block.
1170 * @return boolean true if anything was done. False if not.
1171 */
1172 public function process_url_add() {
aff24313 1173 $blocktype = optional_param('bui_addblock', null, PARAM_PLUGIN);
a19f419d 1174 if (!$blocktype) {
1175 return false;
1176 }
1177
c74eec3b 1178 require_sesskey();
a19f419d 1179
1d7e341e 1180 if (!$this->page->user_can_edit_blocks()) {
a19f419d 1181 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('addblock'));
1182 }
1183
1184 if (!array_key_exists($blocktype, $this->get_addable_blocks())) {
1185 throw new moodle_exception('cannotaddthisblocktype', '', $this->page->url->out(), $blocktype);
1186 }
1187
1188 $this->add_block_at_end_of_default_region($blocktype);
1189
847bed23 1190 // If the page URL was a guess, it will contain the bui_... param, so we must make sure it is not there.
a19f419d 1191 $this->page->ensure_param_not_in_url('bui_addblock');
1192
1193 return true;
1194 }
1195
1196 /**
1197 * Handle deleting a block.
1198 * @return boolean true if anything was done. False if not.
1199 */
1200 public function process_url_delete() {
df5ac8d0 1201 global $CFG, $PAGE, $OUTPUT;
2b7ece00 1202
1e12c120 1203 $blockid = optional_param('bui_deleteid', null, PARAM_INT);
2b7ece00
AG
1204 $confirmdelete = optional_param('bui_confirm', null, PARAM_INT);
1205
a19f419d 1206 if (!$blockid) {
1207 return false;
1208 }
1209
c74eec3b 1210 require_sesskey();
a19f419d 1211 $block = $this->page->blocks->find_instance($blockid);
1c76fc96 1212 if (!$this->user_can_delete_block($block)) {
a19f419d 1213 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('deleteablock'));
1214 }
1215
2b7ece00
AG
1216 if (!$confirmdelete) {
1217 $deletepage = new moodle_page();
1218 $deletepage->set_pagelayout('admin');
1219 $deletepage->set_course($this->page->course);
1220 $deletepage->set_context($this->page->context);
1221 if ($this->page->cm) {
1222 $deletepage->set_cm($this->page->cm);
1223 }
a19f419d 1224
2b7ece00
AG
1225 $deleteurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
1226 $deleteurlparams = $this->page->url->params();
1227 $deletepage->set_url($deleteurlbase, $deleteurlparams);
1228 $deletepage->set_block_actions_done();
1229 // At this point we are either going to redirect, or display the form, so
1230 // overwrite global $PAGE ready for this. (Formslib refers to it.)
1231 $PAGE = $deletepage;
1232 //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that too
1233 $output = $deletepage->get_renderer('core');
1234 $OUTPUT = $output;
1235
1236 $site = get_site();
1237 $blocktitle = $block->get_title();
1238 $strdeletecheck = get_string('deletecheck', 'block', $blocktitle);
1239 $message = get_string('deleteblockcheck', 'block', $blocktitle);
1240
3dd204b1
AG
1241 // If the block is being shown in sub contexts display a warning.
1242 if ($block->instance->showinsubcontexts == 1) {
1243 $parentcontext = context::instance_by_id($block->instance->parentcontextid);
1244 $systemcontext = context_system::instance();
1245 $messagestring = new stdClass();
1246 $messagestring->location = $parentcontext->get_context_name();
1247
1248 // Checking for blocks that may have visibility on the front page and pages added on that.
1249 if ($parentcontext->id != $systemcontext->id && is_inside_frontpage($parentcontext)) {
1250 $messagestring->pagetype = get_string('showonfrontpageandsubs', 'block');
1251 } else {
1252 $pagetypes = generate_page_type_patterns($this->page->pagetype, $parentcontext);
1253 $messagestring->pagetype = $block->instance->pagetypepattern;
1254 if (isset($pagetypes[$block->instance->pagetypepattern])) {
1255 $messagestring->pagetype = $pagetypes[$block->instance->pagetypepattern];
1256 }
1257 }
1258
1259 $message = get_string('deleteblockwarning', 'block', $messagestring);
1260 }
1261
2b7ece00
AG
1262 $PAGE->navbar->add($strdeletecheck);
1263 $PAGE->set_title($blocktitle . ': ' . $strdeletecheck);
1264 $PAGE->set_heading($site->fullname);
1265 echo $OUTPUT->header();
1336e2e1 1266 $confirmurl = new moodle_url($deletepage->url, array('sesskey' => sesskey(), 'bui_deleteid' => $block->instance->id, 'bui_confirm' => 1));
2b7ece00
AG
1267 $cancelurl = new moodle_url($deletepage->url);
1268 $yesbutton = new single_button($confirmurl, get_string('yes'));
1269 $nobutton = new single_button($cancelurl, get_string('no'));
1270 echo $OUTPUT->confirm($message, $yesbutton, $nobutton);
1271 echo $OUTPUT->footer();
1272 // Make sure that nothing else happens after we have displayed this form.
1273 exit;
1274 } else {
1275 blocks_delete_instance($block->instance);
1276 // bui_deleteid and bui_confirm should not be in the PAGE url.
1277 $this->page->ensure_param_not_in_url('bui_deleteid');
1278 $this->page->ensure_param_not_in_url('bui_confirm');
1279 return true;
1280 }
a19f419d 1281 }
1282
1283 /**
1284 * Handle showing or hiding a block.
1285 * @return boolean true if anything was done. False if not.
1286 */
1287 public function process_url_show_hide() {
1e12c120 1288 if ($blockid = optional_param('bui_hideid', null, PARAM_INT)) {
a19f419d 1289 $newvisibility = 0;
1e12c120 1290 } else if ($blockid = optional_param('bui_showid', null, PARAM_INT)) {
a19f419d 1291 $newvisibility = 1;
1292 } else {
1293 return false;
1294 }
1295
c74eec3b 1296 require_sesskey();
a19f419d 1297
1298 $block = $this->page->blocks->find_instance($blockid);
1299
d14edf06 1300 if (!$this->page->user_can_edit_blocks()) {
a19f419d 1301 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('hideshowblocks'));
d8ef60bd
SH
1302 } else if (!$block->instance_can_be_hidden()) {
1303 return false;
a19f419d 1304 }
1305
1306 blocks_set_visibility($block->instance, $this->page, $newvisibility);
1307
1308 // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1309 $this->page->ensure_param_not_in_url('bui_hideid');
1310 $this->page->ensure_param_not_in_url('bui_showid');
1311
1312 return true;
1313 }
1314
1315 /**
1316 * Handle showing/processing the submission from the block editing form.
1317 * @return boolean true if the form was submitted and the new config saved. Does not
1318 * return if the editing form was displayed. False otherwise.
1319 */
1320 public function process_url_edit() {
0f63f271 1321 global $CFG, $DB, $PAGE, $OUTPUT;
a19f419d 1322
1e12c120 1323 $blockid = optional_param('bui_editid', null, PARAM_INT);
a19f419d 1324 if (!$blockid) {
1325 return false;
1326 }
1327
c74eec3b 1328 require_sesskey();
a19f419d 1329 require_once($CFG->dirroot . '/blocks/edit_form.php');
1330
1331 $block = $this->find_instance($blockid);
1332
d14edf06 1333 if (!$block->user_can_edit() && !$this->page->user_can_edit_blocks()) {
a19f419d 1334 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1335 }
1336
1337 $editpage = new moodle_page();
3c14795a 1338 $editpage->set_pagelayout('admin');
a19f419d 1339 $editpage->set_course($this->page->course);
b1627a92
DC
1340 //$editpage->set_context($block->context);
1341 $editpage->set_context($this->page->context);
88f77c3c
SH
1342 if ($this->page->cm) {
1343 $editpage->set_cm($this->page->cm);
1344 }
24e4119a 1345 $editurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
a19f419d 1346 $editurlparams = $this->page->url->params();
1347 $editurlparams['bui_editid'] = $blockid;
1348 $editpage->set_url($editurlbase, $editurlparams);
05c92729 1349 $editpage->set_block_actions_done();
a19f419d 1350 // At this point we are either going to redirect, or display the form, so
1351 // overwrite global $PAGE ready for this. (Formslib refers to it.)
1352 $PAGE = $editpage;
0f63f271
AD
1353 //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that to
1354 $output = $editpage->get_renderer('core');
1355 $OUTPUT = $output;
a19f419d 1356
1357 $formfile = $CFG->dirroot . '/blocks/' . $block->name() . '/edit_form.php';
1358 if (is_readable($formfile)) {
1359 require_once($formfile);
1360 $classname = 'block_' . $block->name() . '_edit_form';
34a988e2
MD
1361 if (!class_exists($classname)) {
1362 $classname = 'block_edit_form';
1363 }
a19f419d 1364 } else {
1365 $classname = 'block_edit_form';
1366 }
1367
1368 $mform = new $classname($editpage->url, $block, $this->page);
1369 $mform->set_data($block->instance);
1370
1371 if ($mform->is_cancelled()) {
1372 redirect($this->page->url);
1373
1374 } else if ($data = $mform->get_data()) {
1375 $bi = new stdClass;
1376 $bi->id = $block->instance->id;
f187622a
TH
1377
1378 // This may get overwritten by the special case handling below.
a19f419d 1379 $bi->pagetypepattern = $data->bui_pagetypepattern;
67cc8019 1380 $bi->showinsubcontexts = (bool) $data->bui_contexts;
a19f419d 1381 if (empty($data->bui_subpagepattern) || $data->bui_subpagepattern == '%@NULL@%') {
1382 $bi->subpagepattern = null;
1383 } else {
1384 $bi->subpagepattern = $data->bui_subpagepattern;
1385 }
0aed347f 1386
b0c6dc1c
AG
1387 $systemcontext = context_system::instance();
1388 $frontpagecontext = context_course::instance(SITEID);
d197ea43 1389 $parentcontext = context::instance_by_id($data->bui_parentcontextid);
0aed347f
MD
1390
1391 // Updating stickiness and contexts. See MDL-21375 for details.
1392 if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination
0aed347f 1393
d4e71a4e 1394 // Explicitly set the default context
0aed347f
MD
1395 $bi->parentcontextid = $parentcontext->id;
1396
192a3380 1397 if ($data->bui_editingatfrontpage) { // The block is being edited on the front page
0aed347f 1398
192a3380
MD
1399 // The interface here is a special case because the pagetype pattern is
1400 // totally derived from the context menu. Here are the excpetions. MDL-30340
0aed347f 1401
192a3380
MD
1402 switch ($data->bui_contexts) {
1403 case BUI_CONTEXTS_ENTIRE_SITE:
1404 // The user wants to show the block across the entire site
1405 $bi->parentcontextid = $systemcontext->id;
1406 $bi->showinsubcontexts = true;
d4e71a4e 1407 $bi->pagetypepattern = '*';
192a3380
MD
1408 break;
1409 case BUI_CONTEXTS_FRONTPAGE_SUBS:
1410 // The user wants the block shown on the front page and all subcontexts
1411 $bi->parentcontextid = $frontpagecontext->id;
1412 $bi->showinsubcontexts = true;
d4e71a4e 1413 $bi->pagetypepattern = '*';
192a3380
MD
1414 break;
1415 case BUI_CONTEXTS_FRONTPAGE_ONLY:
1416 // The user want to show the front page on the frontpage only
1417 $bi->parentcontextid = $frontpagecontext->id;
1418 $bi->showinsubcontexts = false;
d4e71a4e 1419 $bi->pagetypepattern = 'site-index';
192a3380
MD
1420 // This is the only relevant page type anyway but we'll set it explicitly just
1421 // in case the front page grows site-index-* subpages of its own later
1422 break;
0aed347f 1423 }
02ba576c
MD
1424 }
1425 }
0aed347f 1426
b1627a92
DC
1427 $bits = explode('-', $bi->pagetypepattern);
1428 // hacks for some contexts
1429 if (($parentcontext->contextlevel == CONTEXT_COURSE) && ($parentcontext->instanceid != SITEID)) {
1430 // For course context
1431 // is page type pattern is mod-*, change showinsubcontext to 1
1432 if ($bits[0] == 'mod' || $bi->pagetypepattern == '*') {
1433 $bi->showinsubcontexts = 1;
1434 } else {
1435 $bi->showinsubcontexts = 0;
1436 }
1437 } else if ($parentcontext->contextlevel == CONTEXT_USER) {
1438 // for user context
1439 // subpagepattern should be null
1440 if ($bits[0] == 'user' or $bits[0] == 'my') {
1441 // we don't need subpagepattern in usercontext
1442 $bi->subpagepattern = null;
1443 }
1444 }
1445
a19f419d 1446 $bi->defaultregion = $data->bui_defaultregion;
1447 $bi->defaultweight = $data->bui_defaultweight;
1448 $DB->update_record('block_instances', $bi);
1449
a23bbaa3 1450 if (!empty($block->config)) {
1451 $config = clone($block->config);
1452 } else {
1453 $config = new stdClass;
1454 }
a19f419d 1455 foreach ($data as $configfield => $value) {
1456 if (strpos($configfield, 'config_') !== 0) {
1457 continue;
1458 }
1459 $field = substr($configfield, 7);
1460 $config->$field = $value;
1461 }
1462 $block->instance_config_save($config);
1463
1464 $bp = new stdClass;
1465 $bp->visible = $data->bui_visible;
1466 $bp->region = $data->bui_region;
1467 $bp->weight = $data->bui_weight;
1468 $needbprecord = !$data->bui_visible || $data->bui_region != $data->bui_defaultregion ||
1469 $data->bui_weight != $data->bui_defaultweight;
1470
1471 if ($block->instance->blockpositionid && !$needbprecord) {
1472 $DB->delete_records('block_positions', array('id' => $block->instance->blockpositionid));
1473
1474 } else if ($block->instance->blockpositionid && $needbprecord) {
1475 $bp->id = $block->instance->blockpositionid;
1476 $DB->update_record('block_positions', $bp);
1477
1478 } else if ($needbprecord) {
1479 $bp->blockinstanceid = $block->instance->id;
a23bbaa3 1480 $bp->contextid = $this->page->context->id;
a19f419d 1481 $bp->pagetype = $this->page->pagetype;
1482 if ($this->page->subpage) {
1483 $bp->subpage = $this->page->subpage;
1484 } else {
a23bbaa3 1485 $bp->subpage = '';
a19f419d 1486 }
1487 $DB->insert_record('block_positions', $bp);
1488 }
1489
1490 redirect($this->page->url);
1491
1492 } else {
69c14bbd 1493 $strheading = get_string('blockconfiga', 'moodle', $block->get_title());
a19f419d 1494 $editpage->set_title($strheading);
1495 $editpage->set_heading($strheading);
b1627a92
DC
1496 $bits = explode('-', $this->page->pagetype);
1497 if ($bits[0] == 'tag' && !empty($this->page->subpage)) {
1498 // better navbar for tag pages
1499 $editpage->navbar->add(get_string('tags'), new moodle_url('/tag/'));
1500 $tag = tag_get('id', $this->page->subpage, '*');
1501 // tag search page doesn't have subpageid
1502 if ($tag) {
1503 $editpage->navbar->add($tag->name, new moodle_url('/tag/index.php', array('id'=>$tag->id)));
1504 }
1505 }
1506 $editpage->navbar->add($block->get_title());
1507 $editpage->navbar->add(get_string('configuration'));
a19f419d 1508 echo $output->header();
1509 echo $output->heading($strheading, 2);
1510 $mform->display();
1511 echo $output->footer();
1512 exit;
1513 }
1514 }
00a24d44 1515
1516 /**
1517 * Handle showing/processing the submission from the block editing form.
1518 * @return boolean true if the form was submitted and the new config saved. Does not
1519 * return if the editing form was displayed. False otherwise.
1520 */
1521 public function process_url_move() {
1522 global $CFG, $DB, $PAGE;
1523
1e12c120 1524 $blockid = optional_param('bui_moveid', null, PARAM_INT);
00a24d44 1525 if (!$blockid) {
1526 return false;
1527 }
1528
c74eec3b 1529 require_sesskey();
00a24d44 1530
1531 $block = $this->find_instance($blockid);
1532
1533 if (!$this->page->user_can_edit_blocks()) {
1534 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1535 }
1536
1537 $newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT);
1538 $newweight = optional_param('bui_newweight', null, PARAM_FLOAT);
1539 if (!$newregion || is_null($newweight)) {
1540 // Don't have a valid target position yet, must be just starting the move.
1541 $this->movingblock = $blockid;
1542 $this->page->ensure_param_not_in_url('bui_moveid');
1543 return false;
1544 }
1545
2cdb8d84 1546 if (!$this->is_known_region($newregion)) {
1547 throw new moodle_exception('unknownblockregion', '', $this->page->url, $newregion);
1548 }
1549
1550 // Move this block. This may involve moving other nearby blocks.
1551 $blocks = $this->birecordsbyregion[$newregion];
1552
f4e6a86e 1553 $maxweight = self::MAX_WEIGHT;
1554 $minweight = -self::MAX_WEIGHT;
1555
1556 // Initialise the used weights and spareweights array with the default values
2cdb8d84 1557 $spareweights = array();
1558 $usedweights = array();
f4e6a86e 1559 for ($i = $minweight; $i <= $maxweight; $i++) {
2cdb8d84 1560 $spareweights[$i] = $i;
1561 $usedweights[$i] = array();
1562 }
f4e6a86e 1563
1564 // Check each block and sort out where we have used weights
2cdb8d84 1565 foreach ($blocks as $bi) {
f4e6a86e 1566 if ($bi->weight > $maxweight) {
1567 // If this statement is true then the blocks weight is more than the
1568 // current maximum. To ensure that we can get the best block position
1569 // we will initialise elements within the usedweights and spareweights
1570 // arrays between the blocks weight (which will then be the new max) and
1571 // the current max
1572 $parseweight = $bi->weight;
1573 while (!array_key_exists($parseweight, $usedweights)) {
1574 $usedweights[$parseweight] = array();
1575 $spareweights[$parseweight] = $parseweight;
1576 $parseweight--;
1577 }
1578 $maxweight = $bi->weight;
1579 } else if ($bi->weight < $minweight) {
1580 // As above except this time the blocks weight is LESS than the
1581 // the current minimum, so we will initialise the array from the
1582 // blocks weight (new minimum) to the current minimum
1583 $parseweight = $bi->weight;
1584 while (!array_key_exists($parseweight, $usedweights)) {
1585 $usedweights[$parseweight] = array();
1586 $spareweights[$parseweight] = $parseweight;
1587 $parseweight++;
1588 }
1589 $minweight = $bi->weight;
1590 }
1591 if ($bi->id != $block->instance->id) {
1592 unset($spareweights[$bi->weight]);
1593 $usedweights[$bi->weight][] = $bi->id;
2cdb8d84 1594 }
2cdb8d84 1595 }
1596
f4e6a86e 1597 // First we find the nearest gap in the list of weights.
2cdb8d84 1598 $bestdistance = max(abs($newweight - self::MAX_WEIGHT), abs($newweight + self::MAX_WEIGHT)) + 1;
1599 $bestgap = null;
1600 foreach ($spareweights as $spareweight) {
1601 if (abs($newweight - $spareweight) < $bestdistance) {
1602 $bestdistance = abs($newweight - $spareweight);
1603 $bestgap = $spareweight;
1604 }
1605 }
1606
1607 // If there is no gap, we have to go outside -self::MAX_WEIGHT .. self::MAX_WEIGHT.
1608 if (is_null($bestgap)) {
1609 $bestgap = self::MAX_WEIGHT + 1;
1610 while (!empty($usedweights[$bestgap])) {
1611 $bestgap++;
1612 }
1613 }
1614
1615 // Now we know the gap we are aiming for, so move all the blocks along.
1616 if ($bestgap < $newweight) {
1617 $newweight = floor($newweight);
1618 for ($weight = $bestgap + 1; $weight <= $newweight; $weight++) {
1619 foreach ($usedweights[$weight] as $biid) {
1620 $this->reposition_block($biid, $newregion, $weight - 1);
1621 }
1622 }
1623 $this->reposition_block($block->instance->id, $newregion, $newweight);
1624 } else {
1625 $newweight = ceil($newweight);
1626 for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {
7728860a 1627 if (array_key_exists($weight, $usedweights)) {
1628 foreach ($usedweights[$weight] as $biid) {
1629 $this->reposition_block($biid, $newregion, $weight + 1);
1630 }
2cdb8d84 1631 }
1632 }
1633 $this->reposition_block($block->instance->id, $newregion, $newweight);
1634 }
6f5e0852 1635
00a24d44 1636 $this->page->ensure_param_not_in_url('bui_moveid');
1637 $this->page->ensure_param_not_in_url('bui_newregion');
1638 $this->page->ensure_param_not_in_url('bui_newweight');
1639 return true;
1640 }
56ed242b
SH
1641
1642 /**
1643 * Turns the display of normal blocks either on or off.
78bfb562 1644 *
56ed242b
SH
1645 * @param bool $setting
1646 */
1647 public function show_only_fake_blocks($setting = true) {
1648 $this->fakeblocksonly = $setting;
1649 }
86b5ea0f 1650}
1651
08eab897 1652/// Helper functions for working with block classes ============================
1653
1654/**
847bed23 1655 * Call a class method (one that does not require a block instance) on a block class.
d4accfc0 1656 *
08eab897 1657 * @param string $blockname the name of the block.
1658 * @param string $method the method name.
1659 * @param array $param parameters to pass to the method.
1660 * @return mixed whatever the method returns.
1661 */
11306331 1662function block_method_result($blockname, $method, $param = NULL) {
0f3fe4b6 1663 if(!block_load_class($blockname)) {
1664 return NULL;
1665 }
11306331 1666 return call_user_func(array('block_'.$blockname, $method), $param);
0f3fe4b6 1667}
1668
08eab897 1669/**
365a5941 1670 * Creates a new instance of the specified block class.
d4accfc0 1671 *
08eab897 1672 * @param string $blockname the name of the block.
1673 * @param $instance block_instances DB table row (optional).
bb46a4fa 1674 * @param moodle_page $page the page this block is appearing on.
08eab897 1675 * @return block_base the requested block instance.
1676 */
bb46a4fa 1677function block_instance($blockname, $instance = NULL, $page = NULL) {
0f3fe4b6 1678 if(!block_load_class($blockname)) {
1679 return false;
1680 }
e89d741a 1681 $classname = 'block_'.$blockname;
f032aa7a 1682 $retval = new $classname;
9b4b78fd 1683 if($instance !== NULL) {
bb46a4fa 1684 if (is_null($page)) {
1685 global $PAGE;
1686 $page = $PAGE;
1687 }
1688 $retval->_load_instance($instance, $page);
9b4b78fd 1689 }
1690 return $retval;
0f3fe4b6 1691}
1692
08eab897 1693/**
1694 * Load the block class for a particular type of block.
d4accfc0 1695 *
08eab897 1696 * @param string $blockname the name of the block.
1697 * @return boolean success or failure.
1698 */
0f3fe4b6 1699function block_load_class($blockname) {
1700 global $CFG;
1701
a9033ad5 1702 if(empty($blockname)) {
c7a9e293 1703 return false;
1704 }
1705
e89d741a 1706 $classname = 'block_'.$blockname;
a9033ad5 1707
1708 if(class_exists($classname)) {
1709 return true;
1710 }
1711
d836aa4b 1712 $blockpath = $CFG->dirroot.'/blocks/'.$blockname.'/block_'.$blockname.'.php';
1713
1714 if (file_exists($blockpath)) {
1715 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
1716 include_once($blockpath);
1717 }else{
15822fe2 1718 //debugging("$blockname code does not exist in $blockpath", DEBUG_DEVELOPER);
d836aa4b 1719 return false;
1720 }
0f3fe4b6 1721
0f3fe4b6 1722 return class_exists($classname);
1723}
1724
1d13c75c 1725/**
1726 * Given a specific page type, return all the page type patterns that might
1727 * match it.
1728 *
1729 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1730 * @return array an array of all the page type patterns that might match this page type.
1731 */
1732function matching_page_type_patterns($pagetype) {
1733 $patterns = array($pagetype);
1734 $bits = explode('-', $pagetype);
1735 if (count($bits) == 3 && $bits[0] == 'mod') {
1736 if ($bits[2] == 'view') {
1737 $patterns[] = 'mod-*-view';
1738 } else if ($bits[2] == 'index') {
1739 $patterns[] = 'mod-*-index';
1740 }
1741 }
1742 while (count($bits) > 0) {
1743 $patterns[] = implode('-', $bits) . '-*';
1744 array_pop($bits);
1745 }
4d74c876 1746 $patterns[] = '*';
1d13c75c 1747 return $patterns;
1748}
1749
b1627a92
DC
1750/**
1751 * Given a specific page type, parent context and currect context, return all the page type patterns
1752 * that might be used by this block.
1753 *
1754 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1755 * @param stdClass $parentcontext Block's parent context
1756 * @param stdClass $currentcontext Current context of block
1757 * @return array an array of all the page type patterns that might match this page type.
1758 */
1759function generate_page_type_patterns($pagetype, $parentcontext = null, $currentcontext = null) {
9e19a0f0 1760 global $CFG; // Required for includes bellow.
32a510de 1761
b1627a92 1762 $bits = explode('-', $pagetype);
32a510de 1763
9e19a0f0
PS
1764 $core = core_component::get_core_subsystems();
1765 $plugins = core_component::get_plugin_types();
32a510de 1766
b822fc85 1767 //progressively strip pieces off the page type looking for a match
b822fc85 1768 $componentarray = null;
b38e2e28
AD
1769 for ($i = count($bits); $i > 0; $i--) {
1770 $possiblecomponentarray = array_slice($bits, 0, $i);
1771 $possiblecomponent = implode('', $possiblecomponentarray);
b822fc85 1772
b38e2e28
AD
1773 // Check to see if the component is a core component
1774 if (array_key_exists($possiblecomponent, $core) && !empty($core[$possiblecomponent])) {
9e19a0f0 1775 $libfile = $core[$possiblecomponent].'/lib.php';
b822fc85
AD
1776 if (file_exists($libfile)) {
1777 require_once($libfile);
b38e2e28
AD
1778 $function = $possiblecomponent.'_page_type_list';
1779 if (function_exists($function)) {
1780 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1781 break;
1782 }
b822fc85 1783 }
b1627a92 1784 }
32a510de 1785 }
b822fc85 1786
b38e2e28
AD
1787 //check the plugin directory and look for a callback
1788 if (array_key_exists($possiblecomponent, $plugins) && !empty($plugins[$possiblecomponent])) {
1789
1790 //We've found a plugin type. Look for a plugin name by getting the next section of page type
1791 if (count($bits) > $i) {
1792 $pluginname = $bits[$i];
9e19a0f0 1793 $directory = core_component::get_plugin_directory($possiblecomponent, $pluginname);
b38e2e28
AD
1794 if (!empty($directory)){
1795 $libfile = $directory.'/lib.php';
1796 if (file_exists($libfile)) {
1797 require_once($libfile);
2a1ce6a8
PS
1798 $function = $possiblecomponent.'_'.$pluginname.'_page_type_list';
1799 if (!function_exists($function)) {
1800 $function = $pluginname.'_page_type_list';
1801 }
b38e2e28
AD
1802 if (function_exists($function)) {
1803 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1804 break;
1805 }
1806 }
1807 }
1808 }
1809 }
1810
1811 //we'll only get to here if we still don't have any patterns
1812 //the plugin type may have a callback
9e19a0f0
PS
1813 $directory = $plugins[$possiblecomponent];
1814 $libfile = $directory.'/lib.php';
1815 if (file_exists($libfile)) {
1816 require_once($libfile);
1817 $function = $possiblecomponent.'_page_type_list';
1818 if (function_exists($function)) {
1819 if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1820 break;
b38e2e28
AD
1821 }
1822 }
b822fc85 1823 }
32a510de
SH
1824 }
1825 }
b822fc85 1826
32a510de 1827 if (empty($patterns)) {
b38e2e28 1828 $patterns = default_page_type_list($pagetype, $parentcontext, $currentcontext);
b1627a92 1829 }
b38e2e28 1830
b2221ee5
EL
1831 // Ensure that the * pattern is always available if editing block 'at distance', so
1832 // we always can 'bring back' it to the original context. MDL-30340
b85b25eb 1833 if ((!isset($currentcontext) or !isset($parentcontext) or $currentcontext->id != $parentcontext->id) && !isset($patterns['*'])) {
b2221ee5
EL
1834 // TODO: We could change the string here, showing its 'bring back' meaning
1835 $patterns['*'] = get_string('page-x', 'pagetype');
1836 }
1837
32a510de
SH
1838 return $patterns;
1839}
b1627a92 1840
32a510de
SH
1841/**
1842 * Generates a default page type list when a more appropriate callback cannot be decided upon.
1843 *
1844 * @param string $pagetype
1845 * @param stdClass $parentcontext
1846 * @param stdClass $currentcontext
1847 * @return array
1848 */
b38e2e28 1849function default_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
b1627a92
DC
1850 // Generate page type patterns based on current page type if
1851 // callbacks haven't been defined
32a510de
SH
1852 $patterns = array($pagetype => $pagetype);
1853 $bits = explode('-', $pagetype);
b1627a92
DC
1854 while (count($bits) > 0) {
1855 $pattern = implode('-', $bits) . '-*';
1856 $pagetypestringname = 'page-'.str_replace('*', 'x', $pattern);
1857 // guessing page type description
1858 if (get_string_manager()->string_exists($pagetypestringname, 'pagetype')) {
1859 $patterns[$pattern] = get_string($pagetypestringname, 'pagetype');
1860 } else {
1861 $patterns[$pattern] = $pattern;
1862 }
1863 array_pop($bits);
1864 }
1865 $patterns['*'] = get_string('page-x', 'pagetype');
1866 return $patterns;
1867}
1868
84a1bea9
AD
1869/**
1870 * Generates the page type list for the my moodle page
1871 *
1872 * @param string $pagetype
1873 * @param stdClass $parentcontext
1874 * @param stdClass $currentcontext
1875 * @return array
1876 */
b38e2e28 1877function my_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
49ae1fdc 1878 return array('my-index' => get_string('page-my-index', 'pagetype'));
84a1bea9
AD
1879}
1880
32a510de
SH
1881/**
1882 * Generates the page type list for a module by either locating and using the modules callback
1883 * or by generating a default list.
1884 *
1885 * @param string $pagetype
1886 * @param stdClass $parentcontext
1887 * @param stdClass $currentcontext
1888 * @return array
1889 */
b38e2e28
AD
1890function mod_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1891 $patterns = plugin_page_type_list($pagetype, $parentcontext, $currentcontext);
32a510de
SH
1892 if (empty($patterns)) {
1893 // if modules don't have callbacks
1894 // generate two default page type patterns for modules only
1895 $bits = explode('-', $pagetype);
1896 $patterns = array($pagetype => $pagetype);
1897 if ($bits[2] == 'view') {
1898 $patterns['mod-*-view'] = get_string('page-mod-x-view', 'pagetype');
1899 } else if ($bits[2] == 'index') {
1900 $patterns['mod-*-index'] = get_string('page-mod-x-index', 'pagetype');
1901 }
1902 }
1903 return $patterns;
1904}
21d33bdf 1905/// Functions update the blocks if required by the request parameters ==========
1906
1907/**
1908 * Return a {@link block_contents} representing the add a new block UI, if
1909 * this user is allowed to see it.
1910 *
1911 * @return block_contents an appropriate block_contents, or null if the user
1912 * cannot add any blocks here.
1913 */
1914function block_add_block_ui($page, $output) {
d81b05e7 1915 global $CFG, $OUTPUT;
21d33bdf 1916 if (!$page->user_is_editing() || !$page->user_can_edit_blocks()) {
1917 return null;
1918 }
1919
1920 $bc = new block_contents();
1921 $bc->title = get_string('addblock');
1922 $bc->add_class('block_adminblock');
a69a7e89 1923 $bc->attributes['data-block'] = 'adminblock';
21d33bdf 1924
a2789e34 1925 $missingblocks = $page->blocks->get_addable_blocks();
21d33bdf 1926 if (empty($missingblocks)) {
a2789e34 1927 $bc->content = get_string('noblockstoaddhere');
21d33bdf 1928 return $bc;
1929 }
1930
1931 $menu = array();
a2789e34 1932 foreach ($missingblocks as $block) {
21d33bdf 1933 $blockobject = block_instance($block->name);
1934 if ($blockobject !== false && $blockobject->user_can_addto($page)) {
1935 $menu[$block->name] = $blockobject->get_title();
1936 }
1937 }
2f1e464a 1938 core_collator::asort($menu);
21d33bdf 1939
8afba50b 1940 $actionurl = new moodle_url($page->url, array('sesskey'=>sesskey()));
f8dab966 1941 $select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');
e9904e09 1942 $select->set_label(get_string('addblock'), array('class'=>'accesshide'));
f8dab966 1943 $bc->content = $OUTPUT->render($select);
21d33bdf 1944 return $bc;
1945}
1946
21d33bdf 1947// Functions that have been deprecated by block_manager =======================
f032aa7a 1948
08eab897 1949/**
a2789e34 1950 * @deprecated since Moodle 2.0 - use $page->blocks->get_addable_blocks();
d4accfc0 1951 *
08eab897 1952 * This function returns an array with the IDs of any blocks that you can add to your page.
1953 * Parameters are passed by reference for speed; they are not modified at all.
d4accfc0 1954 *
08eab897 1955 * @param $page the page object.
bb46a4fa 1956 * @param $blockmanager Not used.
08eab897 1957 * @return array of block type ids.
1958 */
bb46a4fa 1959function blocks_get_missing(&$page, &$blockmanager) {
a2789e34 1960 debugging('blocks_get_missing is deprecated. Please use $page->blocks->get_addable_blocks() instead.', DEBUG_DEVELOPER);
1961 $blocks = $page->blocks->get_addable_blocks();
1962 $ids = array();
1963 foreach ($blocks as $block) {
1964 $ids[] = $block->id;
1965 }
1966 return $ids;
f032aa7a 1967}
1968
bb46a4fa 1969/**
1970 * Actually delete from the database any blocks that are currently on this page,
1971 * but which should not be there according to blocks_name_allowed_in_format.
d4accfc0 1972 *
847bed23 1973 * @todo Write/Fix this function. Currently returns immediately
c679c358 1974 * @param $course
bb46a4fa 1975 */
c679c358 1976function blocks_remove_inappropriate($course) {
bb46a4fa 1977 // TODO
1978 return;
7604f5c1 1979 /*
bb46a4fa 1980 $blockmanager = blocks_get_by_page($page);
f032aa7a 1981
78946b9b 1982 if (empty($blockmanager)) {
f032aa7a 1983 return;
1984 }
1985
78946b9b 1986 if (($pageformat = $page->pagetype) == NULL) {
f032aa7a 1987 return;
1988 }
1989
f2c6739c 1990 foreach($blockmanager as $region) {
1991 foreach($region as $instance) {
f032aa7a 1992 $block = blocks_get_record($instance->blockid);
5bbbe0be 1993 if(!blocks_name_allowed_in_format($block->name, $pageformat)) {
a2789e34 1994 blocks_delete_instance($instance->instance);
f032aa7a 1995 }
1996 }
7604f5c1 1997 }*/
f032aa7a 1998}
1999
d4accfc0 2000/**
2001 * Check that a given name is in a permittable format
2002 *
2003 * @param string $name
2004 * @param string $pageformat
2005 * @return bool
2006 */
5bbbe0be 2007function blocks_name_allowed_in_format($name, $pageformat) {
cd2bc3c9 2008 $accept = NULL;
2009 $maxdepth = -1;
f20edd52
PS
2010 if (!$bi = block_instance($name)) {
2011 return false;
2012 }
2013
2014 $formats = $bi->applicable_formats();
cd2bc3c9 2015 if (!$formats) {
2016 $formats = array();
2017 }
2018 foreach ($formats as $format => $allowed) {
2019 $formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';
2020 $depth = substr_count($format, '-');
2021 if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {
2022 $maxdepth = $depth;
2023 $accept = $allowed;
5bbbe0be 2024 }
2025 }
cd2bc3c9 2026 if ($accept === NULL) {
5bbbe0be 2027 $accept = !empty($formats['all']);
2028 }
2029 return $accept;
2030}
2031
feed1900 2032/**
2033 * Delete a block, and associated data.
d4accfc0 2034 *
feed1900 2035 * @param object $instance a row from the block_instances table
847bed23 2036 * @param bool $nolongerused legacy parameter. Not used, but kept for backwards compatibility.
d4accfc0 2037 * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.
feed1900 2038 */
2039function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {
f4d38d20 2040 global $DB;
2041
2042 if ($block = block_instance($instance->blockname, $instance)) {
feed1900 2043 $block->instance_delete();
2044 }
c592eea2 2045 context_helper::delete_instance(CONTEXT_BLOCK, $instance->id);
f032aa7a 2046
feed1900 2047 if (!$skipblockstables) {
2048 $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id));
2049 $DB->delete_records('block_instances', array('id' => $instance->id));
e2f4557a 2050 $DB->delete_records_list('user_preferences', 'name', array('block'.$instance->id.'hidden','docked_block_instance_'.$instance->id));
b33dd23a 2051 }
feed1900 2052}
b33dd23a 2053
feed1900 2054/**
2055 * Delete all the blocks that belong to a particular context.
d4accfc0 2056 *
d4accfc0 2057 * @param int $contextid the context id.
feed1900 2058 */
2059function blocks_delete_all_for_context($contextid) {
2060 global $DB;
a2789e34 2061 $instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));
feed1900 2062 foreach ($instances as $instance) {
2063 blocks_delete_instance($instance, true);
0d6b9d4f 2064 }
feed1900 2065 $instances->close();
13a0d3d3 2066 $DB->delete_records('block_instances', array('parentcontextid' => $contextid));
feed1900 2067 $DB->delete_records('block_positions', array('contextid' => $contextid));
f032aa7a 2068}
2069
ae42ff6f 2070/**
2071 * Set a block to be visible or hidden on a particular page.
2072 *
2073 * @param object $instance a row from the block_instances, preferably LEFT JOINed with the
2074 * block_positions table as return by block_manager.
2075 * @param moodle_page $page the back to set the visibility with respect to.
2076 * @param integer $newvisibility 1 for visible, 0 for hidden.
2077 */
2078function blocks_set_visibility($instance, $page, $newvisibility) {
2079 global $DB;
2080 if (!empty($instance->blockpositionid)) {
2081 // Already have local information on this page.
2082 $DB->set_field('block_positions', 'visible', $newvisibility, array('id' => $instance->blockpositionid));
2083 return;
2084 }
2085
2086 // Create a new block_positions record.
2087 $bp = new stdClass;
2088 $bp->blockinstanceid = $instance->id;
2089 $bp->contextid = $page->context->id;
2090 $bp->pagetype = $page->pagetype;
2091 if ($page->subpage) {
2092 $bp->subpage = $page->subpage;
2093 }
2094 $bp->visible = $newvisibility;
2095 $bp->region = $instance->defaultregion;
2096 $bp->weight = $instance->defaultweight;
2097 $DB->insert_record('block_positions', $bp);
2098}
2099
d4accfc0 2100/**
d4a03c00 2101 * @deprecated since 2.0
2102 * Delete all the blocks from a particular page.
d4accfc0 2103 *
d4a03c00 2104 * @param string $pagetype the page type.
2105 * @param integer $pageid the page id.
2106 * @return bool success or failure.
d4accfc0 2107 */
d4a03c00 2108function blocks_delete_all_on_page($pagetype, $pageid) {
2109 global $DB;
2110
2111 debugging('Call to deprecated function blocks_delete_all_on_page. ' .
2112 'This function cannot work any more. Doing nothing. ' .
2113 'Please update your code to use a block_manager method $PAGE->blocks->....', DEBUG_DEVELOPER);
2114 return false;
0f3fe4b6 2115}
2116
08eab897 2117/**
847bed23 2118 * Get the block record for a particular blockid - that is, a particular type os block.
d4accfc0 2119 *
d4accfc0 2120 * @param $int blockid block type id. If null, an array of all block types is returned.
2121 * @param bool $notusedanymore No longer used.
08eab897 2122 * @return array|object row from block table, or all rows.
2123 */
2124function blocks_get_record($blockid = NULL, $notusedanymore = false) {
2125 global $PAGE;
2126 $blocks = $PAGE->blocks->get_installed_blocks();
2127 if ($blockid === NULL) {
2128 return $blocks;
2129 } else if (isset($blocks[$blockid])) {
2130 return $blocks[$blockid];
2131 } else {
2132 return false;
9b4b78fd 2133 }
9b4b78fd 2134}
2135
d4accfc0 2136/**
2137 * Find a given block by its blockid within a provide array
2138 *
2139 * @param int $blockid
2140 * @param array $blocksarray
2141 * @return bool|object Instance if found else false
2142 */
9b4b78fd 2143function blocks_find_block($blockid, $blocksarray) {
0d6b9d4f 2144 if (empty($blocksarray)) {
2145 return false;
2146 }
9b4b78fd 2147 foreach($blocksarray as $blockgroup) {
0d6b9d4f 2148 if (empty($blockgroup)) {
2149 continue;
2150 }
9b4b78fd 2151 foreach($blockgroup as $instance) {
2152 if($instance->blockid == $blockid) {
2153 return $instance;
2154 }
2155 }
2156 }
2157 return false;
2158}
2159
d4a03c00 2160// Functions for programatically adding default blocks to pages ================
0f3fe4b6 2161
9d1d606e 2162/**
2163 * Parse a list of default blocks. See config-dist for a description of the format.
d4accfc0 2164 *
9d1d606e 2165 * @param string $blocksstr
2166 * @return array
2167 */
2168function blocks_parse_default_blocks_list($blocksstr) {
f474a4e5 2169 $blocks = array();
2170 $bits = explode(':', $blocksstr);
2171 if (!empty($bits)) {
7d2a0492 2172 $leftbits = trim(array_shift($bits));
2173 if ($leftbits != '') {
2174 $blocks[BLOCK_POS_LEFT] = explode(',', $leftbits);
2175 }
f474a4e5 2176 }
2177 if (!empty($bits)) {
7d2a0492 2178 $rightbits =trim(array_shift($bits));
2179 if ($rightbits != '') {
2180 $blocks[BLOCK_POS_RIGHT] = explode(',', $rightbits);
2181 }
f474a4e5 2182 }
2183 return $blocks;
9d1d606e 2184}
5b224948 2185
9d1d606e 2186/**
2187 * @return array the blocks that should be added to the site course by default.
2188 */
2189function blocks_get_default_site_course_blocks() {
2190 global $CFG;
9b4b78fd 2191
9d1d606e 2192 if (!empty($CFG->defaultblocks_site)) {
f474a4e5 2193 return blocks_parse_default_blocks_list($CFG->defaultblocks_site);
9d1d606e 2194 } else {
f474a4e5 2195 return array(
7d2a0492 2196 BLOCK_POS_LEFT => array('site_main_menu'),
9d1d606e 2197 BLOCK_POS_RIGHT => array('course_summary', 'calendar_month')
2198 );
9b4b78fd 2199 }
9d1d606e 2200}
2201
2202/**
2203 * Add the default blocks to a course.
d4accfc0 2204 *
9d1d606e 2205 * @param object $course a course object.
2206 */
2207function blocks_add_default_course_blocks($course) {
2208 global $CFG;
2209
2210 if (!empty($CFG->defaultblocks_override)) {
2211 $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override);
2212
2213 } else if ($course->id == SITEID) {
2214 $blocknames = blocks_get_default_site_course_blocks();
2215
a49e2ea7
MG
2216 } else if (!empty($CFG->{'defaultblocks_' . $course->format})) {
2217 $blocknames = blocks_parse_default_blocks_list($CFG->{'defaultblocks_' . $course->format});
9b4b78fd 2218
a49e2ea7 2219 } else {
498e9a9a 2220 require_once($CFG->dirroot. '/course/lib.php');
a49e2ea7 2221 $blocknames = course_get_format($course)->get_default_blocks();
9d1d606e 2222
9b4b78fd 2223 }
2224
f474a4e5 2225 if ($course->id == SITEID) {
2226 $pagetypepattern = 'site-index';
2227 } else {
2228 $pagetypepattern = 'course-view-*';
2229 }
9d1d606e 2230 $page = new moodle_page();
2231 $page->set_course($course);
f474a4e5 2232 $page->blocks->add_blocks($blocknames, $pagetypepattern);
9d1d606e 2233}
2234
2235/**
2236 * Add the default system-context blocks. E.g. the admin tree.
2237 */
2238function blocks_add_default_system_blocks() {
03d9401e
MD
2239 global $DB;
2240
9d1d606e 2241 $page = new moodle_page();
b0c6dc1c 2242 $page->set_context(context_system::instance());
3406acde 2243 $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('navigation', 'settings')), '*', null, true);
7d2a0492 2244 $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('admin_bookmarks')), 'admin-*', null, null, 2);
03d9401e
MD
2245
2246 if ($defaultmypage = $DB->get_record('my_pages', array('userid'=>null, 'name'=>'__default', 'private'=>1))) {
2247 $subpagepattern = $defaultmypage->id;
2248 } else {
2249 $subpagepattern = null;
2250 }
2251
0184a3fd 2252 $page->blocks->add_blocks(array(BLOCK_POS_RIGHT => array('private_files', 'online_users'), 'content' => array('course_overview')), 'my-index', $subpagepattern, false);
9d1d606e 2253}