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