Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Supporting infrastructure for the multiblock editing.
19
 *
20
 * @package   block_multiblock
21
 * @copyright 2019 Peter Spicer <peter.spicer@catalyst-eu.net>
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace block_multiblock;
26
 
27
use block_multiblock\navigation;
28
use block_multiblock\form\editblock;
29
use block_multiblock\form\editblock_totara;
30
use context;
31
use context_block;
32
use context_helper;
33
use moodle_exception;
34
use moodle_url;
35
use navigation_node;
36
 
37
/**
38
 * Supporting infrastructure for the multiblock editing.
39
 *
40
 * @package   block_multiblock
41
 * @copyright 2019 Peter Spicer <peter.spicer@catalyst-eu.net>
42
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43
 */
44
class helper {
45
    /** @var moodle_url $pageurl The URL that the block's context belongs to. */
46
    protected static $pageurl = null;
47
 
48
    /** @var moodle_url $realpageurl The URL that we're really on for this block management. */
49
    protected static $realpageurl = null;
50
 
51
    /**
52
     * Sets the block parent URL (i.e. fake context) for permissions checks.
53
     *
54
     * @param moodle_url $url The URL to set this to (will be reused if not supplied)
55
     */
56
    public static function set_page_fake_url(moodle_url $url = null) {
57
        global $PAGE;
58
 
59
        if (!is_null($url)) {
60
            static::$pageurl = $url;
61
        }
62
 
63
        $PAGE->set_url(static::$pageurl);
64
    }
65
 
66
    /**
67
     * Sets the real page URL (e.g. management).
68
     *
69
     * @param moodle_url $url The URL to set this to (will be reused if not supplied)
70
     */
71
    public static function set_page_real_url(moodle_url $url = null) {
72
        global $PAGE;
73
 
74
        if (!is_null($url)) {
75
            static::$realpageurl = $url;
76
        }
77
 
78
        $PAGE->set_url(static::$realpageurl);
79
    }
80
 
81
    /**
82
     * Provide some functionality for bootstrapping the page for a given block.
83
     *
84
     * Namely: load the block instance, some $PAGE setup, navigation setup.
85
     *
86
     * @param int $blockid The block ID being operated on.
87
     * @return array Return the block record and its instance class.
88
     */
89
    public static function bootstrap_page($blockid) : array {
90
        global $DB, $PAGE;
91
 
92
        $block = $DB->get_record('block_instances', ['id' => $blockid], '*', MUST_EXIST);
93
        if (block_load_class($block->blockname)) {
94
            $class = 'block_' . $block->blockname;
95
            $blockinstance = new $class;
96
            $blockinstance->_load_instance($block, $PAGE);
97
        }
98
 
99
        $parentctx = context::instance_by_id($block->parentcontextid);
100
 
101
        $PAGE->set_context($parentctx);
102
 
103
        $actualpageurl = navigation::get_page_url($blockid);
104
        static::set_page_fake_url($actualpageurl);
105
        $PAGE->set_pagelayout('admin');
106
 
107
        $blockmanager = $PAGE->blocks;
108
 
109
        // The my-dashboard page adds an additional 'phantom' block region to cope with the dashboard content.
110
        if (navigation::is_dashboard($actualpageurl)) {
111
            $PAGE->blocks->add_region('content');
112
            // For some reason, adding extra navbar items to dashboard requires doing it twice.
113
            $PAGE->navbar->add(get_string('managemultiblock', 'block_multiblock', $blockinstance->get_title()),
114
                new moodle_url('/blocks/multiblock/manage.php', ['id' => $blockid, 'sesskey' => sesskey()]));
115
            $PAGE->set_blocks_editing_capability('moodle/my:manageblocks');
116
        }
117
 
118
        // Blocks on admin pages require loading the admin tree to be able to render the breadcrumbs.
119
        if (navigation::is_admin_url($actualpageurl)) {
120
            navigation_node::require_admin_tree();
121
        }
122
 
123
        // If the course is on a block page we need to explicitly load the course to get the correct breadcrumbs.
124
        if ($parentctx->contextlevel == CONTEXT_COURSE) {
125
            $courseid = $parentctx->instanceid;
126
            context_helper::preload_course($courseid);
127
            $course = $DB->get_record('course', ['id' => $courseid]);
128
            $PAGE->set_course($course);
129
        }
130
 
131
        // If it's an activity, we have to do a bit more work, loading the course and the context module.
132
        if ($parentctx->contextlevel == CONTEXT_MODULE) {
133
            $cm = $DB->get_record('course_modules', ['id' => $parentctx->instanceid]);
134
            $PAGE->set_cm($cm);
135
        }
136
 
137
        // And hand over to the Moodle architecture to do its thing.
138
        $PAGE->navigation->initialise();
139
        $PAGE->navbar->add(get_string('manageblocklocation', 'block_multiblock'), $actualpageurl);
140
        $PAGE->navbar->add(get_string('managemultiblock', 'block_multiblock', $blockinstance->get_title()),
141
            new moodle_url('/blocks/multiblock/manage.php', ['id' => $blockid, 'sesskey' => sesskey()]));
142
 
143
        require_sesskey();
144
 
145
        $PAGE->set_title(get_string('managemultiblocktitle', 'block_multiblock', $blockinstance->title));
146
        $PAGE->set_heading(get_string('managemultiblocktitle', 'block_multiblock', $blockinstance->title));
147
 
148
        if (!$blockinstance->user_can_edit() || !$PAGE->user_can_edit_blocks()) {
149
            throw new moodle_exception('nopermissions', '', $PAGE->url->out(), get_string('editblock', 'block_multiblock'));
150
        }
151
 
152
        return [$block, $blockinstance, $blockmanager];
153
    }
154
 
155
    /**
156
     * Finds the parent non-block context for a given block.
157
     * For example, a dashboard can hook off the user context
158
     * for which the dashboard is created, which contains the
159
     * multiblock context, which under it will contain the
160
     * child blocks. This, given the child block id will
161
     * traverse the parents in order until it hits the closest
162
     * ancestor that is not a block context.
163
     *
164
     * @param int $blockid
165
     * @return object A context object reflecting the nearest ancestor
166
     */
167
    public static function find_nearest_nonblock_ancestor($blockid) {
168
        global $DB;
169
 
170
        $context = $DB->get_record('context', ['instanceid' => $blockid, 'contextlevel' => CONTEXT_BLOCK]);
171
        // Convert the path from /1/2/3 to [1, 2, 3], remove the leading empty item and this item.
172
        $path = explode('/', $context->path);
173
        $path = array_diff($path, ['', $context->id]);
174
        foreach (array_reverse($path) as $contextid) {
175
            $parentcontext = $DB->get_record('context', ['id' => $contextid]);
176
            if ($parentcontext->contextlevel != CONTEXT_BLOCK) {
177
                // We found the one we care about.
178
                return context::instance_by_id($parentcontext->id);
179
            }
180
        }
181
 
182
        throw new coding_exception('Could not find parent non-block ancestor for block id ' . $blockid);
183
    }
184
 
185
    /**
186
     * Splits a subblock out of the multiblock and returns it to the
187
     * parent context that the parent multiblock lives in.
188
     *
189
     * @param object $parent The parent instance object, from block_instances table.
190
     * @param int $childid The id of the subblock to remove, from block_instances table.
191
     */
192
    public static function split_block($parent, $childid) {
193
        global $DB;
194
 
195
        // Get the block details and the target context to move it to.
196
        $subblock = $DB->get_record('block_instances', ['id' => $childid]);
197
        $parentcontext = static::find_nearest_nonblock_ancestor($childid);
198
 
199
        // Copy some parameters from the parent since that's what we're using now.
200
        $params = [
201
            'showinsubcontexts', 'requiredbytheme', 'pagetypepattern', 'subpagepattern',
202
            'defaultregion', 'defaultweight',
203
        ];
204
        foreach ($params as $param) {
205
            $subblock->$param = $parent->$param;
206
        }
207
 
208
        // Then set up the parts that aren't inherited from the old parent, and commit.
209
        $subblock->parentcontextid = $parentcontext->id;
210
        $subblock->timemodified = time();
211
        $DB->update_record('block_instances', $subblock);
212
 
213
        // Now fix the position to mirror the parent, if it has one.
214
        $parentposition = $DB->get_record('block_positions', [
215
            'contextid' => $parentcontext->id,
216
            'blockinstanceid' => $parent->id,
217
        ], '*', IGNORE_MISSING);
218
        if ($parentposition) {
219
            // The parent has a specific position, we need to add that.
220
            $newchild = $parentposition;
221
            $newchild->blockinstanceid = $childid;
222
            $DB->insert_record('block_positions', $newchild);
223
        }
224
 
225
        // Finally commit the updated context path to this block.
226
        $childcontext = context_block::instance($childid);
227
        $childcontext->update_moved($parentcontext);
228
    }
229
 
230
    /**
231
     * Moves a block from its current (non-multiblock) context to under a given
232
     * new multiblock parent.
233
     *
234
     * @param int $newchild The id of the block instance to move
235
     * @param int $newparent The id of the multiblock instance to move to
236
     */
237
    public static function move_block($newchild, $newparent) {
238
        global $DB;
239
 
240
        $parent = $DB->get_record('block_instances', ['id' => $newparent]);
241
        $subblock = $DB->get_record('block_instances', ['id' => $newchild]);
242
        $parentcontext = context_block::instance($newparent);
243
 
244
        // Copy some parameters from the parent since that's what we're using now.
245
        $params = [
246
            'showinsubcontexts', 'requiredbytheme', 'pagetypepattern', 'subpagepattern',
247
            'defaultregion',
248
        ];
249
        foreach ($params as $param) {
250
            $subblock->$param = $parent->$param;
251
        }
252
 
253
        // And fix the position, add it to the end of all the blocks attached to this sub-block.
254
        $max = $DB->get_record_sql("
255
            SELECT MAX(defaultweight) AS maxweight
256
              FROM {block_instances}
257
             WHERE parentcontextid = ?
258
          GROUP BY parentcontextid", [$parentcontext->id]);
259
        if (empty($max)) {
260
            $subblock->defaultweight = 1;
261
        } else {
262
            $subblock->defaultweight = $max->maxweight + 1;
263
        }
264
 
265
        // Then set up the parts that aren't inherited from the old parent, and commit.
266
        $subblock->parentcontextid = $parentcontext->id;
267
        $subblock->timemodified = time();
268
        $DB->update_record('block_instances', $subblock);
269
 
270
        // And remove any references it has to its own position, regardless of where it was, since
271
        // those don't exist any more.
272
        $DB->delete_records('block_positions', ['blockinstanceid' => $newchild]);
273
 
274
        // Finally commit the updated context path to this block.
275
        $childcontext = context_block::instance($newchild);
276
        $childcontext->update_moved($parentcontext);
277
    }
278
 
279
    /**
280
     * Different functionality might need to be used if implementing
281
     * Multiblock on a Totara installation as opposed to a Moodle.
282
     *
283
     * @return bool True if the current installation is Totara.
284
     */
285
    public static function is_totara(): bool {
286
        return class_exists('\\totara_core\\helper');
287
    }
288
 
289
    /**
290
     * Gets the correct editing form based for configuring an instance.
291
     *
292
     * @param string|moodle_url $actionurl The form action to submit to
293
     * @param block_base $block The block class being edited
294
     * @param object $page The contextually appropriate $PAGE type object of the block being edited
295
     * @param object $multiblock The multiblock object being edited (mostly for its configuration
296
     */
297
    public static function get_edit_form($actionurl, $block, $page, $multiblock = null) {
298
        if (static::is_totara()) {
299
            return new editblock_totara($actionurl, $block, $page, $multiblock);
300
        } else {
301
            return new editblock($actionurl, $block, $page, $multiblock);
302
        }
303
    }
304
}