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