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