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