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