Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
// This file is part of Moodle - http://moodle.org/
4
//
5
// Moodle is free software: you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation, either version 3 of the License, or
8
// (at your option) any later version.
9
//
10
// Moodle is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
// GNU General Public License for more details.
14
//
15
// You should have received a copy of the GNU General Public License
16
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17
 
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
 */
28
 
29
defined('MOODLE_INTERNAL') || die();
30
 
31
/**#@+
32
 * Default names for the block regions in the standard theme.
33
 */
34
define('BLOCK_POS_LEFT',  'side-pre');
35
define('BLOCK_POS_RIGHT', 'side-post');
36
/**#@-*/
37
 
38
define('BUI_CONTEXTS_FRONTPAGE_ONLY', 0);
39
define('BUI_CONTEXTS_FRONTPAGE_SUBS', 1);
40
define('BUI_CONTEXTS_ENTIRE_SITE', 2);
41
 
42
define('BUI_CONTEXTS_CURRENT', 0);
43
define('BUI_CONTEXTS_CURRENT_SUBS', 1);
44
 
45
// Position of "Add block" control, to be used in theme config as a value for $THEME->addblockposition:
46
// - default: as a fake block that is displayed in editing mode
47
// - flatnav: "Add block" item in the flat navigation drawer in editing mode
48
// - custom: none of the above, theme will take care of displaying the control.
49
define('BLOCK_ADDBLOCK_POSITION_DEFAULT', 0);
50
define('BLOCK_ADDBLOCK_POSITION_FLATNAV', 1);
51
define('BLOCK_ADDBLOCK_POSITION_CUSTOM', -1);
52
 
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
     * Constructor
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
}
74
 
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;
90
 
91
/// Field declarations =========================================================
92
 
93
    /**
94
     * the moodle_page we are managing blocks for.
95
     * @var moodle_page
96
     */
97
    protected $page;
98
 
99
    /** @var array region name => 1.*/
100
    protected $regions = array();
101
 
102
    /** @var string the region where new blocks are added.*/
103
    protected $defaultregion = null;
104
 
105
    /** @var array will be $DB->get_records('blocks') */
106
    protected $allblocks = null;
107
 
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;
114
 
115
    /**
116
     * Will be an array region-name => array(db rows loaded in load_blocks);
117
     * @var array
118
     */
119
    protected $birecordsbyregion = null;
120
 
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();
127
 
128
    /**
129
     * array region-name => array(block_contents objects) what actually needs to
130
     * be displayed in each region.
131
     * @var array
132
     */
133
    protected $visibleblockcontent = array();
134
 
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();
141
 
142
    /**
143
     * Used by the block move id, to track whether a block is currently being moved.
144
     *
145
     * When you click on the move icon of a block, first the page needs to reload with
146
     * extra UI for choosing 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;
152
 
153
    /**
154
     * Show only fake blocks
155
     */
156
    protected $fakeblocksonly = false;
157
 
158
/// Constructor ================================================================
159
 
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 class
164
     * and {@link http://en.wikipedia.org/wiki/Duck_typing})
165
     */
166
    public function __construct($page) {
167
        $this->page = $page;
168
    }
169
 
170
/// Getter methods =============================================================
171
 
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
    }
183
 
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
    }
196
 
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();
204
 
205
        if (!is_null($this->addableblocks)) {
206
            return $this->addableblocks;
207
        }
208
 
209
        // Lazy load.
210
        $this->addableblocks = array();
211
 
212
        $allblocks = blocks_get_record();
213
        if (empty($allblocks)) {
214
            return $this->addableblocks;
215
        }
216
 
217
        $undeletableblocks = self::get_undeletable_block_types();
218
        $unaddablebythemeblocks = $this->get_unaddable_by_theme_block_types();
219
        $requiredbythemeblocks = $this->get_required_by_theme_block_types();
220
        $pageformat = $this->page->pagetype;
221
        foreach($allblocks as $block) {
222
            if (!$bi = block_instance($block->name)) {
223
                continue;
224
            }
225
            if ($block->visible && !in_array($block->name, $undeletableblocks) &&
226
                    !in_array($block->name, $requiredbythemeblocks) &&
227
                    !in_array($block->name, $unaddablebythemeblocks) &&
228
                    $bi->can_block_be_added($this->page) &&
229
                    ($bi->instance_allow_multiple() || !$this->is_block_present($block->name)) &&
230
                    blocks_name_allowed_in_format($block->name, $pageformat) &&
231
                    $bi->user_can_addto($this->page)) {
232
                $block->title = $bi->get_title();
233
                $this->addableblocks[$block->name] = $block;
234
            }
235
        }
236
 
237
        core_collator::asort_objects_by_property($this->addableblocks, 'title');
238
        return $this->addableblocks;
239
    }
240
 
241
    /**
242
     * Given a block name, find out of any of them are currently present in the page
243
 
244
     * @param string $blockname - the basic name of a block (eg "navigation")
245
     * @return boolean - is there one of these blocks in the current page?
246
     */
247
    public function is_block_present($blockname) {
248
        if (empty($this->blockinstances)) {
249
            return false;
250
        }
251
 
252
        $requiredbythemeblocks = $this->get_required_by_theme_block_types();
253
        foreach ($this->blockinstances as $region) {
254
            foreach ($region as $instance) {
255
                if (empty($instance->instance->blockname)) {
256
                    continue;
257
                }
258
                if ($instance->instance->blockname == $blockname) {
259
                    if ($instance->instance->requiredbytheme) {
260
                        if (!in_array($blockname, $requiredbythemeblocks)) {
261
                            continue;
262
                        }
263
                    }
264
                    return true;
265
                }
266
            }
267
        }
268
        return false;
269
    }
270
 
271
    /**
272
     * Find out if a block type is known by the system
273
     *
274
     * @param string $blockname the name of the type of block.
275
     * @param boolean $includeinvisible if false (default) only check 'visible' blocks, that is, blocks enabled by the admin.
276
     * @return boolean true if this block in installed.
277
     */
278
    public function is_known_block_type($blockname, $includeinvisible = false) {
279
        $blocks = $this->get_installed_blocks();
280
        foreach ($blocks as $block) {
281
            if ($block->name == $blockname && ($includeinvisible || $block->visible)) {
282
                return true;
283
            }
284
        }
285
        return false;
286
    }
287
 
288
    /**
289
     * Find out if a region exists on a page
290
     *
291
     * @param string $region a region name
292
     * @return boolean true if this region exists on this page.
293
     */
294
    public function is_known_region($region) {
295
        if (empty($region)) {
296
            return false;
297
        }
298
        return array_key_exists($region, $this->regions);
299
    }
300
 
301
    /**
302
     * Get an array of all blocks within a given region
303
     *
304
     * @param string $region a block region that exists on this page.
305
     * @return array of block instances.
306
     */
307
    public function get_blocks_for_region($region) {
308
        $this->check_is_loaded();
309
        $this->ensure_instances_exist($region);
310
        return $this->blockinstances[$region];
311
    }
312
 
313
    /**
314
     * Returns an array of block content objects that exist in a region
315
     *
316
     * @param string $region a block region that exists on this page.
317
     * @return array of block block_contents objects for all the blocks in a region.
318
     */
319
    public function get_content_for_region($region, $output) {
320
        $this->check_is_loaded();
321
        $this->ensure_content_created($region, $output);
322
        return $this->visibleblockcontent[$region];
323
    }
324
 
325
    /**
326
     * Returns an array of block content objects for all the existings regions
327
     *
328
     * @param renderer_base $output the rendered to use
329
     * @return array of block block_contents objects for all the blocks in all regions.
330
     * @since  Moodle 3.3
331
     */
332
    public function get_content_for_all_regions($output) {
333
        $contents = array();
334
        $this->check_is_loaded();
335
 
336
        foreach ($this->regions as $region => $val) {
337
            $this->ensure_content_created($region, $output);
338
            $contents[$region] = $this->visibleblockcontent[$region];
339
        }
340
        return $contents;
341
    }
342
 
343
    /**
344
     * Helper method used by get_content_for_region.
345
     * @param string $region region name
346
     * @param float $weight weight. May be fractional, since you may want to move a block
347
     * between ones with weight 2 and 3, say ($weight would be 2.5).
348
     * @return moodle_url URL for moving block $this->movingblock to this position.
349
     */
350
    protected function get_move_target_url($region, $weight) {
351
        return new moodle_url($this->page->url, array('bui_moveid' => $this->movingblock,
352
                'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()));
353
    }
354
 
355
    /**
356
     * Determine whether a region contains anything. (Either any real blocks, or
357
     * the add new block UI.)
358
     *
359
     * (You may wonder why the $output parameter is required. Unfortunately,
360
     * because of the way that blocks work, the only reliable way to find out
361
     * if a block will be visible is to get the content for output, and to
362
     * get the content, you need a renderer. Fortunately, this is not a
363
     * performance problem, because we cache the output that is generated, and
364
     * in almost every case where we call region_has_content, we are about to
365
     * output the blocks anyway, so we are not doing wasted effort.)
366
     *
367
     * @param string $region a block region that exists on this page.
368
     * @param core_renderer $output a core_renderer. normally the global $OUTPUT.
369
     * @return boolean Whether there is anything in this region.
370
     */
371
    public function region_has_content($region, $output) {
372
 
373
        if (!$this->is_known_region($region)) {
374
            return false;
375
        }
376
        $this->check_is_loaded();
377
        $this->ensure_content_created($region, $output);
378
        // if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks()) {
379
        // Mark Nielsen's patch - part 1
380
        if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks() && $this->movingblock) {
381
            // If editing is on, we need all the block regions visible, for the
382
            // move blocks UI.
383
            return true;
384
        }
385
        return !empty($this->visibleblockcontent[$region]) || !empty($this->extracontent[$region]);
386
    }
387
 
388
    /**
389
     * Determine whether a region contains any fake blocks.
390
     *
391
     * (Fake blocks are typically added to the extracontent array per region)
392
     *
393
     * @param string $region a block region that exists on this page.
394
     * @return boolean Whether there are fake blocks in this region.
395
     */
396
    public function region_has_fakeblocks($region): bool {
397
        return !empty($this->extracontent[$region]);
398
    }
399
 
400
    /**
401
     * Get an array of all of the installed blocks.
402
     *
403
     * @return array contents of the block table.
404
     */
405
    public function get_installed_blocks() {
406
        global $DB;
407
        if (is_null($this->allblocks)) {
408
            $this->allblocks = $DB->get_records('block');
409
        }
410
        return $this->allblocks;
411
    }
412
 
413
    /**
414
     * @return array names of block types that must exist on every page with this theme.
415
     */
416
    public function get_required_by_theme_block_types() {
417
        $requiredbythemeblocks = false;
418
        if (isset($this->page->theme->requiredblocks)) {
419
            $requiredbythemeblocks = $this->page->theme->requiredblocks;
420
        }
421
 
422
        if ($requiredbythemeblocks === false) {
423
            return array('navigation', 'settings');
424
        } else if ($requiredbythemeblocks === '') {
425
            return array();
426
        } else if (is_string($requiredbythemeblocks)) {
427
            return explode(',', $requiredbythemeblocks);
428
        } else {
429
            return $requiredbythemeblocks;
430
        }
431
    }
432
 
433
    /**
434
     * It returns the list of blocks that can't be displayed in the "Add a block" list.
435
     * This information is taken from the unaddableblocks theme setting.
436
     *
437
     * @return array A list with the blocks that won't be displayed in the "Add a block" list.
438
     */
439
    public function get_unaddable_by_theme_block_types(): array {
440
        $unaddablebythemeblocks = [];
441
        if (isset($this->page->theme->settings->unaddableblocks) && !empty($this->page->theme->settings->unaddableblocks)) {
442
            $unaddablebythemeblocks = array_map('trim', explode(',', $this->page->theme->settings->unaddableblocks));
443
        }
444
 
445
        return $unaddablebythemeblocks;
446
    }
447
 
448
    /**
449
     * Make this block type undeletable and unaddable.
450
     *
451
     * @param mixed $blockidorname string or int
452
     */
453
    public static function protect_block($blockidorname) {
454
        global $DB;
455
 
456
        $syscontext = context_system::instance();
457
 
458
        require_capability('moodle/site:config', $syscontext);
459
 
460
        $block = false;
461
        if (is_int($blockidorname)) {
462
            $block = $DB->get_record('block', array('id' => $blockidorname), 'id, name', MUST_EXIST);
463
        } else {
464
            $block = $DB->get_record('block', array('name' => $blockidorname), 'id, name', MUST_EXIST);
465
        }
466
        $undeletableblocktypes = self::get_undeletable_block_types();
467
        if (!in_array($block->name, $undeletableblocktypes)) {
468
            $undeletableblocktypes[] = $block->name;
469
            set_config('undeletableblocktypes', implode(',', $undeletableblocktypes));
470
            add_to_config_log('block_protect', "0", "1", $block->name);
471
        }
472
    }
473
 
474
    /**
475
     * Make this block type deletable and addable.
476
     *
477
     * @param mixed $blockidorname string or int
478
     */
479
    public static function unprotect_block($blockidorname) {
480
        global $DB;
481
 
482
        $syscontext = context_system::instance();
483
 
484
        require_capability('moodle/site:config', $syscontext);
485
 
486
        $block = false;
487
        if (is_int($blockidorname)) {
488
            $block = $DB->get_record('block', array('id' => $blockidorname), 'id, name', MUST_EXIST);
489
        } else {
490
            $block = $DB->get_record('block', array('name' => $blockidorname), 'id, name', MUST_EXIST);
491
        }
492
        $undeletableblocktypes = self::get_undeletable_block_types();
493
        if (in_array($block->name, $undeletableblocktypes)) {
494
            $undeletableblocktypes = array_diff($undeletableblocktypes, array($block->name));
495
            set_config('undeletableblocktypes', implode(',', $undeletableblocktypes));
496
            add_to_config_log('block_protect', "1", "0", $block->name);
497
        }
498
 
499
    }
500
 
501
    /**
502
     * Get the list of "protected" blocks via admin block manager ui.
503
     *
504
     * @return array names of block types that cannot be added or deleted. E.g. array('navigation','settings').
505
     */
506
    public static function get_undeletable_block_types() {
507
        global $CFG;
508
        $undeletableblocks = false;
509
        if (isset($CFG->undeletableblocktypes)) {
510
            $undeletableblocks = $CFG->undeletableblocktypes;
511
        }
512
 
513
        if (empty($undeletableblocks)) {
514
            return array();
515
        } else if (is_string($undeletableblocks)) {
516
            return explode(',', $undeletableblocks);
517
        } else {
518
            return $undeletableblocks;
519
        }
520
    }
521
 
522
/// Setter methods =============================================================
523
 
524
    /**
525
     * Add a region to a page
526
     *
527
     * @param string $region add a named region where blocks may appear on the current page.
528
     *      This is an internal name, like 'side-pre', not a string to display in the UI.
529
     * @param bool $custom True if this is a custom block region, being added by the page rather than the theme layout.
530
     */
531
    public function add_region($region, $custom = true) {
532
        global $SESSION;
533
        $this->check_not_yet_loaded();
534
        if ($custom) {
535
            if (array_key_exists($region, $this->regions)) {
536
                // This here is EXACTLY why we should not be adding block regions into a page. It should
537
                // ALWAYS be done in a theme layout.
538
                debugging('A custom region conflicts with a block region in the theme.', DEBUG_DEVELOPER);
539
            }
540
            // We need to register this custom region against the page type being used.
541
            // This allows us to check, when performing block actions, that unrecognised regions can be worked with.
542
            $type = $this->page->pagetype;
543
            if (!isset($SESSION->custom_block_regions)) {
544
                $SESSION->custom_block_regions = array($type => array($region));
545
            } else if (!isset($SESSION->custom_block_regions[$type])) {
546
                $SESSION->custom_block_regions[$type] = array($region);
547
            } else if (!in_array($region, $SESSION->custom_block_regions[$type])) {
548
                $SESSION->custom_block_regions[$type][] = $region;
549
            }
550
        }
551
        $this->regions[$region] = 1;
552
 
553
        // Checking the actual property instead of calling get_default_region as it ends up in a recursive call.
554
        if (empty($this->defaultregion)) {
555
            $this->set_default_region($region);
556
        }
557
    }
558
 
559
    /**
560
     * Add an array of regions
561
     * @see add_region()
562
     *
563
     * @param array $regions this utility method calls add_region for each array element.
564
     */
565
    public function add_regions($regions, $custom = true) {
566
        foreach ($regions as $region) {
567
            $this->add_region($region, $custom);
568
        }
569
    }
570
 
571
    /**
572
     * Finds custom block regions associated with a page type and registers them with this block manager.
573
     *
574
     * @param string $pagetype
575
     */
576
    public function add_custom_regions_for_pagetype($pagetype) {
577
        global $SESSION;
578
        if (isset($SESSION->custom_block_regions[$pagetype])) {
579
            foreach ($SESSION->custom_block_regions[$pagetype] as $customregion) {
580
                $this->add_region($customregion, false);
581
            }
582
        }
583
    }
584
 
585
    /**
586
     * Set the default region for new blocks on the page
587
     *
588
     * @param string $defaultregion the internal names of the region where new
589
     * blocks should be added by default, and where any blocks from an
590
     * unrecognised region are shown.
591
     */
592
    public function set_default_region($defaultregion) {
593
        $this->check_not_yet_loaded();
594
        if ($defaultregion) {
595
            $this->check_region_is_known($defaultregion);
596
        }
597
        $this->defaultregion = $defaultregion;
598
    }
599
 
600
    /**
601
     * Add something that looks like a block, but which isn't an actual block_instance,
602
     * to this page.
603
     *
604
     * @param block_contents $bc the content of the block-like thing.
605
     * @param string $region a block region that exists on this page.
606
     */
607
    public function add_fake_block($bc, $region) {
608
        $this->page->initialise_theme_and_output();
609
        if (!$this->is_known_region($region)) {
610
            $region = $this->get_default_region();
611
        }
612
        if (array_key_exists($region, $this->visibleblockcontent)) {
613
            throw new coding_exception('block_manager has already prepared the blocks in region ' .
614
                    $region . 'for output. It is too late to add a fake block.');
615
        }
616
        if (!isset($bc->attributes['data-block'])) {
617
            $bc->attributes['data-block'] = '_fake';
618
        }
619
        $bc->attributes['class'] .= ' block_fake';
620
        $this->extracontent[$region][] = $bc;
621
    }
622
 
623
    /**
624
     * Checks to see whether all of the blocks within the given region are docked
625
     *
626
     * @see region_uses_dock
627
     * @param string $region
628
     * @return bool True if all of the blocks within that region are docked
629
     *
630
     * Return false as from MDL-64506
631
     */
632
    public function region_completely_docked($region, $output) {
633
        return false;
634
    }
635
 
636
    /**
637
     * Checks to see whether any of the blocks within the given regions are docked
638
     *
639
     * @see region_completely_docked
640
     * @param array|string $regions array of regions (or single region)
641
     * @return bool True if any of the blocks within that region are docked
642
     *
643
     * Return false as from MDL-64506
644
     */
645
    public function region_uses_dock($regions, $output) {
646
        return false;
647
    }
648
 
649
/// Actions ====================================================================
650
 
651
    /**
652
     * This method actually loads the blocks for our page from the database.
653
     *
654
     * @param boolean|null $includeinvisible
655
     *      null (default) - load hidden blocks if $this->page->user_is_editing();
656
     *      true - load hidden blocks.
657
     *      false - don't load hidden blocks.
658
     */
659
    public function load_blocks($includeinvisible = null) {
660
        global $DB, $CFG;
661
 
662
        if (!is_null($this->birecordsbyregion)) {
663
            // Already done.
664
            return;
665
        }
666
 
667
        if ($CFG->version < 2009050619) {
668
            // Upgrade/install not complete. Don't try too show any blocks.
669
            $this->birecordsbyregion = array();
670
            return;
671
        }
672
 
673
        // Ensure we have been initialised.
674
        if (is_null($this->defaultregion)) {
675
            $this->page->initialise_theme_and_output();
676
            // If there are still no block regions, then there are no blocks on this page.
677
            if (empty($this->regions)) {
678
                $this->birecordsbyregion = array();
679
                return;
680
            }
681
        }
682
 
683
        // Check if we need to load normal blocks
684
        if ($this->fakeblocksonly) {
685
            $this->birecordsbyregion = $this->prepare_per_region_arrays();
686
            return;
687
        }
688
 
689
        // Exclude auto created blocks if they are not undeletable in this theme.
690
        $requiredbytheme = $this->get_required_by_theme_block_types();
691
        $requiredbythemecheck = '';
692
        $requiredbythemeparams = array();
693
        $requiredbythemenotparams = array();
694
        if (!empty($requiredbytheme)) {
695
            list($testsql, $requiredbythemeparams) = $DB->get_in_or_equal($requiredbytheme, SQL_PARAMS_NAMED, 'requiredbytheme');
696
            list($testnotsql, $requiredbythemenotparams) = $DB->get_in_or_equal($requiredbytheme, SQL_PARAMS_NAMED,
697
                                                                                'notrequiredbytheme', false);
698
            $requiredbythemecheck = 'AND ((bi.blockname ' . $testsql . ' AND bi.requiredbytheme = 1) OR ' .
699
                                ' (bi.blockname ' . $testnotsql . ' AND bi.requiredbytheme = 0))';
700
        } else {
701
            $requiredbythemecheck = 'AND (bi.requiredbytheme = 0)';
702
        }
703
 
704
        if (is_null($includeinvisible)) {
705
            $includeinvisible = $this->page->user_is_editing();
706
        }
707
        if ($includeinvisible) {
708
            $visiblecheck = '';
709
        } else {
710
            $visiblecheck = 'AND (bp.visible = 1 OR bp.visible IS NULL) AND (bs.visible = 1 OR bs.visible IS NULL)';
711
        }
712
 
713
        $context = $this->page->context;
714
        $contexttest = 'bi.parentcontextid IN (:contextid2, :contextid3)';
715
        $parentcontextparams = array();
716
        $parentcontextids = $context->get_parent_context_ids();
717
        if ($parentcontextids) {
718
            list($parentcontexttest, $parentcontextparams) =
719
                    $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED, 'parentcontext');
720
            $contexttest = "($contexttest OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontexttest))";
721
        }
722
 
723
        $pagetypepatterns = matching_page_type_patterns($this->page->pagetype);
724
        list($pagetypepatterntest, $pagetypepatternparams) =
725
                $DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED, 'pagetypepatterntest');
726
 
727
        $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
728
        $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = bi.id AND ctx.contextlevel = :contextlevel)";
729
 
730
        $systemcontext = context_system::instance();
731
        $params = array(
732
            'contextlevel' => CONTEXT_BLOCK,
733
            'subpage1' => $this->page->subpage,
734
            'subpage2' => $this->page->subpage,
735
            'subpage3' => $this->page->subpage,
736
            'contextid1' => $context->id,
737
            'contextid2' => $context->id,
738
            'contextid3' => $systemcontext->id,
739
            'contextid4' => $systemcontext->id,
740
            'pagetype' => $this->page->pagetype,
741
            'pagetype2' => $this->page->pagetype,
742
        );
743
        if ($this->page->subpage === '') {
744
            $params['subpage1'] = '';
745
            $params['subpage2'] = '';
746
            $params['subpage3'] = '';
747
        }
748
        $sql = "SELECT
749
                    bi.id,
750
                    COALESCE(bp.id, bs.id) AS blockpositionid,
751
                    bi.blockname,
752
                    bi.parentcontextid,
753
                    bi.showinsubcontexts,
754
                    bi.pagetypepattern,
755
                    bi.requiredbytheme,
756
                    bi.subpagepattern,
757
                    bi.defaultregion,
758
                    bi.defaultweight,
759
                    COALESCE(bp.visible, bs.visible, 1) AS visible,
760
                    COALESCE(bp.region, bs.region, bi.defaultregion) AS region,
761
                    COALESCE(bp.weight, bs.weight, bi.defaultweight) AS weight,
762
                    bi.configdata
763
                    $ccselect
764
 
765
                FROM {block_instances} bi
766
                JOIN {block} b ON bi.blockname = b.name
767
                LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id
768
                                                  AND bp.contextid = :contextid1
769
                                                  AND bp.pagetype = :pagetype
770
                                                  AND bp.subpage = :subpage1
771
                LEFT JOIN {block_positions} bs ON bs.blockinstanceid = bi.id
772
                                                  AND bs.contextid = :contextid4
773
                                                  AND bs.pagetype = :pagetype2
774
                                                  AND bs.subpage = :subpage3
775
                $ccjoin
776
 
777
                WHERE
778
                $contexttest
779
                AND bi.pagetypepattern $pagetypepatterntest
780
                AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)
781
                $visiblecheck
782
                AND b.visible = 1
783
                $requiredbythemecheck
784
 
785
                ORDER BY
786
                    COALESCE(bp.region, bs.region, bi.defaultregion),
787
                    COALESCE(bp.weight, bs.weight, bi.defaultweight),
788
                    bi.id";
789
 
790
        $allparams = $params + $parentcontextparams + $pagetypepatternparams + $requiredbythemeparams + $requiredbythemenotparams;
791
        $blockinstances = $DB->get_recordset_sql($sql, $allparams);
792
 
793
        $this->birecordsbyregion = $this->prepare_per_region_arrays();
794
        $unknown = array();
795
        foreach ($blockinstances as $bi) {
796
            context_helper::preload_from_record($bi);
797
            if ($this->is_known_region($bi->region)) {
798
                $this->birecordsbyregion[$bi->region][] = $bi;
799
            } else {
800
                $unknown[] = $bi;
801
            }
802
        }
803
        $blockinstances->close();
804
 
805
        // Pages don't necessarily have a defaultregion. The  one time this can
806
        // happen is when there are no theme block regions, but the script itself
807
        // has a block region in the main content area.
808
        if (!empty($this->defaultregion)) {
809
            $this->birecordsbyregion[$this->defaultregion] =
810
                    array_merge($this->birecordsbyregion[$this->defaultregion], $unknown);
811
        }
812
    }
813
 
814
    /**
815
     * Add a block to the current page, or related pages. The block is added to
816
     * context $this->page->contextid. If $pagetypepattern $subpagepattern
817
     *
818
     * @param string $blockname The type of block to add.
819
     * @param string $region the block region on this page to add the block to.
820
     * @param integer $weight determines the order where this block appears in the region.
821
     * @param boolean $showinsubcontexts whether this block appears in subcontexts, or just the current context.
822
     * @param string|null $pagetypepattern which page types this block should appear on. Defaults to just the current page type.
823
     * @param string|null $subpagepattern which subpage this block should appear on. NULL = any (the default), otherwise only the specified subpage.
824
     * @return block_base
825
     */
826
    public function add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern = NULL, $subpagepattern = NULL) {
827
        global $DB;
828
        // Allow invisible blocks because this is used when adding default page blocks, which
829
        // might include invisible ones if the user makes some default blocks invisible
830
        $this->check_known_block_type($blockname, true);
831
        $this->check_region_is_known($region);
832
 
833
        if (empty($pagetypepattern)) {
834
            $pagetypepattern = $this->page->pagetype;
835
        }
836
 
837
        $blockinstance = new stdClass;
838
        $blockinstance->blockname = $blockname;
839
        $blockinstance->parentcontextid = $this->page->context->id;
840
        $blockinstance->showinsubcontexts = !empty($showinsubcontexts);
841
        $blockinstance->pagetypepattern = $pagetypepattern;
842
        $blockinstance->subpagepattern = $subpagepattern;
843
        $blockinstance->defaultregion = $region;
844
        $blockinstance->defaultweight = $weight;
845
        $blockinstance->configdata = '';
846
        $blockinstance->timecreated = time();
847
        $blockinstance->timemodified = $blockinstance->timecreated;
848
        $blockinstance->id = $DB->insert_record('block_instances', $blockinstance);
849
 
850
        // Ensure the block context is created.
851
        context_block::instance($blockinstance->id);
852
 
853
        // If the new instance was created, allow it to do additional setup
854
        if ($block = block_instance($blockname, $blockinstance)) {
855
            $block->instance_create();
856
        }
857
 
858
        if (!is_null($this->birecordsbyregion)) {
859
            // If blocks were already loaded on this page, reload them.
860
            $this->birecordsbyregion = null;
861
            $this->load_blocks();
862
        }
863
        return $block;
864
    }
865
 
866
    /**
867
     * When passed a block name create a new instance of the block in the specified region.
868
     *
869
     * @param string $blockname Name of the block to add.
870
     * @param null|string $blockregion If defined add the new block to the specified region.
871
     * @return ?block_base
872
     */
873
    public function add_block_at_end_of_default_region($blockname, $blockregion = null) {
874
        if (empty($this->birecordsbyregion)) {
875
            // No blocks or block regions exist yet.
876
            return null;
877
        }
878
 
879
        if ($blockregion === null) {
880
            $defaulregion = $this->get_default_region();
881
        } else {
882
            $defaulregion = $blockregion;
883
        }
884
 
885
        $lastcurrentblock = end($this->birecordsbyregion[$defaulregion]);
886
        if ($lastcurrentblock) {
887
            $weight = $lastcurrentblock->weight + 1;
888
        } else {
889
            $weight = 0;
890
        }
891
 
892
        if ($this->page->subpage) {
893
            $subpage = $this->page->subpage;
894
        } else {
895
            $subpage = null;
896
        }
897
 
898
        // Special case. Course view page type include the course format, but we
899
        // want to add the block non-format-specifically.
900
        $pagetypepattern = $this->page->pagetype;
901
        if (strpos($pagetypepattern, 'course-view') === 0) {
902
            $pagetypepattern = 'course-view-*';
903
        }
904
 
905
        // We should end using this for ALL the blocks, making always the 1st option
906
        // the default one to be used. Until then, this is one hack to avoid the
907
        // 'pagetypewarning' message on blocks initial edition (MDL-27829) caused by
908
        // non-existing $pagetypepattern set. This way at least we guarantee one "valid"
909
        // (the FIRST $pagetypepattern will be set)
910
 
911
        // We are applying it to all blocks created in mod pages for now and only if the
912
        // default pagetype is not one of the available options
913
        if (preg_match('/^mod-.*-/', $pagetypepattern)) {
914
            $pagetypelist = generate_page_type_patterns($this->page->pagetype, null, $this->page->context);
915
            // Only go for the first if the pagetype is not a valid option
916
            if (is_array($pagetypelist) && !array_key_exists($pagetypepattern, $pagetypelist)) {
917
                $pagetypepattern = key($pagetypelist);
918
            }
919
        }
920
        // Surely other pages like course-report will need this too, they just are not important
921
        // enough now. This will be decided in the coming days. (MDL-27829, MDL-28150)
922
 
923
        return $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);
924
    }
925
 
926
    /**
927
     * Convenience method, calls add_block repeatedly for all the blocks in $blocks. Optionally, a starting weight
928
     * can be used to decide the starting point that blocks are added in the region, the weight is passed to {@link add_block}
929
     * and incremented by the position of the block in the $blocks array
930
     *
931
     * @param array $blocks array with array keys the region names, and values an array of block names.
932
     * @param string|null $pagetypepattern optional. Passed to {@see self::add_block()}
933
     * @param string|null $subpagepattern optional. Passed to {@see self::add_block()}
934
     * @param bool $showinsubcontexts optional. Passed to {@see self::add_block()}
935
     * @param int $weight optional. Determines the starting point that the blocks are added in the region.
936
     */
937
    public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {
938
        $initialweight = $weight;
939
        $this->add_regions(array_keys($blocks), false);
940
        foreach ($blocks as $region => $regionblocks) {
941
            foreach ($regionblocks as $offset => $blockname) {
942
                $weight = $initialweight + $offset;
943
                $this->add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern, $subpagepattern);
944
            }
945
        }
946
    }
947
 
948
    /**
949
     * Move a block to a new position on this page.
950
     *
951
     * If this block cannot appear on any other pages, then we change defaultposition/weight
952
     * in the block_instances table. Otherwise we just set the position on this page.
953
     *
954
     * @param $blockinstanceid the block instance id.
955
     * @param $newregion the new region name.
956
     * @param $newweight the new weight.
957
     */
958
    public function reposition_block($blockinstanceid, $newregion, $newweight) {
959
        global $DB;
960
 
961
        $this->check_region_is_known($newregion);
962
        $inst = $this->find_instance($blockinstanceid);
963
 
964
        $bi = $inst->instance;
965
        if ($bi->weight == $bi->defaultweight && $bi->region == $bi->defaultregion &&
966
                !$bi->showinsubcontexts && strpos($bi->pagetypepattern, '*') === false &&
967
                (!$this->page->subpage || $bi->subpagepattern)) {
968
 
969
            // Set default position
970
            $newbi = new stdClass;
971
            $newbi->id = $bi->id;
972
            $newbi->defaultregion = $newregion;
973
            $newbi->defaultweight = $newweight;
974
            $newbi->timemodified = time();
975
            $DB->update_record('block_instances', $newbi);
976
 
977
            if ($bi->blockpositionid) {
978
                $bp = new stdClass;
979
                $bp->id = $bi->blockpositionid;
980
                $bp->region = $newregion;
981
                $bp->weight = $newweight;
982
                $DB->update_record('block_positions', $bp);
983
            }
984
 
985
        } else {
986
            // Just set position on this page.
987
            $bp = new stdClass;
988
            $bp->region = $newregion;
989
            $bp->weight = $newweight;
990
 
991
            if ($bi->blockpositionid) {
992
                $bp->id = $bi->blockpositionid;
993
                $DB->update_record('block_positions', $bp);
994
 
995
            } else {
996
                $bp->blockinstanceid = $bi->id;
997
                $bp->contextid = $this->page->context->id;
998
                $bp->pagetype = $this->page->pagetype;
999
                if ($this->page->subpage) {
1000
                    $bp->subpage = $this->page->subpage;
1001
                } else {
1002
                    $bp->subpage = '';
1003
                }
1004
                $bp->visible = $bi->visible;
1005
                $DB->insert_record('block_positions', $bp);
1006
            }
1007
        }
1008
    }
1009
 
1010
    /**
1011
     * Find a given block by its instance id
1012
     *
1013
     * @param integer $instanceid
1014
     * @return block_base
1015
     */
1016
    public function find_instance($instanceid) {
1017
        foreach ($this->regions as $region => $notused) {
1018
            $this->ensure_instances_exist($region);
1019
            foreach($this->blockinstances[$region] as $instance) {
1020
                if ($instance->instance->id == $instanceid) {
1021
                    return $instance;
1022
                }
1023
            }
1024
        }
1025
        throw new block_not_on_page_exception($instanceid, $this->page);
1026
    }
1027
 
1028
/// Inner workings =============================================================
1029
 
1030
    /**
1031
     * Check whether the page blocks have been loaded yet
1032
     *
1033
     * @return void Throws coding exception if already loaded
1034
     */
1035
    protected function check_not_yet_loaded() {
1036
        if (!is_null($this->birecordsbyregion)) {
1037
            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.');
1038
        }
1039
    }
1040
 
1041
    /**
1042
     * Check whether the page blocks have been loaded yet
1043
     *
1044
     * Nearly identical to the above function {@link check_not_yet_loaded()} except different message
1045
     *
1046
     * @return void Throws coding exception if already loaded
1047
     */
1048
    protected function check_is_loaded() {
1049
        if (is_null($this->birecordsbyregion)) {
1050
            throw new coding_exception('block_manager has not yet loaded the blocks, to it is too soon to request the information you asked for.');
1051
        }
1052
    }
1053
 
1054
    /**
1055
     * Check if a block type is known and usable
1056
     *
1057
     * @param string $blockname The block type name to search for
1058
     * @param bool $includeinvisible Include disabled block types in the initial pass
1059
     * @return void Coding Exception thrown if unknown or not enabled
1060
     */
1061
    protected function check_known_block_type($blockname, $includeinvisible = false) {
1062
        if (!$this->is_known_block_type($blockname, $includeinvisible)) {
1063
            if ($this->is_known_block_type($blockname, true)) {
1064
                throw new coding_exception('Unknown block type ' . $blockname);
1065
            } else {
1066
                throw new coding_exception('Block type ' . $blockname . ' has been disabled by the administrator.');
1067
            }
1068
        }
1069
    }
1070
 
1071
    /**
1072
     * Check if a region is known by its name
1073
     *
1074
     * @param string $region
1075
     * @return void Coding Exception thrown if the region is not known
1076
     */
1077
    protected function check_region_is_known($region) {
1078
        if (!$this->is_known_region($region)) {
1079
            throw new coding_exception('Trying to reference an unknown block region ' . $region);
1080
        }
1081
    }
1082
 
1083
    /**
1084
     * Returns an array of region names as keys and nested arrays for values
1085
     *
1086
     * @return array an array where the array keys are the region names, and the array
1087
     * values are empty arrays.
1088
     */
1089
    protected function prepare_per_region_arrays() {
1090
        $result = array();
1091
        foreach ($this->regions as $region => $notused) {
1092
            $result[$region] = array();
1093
        }
1094
        return $result;
1095
    }
1096
 
1097
    /**
1098
     * Create a set of new block instance from a record array
1099
     *
1100
     * @param array $birecords An array of block instance records
1101
     * @return array An array of instantiated block_instance objects
1102
     */
1103
    protected function create_block_instances($birecords) {
1104
        $results = array();
1105
        foreach ($birecords as $record) {
1106
            if ($blockobject = block_instance($record->blockname, $record, $this->page)) {
1107
                $results[] = $blockobject;
1108
            }
1109
        }
1110
        return $results;
1111
    }
1112
 
1113
    /**
1114
     * Create all the block instances for all the blocks that were loaded by
1115
     * load_blocks. This is used, for example, to ensure that all blocks get a
1116
     * chance to initialise themselves via the {@link block_base::specialize()}
1117
     * method, before any output is done.
1118
     *
1119
     * It is also used to create any blocks that are "requiredbytheme" by the current theme.
1120
     * These blocks that are auto-created have requiredbytheme set on the block instance
1121
     * so they are only visible on themes that require them.
1122
     */
1123
    public function create_all_block_instances() {
1124
        $missing = false;
1125
 
1126
        // If there are any un-removable blocks that were not created - force them.
1127
        $requiredbytheme = $this->get_required_by_theme_block_types();
1128
        if (!$this->fakeblocksonly) {
1129
            foreach ($requiredbytheme as $forced) {
1130
                if (empty($forced)) {
1131
                    continue;
1132
                }
1133
                $found = false;
1134
                foreach ($this->get_regions() as $region) {
1135
                    foreach($this->birecordsbyregion[$region] as $instance) {
1136
                        if ($instance->blockname == $forced) {
1137
                            $found = true;
1138
                        }
1139
                    }
1140
                }
1141
                if (!$found) {
1142
                    $this->add_block_required_by_theme($forced);
1143
                    $missing = true;
1144
                }
1145
            }
1146
        }
1147
 
1148
        if ($missing) {
1149
            // Some blocks were missing. Lets do it again.
1150
            $this->birecordsbyregion = null;
1151
            $this->load_blocks();
1152
        }
1153
        foreach ($this->get_regions() as $region) {
1154
            $this->ensure_instances_exist($region);
1155
        }
1156
 
1157
    }
1158
 
1159
    /**
1160
     * Add a block that is required by the current theme but has not been
1161
     * created yet. This is a special type of block that only shows in themes that
1162
     * require it (by listing it in undeletable_block_types).
1163
     *
1164
     * @param string $blockname the name of the block type.
1165
     */
1166
    protected function add_block_required_by_theme($blockname) {
1167
        global $DB;
1168
 
1169
        if (empty($this->birecordsbyregion)) {
1170
            // No blocks or block regions exist yet.
1171
            return;
1172
        }
1173
 
1174
        // Never auto create blocks when we are showing fake blocks only.
1175
        if ($this->fakeblocksonly) {
1176
            return;
1177
        }
1178
 
1179
        // Never add a duplicate block required by theme.
1180
        if ($DB->record_exists('block_instances', array('blockname' => $blockname, 'requiredbytheme' => 1))) {
1181
            return;
1182
        }
1183
 
1184
        $systemcontext = context_system::instance();
1185
        $defaultregion = $this->get_default_region();
1186
        // Add a special system wide block instance only for themes that require it.
1187
        $blockinstance = new stdClass;
1188
        $blockinstance->blockname = $blockname;
1189
        $blockinstance->parentcontextid = $systemcontext->id;
1190
        $blockinstance->showinsubcontexts = true;
1191
        $blockinstance->requiredbytheme = true;
1192
        $blockinstance->pagetypepattern = '*';
1193
        $blockinstance->subpagepattern = null;
1194
        $blockinstance->defaultregion = $defaultregion;
1195
        $blockinstance->defaultweight = 0;
1196
        $blockinstance->configdata = '';
1197
        $blockinstance->timecreated = time();
1198
        $blockinstance->timemodified = $blockinstance->timecreated;
1199
        $blockinstance->id = $DB->insert_record('block_instances', $blockinstance);
1200
 
1201
        // Ensure the block context is created.
1202
        context_block::instance($blockinstance->id);
1203
 
1204
        // If the new instance was created, allow it to do additional setup.
1205
        if ($block = block_instance($blockname, $blockinstance)) {
1206
            $block->instance_create();
1207
        }
1208
    }
1209
 
1210
    /**
1211
     * Return an array of content objects from a set of block instances
1212
     *
1213
     * @param array $instances An array of block instances
1214
     * @param renderer_base The renderer to use.
1215
     * @param string $region the region name.
1216
     * @return array An array of block_content (and possibly block_move_target) objects.
1217
     */
1218
    protected function create_block_contents($instances, $output, $region) {
1219
        $results = array();
1220
 
1221
        $lastweight = 0;
1222
        $lastblock = 0;
1223
        if ($this->movingblock) {
1224
            $first = reset($instances);
1225
            if ($first) {
1226
                $lastweight = $first->instance->weight - 2;
1227
            }
1228
        }
1229
 
1230
        foreach ($instances as $instance) {
1231
            $content = $instance->get_content_for_output($output);
1232
            if (empty($content)) {
1233
                continue;
1234
            }
1235
 
1236
            if ($this->movingblock && $lastweight != $instance->instance->weight &&
1237
                    $content->blockinstanceid != $this->movingblock && $lastblock != $this->movingblock) {
1238
                $results[] = new block_move_target($this->get_move_target_url($region, ($lastweight + $instance->instance->weight)/2));
1239
            }
1240
 
1241
            if ($content->blockinstanceid == $this->movingblock) {
1242
                $content->add_class('beingmoved');
1243
                $content->annotation .= get_string('movingthisblockcancel', 'block',
1244
                        html_writer::link($this->page->url, get_string('cancel')));
1245
            }
1246
 
1247
            $results[] = $content;
1248
            $lastweight = $instance->instance->weight;
1249
            $lastblock = $instance->instance->id;
1250
        }
1251
 
1252
        if ($this->movingblock && $lastblock != $this->movingblock) {
1253
            $results[] = new block_move_target($this->get_move_target_url($region, $lastweight + 1));
1254
        }
1255
        return $results;
1256
    }
1257
 
1258
    /**
1259
     * Ensure block instances exist for a given region
1260
     *
1261
     * @param string $region Check for bi's with the instance with this name
1262
     */
1263
    protected function ensure_instances_exist($region) {
1264
        $this->check_region_is_known($region);
1265
        if (!array_key_exists($region, $this->blockinstances)) {
1266
            $this->blockinstances[$region] =
1267
                    $this->create_block_instances($this->birecordsbyregion[$region]);
1268
        }
1269
    }
1270
 
1271
    /**
1272
     * Ensure that there is some content within the given region
1273
     *
1274
     * @param string $region The name of the region to check
1275
     */
1276
    public function ensure_content_created($region, $output) {
1277
        $this->ensure_instances_exist($region);
1278
 
1279
        if (!has_capability('moodle/block:view', $this->page->context) ) {
1280
            $this->visibleblockcontent[$region] = [];
1281
            return;
1282
        }
1283
 
1284
        if (!array_key_exists($region, $this->visibleblockcontent)) {
1285
            $contents = array();
1286
            if (array_key_exists($region, $this->extracontent)) {
1287
                $contents = $this->extracontent[$region];
1288
            }
1289
            $contents = array_merge($contents, $this->create_block_contents($this->blockinstances[$region], $output, $region));
1290
            if (($region == $this->defaultregion) && (!isset($this->page->theme->addblockposition) ||
1291
                    $this->page->theme->addblockposition == BLOCK_ADDBLOCK_POSITION_DEFAULT)) {
1292
                $addblockui = block_add_block_ui($this->page, $output);
1293
                if ($addblockui) {
1294
                    $contents[] = $addblockui;
1295
                }
1296
            }
1297
            $this->visibleblockcontent[$region] = $contents;
1298
        }
1299
    }
1300
 
1301
/// Process actions from the URL ===============================================
1302
 
1303
    /**
1304
     * Get the appropriate list of editing icons for a block. This is used
1305
     * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}.
1306
     *
1307
     * @param block_base $block
1308
     * @return array an array in the format for {@link block_contents::$controls}
1309
     */
1310
    public function edit_controls($block) {
1311
        global $CFG;
1312
 
1313
        $controls = array();
1314
        $actionurl = $this->page->url->out(false, array('sesskey'=> sesskey()));
1315
        $blocktitle = $block->title;
1316
        if (empty($blocktitle)) {
1317
            $blocktitle = $block->arialabel;
1318
        }
1319
 
1320
        if ($this->page->user_can_edit_blocks()) {
1321
            // Move icon.
1322
            $str = new lang_string('moveblock', 'block', $blocktitle);
1323
            $controls[] = new action_menu_link_primary(
1324
                new moodle_url($actionurl, array('bui_moveid' => $block->instance->id)),
1325
                new pix_icon('t/move', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1326
                $str,
1327
                array('class' => 'editing_move')
1328
            );
1329
 
1330
        }
1331
 
1332
        if ($this->page->user_can_edit_blocks() || $block->user_can_edit()) {
1333
            // Edit config icon - always show - needed for positioning UI.
1334
            $str = new lang_string('configureblock', 'block', $blocktitle);
1335
            $editactionurl = new moodle_url($actionurl, ['bui_editid' => $block->instance->id]);
1336
            $editactionurl->remove_params(['sesskey']);
1337
 
1338
            // Handle editing block on admin index page, prevent the page redirecting before block action can begin.
1339
            if ($editactionurl->compare(new moodle_url('/admin/index.php'), URL_MATCH_BASE)) {
1340
                $editactionurl->param('cache', 1);
1341
            }
1342
 
1343
            $controls[] = new action_menu_link_secondary(
1344
                $editactionurl,
1345
                new pix_icon('t/edit', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1346
                $str,
1347
                [
1348
                    'class' => 'editing_edit',
1349
                    'data-action' => 'editblock',
1350
                    'data-blockid' => $block->instance->id,
1351
                    'data-blockform' => self::get_block_edit_form_class($block->name()),
1352
                    'data-header' => $str,
1353
                ]
1354
            );
1355
 
1356
        }
1357
 
1358
        if ($this->page->user_can_edit_blocks() && $block->instance_can_be_hidden()) {
1359
            // Show/hide icon.
1360
            if ($block->instance->visible) {
1361
                $str = new lang_string('hideblock', 'block', $blocktitle);
1362
                $url = new moodle_url($actionurl, array('bui_hideid' => $block->instance->id));
1363
                $icon = new pix_icon('t/hide', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1364
                $attributes = array('class' => 'editing_hide');
1365
            } else {
1366
                $str = new lang_string('showblock', 'block', $blocktitle);
1367
                $url = new moodle_url($actionurl, array('bui_showid' => $block->instance->id));
1368
                $icon = new pix_icon('t/show', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1369
                $attributes = array('class' => 'editing_show');
1370
            }
1371
            $controls[] = new action_menu_link_secondary($url, $icon, $str, $attributes);
1372
        }
1373
 
1374
        // Assign roles.
1375
        if (get_assignable_roles($block->context, ROLENAME_SHORT)) {
1376
            $rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid' => $block->context->id,
1377
                'returnurl' => $this->page->url->out_as_local_url()));
1378
            $str = new lang_string('assignrolesinblock', 'block', $blocktitle);
1379
            $controls[] = new action_menu_link_secondary(
1380
                $rolesurl,
1381
                new pix_icon('i/assignroles', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1382
                $str, array('class' => 'editing_assignroles')
1383
            );
1384
        }
1385
 
1386
        // Permissions.
1387
        if (has_capability('moodle/role:review', $block->context) or get_overridable_roles($block->context)) {
1388
            $rolesurl = new moodle_url('/admin/roles/permissions.php', array('contextid' => $block->context->id,
1389
                'returnurl' => $this->page->url->out_as_local_url()));
1390
            $str = get_string('permissions', 'role');
1391
            $controls[] = new action_menu_link_secondary(
1392
                $rolesurl,
1393
                new pix_icon('i/permissions', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1394
                $str, array('class' => 'editing_permissions')
1395
            );
1396
        }
1397
 
1398
        // Change permissions.
1399
        if (has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:assign'), $block->context)) {
1400
            $rolesurl = new moodle_url('/admin/roles/check.php', array('contextid' => $block->context->id,
1401
                'returnurl' => $this->page->url->out_as_local_url()));
1402
            $str = get_string('checkpermissions', 'role');
1403
            $controls[] = new action_menu_link_secondary(
1404
                $rolesurl,
1405
                new pix_icon('i/checkpermissions', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1406
                $str, array('class' => 'editing_checkroles')
1407
            );
1408
        }
1409
 
1410
        if ($this->user_can_delete_block($block)) {
1411
            // Delete icon.
1412
            $str = new lang_string('deleteblock', 'block', $blocktitle);
1413
            $deleteactionurl = new moodle_url($actionurl, ['bui_deleteid' => $block->instance->id]);
1414
            $deleteactionurl->remove_params(['sesskey']);
1415
 
1416
            // Handle deleting block on admin index page, prevent the page redirecting before block action can begin.
1417
            if ($deleteactionurl->compare(new moodle_url('/admin/index.php'), URL_MATCH_BASE)) {
1418
                $deleteactionurl->param('cache', 1);
1419
            }
1420
 
1421
            $deleteconfirmationurl = new moodle_url($actionurl, [
1422
                'bui_deleteid' => $block->instance->id,
1423
                'bui_confirm' => 1,
1424
                'sesskey' => sesskey(),
1425
            ]);
1426
            $blocktitle = $block->get_title();
1427
 
1428
            $controls[] = new action_menu_link_secondary(
1429
                $deleteactionurl,
1430
                new pix_icon('t/delete', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1431
                $str,
1432
                [
1433
                    'class' => 'editing_delete',
1434
                    'data-modal' => 'confirmation',
1435
                    'data-modal-title-str' => json_encode(['deletecheck_modal', 'block']),
1436
                    'data-modal-content-str' => json_encode(['deleteblockcheck', 'block', $blocktitle]),
1437
                    'data-modal-yes-button-str' => json_encode(['delete', 'core']),
1438
                    'data-modal-toast' => 'true',
1439
                    'data-modal-toast-confirmation-str' => json_encode(['deleteblockinprogress', 'block', $blocktitle]),
1440
                    'data-modal-destination' => $deleteconfirmationurl->out(false),
1441
                ]
1442
            );
1443
        }
1444
 
1445
        if (!empty($CFG->contextlocking) && has_capability('moodle/site:managecontextlocks', $block->context)) {
1446
            $parentcontext = $block->context->get_parent_context();
1447
            if (empty($parentcontext) || empty($parentcontext->locked)) {
1448
                if ($block->context->locked) {
1449
                    $lockicon = 'i/unlock';
1450
                    $lockstring = get_string('managecontextunlock', 'admin');
1451
                } else {
1452
                    $lockicon = 'i/lock';
1453
                    $lockstring = get_string('managecontextlock', 'admin');
1454
                }
1455
                $controls[] = new action_menu_link_secondary(
1456
                    new moodle_url(
1457
                        '/admin/lock.php',
1458
                        [
1459
                            'id' => $block->context->id,
1460
                        ]
1461
                    ),
1462
                    new pix_icon($lockicon, $lockstring, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1463
                    $lockstring,
1464
                    ['class' => 'editing_lock']
1465
                );
1466
            }
1467
        }
1468
 
1469
        return $controls;
1470
    }
1471
 
1472
    /**
1473
     * @param block_base $block a block that appears on this page.
1474
     * @return boolean boolean whether the currently logged in user is allowed to delete this block.
1475
     */
1476
    protected function user_can_delete_block($block) {
1477
        return $this->page->user_can_edit_blocks() && $block->user_can_edit() &&
1478
                $block->user_can_addto($this->page) &&
1479
                !in_array($block->instance->blockname, self::get_undeletable_block_types()) &&
1480
                !in_array($block->instance->blockname, $this->get_required_by_theme_block_types());
1481
    }
1482
 
1483
    /**
1484
     * Process any block actions that were specified in the URL.
1485
     *
1486
     * @return boolean true if anything was done. False if not.
1487
     */
1488
    public function process_url_actions() {
1489
        if (!$this->page->user_is_editing()) {
1490
            return false;
1491
        }
1492
        return $this->process_url_add() || $this->process_url_delete() ||
1493
            $this->process_url_show_hide() || $this->process_url_edit() ||
1494
            $this->process_url_move();
1495
    }
1496
 
1497
    /**
1498
     * Handle adding a block.
1499
     * @return boolean true if anything was done. False if not.
1500
     */
1501
    public function process_url_add() {
1502
        global $CFG, $PAGE, $OUTPUT;
1503
 
1504
        $blocktype = optional_param('bui_addblock', null, PARAM_PLUGIN);
1505
        $blockregion = optional_param('bui_blockregion', null, PARAM_TEXT);
1506
 
1507
        if ($blocktype === null) {
1508
            return false;
1509
        }
1510
 
1511
        require_sesskey();
1512
 
1513
        if (!$this->page->user_can_edit_blocks()) {
1514
            throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('addblock'));
1515
        }
1516
 
1517
        $addableblocks = $this->get_addable_blocks();
1518
 
1519
        if ($blocktype === '') {
1520
            // Display add block selection.
1521
            $addpage = new moodle_page();
1522
            $addpage->set_pagelayout('admin');
1523
            $addpage->blocks->show_only_fake_blocks(true);
1524
            $addpage->set_course($this->page->course);
1525
            $addpage->set_context($this->page->context);
1526
            if ($this->page->cm) {
1527
                $addpage->set_cm($this->page->cm);
1528
            }
1529
 
1530
            $addpagebase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
1531
            $addpageparams = $this->page->url->params();
1532
            $addpage->set_url($addpagebase, $addpageparams);
1533
            $addpage->set_block_actions_done();
1534
            // At this point we are going to display the block selector, overwrite global $PAGE ready for this.
1535
            $PAGE = $addpage;
1536
            // Some functions use $OUTPUT so we need to replace that too.
1537
            /** @var core_renderer $OUTPUT */
1538
            $OUTPUT = $addpage->get_renderer('core');
1539
 
1540
            $site = get_site();
1541
            $straddblock = get_string('addblock');
1542
 
1543
            $PAGE->navbar->add($straddblock);
1544
            $PAGE->set_title($straddblock);
1545
            $PAGE->set_heading($site->fullname);
1546
            echo $OUTPUT->header();
1547
            echo $OUTPUT->heading($straddblock);
1548
 
1549
            if (!$addableblocks) {
1550
                echo $OUTPUT->box(get_string('noblockstoaddhere'));
1551
                echo $OUTPUT->container($OUTPUT->action_link($addpage->url, get_string('back')), 'mx-3 mb-1');
1552
            } else {
1553
                $url = new moodle_url($addpage->url, array('sesskey' => sesskey()));
1554
                echo $OUTPUT->render_from_template('core/add_block_body',
1555
                    ['blocks' => array_values($addableblocks),
1556
                     'url' => '?' . $url->get_query_string(false)]);
1557
                echo $OUTPUT->container($OUTPUT->action_link($addpage->url, get_string('cancel')), 'mx-3 mb-1');
1558
            }
1559
 
1560
            echo $OUTPUT->footer();
1561
            // Make sure that nothing else happens after we have displayed this form.
1562
            exit;
1563
        }
1564
 
1565
        if (!array_key_exists($blocktype, $addableblocks)) {
1566
            throw new moodle_exception('cannotaddthisblocktype', '', $this->page->url->out(), $blocktype);
1567
        }
1568
 
1569
        $this->add_block_at_end_of_default_region($blocktype, $blockregion);
1570
 
1571
        // If the page URL was a guess, it will contain the bui_... param, so we must make sure it is not there.
1572
        $this->page->ensure_param_not_in_url('bui_addblock');
1573
 
1574
        return true;
1575
    }
1576
 
1577
    /**
1578
     * Handle deleting a block.
1579
     * @return boolean true if anything was done. False if not.
1580
     */
1581
    public function process_url_delete() {
1582
        global $CFG, $PAGE, $OUTPUT;
1583
 
1584
        $blockid = optional_param('bui_deleteid', null, PARAM_INT);
1585
        $confirmdelete = optional_param('bui_confirm', null, PARAM_INT);
1586
 
1587
        if (!$blockid) {
1588
            return false;
1589
        }
1590
 
1591
        $block = $this->page->blocks->find_instance($blockid);
1592
        if (!$this->user_can_delete_block($block)) {
1593
            throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('deleteablock'));
1594
        }
1595
 
1596
        if (!$confirmdelete) {
1597
            $deletepage = new moodle_page();
1598
            $deletepage->set_pagelayout('admin');
1599
            $deletepage->blocks->show_only_fake_blocks(true);
1600
            $deletepage->set_course($this->page->course);
1601
            $deletepage->set_context($this->page->context);
1602
            if ($this->page->cm) {
1603
                $deletepage->set_cm($this->page->cm);
1604
            }
1605
 
1606
            $deleteurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
1607
            $deleteurlparams = $this->page->url->params();
1608
            $deletepage->set_url($deleteurlbase, $deleteurlparams);
1609
            $deletepage->set_block_actions_done();
1610
            $deletepage->set_secondarynav($this->get_secondarynav($block));
1611
            // At this point we are either going to redirect, or display the form, so
1612
            // overwrite global $PAGE ready for this. (Formslib refers to it.)
1613
            $PAGE = $deletepage;
1614
            //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that too
1615
            /** @var core_renderer $output */
1616
            $output = $deletepage->get_renderer('core');
1617
            $OUTPUT = $output;
1618
 
1619
            $site = get_site();
1620
            $blocktitle = $block->get_title();
1621
            $strdeletecheck = get_string('deletecheck', 'block', $blocktitle);
1622
            $message = get_string('deleteblockcheck', 'block', $blocktitle);
1623
 
1624
            // If the block is being shown in sub contexts display a warning.
1625
            if ($block->instance->showinsubcontexts == 1) {
1626
                $parentcontext = context::instance_by_id($block->instance->parentcontextid);
1627
                $systemcontext = context_system::instance();
1628
                $messagestring = new stdClass();
1629
                $messagestring->location = $parentcontext->get_context_name();
1630
 
1631
                // Checking for blocks that may have visibility on the front page and pages added on that.
1632
                if ($parentcontext->id != $systemcontext->id && is_inside_frontpage($parentcontext)) {
1633
                    $messagestring->pagetype = get_string('showonfrontpageandsubs', 'block');
1634
                } else {
1635
                    $pagetypes = generate_page_type_patterns($this->page->pagetype, $parentcontext);
1636
                    $messagestring->pagetype = $block->instance->pagetypepattern;
1637
                    if (isset($pagetypes[$block->instance->pagetypepattern])) {
1638
                        $messagestring->pagetype = $pagetypes[$block->instance->pagetypepattern];
1639
                    }
1640
                }
1641
 
1642
                $message = get_string('deleteblockwarning', 'block', $messagestring);
1643
            }
1644
 
1645
            $PAGE->navbar->add($strdeletecheck);
1646
            $PAGE->set_title($blocktitle . ': ' . $strdeletecheck);
1647
            $PAGE->set_heading($site->fullname);
1648
            echo $OUTPUT->header();
1649
            $confirmurl = new moodle_url($deletepage->url, array('sesskey' => sesskey(), 'bui_deleteid' => $block->instance->id, 'bui_confirm' => 1));
1650
            $cancelurl = new moodle_url($deletepage->url);
1651
            $yesbutton = new single_button($confirmurl, get_string('yes'));
1652
            $nobutton = new single_button($cancelurl, get_string('no'));
1653
            echo $OUTPUT->confirm($message, $yesbutton, $nobutton);
1654
            echo $OUTPUT->footer();
1655
            // Make sure that nothing else happens after we have displayed this form.
1656
            exit;
1657
        } else {
1658
            require_sesskey();
1659
 
1660
            blocks_delete_instance($block->instance);
1661
            // bui_deleteid and bui_confirm should not be in the PAGE url.
1662
            $this->page->ensure_param_not_in_url('bui_deleteid');
1663
            $this->page->ensure_param_not_in_url('bui_confirm');
1664
            return true;
1665
        }
1666
    }
1667
 
1668
    /**
1669
     * Returns the name of the class for block editing and makes sure it is autoloaded
1670
     *
1671
     * @param string $blockname name of the block plugin (without block_ prefix)
1672
     * @return string
1673
     */
1674
    public static function get_block_edit_form_class(string $blockname): string {
1675
        global $CFG;
1676
        require_once("$CFG->dirroot/blocks/moodleblock.class.php");
1677
        $blockname = clean_param($blockname, PARAM_PLUGIN);
1678
        $formfile = $CFG->dirroot . '/blocks/' . $blockname . '/edit_form.php';
1679
        if (is_readable($formfile)) {
1680
            require_once($CFG->dirroot . '/blocks/edit_form.php');
1681
            require_once($formfile);
1682
            $classname = 'block_' . $blockname . '_edit_form';
1683
            if (!class_exists($classname)) {
1684
                $classname = 'block_edit_form';
1685
            }
1686
        } else {
1687
            require_once($CFG->dirroot . '/blocks/edit_form.php');
1688
            $classname = 'block_edit_form';
1689
        }
1690
        return $classname;
1691
    }
1692
 
1693
    /**
1694
     * Handle showing or hiding a block.
1695
     * @return boolean true if anything was done. False if not.
1696
     */
1697
    public function process_url_show_hide() {
1698
        if ($blockid = optional_param('bui_hideid', null, PARAM_INT)) {
1699
            $newvisibility = 0;
1700
        } else if ($blockid = optional_param('bui_showid', null, PARAM_INT)) {
1701
            $newvisibility = 1;
1702
        } else {
1703
            return false;
1704
        }
1705
 
1706
        require_sesskey();
1707
 
1708
        $block = $this->page->blocks->find_instance($blockid);
1709
 
1710
        if (!$this->page->user_can_edit_blocks()) {
1711
            throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('hideshowblocks'));
1712
        } else if (!$block->instance_can_be_hidden()) {
1713
            return false;
1714
        }
1715
 
1716
        blocks_set_visibility($block->instance, $this->page, $newvisibility);
1717
 
1718
        // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1719
        $this->page->ensure_param_not_in_url('bui_hideid');
1720
        $this->page->ensure_param_not_in_url('bui_showid');
1721
 
1722
        return true;
1723
    }
1724
 
1725
    /**
1726
     * Convenience function to check whether a block is implementing a secondary nav class and return it
1727
     * initialised to the calling function
1728
     *
1729
     * @todo MDL-74939 Remove support for old 'local\views\secondary' class location
1730
     * @param block_base $block
1731
     * @return \core\navigation\views\secondary
1732
     */
1733
    protected function get_secondarynav(block_base $block): \core\navigation\views\secondary {
1734
        $class = "core_block\\navigation\\views\\secondary";
1735
        if (class_exists("block_{$block->name()}\\navigation\\views\\secondary")) {
1736
            $class = "block_{$block->name()}\\navigation\\views\\secondary";
1737
        } else if (class_exists("block_{$block->name()}\\local\\views\\secondary")) {
1738
            // For backwards compatibility, support the old location for this class (it was in a
1739
            // 'local' namespace which shouldn't be used for core APIs).
1740
            debugging("The class block_{$block->name()}\\local\\views\\secondary uses a deprecated " .
1741
                    "namespace. Please move it to block_{$block->name()}\\navigation\\views\\secondary.",
1742
                    DEBUG_DEVELOPER);
1743
            $class = "block_{$block->name()}\\local\\views\\secondary";
1744
        }
1745
        $secondarynav = new $class($this->page);
1746
        $secondarynav->initialise();
1747
        return $secondarynav;
1748
    }
1749
 
1750
    /**
1751
     * Handle showing/processing the submission from the block editing form.
1752
     * @return boolean true if the form was submitted and the new config saved. Does not
1753
     *      return if the editing form was displayed. False otherwise.
1754
     */
1755
    public function process_url_edit() {
1756
        global $CFG, $DB, $PAGE, $OUTPUT;
1757
 
1758
        $blockid = optional_param('bui_editid', null, PARAM_INT);
1759
        if (!$blockid) {
1760
            return false;
1761
        }
1762
 
1763
        require_once($CFG->dirroot . '/blocks/edit_form.php');
1764
 
1765
        $block = $this->find_instance($blockid);
1766
 
1767
        if (!$block->user_can_edit() && !$this->page->user_can_edit_blocks()) {
1768
            throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1769
        }
1770
 
1771
        $editpage = new moodle_page();
1772
        $editpage->set_pagelayout('admin');
1773
        $editpage->blocks->show_only_fake_blocks(true);
1774
        $editpage->set_course($this->page->course);
1775
        //$editpage->set_context($block->context);
1776
        $editpage->set_context($this->page->context);
1777
        $editpage->set_secondarynav($this->get_secondarynav($block));
1778
 
1779
        if ($this->page->cm) {
1780
            $editpage->set_cm($this->page->cm);
1781
        }
1782
        $editurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
1783
        $editurlparams = $this->page->url->params();
1784
        $editurlparams['bui_editid'] = $blockid;
1785
        $editpage->set_url($editurlbase, $editurlparams);
1786
        $editpage->set_block_actions_done();
1787
        // At this point we are either going to redirect, or display the form, so
1788
        // overwrite global $PAGE ready for this. (Formslib refers to it.)
1789
        $PAGE = $editpage;
1790
        //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that to
1791
        $output = $editpage->get_renderer('core');
1792
        $OUTPUT = $output;
1793
 
1794
        $classname = self::get_block_edit_form_class($block->name());
1795
        /** @var block_edit_form $mform */
1796
        $mform = new $classname($editpage->url->out(false), ['page' => $this->page, 'block' => $block, 'actionbuttons' => true]);
1797
        $mform->set_data($block->instance);
1798
 
1799
        if ($mform->is_cancelled()) {
1800
            redirect($this->page->url);
1801
 
1802
        } else if ($data = $mform->get_data()) {
1803
 
1804
            $this->save_block_data($block, $data);
1805
            redirect($this->page->url);
1806
 
1807
        } else {
1808
            $strheading = get_string('blockconfiga', 'moodle', $block->get_title());
1809
            $editpage->set_title($strheading);
1810
            $editpage->set_heading($strheading);
1811
            $bits = explode('-', $this->page->pagetype);
1812
            if ($bits[0] == 'tag' && !empty($this->page->subpage)) {
1813
                // better navbar for tag pages
1814
                $editpage->navbar->add(get_string('tags'), new moodle_url('/tag/'));
1815
                $tag = core_tag_tag::get($this->page->subpage);
1816
                // tag search page doesn't have subpageid
1817
                if ($tag) {
1818
                    $editpage->navbar->add($tag->get_display_name(), $tag->get_view_url());
1819
                }
1820
            }
1821
            $editpage->navbar->add($block->get_title());
1822
            $editpage->navbar->add(get_string('configuration'));
1823
            echo $output->header();
1824
            $mform->display();
1825
            echo $output->footer();
1826
            exit;
1827
        }
1828
    }
1829
 
1830
    /**
1831
     * Updates block configuration in the database
1832
     *
1833
     * @param block_base $block
1834
     * @param stdClass $data data from the block edit form
1835
     * @return void
1836
     */
1837
    public function save_block_data(block_base $block, stdClass $data): void {
1838
        global $DB;
1839
 
1840
        $bi = new stdClass;
1841
        $bi->id = $block->instance->id;
1842
 
1843
        // This may get overwritten by the special case handling below.
1844
        $bi->pagetypepattern = $data->bui_pagetypepattern;
1845
        $bi->showinsubcontexts = (bool) $data->bui_contexts;
1846
        if (empty($data->bui_subpagepattern) || $data->bui_subpagepattern == '%@NULL@%') {
1847
            $bi->subpagepattern = null;
1848
        } else {
1849
            $bi->subpagepattern = $data->bui_subpagepattern;
1850
        }
1851
 
1852
        $systemcontext = context_system::instance();
1853
        $frontpagecontext = context_course::instance(SITEID);
1854
        $parentcontext = context::instance_by_id($data->bui_parentcontextid);
1855
 
1856
        // Updating stickiness and contexts.  See MDL-21375 for details.
1857
        if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination.
1858
 
1859
            // Explicitly set the default context.
1860
            $bi->parentcontextid = $parentcontext->id;
1861
 
1862
            if ($data->bui_editingatfrontpage) {   // The block is being edited on the front page.
1863
 
1864
                // The interface here is a special case because the pagetype pattern is
1865
                // totally derived from the context menu.  Here are the excpetions.   MDL-30340 .
1866
 
1867
                switch ($data->bui_contexts) {
1868
                    case BUI_CONTEXTS_ENTIRE_SITE:
1869
                        // The user wants to show the block across the entire site.
1870
                        $bi->parentcontextid = $systemcontext->id;
1871
                        $bi->showinsubcontexts = true;
1872
                        $bi->pagetypepattern = '*';
1873
                        break;
1874
                    case BUI_CONTEXTS_FRONTPAGE_SUBS:
1875
                        // The user wants the block shown on the front page and all subcontexts.
1876
                        $bi->parentcontextid = $frontpagecontext->id;
1877
                        $bi->showinsubcontexts = true;
1878
                        $bi->pagetypepattern = '*';
1879
                        break;
1880
                    case BUI_CONTEXTS_FRONTPAGE_ONLY:
1881
                        // The user want to show the front page on the frontpage only.
1882
                        $bi->parentcontextid = $frontpagecontext->id;
1883
                        $bi->showinsubcontexts = false;
1884
                        $bi->pagetypepattern = 'site-index';
1885
                        // This is the only relevant page type anyway but we'll set it explicitly just
1886
                        // in case the front page grows site-index-* subpages of its own later.
1887
                        break;
1888
                }
1889
            }
1890
        }
1891
 
1892
        $bits = explode('-', $bi->pagetypepattern);
1893
        // Hacks for some contexts.
1894
        if (($parentcontext->contextlevel == CONTEXT_COURSE) && ($parentcontext->instanceid != SITEID)) {
1895
            // For course context
1896
            // is page type pattern is mod-*, change showinsubcontext to 1.
1897
            if ($bits[0] == 'mod' || $bi->pagetypepattern == '*') {
1898
                $bi->showinsubcontexts = 1;
1899
            } else {
1900
                $bi->showinsubcontexts = 0;
1901
            }
1902
        } else if ($parentcontext->contextlevel == CONTEXT_USER) {
1903
            // For user context subpagepattern should be null.
1904
            if ($bits[0] == 'user' || $bits[0] == 'my') {
1905
                // We don't need subpagepattern in usercontext.
1906
                $bi->subpagepattern = null;
1907
            }
1908
        }
1909
 
1910
        $bi->defaultregion = $data->bui_defaultregion;
1911
        $bi->defaultweight = $data->bui_defaultweight;
1912
        $bi->timemodified = time();
1913
        $DB->update_record('block_instances', $bi);
1914
 
1915
        if (!empty($block->config)) {
1916
            $config = clone($block->config);
1917
        } else {
1918
            $config = new stdClass;
1919
        }
1920
        foreach ($data as $configfield => $value) {
1921
            if (strpos($configfield, 'config_') !== 0) {
1922
                continue;
1923
            }
1924
            $field = substr($configfield, 7);
1925
            $config->$field = $value;
1926
        }
1927
        $block->instance_config_save($config);
1928
 
1929
        $bp = new stdClass;
1930
        $bp->visible = $data->bui_visible;
1931
        $bp->region = $data->bui_region;
1932
        $bp->weight = $data->bui_weight;
1933
        $needbprecord = !$data->bui_visible || $data->bui_region != $data->bui_defaultregion ||
1934
                $data->bui_weight != $data->bui_defaultweight;
1935
 
1936
        if ($block->instance->blockpositionid && !$needbprecord) {
1937
            $DB->delete_records('block_positions', array('id' => $block->instance->blockpositionid));
1938
 
1939
        } else if ($block->instance->blockpositionid && $needbprecord) {
1940
            $bp->id = $block->instance->blockpositionid;
1941
            $DB->update_record('block_positions', $bp);
1942
 
1943
        } else if ($needbprecord) {
1944
            $bp->blockinstanceid = $block->instance->id;
1945
            $bp->contextid = $this->page->context->id;
1946
            $bp->pagetype = $this->page->pagetype;
1947
            if ($this->page->subpage) {
1948
                $bp->subpage = $this->page->subpage;
1949
            } else {
1950
                $bp->subpage = '';
1951
            }
1952
            $DB->insert_record('block_positions', $bp);
1953
        }
1954
    }
1955
 
1956
    /**
1957
     * Handle showing/processing the submission from the block editing form.
1958
     * @return boolean true if the form was submitted and the new config saved. Does not
1959
     *      return if the editing form was displayed. False otherwise.
1960
     */
1961
    public function process_url_move() {
1962
        global $CFG, $DB, $PAGE;
1963
 
1964
        $blockid = optional_param('bui_moveid', null, PARAM_INT);
1965
        if (!$blockid) {
1966
            return false;
1967
        }
1968
 
1969
        require_sesskey();
1970
 
1971
        $block = $this->find_instance($blockid);
1972
 
1973
        if (!$this->page->user_can_edit_blocks()) {
1974
            throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1975
        }
1976
 
1977
        $newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT);
1978
        $newweight = optional_param('bui_newweight', null, PARAM_FLOAT);
1979
        if (!$newregion || is_null($newweight)) {
1980
            // Don't have a valid target position yet, must be just starting the move.
1981
            $this->movingblock = $blockid;
1982
            $this->page->ensure_param_not_in_url('bui_moveid');
1983
            return false;
1984
        }
1985
 
1986
        if (!$this->is_known_region($newregion)) {
1987
            throw new moodle_exception('unknownblockregion', '', $this->page->url, $newregion);
1988
        }
1989
 
1990
        // Move this block. This may involve moving other nearby blocks.
1991
        $blocks = $this->birecordsbyregion[$newregion];
1992
 
1993
        $maxweight = self::MAX_WEIGHT;
1994
        $minweight = -self::MAX_WEIGHT;
1995
 
1996
        // Initialise the used weights and spareweights array with the default values
1997
        $spareweights = array();
1998
        $usedweights = array();
1999
        for ($i = $minweight; $i <= $maxweight; $i++) {
2000
            $spareweights[$i] = $i;
2001
            $usedweights[$i] = array();
2002
        }
2003
 
2004
        // Check each block and sort out where we have used weights
2005
        foreach ($blocks as $bi) {
2006
            if ($bi->weight > $maxweight) {
2007
                // If this statement is true then the blocks weight is more than the
2008
                // current maximum. To ensure that we can get the best block position
2009
                // we will initialise elements within the usedweights and spareweights
2010
                // arrays between the blocks weight (which will then be the new max) and
2011
                // the current max
2012
                $parseweight = $bi->weight;
2013
                while (!array_key_exists($parseweight, $usedweights)) {
2014
                    $usedweights[$parseweight] = array();
2015
                    $spareweights[$parseweight] = $parseweight;
2016
                    $parseweight--;
2017
                }
2018
                $maxweight = $bi->weight;
2019
            } else if ($bi->weight < $minweight) {
2020
                // As above except this time the blocks weight is LESS than the
2021
                // the current minimum, so we will initialise the array from the
2022
                // blocks weight (new minimum) to the current minimum
2023
                $parseweight = $bi->weight;
2024
                while (!array_key_exists($parseweight, $usedweights)) {
2025
                    $usedweights[$parseweight] = array();
2026
                    $spareweights[$parseweight] = $parseweight;
2027
                    $parseweight++;
2028
                }
2029
                $minweight = $bi->weight;
2030
            }
2031
            if ($bi->id != $block->instance->id) {
2032
                unset($spareweights[$bi->weight]);
2033
                $usedweights[$bi->weight][] = $bi->id;
2034
            }
2035
        }
2036
 
2037
        // First we find the nearest gap in the list of weights.
2038
        $bestdistance = max(abs($newweight - self::MAX_WEIGHT), abs($newweight + self::MAX_WEIGHT)) + 1;
2039
        $bestgap = null;
2040
        foreach ($spareweights as $spareweight) {
2041
            if (abs($newweight - $spareweight) < $bestdistance) {
2042
                $bestdistance = abs($newweight - $spareweight);
2043
                $bestgap = $spareweight;
2044
            }
2045
        }
2046
 
2047
        // If there is no gap, we have to go outside -self::MAX_WEIGHT .. self::MAX_WEIGHT.
2048
        if (is_null($bestgap)) {
2049
            $bestgap = self::MAX_WEIGHT + 1;
2050
            while (!empty($usedweights[$bestgap])) {
2051
                $bestgap++;
2052
            }
2053
        }
2054
 
2055
        // Now we know the gap we are aiming for, so move all the blocks along.
2056
        if ($bestgap < $newweight) {
2057
            $newweight = floor($newweight);
2058
            for ($weight = $bestgap + 1; $weight <= $newweight; $weight++) {
2059
                if (array_key_exists($weight, $usedweights)) {
2060
                    foreach ($usedweights[$weight] as $biid) {
2061
                        $this->reposition_block($biid, $newregion, $weight - 1);
2062
                    }
2063
                }
2064
            }
2065
            $this->reposition_block($block->instance->id, $newregion, $newweight);
2066
        } else {
2067
            $newweight = ceil($newweight);
2068
            for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {
2069
                if (array_key_exists($weight, $usedweights)) {
2070
                    foreach ($usedweights[$weight] as $biid) {
2071
                        $this->reposition_block($biid, $newregion, $weight + 1);
2072
                    }
2073
                }
2074
            }
2075
            $this->reposition_block($block->instance->id, $newregion, $newweight);
2076
        }
2077
 
2078
        $this->page->ensure_param_not_in_url('bui_moveid');
2079
        $this->page->ensure_param_not_in_url('bui_newregion');
2080
        $this->page->ensure_param_not_in_url('bui_newweight');
2081
        return true;
2082
    }
2083
 
2084
    /**
2085
     * Turns the display of normal blocks either on or off.
2086
     *
2087
     * @param bool $setting
2088
     */
2089
    public function show_only_fake_blocks($setting = true) {
2090
        $this->fakeblocksonly = $setting;
2091
    }
2092
}
2093
 
2094
/// Helper functions for working with block classes ============================
2095
 
2096
/**
2097
 * Call a class method (one that does not require a block instance) on a block class.
2098
 *
2099
 * @param string $blockname the name of the block.
2100
 * @param string $method the method name.
2101
 * @param array $param parameters to pass to the method.
2102
 * @return mixed whatever the method returns.
2103
 */
2104
function block_method_result($blockname, $method, $param = NULL) {
2105
    if(!block_load_class($blockname)) {
2106
        return NULL;
2107
    }
2108
    return call_user_func(array('block_'.$blockname, $method), $param);
2109
}
2110
 
2111
/**
2112
 * Returns a new instance of the specified block instance id.
2113
 *
2114
 * @param int $blockinstanceid
2115
 * @return block_base the requested block instance.
2116
 */
2117
function block_instance_by_id($blockinstanceid) {
2118
    global $DB;
2119
 
2120
    $blockinstance = $DB->get_record('block_instances', ['id' => $blockinstanceid]);
2121
    $instance = block_instance($blockinstance->blockname, $blockinstance);
2122
    return $instance;
2123
}
2124
 
2125
/**
2126
 * Creates a new instance of the specified block class.
2127
 *
2128
 * @param string $blockname the name of the block.
2129
 * @param stdClass $instance block_instances DB table row (optional).
2130
 * @param moodle_page $page the page this block is appearing on.
2131
 * @return block_base|false the requested block instance.
2132
 */
2133
function block_instance($blockname, $instance = NULL, $page = NULL) {
2134
    if(!block_load_class($blockname)) {
2135
        return false;
2136
    }
2137
    $classname = 'block_'.$blockname;
2138
    /** @var block_base $retval */
2139
    $retval = new $classname;
2140
    if($instance !== NULL) {
2141
        if (is_null($page)) {
2142
            global $PAGE;
2143
            $page = $PAGE;
2144
        }
2145
        $retval->_load_instance($instance, $page);
2146
    }
2147
    return $retval;
2148
}
2149
 
2150
/**
2151
 * Load the block class for a particular type of block.
2152
 *
2153
 * @param string $blockname the name of the block.
2154
 * @return boolean success or failure.
2155
 */
2156
function block_load_class($blockname) {
2157
    global $CFG;
2158
 
11 efrain 2159
    $blocknameclean = clean_param($blockname, PARAM_PLUGIN);
2160
    if (empty($blockname) || empty($blocknameclean)) {
1 efrain 2161
        return false;
2162
    }
2163
 
2164
    $classname = 'block_'.$blockname;
2165
 
2166
    if(class_exists($classname)) {
2167
        return true;
2168
    }
2169
 
2170
    $blockpath = $CFG->dirroot.'/blocks/'.$blockname.'/block_'.$blockname.'.php';
2171
 
2172
    if (file_exists($blockpath)) {
2173
        require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
2174
        include_once($blockpath);
2175
    }else{
2176
        //debugging("$blockname code does not exist in $blockpath", DEBUG_DEVELOPER);
2177
        return false;
2178
    }
2179
 
2180
    return class_exists($classname);
2181
}
2182
 
2183
/**
2184
 * Given a specific page type, return all the page type patterns that might
2185
 * match it.
2186
 *
2187
 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
2188
 * @return array an array of all the page type patterns that might match this page type.
2189
 */
2190
function matching_page_type_patterns($pagetype) {
2191
    $patterns = array($pagetype);
2192
    $bits = explode('-', $pagetype);
2193
    if (count($bits) == 3 && $bits[0] == 'mod') {
2194
        if ($bits[2] == 'view') {
2195
            $patterns[] = 'mod-*-view';
2196
        } else if ($bits[2] == 'index') {
2197
            $patterns[] = 'mod-*-index';
2198
        }
2199
    }
2200
    while (count($bits) > 0) {
2201
        $patterns[] = implode('-', $bits) . '-*';
2202
        array_pop($bits);
2203
    }
2204
    $patterns[] = '*';
2205
    return $patterns;
2206
}
2207
 
2208
/**
2209
 * Give an specific pattern, return all the page type patterns that would also match it.
2210
 *
2211
 * @param  string $pattern the pattern, e.g. 'mod-forum-*' or 'mod-quiz-view'.
2212
 * @return array of all the page type patterns matching.
2213
 */
2214
function matching_page_type_patterns_from_pattern($pattern) {
2215
    $patterns = array($pattern);
2216
    if ($pattern === '*') {
2217
        return $patterns;
2218
    }
2219
 
2220
    // Only keep the part before the star because we will append -* to all the bits.
2221
    $star = strpos($pattern, '-*');
2222
    if ($star !== false) {
2223
        $pattern = substr($pattern, 0, $star);
2224
    }
2225
 
2226
    $patterns = array_merge($patterns, matching_page_type_patterns($pattern));
2227
    $patterns = array_unique($patterns);
2228
 
2229
    return $patterns;
2230
}
2231
 
2232
/**
2233
 * Given a specific page type, parent context and currect context, return all the page type patterns
2234
 * that might be used by this block.
2235
 *
2236
 * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
2237
 * @param stdClass $parentcontext Block's parent context
2238
 * @param stdClass $currentcontext Current context of block
2239
 * @return array an array of all the page type patterns that might match this page type.
2240
 */
2241
function generate_page_type_patterns($pagetype, $parentcontext = null, $currentcontext = null) {
2242
    global $CFG; // Required for includes bellow.
2243
 
2244
    $bits = explode('-', $pagetype);
2245
 
2246
    $core = core_component::get_core_subsystems();
2247
    $plugins = core_component::get_plugin_types();
2248
 
2249
    //progressively strip pieces off the page type looking for a match
2250
    $componentarray = null;
2251
    for ($i = count($bits); $i > 0; $i--) {
2252
        $possiblecomponentarray = array_slice($bits, 0, $i);
2253
        $possiblecomponent = implode('', $possiblecomponentarray);
2254
 
2255
        // Check to see if the component is a core component
2256
        if (array_key_exists($possiblecomponent, $core) && !empty($core[$possiblecomponent])) {
2257
            $libfile = $core[$possiblecomponent].'/lib.php';
2258
            if (file_exists($libfile)) {
2259
                require_once($libfile);
2260
                $function = $possiblecomponent.'_page_type_list';
2261
                if (function_exists($function)) {
2262
                    if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
2263
                        break;
2264
                    }
2265
                }
2266
            }
2267
        }
2268
 
2269
        //check the plugin directory and look for a callback
2270
        if (array_key_exists($possiblecomponent, $plugins) && !empty($plugins[$possiblecomponent])) {
2271
 
2272
            //We've found a plugin type. Look for a plugin name by getting the next section of page type
2273
            if (count($bits) > $i) {
2274
                $pluginname = $bits[$i];
2275
                $directory = core_component::get_plugin_directory($possiblecomponent, $pluginname);
2276
                if (!empty($directory)){
2277
                    $libfile = $directory.'/lib.php';
2278
                    if (file_exists($libfile)) {
2279
                        require_once($libfile);
2280
                        $function = $possiblecomponent.'_'.$pluginname.'_page_type_list';
2281
                        if (!function_exists($function)) {
2282
                            $function = $pluginname.'_page_type_list';
2283
                        }
2284
                        if (function_exists($function)) {
2285
                            if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
2286
                                break;
2287
                            }
2288
                        }
2289
                    }
2290
                }
2291
            }
2292
 
2293
            //we'll only get to here if we still don't have any patterns
2294
            //the plugin type may have a callback
2295
            $directory = $plugins[$possiblecomponent];
2296
            $libfile = $directory.'/lib.php';
2297
            if (file_exists($libfile)) {
2298
                require_once($libfile);
2299
                $function = $possiblecomponent.'_page_type_list';
2300
                if (function_exists($function)) {
2301
                    if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
2302
                        break;
2303
                    }
2304
                }
2305
            }
2306
        }
2307
    }
2308
 
2309
    if (empty($patterns)) {
2310
        $patterns = default_page_type_list($pagetype, $parentcontext, $currentcontext);
2311
    }
2312
 
2313
    // Ensure that the * pattern is always available if editing block 'at distance', so
2314
    // we always can 'bring back' it to the original context. MDL-30340
2315
    if ((!isset($currentcontext) or !isset($parentcontext) or $currentcontext->id != $parentcontext->id) && !isset($patterns['*'])) {
2316
        // TODO: We could change the string here, showing its 'bring back' meaning
2317
        $patterns['*'] = get_string('page-x', 'pagetype');
2318
    }
2319
 
2320
    return $patterns;
2321
}
2322
 
2323
/**
2324
 * Generates a default page type list when a more appropriate callback cannot be decided upon.
2325
 *
2326
 * @param string $pagetype
2327
 * @param stdClass $parentcontext
2328
 * @param stdClass $currentcontext
2329
 * @return array
2330
 */
2331
function default_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
2332
    // Generate page type patterns based on current page type if
2333
    // callbacks haven't been defined
2334
    $patterns = array($pagetype => $pagetype);
2335
    $bits = explode('-', $pagetype);
2336
    while (count($bits) > 0) {
2337
        $pattern = implode('-', $bits) . '-*';
2338
        $pagetypestringname = 'page-'.str_replace('*', 'x', $pattern);
2339
        // guessing page type description
2340
        if (get_string_manager()->string_exists($pagetypestringname, 'pagetype')) {
2341
            $patterns[$pattern] = get_string($pagetypestringname, 'pagetype');
2342
        } else {
2343
            $patterns[$pattern] = $pattern;
2344
        }
2345
        array_pop($bits);
2346
    }
2347
    $patterns['*'] = get_string('page-x', 'pagetype');
2348
    return $patterns;
2349
}
2350
 
2351
/**
2352
 * Generates the page type list for the my moodle page
2353
 *
2354
 * @param string $pagetype
2355
 * @param stdClass $parentcontext
2356
 * @param stdClass $currentcontext
2357
 * @return array
2358
 */
2359
function my_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
2360
    return array('my-index' => get_string('page-my-index', 'pagetype'));
2361
}
2362
 
2363
/**
2364
 * Generates the page type list for a module by either locating and using the modules callback
2365
 * or by generating a default list.
2366
 *
2367
 * @param string $pagetype
2368
 * @param stdClass $parentcontext
2369
 * @param stdClass $currentcontext
2370
 * @return array
2371
 */
2372
function mod_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
2373
    $patterns = plugin_page_type_list($pagetype, $parentcontext, $currentcontext);
2374
    if (empty($patterns)) {
2375
        // if modules don't have callbacks
2376
        // generate two default page type patterns for modules only
2377
        $bits = explode('-', $pagetype);
2378
        $patterns = array($pagetype => $pagetype);
2379
        if ($bits[2] == 'view') {
2380
            $patterns['mod-*-view'] = get_string('page-mod-x-view', 'pagetype');
2381
        } else if ($bits[2] == 'index') {
2382
            $patterns['mod-*-index'] = get_string('page-mod-x-index', 'pagetype');
2383
        }
2384
    }
2385
    return $patterns;
2386
}
2387
/// Functions update the blocks if required by the request parameters ==========
2388
 
2389
/**
2390
 * Return a {@link block_contents} representing the add a new block UI, if
2391
 * this user is allowed to see it.
2392
 *
2393
 * @return ?block_contents an appropriate block_contents, or null if the user
2394
 * cannot add any blocks here.
2395
 */
2396
function block_add_block_ui($page, $output) {
2397
    global $CFG, $OUTPUT;
2398
    if (!$page->user_is_editing() || !$page->user_can_edit_blocks()) {
2399
        return null;
2400
    }
2401
 
2402
    $bc = new block_contents();
2403
    $bc->title = get_string('addblock');
2404
    $bc->add_class('block_adminblock');
2405
    $bc->attributes['data-block'] = 'adminblock';
2406
 
2407
    $missingblocks = $page->blocks->get_addable_blocks();
2408
    if (empty($missingblocks)) {
2409
        $bc->content = get_string('noblockstoaddhere');
2410
        return $bc;
2411
    }
2412
 
2413
    $menu = array();
2414
    foreach ($missingblocks as $block) {
2415
        $menu[$block->name] = $block->title;
2416
    }
2417
 
2418
    $actionurl = new moodle_url($page->url, array('sesskey'=>sesskey()));
2419
    $select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');
2420
    $select->set_label(get_string('addblock'), array('class'=>'accesshide'));
2421
    $bc->content = $OUTPUT->render($select);
2422
    return $bc;
2423
}
2424
 
2425
/**
2426
 * Actually delete from the database any blocks that are currently on this page,
2427
 * but which should not be there according to blocks_name_allowed_in_format.
2428
 *
2429
 * @todo Write/Fix this function. Currently returns immediately
2430
 * @param $course
2431
 */
2432
function blocks_remove_inappropriate($course) {
2433
    // TODO
2434
    return;
2435
    /*
2436
    $blockmanager = blocks_get_by_page($page);
2437
 
2438
    if (empty($blockmanager)) {
2439
        return;
2440
    }
2441
 
2442
    if (($pageformat = $page->pagetype) == NULL) {
2443
        return;
2444
    }
2445
 
2446
    foreach($blockmanager as $region) {
2447
        foreach($region as $instance) {
2448
            $block = blocks_get_record($instance->blockid);
2449
            if(!blocks_name_allowed_in_format($block->name, $pageformat)) {
2450
               blocks_delete_instance($instance->instance);
2451
            }
2452
        }
2453
    }*/
2454
}
2455
 
2456
/**
2457
 * Check that a given name is in a permittable format
2458
 *
2459
 * @param string $name
2460
 * @param string $pageformat
2461
 * @return bool
2462
 */
2463
function blocks_name_allowed_in_format($name, $pageformat) {
2464
    $accept = NULL;
2465
    $maxdepth = -1;
2466
    if (!$bi = block_instance($name)) {
2467
        return false;
2468
    }
2469
 
2470
    $formats = $bi->applicable_formats();
2471
    if (!$formats) {
2472
        $formats = array();
2473
    }
2474
    foreach ($formats as $format => $allowed) {
2475
        $formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';
2476
        $depth = substr_count($format, '-');
2477
        if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {
2478
            $maxdepth = $depth;
2479
            $accept = $allowed;
2480
        }
2481
    }
2482
    if ($accept === NULL) {
2483
        $accept = !empty($formats['all']);
2484
    }
2485
    return $accept;
2486
}
2487
 
2488
/**
2489
 * Delete a block, and associated data.
2490
 *
2491
 * @param object $instance a row from the block_instances table
2492
 * @param bool $nolongerused legacy parameter. Not used, but kept for backwards compatibility.
2493
 * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.
2494
 */
2495
function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {
2496
    global $DB;
2497
 
2498
    // Allow plugins to use this block before we completely delete it.
2499
    if ($pluginsfunction = get_plugins_with_function('pre_block_delete')) {
2500
        foreach ($pluginsfunction as $plugintype => $plugins) {
2501
            foreach ($plugins as $pluginfunction) {
2502
                $pluginfunction($instance);
2503
            }
2504
        }
2505
    }
2506
 
2507
    if ($block = block_instance($instance->blockname, $instance)) {
2508
        $block->instance_delete();
2509
    }
2510
    context_helper::delete_instance(CONTEXT_BLOCK, $instance->id);
2511
 
2512
    if (!$skipblockstables) {
2513
        $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id));
2514
        $DB->delete_records('block_instances', array('id' => $instance->id));
2515
        $DB->delete_records_list('user_preferences', 'name', array('block'.$instance->id.'hidden','docked_block_instance_'.$instance->id));
2516
    }
2517
}
2518
 
2519
/**
2520
 * Delete multiple blocks at once.
2521
 *
2522
 * @param array $instanceids A list of block instance ID.
2523
 */
2524
function blocks_delete_instances($instanceids) {
2525
    global $DB;
2526
 
2527
    $limit = 1000;
2528
    $count = count($instanceids);
2529
    $chunks = [$instanceids];
2530
    if ($count > $limit) {
2531
        $chunks = array_chunk($instanceids, $limit);
2532
    }
2533
 
2534
    // Perform deletion for each chunk.
2535
    foreach ($chunks as $chunk) {
2536
        $instances = $DB->get_recordset_list('block_instances', 'id', $chunk);
2537
        foreach ($instances as $instance) {
2538
            blocks_delete_instance($instance, false, true);
2539
        }
2540
        $instances->close();
2541
 
2542
        $DB->delete_records_list('block_positions', 'blockinstanceid', $chunk);
2543
        $DB->delete_records_list('block_instances', 'id', $chunk);
2544
 
2545
        $preferences = array();
2546
        foreach ($chunk as $instanceid) {
2547
            $preferences[] = 'block' . $instanceid . 'hidden';
2548
            $preferences[] = 'docked_block_instance_' . $instanceid;
2549
        }
2550
        $DB->delete_records_list('user_preferences', 'name', $preferences);
2551
    }
2552
}
2553
 
2554
/**
2555
 * Delete all the blocks that belong to a particular context.
2556
 *
2557
 * @param int $contextid the context id.
2558
 */
2559
function blocks_delete_all_for_context($contextid) {
2560
    global $DB;
2561
    $instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));
2562
    foreach ($instances as $instance) {
2563
        blocks_delete_instance($instance, true);
2564
    }
2565
    $instances->close();
2566
    $DB->delete_records('block_instances', array('parentcontextid' => $contextid));
2567
    $DB->delete_records('block_positions', array('contextid' => $contextid));
2568
}
2569
 
2570
/**
2571
 * Set a block to be visible or hidden on a particular page.
2572
 *
2573
 * @param object $instance a row from the block_instances, preferably LEFT JOINed with the
2574
 *      block_positions table as return by block_manager.
2575
 * @param moodle_page $page the back to set the visibility with respect to.
2576
 * @param integer $newvisibility 1 for visible, 0 for hidden.
2577
 */
2578
function blocks_set_visibility($instance, $page, $newvisibility) {
2579
    global $DB;
2580
    if (!empty($instance->blockpositionid)) {
2581
        // Already have local information on this page.
2582
        $DB->set_field('block_positions', 'visible', $newvisibility, array('id' => $instance->blockpositionid));
2583
        return;
2584
    }
2585
 
2586
    // Create a new block_positions record.
2587
    $bp = new stdClass;
2588
    $bp->blockinstanceid = $instance->id;
2589
    $bp->contextid = $page->context->id;
2590
    $bp->pagetype = $page->pagetype;
2591
    if ($page->subpage) {
2592
        $bp->subpage = $page->subpage;
2593
    }
2594
    $bp->visible = $newvisibility;
2595
    $bp->region = $instance->defaultregion;
2596
    $bp->weight = $instance->defaultweight;
2597
    $DB->insert_record('block_positions', $bp);
2598
}
2599
 
2600
/**
2601
 * Get the block record for a particular blockid - that is, a particular type os block.
2602
 *
2603
 * @param $int blockid block type id. If null, an array of all block types is returned.
2604
 * @param bool $notusedanymore No longer used.
2605
 * @return array|object|false row from block table, or all rows.
2606
 */
2607
function blocks_get_record($blockid = NULL, $notusedanymore = false) {
2608
    global $PAGE;
2609
    $blocks = $PAGE->blocks->get_installed_blocks();
2610
    if ($blockid === NULL) {
2611
        return $blocks;
2612
    } else if (isset($blocks[$blockid])) {
2613
        return $blocks[$blockid];
2614
    } else {
2615
        return false;
2616
    }
2617
}
2618
 
2619
/**
2620
 * Find a given block by its blockid within a provide array
2621
 *
2622
 * @param int $blockid
2623
 * @param array $blocksarray
2624
 * @return bool|object Instance if found else false
2625
 */
2626
function blocks_find_block($blockid, $blocksarray) {
2627
    if (empty($blocksarray)) {
2628
        return false;
2629
    }
2630
    foreach($blocksarray as $blockgroup) {
2631
        if (empty($blockgroup)) {
2632
            continue;
2633
        }
2634
        foreach($blockgroup as $instance) {
2635
            if($instance->blockid == $blockid) {
2636
                return $instance;
2637
            }
2638
        }
2639
    }
2640
    return false;
2641
}
2642
 
2643
// Functions for programatically adding default blocks to pages ================
2644
 
2645
 /**
2646
  * Parse a list of default blocks. See config-dist for a description of the format.
2647
  *
2648
  * @param string $blocksstr Determines the starting point that the blocks are added in the region.
2649
  * @return array the parsed list of default blocks
2650
  */
2651
function blocks_parse_default_blocks_list($blocksstr) {
2652
    $blocks = array();
2653
    $bits = explode(':', $blocksstr);
2654
    if (!empty($bits)) {
2655
        $leftbits = trim(array_shift($bits));
2656
        if ($leftbits != '') {
2657
            $blocks[BLOCK_POS_LEFT] = explode(',', $leftbits);
2658
        }
2659
    }
2660
    if (!empty($bits)) {
2661
        $rightbits = trim(array_shift($bits));
2662
        if ($rightbits != '') {
2663
            $blocks[BLOCK_POS_RIGHT] = explode(',', $rightbits);
2664
        }
2665
    }
2666
    return $blocks;
2667
}
2668
 
2669
/**
2670
 * @return array the blocks that should be added to the site course by default.
2671
 */
2672
function blocks_get_default_site_course_blocks() {
2673
    global $CFG;
2674
 
2675
    if (isset($CFG->defaultblocks_site)) {
2676
        return blocks_parse_default_blocks_list($CFG->defaultblocks_site);
2677
    } else {
2678
        return array(
2679
            BLOCK_POS_LEFT => array(),
2680
            BLOCK_POS_RIGHT => array()
2681
        );
2682
    }
2683
}
2684
 
2685
/**
2686
 * Add the default blocks to a course.
2687
 *
2688
 * @param object $course a course object.
2689
 */
2690
function blocks_add_default_course_blocks($course) {
2691
    global $CFG;
2692
 
2693
    if (isset($CFG->defaultblocks_override)) {
2694
        $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override);
2695
 
2696
    } else if ($course->id == SITEID) {
2697
        $blocknames = blocks_get_default_site_course_blocks();
2698
 
2699
    } else if (isset($CFG->{'defaultblocks_' . $course->format})) {
2700
        $blocknames = blocks_parse_default_blocks_list($CFG->{'defaultblocks_' . $course->format});
2701
 
2702
    } else {
2703
        require_once($CFG->dirroot. '/course/lib.php');
2704
        $blocknames = course_get_format($course)->get_default_blocks();
2705
 
2706
    }
2707
 
2708
    if ($course->id == SITEID) {
2709
        $pagetypepattern = 'site-index';
2710
    } else {
2711
        $pagetypepattern = 'course-view-*';
2712
    }
2713
    $page = new moodle_page();
2714
    $page->set_course($course);
2715
    $page->blocks->add_blocks($blocknames, $pagetypepattern);
2716
}
2717
 
2718
/**
2719
 * Add the default system-context blocks. E.g. the admin tree.
2720
 */
2721
function blocks_add_default_system_blocks() {
2722
    global $DB;
2723
 
2724
    $page = new moodle_page();
2725
    $page->set_context(context_system::instance());
2726
    // We don't add blocks required by the theme, they will be auto-created.
2727
    $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('admin_bookmarks')), 'admin-*', null, null, 2);
2728
 
2729
    if ($defaultmypage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => 1))) {
2730
        $subpagepattern = $defaultmypage->id;
2731
    } else {
2732
        $subpagepattern = null;
2733
    }
2734
 
2735
    if ($defaultmycoursespage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__courses', 'private' => 0))) {
2736
        $mycoursesubpagepattern = $defaultmycoursespage->id;
2737
    } else {
2738
        $mycoursesubpagepattern = null;
2739
    }
2740
 
2741
    $page->blocks->add_blocks([
2742
        BLOCK_POS_RIGHT => [
2743
            'recentlyaccesseditems',
2744
        ],
2745
        'content' => [
2746
            'timeline',
2747
            'calendar_month',
2748
        ]],
2749
        'my-index',
2750
        $subpagepattern
2751
    );
2752
 
2753
    $page->blocks->add_blocks([
2754
        'content' => [
2755
            'myoverview'
2756
        ]],
2757
        'my-index',
2758
        $mycoursesubpagepattern
2759
    );
2760
}