blocks editing ui: MDL-19398 fix adding a block to a page that forgets to call $PAGE...
[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
841/**
842 * Process any block actions that were specified in the URL.
843 *
844 * This can only be done given a valid $page object.
845 *
a2789e34 846 * @param moodle_page $page the page to add blocks to.
21d33bdf 847 * @return boolean true if anything was done. False if not.
848 */
849function block_process_url_actions($page) {
a2789e34 850 return block_process_url_add($page) ||
851 block_process_url_delete($page) ||
852 block_process_url_show_hide($page);
21d33bdf 853}
854
855/**
a2789e34 856 * Handle adding a block.
857 * @param moodle_page $page the page to add blocks to.
21d33bdf 858 * @return boolean true if anything was done. False if not.
859 */
860function block_process_url_add($page) {
861 $blocktype = optional_param('bui_addblock', null, PARAM_SAFEDIR);
862 if (!$blocktype) {
863 return false;
864 }
865
727ae436 866 confirm_sesskey();
867
a2789e34 868 if (!$page->user_is_editing() && !$page->user_can_edit_blocks()) {
869 throw new moodle_exception('nopermissions', '', $page->url->out(), get_string('addblock'));
870 }
871
872 if (!array_key_exists($blocktype, $page->blocks->get_addable_blocks())) {
873 throw new moodle_exception('cannotaddthisblocktype', '', $page->url->out(), $blocktype);
874 }
875
21d33bdf 876 $page->blocks->add_block_at_end_of_default_region($blocktype);
2a3b0763 877
727ae436 878 // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
879 $page->ensure_param_not_in_url('bui_addblock');
2a3b0763 880
21d33bdf 881 return true;
882}
883
a2789e34 884/**
885 * Handle deleting a block.
886 * @param moodle_page $page the page to add blocks to.
887 * @return boolean true if anything was done. False if not.
888 */
889function block_process_url_delete($page) {
890 $blockid = optional_param('bui_deleteid', null, PARAM_INTEGER);
891 if (!$blockid) {
892 return false;
893 }
894
727ae436 895 confirm_sesskey();
896
a2789e34 897 $instance = $page->blocks->find_instance($blockid);
898 blocks_delete_instance($instance->instance);
2a3b0763 899
727ae436 900 // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
901 $page->ensure_param_not_in_url('bui_deleteid');
2a3b0763 902
a2789e34 903 return true;
904}
21d33bdf 905
a2789e34 906/**
907 * Handle showing or hiding a block.
908 * @param moodle_page $page the page to add blocks to.
909 * @return boolean true if anything was done. False if not.
910 */
911function block_process_url_show_hide($page) {
912
913}
914
915///**
916// * Handle deleting a block.
917// * @param moodle_page $page the page to add blocks to.
918// * @return boolean true if anything was done. False if not.
919// */
920//function block_process_url_delete($page) {
921//
922//}
21d33bdf 923
924// Functions that have been deprecated by block_manager =======================
f032aa7a 925
08eab897 926/**
a2789e34 927 * @deprecated since Moodle 2.0 - use $page->blocks->get_addable_blocks();
d4accfc0 928 *
08eab897 929 * This function returns an array with the IDs of any blocks that you can add to your page.
930 * Parameters are passed by reference for speed; they are not modified at all.
d4accfc0 931 *
08eab897 932 * @param $page the page object.
bb46a4fa 933 * @param $blockmanager Not used.
08eab897 934 * @return array of block type ids.
935 */
bb46a4fa 936function blocks_get_missing(&$page, &$blockmanager) {
a2789e34 937 debugging('blocks_get_missing is deprecated. Please use $page->blocks->get_addable_blocks() instead.', DEBUG_DEVELOPER);
938 $blocks = $page->blocks->get_addable_blocks();
939 $ids = array();
940 foreach ($blocks as $block) {
941 $ids[] = $block->id;
942 }
943 return $ids;
f032aa7a 944}
945
bb46a4fa 946/**
947 * Actually delete from the database any blocks that are currently on this page,
948 * but which should not be there according to blocks_name_allowed_in_format.
d4accfc0 949 *
950 * @todo Write/Fix this function. Currently returns immediatly
c679c358 951 * @param $course
bb46a4fa 952 */
c679c358 953function blocks_remove_inappropriate($course) {
bb46a4fa 954 // TODO
955 return;
956 $blockmanager = blocks_get_by_page($page);
f032aa7a 957
bb46a4fa 958 if(empty($blockmanager)) {
f032aa7a 959 return;
960 }
961
d529807a 962 if(($pageformat = $page->pagetype) == NULL) {
f032aa7a 963 return;
964 }
965
f2c6739c 966 foreach($blockmanager as $region) {
967 foreach($region as $instance) {
f032aa7a 968 $block = blocks_get_record($instance->blockid);
5bbbe0be 969 if(!blocks_name_allowed_in_format($block->name, $pageformat)) {
a2789e34 970 blocks_delete_instance($instance->instance);
f032aa7a 971 }
972 }
973 }
974}
975
d4accfc0 976/**
977 * Check that a given name is in a permittable format
978 *
979 * @param string $name
980 * @param string $pageformat
981 * @return bool
982 */
5bbbe0be 983function blocks_name_allowed_in_format($name, $pageformat) {
cd2bc3c9 984 $accept = NULL;
985 $maxdepth = -1;
986 $formats = block_method_result($name, 'applicable_formats');
987 if (!$formats) {
988 $formats = array();
989 }
990 foreach ($formats as $format => $allowed) {
991 $formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';
992 $depth = substr_count($format, '-');
993 if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {
994 $maxdepth = $depth;
995 $accept = $allowed;
5bbbe0be 996 }
997 }
cd2bc3c9 998 if ($accept === NULL) {
5bbbe0be 999 $accept = !empty($formats['all']);
1000 }
1001 return $accept;
1002}
1003
feed1900 1004/**
1005 * Delete a block, and associated data.
d4accfc0 1006 *
feed1900 1007 * @param object $instance a row from the block_instances table
d4accfc0 1008 * @param bool $nolongerused legacy parameter. Not used, but kept for bacwards compatibility.
1009 * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.
feed1900 1010 */
1011function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {
f4d38d20 1012 global $DB;
1013
1014 if ($block = block_instance($instance->blockname, $instance)) {
feed1900 1015 $block->instance_delete();
1016 }
1017 delete_context(CONTEXT_BLOCK, $instance->id);
f032aa7a 1018
feed1900 1019 if (!$skipblockstables) {
1020 $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id));
1021 $DB->delete_records('block_instances', array('id' => $instance->id));
b33dd23a 1022 }
feed1900 1023}
b33dd23a 1024
feed1900 1025/**
1026 * Delete all the blocks that belong to a particular context.
d4accfc0 1027 *
d4accfc0 1028 * @param int $contextid the context id.
feed1900 1029 */
1030function blocks_delete_all_for_context($contextid) {
1031 global $DB;
a2789e34 1032 $instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));
feed1900 1033 foreach ($instances as $instance) {
1034 blocks_delete_instance($instance, true);
0d6b9d4f 1035 }
feed1900 1036 $instances->close();
13a0d3d3 1037 $DB->delete_records('block_instances', array('parentcontextid' => $contextid));
feed1900 1038 $DB->delete_records('block_positions', array('contextid' => $contextid));
f032aa7a 1039}
1040
d4accfc0 1041/**
d4a03c00 1042 * @deprecated since 2.0
1043 * Delete all the blocks from a particular page.
d4accfc0 1044 *
d4a03c00 1045 * @param string $pagetype the page type.
1046 * @param integer $pageid the page id.
1047 * @return bool success or failure.
d4accfc0 1048 */
d4a03c00 1049function blocks_delete_all_on_page($pagetype, $pageid) {
1050 global $DB;
1051
1052 debugging('Call to deprecated function blocks_delete_all_on_page. ' .
1053 'This function cannot work any more. Doing nothing. ' .
1054 'Please update your code to use a block_manager method $PAGE->blocks->....', DEBUG_DEVELOPER);
1055 return false;
0f3fe4b6 1056}
1057
d4accfc0 1058/**
d4a03c00 1059 * Dispite what this function is called, it seems to be mostly used to populate
1060 * the default blocks when a new course (or whatever) is created.
d4accfc0 1061 *
d4a03c00 1062 * @deprecated since 2.0
d4accfc0 1063 *
d4a03c00 1064 * @param object $page the page to add default blocks to.
1065 * @return boolean success or failure.
d4accfc0 1066 */
d4a03c00 1067function blocks_repopulate_page($page) {
1068 global $CFG;
0f3fe4b6 1069
d4a03c00 1070 debugging('Call to deprecated function blocks_repopulate_page. ' .
1071 'Use a more specific method like blocks_add_default_course_blocks, ' .
1072 'or just call $PAGE->blocks->add_blocks()', DEBUG_DEVELOPER);
d23157d8 1073
d4a03c00 1074 /// If the site override has been defined, it is the only valid one.
1075 if (!empty($CFG->defaultblocks_override)) {
1076 $blocknames = $CFG->defaultblocks_override;
1077 } else {
1078 $blocknames = $page->blocks_get_default();
66492322 1079 }
0f3fe4b6 1080
d4a03c00 1081 $blocks = blocks_parse_default_blocks_list($blocknames);
1082 $page->blocks->add_blocks($blocks);
1083
1084 return true;
0f3fe4b6 1085}
1086
08eab897 1087/**
d4a03c00 1088 * Get the block record for a particular blockid - that is, a particul type os block.
d4accfc0 1089 *
d4accfc0 1090 * @param $int blockid block type id. If null, an array of all block types is returned.
1091 * @param bool $notusedanymore No longer used.
08eab897 1092 * @return array|object row from block table, or all rows.
1093 */
1094function blocks_get_record($blockid = NULL, $notusedanymore = false) {
1095 global $PAGE;
1096 $blocks = $PAGE->blocks->get_installed_blocks();
1097 if ($blockid === NULL) {
1098 return $blocks;
1099 } else if (isset($blocks[$blockid])) {
1100 return $blocks[$blockid];
1101 } else {
1102 return false;
9b4b78fd 1103 }
9b4b78fd 1104}
1105
d4accfc0 1106/**
1107 * Find a given block by its blockid within a provide array
1108 *
1109 * @param int $blockid
1110 * @param array $blocksarray
1111 * @return bool|object Instance if found else false
1112 */
9b4b78fd 1113function blocks_find_block($blockid, $blocksarray) {
0d6b9d4f 1114 if (empty($blocksarray)) {
1115 return false;
1116 }
9b4b78fd 1117 foreach($blocksarray as $blockgroup) {
0d6b9d4f 1118 if (empty($blockgroup)) {
1119 continue;
1120 }
9b4b78fd 1121 foreach($blockgroup as $instance) {
1122 if($instance->blockid == $blockid) {
1123 return $instance;
1124 }
1125 }
1126 }
1127 return false;
1128}
1129
d4accfc0 1130/**
d4a03c00 1131 * TODO Document this function, description
d4accfc0 1132 *
d4accfc0 1133 * @param object $page The page object
1134 * @param object $blockmanager The block manager object
1135 * @param string $blockaction One of [config, add, delete]
1136 * @param int|object $instanceorid The instance id or a block_instance object
1137 * @param bool $pinned
1138 * @param bool $redirect To redirect or not to that is the question but you should stick with true
1139 */
bb46a4fa 1140function blocks_execute_action($page, &$blockmanager, $blockaction, $instanceorid, $pinned=false, $redirect=true) {
58ff964f 1141 global $CFG, $USER, $DB;
9b4b78fd 1142
feed1900 1143 if (!in_array($blockaction, array('config', 'add', 'delete'))) {
1144 throw new moodle_exception('Sorry, blocks editing is currently broken. Will be fixed. See MDL-19010.');
1145 }
1146
a9c75a9c 1147 if (is_int($instanceorid)) {
9b4b78fd 1148 $blockid = $instanceorid;
a9c75a9c 1149 } else if (is_object($instanceorid)) {
9b4b78fd 1150 $instance = $instanceorid;
1151 }
0f3fe4b6 1152
1153 switch($blockaction) {
9b4b78fd 1154 case 'config':
11306331 1155 // First of all check to see if the block wants to be edited
e03c0c1d 1156 if(!$instance->user_can_edit()) {
11306331 1157 break;
9b4b78fd 1158 }
11306331 1159
e82d6cac 1160 // Now get the title and AFTER that load up the instance
e03c0c1d 1161 $blocktitle = $instance->get_title();
afd1ec02 1162
27ec21a0 1163 // Define the data we're going to silently include in the instance config form here,
9b4b78fd 1164 // so we can strip them from the submitted data BEFORE serializing it.
1165 $hiddendata = array(
19f5b2db 1166 'sesskey' => sesskey(),
e03c0c1d 1167 'instanceid' => $instance->instance->id,
9b4b78fd 1168 'blockaction' => 'config'
1169 );
f032aa7a 1170
1171 // To this data, add anything the page itself needs to display
ad52c04f 1172 $hiddendata = $page->url->params($hiddendata);
9b4b78fd 1173
294ce987 1174 if ($data = data_submitted()) {
9b4b78fd 1175 $remove = array_keys($hiddendata);
1176 foreach($remove as $item) {
1177 unset($data->$item);
0f3fe4b6 1178 }
e03c0c1d 1179 $instance->instance_config_save($data);
1180 redirect($page->url->out());
1181
1182 } else {
f032aa7a 1183 // We need to show the config screen, so we highjack the display logic and then die
e82d6cac 1184 $strheading = get_string('blockconfiga', 'moodle', $blocktitle);
e03c0c1d 1185 $nav = build_navigation($strheading, $page->cm);
1186 print_header($strheading, $strheading, $nav);
b9709905 1187
e03c0c1d 1188 echo '<div class="block-config" id="'.$instance->name().'">'; /// Make CSS easier
0be6f678 1189
edb42f09 1190 print_heading($strheading);
ad52c04f 1191 echo '<form method="post" name="block-config" action="'. $page->url->out(false) .'">';
9b4b78fd 1192 echo '<p>';
e03c0c1d 1193 echo $page->url->hidden_params_out(array(), 0, $hiddendata);
9b4b78fd 1194 echo '</p>';
e03c0c1d 1195 $instance->instance_config_print();
9b4b78fd 1196 echo '</form>';
b9709905 1197
1198 echo '</div>';
e03c0c1d 1199 global $PAGE;
1200 $PAGE->set_docs_path('blocks/' . $instance->name());
9b4b78fd 1201 print_footer();
e03c0c1d 1202 die; // Do not go on with the other page-related stuff
0f3fe4b6 1203 }
1204 break;
9b4b78fd 1205 case 'toggle':
1206 if(empty($instance)) {
e49ef64a 1207 print_error('invalidblockinstance', '', '', $blockaction);
0f3fe4b6 1208 }
9b4b78fd 1209 $instance->visible = ($instance->visible) ? 0 : 1;
0d6b9d4f 1210 if (!empty($pinned)) {
66b10689 1211 $DB->update_record('block_pinned_old', $instance);
0d6b9d4f 1212 } else {
66b10689 1213 $DB->update_record('block_instance_old', $instance);
0d6b9d4f 1214 }
9b4b78fd 1215 break;
1216 case 'delete':
1217 if(empty($instance)) {
e49ef64a 1218 print_error('invalidblockinstance', '', '', $blockaction);
0f3fe4b6 1219 }
f4d38d20 1220 blocks_delete_instance($instance->instance, $pinned);
0f3fe4b6 1221 break;
1222 case 'moveup':
9b4b78fd 1223 if(empty($instance)) {
e49ef64a 1224 print_error('invalidblockinstance', '', '', $blockaction);
9b4b78fd 1225 }
f032aa7a 1226
1227 if($instance->weight == 0) {
1228 // The block is the first one, so a move "up" probably means it changes position
1229 // Where is the instance going to be moved?
1230 $newpos = $page->blocks_move_position($instance, BLOCK_MOVE_UP);
bb46a4fa 1231 $newweight = (empty($blockmanager[$newpos]) ? 0 : max(array_keys($blockmanager[$newpos])) + 1);
f032aa7a 1232
0d6b9d4f 1233 blocks_execute_repositioning($instance, $newpos, $newweight, $pinned);
89a5baab 1234 }
f032aa7a 1235 else {
1236 // The block is just moving upwards in the same position.
1237 // This configuration will make sure that even if somehow the weights
1238 // become not continuous, block move operations will eventually bring
1239 // the situation back to normal without printing any warnings.
bb46a4fa 1240 if(!empty($blockmanager[$instance->position][$instance->weight - 1])) {
1241 $other = $blockmanager[$instance->position][$instance->weight - 1];
f032aa7a 1242 }
1243 if(!empty($other)) {
1244 ++$other->weight;
0d6b9d4f 1245 if (!empty($pinned)) {
66b10689 1246 $DB->update_record('block_pinned_old', $other);
0d6b9d4f 1247 } else {
66b10689 1248 $DB->update_record('block_instance_old', $other);
afd1ec02 1249 }
f032aa7a 1250 }
1251 --$instance->weight;
0d6b9d4f 1252 if (!empty($pinned)) {
66b10689 1253 $DB->update_record('block_pinned_old', $instance);
0d6b9d4f 1254 } else {
66b10689 1255 $DB->update_record('block_instance_old', $instance);
0d6b9d4f 1256 }
0f3fe4b6 1257 }
1258 break;
1259 case 'movedown':
9b4b78fd 1260 if(empty($instance)) {
e49ef64a 1261 print_error('invalidblockinstance', '', '', $blockaction);
9b4b78fd 1262 }
f032aa7a 1263
bb46a4fa 1264 if($instance->weight == max(array_keys($blockmanager[$instance->position]))) {
f032aa7a 1265 // The block is the last one, so a move "down" probably means it changes position
1266 // Where is the instance going to be moved?
1267 $newpos = $page->blocks_move_position($instance, BLOCK_MOVE_DOWN);
bb46a4fa 1268 $newweight = (empty($blockmanager[$newpos]) ? 0 : max(array_keys($blockmanager[$newpos])) + 1);
f032aa7a 1269
0d6b9d4f 1270 blocks_execute_repositioning($instance, $newpos, $newweight, $pinned);
89a5baab 1271 }
f032aa7a 1272 else {
1273 // The block is just moving downwards in the same position.
1274 // This configuration will make sure that even if somehow the weights
1275 // become not continuous, block move operations will eventually bring
1276 // the situation back to normal without printing any warnings.
bb46a4fa 1277 if(!empty($blockmanager[$instance->position][$instance->weight + 1])) {
1278 $other = $blockmanager[$instance->position][$instance->weight + 1];
f032aa7a 1279 }
1280 if(!empty($other)) {
1281 --$other->weight;
0d6b9d4f 1282 if (!empty($pinned)) {
66b10689 1283 $DB->update_record('block_pinned_old', $other);
0d6b9d4f 1284 } else {
66b10689 1285 $DB->update_record('block_instance_old', $other);
0d6b9d4f 1286 }
f032aa7a 1287 }
1288 ++$instance->weight;
0d6b9d4f 1289 if (!empty($pinned)) {
66b10689 1290 $DB->update_record('block_pinned_old', $instance);
0d6b9d4f 1291 } else {
66b10689 1292 $DB->update_record('block_instance_old', $instance);
0d6b9d4f 1293 }
0f3fe4b6 1294 }
1295 break;
9b4b78fd 1296 case 'moveleft':
1297 if(empty($instance)) {
e49ef64a 1298 print_error('invalidblockinstance', '', '', $blockaction);
9b4b78fd 1299 }
f032aa7a 1300
1301 // Where is the instance going to be moved?
1302 $newpos = $page->blocks_move_position($instance, BLOCK_MOVE_LEFT);
bb46a4fa 1303 $newweight = (empty($blockmanager[$newpos]) ? 0 : max(array_keys($blockmanager[$newpos])) + 1);
f032aa7a 1304
0d6b9d4f 1305 blocks_execute_repositioning($instance, $newpos, $newweight, $pinned);
0f3fe4b6 1306 break;
9b4b78fd 1307 case 'moveright':
1308 if(empty($instance)) {
e49ef64a 1309 print_error('invalidblockinstance', '', '', $blockaction);
9b4b78fd 1310 }
f032aa7a 1311
1312 // Where is the instance going to be moved?
1313 $newpos = $page->blocks_move_position($instance, BLOCK_MOVE_RIGHT);
bb46a4fa 1314 $newweight = (empty($blockmanager[$newpos]) ? 0 : max(array_keys($blockmanager[$newpos])) + 1);
f032aa7a 1315
0d6b9d4f 1316 blocks_execute_repositioning($instance, $newpos, $newweight, $pinned);
9b4b78fd 1317 break;
1318 case 'add':
1319 // Add a new instance of this block, if allowed
1320 $block = blocks_get_record($blockid);
0f3fe4b6 1321
feed1900 1322 if (empty($block) || !$block->visible) {
3cacefda 1323 // Only allow adding if the block exists and is enabled
11306331 1324 break;
9b4b78fd 1325 }
0f3fe4b6 1326
feed1900 1327 if (!$block->multiple && blocks_find_block($blockid, $blockmanager) !== false) {
89a5baab 1328 // If no multiples are allowed and we already have one, return now
11306331 1329 break;
1330 }
1331
feed1900 1332 if (!block_method_result($block->name, 'user_can_addto', $page)) {
11306331 1333 // If the block doesn't want to be added...
1334 break;
89a5baab 1335 }
1336
feed1900 1337 $region = $page->blocks->get_default_region();
7130fb21 1338 $weight = $DB->get_field_sql("SELECT MAX(defaultweight) FROM {block_instances}
13a0d3d3 1339 WHERE parentcontextid = ? AND defaultregion = ?", array($page->context->id, $region));
feed1900 1340 $pagetypepattern = $page->pagetype;
1341 if (strpos($pagetypepattern, 'course-view') === 0) {
1342 $pagetypepattern = 'course-view-*';
b33dd23a 1343 }
feed1900 1344 $page->blocks->add_block($block->name, $region, $weight, false, $pagetypepattern);
9b4b78fd 1345 break;
1346 }
f032aa7a 1347
b1631fef 1348 if ($redirect) {
1349 // In order to prevent accidental duplicate actions, redirect to a page with a clean url
ad52c04f 1350 redirect($page->url->out());
b1631fef 1351 }
f032aa7a 1352}
1353
d4accfc0 1354/**
d4a03c00 1355 * TODO deprecate
1356 *
d4accfc0 1357 * You can use this to get the blocks to respond to URL actions without much hassle
1358 *
d4accfc0 1359 * @param object $PAGE
1360 * @param object $blockmanager
1361 * @param bool $pinned
1362 */
bb46a4fa 1363function blocks_execute_url_action(&$PAGE, &$blockmanager,$pinned=false) {
02cc05a7 1364 $blockaction = optional_param('blockaction', '', PARAM_ALPHA);
da71112b 1365
3edc57e1 1366 if (empty($blockaction) || !$PAGE->user_allowed_editing() || !confirm_sesskey()) {
da71112b 1367 return;
1368 }
1369
1370 $instanceid = optional_param('instanceid', 0, PARAM_INT);
1371 $blockid = optional_param('blockid', 0, PARAM_INT);
afd1ec02 1372
da71112b 1373 if (!empty($blockid)) {
bb46a4fa 1374 blocks_execute_action($PAGE, $blockmanager, strtolower($blockaction), $blockid, $pinned);
f4d38d20 1375 } else if (!empty($instanceid)) {
1376 $instance = $blockmanager->find_instance($instanceid);
bb46a4fa 1377 blocks_execute_action($PAGE, $blockmanager, strtolower($blockaction), $instance, $pinned);
da71112b 1378 }
1379}
1380
d4accfc0 1381/**
d4a03c00 1382 * TODO deprecate
d4accfc0 1383 * This shouldn't be used externally at all, it's here for use by blocks_execute_action()
1384 * in order to reduce code repetition.
1385 *
1386 * @todo Remove exception when MDL-19010 is fixed
1387 *
1388 * global object
1389 * @param $instance
1390 * @param $newpos
1391 * @param string|int $newweight
1392 * @param bool $pinned
1393 */
29ca8b88 1394function blocks_execute_repositioning(&$instance, $newpos, $newweight, $pinned=false) {
58ff964f 1395 global $DB;
f032aa7a 1396
feed1900 1397 throw new moodle_exception('Sorry, blocks editing is currently broken. Will be fixed. See MDL-19010.');
1398
c4308cfa 1399 // If it's staying where it is, don't do anything, unless overridden
29ca8b88 1400 if ($newpos == $instance->position) {
f032aa7a 1401 return;
1402 }
1403
1404 // Close the weight gap we 'll leave behind
0d6b9d4f 1405 if (!empty($pinned)) {
66b10689 1406 $sql = "UPDATE {block_instance_old}
58ff964f 1407 SET weight = weight - 1
1408 WHERE pagetype = ? AND position = ? AND weight > ?";
1409 $params = array($instance->pagetype, $instance->position, $instance->weight);
1410
0d6b9d4f 1411 } else {
66b10689 1412 $sql = "UPDATE {block_instance_old}
58ff964f 1413 SET weight = weight - 1
1414 WHERE pagetype = ? AND pageid = ?
1415 AND position = ? AND weight > ?";
1416 $params = array($instance->pagetype, $instance->pageid, $instance->position, $instance->weight);
0d6b9d4f 1417 }
58ff964f 1418 $DB->execute($sql, $params);
f032aa7a 1419
1420 $instance->position = $newpos;
1421 $instance->weight = $newweight;
1422
0d6b9d4f 1423 if (!empty($pinned)) {
66b10689 1424 $DB->update_record('block_pinned_old', $instance);
0d6b9d4f 1425 } else {
66b10689 1426 $DB->update_record('block_instance_old', $instance);
0d6b9d4f 1427 }
1428}
1429
29ca8b88 1430
1431/**
d4a03c00 1432 * TODO deprecate
29ca8b88 1433 * Moves a block to the new position (column) and weight (sort order).
d4accfc0 1434 *
d4accfc0 1435 * @param object $instance The block instance to be moved.
1436 * @param string $destpos BLOCK_POS_LEFT or BLOCK_POS_RIGHT. The destination column.
1437 * @param string $destweight The destination sort order. If NULL, we add to the end
1438 * of the destination column.
1439 * @param bool $pinned Are we moving pinned blocks? We can only move pinned blocks
1440 * to a new position withing the pinned list. Likewise, we
1441 * can only moved non-pinned blocks to a new position within
1442 * the non-pinned list.
1443 * @return boolean success or failure
29ca8b88 1444 */
1445function blocks_move_block($page, &$instance, $destpos, $destweight=NULL, $pinned=false) {
58ff964f 1446 global $CFG, $DB;
afd1ec02 1447
feed1900 1448 throw new moodle_exception('Sorry, blocks editing is currently broken. Will be fixed. See MDL-19010.');
1449
29ca8b88 1450 if ($pinned) {
d4a03c00 1451 $blocklist = array(); //blocks_get_pinned($page);
29ca8b88 1452 } else {
d4a03c00 1453 $blocklist = array(); //blocks_get_by_page($page);
29ca8b88 1454 }
afd1ec02 1455
29ca8b88 1456 if ($blocklist[$instance->position][$instance->weight]->id != $instance->id) {
1457 // The source block instance is not where we think it is.
c4308cfa 1458 return false;
d23157d8 1459 }
afd1ec02 1460
29ca8b88 1461 // First we close the gap that will be left behind when we take out the
1462 // block from it's current column.
1463 if ($pinned) {
66b10689 1464 $closegapsql = "UPDATE {block_instance_old}
afd1ec02 1465 SET weight = weight - 1
58ff964f 1466 WHERE weight > ? AND position = ? AND pagetype = ?";
1467 $params = array($instance->weight, $instance->position, $instance->pagetype);
e028ed34 1468 } else {
66b10689 1469 $closegapsql = "UPDATE {block_instance_old}
afd1ec02 1470 SET weight = weight - 1
58ff964f 1471 WHERE weight > ? AND position = ?
1472 AND pagetype = ? AND pageid = ?";
1473 $params = array($instance->weight, $instance->position, $instance->pagetype, $instance->pageid);
29ca8b88 1474 }
58ff964f 1475 if (!$DB->execute($closegapsql, $params)) {
29ca8b88 1476 return false;
77e65ff7 1477 }
afd1ec02 1478
29ca8b88 1479 // Now let's make space for the block being moved.
1480 if ($pinned) {
66b10689 1481 $opengapsql = "UPDATE {block_instance_old}
afd1ec02 1482 SET weight = weight + 1
58ff964f 1483 WHERE weight >= ? AND position = ? AND pagetype = ?";
1484 $params = array($destweight, $destpos, $instance->pagetype);
d23157d8 1485 } else {
66b10689 1486 $opengapsql = "UPDATE {block_instance_old}
58ff964f 1487 SET weight = weight + 1
1488 WHERE weight >= ? AND position = ?
1489 AND pagetype = ? AND pageid = ?";
1490 $params = array($destweight, $destpos, $instance->pagetype, $instance->pageid);
29ca8b88 1491 }
655b09ca 1492 if (!$DB->execute($opengapsql, $params)) {
29ca8b88 1493 return false;
c4308cfa 1494 }
afd1ec02 1495
29ca8b88 1496 // Move the block.
1497 $instance->position = $destpos;
1498 $instance->weight = $destweight;
e028ed34 1499
29ca8b88 1500 if ($pinned) {
66b10689 1501 $table = 'block_pinned_old';
29ca8b88 1502 } else {
66b10689 1503 $table = 'block_instance_old';
29ca8b88 1504 }
58ff964f 1505 return $DB->update_record($table, $instance);
e028ed34 1506}
1507
d4a03c00 1508// Functions for programatically adding default blocks to pages ================
0f3fe4b6 1509
9d1d606e 1510/**
1511 * Parse a list of default blocks. See config-dist for a description of the format.
d4accfc0 1512 *
9d1d606e 1513 * @param string $blocksstr
1514 * @return array
1515 */
1516function blocks_parse_default_blocks_list($blocksstr) {
f474a4e5 1517 $blocks = array();
1518 $bits = explode(':', $blocksstr);
1519 if (!empty($bits)) {
1520 $blocks[BLOCK_POS_LEFT] = explode(',', array_shift($bits));
1521 }
1522 if (!empty($bits)) {
1523 $blocks[BLOCK_POS_RIGHT] = explode(',', array_shift($bits));
1524 }
1525 return $blocks;
9d1d606e 1526}
5b224948 1527
9d1d606e 1528/**
1529 * @return array the blocks that should be added to the site course by default.
1530 */
1531function blocks_get_default_site_course_blocks() {
1532 global $CFG;
9b4b78fd 1533
9d1d606e 1534 if (!empty($CFG->defaultblocks_site)) {
f474a4e5 1535 return blocks_parse_default_blocks_list($CFG->defaultblocks_site);
9d1d606e 1536 } else {
f474a4e5 1537 return array(
9d1d606e 1538 BLOCK_POS_LEFT => array('site_main_menu', 'admin_tree'),
1539 BLOCK_POS_RIGHT => array('course_summary', 'calendar_month')
1540 );
9b4b78fd 1541 }
9d1d606e 1542}
1543
1544/**
1545 * Add the default blocks to a course.
d4accfc0 1546 *
9d1d606e 1547 * @param object $course a course object.
1548 */
1549function blocks_add_default_course_blocks($course) {
1550 global $CFG;
1551
1552 if (!empty($CFG->defaultblocks_override)) {
1553 $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override);
1554
1555 } else if ($course->id == SITEID) {
1556 $blocknames = blocks_get_default_site_course_blocks();
1557
1558 } else {
1559 $defaultblocks = 'defaultblocks_' . $course->format;
1560 if (!empty($CFG->$defaultblocks)) {
1561 $blocknames = blocks_parse_default_blocks_list($CFG->$defaultblocks);
1562
1563 } else {
1d00ec6a 1564 $formatconfig = $CFG->dirroot.'/course/format/'.$course->format.'/config.php';
1565 if (is_readable($formatconfig)) {
9d1d606e 1566 require($formatconfig);
1567 }
1568 if (!empty($format['defaultblocks'])) {
1569 $blocknames = blocks_parse_default_blocks_list($format['defaultblocks']);
9b4b78fd 1570
9d1d606e 1571 } else if (!empty($CFG->defaultblocks)){
1572 $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks);
1573
1574 } else {
1575 $blocknames = array(
1576 BLOCK_POS_LEFT => array('participants', 'activity_modules', 'search_forums', 'admin', 'course_list'),
1577 BLOCK_POS_RIGHT => array('news_items', 'calendar_upcoming', 'recent_activity')
1578 );
1579 }
1580 }
9b4b78fd 1581 }
1582
f474a4e5 1583 if ($course->id == SITEID) {
1584 $pagetypepattern = 'site-index';
1585 } else {
1586 $pagetypepattern = 'course-view-*';
1587 }
1588
9d1d606e 1589 $page = new moodle_page();
1590 $page->set_course($course);
f474a4e5 1591 $page->blocks->add_blocks($blocknames, $pagetypepattern);
9d1d606e 1592}
1593
1594/**
1595 * Add the default system-context blocks. E.g. the admin tree.
1596 */
1597function blocks_add_default_system_blocks() {
1598 $page = new moodle_page();
1599 $page->set_context(get_context_instance(CONTEXT_SYSTEM));
1600 $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('admin_tree', 'admin_bookmarks')), 'admin-*');
1601}