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