cd3e7c50c37f992ddd365b431b12f34628ba3f47
[moodle.git] / lib / blocklib.php
1 <?php
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/>.
18 /**
19  * Block Class and Functions
20  *
21  * This file defines the {@link block_manager} class,
22  *
23  * @package    core
24  * @subpackage block
25  * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
26  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 defined('MOODLE_INTERNAL') || die();
31 /**#@+
32  * @deprecated since Moodle 2.0. No longer used.
33  */
34 define('BLOCK_MOVE_LEFT',   0x01);
35 define('BLOCK_MOVE_RIGHT',  0x02);
36 define('BLOCK_MOVE_UP',     0x04);
37 define('BLOCK_MOVE_DOWN',   0x08);
38 define('BLOCK_CONFIGURE',   0x10);
39 /**#@-*/
41 /**#@+
42  * Default names for the block regions in the standard theme.
43  */
44 define('BLOCK_POS_LEFT',  'side-pre');
45 define('BLOCK_POS_RIGHT', 'side-post');
46 /**#@-*/
48 /**#@+
49  * @deprecated since Moodle 2.0. No longer used.
50  */
51 define('BLOCKS_PINNED_TRUE',0);
52 define('BLOCKS_PINNED_FALSE',1);
53 define('BLOCKS_PINNED_BOTH',2);
54 /**#@-*/
56 /**
57  * Exception thrown when someone tried to do something with a block that does
58  * not exist on a page.
59  *
60  * @copyright 2009 Tim Hunt
61  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
62  * @since     Moodle 2.0
63  */
64 class block_not_on_page_exception extends moodle_exception {
65     /**
66      * Contructor
67      * @param int $instanceid the block instance id of the block that was looked for.
68      * @param object $page the current page.
69      */
70     public function __construct($instanceid, $page) {
71         $a = new stdClass;
72         $a->instanceid = $instanceid;
73         $a->url = $page->url->out();
74         parent::__construct('blockdoesnotexistonpage', '', $page->url->out(), $a);
75     }
76 }
78 /**
79  * This class keeps track of the block that should appear on a moodle_page.
80  *
81  * The page to work with as passed to the constructor.
82  *
83  * @copyright 2009 Tim Hunt
84  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
85  * @since     Moodle 2.0
86  */
87 class block_manager {
88     /**
89      * The UI normally only shows block weights between -MAX_WEIGHT and MAX_WEIGHT,
90      * although other weights are valid.
91      */
92     const MAX_WEIGHT = 10;
94 /// Field declarations =========================================================
96     /** @var moodle_page the moodle_page we aremanaging blocks for. */
97     protected $page;
99     /** @var array region name => 1.*/
100     protected $regions = array();
102     /** @var string the region where new blocks are added.*/
103     protected $defaultregion = null;
105     /** @var array will be $DB->get_records('blocks') */
106     protected $allblocks = null;
108     /**
109      * @var array blocks that this user can add to this page. Will be a subset
110      * of $allblocks, but with array keys block->name. Access this via the
111      * {@link get_addable_blocks()} method to ensure it is lazy-loaded.
112      */
113     protected $addableblocks = null;
115     /**
116      * Will be an array region-name => array(db rows loaded in load_blocks);
117      * @var array
118      */
119     protected $birecordsbyregion = null;
121     /**
122      * array region-name => array(block objects); populated as necessary by
123      * the ensure_instances_exist method.
124      * @var array
125      */
126     protected $blockinstances = array();
128     /**
129      * array region-name => array(block_contents objects) what acutally needs to
130      * be displayed in each region.
131      * @var array
132      */
133     protected $visibleblockcontent = array();
135     /**
136      * array region-name => array(block_contents objects) extra block-like things
137      * to be displayed in each region, before the real blocks.
138      * @var array
139      */
140     protected $extracontent = array();
142     /**
143      * Used by the block move id, to track whether a block is cuurently being moved.
144      *
145      * Whe you click on the move icon of a block, first the page needs to reload with
146      * extra UI for chooseing a new position for a particular block. In that situation
147      * this field holds the id of the block being moved.
148      *
149      * @var integer|null
150      */
151     protected $movingblock = null;
153     /**
154      * Show only fake blocks
155      */
156     protected $fakeblocksonly = false;
158 /// Constructor ================================================================
160     /**
161      * Constructor.
162      * @param object $page the moodle_page object object we are managing the blocks for,
163      * or a reasonable faxilimily. (See the comment at the top of this classe
164      * and {@link http://en.wikipedia.org/wiki/Duck_typing})
165      */
166     public function __construct($page) {
167         $this->page = $page;
168     }
170 /// Getter methods =============================================================
172     /**
173      * Get an array of all region names on this page where a block may appear
174      *
175      * @return array the internal names of the regions on this page where block may appear.
176      */
177     public function get_regions() {
178         if (is_null($this->defaultregion)) {
179             $this->page->initialise_theme_and_output();
180         }
181         return array_keys($this->regions);
182     }
184     /**
185      * Get the region name of the region blocks are added to by default
186      *
187      * @return string the internal names of the region where new blocks are added
188      * by default, and where any blocks from an unrecognised region are shown.
189      * (Imagine that blocks were added with one theme selected, then you switched
190      * to a theme with different block positions.)
191      */
192     public function get_default_region() {
193         $this->page->initialise_theme_and_output();
194         return $this->defaultregion;
195     }
197     /**
198      * The list of block types that may be added to this page.
199      *
200      * @return array block name => record from block table.
201      */
202     public function get_addable_blocks() {
203         $this->check_is_loaded();
205         if (!is_null($this->addableblocks)) {
206             return $this->addableblocks;
207         }
209         // Lazy load.
210         $this->addableblocks = array();
212         $allblocks = blocks_get_record();
213         if (empty($allblocks)) {
214             return $this->addableblocks;
215         }
217         $pageformat = $this->page->pagetype;
218         foreach($allblocks as $block) {
219             if ($block->visible &&
220                     (block_method_result($block->name, 'instance_allow_multiple') || !$this->is_block_present($block->name)) &&
221                     blocks_name_allowed_in_format($block->name, $pageformat) &&
222                     block_method_result($block->name, 'user_can_addto', $this->page)) {
223                 $this->addableblocks[$block->name] = $block;
224             }
225         }
227         return $this->addableblocks;
228     }
230     /**
231      * Given a block name, find out of any of them are currently present in the page
233      * @param string $blockname - the basic name of a block (eg "navigation")
234      * @return boolean - is there one of these blocks in the current page?
235      */
236     public function is_block_present($blockname) {
237         if (empty($this->blockinstances)) {
238             return false;
239         }
241         foreach ($this->blockinstances as $region) {
242             foreach ($region as $instance) {
243                 if (empty($instance->instance->blockname)) {
244                     continue;
245                 }
246                 if ($instance->instance->blockname == $blockname) {
247                     return true;
248                 }
249             }
250         }
251         return false;
252     }
254     /**
255      * Find out if a block type is known by the system
256      *
257      * @param string $blockname the name of ta type of block.
258      * @param boolean $includeinvisible if false (default) only check 'visible' blocks, that is, blocks enabled by the admin.
259      * @return boolean true if this block in installed.
260      */
261     public function is_known_block_type($blockname, $includeinvisible = false) {
262         $blocks = $this->get_installed_blocks();
263         foreach ($blocks as $block) {
264             if ($block->name == $blockname && ($includeinvisible || $block->visible)) {
265                 return true;
266             }
267         }
268         return false;
269     }
271     /**
272      * Find out if a region exists on a page
273      *
274      * @param string $region a region name
275      * @return boolean true if this retion exists on this page.
276      */
277     public function is_known_region($region) {
278         return array_key_exists($region, $this->regions);
279     }
281     /**
282      * Get an array of all blocks within a given region
283      *
284      * @param string $region a block region that exists on this page.
285      * @return array of block instances.
286      */
287     public function get_blocks_for_region($region) {
288         $this->check_is_loaded();
289         $this->ensure_instances_exist($region);
290         return $this->blockinstances[$region];
291     }
293     /**
294      * Returns an array of block content objects that exist in a region
295      *
296      * @param string $region a block region that exists on this page.
297      * @return array of block block_contents objects for all the blocks in a region.
298      */
299     public function get_content_for_region($region, $output) {
300         $this->check_is_loaded();
301         $this->ensure_content_created($region, $output);
302         return $this->visibleblockcontent[$region];
303     }
305     /**
306      * Helper method used by get_content_for_region.
307      * @param string $region region name
308      * @param float $weight weight. May be fractional, since you may want to move a block
309      * between ones with weight 2 and 3, say ($weight would be 2.5).
310      * @return string URL for moving block $this->movingblock to this position.
311      */
312     protected function get_move_target_url($region, $weight) {
313         return new moodle_url($this->page->url, array('bui_moveid' => $this->movingblock,
314                 'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()));
315     }
317     /**
318      * Determine whether a region contains anything. (Either any real blocks, or
319      * the add new block UI.)
320      *
321      * (You may wonder why the $output parameter is required. Unfortunately,
322      * becuase of the way that blocks work, the only reliable way to find out
323      * if a block will be visible is to get the content for output, and to
324      * get the content, you need a renderer. Fortunately, this is not a
325      * performance problem, becuase we cache the output that is generated, and
326      * in almost every case where we call region_has_content, we are about to
327      * output the blocks anyway, so we are not doing wasted effort.)
328      *
329      * @param string $region a block region that exists on this page.
330      * @param object $output a core_renderer. normally the global $OUTPUT.
331      * @return boolean Whether there is anything in this region.
332      */
333     public function region_has_content($region, $output) {
335         if (!$this->is_known_region($region)) {
336             return false;
337         }
338         $this->check_is_loaded();
339         $this->ensure_content_created($region, $output);
340         if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks()) {
341             // If editing is on, we need all the block regions visible, for the
342             // move blocks UI.
343             return true;
344         }
345         return !empty($this->visibleblockcontent[$region]) || !empty($this->extracontent[$region]);
346     }
348     /**
349      * Get an array of all of the installed blocks.
350      *
351      * @return array contents of the block table.
352      */
353     public function get_installed_blocks() {
354         global $DB;
355         if (is_null($this->allblocks)) {
356             $this->allblocks = $DB->get_records('block');
357         }
358         return $this->allblocks;
359     }
361 /// Setter methods =============================================================
363     /**
364      * Add a region to a page
365      *
366      * @param string $region add a named region where blocks may appear on the
367      * current page. This is an internal name, like 'side-pre', not a string to
368      * display in the UI.
369      */
370     public function add_region($region) {
371         $this->check_not_yet_loaded();
372         $this->regions[$region] = 1;
373     }
375     /**
376      * Add an array of regions
377      * @see add_region()
378      *
379      * @param array $regions this utility method calls add_region for each array element.
380      */
381     public function add_regions($regions) {
382         foreach ($regions as $region) {
383             $this->add_region($region);
384         }
385     }
387     /**
388      * Set the default region for new blocks on the page
389      *
390      * @param string $defaultregion the internal names of the region where new
391      * blocks should be added by default, and where any blocks from an
392      * unrecognised region are shown.
393      */
394     public function set_default_region($defaultregion) {
395         $this->check_not_yet_loaded();
396         if ($defaultregion) {
397             $this->check_region_is_known($defaultregion);
398         }
399         $this->defaultregion = $defaultregion;
400     }
402     /**
403      * Add something that looks like a block, but which isn't an actual block_instance,
404      * to this page.
405      *
406      * @param block_contents $bc the content of the block like thing.
407      * @param string $region a block region that exists on this page.
408      */
409     public function add_pretend_block($bc, $region) {
410         $this->page->initialise_theme_and_output();
411         $this->check_region_is_known($region);
412         if (array_key_exists($region, $this->visibleblockcontent)) {
413             throw new coding_exception('block_manager has already prepared the blocks in region ' .
414                     $region . 'for output. It is too late to add a pretend block.');
415         }
416         $this->extracontent[$region][] = $bc;
417     }
419     /**
420      * Checks to see whether all of the blocks within the given region are docked
421      *
422      * @param string $region
423      * @return bool True if all of the blocks within that region are docked
424      */
425     public function region_completely_docked($region, $output) {
426         if (!$this->page->theme->enable_dock) {
427             return false;
428         }
429         $this->check_is_loaded();
430         $this->ensure_content_created($region, $output);
431         foreach($this->visibleblockcontent[$region] as $instance) {
432             if (!empty($instance->content) && !get_user_preferences('docked_block_instance_'.$instance->blockinstanceid, 0)) {
433                 return false;
434             }
435         }
436         return true;
437     }
439 /// Actions ====================================================================
441     /**
442      * This method actually loads the blocks for our page from the database.
443      *
444      * @param boolean|null $includeinvisible
445      *      null (default) - load hidden blocks if $this->page->user_is_editing();
446      *      true - load hidden blocks.
447      *      false - don't load hidden blocks.
448      */
449     public function load_blocks($includeinvisible = null) {
450         global $DB, $CFG;
452         if (!is_null($this->birecordsbyregion)) {
453             // Already done.
454             return;
455         }
457         if ($CFG->version < 2009050619) {
458             // Upgrade/install not complete. Don't try too show any blocks.
459             $this->birecordsbyregion = array();
460             return;
461         }
463         // Ensure we have been initialised.
464         if (is_null($this->defaultregion)) {
465             $this->page->initialise_theme_and_output();
466             // If there are still no block regions, then there are no blocks on this page.
467             if (empty($this->regions)) {
468                 $this->birecordsbyregion = array();
469                 return;
470             }
471         }
473         // Check if we need to load normal blocks
474         if ($this->fakeblocksonly) {
475             $this->birecordsbyregion = $this->prepare_per_region_arrays();
476             return;
477         }
479         if (is_null($includeinvisible)) {
480             $includeinvisible = $this->page->user_is_editing();
481         }
482         if ($includeinvisible) {
483             $visiblecheck = '';
484         } else {
485             $visiblecheck = 'AND (bp.visible = 1 OR bp.visible IS NULL)';
486         }
488         $context = $this->page->context;
489         $contexttest = 'bi.parentcontextid = :contextid2';
490         $parentcontextparams = array();
491         $parentcontextids = get_parent_contexts($context);
492         if ($parentcontextids) {
493             list($parentcontexttest, $parentcontextparams) =
494                     $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED, 'parentcontext0000');
495             $contexttest = "($contexttest OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontexttest))";
496         }
498         $pagetypepatterns = matching_page_type_patterns($this->page->pagetype);
499         list($pagetypepatterntest, $pagetypepatternparams) =
500                 $DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED, 'pagetypepatterntest0000');
502         list($ccselect, $ccjoin) = context_instance_preload_sql('b.id', CONTEXT_BLOCK, 'ctx');
504         $params = array(
505             'subpage1' => $this->page->subpage,
506             'subpage2' => $this->page->subpage,
507             'contextid1' => $context->id,
508             'contextid2' => $context->id,
509             'pagetype' => $this->page->pagetype,
510         );
511         $sql = "SELECT
512                     bi.id,
513                     bp.id AS blockpositionid,
514                     bi.blockname,
515                     bi.parentcontextid,
516                     bi.showinsubcontexts,
517                     bi.pagetypepattern,
518                     bi.subpagepattern,
519                     bi.defaultregion,
520                     bi.defaultweight,
521                     COALESCE(bp.visible, 1) AS visible,
522                     COALESCE(bp.region, bi.defaultregion) AS region,
523                     COALESCE(bp.weight, bi.defaultweight) AS weight,
524                     bi.configdata
525                     $ccselect
527                 FROM {block_instances} bi
528                 JOIN {block} b ON bi.blockname = b.name
529                 LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id
530                                                   AND bp.contextid = :contextid1
531                                                   AND bp.pagetype = :pagetype
532                                                   AND bp.subpage = :subpage1
533                 $ccjoin
535                 WHERE
536                 $contexttest
537                 AND bi.pagetypepattern $pagetypepatterntest
538                 AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)
539                 $visiblecheck
540                 AND b.visible = 1
542                 ORDER BY
543                     COALESCE(bp.region, bi.defaultregion),
544                     COALESCE(bp.weight, bi.defaultweight),
545                     bi.id";
546         $blockinstances = $DB->get_recordset_sql($sql, $params + $parentcontextparams + $pagetypepatternparams);
548         $this->birecordsbyregion = $this->prepare_per_region_arrays();
549         $unknown = array();
550         foreach ($blockinstances as $bi) {
551             context_instance_preload($bi);
552             if ($this->is_known_region($bi->region)) {
553                 $this->birecordsbyregion[$bi->region][] = $bi;
554             } else {
555                 $unknown[] = $bi;
556             }
557         }
559         // Pages don't necessarily have a defaultregion. The  one time this can
560         // happen is when there are no theme block regions, but the script itself
561         // has a block region in the main content area.
562         if (!empty($this->defaultregion)) {
563             $this->birecordsbyregion[$this->defaultregion] =
564                     array_merge($this->birecordsbyregion[$this->defaultregion], $unknown);
565         }
566     }
568     /**
569      * Add a block to the current page, or related pages. The block is added to
570      * context $this->page->contextid. If $pagetypepattern $subpagepattern
571      *
572      * @param string $blockname The type of block to add.
573      * @param string $region the block region on this page to add the block to.
574      * @param integer $weight determines the order where this block appears in the region.
575      * @param boolean $showinsubcontexts whether this block appears in subcontexts, or just the current context.
576      * @param string|null $pagetypepattern which page types this block should appear on. Defaults to just the current page type.
577      * @param string|null $subpagepattern which subpage this block should appear on. NULL = any (the default), otherwise only the specified subpage.
578      */
579     public function add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern = NULL, $subpagepattern = NULL) {
580         global $DB;
581         $this->check_known_block_type($blockname);
582         $this->check_region_is_known($region);
584         if (empty($pagetypepattern)) {
585             $pagetypepattern = $this->page->pagetype;
586         }
588         $blockinstance = new stdClass;
589         $blockinstance->blockname = $blockname;
590         $blockinstance->parentcontextid = $this->page->context->id;
591         $blockinstance->showinsubcontexts = !empty($showinsubcontexts);
592         $blockinstance->pagetypepattern = $pagetypepattern;
593         $blockinstance->subpagepattern = $subpagepattern;
594         $blockinstance->defaultregion = $region;
595         $blockinstance->defaultweight = $weight;
596         $blockinstance->configdata = '';
597         $blockinstance->id = $DB->insert_record('block_instances', $blockinstance);
599         // Ensure the block context is created.
600         get_context_instance(CONTEXT_BLOCK, $blockinstance->id);
602         // If the new instance was created, allow it to do additional setup
603         if ($block = block_instance($blockname, $blockinstance)) {
604             $block->instance_create();
605         }
606     }
608     public function add_block_at_end_of_default_region($blockname) {
609         $defaulregion = $this->get_default_region();
611         $lastcurrentblock = end($this->birecordsbyregion[$defaulregion]);
612         if ($lastcurrentblock) {
613             $weight = $lastcurrentblock->weight + 1;
614         } else {
615             $weight = 0;
616         }
618         if ($this->page->subpage) {
619             $subpage = $this->page->subpage;
620         } else {
621             $subpage = null;
622         }
624         // Special case. Course view page type include the course format, but we
625         // want to add the block non-format-specifically.
626         $pagetypepattern = $this->page->pagetype;
627         if (strpos($pagetypepattern, 'course-view') === 0) {
628             $pagetypepattern = 'course-view-*';
629         }
631         $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);
632     }
634     /**
635      * Convenience method, calls add_block repeatedly for all the blocks in $blocks.
636      *
637      * @param array $blocks array with array keys the region names, and values an array of block names.
638      * @param string $pagetypepattern optional. Passed to @see add_block()
639      * @param string $subpagepattern optional. Passed to @see add_block()
640      */
641     public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {
642         $this->add_regions(array_keys($blocks));
643         foreach ($blocks as $region => $regionblocks) {
644             $weight = 0;
645             foreach ($regionblocks as $blockname) {
646                 $this->add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern, $subpagepattern);
647                 $weight += 1;
648             }
649         }
650     }
652     /**
653      * Move a block to a new position on this page.
654      *
655      * If this block cannot appear on any other pages, then we change defaultposition/weight
656      * in the block_instances table. Otherwise we just set the postition on this page.
657      *
658      * @param $blockinstanceid the block instance id.
659      * @param $newregion the new region name.
660      * @param $newweight the new weight.
661      */
662     public function reposition_block($blockinstanceid, $newregion, $newweight) {
663         global $DB;
665         $this->check_region_is_known($newregion);
666         $inst = $this->find_instance($blockinstanceid);
668         $bi = $inst->instance;
669         if ($bi->weight == $bi->defaultweight && $bi->region == $bi->defaultregion &&
670                 !$bi->showinsubcontexts && strpos($bi->pagetypepattern, '*') === false &&
671                 (!$this->page->subpage || $bi->subpagepattern)) {
673             // Set default position
674             $newbi = new stdClass;
675             $newbi->id = $bi->id;
676             $newbi->defaultregion = $newregion;
677             $newbi->defaultweight = $newweight;
678             $DB->update_record('block_instances', $newbi);
680             if ($bi->blockpositionid) {
681                 $bp = new stdClass;
682                 $bp->id = $bi->blockpositionid;
683                 $bp->region = $newregion;
684                 $bp->weight = $newweight;
685                 $DB->update_record('block_positions', $bp);
686             }
688         } else {
689             // Just set position on this page.
690             $bp = new stdClass;
691             $bp->region = $newregion;
692             $bp->weight = $newweight;
694             if ($bi->blockpositionid) {
695                 $bp->id = $bi->blockpositionid;
696                 $DB->update_record('block_positions', $bp);
698             } else {
699                 $bp->blockinstanceid = $bi->id;
700                 $bp->contextid = $this->page->context->id;
701                 $bp->pagetype = $this->page->pagetype;
702                 if ($this->page->subpage) {
703                     $bp->subpage = $this->page->subpage;
704                 } else {
705                     $bp->subpage = '';
706                 }
707                 $bp->visible = $bi->visible;
708                 $DB->insert_record('block_positions', $bp);
709             }
710         }
711     }
713     /**
714      * Find a given block by its instance id
715      *
716      * @param integer $instanceid
717      * @return object
718      */
719     public function find_instance($instanceid) {
720         foreach ($this->regions as $region => $notused) {
721             $this->ensure_instances_exist($region);
722             foreach($this->blockinstances[$region] as $instance) {
723                 if ($instance->instance->id == $instanceid) {
724                     return $instance;
725                 }
726             }
727         }
728         throw new block_not_on_page_exception($instanceid, $this->page);
729     }
731 /// Inner workings =============================================================
733     /**
734      * Check whether the page blocks have been loaded yet
735      *
736      * @return void Throws coding exception if already loaded
737      */
738     protected function check_not_yet_loaded() {
739         if (!is_null($this->birecordsbyregion)) {
740             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.');
741         }
742     }
744     /**
745      * Check whether the page blocks have been loaded yet
746      *
747      * Nearly identical to the above function {@link check_not_yet_loaded()} except different message
748      *
749      * @return void Throws coding exception if already loaded
750      */
751     protected function check_is_loaded() {
752         if (is_null($this->birecordsbyregion)) {
753             throw new coding_exception('block_manager has not yet loaded the blocks, to it is too soon to request the information you asked for.');
754         }
755     }
757     /**
758      * Check if a block type is known and usable
759      *
760      * @param string $blockname The block type name to search for
761      * @param bool $includeinvisible Include disabled block types in the intial pass
762      * @return void Coding Exception thrown if unknown or not enabled
763      */
764     protected function check_known_block_type($blockname, $includeinvisible = false) {
765         if (!$this->is_known_block_type($blockname, $includeinvisible)) {
766             if ($this->is_known_block_type($blockname, true)) {
767                 throw new coding_exception('Unknown block type ' . $blockname);
768             } else {
769                 throw new coding_exception('Block type ' . $blockname . ' has been disabled by the administrator.');
770             }
771         }
772     }
774     /**
775      * Check if a region is known by its name
776      *
777      * @param string $region
778      * @return void Coding Exception thrown if the region is not known
779      */
780     protected function check_region_is_known($region) {
781         if (!$this->is_known_region($region)) {
782             throw new coding_exception('Trying to reference an unknown block region ' . $region);
783         }
784     }
786     /**
787      * Returns an array of region names as keys and nested arrays for values
788      *
789      * @return array an array where the array keys are the region names, and the array
790      * values are empty arrays.
791      */
792     protected function prepare_per_region_arrays() {
793         $result = array();
794         foreach ($this->regions as $region => $notused) {
795             $result[$region] = array();
796         }
797         return $result;
798     }
800     /**
801      * Create a set of new block instance from a record array
802      *
803      * @param array $birecords An array of block instance records
804      * @return array An array of instantiated block_instance objects
805      */
806     protected function create_block_instances($birecords) {
807         $results = array();
808         foreach ($birecords as $record) {
809             if ($blockobject = block_instance($record->blockname, $record, $this->page)) {
810                 $results[] = $blockobject;
811             }
812         }
813         return $results;
814     }
816     /**
817      * Create all the bock instances for all the blocks that were loaded by
818      * load_blocks. This is used, for example, to ensure that all blocks get a
819      * chance to initialise themselves via the {@link block_base::specialize()}
820      * method, before any output is done.
821      */
822     public function create_all_block_instances() {
823         foreach ($this->get_regions() as $region) {
824             $this->ensure_instances_exist($region);
825         }
826     }
828     /**
829      * Return an array of content objects from a set of block instances
830      *
831      * @param array $instances An array of block instances
832      * @param renderer_base The renderer to use.
833      * @param string $region the region name.
834      * @return array An array of block_content (and possibly block_move_target) objects.
835      */
836     protected function create_block_contents($instances, $output, $region) {
837         $results = array();
839         $lastweight = 0;
840         $lastblock = 0;
841         if ($this->movingblock) {
842             $first = reset($instances);
843             if ($first) {
844                 $lastweight = $first->instance->weight - 2;
845             }
847             $strmoveblockhere = get_string('moveblockhere', 'block');
848         }
850         foreach ($instances as $instance) {
851             $content = $instance->get_content_for_output($output);
852             if (empty($content)) {
853                 continue;
854             }
856             if ($this->movingblock && $lastweight != $instance->instance->weight &&
857                     $content->blockinstanceid != $this->movingblock && $lastblock != $this->movingblock) {
858                 $results[] = new block_move_target($strmoveblockhere, $this->get_move_target_url($region, ($lastweight + $instance->instance->weight)/2));
859             }
861             if ($content->blockinstanceid == $this->movingblock) {
862                 $content->add_class('beingmoved');
863                 $content->annotation .= get_string('movingthisblockcancel', 'block',
864                         html_writer::link($this->page->url, get_string('cancel')));
865             }
867             $results[] = $content;
868             $lastweight = $instance->instance->weight;
869             $lastblock = $instance->instance->id;
870         }
872         if ($this->movingblock && $lastblock != $this->movingblock) {
873             $results[] = new block_move_target($strmoveblockhere, $this->get_move_target_url($region, $lastweight + 1));
874         }
875         return $results;
876     }
878     /**
879      * Ensure block instances exist for a given region
880      *
881      * @param string $region Check for bi's with the instance with this name
882      */
883     protected function ensure_instances_exist($region) {
884         $this->check_region_is_known($region);
885         if (!array_key_exists($region, $this->blockinstances)) {
886             $this->blockinstances[$region] =
887                     $this->create_block_instances($this->birecordsbyregion[$region]);
888         }
889     }
891     /**
892      * Ensure that there is some content within the given region
893      *
894      * @param string $region The name of the region to check
895      */
896     protected function ensure_content_created($region, $output) {
897         $this->ensure_instances_exist($region);
898         if (!array_key_exists($region, $this->visibleblockcontent)) {
899             $contents = array();
900             if (array_key_exists($region, $this->extracontent)) {
901                 $contents = $this->extracontent[$region];
902             }
903             $contents = array_merge($contents, $this->create_block_contents($this->blockinstances[$region], $output, $region));
904             if ($region == $this->defaultregion) {
905                 $addblockui = block_add_block_ui($this->page, $output);
906                 if ($addblockui) {
907                     $contents[] = $addblockui;
908                 }
909             }
910             $this->visibleblockcontent[$region] = $contents;
911         }
912     }
914 /// Process actions from the URL ===============================================
916     /**
917      * Get the appropriate list of editing icons for a block. This is used
918      * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}.
919      *
920      * @param $output The core_renderer to use when generating the output. (Need to get icon paths.)
921      * @return an array in the format for {@link block_contents::$controls}
922      */
923     public function edit_controls($block) {
924         global $CFG;
926         if (!isset($CFG->undeletableblocktypes) || (!is_array($CFG->undeletableblocktypes) && !is_string($CFG->undeletableblocktypes))) {
927             $CFG->undeletableblocktypes = array('navigation','settings');
928         } else if (is_string($CFG->undeletableblocktypes)) {
929             $CFG->undeletableblocktypes = explode(',', $CFG->undeletableblocktypes);
930         }
932         $controls = array();
933         $actionurl = $this->page->url->out(false, array('sesskey'=> sesskey()));
935         // Assign roles icon.
936         if (has_capability('moodle/role:assign', $block->context)) {
937             //TODO: please note it is sloppy to pass urls through page parameters!!
938             //      it is shortened because some web servers (e.g. IIS by default) give
939             //      a 'security' error if you try to pass a full URL as a GET parameter in another URL.
941             $return = $this->page->url->out(false);
942             $return = str_replace($CFG->wwwroot . '/', '', $return);
944             $controls[] = array('url' => $CFG->wwwroot . '/' . $CFG->admin .
945                     '/roles/assign.php?contextid=' . $block->context->id . '&returnurl=' . urlencode($return),
946                     'icon' => 'i/roles', 'caption' => get_string('assignroles', 'role'));
947         }
949         if ($this->page->user_can_edit_blocks()) {
950             // Show/hide icon.
951             if ($block->instance->visible) {
952                 $controls[] = array('url' => $actionurl . '&bui_hideid=' . $block->instance->id,
953                         'icon' => 't/hide', 'caption' => get_string('hide'));
954             } else {
955                 $controls[] = array('url' => $actionurl . '&bui_showid=' . $block->instance->id,
956                         'icon' => 't/show', 'caption' => get_string('show'));
957             }
958         }
960         if ($this->page->user_can_edit_blocks() || $block->user_can_edit()) {
961             // Edit config icon - always show - needed for positioning UI.
962             $controls[] = array('url' => $actionurl . '&bui_editid=' . $block->instance->id,
963                     'icon' => 't/edit', 'caption' => get_string('configuration'));
964         }
966         if ($this->page->user_can_edit_blocks() && $block->user_can_edit() && $block->user_can_addto($this->page)) {
967             if (!in_array($block->instance->blockname, $CFG->undeletableblocktypes)) {
968                 // Delete icon.
969                 $controls[] = array('url' => $actionurl . '&bui_deleteid=' . $block->instance->id,
970                         'icon' => 't/delete', 'caption' => get_string('delete'));
971             }
972         }
974         if ($this->page->user_can_edit_blocks()) {
975             // Move icon.
976             $controls[] = array('url' => $actionurl . '&bui_moveid=' . $block->instance->id,
977                     'icon' => 't/move', 'caption' => get_string('move'));
978         }
980         return $controls;
981     }
983     /**
984      * Process any block actions that were specified in the URL.
985      *
986      * This can only be done given a valid $page object.
987      *
988      * @param moodle_page $page the page to add blocks to.
989      * @return boolean true if anything was done. False if not.
990      */
991     public function process_url_actions() {
992         if (!$this->page->user_is_editing()) {
993             return false;
994         }
995         return $this->process_url_add() || $this->process_url_delete() ||
996             $this->process_url_show_hide() || $this->process_url_edit() ||
997             $this->process_url_move();
998     }
1000     /**
1001      * Handle adding a block.
1002      * @return boolean true if anything was done. False if not.
1003      */
1004     public function process_url_add() {
1005         $blocktype = optional_param('bui_addblock', null, PARAM_SAFEDIR);
1006         if (!$blocktype) {
1007             return false;
1008         }
1010         require_sesskey();
1012         if (!$this->page->user_can_edit_blocks()) {
1013             throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('addblock'));
1014         }
1016         if (!array_key_exists($blocktype, $this->get_addable_blocks())) {
1017             throw new moodle_exception('cannotaddthisblocktype', '', $this->page->url->out(), $blocktype);
1018         }
1020         $this->add_block_at_end_of_default_region($blocktype);
1022         // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1023         $this->page->ensure_param_not_in_url('bui_addblock');
1025         return true;
1026     }
1028     /**
1029      * Handle deleting a block.
1030      * @return boolean true if anything was done. False if not.
1031      */
1032     public function process_url_delete() {
1033         $blockid = optional_param('bui_deleteid', null, PARAM_INTEGER);
1034         if (!$blockid) {
1035             return false;
1036         }
1038         require_sesskey();
1040         $block = $this->page->blocks->find_instance($blockid);
1042         if (!$block->user_can_edit() || !$this->page->user_can_edit_blocks() || !$block->user_can_addto($this->page)) {
1043             throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('deleteablock'));
1044         }
1046         blocks_delete_instance($block->instance);
1048         // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1049         $this->page->ensure_param_not_in_url('bui_deleteid');
1051         return true;
1052     }
1054     /**
1055      * Handle showing or hiding a block.
1056      * @return boolean true if anything was done. False if not.
1057      */
1058     public function process_url_show_hide() {
1059         if ($blockid = optional_param('bui_hideid', null, PARAM_INTEGER)) {
1060             $newvisibility = 0;
1061         } else if ($blockid = optional_param('bui_showid', null, PARAM_INTEGER)) {
1062             $newvisibility = 1;
1063         } else {
1064             return false;
1065         }
1067         require_sesskey();
1069         $block = $this->page->blocks->find_instance($blockid);
1071         if (!$this->page->user_can_edit_blocks()) {
1072             throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('hideshowblocks'));
1073         }
1075         blocks_set_visibility($block->instance, $this->page, $newvisibility);
1077         // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1078         $this->page->ensure_param_not_in_url('bui_hideid');
1079         $this->page->ensure_param_not_in_url('bui_showid');
1081         return true;
1082     }
1084     /**
1085      * Handle showing/processing the submission from the block editing form.
1086      * @return boolean true if the form was submitted and the new config saved. Does not
1087      *      return if the editing form was displayed. False otherwise.
1088      */
1089     public function process_url_edit() {
1090         global $CFG, $DB, $PAGE;
1092         $blockid = optional_param('bui_editid', null, PARAM_INTEGER);
1093         if (!$blockid) {
1094             return false;
1095         }
1097         require_sesskey();
1098         require_once($CFG->dirroot . '/blocks/edit_form.php');
1100         $block = $this->find_instance($blockid);
1102         if (!$block->user_can_edit() && !$this->page->user_can_edit_blocks()) {
1103             throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1104         }
1106         $editpage = new moodle_page();
1107         $editpage->set_pagelayout('admin');
1108         $editpage->set_course($this->page->course);
1109         $editpage->set_context($block->context);
1110         if ($this->page->cm) {
1111             $editpage->set_cm($this->page->cm);
1112         }
1113         $editurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
1114         $editurlparams = $this->page->url->params();
1115         $editurlparams['bui_editid'] = $blockid;
1116         $editpage->set_url($editurlbase, $editurlparams);
1117         $editpage->set_block_actions_done();
1118         // At this point we are either going to redirect, or display the form, so
1119         // overwrite global $PAGE ready for this. (Formslib refers to it.)
1120         $PAGE = $editpage;
1122         $formfile = $CFG->dirroot . '/blocks/' . $block->name() . '/edit_form.php';
1123         if (is_readable($formfile)) {
1124             require_once($formfile);
1125             $classname = 'block_' . $block->name() . '_edit_form';
1126             if (!class_exists($classname)) {
1127                 $classname = 'block_edit_form';
1128             }
1129         } else {
1130             $classname = 'block_edit_form';
1131         }
1133         $mform = new $classname($editpage->url, $block, $this->page);
1134         $mform->set_data($block->instance);
1136         if ($mform->is_cancelled()) {
1137             redirect($this->page->url);
1139         } else if ($data = $mform->get_data()) {
1140             $bi = new stdClass;
1141             $bi->id = $block->instance->id;
1142             $bi->pagetypepattern = $data->bui_pagetypepattern;
1143             if (empty($data->bui_subpagepattern) || $data->bui_subpagepattern == '%@NULL@%') {
1144                 $bi->subpagepattern = null;
1145             } else {
1146                 $bi->subpagepattern = $data->bui_subpagepattern;
1147             }
1149             $parentcontext = get_context_instance_by_id($data->bui_parentcontextid);
1150             $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1152             // Updating stickiness and contexts.  See MDL-21375 for details.
1153             if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination
1154                 // Explicitly set the context
1155                 $bi->parentcontextid = $parentcontext->id;
1157                 // If the context type is > 0 then we'll explicitly set the block as sticky, otherwise not
1158                 $bi->showinsubcontexts = (int)(!empty($data->bui_contexts));
1160                 // If the block wants to be system-wide, then explicitly set that
1161                 if ($data->bui_contexts == 2) {   // Only possible on a frontpage or system page
1162                     $bi->parentcontextid = $systemcontext->id;
1164                 } else { // The block doesn't want to be system-wide, so let's ensure that
1165                     if ($parentcontext->id == $systemcontext->id) {  // We need to move it to the front page
1166                         $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
1167                         $bi->parentcontextid = $frontpagecontext->id;
1168                         $bi->pagetypepattern = '*';  // Just in case
1169                     }
1170                 }
1171             }
1173             $bi->defaultregion = $data->bui_defaultregion;
1174             $bi->defaultweight = $data->bui_defaultweight;
1175             $DB->update_record('block_instances', $bi);
1177             if (!empty($block->config)) {
1178                 $config = clone($block->config);
1179             } else {
1180                 $config = new stdClass;
1181             }
1182             foreach ($data as $configfield => $value) {
1183                 if (strpos($configfield, 'config_') !== 0) {
1184                     continue;
1185                 }
1186                 $field = substr($configfield, 7);
1187                 $config->$field = $value;
1188             }
1189             $block->instance_config_save($config);
1191             $bp = new stdClass;
1192             $bp->visible = $data->bui_visible;
1193             $bp->region = $data->bui_region;
1194             $bp->weight = $data->bui_weight;
1195             $needbprecord = !$data->bui_visible || $data->bui_region != $data->bui_defaultregion ||
1196                     $data->bui_weight != $data->bui_defaultweight;
1198             if ($block->instance->blockpositionid && !$needbprecord) {
1199                 $DB->delete_records('block_positions', array('id' => $block->instance->blockpositionid));
1201             } else if ($block->instance->blockpositionid && $needbprecord) {
1202                 $bp->id = $block->instance->blockpositionid;
1203                 $DB->update_record('block_positions', $bp);
1205             } else if ($needbprecord) {
1206                 $bp->blockinstanceid = $block->instance->id;
1207                 $bp->contextid = $this->page->context->id;
1208                 $bp->pagetype = $this->page->pagetype;
1209                 if ($this->page->subpage) {
1210                     $bp->subpage = $this->page->subpage;
1211                 } else {
1212                     $bp->subpage = '';
1213                 }
1214                 $DB->insert_record('block_positions', $bp);
1215             }
1217             redirect($this->page->url);
1219         } else {
1220             $strheading = get_string('blockconfiga', 'moodle', $block->get_title());
1221             $editpage->set_title($strheading);
1222             $editpage->set_heading($strheading);
1223             $editpage->navbar->add($strheading);
1224             $output = $editpage->get_renderer('core');
1225             echo $output->header();
1226             echo $output->heading($strheading, 2);
1227             $mform->display();
1228             echo $output->footer();
1229             exit;
1230         }
1231     }
1233     /**
1234      * Handle showing/processing the submission from the block editing form.
1235      * @return boolean true if the form was submitted and the new config saved. Does not
1236      *      return if the editing form was displayed. False otherwise.
1237      */
1238     public function process_url_move() {
1239         global $CFG, $DB, $PAGE;
1241         $blockid = optional_param('bui_moveid', null, PARAM_INTEGER);
1242         if (!$blockid) {
1243             return false;
1244         }
1246         require_sesskey();
1248         $block = $this->find_instance($blockid);
1250         if (!$this->page->user_can_edit_blocks()) {
1251             throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1252         }
1254         $newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT);
1255         $newweight = optional_param('bui_newweight', null, PARAM_FLOAT);
1256         if (!$newregion || is_null($newweight)) {
1257             // Don't have a valid target position yet, must be just starting the move.
1258             $this->movingblock = $blockid;
1259             $this->page->ensure_param_not_in_url('bui_moveid');
1260             return false;
1261         }
1263         if (!$this->is_known_region($newregion)) {
1264             throw new moodle_exception('unknownblockregion', '', $this->page->url, $newregion);
1265         }
1267         // Move this block. This may involve moving other nearby blocks.
1268         $blocks = $this->birecordsbyregion[$newregion];
1270         $maxweight = self::MAX_WEIGHT;
1271         $minweight = -self::MAX_WEIGHT;
1273         // Initialise the used weights and spareweights array with the default values
1274         $spareweights = array();
1275         $usedweights = array();
1276         for ($i = $minweight; $i <= $maxweight; $i++) {
1277             $spareweights[$i] = $i;
1278             $usedweights[$i] = array();
1279         }
1281         // Check each block and sort out where we have used weights
1282         foreach ($blocks as $bi) {
1283             if ($bi->weight > $maxweight) {
1284                 // If this statement is true then the blocks weight is more than the
1285                 // current maximum. To ensure that we can get the best block position
1286                 // we will initialise elements within the usedweights and spareweights
1287                 // arrays between the blocks weight (which will then be the new max) and
1288                 // the current max
1289                 $parseweight = $bi->weight;
1290                 while (!array_key_exists($parseweight, $usedweights)) {
1291                     $usedweights[$parseweight] = array();
1292                     $spareweights[$parseweight] = $parseweight;
1293                     $parseweight--;
1294                 }
1295                 $maxweight = $bi->weight;
1296             } else if ($bi->weight < $minweight) {
1297                 // As above except this time the blocks weight is LESS than the
1298                 // the current minimum, so we will initialise the array from the
1299                 // blocks weight (new minimum) to the current minimum
1300                 $parseweight = $bi->weight;
1301                 while (!array_key_exists($parseweight, $usedweights)) {
1302                     $usedweights[$parseweight] = array();
1303                     $spareweights[$parseweight] = $parseweight;
1304                     $parseweight++;
1305                 }
1306                 $minweight = $bi->weight;
1307             }
1308             if ($bi->id != $block->instance->id) {
1309                 unset($spareweights[$bi->weight]);
1310                 $usedweights[$bi->weight][] = $bi->id;
1311             }
1312         }
1314         // First we find the nearest gap in the list of weights.
1315         $bestdistance = max(abs($newweight - self::MAX_WEIGHT), abs($newweight + self::MAX_WEIGHT)) + 1;
1316         $bestgap = null;
1317         foreach ($spareweights as $spareweight) {
1318             if (abs($newweight - $spareweight) < $bestdistance) {
1319                 $bestdistance = abs($newweight - $spareweight);
1320                 $bestgap = $spareweight;
1321             }
1322         }
1324         // If there is no gap, we have to go outside -self::MAX_WEIGHT .. self::MAX_WEIGHT.
1325         if (is_null($bestgap)) {
1326             $bestgap = self::MAX_WEIGHT + 1;
1327             while (!empty($usedweights[$bestgap])) {
1328                 $bestgap++;
1329             }
1330         }
1332         // Now we know the gap we are aiming for, so move all the blocks along.
1333         if ($bestgap < $newweight) {
1334             $newweight = floor($newweight);
1335             for ($weight = $bestgap + 1; $weight <= $newweight; $weight++) {
1336                 foreach ($usedweights[$weight] as $biid) {
1337                     $this->reposition_block($biid, $newregion, $weight - 1);
1338                 }
1339             }
1340             $this->reposition_block($block->instance->id, $newregion, $newweight);
1341         } else {
1342             $newweight = ceil($newweight);
1343             for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {
1344                 foreach ($usedweights[$weight] as $biid) {
1345                     $this->reposition_block($biid, $newregion, $weight + 1);
1346                 }
1347             }
1348             $this->reposition_block($block->instance->id, $newregion, $newweight);
1349         }
1351         $this->page->ensure_param_not_in_url('bui_moveid');
1352         $this->page->ensure_param_not_in_url('bui_newregion');
1353         $this->page->ensure_param_not_in_url('bui_newweight');
1354         return true;
1355     }
1357     /**
1358      * Turns the display of normal blocks either on or off.
1359      *
1360      * @param bool $setting
1361      */
1362     public function show_only_fake_blocks($setting = true) {
1363         $this->fakeblocksonly = $setting;
1364     }
1367 /// Helper functions for working with block classes ============================
1369 /**
1370  * Call a class method (one that does not requrie a block instance) on a block class.
1371  *
1372  * @param string $blockname the name of the block.
1373  * @param string $method the method name.
1374  * @param array $param parameters to pass to the method.
1375  * @return mixed whatever the method returns.
1376  */
1377 function block_method_result($blockname, $method, $param = NULL) {
1378     if(!block_load_class($blockname)) {
1379         return NULL;
1380     }
1381     return call_user_func(array('block_'.$blockname, $method), $param);
1384 /**
1385  * Creates a new object of the specified block class.
1386  *
1387  * @param string $blockname the name of the block.
1388  * @param $instance block_instances DB table row (optional).
1389  * @param moodle_page $page the page this block is appearing on.
1390  * @return block_base the requested block instance.
1391  */
1392 function block_instance($blockname, $instance = NULL, $page = NULL) {
1393     if(!block_load_class($blockname)) {
1394         return false;
1395     }
1396     $classname = 'block_'.$blockname;
1397     $retval = new $classname;
1398     if($instance !== NULL) {
1399         if (is_null($page)) {
1400             global $PAGE;
1401             $page = $PAGE;
1402         }
1403         $retval->_load_instance($instance, $page);
1404     }
1405     return $retval;
1408 /**
1409  * Load the block class for a particular type of block.
1410  *
1411  * @param string $blockname the name of the block.
1412  * @return boolean success or failure.
1413  */
1414 function block_load_class($blockname) {
1415     global $CFG;
1417     if(empty($blockname)) {
1418         return false;
1419     }
1421     $classname = 'block_'.$blockname;
1423     if(class_exists($classname)) {
1424         return true;
1425     }
1427     $blockpath = $CFG->dirroot.'/blocks/'.$blockname.'/block_'.$blockname.'.php';
1429     if (file_exists($blockpath)) {
1430         require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
1431         include_once($blockpath);
1432     }else{
1433         //debugging("$blockname code does not exist in $blockpath", DEBUG_DEVELOPER);
1434         return false;
1435     }
1437     return class_exists($classname);
1440 /**
1441  * Given a specific page type, return all the page type patterns that might
1442  * match it.
1443  *
1444  * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1445  * @return array an array of all the page type patterns that might match this page type.
1446  */
1447 function matching_page_type_patterns($pagetype) {
1448     $patterns = array($pagetype);
1449     $bits = explode('-', $pagetype);
1450     if (count($bits) == 3 && $bits[0] == 'mod') {
1451         if ($bits[2] == 'view') {
1452             $patterns[] = 'mod-*-view';
1453         } else if ($bits[2] == 'index') {
1454             $patterns[] = 'mod-*-index';
1455         }
1456     }
1457     while (count($bits) > 0) {
1458         $patterns[] = implode('-', $bits) . '-*';
1459         array_pop($bits);
1460     }
1461     $patterns[] = '*';
1462     return $patterns;
1465 /// Functions update the blocks if required by the request parameters ==========
1467 /**
1468  * Return a {@link block_contents} representing the add a new block UI, if
1469  * this user is allowed to see it.
1470  *
1471  * @return block_contents an appropriate block_contents, or null if the user
1472  * cannot add any blocks here.
1473  */
1474 function block_add_block_ui($page, $output) {
1475     global $CFG, $OUTPUT;
1476     if (!$page->user_is_editing() || !$page->user_can_edit_blocks()) {
1477         return null;
1478     }
1480     $bc = new block_contents();
1481     $bc->title = get_string('addblock');
1482     $bc->add_class('block_adminblock');
1484     $missingblocks = $page->blocks->get_addable_blocks();
1485     if (empty($missingblocks)) {
1486         $bc->content = get_string('noblockstoaddhere');
1487         return $bc;
1488     }
1490     $menu = array();
1491     foreach ($missingblocks as $block) {
1492         $blockobject = block_instance($block->name);
1493         if ($blockobject !== false && $blockobject->user_can_addto($page)) {
1494             $menu[$block->name] = $blockobject->get_title();
1495         }
1496     }
1497     asort($menu, SORT_LOCALE_STRING);
1499     $actionurl = new moodle_url($page->url, array('sesskey'=>sesskey()));
1500     $select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');
1501     $bc->content = $OUTPUT->render($select);
1502     return $bc;
1505 // Functions that have been deprecated by block_manager =======================
1507 /**
1508  * @deprecated since Moodle 2.0 - use $page->blocks->get_addable_blocks();
1509  *
1510  * This function returns an array with the IDs of any blocks that you can add to your page.
1511  * Parameters are passed by reference for speed; they are not modified at all.
1512  *
1513  * @param $page the page object.
1514  * @param $blockmanager Not used.
1515  * @return array of block type ids.
1516  */
1517 function blocks_get_missing(&$page, &$blockmanager) {
1518     debugging('blocks_get_missing is deprecated. Please use $page->blocks->get_addable_blocks() instead.', DEBUG_DEVELOPER);
1519     $blocks = $page->blocks->get_addable_blocks();
1520     $ids = array();
1521     foreach ($blocks as $block) {
1522         $ids[] = $block->id;
1523     }
1524     return $ids;
1527 /**
1528  * Actually delete from the database any blocks that are currently on this page,
1529  * but which should not be there according to blocks_name_allowed_in_format.
1530  *
1531  * @todo Write/Fix this function. Currently returns immediatly
1532  * @param $course
1533  */
1534 function blocks_remove_inappropriate($course) {
1535     // TODO
1536     return;
1537     $blockmanager = blocks_get_by_page($page);
1539     if (empty($blockmanager)) {
1540         return;
1541     }
1543     if (($pageformat = $page->pagetype) == NULL) {
1544         return;
1545     }
1547     foreach($blockmanager as $region) {
1548         foreach($region as $instance) {
1549             $block = blocks_get_record($instance->blockid);
1550             if(!blocks_name_allowed_in_format($block->name, $pageformat)) {
1551                blocks_delete_instance($instance->instance);
1552             }
1553         }
1554     }
1557 /**
1558  * Check that a given name is in a permittable format
1559  *
1560  * @param string $name
1561  * @param string $pageformat
1562  * @return bool
1563  */
1564 function blocks_name_allowed_in_format($name, $pageformat) {
1565     $accept = NULL;
1566     $maxdepth = -1;
1567     $formats = block_method_result($name, 'applicable_formats');
1568     if (!$formats) {
1569         $formats = array();
1570     }
1571     foreach ($formats as $format => $allowed) {
1572         $formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';
1573         $depth = substr_count($format, '-');
1574         if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {
1575             $maxdepth = $depth;
1576             $accept = $allowed;
1577         }
1578     }
1579     if ($accept === NULL) {
1580         $accept = !empty($formats['all']);
1581     }
1582     return $accept;
1585 /**
1586  * Delete a block, and associated data.
1587  *
1588  * @param object $instance a row from the block_instances table
1589  * @param bool $nolongerused legacy parameter. Not used, but kept for bacwards compatibility.
1590  * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.
1591  */
1592 function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {
1593     global $DB;
1595     if ($block = block_instance($instance->blockname, $instance)) {
1596         $block->instance_delete();
1597     }
1598     delete_context(CONTEXT_BLOCK, $instance->id);
1600     if (!$skipblockstables) {
1601         $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id));
1602         $DB->delete_records('block_instances', array('id' => $instance->id));
1603     }
1606 /**
1607  * Delete all the blocks that belong to a particular context.
1608  *
1609  * @param int $contextid the context id.
1610  */
1611 function blocks_delete_all_for_context($contextid) {
1612     global $DB;
1613     $instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));
1614     foreach ($instances as $instance) {
1615         blocks_delete_instance($instance, true);
1616     }
1617     $instances->close();
1618     $DB->delete_records('block_instances', array('parentcontextid' => $contextid));
1619     $DB->delete_records('block_positions', array('contextid' => $contextid));
1622 /**
1623  * Set a block to be visible or hidden on a particular page.
1624  *
1625  * @param object $instance a row from the block_instances, preferably LEFT JOINed with the
1626  *      block_positions table as return by block_manager.
1627  * @param moodle_page $page the back to set the visibility with respect to.
1628  * @param integer $newvisibility 1 for visible, 0 for hidden.
1629  */
1630 function blocks_set_visibility($instance, $page, $newvisibility) {
1631     global $DB;
1632     if (!empty($instance->blockpositionid)) {
1633         // Already have local information on this page.
1634         $DB->set_field('block_positions', 'visible', $newvisibility, array('id' => $instance->blockpositionid));
1635         return;
1636     }
1638     // Create a new block_positions record.
1639     $bp = new stdClass;
1640     $bp->blockinstanceid = $instance->id;
1641     $bp->contextid = $page->context->id;
1642     $bp->pagetype = $page->pagetype;
1643     if ($page->subpage) {
1644         $bp->subpage = $page->subpage;
1645     }
1646     $bp->visible = $newvisibility;
1647     $bp->region = $instance->defaultregion;
1648     $bp->weight = $instance->defaultweight;
1649     $DB->insert_record('block_positions', $bp);
1652 /**
1653  * @deprecated since 2.0
1654  * Delete all the blocks from a particular page.
1655  *
1656  * @param string $pagetype the page type.
1657  * @param integer $pageid the page id.
1658  * @return bool success or failure.
1659  */
1660 function blocks_delete_all_on_page($pagetype, $pageid) {
1661     global $DB;
1663     debugging('Call to deprecated function blocks_delete_all_on_page. ' .
1664             'This function cannot work any more. Doing nothing. ' .
1665             'Please update your code to use a block_manager method $PAGE->blocks->....', DEBUG_DEVELOPER);
1666     return false;
1669 /**
1670  * Dispite what this function is called, it seems to be mostly used to populate
1671  * the default blocks when a new course (or whatever) is created.
1672  *
1673  * @deprecated since 2.0
1674  *
1675  * @param object $page the page to add default blocks to.
1676  * @return boolean success or failure.
1677  */
1678 function blocks_repopulate_page($page) {
1679     global $CFG;
1681     debugging('Call to deprecated function blocks_repopulate_page. ' .
1682             'Use a more specific method like blocks_add_default_course_blocks, ' .
1683             'or just call $PAGE->blocks->add_blocks()', DEBUG_DEVELOPER);
1685     /// If the site override has been defined, it is the only valid one.
1686     if (!empty($CFG->defaultblocks_override)) {
1687         $blocknames = $CFG->defaultblocks_override;
1688     } else {
1689         $blocknames = $page->blocks_get_default();
1690     }
1692     $blocks = blocks_parse_default_blocks_list($blocknames);
1693     $page->blocks->add_blocks($blocks);
1695     return true;
1698 /**
1699  * Get the block record for a particular blockid - that is, a particul type os block.
1700  *
1701  * @param $int blockid block type id. If null, an array of all block types is returned.
1702  * @param bool $notusedanymore No longer used.
1703  * @return array|object row from block table, or all rows.
1704  */
1705 function blocks_get_record($blockid = NULL, $notusedanymore = false) {
1706     global $PAGE;
1707     $blocks = $PAGE->blocks->get_installed_blocks();
1708     if ($blockid === NULL) {
1709         return $blocks;
1710     } else if (isset($blocks[$blockid])) {
1711         return $blocks[$blockid];
1712     } else {
1713         return false;
1714     }
1717 /**
1718  * Find a given block by its blockid within a provide array
1719  *
1720  * @param int $blockid
1721  * @param array $blocksarray
1722  * @return bool|object Instance if found else false
1723  */
1724 function blocks_find_block($blockid, $blocksarray) {
1725     if (empty($blocksarray)) {
1726         return false;
1727     }
1728     foreach($blocksarray as $blockgroup) {
1729         if (empty($blockgroup)) {
1730             continue;
1731         }
1732         foreach($blockgroup as $instance) {
1733             if($instance->blockid == $blockid) {
1734                 return $instance;
1735             }
1736         }
1737     }
1738     return false;
1741 // Functions for programatically adding default blocks to pages ================
1743 /**
1744  * Parse a list of default blocks. See config-dist for a description of the format.
1745  *
1746  * @param string $blocksstr
1747  * @return array
1748  */
1749 function blocks_parse_default_blocks_list($blocksstr) {
1750     $blocks = array();
1751     $bits = explode(':', $blocksstr);
1752     if (!empty($bits)) {
1753         $leftbits = trim(array_shift($bits));
1754         if ($leftbits != '') {
1755             $blocks[BLOCK_POS_LEFT] = explode(',', $leftbits);
1756         }
1757     }
1758     if (!empty($bits)) {
1759         $rightbits =trim(array_shift($bits));
1760         if ($rightbits != '') {
1761             $blocks[BLOCK_POS_RIGHT] = explode(',', $rightbits);
1762         }
1763     }
1764     return $blocks;
1767 /**
1768  * @return array the blocks that should be added to the site course by default.
1769  */
1770 function blocks_get_default_site_course_blocks() {
1771     global $CFG;
1773     if (!empty($CFG->defaultblocks_site)) {
1774         return blocks_parse_default_blocks_list($CFG->defaultblocks_site);
1775     } else {
1776         return array(
1777             BLOCK_POS_LEFT => array('site_main_menu'),
1778             BLOCK_POS_RIGHT => array('course_summary', 'calendar_month')
1779         );
1780     }
1783 /**
1784  * Add the default blocks to a course.
1785  *
1786  * @param object $course a course object.
1787  */
1788 function blocks_add_default_course_blocks($course) {
1789     global $CFG;
1791     if (!empty($CFG->defaultblocks_override)) {
1792         $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override);
1794     } else if ($course->id == SITEID) {
1795         $blocknames = blocks_get_default_site_course_blocks();
1797     } else {
1798         $defaultblocks = 'defaultblocks_' . $course->format;
1799         if (!empty($CFG->$defaultblocks)) {
1800             $blocknames = blocks_parse_default_blocks_list($CFG->$defaultblocks);
1802         } else {
1803             $formatconfig = $CFG->dirroot.'/course/format/'.$course->format.'/config.php';
1804             if (is_readable($formatconfig)) {
1805                 require($formatconfig);
1806             }
1807             if (!empty($format['defaultblocks'])) {
1808                 $blocknames = blocks_parse_default_blocks_list($format['defaultblocks']);
1810             } else if (!empty($CFG->defaultblocks)){
1811                 $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks);
1813             } else {
1814                 $blocknames = array(
1815                     BLOCK_POS_LEFT => array(),
1816                     BLOCK_POS_RIGHT => array('search_forums', 'news_items', 'calendar_upcoming', 'recent_activity')
1817                 );
1818             }
1819         }
1820     }
1822     if ($course->id == SITEID) {
1823         $pagetypepattern = 'site-index';
1824     } else {
1825         $pagetypepattern = 'course-view-*';
1826     }
1828     $page = new moodle_page();
1829     $page->set_course($course);
1830     $page->blocks->add_blocks($blocknames, $pagetypepattern);
1833 /**
1834  * Add the default system-context blocks. E.g. the admin tree.
1835  */
1836 function blocks_add_default_system_blocks() {
1837     global $DB;
1839     $page = new moodle_page();
1840     $page->set_context(get_context_instance(CONTEXT_SYSTEM));
1841     $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('navigation', 'settings')), '*', null, true);
1842     $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('admin_bookmarks')), 'admin-*', null, null, 2);
1844     if ($defaultmypage = $DB->get_record('my_pages', array('userid'=>null, 'name'=>'__default', 'private'=>1))) {
1845         $subpagepattern = $defaultmypage->id;
1846     } else {
1847         $subpagepattern = null;
1848     }
1850     $page->blocks->add_blocks(array(BLOCK_POS_RIGHT => array('private_files', 'online_users'), 'content' => array('course_overview')), 'my-index', $subpagepattern, false);