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