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