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