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