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