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