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