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