MDL-22054 improving blocks management UI and removing debug message because the block...
[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 *
d4accfc0 23 * @package moodlecore
24 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
e8c8cee9 26 */
0f3fe4b6 27
13a0d3d3 28/**#@+
29 * @deprecated since Moodle 2.0. No longer used.
d4a03c00 30 */
0f3fe4b6 31define('BLOCK_MOVE_LEFT', 0x01);
32define('BLOCK_MOVE_RIGHT', 0x02);
33define('BLOCK_MOVE_UP', 0x04);
34define('BLOCK_MOVE_DOWN', 0x08);
9b4b78fd 35define('BLOCK_CONFIGURE', 0x10);
13a0d3d3 36/**#@-*/
0f3fe4b6 37
13a0d3d3 38/**#@+
39 * Default names for the block regions in the standard theme.
40 */
bb46a4fa 41define('BLOCK_POS_LEFT', 'side-pre');
42define('BLOCK_POS_RIGHT', 'side-post');
13a0d3d3 43/**#@-*/
0e9af917 44
13a0d3d3 45/**#@+
46 * @deprecated since Moodle 2.0. No longer used.
47 */
ee6055eb 48define('BLOCKS_PINNED_TRUE',0);
49define('BLOCKS_PINNED_FALSE',1);
50define('BLOCKS_PINNED_BOTH',2);
13a0d3d3 51/**#@-*/
ee6055eb 52
d4accfc0 53/**
d4a03c00 54 * Exception thrown when someone tried to do something with a block that does
55 * not exist on a page.
d4accfc0 56 *
d4a03c00 57 * @copyright 2009 Tim Hunt
58 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
59 * @since Moodle 2.0
d4accfc0 60 */
f4d38d20 61class block_not_on_page_exception extends moodle_exception {
d4accfc0 62 /**
63 * Contructor
d4a03c00 64 * @param int $instanceid the block instance id of the block that was looked for.
65 * @param object $page the current page.
d4accfc0 66 */
f4d38d20 67 public function __construct($instanceid, $page) {
68 $a = new stdClass;
69 $a->instanceid = $instanceid;
2a3b0763 70 $a->url = $page->url->out();
71 parent::__construct('blockdoesnotexistonpage', '', $page->url->out(), $a);
f4d38d20 72 }
73}
74
86b5ea0f 75/**
76 * This class keeps track of the block that should appear on a moodle_page.
bb46a4fa 77 *
d4a03c00 78 * The page to work with as passed to the constructor.
1d00ec6a 79 *
d4a03c00 80 * @copyright 2009 Tim Hunt
81 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
82 * @since Moodle 2.0
86b5ea0f 83 */
d4a03c00 84class block_manager {
2cdb8d84 85 /**
86 * The UI normally only shows block weights between -MAX_WEIGHT and MAX_WEIGHT,
87 * although other weights are valid.
88 */
89 const MAX_WEIGHT = 10;
86b5ea0f 90
91/// Field declarations =========================================================
d4a03c00 92
93 /** @var moodle_page the moodle_page we aremanaging blocks for. */
86b5ea0f 94 protected $page;
d4a03c00 95
96 /** @var array region name => 1.*/
86b5ea0f 97 protected $regions = array();
d4a03c00 98
99 /** @var string the region where new blocks are added.*/
100 protected $defaultregion = null;
101
102 /** @var array will be $DB->get_records('blocks') */
103 protected $allblocks = null;
104
105 /**
106 * @var array blocks that this user can add to this page. Will be a subset
a2789e34 107 * of $allblocks, but with array keys block->name. Access this via the
108 * {@link get_addable_blocks()} method to ensure it is lazy-loaded.
d4a03c00 109 */
110 protected $addableblocks = null;
08eab897 111
bb46a4fa 112 /**
113 * Will be an array region-name => array(db rows loaded in load_blocks);
d4accfc0 114 * @var array
bb46a4fa 115 */
116 protected $birecordsbyregion = null;
117
118 /**
119 * array region-name => array(block objects); populated as necessary by
120 * the ensure_instances_exist method.
d4accfc0 121 * @var array
bb46a4fa 122 */
123 protected $blockinstances = array();
124
125 /**
d4a03c00 126 * array region-name => array(block_contents objects) what acutally needs to
bb46a4fa 127 * be displayed in each region.
d4accfc0 128 * @var array
bb46a4fa 129 */
130 protected $visibleblockcontent = array();
08eab897 131
d4a03c00 132 /**
133 * array region-name => array(block_contents objects) extra block-like things
134 * to be displayed in each region, before the real blocks.
135 * @var array
136 */
137 protected $extracontent = array();
138
00a24d44 139 /**
140 * Used by the block move id, to track whether a block is cuurently being moved.
141 *
142 * Whe you click on the move icon of a block, first the page needs to reload with
143 * extra UI for chooseing a new position for a particular block. In that situation
144 * this field holds the id of the block being moved.
145 *
146 * @var integer|null
147 */
148 protected $movingblock = null;
149
86b5ea0f 150/// Constructor ================================================================
151
152 /**
153 * Constructor.
154 * @param object $page the moodle_page object object we are managing the blocks for,
155 * or a reasonable faxilimily. (See the comment at the top of this classe
d4accfc0 156 * and {@link http://en.wikipedia.org/wiki/Duck_typing})
86b5ea0f 157 */
158 public function __construct($page) {
159 $this->page = $page;
160 }
161
162/// Getter methods =============================================================
163
164 /**
d4accfc0 165 * Get an array of all region names on this page where a block may appear
166 *
86b5ea0f 167 * @return array the internal names of the regions on this page where block may appear.
168 */
169 public function get_regions() {
78946b9b 170 if (is_null($this->defaultregion)) {
7d875874 171 $this->page->initialise_theme_and_output();
78946b9b 172 }
86b5ea0f 173 return array_keys($this->regions);
174 }
175
176 /**
d4accfc0 177 * Get the region name of the region blocks are added to by default
178 *
86b5ea0f 179 * @return string the internal names of the region where new blocks are added
180 * by default, and where any blocks from an unrecognised region are shown.
181 * (Imagine that blocks were added with one theme selected, then you switched
182 * to a theme with different block positions.)
183 */
184 public function get_default_region() {
d4a03c00 185 $this->page->initialise_theme_and_output();
86b5ea0f 186 return $this->defaultregion;
187 }
188
08eab897 189 /**
190 * The list of block types that may be added to this page.
d4accfc0 191 *
727ae436 192 * @return array block name => record from block table.
08eab897 193 */
194 public function get_addable_blocks() {
195 $this->check_is_loaded();
196
197 if (!is_null($this->addableblocks)) {
198 return $this->addableblocks;
199 }
200
201 // Lazy load.
202 $this->addableblocks = array();
203
204 $allblocks = blocks_get_record();
205 if (empty($allblocks)) {
206 return $this->addableblocks;
207 }
208
bb46a4fa 209 $pageformat = $this->page->pagetype;
08eab897 210 foreach($allblocks as $block) {
211 if ($block->visible &&
bb46a4fa 212 (block_method_result($block->name, 'instance_allow_multiple') || !$this->is_block_present($block->id)) &&
a2789e34 213 blocks_name_allowed_in_format($block->name, $pageformat) &&
214 block_method_result($block->name, 'user_can_addto', $this->page)) {
215 $this->addableblocks[$block->name] = $block;
08eab897 216 }
217 }
218
219 return $this->addableblocks;
220 }
221
d4accfc0 222 /**
223 * Find out if a block is present ? just a guess
224 * @todo Write this function and document
225 */
08eab897 226 public function is_block_present($blocktypeid) {
227 // TODO
228 }
229
230 /**
d4accfc0 231 * Find out if a block type is known by the system
232 *
08eab897 233 * @param string $blockname the name of ta type of block.
234 * @param boolean $includeinvisible if false (default) only check 'visible' blocks, that is, blocks enabled by the admin.
235 * @return boolean true if this block in installed.
236 */
237 public function is_known_block_type($blockname, $includeinvisible = false) {
238 $blocks = $this->get_installed_blocks();
239 foreach ($blocks as $block) {
240 if ($block->name == $blockname && ($includeinvisible || $block->visible)) {
241 return true;
242 }
243 }
244 return false;
245 }
246
247 /**
d4accfc0 248 * Find out if a region exists on a page
249 *
08eab897 250 * @param string $region a region name
251 * @return boolean true if this retion exists on this page.
252 */
253 public function is_known_region($region) {
254 return array_key_exists($region, $this->regions);
255 }
256
257 /**
d4accfc0 258 * Get an array of all blocks within a given region
259 *
260 * @param string $region a block region that exists on this page.
08eab897 261 * @return array of block instances.
262 */
263 public function get_blocks_for_region($region) {
264 $this->check_is_loaded();
bb46a4fa 265 $this->ensure_instances_exist($region);
266 return $this->blockinstances[$region];
267 }
268
269 /**
d4accfc0 270 * Returns an array of block content objects that exist in a region
271 *
d4a03c00 272 * @param string $region a block region that exists on this page.
273 * @return array of block block_contents objects for all the blocks in a region.
bb46a4fa 274 */
d4a03c00 275 public function get_content_for_region($region, $output) {
bb46a4fa 276 $this->check_is_loaded();
d4a03c00 277 $this->ensure_content_created($region, $output);
bb46a4fa 278 return $this->visibleblockcontent[$region];
08eab897 279 }
280
00a24d44 281 /**
282 * Helper method used by get_content_for_region.
283 * @param string $region region name
284 * @param float $weight weight. May be fractional, since you may want to move a block
285 * between ones with weight 2 and 3, say ($weight would be 2.5).
286 * @return string URL for moving block $this->movingblock to this position.
287 */
288 protected function get_move_target_url($region, $weight) {
dd72b308 289 return new moodle_url($this->page->url, array('bui_moveid' => $this->movingblock,
b9bc2019 290 'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()));
00a24d44 291 }
292
d4a03c00 293 /**
294 * Determine whether a region contains anything. (Either any real blocks, or
295 * the add new block UI.)
78d27a90 296 *
297 * (You may wonder why the $output parameter is required. Unfortunately,
298 * becuase of the way that blocks work, the only reliable way to find out
299 * if a block will be visible is to get the content for output, and to
300 * get the content, you need a renderer. Fortunately, this is not a
301 * performance problem, becuase we cache the output that is generated, and
302 * in almost every case where we call region_has_content, we are about to
303 * output the blocks anyway, so we are not doing wasted effort.)
304 *
d4a03c00 305 * @param string $region a block region that exists on this page.
78946b9b 306 * @param object $output a core_renderer. normally the global $OUTPUT.
d4a03c00 307 * @return boolean Whether there is anything in this region.
308 */
78d27a90 309 public function region_has_content($region, $output) {
4f0c2d00 310
d4a03c00 311 if (!$this->is_known_region($region)) {
312 return false;
313 }
314 $this->check_is_loaded();
78d27a90 315 $this->ensure_content_created($region, $output);
d4a03c00 316 if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks()) {
317 // If editing is on, we need all the block regions visible, for the
318 // move blocks UI.
319 return true;
320 }
78d27a90 321 return !empty($this->visibleblockcontent[$region]) || !empty($this->extracontent[$region]);
d4a03c00 322 }
323
08eab897 324 /**
d4accfc0 325 * Get an array of all of the installed blocks.
326 *
08eab897 327 * @return array contents of the block table.
328 */
329 public function get_installed_blocks() {
330 global $DB;
331 if (is_null($this->allblocks)) {
332 $this->allblocks = $DB->get_records('block');
333 }
334 return $this->allblocks;
335 }
336
86b5ea0f 337/// Setter methods =============================================================
338
339 /**
d4accfc0 340 * Add a region to a page
341 *
86b5ea0f 342 * @param string $region add a named region where blocks may appear on the
343 * current page. This is an internal name, like 'side-pre', not a string to
344 * display in the UI.
345 */
346 public function add_region($region) {
347 $this->check_not_yet_loaded();
348 $this->regions[$region] = 1;
349 }
350
351 /**
d4accfc0 352 * Add an array of regions
353 * @see add_region()
354 *
86b5ea0f 355 * @param array $regions this utility method calls add_region for each array element.
356 */
357 public function add_regions($regions) {
358 foreach ($regions as $region) {
359 $this->add_region($region);
360 }
361 }
362
363 /**
d4accfc0 364 * Set the default region for new blocks on the page
365 *
86b5ea0f 366 * @param string $defaultregion the internal names of the region where new
367 * blocks should be added by default, and where any blocks from an
368 * unrecognised region are shown.
369 */
370 public function set_default_region($defaultregion) {
371 $this->check_not_yet_loaded();
035b96a9 372 if ($defaultregion) {
373 $this->check_region_is_known($defaultregion);
374 }
86b5ea0f 375 $this->defaultregion = $defaultregion;
376 }
377
d4a03c00 378 /**
379 * Add something that looks like a block, but which isn't an actual block_instance,
380 * to this page.
381 *
382 * @param block_contents $bc the content of the block like thing.
383 * @param string $region a block region that exists on this page.
384 */
385 public function add_pretend_block($bc, $region) {
386 $this->page->initialise_theme_and_output();
387 $this->check_region_is_known($region);
388 if (array_key_exists($region, $this->visibleblockcontent)) {
389 throw new coding_exception('block_manager has already prepared the blocks in region ' .
390 $region . 'for output. It is too late to add a pretend block.');
391 }
392 $this->extracontent[$region][] = $bc;
393 }
394
08eab897 395/// Actions ====================================================================
396
397 /**
398 * This method actually loads the blocks for our page from the database.
d4accfc0 399 *
ae42ff6f 400 * @param boolean|null $includeinvisible
401 * null (default) - load hidden blocks if $this->page->user_is_editing();
402 * true - load hidden blocks.
403 * false - don't load hidden blocks.
08eab897 404 */
ae42ff6f 405 public function load_blocks($includeinvisible = null) {
d19e8195 406 global $DB, $CFG;
7d875874 407
bb46a4fa 408 if (!is_null($this->birecordsbyregion)) {
409 // Already done.
410 return;
411 }
08eab897 412
d19e8195 413 if ($CFG->version < 2009050619) {
414 // Upgrade/install not complete. Don't try too show any blocks.
415 $this->birecordsbyregion = array();
416 return;
417 }
418
d4a03c00 419 // Ensure we have been initialised.
7d875874 420 if (is_null($this->defaultregion)) {
b7009474 421 $this->page->initialise_theme_and_output();
d4a03c00 422 // If there are still no block regions, then there are no blocks on this page.
423 if (empty($this->regions)) {
424 $this->birecordsbyregion = array();
425 return;
426 }
b7009474 427 }
428
08eab897 429 if (is_null($includeinvisible)) {
430 $includeinvisible = $this->page->user_is_editing();
431 }
432 if ($includeinvisible) {
08eab897 433 $visiblecheck = '';
ae42ff6f 434 } else {
435 $visiblecheck = 'AND (bp.visible = 1 OR bp.visible IS NULL)';
08eab897 436 }
437
438 $context = $this->page->context;
13a0d3d3 439 $contexttest = 'bi.parentcontextid = :contextid2';
08eab897 440 $parentcontextparams = array();
441 $parentcontextids = get_parent_contexts($context);
442 if ($parentcontextids) {
443 list($parentcontexttest, $parentcontextparams) =
444 $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED, 'parentcontext0000');
13a0d3d3 445 $contexttest = "($contexttest OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontexttest))";
08eab897 446 }
447
1d13c75c 448 $pagetypepatterns = matching_page_type_patterns($this->page->pagetype);
08eab897 449 list($pagetypepatterntest, $pagetypepatternparams) =
450 $DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED, 'pagetypepatterntest0000');
451
4f0c2d00
PS
452 list($ccselect, $ccjoin) = context_instance_preload_sql('b.id', CONTEXT_BLOCK, 'ctx');
453
08eab897 454 $params = array(
455 'subpage1' => $this->page->subpage,
456 'subpage2' => $this->page->subpage,
457 'contextid1' => $context->id,
458 'contextid2' => $context->id,
459 'pagetype' => $this->page->pagetype,
460 );
461 $sql = "SELECT
462 bi.id,
d4a03c00 463 bp.id AS blockpositionid,
08eab897 464 bi.blockname,
13a0d3d3 465 bi.parentcontextid,
08eab897 466 bi.showinsubcontexts,
467 bi.pagetypepattern,
468 bi.subpagepattern,
ae42ff6f 469 bi.defaultregion,
470 bi.defaultweight,
bb46a4fa 471 COALESCE(bp.visible, 1) AS visible,
08eab897 472 COALESCE(bp.region, bi.defaultregion) AS region,
473 COALESCE(bp.weight, bi.defaultweight) AS weight,
4f0c2d00
PS
474 bi.configdata
475 $ccselect
08eab897 476
477 FROM {block_instances} bi
478 JOIN {block} b ON bi.blockname = b.name
479 LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id
480 AND bp.contextid = :contextid1
481 AND bp.pagetype = :pagetype
482 AND bp.subpage = :subpage1
4f0c2d00 483 $ccjoin
08eab897 484
485 WHERE
486 $contexttest
487 AND bi.pagetypepattern $pagetypepatterntest
488 AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)
489 $visiblecheck
490 AND b.visible = 1
491
492 ORDER BY
493 COALESCE(bp.region, bi.defaultregion),
494 COALESCE(bp.weight, bi.defaultweight),
495 bi.id";
496 $blockinstances = $DB->get_recordset_sql($sql, $params + $parentcontextparams + $pagetypepatternparams);
497
bb46a4fa 498 $this->birecordsbyregion = $this->prepare_per_region_arrays();
08eab897 499 $unknown = array();
08eab897 500 foreach ($blockinstances as $bi) {
4f0c2d00 501 context_instance_preload($bi);
08eab897 502 if ($this->is_known_region($bi->region)) {
bb46a4fa 503 $this->birecordsbyregion[$bi->region][] = $bi;
08eab897 504 } else {
505 $unknown[] = $bi;
506 }
507 }
d4a03c00 508
509 // Pages don't necessarily have a defaultregion. The one time this can
510 // happen is when there are no theme block regions, but the script itself
511 // has a block region in the main content area.
512 if (!empty($this->defaultregion)) {
513 $this->birecordsbyregion[$this->defaultregion] =
514 array_merge($this->birecordsbyregion[$this->defaultregion], $unknown);
515 }
08eab897 516 }
517
518 /**
519 * Add a block to the current page, or related pages. The block is added to
520 * context $this->page->contextid. If $pagetypepattern $subpagepattern
d4accfc0 521 *
08eab897 522 * @param string $blockname The type of block to add.
523 * @param string $region the block region on this page to add the block to.
524 * @param integer $weight determines the order where this block appears in the region.
525 * @param boolean $showinsubcontexts whether this block appears in subcontexts, or just the current context.
526 * @param string|null $pagetypepattern which page types this block should appear on. Defaults to just the current page type.
527 * @param string|null $subpagepattern which subpage this block should appear on. NULL = any (the default), otherwise only the specified subpage.
528 */
529 public function add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern = NULL, $subpagepattern = NULL) {
530 global $DB;
531 $this->check_known_block_type($blockname);
532 $this->check_region_is_known($region);
533
534 if (empty($pagetypepattern)) {
535 $pagetypepattern = $this->page->pagetype;
536 }
537
538 $blockinstance = new stdClass;
539 $blockinstance->blockname = $blockname;
13a0d3d3 540 $blockinstance->parentcontextid = $this->page->context->id;
08eab897 541 $blockinstance->showinsubcontexts = !empty($showinsubcontexts);
542 $blockinstance->pagetypepattern = $pagetypepattern;
543 $blockinstance->subpagepattern = $subpagepattern;
544 $blockinstance->defaultregion = $region;
545 $blockinstance->defaultweight = $weight;
546 $blockinstance->configdata = '';
feed1900 547 $blockinstance->id = $DB->insert_record('block_instances', $blockinstance);
548
e92c286c 549 // Ensure the block context is created.
550 get_context_instance(CONTEXT_BLOCK, $blockinstance->id);
e03c0c1d 551
feed1900 552 // If the new instance was created, allow it to do additional setup
e92c286c 553 if ($block = block_instance($blockname, $blockinstance)) {
feed1900 554 $block->instance_create();
555 }
08eab897 556 }
557
21d33bdf 558 public function add_block_at_end_of_default_region($blockname) {
559 $defaulregion = $this->get_default_region();
2a3b0763 560
21d33bdf 561 $lastcurrentblock = end($this->birecordsbyregion[$defaulregion]);
2a3b0763 562 if ($lastcurrentblock) {
563 $weight = $lastcurrentblock->weight + 1;
564 } else {
565 $weight = 0;
566 }
567
21d33bdf 568 if ($this->page->subpage) {
569 $subpage = $this->page->subpage;
570 } else {
571 $subpage = null;
572 }
a2789e34 573
574 // Special case. Course view page type include the course format, but we
575 // want to add the block non-format-specifically.
576 $pagetypepattern = $this->page->pagetype;
577 if (strpos($pagetypepattern, 'course-view') === 0) {
578 $pagetypepattern = 'course-view-*';
579 }
580
2a3b0763 581 $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);
21d33bdf 582 }
583
9d1d606e 584 /**
585 * Convenience method, calls add_block repeatedly for all the blocks in $blocks.
d4accfc0 586 *
2a3b0763 587 * @param array $blocks array with array keys the region names, and values an array of block names.
9d1d606e 588 * @param string $pagetypepattern optional. Passed to @see add_block()
589 * @param string $subpagepattern optional. Passed to @see add_block()
590 */
7d2a0492 591 public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {
9d1d606e 592 $this->add_regions(array_keys($blocks));
593 foreach ($blocks as $region => $regionblocks) {
594 $weight = 0;
595 foreach ($regionblocks as $blockname) {
7d2a0492 596 $this->add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern, $subpagepattern);
9d1d606e 597 $weight += 1;
598 }
599 }
600 }
601
2cdb8d84 602 /**
603 * Move a block to a new position on this page.
604 *
605 * If this block cannot appear on any other pages, then we change defaultposition/weight
606 * in the block_instances table. Otherwise we just set the postition on this page.
607 *
608 * @param $blockinstanceid the block instance id.
609 * @param $newregion the new region name.
610 * @param $newweight the new weight.
611 */
612 public function reposition_block($blockinstanceid, $newregion, $newweight) {
613 global $DB;
614
615 $this->check_region_is_known($newregion);
616 $inst = $this->find_instance($blockinstanceid);
617
618 $bi = $inst->instance;
619 if ($bi->weight == $bi->defaultweight && $bi->region == $bi->defaultregion &&
620 !$bi->showinsubcontexts && strpos($bi->pagetypepattern, '*') === false &&
621 (!$this->page->subpage || $bi->subpagepattern)) {
622
623 // Set default position
624 $newbi = new stdClass;
625 $newbi->id = $bi->id;
626 $newbi->defaultregion = $newregion;
627 $newbi->defaultweight = $newweight;
628 $DB->update_record('block_instances', $newbi);
629
630 if ($bi->blockpositionid) {
631 $bp = new stdClass;
632 $bp->id = $bi->blockpositionid;
633 $bp->region = $newregion;
634 $bp->weight = $newweight;
635 $DB->update_record('block_positions', $bp);
636 }
637
638 } else {
639 // Just set position on this page.
640 $bp = new stdClass;
641 $bp->region = $newregion;
642 $bp->weight = $newweight;
643
644 if ($bi->blockpositionid) {
645 $bp->id = $bi->blockpositionid;
646 $DB->update_record('block_positions', $bp);
647
648 } else {
649 $bp->blockinstanceid = $bi->id;
650 $bp->contextid = $this->page->context->id;
651 $bp->pagetype = $this->page->pagetype;
652 if ($this->page->subpage) {
653 $bp->subpage = $this->page->subpage;
654 } else {
655 $bp->subpage = '';
656 }
657 $bp->visible = $bi->visible;
658 $DB->insert_record('block_positions', $bp);
659 }
660 }
661 }
662
f4d38d20 663 /**
a19f419d 664 * Find a given block by its instance id
d4accfc0 665 *
f4d38d20 666 * @param integer $instanceid
d4accfc0 667 * @return object
f4d38d20 668 */
669 public function find_instance($instanceid) {
670 foreach ($this->regions as $region => $notused) {
671 $this->ensure_instances_exist($region);
672 foreach($this->blockinstances[$region] as $instance) {
673 if ($instance->instance->id == $instanceid) {
674 return $instance;
675 }
676 }
677 }
678 throw new block_not_on_page_exception($instanceid, $this->page);
679 }
680
86b5ea0f 681/// Inner workings =============================================================
682
d4accfc0 683 /**
684 * Check whether the page blocks have been loaded yet
685 *
686 * @return void Throws coding exception if already loaded
687 */
86b5ea0f 688 protected function check_not_yet_loaded() {
bb46a4fa 689 if (!is_null($this->birecordsbyregion)) {
86b5ea0f 690 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.');
691 }
692 }
693
d4accfc0 694 /**
695 * Check whether the page blocks have been loaded yet
696 *
697 * Nearly identical to the above function {@link check_not_yet_loaded()} except different message
698 *
699 * @return void Throws coding exception if already loaded
700 */
08eab897 701 protected function check_is_loaded() {
bb46a4fa 702 if (is_null($this->birecordsbyregion)) {
08eab897 703 throw new coding_exception('block_manager has not yet loaded the blocks, to it is too soon to request the information you asked for.');
704 }
705 }
706
d4accfc0 707 /**
708 * Check if a block type is known and usable
709 *
710 * @param string $blockname The block type name to search for
711 * @param bool $includeinvisible Include disabled block types in the intial pass
712 * @return void Coding Exception thrown if unknown or not enabled
713 */
08eab897 714 protected function check_known_block_type($blockname, $includeinvisible = false) {
715 if (!$this->is_known_block_type($blockname, $includeinvisible)) {
716 if ($this->is_known_block_type($blockname, true)) {
717 throw new coding_exception('Unknown block type ' . $blockname);
718 } else {
719 throw new coding_exception('Block type ' . $blockname . ' has been disabled by the administrator.');
720 }
721 }
722 }
723
d4accfc0 724 /**
725 * Check if a region is known by its name
726 *
727 * @param string $region
728 * @return void Coding Exception thrown if the region is not known
729 */
08eab897 730 protected function check_region_is_known($region) {
731 if (!$this->is_known_region($region)) {
732 throw new coding_exception('Trying to reference an unknown block region ' . $region);
733 }
86b5ea0f 734 }
bb46a4fa 735
736 /**
d4accfc0 737 * Returns an array of region names as keys and nested arrays for values
738 *
bb46a4fa 739 * @return array an array where the array keys are the region names, and the array
740 * values are empty arrays.
741 */
742 protected function prepare_per_region_arrays() {
743 $result = array();
744 foreach ($this->regions as $region => $notused) {
745 $result[$region] = array();
746 }
747 return $result;
748 }
749
d4accfc0 750 /**
751 * Create a set of new block instance from a record array
752 *
753 * @param array $birecords An array of block instance records
754 * @return array An array of instantiated block_instance objects
755 */
bb46a4fa 756 protected function create_block_instances($birecords) {
757 $results = array();
758 foreach ($birecords as $record) {
d836aa4b 759 if ($blockobject = block_instance($record->blockname, $record, $this->page)) {
760 $results[] = $blockobject;
761 }
bb46a4fa 762 }
763 return $results;
764 }
765
4578a5eb 766 /**
767 * Create all the bock instances for all the blocks that were loaded by
768 * load_blocks. This is used, for example, to ensure that all blocks get a
769 * chance to initialise themselves via the {@link block_base::specialize()}
770 * method, before any output is done.
771 */
772 public function create_all_block_instances() {
773 foreach ($this->get_regions() as $region) {
774 $this->ensure_instances_exist($region);
775 }
776 }
777
d4accfc0 778 /**
00a24d44 779 * Return an array of content objects from a set of block instances
d4accfc0 780 *
781 * @param array $instances An array of block instances
78946b9b 782 * @param renderer_base The renderer to use.
00a24d44 783 * @param string $region the region name.
784 * @return array An array of block_content (and possibly block_move_target) objects.
d4accfc0 785 */
00a24d44 786 protected function create_block_contents($instances, $output, $region) {
bb46a4fa 787 $results = array();
00a24d44 788
789 $lastweight = 0;
790 $lastblock = 0;
791 if ($this->movingblock) {
792 $first = reset($instances);
793 if ($first) {
794 $lastweight = $first->instance->weight - 2;
795 }
796
797 $strmoveblockhere = get_string('moveblockhere', 'block');
798 }
799
bb46a4fa 800 foreach ($instances as $instance) {
d4a03c00 801 $content = $instance->get_content_for_output($output);
00a24d44 802 if (empty($content)) {
803 continue;
804 }
805
806 if ($this->movingblock && $lastweight != $instance->instance->weight &&
807 $content->blockinstanceid != $this->movingblock && $lastblock != $this->movingblock) {
dd72b308 808 $results[] = new block_move_target($strmoveblockhere, $this->get_move_target_url($region, ($lastweight + $instance->instance->weight)/2));
00a24d44 809 }
810
811 if ($content->blockinstanceid == $this->movingblock) {
812 $content->add_class('beingmoved');
813 $content->annotation .= get_string('movingthisblockcancel', 'block',
75015e5f 814 html_writer::link($this->page->url, get_string('cancel')));
bb46a4fa 815 }
00a24d44 816
817 $results[] = $content;
818 $lastweight = $instance->instance->weight;
819 $lastblock = $instance->instance->id;
820 }
821
822 if ($this->movingblock && $lastblock != $this->movingblock) {
dd72b308 823 $results[] = new block_move_target($strmoveblockhere, $this->get_move_target_url($region, $lastweight + 1));
bb46a4fa 824 }
825 return $results;
826 }
827
d4accfc0 828 /**
829 * Ensure block instances exist for a given region
a19f419d 830 *
d4accfc0 831 * @param string $region Check for bi's with the instance with this name
832 */
bb46a4fa 833 protected function ensure_instances_exist($region) {
834 $this->check_region_is_known($region);
835 if (!array_key_exists($region, $this->blockinstances)) {
836 $this->blockinstances[$region] =
837 $this->create_block_instances($this->birecordsbyregion[$region]);
838 }
839 }
840
d4accfc0 841 /**
842 * Ensure that there is some content within the given region
843 *
844 * @param string $region The name of the region to check
845 */
d4a03c00 846 protected function ensure_content_created($region, $output) {
bb46a4fa 847 $this->ensure_instances_exist($region);
848 if (!array_key_exists($region, $this->visibleblockcontent)) {
d4a03c00 849 $contents = array();
850 if (array_key_exists($region, $this->extracontent)) {
851 $contents = $this->extracontent[$region];
852 }
00a24d44 853 $contents = array_merge($contents, $this->create_block_contents($this->blockinstances[$region], $output, $region));
d4a03c00 854 if ($region == $this->defaultregion) {
21d33bdf 855 $addblockui = block_add_block_ui($this->page, $output);
d4a03c00 856 if ($addblockui) {
857 $contents[] = $addblockui;
858 }
859 }
860 $this->visibleblockcontent[$region] = $contents;
bb46a4fa 861 }
862 }
a19f419d 863
864/// Process actions from the URL ===============================================
865
00a24d44 866 /**
867 * Get the appropriate list of editing icons for a block. This is used
868 * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}.
869 *
870 * @param $output The core_renderer to use when generating the output. (Need to get icon paths.)
871 * @return an array in the format for {@link block_contents::$controls}
872 */
873 public function edit_controls($block) {
874 global $CFG;
875
5afbd0e7 876 if (!isset($CFG->undeletableblocktypes) || (!is_array($CFG->undeletableblocktypes) && !is_string($CFG->undeletableblocktypes))) {
877 $CFG->undeletableblocktypes = array('global_navigation_tree','settings_navigation_tree');
878 } else if (is_string($CFG->undeletableblocktypes)) {
879 $CFG->undeletableblocktypes = explode(',', $CFG->undeletableblocktypes);
880 }
881
00a24d44 882 $controls = array();
b9bc2019 883 $actionurl = $this->page->url->out(false, array('sesskey'=> sesskey()));
00a24d44 884
885 // Assign roles icon.
886 if (has_capability('moodle/role:assign', $block->context)) {
a4a04ada
PS
887 //TODO: please note it is sloppy to pass urls through page parameters!!
888 // it is shortened because some web servers (e.g. IIS by default) give
889 // a 'security' error if you try to pass a full URL as a GET parameter in another URL.
4f0c2d00 890
bc0663ab 891 $return = $this->page->url->out(false);
a4a04ada 892 $return = str_replace($CFG->wwwroot . '/', '', $return);
4f0c2d00 893
00a24d44 894 $controls[] = array('url' => $CFG->wwwroot . '/' . $CFG->admin .
a4a04ada 895 '/roles/assign.php?contextid=' . $block->context->id . '&returnurl=' . urlencode($return),
00a24d44 896 'icon' => 'i/roles', 'caption' => get_string('assignroles', 'role'));
897 }
898
899 if ($this->page->user_can_edit_blocks()) {
900 // Show/hide icon.
901 if ($block->instance->visible) {
902 $controls[] = array('url' => $actionurl . '&bui_hideid=' . $block->instance->id,
903 'icon' => 't/hide', 'caption' => get_string('hide'));
904 } else {
905 $controls[] = array('url' => $actionurl . '&bui_showid=' . $block->instance->id,
906 'icon' => 't/show', 'caption' => get_string('show'));
907 }
908 }
909
910 if ($this->page->user_can_edit_blocks() || $block->user_can_edit()) {
911 // Edit config icon - always show - needed for positioning UI.
912 $controls[] = array('url' => $actionurl . '&bui_editid=' . $block->instance->id,
913 'icon' => 't/edit', 'caption' => get_string('configuration'));
914 }
915
916 if ($this->page->user_can_edit_blocks() && $block->user_can_edit() && $block->user_can_addto($this->page)) {
5afbd0e7 917 if (!in_array($block->instance->blockname, $CFG->undeletableblocktypes)) {
918 // Delete icon.
919 $controls[] = array('url' => $actionurl . '&bui_deleteid=' . $block->instance->id,
920 'icon' => 't/delete', 'caption' => get_string('delete'));
921 }
00a24d44 922 }
923
924 if ($this->page->user_can_edit_blocks()) {
925 // Move icon.
926 $controls[] = array('url' => $actionurl . '&bui_moveid=' . $block->instance->id,
927 'icon' => 't/move', 'caption' => get_string('move'));
928 }
929
930 return $controls;
931 }
932
a19f419d 933 /**
934 * Process any block actions that were specified in the URL.
935 *
936 * This can only be done given a valid $page object.
937 *
938 * @param moodle_page $page the page to add blocks to.
939 * @return boolean true if anything was done. False if not.
940 */
941 public function process_url_actions() {
00a24d44 942 if (!$this->page->user_is_editing()) {
943 return false;
944 }
a19f419d 945 return $this->process_url_add() || $this->process_url_delete() ||
00a24d44 946 $this->process_url_show_hide() || $this->process_url_edit() ||
947 $this->process_url_move();
a19f419d 948 }
949
950 /**
951 * Handle adding a block.
952 * @return boolean true if anything was done. False if not.
953 */
954 public function process_url_add() {
955 $blocktype = optional_param('bui_addblock', null, PARAM_SAFEDIR);
956 if (!$blocktype) {
957 return false;
958 }
959
c74eec3b 960 require_sesskey();
a19f419d 961
1d7e341e 962 if (!$this->page->user_can_edit_blocks()) {
a19f419d 963 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('addblock'));
964 }
965
966 if (!array_key_exists($blocktype, $this->get_addable_blocks())) {
967 throw new moodle_exception('cannotaddthisblocktype', '', $this->page->url->out(), $blocktype);
968 }
969
970 $this->add_block_at_end_of_default_region($blocktype);
971
972 // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
973 $this->page->ensure_param_not_in_url('bui_addblock');
974
975 return true;
976 }
977
978 /**
979 * Handle deleting a block.
980 * @return boolean true if anything was done. False if not.
981 */
982 public function process_url_delete() {
983 $blockid = optional_param('bui_deleteid', null, PARAM_INTEGER);
984 if (!$blockid) {
985 return false;
986 }
987
c74eec3b 988 require_sesskey();
a19f419d 989
990 $block = $this->page->blocks->find_instance($blockid);
991
7bbc2890 992 if (!$block->user_can_edit() || !$this->page->user_can_edit_blocks() || !$block->user_can_addto($this->page)) {
a19f419d 993 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('deleteablock'));
994 }
995
996 blocks_delete_instance($block->instance);
997
998 // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
999 $this->page->ensure_param_not_in_url('bui_deleteid');
1000
1001 return true;
1002 }
1003
1004 /**
1005 * Handle showing or hiding a block.
1006 * @return boolean true if anything was done. False if not.
1007 */
1008 public function process_url_show_hide() {
1009 if ($blockid = optional_param('bui_hideid', null, PARAM_INTEGER)) {
1010 $newvisibility = 0;
1011 } else if ($blockid = optional_param('bui_showid', null, PARAM_INTEGER)) {
1012 $newvisibility = 1;
1013 } else {
1014 return false;
1015 }
1016
c74eec3b 1017 require_sesskey();
a19f419d 1018
1019 $block = $this->page->blocks->find_instance($blockid);
1020
d14edf06 1021 if (!$this->page->user_can_edit_blocks()) {
a19f419d 1022 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('hideshowblocks'));
1023 }
1024
1025 blocks_set_visibility($block->instance, $this->page, $newvisibility);
1026
1027 // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1028 $this->page->ensure_param_not_in_url('bui_hideid');
1029 $this->page->ensure_param_not_in_url('bui_showid');
1030
1031 return true;
1032 }
1033
1034 /**
1035 * Handle showing/processing the submission from the block editing form.
1036 * @return boolean true if the form was submitted and the new config saved. Does not
1037 * return if the editing form was displayed. False otherwise.
1038 */
1039 public function process_url_edit() {
1040 global $CFG, $DB, $PAGE;
1041
1042 $blockid = optional_param('bui_editid', null, PARAM_INTEGER);
1043 if (!$blockid) {
1044 return false;
1045 }
1046
c74eec3b 1047 require_sesskey();
a19f419d 1048 require_once($CFG->dirroot . '/blocks/edit_form.php');
1049
1050 $block = $this->find_instance($blockid);
1051
d14edf06 1052 if (!$block->user_can_edit() && !$this->page->user_can_edit_blocks()) {
a19f419d 1053 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1054 }
1055
1056 $editpage = new moodle_page();
3c14795a 1057 $editpage->set_pagelayout('admin');
a19f419d 1058 $editpage->set_course($this->page->course);
7bbc2890 1059 $editpage->set_context($block->context);
24e4119a 1060 $editurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
a19f419d 1061 $editurlparams = $this->page->url->params();
1062 $editurlparams['bui_editid'] = $blockid;
1063 $editpage->set_url($editurlbase, $editurlparams);
1064 $editpage->_block_actions_done = true;
1065 // At this point we are either going to redirect, or display the form, so
1066 // overwrite global $PAGE ready for this. (Formslib refers to it.)
1067 $PAGE = $editpage;
1068
1069 $formfile = $CFG->dirroot . '/blocks/' . $block->name() . '/edit_form.php';
1070 if (is_readable($formfile)) {
1071 require_once($formfile);
1072 $classname = 'block_' . $block->name() . '_edit_form';
1073 } else {
1074 $classname = 'block_edit_form';
1075 }
1076
1077 $mform = new $classname($editpage->url, $block, $this->page);
1078 $mform->set_data($block->instance);
1079
1080 if ($mform->is_cancelled()) {
1081 redirect($this->page->url);
1082
1083 } else if ($data = $mform->get_data()) {
1084 $bi = new stdClass;
1085 $bi->id = $block->instance->id;
a19f419d 1086 $bi->pagetypepattern = $data->bui_pagetypepattern;
1087 if (empty($data->bui_subpagepattern) || $data->bui_subpagepattern == '%@NULL@%') {
1088 $bi->subpagepattern = null;
1089 } else {
1090 $bi->subpagepattern = $data->bui_subpagepattern;
1091 }
0aed347f
MD
1092
1093 $parentcontext = get_context_instance_by_id($data->bui_parentcontextid);
1094 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1095
1096 // Updating stickiness and contexts. See MDL-21375 for details.
1097 if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination
1098 // Explicitly set the context
1099 $bi->parentcontextid = $parentcontext->id;
1100
1101 // If the context type is > 0 then we'll explicitly set the block as sticky, otherwise not
1102 $bi->showinsubcontexts = (int)(!empty($data->bui_contexts));
1103
1104 // If the block wants to be system-wide, then explicitly set that
1105 if ($data->bui_contexts == 2) { // Only possible on a frontpage or system page
1106 $bi->parentcontextid = $systemcontext->id;
1107
1108 } else { // The block doesn't want to be system-wide, so let's ensure that
1109 if ($parentcontext->id == $systemcontext->id) { // We need to move it to the front page
1110 $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
1111 $bi->parentcontextid = $frontpagecontext->id;
1112 $bi->pagetypepattern = '*'; // Just in case
1113 }
02ba576c
MD
1114 }
1115 }
0aed347f 1116
a19f419d 1117 $bi->defaultregion = $data->bui_defaultregion;
1118 $bi->defaultweight = $data->bui_defaultweight;
1119 $DB->update_record('block_instances', $bi);
1120
a23bbaa3 1121 if (!empty($block->config)) {
1122 $config = clone($block->config);
1123 } else {
1124 $config = new stdClass;
1125 }
a19f419d 1126 foreach ($data as $configfield => $value) {
1127 if (strpos($configfield, 'config_') !== 0) {
1128 continue;
1129 }
1130 $field = substr($configfield, 7);
1131 $config->$field = $value;
1132 }
1133 $block->instance_config_save($config);
1134
1135 $bp = new stdClass;
1136 $bp->visible = $data->bui_visible;
1137 $bp->region = $data->bui_region;
1138 $bp->weight = $data->bui_weight;
1139 $needbprecord = !$data->bui_visible || $data->bui_region != $data->bui_defaultregion ||
1140 $data->bui_weight != $data->bui_defaultweight;
1141
1142 if ($block->instance->blockpositionid && !$needbprecord) {
1143 $DB->delete_records('block_positions', array('id' => $block->instance->blockpositionid));
1144
1145 } else if ($block->instance->blockpositionid && $needbprecord) {
1146 $bp->id = $block->instance->blockpositionid;
1147 $DB->update_record('block_positions', $bp);
1148
1149 } else if ($needbprecord) {
1150 $bp->blockinstanceid = $block->instance->id;
a23bbaa3 1151 $bp->contextid = $this->page->context->id;
a19f419d 1152 $bp->pagetype = $this->page->pagetype;
1153 if ($this->page->subpage) {
1154 $bp->subpage = $this->page->subpage;
1155 } else {
a23bbaa3 1156 $bp->subpage = '';
a19f419d 1157 }
1158 $DB->insert_record('block_positions', $bp);
1159 }
1160
1161 redirect($this->page->url);
1162
1163 } else {
1164 $strheading = get_string('editinga', $block->name());
1165 if (strpos($strheading, '[[') === 0) {
1166 $strheading = get_string('blockconfiga', 'moodle', $block->get_title());
1167 }
1168
1169 $editpage->set_title($strheading);
1170 $editpage->set_heading($strheading);
3c14795a 1171 $editpage->navbar->add($strheading);
649cf95d 1172 $output = $editpage->get_renderer('core');
a19f419d 1173 echo $output->header();
1174 echo $output->heading($strheading, 2);
1175 $mform->display();
1176 echo $output->footer();
1177 exit;
1178 }
1179 }
00a24d44 1180
1181 /**
1182 * Handle showing/processing the submission from the block editing form.
1183 * @return boolean true if the form was submitted and the new config saved. Does not
1184 * return if the editing form was displayed. False otherwise.
1185 */
1186 public function process_url_move() {
1187 global $CFG, $DB, $PAGE;
1188
1189 $blockid = optional_param('bui_moveid', null, PARAM_INTEGER);
1190 if (!$blockid) {
1191 return false;
1192 }
1193
c74eec3b 1194 require_sesskey();
00a24d44 1195
1196 $block = $this->find_instance($blockid);
1197
1198 if (!$this->page->user_can_edit_blocks()) {
1199 throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1200 }
1201
1202 $newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT);
1203 $newweight = optional_param('bui_newweight', null, PARAM_FLOAT);
1204 if (!$newregion || is_null($newweight)) {
1205 // Don't have a valid target position yet, must be just starting the move.
1206 $this->movingblock = $blockid;
1207 $this->page->ensure_param_not_in_url('bui_moveid');
1208 return false;
1209 }
1210
2cdb8d84 1211 if (!$this->is_known_region($newregion)) {
1212 throw new moodle_exception('unknownblockregion', '', $this->page->url, $newregion);
1213 }
1214
1215 // Move this block. This may involve moving other nearby blocks.
1216 $blocks = $this->birecordsbyregion[$newregion];
1217
f4e6a86e 1218 $maxweight = self::MAX_WEIGHT;
1219 $minweight = -self::MAX_WEIGHT;
1220
1221 // Initialise the used weights and spareweights array with the default values
2cdb8d84 1222 $spareweights = array();
1223 $usedweights = array();
f4e6a86e 1224 for ($i = $minweight; $i <= $maxweight; $i++) {
2cdb8d84 1225 $spareweights[$i] = $i;
1226 $usedweights[$i] = array();
1227 }
f4e6a86e 1228
1229 // Check each block and sort out where we have used weights
2cdb8d84 1230 foreach ($blocks as $bi) {
f4e6a86e 1231 if ($bi->weight > $maxweight) {
1232 // If this statement is true then the blocks weight is more than the
1233 // current maximum. To ensure that we can get the best block position
1234 // we will initialise elements within the usedweights and spareweights
1235 // arrays between the blocks weight (which will then be the new max) and
1236 // the current max
1237 $parseweight = $bi->weight;
1238 while (!array_key_exists($parseweight, $usedweights)) {
1239 $usedweights[$parseweight] = array();
1240 $spareweights[$parseweight] = $parseweight;
1241 $parseweight--;
1242 }
1243 $maxweight = $bi->weight;
1244 } else if ($bi->weight < $minweight) {
1245 // As above except this time the blocks weight is LESS than the
1246 // the current minimum, so we will initialise the array from the
1247 // blocks weight (new minimum) to the current minimum
1248 $parseweight = $bi->weight;
1249 while (!array_key_exists($parseweight, $usedweights)) {
1250 $usedweights[$parseweight] = array();
1251 $spareweights[$parseweight] = $parseweight;
1252 $parseweight++;
1253 }
1254 $minweight = $bi->weight;
1255 }
1256 if ($bi->id != $block->instance->id) {
1257 unset($spareweights[$bi->weight]);
1258 $usedweights[$bi->weight][] = $bi->id;
2cdb8d84 1259 }
2cdb8d84 1260 }
1261
f4e6a86e 1262 // First we find the nearest gap in the list of weights.
2cdb8d84 1263 $bestdistance = max(abs($newweight - self::MAX_WEIGHT), abs($newweight + self::MAX_WEIGHT)) + 1;
1264 $bestgap = null;
1265 foreach ($spareweights as $spareweight) {
1266 if (abs($newweight - $spareweight) < $bestdistance) {
1267 $bestdistance = abs($newweight - $spareweight);
1268 $bestgap = $spareweight;
1269 }
1270 }
1271
1272 // If there is no gap, we have to go outside -self::MAX_WEIGHT .. self::MAX_WEIGHT.
1273 if (is_null($bestgap)) {
1274 $bestgap = self::MAX_WEIGHT + 1;
1275 while (!empty($usedweights[$bestgap])) {
1276 $bestgap++;
1277 }
1278 }
1279
1280 // Now we know the gap we are aiming for, so move all the blocks along.
1281 if ($bestgap < $newweight) {
1282 $newweight = floor($newweight);
1283 for ($weight = $bestgap + 1; $weight <= $newweight; $weight++) {
1284 foreach ($usedweights[$weight] as $biid) {
1285 $this->reposition_block($biid, $newregion, $weight - 1);
1286 }
1287 }
1288 $this->reposition_block($block->instance->id, $newregion, $newweight);
1289 } else {
1290 $newweight = ceil($newweight);
1291 for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {
1292 foreach ($usedweights[$weight] as $biid) {
1293 $this->reposition_block($biid, $newregion, $weight + 1);
1294 }
1295 }
1296 $this->reposition_block($block->instance->id, $newregion, $newweight);
1297 }
6f5e0852 1298
00a24d44 1299 $this->page->ensure_param_not_in_url('bui_moveid');
1300 $this->page->ensure_param_not_in_url('bui_newregion');
1301 $this->page->ensure_param_not_in_url('bui_newweight');
1302 return true;
1303 }
86b5ea0f 1304}
1305
08eab897 1306/// Helper functions for working with block classes ============================
1307
1308/**
1309 * Call a class method (one that does not requrie a block instance) on a block class.
d4accfc0 1310 *
08eab897 1311 * @param string $blockname the name of the block.
1312 * @param string $method the method name.
1313 * @param array $param parameters to pass to the method.
1314 * @return mixed whatever the method returns.
1315 */
11306331 1316function block_method_result($blockname, $method, $param = NULL) {
0f3fe4b6 1317 if(!block_load_class($blockname)) {
1318 return NULL;
1319 }
11306331 1320 return call_user_func(array('block_'.$blockname, $method), $param);
0f3fe4b6 1321}
1322
08eab897 1323/**
1324 * Creates a new object of the specified block class.
d4accfc0 1325 *
08eab897 1326 * @param string $blockname the name of the block.
1327 * @param $instance block_instances DB table row (optional).
bb46a4fa 1328 * @param moodle_page $page the page this block is appearing on.
08eab897 1329 * @return block_base the requested block instance.
1330 */
bb46a4fa 1331function block_instance($blockname, $instance = NULL, $page = NULL) {
0f3fe4b6 1332 if(!block_load_class($blockname)) {
1333 return false;
1334 }
e89d741a 1335 $classname = 'block_'.$blockname;
f032aa7a 1336 $retval = new $classname;
9b4b78fd 1337 if($instance !== NULL) {
bb46a4fa 1338 if (is_null($page)) {
1339 global $PAGE;
1340 $page = $PAGE;
1341 }
1342 $retval->_load_instance($instance, $page);
9b4b78fd 1343 }
1344 return $retval;
0f3fe4b6 1345}
1346
08eab897 1347/**
1348 * Load the block class for a particular type of block.
d4accfc0 1349 *
08eab897 1350 * @param string $blockname the name of the block.
1351 * @return boolean success or failure.
1352 */
0f3fe4b6 1353function block_load_class($blockname) {
1354 global $CFG;
1355
a9033ad5 1356 if(empty($blockname)) {
c7a9e293 1357 return false;
1358 }
1359
e89d741a 1360 $classname = 'block_'.$blockname;
a9033ad5 1361
1362 if(class_exists($classname)) {
1363 return true;
1364 }
1365
d836aa4b 1366 $blockpath = $CFG->dirroot.'/blocks/'.$blockname.'/block_'.$blockname.'.php';
1367
1368 if (file_exists($blockpath)) {
1369 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
1370 include_once($blockpath);
1371 }else{
15822fe2 1372 //debugging("$blockname code does not exist in $blockpath", DEBUG_DEVELOPER);
d836aa4b 1373 return false;
1374 }
0f3fe4b6 1375
0f3fe4b6 1376 return class_exists($classname);
1377}
1378
1d13c75c 1379/**
1380 * Given a specific page type, return all the page type patterns that might
1381 * match it.
1382 *
1383 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1384 * @return array an array of all the page type patterns that might match this page type.
1385 */
1386function matching_page_type_patterns($pagetype) {
1387 $patterns = array($pagetype);
1388 $bits = explode('-', $pagetype);
1389 if (count($bits) == 3 && $bits[0] == 'mod') {
1390 if ($bits[2] == 'view') {
1391 $patterns[] = 'mod-*-view';
1392 } else if ($bits[2] == 'index') {
1393 $patterns[] = 'mod-*-index';
1394 }
1395 }
1396 while (count($bits) > 0) {
1397 $patterns[] = implode('-', $bits) . '-*';
1398 array_pop($bits);
1399 }
1400 $patterns[] = '*';
1401 return $patterns;
1402}
1403
21d33bdf 1404/// Functions update the blocks if required by the request parameters ==========
1405
1406/**
1407 * Return a {@link block_contents} representing the add a new block UI, if
1408 * this user is allowed to see it.
1409 *
1410 * @return block_contents an appropriate block_contents, or null if the user
1411 * cannot add any blocks here.
1412 */
1413function block_add_block_ui($page, $output) {
d81b05e7 1414 global $CFG, $OUTPUT;
21d33bdf 1415 if (!$page->user_is_editing() || !$page->user_can_edit_blocks()) {
1416 return null;
1417 }
1418
1419 $bc = new block_contents();
1420 $bc->title = get_string('addblock');
1421 $bc->add_class('block_adminblock');
1422
a2789e34 1423 $missingblocks = $page->blocks->get_addable_blocks();
21d33bdf 1424 if (empty($missingblocks)) {
a2789e34 1425 $bc->content = get_string('noblockstoaddhere');
21d33bdf 1426 return $bc;
1427 }
1428
1429 $menu = array();
a2789e34 1430 foreach ($missingblocks as $block) {
21d33bdf 1431 $blockobject = block_instance($block->name);
1432 if ($blockobject !== false && $blockobject->user_can_addto($page)) {
1433 $menu[$block->name] = $blockobject->get_title();
1434 }
1435 }
1436 asort($menu, SORT_LOCALE_STRING);
1437
8afba50b 1438 $actionurl = new moodle_url($page->url, array('sesskey'=>sesskey()));
f8dab966
PS
1439 $select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');
1440 $bc->content = $OUTPUT->render($select);
21d33bdf 1441 return $bc;
1442}
1443
21d33bdf 1444// Functions that have been deprecated by block_manager =======================
f032aa7a 1445
08eab897 1446/**
a2789e34 1447 * @deprecated since Moodle 2.0 - use $page->blocks->get_addable_blocks();
d4accfc0 1448 *
08eab897 1449 * This function returns an array with the IDs of any blocks that you can add to your page.
1450 * Parameters are passed by reference for speed; they are not modified at all.
d4accfc0 1451 *
08eab897 1452 * @param $page the page object.
bb46a4fa 1453 * @param $blockmanager Not used.
08eab897 1454 * @return array of block type ids.
1455 */
bb46a4fa 1456function blocks_get_missing(&$page, &$blockmanager) {
a2789e34 1457 debugging('blocks_get_missing is deprecated. Please use $page->blocks->get_addable_blocks() instead.', DEBUG_DEVELOPER);
1458 $blocks = $page->blocks->get_addable_blocks();
1459 $ids = array();
1460 foreach ($blocks as $block) {
1461 $ids[] = $block->id;
1462 }
1463 return $ids;
f032aa7a 1464}
1465
bb46a4fa 1466/**
1467 * Actually delete from the database any blocks that are currently on this page,
1468 * but which should not be there according to blocks_name_allowed_in_format.
d4accfc0 1469 *
1470 * @todo Write/Fix this function. Currently returns immediatly
c679c358 1471 * @param $course
bb46a4fa 1472 */
c679c358 1473function blocks_remove_inappropriate($course) {
bb46a4fa 1474 // TODO
1475 return;
1476 $blockmanager = blocks_get_by_page($page);
f032aa7a 1477
78946b9b 1478 if (empty($blockmanager)) {
f032aa7a 1479 return;
1480 }
1481
78946b9b 1482 if (($pageformat = $page->pagetype) == NULL) {
f032aa7a 1483 return;
1484 }
1485
f2c6739c 1486 foreach($blockmanager as $region) {
1487 foreach($region as $instance) {
f032aa7a 1488 $block = blocks_get_record($instance->blockid);
5bbbe0be 1489 if(!blocks_name_allowed_in_format($block->name, $pageformat)) {
a2789e34 1490 blocks_delete_instance($instance->instance);
f032aa7a 1491 }
1492 }
1493 }
1494}
1495
d4accfc0 1496/**
1497 * Check that a given name is in a permittable format
1498 *
1499 * @param string $name
1500 * @param string $pageformat
1501 * @return bool
1502 */
5bbbe0be 1503function blocks_name_allowed_in_format($name, $pageformat) {
cd2bc3c9 1504 $accept = NULL;
1505 $maxdepth = -1;
1506 $formats = block_method_result($name, 'applicable_formats');
1507 if (!$formats) {
1508 $formats = array();
1509 }
1510 foreach ($formats as $format => $allowed) {
1511 $formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';
1512 $depth = substr_count($format, '-');
1513 if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {
1514 $maxdepth = $depth;
1515 $accept = $allowed;
5bbbe0be 1516 }
1517 }
cd2bc3c9 1518 if ($accept === NULL) {
5bbbe0be 1519 $accept = !empty($formats['all']);
1520 }
1521 return $accept;
1522}
1523
feed1900 1524/**
1525 * Delete a block, and associated data.
d4accfc0 1526 *
feed1900 1527 * @param object $instance a row from the block_instances table
d4accfc0 1528 * @param bool $nolongerused legacy parameter. Not used, but kept for bacwards compatibility.
1529 * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.
feed1900 1530 */
1531function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {
f4d38d20 1532 global $DB;
1533
1534 if ($block = block_instance($instance->blockname, $instance)) {
feed1900 1535 $block->instance_delete();
1536 }
1537 delete_context(CONTEXT_BLOCK, $instance->id);
f032aa7a 1538
feed1900 1539 if (!$skipblockstables) {
1540 $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id));
1541 $DB->delete_records('block_instances', array('id' => $instance->id));
b33dd23a 1542 }
feed1900 1543}
b33dd23a 1544
feed1900 1545/**
1546 * Delete all the blocks that belong to a particular context.
d4accfc0 1547 *
d4accfc0 1548 * @param int $contextid the context id.
feed1900 1549 */
1550function blocks_delete_all_for_context($contextid) {
1551 global $DB;
a2789e34 1552 $instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));
feed1900 1553 foreach ($instances as $instance) {
1554 blocks_delete_instance($instance, true);
0d6b9d4f 1555 }
feed1900 1556 $instances->close();
13a0d3d3 1557 $DB->delete_records('block_instances', array('parentcontextid' => $contextid));
feed1900 1558 $DB->delete_records('block_positions', array('contextid' => $contextid));
f032aa7a 1559}
1560
ae42ff6f 1561/**
1562 * Set a block to be visible or hidden on a particular page.
1563 *
1564 * @param object $instance a row from the block_instances, preferably LEFT JOINed with the
1565 * block_positions table as return by block_manager.
1566 * @param moodle_page $page the back to set the visibility with respect to.
1567 * @param integer $newvisibility 1 for visible, 0 for hidden.
1568 */
1569function blocks_set_visibility($instance, $page, $newvisibility) {
1570 global $DB;
1571 if (!empty($instance->blockpositionid)) {
1572 // Already have local information on this page.
1573 $DB->set_field('block_positions', 'visible', $newvisibility, array('id' => $instance->blockpositionid));
1574 return;
1575 }
1576
1577 // Create a new block_positions record.
1578 $bp = new stdClass;
1579 $bp->blockinstanceid = $instance->id;
1580 $bp->contextid = $page->context->id;
1581 $bp->pagetype = $page->pagetype;
1582 if ($page->subpage) {
1583 $bp->subpage = $page->subpage;
1584 }
1585 $bp->visible = $newvisibility;
1586 $bp->region = $instance->defaultregion;
1587 $bp->weight = $instance->defaultweight;
1588 $DB->insert_record('block_positions', $bp);
1589}
1590
d4accfc0 1591/**
d4a03c00 1592 * @deprecated since 2.0
1593 * Delete all the blocks from a particular page.
d4accfc0 1594 *
d4a03c00 1595 * @param string $pagetype the page type.
1596 * @param integer $pageid the page id.
1597 * @return bool success or failure.
d4accfc0 1598 */
d4a03c00 1599function blocks_delete_all_on_page($pagetype, $pageid) {
1600 global $DB;
1601
1602 debugging('Call to deprecated function blocks_delete_all_on_page. ' .
1603 'This function cannot work any more. Doing nothing. ' .
1604 'Please update your code to use a block_manager method $PAGE->blocks->....', DEBUG_DEVELOPER);
1605 return false;
0f3fe4b6 1606}
1607
d4accfc0 1608/**
d4a03c00 1609 * Dispite what this function is called, it seems to be mostly used to populate
1610 * the default blocks when a new course (or whatever) is created.
d4accfc0 1611 *
d4a03c00 1612 * @deprecated since 2.0
d4accfc0 1613 *
d4a03c00 1614 * @param object $page the page to add default blocks to.
1615 * @return boolean success or failure.
d4accfc0 1616 */
d4a03c00 1617function blocks_repopulate_page($page) {
1618 global $CFG;
0f3fe4b6 1619
d4a03c00 1620 debugging('Call to deprecated function blocks_repopulate_page. ' .
1621 'Use a more specific method like blocks_add_default_course_blocks, ' .
1622 'or just call $PAGE->blocks->add_blocks()', DEBUG_DEVELOPER);
d23157d8 1623
d4a03c00 1624 /// If the site override has been defined, it is the only valid one.
1625 if (!empty($CFG->defaultblocks_override)) {
1626 $blocknames = $CFG->defaultblocks_override;
1627 } else {
1628 $blocknames = $page->blocks_get_default();
66492322 1629 }
0f3fe4b6 1630
d4a03c00 1631 $blocks = blocks_parse_default_blocks_list($blocknames);
1632 $page->blocks->add_blocks($blocks);
1633
1634 return true;
0f3fe4b6 1635}
1636
08eab897 1637/**
d4a03c00 1638 * Get the block record for a particular blockid - that is, a particul type os block.
d4accfc0 1639 *
d4accfc0 1640 * @param $int blockid block type id. If null, an array of all block types is returned.
1641 * @param bool $notusedanymore No longer used.
08eab897 1642 * @return array|object row from block table, or all rows.
1643 */
1644function blocks_get_record($blockid = NULL, $notusedanymore = false) {
1645 global $PAGE;
1646 $blocks = $PAGE->blocks->get_installed_blocks();
1647 if ($blockid === NULL) {
1648 return $blocks;
1649 } else if (isset($blocks[$blockid])) {
1650 return $blocks[$blockid];
1651 } else {
1652 return false;
9b4b78fd 1653 }
9b4b78fd 1654}
1655
d4accfc0 1656/**
1657 * Find a given block by its blockid within a provide array
1658 *
1659 * @param int $blockid
1660 * @param array $blocksarray
1661 * @return bool|object Instance if found else false
1662 */
9b4b78fd 1663function blocks_find_block($blockid, $blocksarray) {
0d6b9d4f 1664 if (empty($blocksarray)) {
1665 return false;
1666 }
9b4b78fd 1667 foreach($blocksarray as $blockgroup) {
0d6b9d4f 1668 if (empty($blockgroup)) {
1669 continue;
1670 }
9b4b78fd 1671 foreach($blockgroup as $instance) {
1672 if($instance->blockid == $blockid) {
1673 return $instance;
1674 }
1675 }
1676 }
1677 return false;
1678}
1679
d4a03c00 1680// Functions for programatically adding default blocks to pages ================
0f3fe4b6 1681
9d1d606e 1682/**
1683 * Parse a list of default blocks. See config-dist for a description of the format.
d4accfc0 1684 *
9d1d606e 1685 * @param string $blocksstr
1686 * @return array
1687 */
1688function blocks_parse_default_blocks_list($blocksstr) {
f474a4e5 1689 $blocks = array();
1690 $bits = explode(':', $blocksstr);
1691 if (!empty($bits)) {
7d2a0492 1692 $leftbits = trim(array_shift($bits));
1693 if ($leftbits != '') {
1694 $blocks[BLOCK_POS_LEFT] = explode(',', $leftbits);
1695 }
f474a4e5 1696 }
1697 if (!empty($bits)) {
7d2a0492 1698 $rightbits =trim(array_shift($bits));
1699 if ($rightbits != '') {
1700 $blocks[BLOCK_POS_RIGHT] = explode(',', $rightbits);
1701 }
f474a4e5 1702 }
1703 return $blocks;
9d1d606e 1704}
5b224948 1705
9d1d606e 1706/**
1707 * @return array the blocks that should be added to the site course by default.
1708 */
1709function blocks_get_default_site_course_blocks() {
1710 global $CFG;
9b4b78fd 1711
9d1d606e 1712 if (!empty($CFG->defaultblocks_site)) {
f474a4e5 1713 return blocks_parse_default_blocks_list($CFG->defaultblocks_site);
9d1d606e 1714 } else {
f474a4e5 1715 return array(
7d2a0492 1716 BLOCK_POS_LEFT => array('site_main_menu'),
9d1d606e 1717 BLOCK_POS_RIGHT => array('course_summary', 'calendar_month')
1718 );
9b4b78fd 1719 }
9d1d606e 1720}
1721
1722/**
1723 * Add the default blocks to a course.
d4accfc0 1724 *
9d1d606e 1725 * @param object $course a course object.
1726 */
1727function blocks_add_default_course_blocks($course) {
1728 global $CFG;
1729
1730 if (!empty($CFG->defaultblocks_override)) {
1731 $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override);
1732
1733 } else if ($course->id == SITEID) {
1734 $blocknames = blocks_get_default_site_course_blocks();
1735
1736 } else {
1737 $defaultblocks = 'defaultblocks_' . $course->format;
1738 if (!empty($CFG->$defaultblocks)) {
1739 $blocknames = blocks_parse_default_blocks_list($CFG->$defaultblocks);
1740
1741 } else {
1d00ec6a 1742 $formatconfig = $CFG->dirroot.'/course/format/'.$course->format.'/config.php';
1743 if (is_readable($formatconfig)) {
9d1d606e 1744 require($formatconfig);
1745 }
1746 if (!empty($format['defaultblocks'])) {
1747 $blocknames = blocks_parse_default_blocks_list($format['defaultblocks']);
9b4b78fd 1748
9d1d606e 1749 } else if (!empty($CFG->defaultblocks)){
1750 $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks);
1751
1752 } else {
1753 $blocknames = array(
7d2a0492 1754 BLOCK_POS_LEFT => array(),
1755 BLOCK_POS_RIGHT => array('search_forums', 'news_items', 'calendar_upcoming', 'recent_activity')
9d1d606e 1756 );
1757 }
1758 }
9b4b78fd 1759 }
1760
f474a4e5 1761 if ($course->id == SITEID) {
1762 $pagetypepattern = 'site-index';
1763 } else {
1764 $pagetypepattern = 'course-view-*';
1765 }
1766
9d1d606e 1767 $page = new moodle_page();
1768 $page->set_course($course);
f474a4e5 1769 $page->blocks->add_blocks($blocknames, $pagetypepattern);
9d1d606e 1770}
1771
1772/**
1773 * Add the default system-context blocks. E.g. the admin tree.
1774 */
1775function blocks_add_default_system_blocks() {
1776 $page = new moodle_page();
1777 $page->set_context(get_context_instance(CONTEXT_SYSTEM));
7d2a0492 1778 $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('global_navigation_tree', 'settings_navigation_tree')), '*', null, true);
1779 $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('admin_bookmarks')), 'admin-*', null, null, 2);
9d1d606e 1780}