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