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
// 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
 * This file contains classes used to manage the navigation structures within Moodle.
19
 *
20
 * @since      Moodle 2.0
21
 * @package    core
22
 * @copyright  2009 Sam Hemelryk
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
use core\moodlenet\utilities;
27
use core_contentbank\contentbank;
28
 
29
defined('MOODLE_INTERNAL') || die();
30
 
31
/**
32
 * The name that will be used to separate the navigation cache within SESSION
33
 */
34
define('NAVIGATION_CACHE_NAME', 'navigation');
35
define('NAVIGATION_SITE_ADMIN_CACHE_NAME', 'navigationsiteadmin');
36
 
37
/**
38
 * This class is used to represent a node in a navigation tree
39
 *
40
 * This class is used to represent a node in a navigation tree within Moodle,
41
 * the tree could be one of global navigation, settings navigation, or the navbar.
42
 * Each node can be one of two types either a Leaf (default) or a branch.
43
 * When a node is first created it is created as a leaf, when/if children are added
44
 * the node then becomes a branch.
45
 *
46
 * @package   core
47
 * @category  navigation
48
 * @copyright 2009 Sam Hemelryk
49
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50
 */
51
class navigation_node implements renderable {
52
    /** @var int Used to identify this node a leaf (default) 0 */
53
    const NODETYPE_LEAF =   0;
54
    /** @var int Used to identify this node a branch, happens with children  1 */
55
    const NODETYPE_BRANCH = 1;
56
    /** @var null Unknown node type null */
57
    const TYPE_UNKNOWN =    null;
58
    /** @var int System node type 0 */
59
    const TYPE_ROOTNODE =   0;
60
    /** @var int System node type 1 */
61
    const TYPE_SYSTEM =     1;
62
    /** @var int Category node type 10 */
63
    const TYPE_CATEGORY =   10;
64
    /** var int Category displayed in MyHome navigation node */
65
    const TYPE_MY_CATEGORY = 11;
66
    /** @var int Course node type 20 */
67
    const TYPE_COURSE =     20;
68
    /** @var int Course Structure node type 30 */
69
    const TYPE_SECTION =    30;
70
    /** @var int Activity node type, e.g. Forum, Quiz 40 */
71
    const TYPE_ACTIVITY =   40;
72
    /** @var int Resource node type, e.g. Link to a file, or label 50 */
73
    const TYPE_RESOURCE =   50;
74
    /** @var int A custom node type, default when adding without specifing type 60 */
75
    const TYPE_CUSTOM =     60;
76
    /** @var int Setting node type, used only within settings nav 70 */
77
    const TYPE_SETTING =    70;
78
    /** @var int site admin branch node type, used only within settings nav 71 */
79
    const TYPE_SITE_ADMIN = 71;
80
    /** @var int Setting node type, used only within settings nav 80 */
81
    const TYPE_USER =       80;
82
    /** @var int Setting node type, used for containers of no importance 90 */
83
    const TYPE_CONTAINER =  90;
84
    /** var int Course the current user is not enrolled in */
85
    const COURSE_OTHER = 0;
86
    /** var int Course the current user is enrolled in but not viewing */
87
    const COURSE_MY = 1;
88
    /** var int Course the current user is currently viewing */
89
    const COURSE_CURRENT = 2;
90
    /** var string The course index page navigation node */
91
    const COURSE_INDEX_PAGE = 'courseindexpage';
92
 
93
    /** @var int Parameter to aid the coder in tracking [optional] */
94
    public $id = null;
95
    /** @var string|int The identifier for the node, used to retrieve the node */
96
    public $key = null;
97
    /** @var string|lang_string The text to use for the node */
98
    public $text = null;
99
    /** @var string Short text to use if requested [optional] */
100
    public $shorttext = null;
101
    /** @var string The title attribute for an action if one is defined */
102
    public $title = null;
103
    /** @var string A string that can be used to build a help button */
104
    public $helpbutton = null;
105
    /** @var moodle_url|action_link|null An action for the node (link) */
106
    public $action = null;
107
    /** @var pix_icon The path to an icon to use for this node */
108
    public $icon = null;
109
    /** @var int See TYPE_* constants defined for this class */
110
    public $type = self::TYPE_UNKNOWN;
111
    /** @var int See NODETYPE_* constants defined for this class */
112
    public $nodetype = self::NODETYPE_LEAF;
113
    /** @var bool If set to true the node will be collapsed by default */
114
    public $collapse = false;
115
    /** @var bool If set to true the node will be expanded by default */
116
    public $forceopen = false;
117
    /** @var array An array of CSS classes for the node */
118
    public $classes = array();
119
    /** @var array An array of HTML attributes for the node */
120
    public $attributes = [];
121
    /** @var navigation_node_collection An array of child nodes */
122
    public $children = array();
123
    /** @var bool If set to true the node will be recognised as active */
124
    public $isactive = false;
125
    /** @var bool If set to true the node will be dimmed */
126
    public $hidden = false;
127
    /** @var bool If set to false the node will not be displayed */
128
    public $display = true;
129
    /** @var bool If set to true then an HR will be printed before the node */
130
    public $preceedwithhr = false;
131
    /** @var bool If set to true the the navigation bar should ignore this node */
132
    public $mainnavonly = false;
133
    /** @var bool If set to true a title will be added to the action no matter what */
134
    public $forcetitle = false;
135
    /** @var navigation_node A reference to the node parent, you should never set this directly you should always call set_parent */
136
    public $parent = null;
137
    /** @var bool Override to not display the icon even if one is provided **/
138
    public $hideicon = false;
139
    /** @var bool Set to true if we KNOW that this node can be expanded.  */
140
    public $isexpandable = false;
141
    /** @var array */
142
    protected $namedtypes = array(0 => 'system', 10 => 'category', 20 => 'course', 30 => 'structure', 40 => 'activity',
143
                                  50 => 'resource', 60 => 'custom', 70 => 'setting', 71 => 'siteadmin', 80 => 'user',
144
                                  90 => 'container');
145
    /** @var moodle_url */
146
    protected static $fullmeurl = null;
147
    /** @var bool toogles auto matching of active node */
148
    public static $autofindactive = true;
149
    /** @var bool should we load full admin tree or rely on AJAX for performance reasons */
150
    protected static $loadadmintree = false;
151
    /** @var mixed If set to an int, that section will be included even if it has no activities */
152
    public $includesectionnum = false;
153
    /** @var bool does the node need to be loaded via ajax */
154
    public $requiresajaxloading = false;
155
    /** @var bool If set to true this node will be added to the "flat" navigation */
156
    public $showinflatnavigation = false;
157
    /** @var bool If set to true this node will be forced into a "more" menu whenever possible */
158
    public $forceintomoremenu = false;
159
    /** @var bool If set to true this node will be displayed in the "secondary" navigation when applicable */
160
    public $showinsecondarynavigation = true;
161
    /** @var bool If set to true the children of this node will be displayed within a submenu when applicable */
162
    public $showchildreninsubmenu = false;
163
    /** @var string tab element ID. */
164
    public $tab;
165
    /** @var string unique identifier. */
166
    public $moremenuid;
167
    /** @var bool node that have children. */
168
    public $haschildren;
169
 
170
    /**
171
     * Constructs a new navigation_node
172
     *
173
     * @param array|string $properties Either an array of properties or a string to use
174
     *                     as the text for the node
175
     */
176
    public function __construct($properties) {
177
        if (is_array($properties)) {
178
            // Check the array for each property that we allow to set at construction.
179
            // text         - The main content for the node
180
            // shorttext    - A short text if required for the node
181
            // icon         - The icon to display for the node
182
            // type         - The type of the node
183
            // key          - The key to use to identify the node
184
            // parent       - A reference to the nodes parent
185
            // action       - The action to attribute to this node, usually a URL to link to
186
            if (array_key_exists('text', $properties)) {
187
                $this->text = $properties['text'];
188
            }
189
            if (array_key_exists('shorttext', $properties)) {
190
                $this->shorttext = $properties['shorttext'];
191
            }
192
            if (!array_key_exists('icon', $properties)) {
193
                $properties['icon'] = new pix_icon('i/navigationitem', '');
194
            }
195
            $this->icon = $properties['icon'];
196
            if ($this->icon instanceof pix_icon) {
197
                if (empty($this->icon->attributes['class'])) {
198
                    $this->icon->attributes['class'] = 'navicon';
199
                } else {
200
                    $this->icon->attributes['class'] .= ' navicon';
201
                }
202
            }
203
            if (array_key_exists('type', $properties)) {
204
                $this->type = $properties['type'];
205
            } else {
206
                $this->type = self::TYPE_CUSTOM;
207
            }
208
            if (array_key_exists('key', $properties)) {
209
                $this->key = $properties['key'];
210
            }
211
            // This needs to happen last because of the check_if_active call that occurs
212
            if (array_key_exists('action', $properties)) {
213
                $this->action = $properties['action'];
214
                if (is_string($this->action)) {
215
                    $this->action = new moodle_url($this->action);
216
                }
217
                if (self::$autofindactive) {
218
                    $this->check_if_active();
219
                }
220
            }
221
            if (array_key_exists('parent', $properties)) {
222
                $this->set_parent($properties['parent']);
223
            }
224
        } else if (is_string($properties)) {
225
            $this->text = $properties;
226
        }
227
        if ($this->text === null) {
228
            throw new coding_exception('You must set the text for the node when you create it.');
229
        }
230
        // Instantiate a new navigation node collection for this nodes children
231
        $this->children = new navigation_node_collection();
232
    }
233
 
234
    /**
235
     * Checks if this node is the active node.
236
     *
237
     * This is determined by comparing the action for the node against the
238
     * defined URL for the page. A match will see this node marked as active.
239
     *
240
     * @param int $strength One of URL_MATCH_EXACT, URL_MATCH_PARAMS, or URL_MATCH_BASE
241
     * @return bool
242
     */
243
    public function check_if_active($strength=URL_MATCH_EXACT) {
244
        global $FULLME, $PAGE;
245
        // Set fullmeurl if it hasn't already been set
246
        if (self::$fullmeurl == null) {
247
            if ($PAGE->has_set_url()) {
248
                self::override_active_url(new moodle_url($PAGE->url));
249
            } else {
250
                self::override_active_url(new moodle_url($FULLME));
251
            }
252
        }
253
 
254
        // Compare the action of this node against the fullmeurl
255
        if ($this->action instanceof moodle_url && $this->action->compare(self::$fullmeurl, $strength)) {
256
            $this->make_active();
257
            return true;
258
        }
259
        return false;
260
    }
261
 
262
    /**
263
     * True if this nav node has siblings in the tree.
264
     *
265
     * @return bool
266
     */
267
    public function has_siblings() {
268
        if (empty($this->parent) || empty($this->parent->children)) {
269
            return false;
270
        }
271
        if ($this->parent->children instanceof navigation_node_collection) {
272
            $count = $this->parent->children->count();
273
        } else {
274
            $count = count($this->parent->children);
275
        }
276
        return ($count > 1);
277
    }
278
 
279
    /**
280
     * Get a list of sibling navigation nodes at the same level as this one.
281
     *
282
     * @return bool|array of navigation_node
283
     */
284
    public function get_siblings() {
285
        // Returns a list of the siblings of the current node for display in a flat navigation element. Either
286
        // the in-page links or the breadcrumb links.
287
        $siblings = false;
288
 
289
        if ($this->has_siblings()) {
290
            $siblings = [];
291
            foreach ($this->parent->children as $child) {
292
                if ($child->display) {
293
                    $siblings[] = $child;
294
                }
295
            }
296
        }
297
        return $siblings;
298
    }
299
 
300
    /**
301
     * This sets the URL that the URL of new nodes get compared to when locating
302
     * the active node.
303
     *
304
     * The active node is the node that matches the URL set here. By default this
305
     * is either $PAGE->url or if that hasn't been set $FULLME.
306
     *
307
     * @param moodle_url $url The url to use for the fullmeurl.
308
     * @param bool $loadadmintree use true if the URL point to administration tree
309
     */
310
    public static function override_active_url(moodle_url $url, $loadadmintree = false) {
311
        // Clone the URL, in case the calling script changes their URL later.
312
        self::$fullmeurl = new moodle_url($url);
313
        // True means we do not want AJAX loaded admin tree, required for all admin pages.
314
        if ($loadadmintree) {
315
            // Do not change back to false if already set.
316
            self::$loadadmintree = true;
317
        }
318
    }
319
 
320
    /**
321
     * Use when page is linked from the admin tree,
322
     * if not used navigation could not find the page using current URL
323
     * because the tree is not fully loaded.
324
     */
325
    public static function require_admin_tree() {
326
        self::$loadadmintree = true;
327
    }
328
 
329
    /**
330
     * Creates a navigation node, ready to add it as a child using add_node
331
     * function. (The created node needs to be added before you can use it.)
332
     * @param string $text
333
     * @param moodle_url|action_link $action
334
     * @param int $type
335
     * @param string $shorttext
336
     * @param string|int $key
337
     * @param pix_icon $icon
338
     * @return navigation_node
339
     */
340
    public static function create($text, $action=null, $type=self::TYPE_CUSTOM,
1441 ariadna 341
            $shorttext=null, $key=null, ?pix_icon $icon=null) {
1 efrain 342
        if ($action && !($action instanceof moodle_url || $action instanceof action_link)) {
343
            debugging(
344
                "It is required that the action provided be either an action_url|moodle_url." .
345
                " Please update your definition.", E_NOTICE);
346
        }
347
        // Properties array used when creating the new navigation node
348
        $itemarray = array(
349
            'text' => $text,
350
            'type' => $type
351
        );
352
        // Set the action if one was provided
353
        if ($action!==null) {
354
            $itemarray['action'] = $action;
355
        }
356
        // Set the shorttext if one was provided
357
        if ($shorttext!==null) {
358
            $itemarray['shorttext'] = $shorttext;
359
        }
360
        // Set the icon if one was provided
361
        if ($icon!==null) {
362
            $itemarray['icon'] = $icon;
363
        }
364
        // Set the key
365
        $itemarray['key'] = $key;
366
        // Construct and return
367
        return new navigation_node($itemarray);
368
    }
369
 
370
    /**
371
     * Adds a navigation node as a child of this node.
372
     *
373
     * @param string $text
374
     * @param moodle_url|action_link|string $action
375
     * @param ?int $type
376
     * @param string $shorttext
377
     * @param string|int $key
378
     * @param pix_icon $icon
379
     * @return navigation_node
380
     */
1441 ariadna 381
    public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, ?pix_icon $icon=null) {
1 efrain 382
        if ($action && is_string($action)) {
383
            $action = new moodle_url($action);
384
        }
385
        // Create child node
386
        $childnode = self::create($text, $action, $type, $shorttext, $key, $icon);
387
 
388
        // Add the child to end and return
389
        return $this->add_node($childnode);
390
    }
391
 
392
    /**
393
     * Adds a navigation node as a child of this one, given a $node object
394
     * created using the create function.
395
     * @param navigation_node $childnode Node to add
396
     * @param string $beforekey
397
     * @return navigation_node The added node
398
     */
399
    public function add_node(navigation_node $childnode, $beforekey=null) {
400
        // First convert the nodetype for this node to a branch as it will now have children
401
        if ($this->nodetype !== self::NODETYPE_BRANCH) {
402
            $this->nodetype = self::NODETYPE_BRANCH;
403
        }
404
        // Set the parent to this node
405
        $childnode->set_parent($this);
406
 
407
        // Default the key to the number of children if not provided
408
        if ($childnode->key === null) {
409
            $childnode->key = $this->children->count();
410
        }
411
 
412
        // Add the child using the navigation_node_collections add method
413
        $node = $this->children->add($childnode, $beforekey);
414
 
415
        // If added node is a category node or the user is logged in and it's a course
416
        // then mark added node as a branch (makes it expandable by AJAX)
417
        $type = $childnode->type;
418
        if (($type == self::TYPE_CATEGORY) || (isloggedin() && ($type == self::TYPE_COURSE)) || ($type == self::TYPE_MY_CATEGORY) ||
419
                ($type === self::TYPE_SITE_ADMIN)) {
420
            $node->nodetype = self::NODETYPE_BRANCH;
421
        }
422
        // If this node is hidden mark it's children as hidden also
423
        if ($this->hidden) {
424
            $node->hidden = true;
425
        }
426
        // Return added node (reference returned by $this->children->add()
427
        return $node;
428
    }
429
 
430
    /**
431
     * Return a list of all the keys of all the child nodes.
432
     * @return array the keys.
433
     */
434
    public function get_children_key_list() {
435
        return $this->children->get_key_list();
436
    }
437
 
438
    /**
439
     * Searches for a node of the given type with the given key.
440
     *
441
     * This searches this node plus all of its children, and their children....
442
     * If you know the node you are looking for is a child of this node then please
443
     * use the get method instead.
444
     *
445
     * @param int|string $key The key of the node we are looking for
446
     * @param ?int $type One of navigation_node::TYPE_*
447
     * @return navigation_node|false
448
     */
449
    public function find($key, $type) {
450
        return $this->children->find($key, $type);
451
    }
452
 
453
    /**
454
     * Walk the tree building up a list of all the flat navigation nodes.
455
     *
456
     * @deprecated since Moodle 4.0
457
     * @param flat_navigation $nodes List of the found flat navigation nodes.
458
     * @param boolean $showdivider Show a divider before the first node.
459
     * @param string $label A label for the collection of navigation links.
460
     */
461
    public function build_flat_navigation_list(flat_navigation $nodes, $showdivider = false, $label = '') {
462
        debugging("Function has been deprecated with the deprecation of the flat_navigation class.");
463
        if ($this->showinflatnavigation) {
464
            $indent = 0;
465
            if ($this->type == self::TYPE_COURSE || $this->key === self::COURSE_INDEX_PAGE) {
466
                $indent = 1;
467
            }
468
            $flat = new flat_navigation_node($this, $indent);
469
            $flat->set_showdivider($showdivider, $label);
470
            $nodes->add($flat);
471
        }
472
        foreach ($this->children as $child) {
473
            $child->build_flat_navigation_list($nodes, false);
474
        }
475
    }
476
 
477
    /**
478
     * Get the child of this node that has the given key + (optional) type.
479
     *
480
     * If you are looking for a node and want to search all children + their children
481
     * then please use the find method instead.
482
     *
483
     * @param int|string $key The key of the node we are looking for
484
     * @param int $type One of navigation_node::TYPE_*
485
     * @return navigation_node|false
486
     */
487
    public function get($key, $type=null) {
488
        return $this->children->get($key, $type);
489
    }
490
 
491
    /**
492
     * Removes this node.
493
     *
494
     * @return bool
495
     */
496
    public function remove() {
497
        return $this->parent->children->remove($this->key, $this->type);
498
    }
499
 
500
    /**
501
     * Checks if this node has or could have any children
502
     *
503
     * @return bool Returns true if it has children or could have (by AJAX expansion)
504
     */
505
    public function has_children() {
506
        return ($this->nodetype === navigation_node::NODETYPE_BRANCH || $this->children->count()>0 || $this->isexpandable);
507
    }
508
 
509
    /**
510
     * Marks this node as active and forces it open.
511
     *
512
     * Important: If you are here because you need to mark a node active to get
513
     * the navigation to do what you want have you looked at {@link navigation_node::override_active_url()}?
514
     * You can use it to specify a different URL to match the active navigation node on
515
     * rather than having to locate and manually mark a node active.
516
     */
517
    public function make_active() {
518
        $this->isactive = true;
519
        $this->add_class('active_tree_node');
520
        $this->force_open();
521
        if ($this->parent !== null) {
522
            $this->parent->make_inactive();
523
        }
524
    }
525
 
526
    /**
527
     * Marks a node as inactive and recusised back to the base of the tree
528
     * doing the same to all parents.
529
     */
530
    public function make_inactive() {
531
        $this->isactive = false;
532
        $this->remove_class('active_tree_node');
533
        if ($this->parent !== null) {
534
            $this->parent->make_inactive();
535
        }
536
    }
537
 
538
    /**
539
     * Forces this node to be open and at the same time forces open all
540
     * parents until the root node.
541
     *
542
     * Recursive.
543
     */
544
    public function force_open() {
545
        $this->forceopen = true;
546
        if ($this->parent !== null) {
547
            $this->parent->force_open();
548
        }
549
    }
550
 
551
    /**
552
     * Adds a CSS class to this node.
553
     *
554
     * @param string $class
555
     * @return bool
556
     */
557
    public function add_class($class) {
558
        if (!in_array($class, $this->classes)) {
559
            $this->classes[] = $class;
560
        }
561
        return true;
562
    }
563
 
564
    /**
565
     * Adds an HTML attribute to this node.
566
     *
567
     * @param string $name
568
     * @param string $value
569
     */
570
    public function add_attribute(string $name, string $value): void {
571
        $this->attributes[] = ['name' => $name, 'value' => $value];
572
    }
573
 
574
    /**
575
     * Removes a CSS class from this node.
576
     *
577
     * @param string $class
578
     * @return bool True if the class was successfully removed.
579
     */
580
    public function remove_class($class) {
581
        if (in_array($class, $this->classes)) {
582
            $key = array_search($class,$this->classes);
583
            if ($key!==false) {
584
                // Remove the class' array element.
585
                unset($this->classes[$key]);
586
                // Reindex the array to avoid failures when the classes array is iterated later in mustache templates.
587
                $this->classes = array_values($this->classes);
588
 
589
                return true;
590
            }
591
        }
592
        return false;
593
    }
594
 
595
    /**
596
     * Sets the title for this node and forces Moodle to utilise it.
597
     *
598
     * Note that this method is named identically to the public "title" property of the class, which unfortunately confuses
599
     * our Mustache renderer, because it will see the method and try and call it without any arguments (hence must be nullable)
600
     * before trying to access the public property
601
     *
602
     * @param string|null $title
603
     * @return string
604
     */
605
    public function title(?string $title = null): string {
606
        if ($title !== null) {
607
            $this->title = $title;
608
            $this->forcetitle = true;
609
        }
610
        return (string) $this->title;
611
    }
612
 
613
    /**
614
     * Resets the page specific information on this node if it is being unserialised.
615
     */
616
    public function __wakeup(){
617
        $this->forceopen = false;
618
        $this->isactive = false;
619
        $this->remove_class('active_tree_node');
620
    }
621
 
622
    /**
623
     * Checks if this node or any of its children contain the active node.
624
     *
625
     * Recursive.
626
     *
627
     * @return bool
628
     */
629
    public function contains_active_node() {
630
        if ($this->isactive) {
631
            return true;
632
        } else {
633
            foreach ($this->children as $child) {
634
                if ($child->isactive || $child->contains_active_node()) {
635
                    return true;
636
                }
637
            }
638
        }
639
        return false;
640
    }
641
 
642
    /**
643
     * To better balance the admin tree, we want to group all the short top branches together.
644
     *
645
     * This means < 8 nodes and no subtrees.
646
     *
647
     * @return bool
648
     */
649
    public function is_short_branch() {
650
        $limit = 8;
651
        if ($this->children->count() >= $limit) {
652
            return false;
653
        }
654
        foreach ($this->children as $child) {
655
            if ($child->has_children()) {
656
                return false;
657
            }
658
        }
659
        return true;
660
    }
661
 
662
    /**
663
     * Finds the active node.
664
     *
665
     * Searches this nodes children plus all of the children for the active node
666
     * and returns it if found.
667
     *
668
     * Recursive.
669
     *
670
     * @return navigation_node|false
671
     */
672
    public function find_active_node() {
673
        if ($this->isactive) {
674
            return $this;
675
        } else {
676
            foreach ($this->children as &$child) {
677
                $outcome = $child->find_active_node();
678
                if ($outcome !== false) {
679
                    return $outcome;
680
                }
681
            }
682
        }
683
        return false;
684
    }
685
 
686
    /**
687
     * Searches all children for the best matching active node
688
     * @param int $strength The url match to be made.
689
     * @return navigation_node|false
690
     */
691
    public function search_for_active_node($strength = URL_MATCH_BASE) {
692
        if ($this->check_if_active($strength)) {
693
            return $this;
694
        } else {
695
            foreach ($this->children as &$child) {
696
                $outcome = $child->search_for_active_node($strength);
697
                if ($outcome !== false) {
698
                    return $outcome;
699
                }
700
            }
701
        }
702
        return false;
703
    }
704
 
705
    /**
706
     * Gets the content for this node.
707
     *
708
     * @param bool $shorttext If true shorttext is used rather than the normal text
709
     * @return string
710
     */
711
    public function get_content($shorttext=false) {
712
        $navcontext = \context_helper::get_navigation_filter_context(null);
713
        $options = !empty($navcontext) ? ['context' => $navcontext] : null;
714
 
715
        if ($shorttext && $this->shorttext!==null) {
716
            return format_string($this->shorttext, null, $options);
717
        } else {
718
            return format_string($this->text, null, $options);
719
        }
720
    }
721
 
722
    /**
723
     * Gets the title to use for this node.
724
     *
725
     * @return string
726
     */
727
    public function get_title() {
728
        if ($this->forcetitle || $this->action != null){
729
            return $this->title;
730
        } else {
731
            return '';
732
        }
733
    }
734
 
735
    /**
736
     * Used to easily determine if this link in the breadcrumbs has a valid action/url.
737
     *
738
     * @return boolean
739
     */
740
    public function has_action() {
741
        return !empty($this->action);
742
    }
743
 
744
    /**
745
     * Used to easily determine if the action is an internal link.
746
     *
747
     * @return bool
748
     */
749
    public function has_internal_action(): bool {
750
        global $CFG;
751
        if ($this->has_action()) {
752
            $url = $this->action();
753
            if ($this->action() instanceof \action_link) {
754
                $url = $this->action()->url;
755
            }
756
 
757
            if (($url->out() === $CFG->wwwroot) || (strpos($url->out(), $CFG->wwwroot.'/') === 0)) {
758
                return true;
759
            }
760
        }
761
        return false;
762
    }
763
 
764
    /**
765
     * Used to easily determine if this link in the breadcrumbs is hidden.
766
     *
767
     * @return boolean
768
     */
769
    public function is_hidden() {
770
        return $this->hidden;
771
    }
772
 
773
    /**
774
     * Gets the CSS class to add to this node to describe its type
775
     *
776
     * @return string
777
     */
778
    public function get_css_type() {
779
        if (array_key_exists($this->type, $this->namedtypes)) {
780
            return 'type_'.$this->namedtypes[$this->type];
781
        }
782
        return 'type_unknown';
783
    }
784
 
785
    /**
786
     * Finds all nodes that are expandable by AJAX
787
     *
788
     * @param array $expandable An array by reference to populate with expandable nodes.
789
     */
790
    public function find_expandable(array &$expandable) {
791
        foreach ($this->children as &$child) {
792
            if ($child->display && $child->has_children() && $child->children->count() == 0) {
793
                $child->id = 'expandable_branch_'.$child->type.'_'.clean_param($child->key, PARAM_ALPHANUMEXT);
794
                $this->add_class('canexpand');
795
                $child->requiresajaxloading = true;
796
                $expandable[] = array('id' => $child->id, 'key' => $child->key, 'type' => $child->type);
797
            }
798
            $child->find_expandable($expandable);
799
        }
800
    }
801
 
802
    /**
803
     * Finds all nodes of a given type (recursive)
804
     *
805
     * @param int $type One of navigation_node::TYPE_*
806
     * @return array
807
     */
808
    public function find_all_of_type($type) {
809
        $nodes = $this->children->type($type);
810
        foreach ($this->children as &$node) {
811
            $childnodes = $node->find_all_of_type($type);
812
            $nodes = array_merge($nodes, $childnodes);
813
        }
814
        return $nodes;
815
    }
816
 
817
    /**
818
     * Removes this node if it is empty
819
     */
820
    public function trim_if_empty() {
821
        if ($this->children->count() == 0) {
822
            $this->remove();
823
        }
824
    }
825
 
826
    /**
827
     * Creates a tab representation of this nodes children that can be used
828
     * with print_tabs to produce the tabs on a page.
829
     *
830
     * call_user_func_array('print_tabs', $node->get_tabs_array());
831
     *
832
     * @param array $inactive
833
     * @param bool $return
834
     * @return array Array (tabs, selected, inactive, activated, return)
835
     */
836
    public function get_tabs_array(array $inactive=array(), $return=false) {
837
        $tabs = array();
838
        $rows = array();
839
        $selected = null;
840
        $activated = array();
841
        foreach ($this->children as $node) {
842
            $tabs[] = new tabobject($node->key, $node->action, $node->get_content(), $node->get_title());
843
            if ($node->contains_active_node()) {
844
                if ($node->children->count() > 0) {
845
                    $activated[] = $node->key;
846
                    foreach ($node->children as $child) {
847
                        if ($child->contains_active_node()) {
848
                            $selected = $child->key;
849
                        }
850
                        $rows[] = new tabobject($child->key, $child->action, $child->get_content(), $child->get_title());
851
                    }
852
                } else {
853
                    $selected = $node->key;
854
                }
855
            }
856
        }
857
        return array(array($tabs, $rows), $selected, $inactive, $activated, $return);
858
    }
859
 
860
    /**
861
     * Sets the parent for this node and if this node is active ensures that the tree is properly
862
     * adjusted as well.
863
     *
864
     * @param navigation_node $parent
865
     */
866
    public function set_parent(navigation_node $parent) {
867
        // Set the parent (thats the easy part)
868
        $this->parent = $parent;
869
        // Check if this node is active (this is checked during construction)
870
        if ($this->isactive) {
871
            // Force all of the parent nodes open so you can see this node
872
            $this->parent->force_open();
873
            // Make all parents inactive so that its clear where we are.
874
            $this->parent->make_inactive();
875
        }
876
    }
877
 
878
    /**
879
     * Hides the node and any children it has.
880
     *
881
     * @since Moodle 2.5
882
     * @param array $typestohide Optional. An array of node types that should be hidden.
883
     *      If null all nodes will be hidden.
884
     *      If an array is given then nodes will only be hidden if their type mtatches an element in the array.
885
     *          e.g. array(navigation_node::TYPE_COURSE) would hide only course nodes.
886
     */
1441 ariadna 887
    public function hide(?array $typestohide = null) {
1 efrain 888
        if ($typestohide === null || in_array($this->type, $typestohide)) {
889
            $this->display = false;
890
            if ($this->has_children()) {
891
                foreach ($this->children as $child) {
892
                    $child->hide($typestohide);
893
                }
894
            }
895
        }
896
    }
897
 
898
    /**
899
     * Get the action url for this navigation node.
900
     * Called from templates.
901
     *
902
     * @since Moodle 3.2
903
     */
904
    public function action() {
905
        if ($this->action instanceof moodle_url) {
906
            return $this->action;
907
        } else if ($this->action instanceof action_link) {
908
            return $this->action->url;
909
        }
910
        return $this->action;
911
    }
912
 
913
    /**
914
     * Return an array consisting of the additional attributes for the action url.
915
     *
916
     * @return array Formatted array to parse in a template
917
     */
918
    public function actionattributes() {
919
        if ($this->action instanceof action_link) {
920
            return array_map(function($key, $value) {
921
                return [
922
                    'name' => $key,
923
                    'value' => $value
924
                ];
925
            }, array_keys($this->action->attributes), $this->action->attributes);
926
        }
927
 
928
        return [];
929
    }
930
 
931
    /**
932
     * Check whether the node's action is of type action_link.
933
     *
934
     * @return bool
935
     */
936
    public function is_action_link() {
937
        return $this->action instanceof action_link;
938
    }
939
 
940
    /**
941
     * Return an array consisting of the actions for the action link.
942
     *
943
     * @return array Formatted array to parse in a template
944
     */
945
    public function action_link_actions() {
946
        global $PAGE;
947
 
948
        if (!$this->is_action_link()) {
949
            return [];
950
        }
951
 
952
        $actionid = $this->action->attributes['id'];
953
        $actionsdata = array_map(function($action) use ($PAGE, $actionid) {
954
            $data = $action->export_for_template($PAGE->get_renderer('core'));
955
            $data->id = $actionid;
956
            return $data;
957
        }, !empty($this->action->actions) ? $this->action->actions : []);
958
 
959
        return ['actions' => $actionsdata];
960
    }
961
 
962
    /**
963
     * Sets whether the node and its children should be added into a "more" menu whenever possible.
964
     *
965
     * @param bool $forceintomoremenu
966
     */
967
    public function set_force_into_more_menu(bool $forceintomoremenu = false) {
968
        $this->forceintomoremenu = $forceintomoremenu;
969
        foreach ($this->children as $child) {
970
            $child->set_force_into_more_menu($forceintomoremenu);
971
        }
972
    }
973
 
974
    /**
975
     * Sets whether the node and its children should be displayed in the "secondary" navigation when applicable.
976
     *
977
     * @param bool $show
978
     */
979
    public function set_show_in_secondary_navigation(bool $show = true) {
980
        $this->showinsecondarynavigation = $show;
981
        foreach ($this->children as $child) {
982
            $child->set_show_in_secondary_navigation($show);
983
        }
984
    }
985
 
986
    /**
987
     * Add the menu item to handle locking and unlocking of a conext.
988
     *
989
     * @param \navigation_node $node Node to add
990
     * @param \context $context The context to be locked
991
     */
992
    protected function add_context_locking_node(\navigation_node $node, \context $context) {
993
        global $CFG;
994
        // Manage context locking.
995
        if (!empty($CFG->contextlocking) && has_capability('moodle/site:managecontextlocks', $context)) {
996
            $parentcontext = $context->get_parent_context();
997
            if (empty($parentcontext) || !$parentcontext->locked) {
998
                if ($context->locked) {
999
                    $lockicon = 'i/unlock';
1000
                    $lockstring = get_string('managecontextunlock', 'admin');
1001
                } else {
1002
                    $lockicon = 'i/lock';
1003
                    $lockstring = get_string('managecontextlock', 'admin');
1004
                }
1005
                $node->add(
1006
                    $lockstring,
1007
                    new moodle_url(
1008
                        '/admin/lock.php',
1009
                        [
1010
                            'id' => $context->id,
1011
                        ]
1012
                    ),
1013
                    self::TYPE_SETTING,
1014
                    null,
1015
                    'contextlocking',
1016
                     new pix_icon($lockicon, '')
1017
                );
1018
            }
1019
        }
1441 ariadna 1020
    }
1 efrain 1021
 
1441 ariadna 1022
    /**
1023
     * Reset all static data.
1024
     *
1025
     * @throws coding_exception if called outside of a unit test
1026
     */
1027
    public static function reset_all_data(): void {
1028
        if (!defined('PHPUNIT_TEST') || !PHPUNIT_TEST) {
1029
            throw new coding_exception('Resetting all data is not allowed outside of PHPUnit tests.');
1030
        }
1031
 
1032
        self::$fullmeurl = null;
1033
        self::$autofindactive = true;
1034
        self::$loadadmintree = false;
1 efrain 1035
    }
1036
}
1037
 
1038
/**
1039
 * Navigation node collection
1040
 *
1041
 * This class is responsible for managing a collection of navigation nodes.
1042
 * It is required because a node's unique identifier is a combination of both its
1043
 * key and its type.
1044
 *
1045
 * Originally an array was used with a string key that was a combination of the two
1046
 * however it was decided that a better solution would be to use a class that
1047
 * implements the standard IteratorAggregate interface.
1048
 *
1049
 * @package   core
1050
 * @category  navigation
1051
 * @copyright 2010 Sam Hemelryk
1052
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1053
 */
1054
class navigation_node_collection implements IteratorAggregate, Countable {
1055
    /**
1056
     * A multidimensional array to where the first key is the type and the second
1057
     * key is the nodes key.
1058
     * @var array
1059
     */
1060
    protected $collection = array();
1061
    /**
1062
     * An array that contains references to nodes in the same order they were added.
1063
     * This is maintained as a progressive array.
1064
     * @var array
1065
     */
1066
    protected $orderedcollection = array();
1067
    /**
1068
     * A reference to the last node that was added to the collection
1069
     * @var navigation_node
1070
     */
1071
    protected $last = null;
1072
    /**
1073
     * The total number of items added to this array.
1074
     * @var int
1075
     */
1076
    protected $count = 0;
1077
 
1078
    /**
1079
     * Label for collection of nodes.
1080
     * @var string
1081
     */
1082
    protected $collectionlabel = '';
1083
 
1084
    /**
1085
     * Adds a navigation node to the collection
1086
     *
1087
     * @param navigation_node $node Node to add
1088
     * @param string $beforekey If specified, adds before a node with this key,
1089
     *   otherwise adds at end
1090
     * @return navigation_node Added node
1091
     */
1092
    public function add(navigation_node $node, $beforekey=null) {
1093
        global $CFG;
1094
        $key = $node->key;
1095
        $type = $node->type;
1096
 
1097
        // First check we have a 2nd dimension for this type
1098
        if (!array_key_exists($type, $this->orderedcollection)) {
1099
            $this->orderedcollection[$type] = array();
1100
        }
1101
        // Check for a collision and report if debugging is turned on
1102
        if ($CFG->debug && array_key_exists($key, $this->orderedcollection[$type])) {
1103
            debugging('Navigation node intersect: Adding a node that already exists '.$key, DEBUG_DEVELOPER);
1104
        }
1105
 
1106
        // Find the key to add before
1107
        $newindex = $this->count;
1108
        $last = true;
1109
        if ($beforekey !== null) {
1110
            foreach ($this->collection as $index => $othernode) {
1111
                if ($othernode->key === $beforekey) {
1112
                    $newindex = $index;
1113
                    $last = false;
1114
                    break;
1115
                }
1116
            }
1117
            if ($newindex === $this->count) {
1118
                debugging('Navigation node add_before: Reference node not found ' . $beforekey .
1119
                        ', options: ' . implode(' ', $this->get_key_list()), DEBUG_DEVELOPER);
1120
            }
1121
        }
1122
 
1123
        // Add the node to the appropriate place in the by-type structure (which
1124
        // is not ordered, despite the variable name)
1125
        $this->orderedcollection[$type][$key] = $node;
1126
        if (!$last) {
1127
            // Update existing references in the ordered collection (which is the
1128
            // one that isn't called 'ordered') to shuffle them along if required
1129
            for ($oldindex = $this->count; $oldindex > $newindex; $oldindex--) {
1130
                $this->collection[$oldindex] = $this->collection[$oldindex - 1];
1131
            }
1132
        }
1133
        // Add a reference to the node to the progressive collection.
1134
        $this->collection[$newindex] = $this->orderedcollection[$type][$key];
1135
        // Update the last property to a reference to this new node.
1136
        $this->last = $this->orderedcollection[$type][$key];
1137
 
1138
        // Reorder the array by index if needed
1139
        if (!$last) {
1140
            ksort($this->collection);
1141
        }
1142
        $this->count++;
1143
        // Return the reference to the now added node
1144
        return $node;
1145
    }
1146
 
1147
    /**
1148
     * Return a list of all the keys of all the nodes.
1149
     * @return array the keys.
1150
     */
1151
    public function get_key_list() {
1152
        $keys = array();
1153
        foreach ($this->collection as $node) {
1154
            $keys[] = $node->key;
1155
        }
1156
        return $keys;
1157
    }
1158
 
1159
    /**
1160
     * Set a label for this collection.
1161
     *
1162
     * @param string $label
1163
     */
1164
    public function set_collectionlabel($label) {
1165
        $this->collectionlabel = $label;
1166
    }
1167
 
1168
    /**
1169
     * Return a label for this collection.
1170
     *
1171
     * @return string
1172
     */
1173
    public function get_collectionlabel() {
1174
        return $this->collectionlabel;
1175
    }
1176
 
1177
    /**
1178
     * Fetches a node from this collection.
1179
     *
1180
     * @param string|int $key The key of the node we want to find.
1181
     * @param int $type One of navigation_node::TYPE_*.
1182
     * @return navigation_node|null|false
1183
     */
1184
    public function get($key, $type=null) {
1185
        if ($type !== null) {
1186
            // If the type is known then we can simply check and fetch
1187
            if (!empty($this->orderedcollection[$type][$key])) {
1188
                return $this->orderedcollection[$type][$key];
1189
            }
1190
        } else {
1191
            // Because we don't know the type we look in the progressive array
1192
            foreach ($this->collection as $node) {
1193
                if ($node->key === $key) {
1194
                    return $node;
1195
                }
1196
            }
1197
        }
1198
        return false;
1199
    }
1200
 
1201
    /**
1202
     * Searches for a node with matching key and type.
1203
     *
1204
     * This function searches both the nodes in this collection and all of
1205
     * the nodes in each collection belonging to the nodes in this collection.
1206
     *
1207
     * Recursive.
1208
     *
1209
     * @param string|int $key  The key of the node we want to find.
1210
     * @param int $type  One of navigation_node::TYPE_*.
1211
     * @return navigation_node|false
1212
     */
1213
    public function find($key, $type=null) {
1214
        if ($type !== null && array_key_exists($type, $this->orderedcollection) && array_key_exists($key, $this->orderedcollection[$type])) {
1215
            return $this->orderedcollection[$type][$key];
1216
        } else {
1217
            $nodes = $this->getIterator();
1218
            // Search immediate children first
1219
            foreach ($nodes as &$node) {
1220
                if ($node->key === $key && ($type === null || $type === $node->type)) {
1221
                    return $node;
1222
                }
1223
            }
1224
            // Now search each childs children
1225
            foreach ($nodes as &$node) {
1226
                $result = $node->children->find($key, $type);
1227
                if ($result !== false) {
1228
                    return $result;
1229
                }
1230
            }
1231
        }
1232
        return false;
1233
    }
1234
 
1235
    /**
1236
     * Fetches the last node that was added to this collection
1237
     *
1238
     * @return navigation_node
1239
     */
1240
    public function last() {
1241
        return $this->last;
1242
    }
1243
 
1244
    /**
1245
     * Fetches all nodes of a given type from this collection
1246
     *
1247
     * @param string|int $type  node type being searched for.
1248
     * @return array ordered collection
1249
     */
1250
    public function type($type) {
1251
        if (!array_key_exists($type, $this->orderedcollection)) {
1252
            $this->orderedcollection[$type] = array();
1253
        }
1254
        return $this->orderedcollection[$type];
1255
    }
1256
    /**
1257
     * Removes the node with the given key and type from the collection
1258
     *
1259
     * @param string|int $key The key of the node we want to find.
1260
     * @param int $type
1261
     * @return bool
1262
     */
1263
    public function remove($key, $type=null) {
1264
        $child = $this->get($key, $type);
1265
        if ($child !== false) {
1266
            foreach ($this->collection as $colkey => $node) {
1267
                if ($node->key === $key && (is_null($type) || $node->type == $type)) {
1268
                    unset($this->collection[$colkey]);
1269
                    $this->collection = array_values($this->collection);
1270
                    break;
1271
                }
1272
            }
1273
            unset($this->orderedcollection[$child->type][$child->key]);
1274
            $this->count--;
1275
            return true;
1276
        }
1277
        return false;
1278
    }
1279
 
1280
    /**
1281
     * Gets the number of nodes in this collection
1282
     *
1283
     * This option uses an internal count rather than counting the actual options to avoid
1284
     * a performance hit through the count function.
1285
     *
1286
     * @return int
1287
     */
1288
    public function count(): int {
1289
        return $this->count;
1290
    }
1291
    /**
1292
     * Gets an array iterator for the collection.
1293
     *
1294
     * This is required by the IteratorAggregator interface and is used by routines
1295
     * such as the foreach loop.
1296
     *
1297
     * @return ArrayIterator
1298
     */
1299
    public function getIterator(): Traversable {
1300
        return new ArrayIterator($this->collection);
1301
    }
1302
}
1303
 
1304
/**
1305
 * The global navigation class used for... the global navigation
1306
 *
1307
 * This class is used by PAGE to store the global navigation for the site
1308
 * and is then used by the settings nav and navbar to save on processing and DB calls
1309
 *
1310
 * See
1311
 * {@link lib/pagelib.php} {@link moodle_page::initialise_theme_and_output()}
1312
 * {@link lib/ajax/getnavbranch.php} Called by ajax
1313
 *
1314
 * @package   core
1315
 * @category  navigation
1316
 * @copyright 2009 Sam Hemelryk
1317
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1318
 */
1319
class global_navigation extends navigation_node {
1320
    /** @var moodle_page The Moodle page this navigation object belongs to. */
1321
    protected $page;
1322
    /** @var bool switch to let us know if the navigation object is initialised*/
1323
    protected $initialised = false;
1324
    /** @var array An array of course information */
1325
    protected $mycourses = array();
1326
    /** @var navigation_node[] An array for containing  root navigation nodes */
1327
    protected $rootnodes = array();
1328
    /** @var bool A switch for whether to show empty sections in the navigation */
1329
    protected $showemptysections = true;
1330
    /** @var bool A switch for whether courses should be shown within categories on the navigation. */
1331
    protected $showcategories = null;
1332
    /** @var null@var bool A switch for whether or not to show categories in the my courses branch. */
1333
    protected $showmycategories = null;
1334
    /** @var array An array of stdClasses for users that the navigation is extended for */
1335
    protected $extendforuser = array();
1336
    /** @var navigation_cache */
1337
    protected $cache;
1338
    /** @var array An array of course ids that are present in the navigation */
1339
    protected $addedcourses = array();
1340
    /** @var bool */
1341
    protected $allcategoriesloaded = false;
1342
    /** @var array An array of category ids that are included in the navigation */
1343
    protected $addedcategories = array();
1344
    /** @var int expansion limit */
1345
    protected $expansionlimit = 0;
1346
    /** @var int userid to allow parent to see child's profile page navigation */
1347
    protected $useridtouseforparentchecks = 0;
1348
    /** @var cache_session A cache that stores information on expanded courses */
1349
    protected $cacheexpandcourse = null;
1350
 
1351
    /** Used when loading categories to load all top level categories [parent = 0] **/
1352
    const LOAD_ROOT_CATEGORIES = 0;
1353
    /** Used when loading categories to load all categories **/
1354
    const LOAD_ALL_CATEGORIES = -1;
1355
 
1356
    /**
1357
     * Constructs a new global navigation
1358
     *
1359
     * @param moodle_page $page The page this navigation object belongs to
1360
     */
1361
    public function __construct(moodle_page $page) {
1362
        global $CFG, $SITE, $USER;
1363
 
1364
        if (during_initial_install()) {
1365
            return;
1366
        }
1367
 
1368
        $homepage = get_home_page();
1441 ariadna 1369
        if ($homepage == HOMEPAGE_MY) {
1370
            // We are using the users my moodle for the root element.
1 efrain 1371
            $properties = array(
1441 ariadna 1372
                'key' => 'myhome',
1 efrain 1373
                'type' => navigation_node::TYPE_SYSTEM,
1441 ariadna 1374
                'text' => get_string('myhome'),
1375
                'action' => new moodle_url('/my/'),
1376
                'icon' => new pix_icon('i/dashboard', ''),
1 efrain 1377
            );
1378
        } else if ($homepage == HOMEPAGE_MYCOURSES) {
1379
            // We are using the user's course summary page for the root element.
1380
            $properties = array(
1381
                'key' => 'mycourses',
1382
                'type' => navigation_node::TYPE_SYSTEM,
1383
                'text' => get_string('mycourses'),
1384
                'action' => new moodle_url('/my/courses.php'),
1385
                'icon' => new pix_icon('i/course', '')
1386
            );
1387
        } else {
1441 ariadna 1388
            // We are using the site home for the root element.
1 efrain 1389
            $properties = array(
1441 ariadna 1390
                'key' => 'home',
1 efrain 1391
                'type' => navigation_node::TYPE_SYSTEM,
1441 ariadna 1392
                'text' => get_string('home'),
1393
                'action' => new moodle_url('/'),
1394
                'icon' => new pix_icon('i/home', ''),
1 efrain 1395
            );
1396
        }
1397
 
1398
        // Use the parents constructor.... good good reuse
1399
        parent::__construct($properties);
1400
        $this->showinflatnavigation = true;
1401
 
1402
        // Initalise and set defaults
1403
        $this->page = $page;
1404
        $this->forceopen = true;
1405
        $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
1406
    }
1407
 
1408
    /**
1409
     * Mutator to set userid to allow parent to see child's profile
1410
     * page navigation. See MDL-25805 for initial issue. Linked to it
1411
     * is an issue explaining why this is a REALLY UGLY HACK thats not
1412
     * for you to use!
1413
     *
1414
     * @param int $userid userid of profile page that parent wants to navigate around.
1415
     */
1416
    public function set_userid_for_parent_checks($userid) {
1417
        $this->useridtouseforparentchecks = $userid;
1418
    }
1419
 
1420
 
1421
    /**
1422
     * Initialises the navigation object.
1423
     *
1424
     * This causes the navigation object to look at the current state of the page
1425
     * that it is associated with and then load the appropriate content.
1426
     *
1427
     * This should only occur the first time that the navigation structure is utilised
1428
     * which will normally be either when the navbar is called to be displayed or
1429
     * when a block makes use of it.
1430
     *
1431
     * @return bool
1432
     */
1433
    public function initialise() {
1434
        global $CFG, $SITE, $USER;
1435
        // Check if it has already been initialised
1436
        if ($this->initialised || during_initial_install()) {
1437
            return true;
1438
        }
1439
        $this->initialised = true;
1440
 
1441
        // Set up the five base root nodes. These are nodes where we will put our
1442
        // content and are as follows:
1443
        // site: Navigation for the front page.
1444
        // myprofile: User profile information goes here.
1445
        // currentcourse: The course being currently viewed.
1446
        // mycourses: The users courses get added here.
1447
        // courses: Additional courses are added here.
1448
        // users: Other users information loaded here.
1449
        $this->rootnodes = array();
1450
        $defaulthomepage = get_home_page();
1451
        if ($defaulthomepage == HOMEPAGE_SITE) {
1452
            // The home element should be my moodle because the root element is the site
1453
            if (isloggedin() && !isguestuser()) {  // Makes no sense if you aren't logged in
1454
                if (!empty($CFG->enabledashboard)) {
1455
                    // Only add dashboard to home if it's enabled.
1456
                    $this->rootnodes['home'] = $this->add(get_string('myhome'), new moodle_url('/my/'),
1457
                        self::TYPE_SETTING, null, 'myhome', new pix_icon('i/dashboard', ''));
1458
                    $this->rootnodes['home']->showinflatnavigation = true;
1459
                }
1460
            }
1461
        } else {
1462
            // The home element should be the site because the root node is my moodle
1463
            $this->rootnodes['home'] = $this->add(get_string('sitehome'), new moodle_url('/'),
1464
                self::TYPE_SETTING, null, 'home', new pix_icon('i/home', ''));
1465
            $this->rootnodes['home']->showinflatnavigation = true;
1466
            if (!empty($CFG->defaulthomepage) &&
1467
                    ($CFG->defaulthomepage == HOMEPAGE_MY || $CFG->defaulthomepage == HOMEPAGE_MYCOURSES)) {
1468
                // We need to stop automatic redirection
1469
                $this->rootnodes['home']->action->param('redirect', '0');
1470
            }
1471
        }
1472
        $this->rootnodes['site'] = $this->add_course($SITE);
1473
        $this->rootnodes['myprofile'] = $this->add(get_string('profile'), null, self::TYPE_USER, null, 'myprofile');
1474
        $this->rootnodes['currentcourse'] = $this->add(get_string('currentcourse'), null, self::TYPE_ROOTNODE, null, 'currentcourse');
1475
        $this->rootnodes['mycourses'] = $this->add(
1476
            get_string('mycourses'),
1477
            new moodle_url('/my/courses.php'),
1478
            self::TYPE_ROOTNODE,
1479
            null,
1480
            'mycourses',
1481
            new pix_icon('i/course', '')
1482
        );
1483
        // We do not need to show this node in the breadcrumbs if the default homepage is mycourses.
1484
        // It will be automatically handled by the breadcrumb generator.
1485
        if ($defaulthomepage == HOMEPAGE_MYCOURSES) {
1486
            $this->rootnodes['mycourses']->mainnavonly = true;
1487
        }
1488
 
1489
        $this->rootnodes['courses'] = $this->add(get_string('courses'), new moodle_url('/course/index.php'), self::TYPE_ROOTNODE, null, 'courses');
1490
        if (!core_course_category::user_top()) {
1491
            $this->rootnodes['courses']->hide();
1492
        }
1493
        $this->rootnodes['users'] = $this->add(get_string('users'), null, self::TYPE_ROOTNODE, null, 'users');
1494
 
1495
        // We always load the frontpage course to ensure it is available without
1496
        // JavaScript enabled.
1497
        $this->add_front_page_course_essentials($this->rootnodes['site'], $SITE);
1498
        $this->load_course_sections($SITE, $this->rootnodes['site']);
1499
 
1500
        $course = $this->page->course;
1501
        $this->load_courses_enrolled();
1502
 
1503
        // $issite gets set to true if the current pages course is the sites frontpage course
1504
        $issite = ($this->page->course->id == $SITE->id);
1505
 
1506
        // Determine if the user is enrolled in any course.
1507
        $enrolledinanycourse = enrol_user_sees_own_courses();
1508
 
1509
        $this->rootnodes['currentcourse']->mainnavonly = true;
1510
        if ($enrolledinanycourse) {
1511
            $this->rootnodes['mycourses']->isexpandable = true;
1512
            $this->rootnodes['mycourses']->showinflatnavigation = true;
1513
            if ($CFG->navshowallcourses) {
1514
                // When we show all courses we need to show both the my courses and the regular courses branch.
1515
                $this->rootnodes['courses']->isexpandable = true;
1516
            }
1517
        } else {
1518
            $this->rootnodes['courses']->isexpandable = true;
1519
        }
1520
        $this->rootnodes['mycourses']->forceopen = true;
1521
 
1522
        $canviewcourseprofile = true;
1523
 
1524
        // Next load context specific content into the navigation
1525
        switch ($this->page->context->contextlevel) {
1526
            case CONTEXT_SYSTEM :
1527
                // Nothing left to do here I feel.
1528
                break;
1529
            case CONTEXT_COURSECAT :
1530
                // This is essential, we must load categories.
1531
                $this->load_all_categories($this->page->context->instanceid, true);
1532
                break;
1533
            case CONTEXT_BLOCK :
1534
            case CONTEXT_COURSE :
1535
                if ($issite) {
1536
                    // Nothing left to do here.
1537
                    break;
1538
                }
1539
 
1540
                // Load the course associated with the current page into the navigation.
1541
                $coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
1542
                // If the course wasn't added then don't try going any further.
1543
                if (!$coursenode) {
1544
                    $canviewcourseprofile = false;
1545
                    break;
1546
                }
1547
 
1548
                // If the user is not enrolled then we only want to show the
1549
                // course node and not populate it.
1550
 
1551
                // Not enrolled, can't view, and hasn't switched roles
1552
                if (!can_access_course($course, null, '', true)) {
1553
                    if ($coursenode->isexpandable === true) {
1554
                        // Obviously the situation has changed, update the cache and adjust the node.
1555
                        // This occurs if the user access to a course has been revoked (one way or another) after
1556
                        // initially logging in for this session.
1557
                        $this->get_expand_course_cache()->set($course->id, 1);
1558
                        $coursenode->isexpandable = true;
1559
                        $coursenode->nodetype = self::NODETYPE_BRANCH;
1560
                    }
1561
                    // Very ugly hack - do not force "parents" to enrol into course their child is enrolled in,
1562
                    // this hack has been propagated from user/view.php to display the navigation node. (MDL-25805)
1563
                    if (!$this->current_user_is_parent_role()) {
1564
                        $coursenode->make_active();
1565
                        $canviewcourseprofile = false;
1566
                        break;
1567
                    }
1568
                } else if ($coursenode->isexpandable === false) {
1569
                    // Obviously the situation has changed, update the cache and adjust the node.
1570
                    // This occurs if the user has been granted access to a course (one way or another) after initially
1571
                    // logging in for this session.
1572
                    $this->get_expand_course_cache()->set($course->id, 1);
1573
                    $coursenode->isexpandable = true;
1574
                    $coursenode->nodetype = self::NODETYPE_BRANCH;
1575
                }
1576
 
1577
                // Add the essentials such as reports etc...
1578
                $this->add_course_essentials($coursenode, $course);
1579
                // Extend course navigation with it's sections/activities
1580
                $this->load_course_sections($course, $coursenode);
1581
                if (!$coursenode->contains_active_node() && !$coursenode->search_for_active_node()) {
1582
                    $coursenode->make_active();
1583
                }
1584
 
1585
                break;
1586
            case CONTEXT_MODULE :
1587
                if ($issite) {
1588
                    // If this is the site course then most information will have
1589
                    // already been loaded.
1590
                    // However we need to check if there is more content that can
1591
                    // yet be loaded for the specific module instance.
1592
                    $activitynode = $this->rootnodes['site']->find($this->page->cm->id, navigation_node::TYPE_ACTIVITY);
1593
                    if ($activitynode) {
1594
                        $this->load_activity($this->page->cm, $this->page->course, $activitynode);
1595
                    }
1596
                    break;
1597
                }
1598
 
1599
                $course = $this->page->course;
1600
                $cm = $this->page->cm;
1601
 
1602
                // Load the course associated with the page into the navigation
1603
                $coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
1604
 
1605
                // If the course wasn't added then don't try going any further.
1606
                if (!$coursenode) {
1607
                    $canviewcourseprofile = false;
1608
                    break;
1609
                }
1610
 
1611
                // If the user is not enrolled then we only want to show the
1612
                // course node and not populate it.
1613
                if (!can_access_course($course, null, '', true)) {
1614
                    $coursenode->make_active();
1615
                    $canviewcourseprofile = false;
1616
                    break;
1617
                }
1618
 
1619
                $this->add_course_essentials($coursenode, $course);
1620
 
1621
                // Load the course sections into the page
1622
                $this->load_course_sections($course, $coursenode, null, $cm);
1623
                $activity = $coursenode->find($cm->id, navigation_node::TYPE_ACTIVITY);
1624
                if (!empty($activity)) {
1625
                    // Finally load the cm specific navigaton information
1626
                    $this->load_activity($cm, $course, $activity);
1627
                    // Check if we have an active ndoe
1628
                    if (!$activity->contains_active_node() && !$activity->search_for_active_node()) {
1629
                        // And make the activity node active.
1630
                        $activity->make_active();
1631
                    }
1632
                }
1633
                break;
1634
            case CONTEXT_USER :
1635
                if ($issite) {
1636
                    // The users profile information etc is already loaded
1637
                    // for the front page.
1638
                    break;
1639
                }
1640
                $course = $this->page->course;
1641
                // Load the course associated with the user into the navigation
1642
                $coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
1643
 
1644
                // If the course wasn't added then don't try going any further.
1645
                if (!$coursenode) {
1646
                    $canviewcourseprofile = false;
1647
                    break;
1648
                }
1649
 
1650
                // If the user is not enrolled then we only want to show the
1651
                // course node and not populate it.
1652
                if (!can_access_course($course, null, '', true)) {
1653
                    $coursenode->make_active();
1654
                    $canviewcourseprofile = false;
1655
                    break;
1656
                }
1657
                $this->add_course_essentials($coursenode, $course);
1658
                $this->load_course_sections($course, $coursenode);
1659
                break;
1660
        }
1661
 
1662
        // Load for the current user
1663
        $this->load_for_user();
1664
        if ($this->page->context->contextlevel >= CONTEXT_COURSE && $this->page->context->instanceid != $SITE->id && $canviewcourseprofile) {
1665
            $this->load_for_user(null, true);
1666
        }
1667
        // Load each extending user into the navigation.
1668
        foreach ($this->extendforuser as $user) {
1669
            if ($user->id != $USER->id) {
1670
                $this->load_for_user($user);
1671
            }
1672
        }
1673
 
1674
        // Give the local plugins a chance to include some navigation if they want.
1675
        $this->load_local_plugin_navigation();
1676
 
1677
        // Remove any empty root nodes
1678
        foreach ($this->rootnodes as $node) {
1679
            // Dont remove the home node
1680
            /** @var navigation_node $node */
1681
            if (!in_array($node->key, ['home', 'mycourses', 'myhome']) && !$node->has_children() && !$node->isactive) {
1682
                $node->remove();
1683
            }
1684
        }
1685
 
1686
        if (!$this->contains_active_node()) {
1687
            $this->search_for_active_node();
1688
        }
1689
 
1690
        // If the user is not logged in modify the navigation structure.
1691
        if (!isloggedin()) {
1692
            $activities = clone($this->rootnodes['site']->children);
1693
            $this->rootnodes['site']->remove();
1694
            $children = clone($this->children);
1695
            $this->children = new navigation_node_collection();
1696
            foreach ($activities as $child) {
1697
                $this->children->add($child);
1698
            }
1699
            foreach ($children as $child) {
1700
                $this->children->add($child);
1701
            }
1702
        }
1703
        return true;
1704
    }
1705
 
1706
    /**
1707
     * This function gives local plugins an opportunity to modify navigation.
1708
     */
1709
    protected function load_local_plugin_navigation() {
1710
        foreach (get_plugin_list_with_function('local', 'extend_navigation') as $function) {
1711
            $function($this);
1712
        }
1713
    }
1714
 
1715
    /**
1716
     * Returns true if the current user is a parent of the user being currently viewed.
1717
     *
1718
     * If the current user is not viewing another user, or if the current user does not hold any parent roles over the
1719
     * other user being viewed this function returns false.
1720
     * In order to set the user for whom we are checking against you must call {@link set_userid_for_parent_checks()}
1721
     *
1722
     * @since Moodle 2.4
1723
     * @return bool
1724
     */
1725
    protected function current_user_is_parent_role() {
1726
        global $USER, $DB;
1727
        if ($this->useridtouseforparentchecks && $this->useridtouseforparentchecks != $USER->id) {
1728
            $usercontext = context_user::instance($this->useridtouseforparentchecks, MUST_EXIST);
1729
            if (!has_capability('moodle/user:viewdetails', $usercontext)) {
1730
                return false;
1731
            }
1732
            if ($DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id))) {
1733
                return true;
1734
            }
1735
        }
1736
        return false;
1737
    }
1738
 
1739
    /**
1740
     * Returns true if courses should be shown within categories on the navigation.
1741
     *
1742
     * @param bool $ismycourse Set to true if you are calculating this for a course.
1743
     * @return bool
1744
     */
1745
    protected function show_categories($ismycourse = false) {
1746
        global $CFG, $DB;
1747
        if ($ismycourse) {
1748
            return $this->show_my_categories();
1749
        }
1750
        if ($this->showcategories === null) {
1751
            $show = false;
1752
            if ($this->page->context->contextlevel == CONTEXT_COURSECAT) {
1753
                $show = true;
1754
            } else if (!empty($CFG->navshowcategories) && $DB->count_records('course_categories') > 1) {
1755
                $show = true;
1756
            }
1757
            $this->showcategories = $show;
1758
        }
1759
        return $this->showcategories;
1760
    }
1761
 
1762
    /**
1763
     * Returns true if we should show categories in the My Courses branch.
1764
     * @return bool
1765
     */
1766
    protected function show_my_categories() {
1767
        global $CFG;
1768
        if ($this->showmycategories === null) {
1769
            $this->showmycategories = !empty($CFG->navshowmycoursecategories) && !core_course_category::is_simple_site();
1770
        }
1771
        return $this->showmycategories;
1772
    }
1773
 
1774
    /**
1775
     * Loads the courses in Moodle into the navigation.
1776
     *
1777
     * @global moodle_database $DB
1778
     * @param string|array $categoryids An array containing categories to load courses
1779
     *                     for, OR null to load courses for all categories.
1780
     * @return array An array of navigation_nodes one for each course
1781
     */
1782
    protected function load_all_courses($categoryids = null) {
1783
        global $CFG, $DB, $SITE;
1784
 
1785
        // Work out the limit of courses.
1786
        $limit = 20;
1787
        if (!empty($CFG->navcourselimit)) {
1788
            $limit = $CFG->navcourselimit;
1789
        }
1790
 
1791
        $toload = (empty($CFG->navshowallcourses))?self::LOAD_ROOT_CATEGORIES:self::LOAD_ALL_CATEGORIES;
1792
 
1793
        // If we are going to show all courses AND we are showing categories then
1794
        // to save us repeated DB calls load all of the categories now
1795
        if ($this->show_categories()) {
1796
            $this->load_all_categories($toload);
1797
        }
1798
 
1799
        // Will be the return of our efforts
1800
        $coursenodes = array();
1801
 
1802
        // Check if we need to show categories.
1803
        if ($this->show_categories()) {
1804
            // Hmmm we need to show categories... this is going to be painful.
1805
            // We now need to fetch up to $limit courses for each category to
1806
            // be displayed.
1807
            if ($categoryids !== null) {
1808
                if (!is_array($categoryids)) {
1809
                    $categoryids = array($categoryids);
1810
                }
1811
                list($categorywhere, $categoryparams) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'cc');
1812
                $categorywhere = 'WHERE cc.id '.$categorywhere;
1813
            } else if ($toload == self::LOAD_ROOT_CATEGORIES) {
1814
                $categorywhere = 'WHERE cc.depth = 1 OR cc.depth = 2';
1815
                $categoryparams = array();
1816
            } else {
1817
                $categorywhere = '';
1818
                $categoryparams = array();
1819
            }
1820
 
1821
            // First up we are going to get the categories that we are going to
1822
            // need so that we can determine how best to load the courses from them.
1823
            $sql = "SELECT cc.id, COUNT(c.id) AS coursecount
1824
                        FROM {course_categories} cc
1825
                    LEFT JOIN {course} c ON c.category = cc.id
1826
                            {$categorywhere}
1827
                    GROUP BY cc.id";
1828
            $categories = $DB->get_recordset_sql($sql, $categoryparams);
1829
            $fullfetch = array();
1830
            $partfetch = array();
1831
            foreach ($categories as $category) {
1832
                if (!$this->can_add_more_courses_to_category($category->id)) {
1833
                    continue;
1834
                }
1835
                if ($category->coursecount > $limit * 5) {
1836
                    $partfetch[] = $category->id;
1837
                } else if ($category->coursecount > 0) {
1838
                    $fullfetch[] = $category->id;
1839
                }
1840
            }
1841
            $categories->close();
1842
 
1843
            if (count($fullfetch)) {
1844
                // First up fetch all of the courses in categories where we know that we are going to
1845
                // need the majority of courses.
1846
                list($categoryids, $categoryparams) = $DB->get_in_or_equal($fullfetch, SQL_PARAMS_NAMED, 'lcategory');
1847
                $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
1848
                $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
1849
                $categoryparams['contextlevel'] = CONTEXT_COURSE;
1850
                $sql = "SELECT c.id, c.sortorder, c.visible, c.fullname, c.shortname, c.category $ccselect
1851
                            FROM {course} c
1852
                                $ccjoin
1853
                            WHERE c.category {$categoryids}
1854
                        ORDER BY c.sortorder ASC";
1855
                $coursesrs = $DB->get_recordset_sql($sql, $categoryparams);
1856
                foreach ($coursesrs as $course) {
1857
                    if ($course->id == $SITE->id) {
1858
                        // This should not be necessary, frontpage is not in any category.
1859
                        continue;
1860
                    }
1861
                    if (array_key_exists($course->id, $this->addedcourses)) {
1862
                        // It is probably better to not include the already loaded courses
1863
                        // directly in SQL because inequalities may confuse query optimisers
1864
                        // and may interfere with query caching.
1865
                        continue;
1866
                    }
1867
                    if (!$this->can_add_more_courses_to_category($course->category)) {
1868
                        continue;
1869
                    }
1870
                    context_helper::preload_from_record($course);
1871
                    if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) {
1872
                        continue;
1873
                    }
1874
                    $coursenodes[$course->id] = $this->add_course($course);
1875
                }
1876
                $coursesrs->close();
1877
            }
1878
 
1879
            if (count($partfetch)) {
1880
                // Next we will work our way through the categories where we will likely only need a small
1881
                // proportion of the courses.
1882
                foreach ($partfetch as $categoryid) {
1883
                    $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
1884
                    $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
1885
                    $sql = "SELECT c.id, c.sortorder, c.visible, c.fullname, c.shortname, c.category $ccselect
1886
                                FROM {course} c
1887
                                    $ccjoin
1888
                                WHERE c.category = :categoryid
1889
                            ORDER BY c.sortorder ASC";
1890
                    $courseparams = array('categoryid' => $categoryid, 'contextlevel' => CONTEXT_COURSE);
1891
                    $coursesrs = $DB->get_recordset_sql($sql, $courseparams, 0, $limit * 5);
1892
                    foreach ($coursesrs as $course) {
1893
                        if ($course->id == $SITE->id) {
1894
                            // This should not be necessary, frontpage is not in any category.
1895
                            continue;
1896
                        }
1897
                        if (array_key_exists($course->id, $this->addedcourses)) {
1898
                            // It is probably better to not include the already loaded courses
1899
                            // directly in SQL because inequalities may confuse query optimisers
1900
                            // and may interfere with query caching.
1901
                            // This also helps to respect expected $limit on repeated executions.
1902
                            continue;
1903
                        }
1904
                        if (!$this->can_add_more_courses_to_category($course->category)) {
1905
                            break;
1906
                        }
1907
                        context_helper::preload_from_record($course);
1908
                        if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) {
1909
                            continue;
1910
                        }
1911
                        $coursenodes[$course->id] = $this->add_course($course);
1912
                    }
1913
                    $coursesrs->close();
1914
                }
1915
            }
1916
        } else {
1917
            // Prepare the SQL to load the courses and their contexts
1918
            list($courseids, $courseparams) = $DB->get_in_or_equal(array_keys($this->addedcourses), SQL_PARAMS_NAMED, 'lc', false);
1919
            $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
1920
            $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
1921
            $courseparams['contextlevel'] = CONTEXT_COURSE;
1922
            $sql = "SELECT c.id, c.sortorder, c.visible, c.fullname, c.shortname, c.category $ccselect
1923
                        FROM {course} c
1924
                            $ccjoin
1925
                        WHERE c.id {$courseids}
1926
                    ORDER BY c.sortorder ASC";
1927
            $coursesrs = $DB->get_recordset_sql($sql, $courseparams);
1928
            foreach ($coursesrs as $course) {
1929
                if ($course->id == $SITE->id) {
1930
                    // frotpage is not wanted here
1931
                    continue;
1932
                }
1933
                if ($this->page->course && ($this->page->course->id == $course->id)) {
1934
                    // Don't include the currentcourse in this nodelist - it's displayed in the Current course node
1935
                    continue;
1936
                }
1937
                context_helper::preload_from_record($course);
1938
                if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) {
1939
                    continue;
1940
                }
1941
                $coursenodes[$course->id] = $this->add_course($course);
1942
                if (count($coursenodes) >= $limit) {
1943
                    break;
1944
                }
1945
            }
1946
            $coursesrs->close();
1947
        }
1948
 
1949
        return $coursenodes;
1950
    }
1951
 
1952
    /**
1953
     * Returns true if more courses can be added to the provided category.
1954
     *
1955
     * @param int|navigation_node|stdClass $category
1956
     * @return bool
1957
     */
1958
    protected function can_add_more_courses_to_category($category) {
1959
        global $CFG;
1960
        $limit = 20;
1961
        if (!empty($CFG->navcourselimit)) {
1962
            $limit = (int)$CFG->navcourselimit;
1963
        }
1964
        if (is_numeric($category)) {
1965
            if (!array_key_exists($category, $this->addedcategories)) {
1966
                return true;
1967
            }
1968
            $coursecount = count($this->addedcategories[$category]->children->type(self::TYPE_COURSE));
1969
        } else if ($category instanceof navigation_node) {
1970
            if (($category->type != self::TYPE_CATEGORY) || ($category->type != self::TYPE_MY_CATEGORY)) {
1971
                return false;
1972
            }
1973
            $coursecount = count($category->children->type(self::TYPE_COURSE));
1974
        } else if (is_object($category) && property_exists($category,'id')) {
1975
            $coursecount = count($this->addedcategories[$category->id]->children->type(self::TYPE_COURSE));
1976
        }
1977
        return ($coursecount <= $limit);
1978
    }
1979
 
1980
    /**
1981
     * Loads all categories (top level or if an id is specified for that category)
1982
     *
1983
     * @param int $categoryid The category id to load or null/0 to load all base level categories
1984
     * @param bool $showbasecategories If set to true all base level categories will be loaded as well
1985
     *        as the requested category and any parent categories.
1986
     * @return true|void
1987
     */
1988
    protected function load_all_categories($categoryid = self::LOAD_ROOT_CATEGORIES, $showbasecategories = false) {
1989
        global $CFG, $DB;
1990
 
1991
        // Check if this category has already been loaded
1992
        if ($this->allcategoriesloaded || ($categoryid < 1 && $this->is_category_fully_loaded($categoryid))) {
1993
            return true;
1994
        }
1995
 
1996
        $catcontextsql = context_helper::get_preload_record_columns_sql('ctx');
1997
        $sqlselect = "SELECT cc.*, $catcontextsql
1998
                      FROM {course_categories} cc
1999
                      JOIN {context} ctx ON cc.id = ctx.instanceid";
2000
        $sqlwhere = "WHERE ctx.contextlevel = ".CONTEXT_COURSECAT;
2001
        $sqlorder = "ORDER BY cc.depth ASC, cc.sortorder ASC, cc.id ASC";
2002
        $params = array();
2003
 
2004
        $categoriestoload = array();
2005
        if ($categoryid == self::LOAD_ALL_CATEGORIES) {
2006
            // We are going to load all categories regardless... prepare to fire
2007
            // on the database server!
2008
        } else if ($categoryid == self::LOAD_ROOT_CATEGORIES) { // can be 0
2009
            // We are going to load all of the first level categories (categories without parents)
2010
            $sqlwhere .= " AND cc.parent = 0";
2011
        } else if (array_key_exists($categoryid, $this->addedcategories)) {
2012
            // The category itself has been loaded already so we just need to ensure its subcategories
2013
            // have been loaded
2014
            $addedcategories = $this->addedcategories;
2015
            unset($addedcategories[$categoryid]);
2016
            if (count($addedcategories) > 0) {
2017
                list($sql, $params) = $DB->get_in_or_equal(array_keys($addedcategories), SQL_PARAMS_NAMED, 'parent', false);
2018
                if ($showbasecategories) {
2019
                    // We need to include categories with parent = 0 as well
2020
                    $sqlwhere .= " AND (cc.parent = :categoryid OR cc.parent = 0) AND cc.parent {$sql}";
2021
                } else {
2022
                    // All we need is categories that match the parent
2023
                    $sqlwhere .= " AND cc.parent = :categoryid AND cc.parent {$sql}";
2024
                }
2025
            }
2026
            $params['categoryid'] = $categoryid;
2027
        } else {
2028
            // This category hasn't been loaded yet so we need to fetch it, work out its category path
2029
            // and load this category plus all its parents and subcategories
2030
            $category = $DB->get_record('course_categories', array('id' => $categoryid), 'path', MUST_EXIST);
2031
            $categoriestoload = explode('/', trim($category->path, '/'));
2032
            list($select, $params) = $DB->get_in_or_equal($categoriestoload);
2033
            // We are going to use select twice so double the params
2034
            $params = array_merge($params, $params);
2035
            $basecategorysql = ($showbasecategories)?' OR cc.depth = 1':'';
2036
            $sqlwhere .= " AND (cc.id {$select} OR cc.parent {$select}{$basecategorysql})";
2037
        }
2038
 
2039
        $categoriesrs = $DB->get_recordset_sql("$sqlselect $sqlwhere $sqlorder", $params);
2040
        $categories = array();
2041
        foreach ($categoriesrs as $category) {
2042
            // Preload the context.. we'll need it when adding the category in order
2043
            // to format the category name.
2044
            context_helper::preload_from_record($category);
2045
            if (array_key_exists($category->id, $this->addedcategories)) {
2046
                // Do nothing, its already been added.
2047
            } else if ($category->parent == '0') {
2048
                // This is a root category lets add it immediately
2049
                $this->add_category($category, $this->rootnodes['courses']);
2050
            } else if (array_key_exists($category->parent, $this->addedcategories)) {
2051
                // This categories parent has already been added we can add this immediately
2052
                $this->add_category($category, $this->addedcategories[$category->parent]);
2053
            } else {
2054
                $categories[] = $category;
2055
            }
2056
        }
2057
        $categoriesrs->close();
2058
 
2059
        // Now we have an array of categories we need to add them to the navigation.
2060
        while (!empty($categories)) {
2061
            $category = reset($categories);
2062
            if (array_key_exists($category->id, $this->addedcategories)) {
2063
                // Do nothing
2064
            } else if ($category->parent == '0') {
2065
                $this->add_category($category, $this->rootnodes['courses']);
2066
            } else if (array_key_exists($category->parent, $this->addedcategories)) {
2067
                $this->add_category($category, $this->addedcategories[$category->parent]);
2068
            } else {
2069
                // This category isn't in the navigation and niether is it's parent (yet).
2070
                // We need to go through the category path and add all of its components in order.
2071
                $path = explode('/', trim($category->path, '/'));
2072
                foreach ($path as $catid) {
2073
                    if (!array_key_exists($catid, $this->addedcategories)) {
2074
                        // This category isn't in the navigation yet so add it.
2075
                        $subcategory = $categories[$catid];
2076
                        if ($subcategory->parent == '0') {
2077
                            // Yay we have a root category - this likely means we will now be able
2078
                            // to add categories without problems.
2079
                            $this->add_category($subcategory, $this->rootnodes['courses']);
2080
                        } else if (array_key_exists($subcategory->parent, $this->addedcategories)) {
2081
                            // The parent is in the category (as we'd expect) so add it now.
2082
                            $this->add_category($subcategory, $this->addedcategories[$subcategory->parent]);
2083
                            // Remove the category from the categories array.
2084
                            unset($categories[$catid]);
2085
                        } else {
2086
                            // We should never ever arrive here - if we have then there is a bigger
2087
                            // problem at hand.
2088
                            throw new coding_exception('Category path order is incorrect and/or there are missing categories');
2089
                        }
2090
                    }
2091
                }
2092
            }
2093
            // Remove the category from the categories array now that we know it has been added.
2094
            unset($categories[$category->id]);
2095
        }
2096
        if ($categoryid === self::LOAD_ALL_CATEGORIES) {
2097
            $this->allcategoriesloaded = true;
2098
        }
2099
        // Check if there are any categories to load.
2100
        if (count($categoriestoload) > 0) {
2101
            $readytoloadcourses = array();
2102
            foreach ($categoriestoload as $category) {
2103
                if ($this->can_add_more_courses_to_category($category)) {
2104
                    $readytoloadcourses[] = $category;
2105
                }
2106
            }
2107
            if (count($readytoloadcourses)) {
2108
                $this->load_all_courses($readytoloadcourses);
2109
            }
2110
        }
2111
 
2112
        // Look for all categories which have been loaded
2113
        if (!empty($this->addedcategories)) {
2114
            $categoryids = array();
2115
            foreach ($this->addedcategories as $category) {
2116
                if ($this->can_add_more_courses_to_category($category)) {
2117
                    $categoryids[] = $category->key;
2118
                }
2119
            }
2120
            if ($categoryids) {
2121
                list($categoriessql, $params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED);
2122
                $params['limit'] = (!empty($CFG->navcourselimit))?$CFG->navcourselimit:20;
2123
                $sql = "SELECT cc.id, COUNT(c.id) AS coursecount
2124
                          FROM {course_categories} cc
2125
                          JOIN {course} c ON c.category = cc.id
2126
                         WHERE cc.id {$categoriessql}
2127
                      GROUP BY cc.id
2128
                        HAVING COUNT(c.id) > :limit";
2129
                $excessivecategories = $DB->get_records_sql($sql, $params);
2130
                foreach ($categories as &$category) {
2131
                    if (array_key_exists($category->key, $excessivecategories) && !$this->can_add_more_courses_to_category($category)) {
2132
                        $url = new moodle_url('/course/index.php', array('categoryid' => $category->key));
2133
                        $category->add(get_string('viewallcourses'), $url, self::TYPE_SETTING);
2134
                    }
2135
                }
2136
            }
2137
        }
2138
    }
2139
 
2140
    /**
2141
     * Adds a structured category to the navigation in the correct order/place
2142
     *
2143
     * @param stdClass $category category to be added in navigation.
2144
     * @param navigation_node $parent parent navigation node
2145
     * @param int $nodetype type of node, if category is under MyHome then it's TYPE_MY_CATEGORY
2146
     * @return void.
2147
     */
2148
    protected function add_category(stdClass $category, navigation_node $parent, $nodetype = self::TYPE_CATEGORY) {
2149
        global $CFG;
2150
        if (array_key_exists($category->id, $this->addedcategories)) {
2151
            return;
2152
        }
2153
        $canview = core_course_category::can_view_category($category);
2154
        $url = $canview ? new moodle_url('/course/index.php', array('categoryid' => $category->id)) : null;
2155
        $context = \context_helper::get_navigation_filter_context(context_coursecat::instance($category->id));
2156
        $categoryname = $canview ? format_string($category->name, true, ['context' => $context]) :
2157
            get_string('categoryhidden');
2158
        $categorynode = $parent->add($categoryname, $url, $nodetype, $categoryname, $category->id);
2159
        if (!$canview) {
2160
            // User does not have required capabilities to view category.
2161
            $categorynode->display = false;
2162
        } else if (!$category->visible) {
2163
            // Category is hidden but user has capability to view hidden categories.
2164
            $categorynode->hidden = true;
2165
        }
2166
        $this->addedcategories[$category->id] = $categorynode;
2167
    }
2168
 
2169
    /**
2170
     * Loads the given course into the navigation
2171
     *
2172
     * @param stdClass $course
2173
     * @return navigation_node
2174
     */
2175
    protected function load_course(stdClass $course) {
2176
        global $SITE;
2177
        if ($course->id == $SITE->id) {
2178
            // This is always loaded during initialisation
2179
            return $this->rootnodes['site'];
2180
        } else if (array_key_exists($course->id, $this->addedcourses)) {
2181
            // The course has already been loaded so return a reference
2182
            return $this->addedcourses[$course->id];
2183
        } else {
2184
            // Add the course
2185
            return $this->add_course($course);
2186
        }
2187
    }
2188
 
2189
    /**
2190
     * Loads all of the courses section into the navigation.
2191
     *
2192
     * This function calls method from current course format, see
2193
     * core_courseformat\base::extend_course_navigation()
2194
     * If course module ($cm) is specified but course format failed to create the node,
2195
     * the activity node is created anyway.
2196
     *
2197
     * By default course formats call the method global_navigation::load_generic_course_sections()
2198
     *
2199
     * @param stdClass $course Database record for the course
2200
     * @param navigation_node $coursenode The course node within the navigation
2201
     * @param null|int $sectionnum If specified load the contents of section with this relative number
2202
     * @param null|cm_info $cm If specified make sure that activity node is created (either
2203
     *    in containg section or by calling load_stealth_activity() )
2204
     */
2205
    protected function load_course_sections(stdClass $course, navigation_node $coursenode, $sectionnum = null, $cm = null) {
2206
        global $CFG, $SITE;
2207
        require_once($CFG->dirroot.'/course/lib.php');
2208
        if (isset($cm->sectionnum)) {
2209
            $sectionnum = $cm->sectionnum;
2210
        }
2211
        if ($sectionnum !== null) {
2212
            $this->includesectionnum = $sectionnum;
2213
        }
2214
        course_get_format($course)->extend_course_navigation($this, $coursenode, $sectionnum, $cm);
2215
        if (isset($cm->id)) {
2216
            $activity = $coursenode->find($cm->id, self::TYPE_ACTIVITY);
2217
            if (empty($activity)) {
2218
                $activity = $this->load_stealth_activity($coursenode, get_fast_modinfo($course));
2219
            }
2220
        }
1441 ariadna 2221
    }
1 efrain 2222
 
2223
    /**
2224
     * Generates an array of sections and an array of activities for the given course.
2225
     *
2226
     * This method uses the cache to improve performance and avoid the get_fast_modinfo call
2227
     *
2228
     * @param stdClass $course
2229
     * @return array Array($sections, $activities)
2230
     */
2231
    protected function generate_sections_and_activities(stdClass $course) {
2232
        global $CFG;
2233
        require_once($CFG->dirroot.'/course/lib.php');
2234
 
2235
        $modinfo = get_fast_modinfo($course);
2236
        $sections = $modinfo->get_section_info_all();
1441 ariadna 2237
        $format = course_get_format($course);
1 efrain 2238
 
2239
        // For course formats using 'numsections' trim the sections list
1441 ariadna 2240
        $courseformatoptions = $format->get_format_options();
1 efrain 2241
        if (isset($courseformatoptions['numsections'])) {
2242
            $sections = array_slice($sections, 0, $courseformatoptions['numsections']+1, true);
2243
        }
2244
 
1441 ariadna 2245
        $activities = [];
1 efrain 2246
 
2247
        foreach ($sections as $key => $section) {
2248
            // Clone and unset summary to prevent $SESSION bloat (MDL-31802).
2249
            $sections[$key] = clone($section);
2250
            unset($sections[$key]->summary);
2251
            $sections[$key]->hasactivites = false;
1441 ariadna 2252
            if (!array_key_exists($section->sectionnum, $modinfo->sections)) {
1 efrain 2253
                continue;
2254
            }
1441 ariadna 2255
            foreach ($section->get_sequence_cm_infos() as $cm) {
1 efrain 2256
                $activity = new stdClass;
2257
                $activity->id = $cm->id;
2258
                $activity->course = $course->id;
1441 ariadna 2259
                $activity->section = $section->sectionnum;
1 efrain 2260
                $activity->name = $cm->name;
2261
                $activity->icon = $cm->icon;
2262
                $activity->iconcomponent = $cm->iconcomponent;
2263
                $activity->hidden = (!$cm->visible);
2264
                $activity->modname = $cm->modname;
2265
                $activity->nodetype = navigation_node::NODETYPE_LEAF;
2266
                $activity->onclick = $cm->onclick;
2267
                $url = $cm->url;
1441 ariadna 2268
 
2269
                // Activities witout url but with delegated section uses the section url.
2270
                $activity->delegatedsection = $cm->get_delegated_section_info();
2271
                if (empty($cm->url) && $activity->delegatedsection) {
2272
                    $url = $format->get_view_url(
2273
                        $activity->delegatedsection->sectionnum,
2274
                        ['navigation' => true]
2275
                    );
2276
                }
2277
 
1 efrain 2278
                if (!$url) {
2279
                    $activity->url = null;
2280
                    $activity->display = false;
2281
                } else {
2282
                    $activity->url = $url->out();
2283
                    $activity->display = $cm->is_visible_on_course_page() ? true : false;
2284
                    if (self::module_extends_navigation($cm->modname)) {
2285
                        $activity->nodetype = navigation_node::NODETYPE_BRANCH;
2286
                    }
2287
                }
1441 ariadna 2288
                $activities[$cm->id] = $activity;
1 efrain 2289
                if ($activity->display) {
2290
                    $sections[$key]->hasactivites = true;
2291
                }
2292
            }
2293
        }
2294
 
1441 ariadna 2295
        return [$sections, $activities];
1 efrain 2296
    }
2297
 
2298
    /**
2299
     * Generically loads the course sections into the course's navigation.
2300
     *
2301
     * @param stdClass $course
2302
     * @param navigation_node $coursenode
2303
     * @return array An array of course section nodes
2304
     */
2305
    public function load_generic_course_sections(stdClass $course, navigation_node $coursenode) {
2306
        global $CFG, $DB, $USER, $SITE;
2307
        require_once($CFG->dirroot.'/course/lib.php');
2308
 
2309
        list($sections, $activities) = $this->generate_sections_and_activities($course);
2310
 
1441 ariadna 2311
        $navigationsections = [];
1 efrain 2312
        foreach ($sections as $sectionid => $section) {
2313
            if ($course->id == $SITE->id) {
1441 ariadna 2314
                $this->load_section_activities_navigation($coursenode, $section, $activities);
2315
                continue;
2316
            }
1 efrain 2317
 
1441 ariadna 2318
            if (
2319
                !$section->uservisible
2320
                || (
2321
                    !$this->showemptysections
2322
                    && !$section->hasactivites
2323
                    && $this->includesectionnum !== $section->section
2324
                )
2325
            ) {
2326
                continue;
2327
            }
1 efrain 2328
 
1441 ariadna 2329
            // Delegated sections are added from the activity node.
2330
            if ($section->get_component_instance()) {
2331
                continue;
1 efrain 2332
            }
1441 ariadna 2333
 
2334
            $navigationsections[$sectionid] = $this->load_section_navigation(
2335
                parentnode: $coursenode,
2336
                section: $section,
2337
                activitiesdata: $activities,
2338
            );
1 efrain 2339
        }
2340
        return $navigationsections;
2341
    }
2342
 
2343
    /**
1441 ariadna 2344
     * Returns true if the section is included in the breadcrumb.
1 efrain 2345
     *
1441 ariadna 2346
     * @param section_info $section
2347
     * @param moodle_url|null $sectionurl
2348
     * @return bool
1 efrain 2349
     */
1441 ariadna 2350
    protected function is_section_in_breadcrumb(section_info $section, ?moodle_url $sectionurl): bool {
2351
        // Ajax requests uses includesectionnum as param.
2352
        if ($this->includesectionnum !== false && $this->includesectionnum == $section->sectionnum) {
2353
            return true;
1 efrain 2354
        }
2355
 
1441 ariadna 2356
        // If we are in a section page, we need to check for any child section.
2357
        $checkchildrenurls = false;
2358
        $format = null;
2359
        if ($sectionurl && $this->page->url->compare($sectionurl, URL_MATCH_BASE)) {
2360
            $checkchildrenurls = true;
2361
            $format = course_get_format($section->course);
1 efrain 2362
        }
2363
 
1441 ariadna 2364
        // Activities can have delegated sections that acts as a child section.
2365
        foreach ($section->get_sequence_cm_infos() as $cm) {
2366
            $delegatedsection = $cm->get_delegated_section_info();
2367
            if (!$delegatedsection) {
1 efrain 2368
                continue;
2369
            }
1441 ariadna 2370
            // Check if the child node is requested via Ajax.
2371
            if ($this->includesectionnum == $delegatedsection->sectionnum) {
2372
                return true;
1 efrain 2373
            }
2374
 
1441 ariadna 2375
            if ($checkchildrenurls) {
2376
                $childurl = $format->get_view_url($delegatedsection, ['navigation' => true]);
2377
                if ($childurl && $this->page->url->compare($childurl, URL_MATCH_EXACT)) {
2378
                    return true;
1 efrain 2379
                }
2380
            }
1441 ariadna 2381
        }
1 efrain 2382
 
1441 ariadna 2383
        return false;
2384
    }
2385
 
2386
    /**
2387
     * Loads a section into the navigation structure.
2388
     *
2389
     * @param navigation_node $parentnode
2390
     * @param section_info $section
2391
     * @param stdClass[] $activitiesdata Array of objects containing activities data indexed by cmid.
2392
     * @return navigation_node the section navigaiton node
2393
     */
2394
    public function load_section_navigation($parentnode, $section, $activitiesdata): navigation_node {
2395
        $format = course_get_format($section->course);
2396
        $sectionname = $format->get_section_name($section);
2397
        $url = $format->get_view_url($section, ['navigation' => true]);
2398
 
2399
        $sectionnode = $parentnode->add(
2400
            text: $sectionname,
2401
            action: $url,
2402
            type: navigation_node::TYPE_SECTION,
2403
            key: $section->id,
2404
            icon: new pix_icon('i/section', ''),
2405
        );
2406
        $sectionnode->nodetype = navigation_node::NODETYPE_BRANCH;
2407
        $sectionnode->hidden = (!$section->visible || !$section->available);
2408
        $sectionnode->add_attribute('data-section-name-for', $section->id);
2409
 
2410
        // Sections content are usually loaded via ajax but the sections from the requested breadcrumb.
2411
        if ($this->is_section_in_breadcrumb($section, $url)) {
2412
            $this->load_section_activities_navigation($sectionnode, $section, $activitiesdata);
1 efrain 2413
        }
1441 ariadna 2414
        return $sectionnode;
2415
    }
1 efrain 2416
 
1441 ariadna 2417
    /**
2418
     * Loads the activities for a section into the navigation structure.
2419
     *
2420
     * This method is called from global_navigation::load_section_navigation(),
2421
     * It is not intended to be called directly.
2422
     *
2423
     * @param navigation_node $sectionnode
2424
     * @param section_info $section
2425
     * @param stdClass[] $activitiesdata Array of objects containing activities data indexed by cmid.
2426
     */
2427
    protected function load_section_activities_navigation(
2428
        navigation_node $sectionnode,
2429
        section_info $section,
2430
        array $activitiesdata
2431
    ): array {
2432
        global $CFG, $SITE;
2433
 
2434
        $activitynodes = [];
2435
        if (empty($activitiesdata)) {
2436
            return $activitynodes;
2437
        }
2438
 
2439
        foreach ($section->get_sequence_cm_infos() as $cm) {
2440
            $activitydata = $activitiesdata[$cm->id];
2441
 
2442
            // If activity is a delegated section, load a section node instead of the activity one.
2443
            if ($activitydata->delegatedsection) {
2444
                $activitynodes[$activitydata->id] = $this->load_section_navigation(
2445
                    parentnode: $sectionnode,
2446
                    section: $activitydata->delegatedsection,
2447
                    activitiesdata: $activitiesdata,
2448
                );
2449
                continue;
2450
            }
2451
 
2452
            $activitynodes[$activitydata->id] = $this->load_activity_navigation($sectionnode, $activitydata);
2453
        }
2454
 
1 efrain 2455
        return $activitynodes;
2456
    }
1441 ariadna 2457
 
1 efrain 2458
    /**
1441 ariadna 2459
     * Loads an activity into the navigation structure.
2460
     *
2461
     * This method is called from global_navigation::load_section_activities_navigation(),
2462
     * It is not intended to be called directly.
2463
     *
2464
     * @param navigation_node $sectionnode
2465
     * @param stdClass $activitydata The acitivy navigation data generated from generate_sections_and_activities
2466
     * @return navigation_node
2467
     */
2468
    protected function load_activity_navigation(
2469
        navigation_node $sectionnode,
2470
        stdClass $activitydata,
2471
    ): navigation_node {
2472
        global $SITE, $CFG;
2473
 
2474
        $showactivities = ($activitydata->course != $SITE->id || !empty($CFG->navshowfrontpagemods));
2475
 
2476
        $icon = new pix_icon(
2477
            $activitydata->icon ?: 'monologo',
2478
            get_string('modulename', $activitydata->modname),
2479
            $activitydata->icon ? $activitydata->iconcomponent : $activitydata->modname,
2480
        );
2481
 
2482
        // Prepare the default name and url for the node.
2483
        $displaycontext = \context_helper::get_navigation_filter_context(context_module::instance($activitydata->id));
2484
        $activityname = format_string($activitydata->name, true, ['context' => $displaycontext]);
2485
 
2486
        $activitynode = $sectionnode->add(
2487
            text: $activityname,
2488
            action: $this->get_activity_action($activitydata, $activityname),
2489
            type: navigation_node::TYPE_ACTIVITY,
2490
            key: $activitydata->id,
2491
            icon: $icon,
2492
        );
2493
        $activitynode->title(get_string('modulename', $activitydata->modname));
2494
        $activitynode->hidden = $activitydata->hidden;
2495
        $activitynode->display = $showactivities && $activitydata->display;
2496
        $activitynode->nodetype = $activitydata->nodetype;
2497
 
2498
        return $activitynode;
2499
    }
2500
 
2501
    /**
2502
     * Returns the action for the activity.
2503
     *
2504
     * @param stdClass $activitydata The acitivy navigation data generated from generate_sections_and_activities
2505
     * @param string $activityname
2506
     * @return moodle_url|action_link
2507
     */
2508
    protected function get_activity_action(stdClass $activitydata, string $activityname): moodle_url|action_link {
2509
        // A static counter for JS function naming.
2510
        static $legacyonclickcounter = 0;
2511
 
2512
        $action = new moodle_url($activitydata->url);
2513
 
2514
        // Check if the onclick property is set (puke!).
2515
        if (!empty($activitydata->onclick)) {
2516
            // Increment the counter so that we have a unique number.
2517
            $legacyonclickcounter++;
2518
            // Generate the function name we will use.
2519
            $functionname = 'legacy_activity_onclick_handler_' . $legacyonclickcounter;
2520
            $propogrationhandler = '';
2521
            // Check if we need to cancel propogation. Remember inline onclick
2522
            // events would return false if they wanted to prevent propogation and the
2523
            // default action.
2524
            if (strpos($activitydata->onclick, 'return false')) {
2525
                $propogrationhandler = 'e.halt();';
2526
            }
2527
            // Decode the onclick - it has already been encoded for display (puke).
2528
            $onclick = htmlspecialchars_decode($activitydata->onclick, ENT_QUOTES);
2529
            // Build the JS function the click event will call.
2530
            $jscode = "function {$functionname}(e) { $propogrationhandler $onclick }";
2531
            $this->page->requires->js_amd_inline($jscode);
2532
            // Override the default url with the new action link.
2533
            $action = new action_link($action, $activityname, new component_action('click', $functionname));
2534
        }
2535
        return $action;
2536
    }
2537
 
2538
    /**
2539
     * Loads all of the activities for a section into the navigation structure.
2540
     *
2541
     * @param navigation_node $sectionnode
2542
     * @param int $sectionnumber
2543
     * @param array $activities An array of activites as returned by {@link global_navigation::generate_sections_and_activities()}
2544
     * @param stdClass $course The course object the section and activities relate to.
2545
     * @return array Array of activity nodes
2546
     */
2547
    #[\core\attribute\deprecated(
2548
        replacement: 'load_section_activities_navigation',
2549
        since: '4.5',
2550
        mdl: 'MDL-82845',
2551
    )]
2552
    protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, array $activities, $course = null) {
2553
        \core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
2554
        if (!is_object($course)) {
2555
            $activity = reset($activities);
2556
            $courseid = $activity->course;
2557
        } else {
2558
            $courseid = $course->id;
2559
        }
2560
        $sectionifo = get_fast_modinfo($courseid)->get_section_info($sectionnumber);
2561
        return $this->load_section_activities_navigation($sectionnode, $sectionifo, $activities);
2562
    }
2563
    /**
1 efrain 2564
     * Loads a stealth module from unavailable section
2565
     * @param navigation_node $coursenode
2566
     * @param stdClass|course_modinfo $modinfo
2567
     * @return navigation_node or null if not accessible
2568
     */
2569
    protected function load_stealth_activity(navigation_node $coursenode, $modinfo) {
2570
        if (empty($modinfo->cms[$this->page->cm->id])) {
2571
            return null;
2572
        }
2573
        $cm = $modinfo->cms[$this->page->cm->id];
2574
        if ($cm->icon) {
2575
            $icon = new pix_icon($cm->icon, get_string('modulename', $cm->modname), $cm->iconcomponent);
2576
        } else {
2577
            $icon = new pix_icon('monologo', get_string('modulename', $cm->modname), $cm->modname);
2578
        }
2579
        $url = $cm->url;
2580
        $activitynode = $coursenode->add(format_string($cm->name), $url, navigation_node::TYPE_ACTIVITY, null, $cm->id, $icon);
2581
        $activitynode->title(get_string('modulename', $cm->modname));
2582
        $activitynode->hidden = (!$cm->visible);
2583
        if (!$cm->is_visible_on_course_page()) {
2584
            // Do not show any error here, let the page handle exception that activity is not visible for the current user.
2585
            // Also there may be no exception at all in case when teacher is logged in as student.
2586
            $activitynode->display = false;
2587
        } else if (!$url) {
2588
            // Don't show activities that don't have links!
2589
            $activitynode->display = false;
2590
        } else if (self::module_extends_navigation($cm->modname)) {
2591
            $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
2592
        }
2593
        return $activitynode;
2594
    }
2595
    /**
2596
     * Loads the navigation structure for the given activity into the activities node.
2597
     *
2598
     * This method utilises a callback within the modules lib.php file to load the
2599
     * content specific to activity given.
2600
     *
2601
     * The callback is a method: {modulename}_extend_navigation()
2602
     * Examples:
2603
     *  * {@link forum_extend_navigation()}
2604
     *  * {@link workshop_extend_navigation()}
2605
     *
2606
     * @param cm_info|stdClass $cm
2607
     * @param stdClass $course
2608
     * @param navigation_node $activity
2609
     * @return bool
2610
     */
2611
    protected function load_activity($cm, stdClass $course, navigation_node $activity) {
2612
        global $CFG, $DB;
2613
 
2614
        // make sure we have a $cm from get_fast_modinfo as this contains activity access details
2615
        if (!($cm instanceof cm_info)) {
2616
            $modinfo = get_fast_modinfo($course);
2617
            $cm = $modinfo->get_cm($cm->id);
2618
        }
2619
        $activity->nodetype = navigation_node::NODETYPE_LEAF;
2620
        $activity->make_active();
2621
        $file = $CFG->dirroot.'/mod/'.$cm->modname.'/lib.php';
2622
        $function = $cm->modname.'_extend_navigation';
2623
 
2624
        if (file_exists($file)) {
2625
            require_once($file);
2626
            if (function_exists($function)) {
2627
                $activtyrecord = $DB->get_record($cm->modname, array('id' => $cm->instance), '*', MUST_EXIST);
2628
                $function($activity, $course, $activtyrecord, $cm);
2629
            }
2630
        }
2631
 
2632
        // Allow the active advanced grading method plugin to append module navigation
2633
        $featuresfunc = $cm->modname.'_supports';
2634
        if (function_exists($featuresfunc) && $featuresfunc(FEATURE_ADVANCED_GRADING)) {
2635
            require_once($CFG->dirroot.'/grade/grading/lib.php');
2636
            $gradingman = get_grading_manager($cm->context,  'mod_'.$cm->modname);
2637
            $gradingman->extend_navigation($this, $activity);
2638
        }
2639
 
2640
        return $activity->has_children();
2641
    }
2642
    /**
2643
     * Loads user specific information into the navigation in the appropriate place.
2644
     *
2645
     * If no user is provided the current user is assumed.
2646
     *
2647
     * @param stdClass $user
2648
     * @param bool $forceforcontext probably force something to be loaded somewhere (ask SamH if not sure what this means)
2649
     * @return bool
2650
     */
2651
    protected function load_for_user($user=null, $forceforcontext=false) {
2652
        global $DB, $CFG, $USER, $SITE;
2653
 
2654
        require_once($CFG->dirroot . '/course/lib.php');
2655
 
2656
        if ($user === null) {
2657
            // We can't require login here but if the user isn't logged in we don't
2658
            // want to show anything
2659
            if (!isloggedin() || isguestuser()) {
2660
                return false;
2661
            }
2662
            $user = $USER;
2663
        } else if (!is_object($user)) {
2664
            // If the user is not an object then get them from the database
2665
            $select = context_helper::get_preload_record_columns_sql('ctx');
2666
            $sql = "SELECT u.*, $select
2667
                      FROM {user} u
2668
                      JOIN {context} ctx ON u.id = ctx.instanceid
2669
                     WHERE u.id = :userid AND
2670
                           ctx.contextlevel = :contextlevel";
2671
            $user = $DB->get_record_sql($sql, array('userid' => (int)$user, 'contextlevel' => CONTEXT_USER), MUST_EXIST);
2672
            context_helper::preload_from_record($user);
2673
        }
2674
 
2675
        $iscurrentuser = ($user->id == $USER->id);
2676
 
2677
        $usercontext = context_user::instance($user->id);
2678
 
2679
        // Get the course set against the page, by default this will be the site
2680
        $course = $this->page->course;
2681
        $baseargs = array('id'=>$user->id);
2682
        if ($course->id != $SITE->id && (!$iscurrentuser || $forceforcontext)) {
2683
            $coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
2684
            $baseargs['course'] = $course->id;
2685
            $coursecontext = context_course::instance($course->id);
2686
            $issitecourse = false;
2687
        } else {
2688
            // Load all categories and get the context for the system
2689
            $coursecontext = context_system::instance();
2690
            $issitecourse = true;
2691
        }
2692
 
2693
        // Create a node to add user information under.
2694
        $usersnode = null;
2695
        if (!$issitecourse) {
2696
            // Not the current user so add it to the participants node for the current course.
2697
            $usersnode = $coursenode->get('participants', navigation_node::TYPE_CONTAINER);
2698
            $userviewurl = new moodle_url('/user/view.php', $baseargs);
2699
        } else if ($USER->id != $user->id) {
2700
            // This is the site so add a users node to the root branch.
2701
            $usersnode = $this->rootnodes['users'];
2702
            if (course_can_view_participants($coursecontext)) {
2703
                $usersnode->action = new moodle_url('/user/index.php', array('id' => $course->id));
2704
            }
2705
            $userviewurl = new moodle_url('/user/profile.php', $baseargs);
2706
        }
2707
        if (!$usersnode) {
2708
            // We should NEVER get here, if the course hasn't been populated
2709
            // with a participants node then the navigaiton either wasn't generated
2710
            // for it (you are missing a require_login or set_context call) or
2711
            // you don't have access.... in the interests of no leaking informatin
2712
            // we simply quit...
2713
            return false;
2714
        }
2715
        // Add a branch for the current user.
2716
        // Only reveal user details if $user is the current user, or a user to which the current user has access.
2717
        $viewprofile = true;
2718
        if (!$iscurrentuser) {
2719
            require_once($CFG->dirroot . '/user/lib.php');
2720
            if ($this->page->context->contextlevel == CONTEXT_USER && !has_capability('moodle/user:viewdetails', $usercontext) ) {
2721
                $viewprofile = false;
2722
            } else if ($this->page->context->contextlevel != CONTEXT_USER && !user_can_view_profile($user, $course, $usercontext)) {
2723
                $viewprofile = false;
2724
            }
2725
            if (!$viewprofile) {
2726
                $viewprofile = user_can_view_profile($user, null, $usercontext);
2727
            }
2728
        }
2729
 
2730
        // Now, conditionally add the user node.
2731
        if ($viewprofile) {
2732
            $canseefullname = has_capability('moodle/site:viewfullnames', $coursecontext);
2733
            $usernode = $usersnode->add(fullname($user, $canseefullname), $userviewurl, self::TYPE_USER, null, 'user' . $user->id);
2734
        } else {
2735
            $usernode = $usersnode->add(get_string('user'));
2736
        }
2737
 
2738
        if ($this->page->context->contextlevel == CONTEXT_USER && $user->id == $this->page->context->instanceid) {
2739
            $usernode->make_active();
2740
        }
2741
 
2742
        // Add user information to the participants or user node.
2743
        if ($issitecourse) {
2744
 
2745
            // If the user is the current user or has permission to view the details of the requested
2746
            // user than add a view profile link.
2747
            if ($iscurrentuser || has_capability('moodle/user:viewdetails', $coursecontext) ||
2748
                    has_capability('moodle/user:viewdetails', $usercontext)) {
2749
                if ($issitecourse || ($iscurrentuser && !$forceforcontext)) {
2750
                    $usernode->add(get_string('viewprofile'), new moodle_url('/user/profile.php', $baseargs));
2751
                } else {
2752
                    $usernode->add(get_string('viewprofile'), new moodle_url('/user/view.php', $baseargs));
2753
                }
2754
            }
2755
 
2756
            // Add blog nodes.
2757
            if (!empty($CFG->enableblogs)) {
2758
                if (!$this->cache->cached('userblogoptions'.$user->id)) {
2759
                    require_once($CFG->dirroot.'/blog/lib.php');
2760
                    // Get all options for the user.
2761
                    $options = blog_get_options_for_user($user);
2762
                    $this->cache->set('userblogoptions'.$user->id, $options);
2763
                } else {
2764
                    $options = $this->cache->{'userblogoptions'.$user->id};
2765
                }
2766
 
2767
                if (count($options) > 0) {
2768
                    $blogs = $usernode->add(get_string('blogs', 'blog'), null, navigation_node::TYPE_CONTAINER);
2769
                    foreach ($options as $type => $option) {
2770
                        if ($type == "rss") {
2771
                            $blogs->add($option['string'], $option['link'], settings_navigation::TYPE_SETTING, null, null,
2772
                                    new pix_icon('i/rss', ''));
2773
                        } else {
2774
                            $blogs->add($option['string'], $option['link']);
2775
                        }
2776
                    }
2777
                }
2778
            }
2779
 
2780
            // Add the messages link.
2781
            // It is context based so can appear in the user's profile and in course participants information.
2782
            if (!empty($CFG->messaging)) {
2783
                $messageargs = array('user1' => $USER->id);
2784
                if ($USER->id != $user->id) {
2785
                    $messageargs['user2'] = $user->id;
2786
                }
2787
                $url = new moodle_url('/message/index.php', $messageargs);
2788
                $usernode->add(get_string('messages', 'message'), $url, self::TYPE_SETTING, null, 'messages');
2789
            }
2790
 
2791
            // Add the "My private files" link.
2792
            // This link doesn't have a unique display for course context so only display it under the user's profile.
2793
            if ($issitecourse && $iscurrentuser && has_capability('moodle/user:manageownfiles', $usercontext)) {
2794
                $url = new moodle_url('/user/files.php');
2795
                $usernode->add(get_string('privatefiles'), $url, self::TYPE_SETTING, null, 'privatefiles');
2796
            }
2797
 
2798
            // Add a node to view the users notes if permitted.
2799
            if (!empty($CFG->enablenotes) &&
2800
                    has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $coursecontext)) {
2801
                $url = new moodle_url('/notes/index.php', array('user' => $user->id));
2802
                if ($coursecontext->instanceid != SITEID) {
2803
                    $url->param('course', $coursecontext->instanceid);
2804
                }
2805
                $usernode->add(get_string('notes', 'notes'), $url);
2806
            }
2807
 
2808
            // Show the grades node.
2809
            if (($issitecourse && $iscurrentuser) || has_capability('moodle/user:viewdetails', $usercontext)) {
2810
                require_once($CFG->dirroot . '/user/lib.php');
2811
                // Set the grades node to link to the "Grades" page.
2812
                if ($course->id == SITEID) {
2813
                    $url = user_mygrades_url($user->id, $course->id);
2814
                } else { // Otherwise we are in a course and should redirect to the user grade report (Activity report version).
2815
                    $url = new moodle_url('/course/user.php', array('mode' => 'grade', 'id' => $course->id, 'user' => $user->id));
2816
                }
2817
                if ($USER->id != $user->id) {
2818
                    $usernode->add(get_string('grades', 'grades'), $url, self::TYPE_SETTING, null, 'usergrades');
2819
                } else {
2820
                    $usernode->add(get_string('grades', 'grades'), $url);
2821
                }
2822
            }
2823
 
2824
            // If the user is the current user add the repositories for the current user.
2825
            $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
2826
            if (!$iscurrentuser &&
2827
                    $course->id == $SITE->id &&
2828
                    has_capability('moodle/user:viewdetails', $usercontext) &&
2829
                    (!in_array('mycourses', $hiddenfields) || has_capability('moodle/user:viewhiddendetails', $coursecontext))) {
2830
 
2831
                // Add view grade report is permitted.
2832
                $reports = core_component::get_plugin_list('gradereport');
2833
                arsort($reports); // User is last, we want to test it first.
2834
 
2835
                $userscourses = enrol_get_users_courses($user->id, false, '*');
2836
                $userscoursesnode = $usernode->add(get_string('courses'));
2837
 
2838
                $count = 0;
2839
                foreach ($userscourses as $usercourse) {
2840
                    if ($count === (int)$CFG->navcourselimit) {
2841
                        $url = new moodle_url('/user/profile.php', array('id' => $user->id, 'showallcourses' => 1));
2842
                        $userscoursesnode->add(get_string('showallcourses'), $url);
2843
                        break;
2844
                    }
2845
                    $count++;
2846
                    $usercoursecontext = context_course::instance($usercourse->id);
2847
                    $usercourseshortname = format_string($usercourse->shortname, true, array('context' => $usercoursecontext));
2848
                    $usercoursenode = $userscoursesnode->add($usercourseshortname, new moodle_url('/user/view.php',
2849
                            array('id' => $user->id, 'course' => $usercourse->id)), self::TYPE_CONTAINER);
2850
 
2851
                    $gradeavailable = has_capability('moodle/grade:view', $usercoursecontext);
2852
                    if (!$gradeavailable && !empty($usercourse->showgrades) && is_array($reports) && !empty($reports)) {
2853
                        foreach ($reports as $plugin => $plugindir) {
2854
                            if (has_capability('gradereport/'.$plugin.':view', $usercoursecontext)) {
2855
                                // Stop when the first visible plugin is found.
2856
                                $gradeavailable = true;
2857
                                break;
2858
                            }
2859
                        }
2860
                    }
2861
 
2862
                    if ($gradeavailable) {
2863
                        $url = new moodle_url('/grade/report/index.php', array('id' => $usercourse->id));
2864
                        $usercoursenode->add(get_string('grades'), $url, self::TYPE_SETTING, null, null,
2865
                                new pix_icon('i/grades', ''));
2866
                    }
2867
 
2868
                    // Add a node to view the users notes if permitted.
2869
                    if (!empty($CFG->enablenotes) &&
2870
                            has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $usercoursecontext)) {
2871
                        $url = new moodle_url('/notes/index.php', array('user' => $user->id, 'course' => $usercourse->id));
2872
                        $usercoursenode->add(get_string('notes', 'notes'), $url, self::TYPE_SETTING);
2873
                    }
2874
 
2875
                    if (can_access_course($usercourse, $user->id, '', true)) {
2876
                        $usercoursenode->add(get_string('entercourse'), new moodle_url('/course/view.php',
2877
                                array('id' => $usercourse->id)), self::TYPE_SETTING, null, null, new pix_icon('i/course', ''));
2878
                    }
2879
 
2880
                    $reporttab = $usercoursenode->add(get_string('activityreports'));
2881
 
2882
                    $reportfunctions = get_plugin_list_with_function('report', 'extend_navigation_user', 'lib.php');
2883
                    foreach ($reportfunctions as $reportfunction) {
2884
                        $reportfunction($reporttab, $user, $usercourse);
2885
                    }
2886
 
2887
                    $reporttab->trim_if_empty();
2888
                }
2889
            }
2890
 
2891
            // Let plugins hook into user navigation.
2892
            $pluginsfunction = get_plugins_with_function('extend_navigation_user', 'lib.php');
2893
            foreach ($pluginsfunction as $plugintype => $plugins) {
2894
                if ($plugintype != 'report') {
2895
                    foreach ($plugins as $pluginfunction) {
2896
                        $pluginfunction($usernode, $user, $usercontext, $course, $coursecontext);
2897
                    }
2898
                }
2899
            }
2900
        }
2901
        return true;
2902
    }
2903
 
2904
    /**
2905
     * This method simply checks to see if a given module can extend the navigation.
2906
     *
2907
     * @todo (MDL-25290) A shared caching solution should be used to save details on what extends navigation.
2908
     *
2909
     * @param string $modname
2910
     * @return bool
2911
     */
2912
    public static function module_extends_navigation($modname) {
2913
        global $CFG;
2914
        static $extendingmodules = array();
2915
        if (!array_key_exists($modname, $extendingmodules)) {
2916
            $extendingmodules[$modname] = false;
2917
            $file = $CFG->dirroot.'/mod/'.$modname.'/lib.php';
2918
            if (file_exists($file)) {
2919
                $function = $modname.'_extend_navigation';
2920
                require_once($file);
2921
                $extendingmodules[$modname] = (function_exists($function));
2922
            }
2923
        }
2924
        return $extendingmodules[$modname];
2925
    }
2926
    /**
2927
     * Extends the navigation for the given user.
2928
     *
2929
     * @param stdClass $user A user from the database
2930
     */
2931
    public function extend_for_user($user) {
2932
        $this->extendforuser[] = $user;
2933
    }
2934
 
2935
    /**
2936
     * Returns all of the users the navigation is being extended for
2937
     *
2938
     * @return array An array of extending users.
2939
     */
2940
    public function get_extending_users() {
2941
        return $this->extendforuser;
2942
    }
2943
    /**
2944
     * Adds the given course to the navigation structure.
2945
     *
2946
     * @param stdClass $course
2947
     * @param bool $forcegeneric
2948
     * @param bool $ismycourse
2949
     * @return navigation_node
2950
     */
2951
    public function add_course(stdClass $course, $forcegeneric = false, $coursetype = self::COURSE_OTHER) {
2952
        global $CFG, $SITE;
2953
 
2954
        // We found the course... we can return it now :)
2955
        if (!$forcegeneric && array_key_exists($course->id, $this->addedcourses)) {
2956
            return $this->addedcourses[$course->id];
2957
        }
2958
 
2959
        $coursecontext = context_course::instance($course->id);
2960
 
2961
        if ($coursetype != self::COURSE_MY && $coursetype != self::COURSE_CURRENT && $course->id != $SITE->id) {
2962
            if (is_role_switched($course->id)) {
2963
                // user has to be able to access course in order to switch, let's skip the visibility test here
2964
            } else if (!core_course_category::can_view_course_info($course)) {
2965
                return false;
2966
            }
2967
        }
2968
 
2969
        $issite = ($course->id == $SITE->id);
2970
        $displaycontext = \context_helper::get_navigation_filter_context($coursecontext);
2971
        $shortname = format_string($course->shortname, true, ['context' => $displaycontext]);
2972
        $fullname = format_string($course->fullname, true, ['context' => $displaycontext]);
2973
        // This is the name that will be shown for the course.
2974
        $coursename = empty($CFG->navshowfullcoursenames) ? $shortname : $fullname;
2975
 
2976
        if ($coursetype == self::COURSE_CURRENT) {
2977
            if ($coursenode = $this->rootnodes['mycourses']->find($course->id, self::TYPE_COURSE)) {
2978
                return $coursenode;
2979
            } else {
2980
                $coursetype = self::COURSE_OTHER;
2981
            }
2982
        }
2983
 
2984
        // Can the user expand the course to see its content.
2985
        $canexpandcourse = true;
2986
        if ($issite) {
2987
            $parent = $this;
2988
            $url = null;
2989
            if (empty($CFG->usesitenameforsitepages)) {
2990
                $coursename = get_string('sitepages');
2991
            }
2992
        } else if ($coursetype == self::COURSE_CURRENT) {
2993
            $parent = $this->rootnodes['currentcourse'];
2994
            $url = new moodle_url('/course/view.php', array('id'=>$course->id));
2995
            $canexpandcourse = $this->can_expand_course($course);
2996
        } else if ($coursetype == self::COURSE_MY && !$forcegeneric) {
2997
            if (!empty($CFG->navshowmycoursecategories) && ($parent = $this->rootnodes['mycourses']->find($course->category, self::TYPE_MY_CATEGORY))) {
2998
                // Nothing to do here the above statement set $parent to the category within mycourses.
2999
            } else {
3000
                $parent = $this->rootnodes['mycourses'];
3001
            }
3002
            $url = new moodle_url('/course/view.php', array('id'=>$course->id));
3003
        } else {
3004
            $parent = $this->rootnodes['courses'];
3005
            $url = new moodle_url('/course/view.php', array('id'=>$course->id));
3006
            // They can only expand the course if they can access it.
3007
            $canexpandcourse = $this->can_expand_course($course);
3008
            if (!empty($course->category) && $this->show_categories($coursetype == self::COURSE_MY)) {
3009
                if (!$this->is_category_fully_loaded($course->category)) {
3010
                    // We need to load the category structure for this course
3011
                    $this->load_all_categories($course->category, false);
3012
                }
3013
                if (array_key_exists($course->category, $this->addedcategories)) {
3014
                    $parent = $this->addedcategories[$course->category];
3015
                    // This could lead to the course being created so we should check whether it is the case again
3016
                    if (!$forcegeneric && array_key_exists($course->id, $this->addedcourses)) {
3017
                        return $this->addedcourses[$course->id];
3018
                    }
3019
                }
3020
            }
3021
        }
3022
 
3023
        $coursenode = $parent->add($coursename, $url, self::TYPE_COURSE, $shortname, $course->id, new pix_icon('i/course', ''));
3024
        $coursenode->showinflatnavigation = $coursetype == self::COURSE_MY;
3025
 
3026
        $coursenode->hidden = (!$course->visible);
3027
        $coursenode->title(format_string($course->fullname, true, ['context' => $displaycontext, 'escape' => false]));
3028
        if ($canexpandcourse) {
3029
            // This course can be expanded by the user, make it a branch to make the system aware that its expandable by ajax.
3030
            $coursenode->nodetype = self::NODETYPE_BRANCH;
3031
            $coursenode->isexpandable = true;
3032
        } else {
3033
            $coursenode->nodetype = self::NODETYPE_LEAF;
3034
            $coursenode->isexpandable = false;
3035
        }
3036
        if (!$forcegeneric) {
3037
            $this->addedcourses[$course->id] = $coursenode;
3038
        }
3039
 
3040
        return $coursenode;
3041
    }
3042
 
3043
    /**
3044
     * Returns a cache instance to use for the expand course cache.
3045
     * @return cache_session
3046
     */
3047
    protected function get_expand_course_cache() {
3048
        if ($this->cacheexpandcourse === null) {
3049
            $this->cacheexpandcourse = cache::make('core', 'navigation_expandcourse');
3050
        }
3051
        return $this->cacheexpandcourse;
3052
    }
3053
 
3054
    /**
3055
     * Checks if a user can expand a course in the navigation.
3056
     *
3057
     * We use a cache here because in order to be accurate we need to call can_access_course which is a costly function.
3058
     * Because this functionality is basic + non-essential and because we lack good event triggering this cache
3059
     * permits stale data.
3060
     * In the situation the user is granted access to a course after we've initialised this session cache the cache
3061
     * will be stale.
3062
     * It is brought up to date in only one of two ways.
3063
     *   1. The user logs out and in again.
3064
     *   2. The user browses to the course they've just being given access to.
3065
     *
3066
     * Really all this controls is whether the node is shown as expandable or not. It is uber un-important.
3067
     *
3068
     * @param stdClass $course
3069
     * @return bool
3070
     */
3071
    protected function can_expand_course($course) {
3072
        $cache = $this->get_expand_course_cache();
3073
        $canexpand = $cache->get($course->id);
3074
        if ($canexpand === false) {
3075
            $canexpand = isloggedin() && can_access_course($course, null, '', true);
3076
            $canexpand = (int)$canexpand;
3077
            $cache->set($course->id, $canexpand);
3078
        }
3079
        return ($canexpand === 1);
3080
    }
3081
 
3082
    /**
3083
     * Returns true if the category has already been loaded as have any child categories
3084
     *
3085
     * @param int $categoryid
3086
     * @return bool
3087
     */
3088
    protected function is_category_fully_loaded($categoryid) {
3089
        return (array_key_exists($categoryid, $this->addedcategories) && ($this->allcategoriesloaded || $this->addedcategories[$categoryid]->children->count() > 0));
3090
    }
3091
 
3092
    /**
3093
     * Adds essential course nodes to the navigation for the given course.
3094
     *
3095
     * This method adds nodes such as reports, blogs and participants
3096
     *
3097
     * @param navigation_node $coursenode
3098
     * @param stdClass $course
3099
     * @return bool returns true on successful addition of a node.
3100
     */
3101
    public function add_course_essentials($coursenode, stdClass $course) {
3102
        global $CFG, $SITE;
3103
        require_once($CFG->dirroot . '/course/lib.php');
3104
 
3105
        if ($course->id == $SITE->id) {
3106
            return $this->add_front_page_course_essentials($coursenode, $course);
3107
        }
3108
 
3109
        if ($coursenode == false || !($coursenode instanceof navigation_node) || $coursenode->get('participants', navigation_node::TYPE_CONTAINER)) {
3110
            return true;
3111
        }
3112
 
3113
        $navoptions = course_get_user_navigation_options($this->page->context, $course);
3114
 
3115
        //Participants
3116
        if ($navoptions->participants) {
3117
            $participants = $coursenode->add(get_string('participants'), new moodle_url('/user/index.php?id='.$course->id),
3118
                self::TYPE_CONTAINER, get_string('participants'), 'participants', new pix_icon('i/users', ''));
3119
 
3120
            if ($navoptions->blogs) {
3121
                $blogsurls = new moodle_url('/blog/index.php');
3122
                if ($currentgroup = groups_get_course_group($course, true)) {
3123
                    $blogsurls->param('groupid', $currentgroup);
3124
                } else {
3125
                    $blogsurls->param('courseid', $course->id);
3126
                }
3127
                $participants->add(get_string('blogscourse', 'blog'), $blogsurls->out(), self::TYPE_SETTING, null, 'courseblogs');
3128
            }
3129
 
3130
            if ($navoptions->notes) {
3131
                $participants->add(get_string('notes', 'notes'), new moodle_url('/notes/index.php', array('filtertype' => 'course', 'filterselect' => $course->id)), self::TYPE_SETTING, null, 'currentcoursenotes');
3132
            }
3133
        } else if (count($this->extendforuser) > 0) {
3134
            $coursenode->add(get_string('participants'), null, self::TYPE_CONTAINER, get_string('participants'), 'participants');
3135
        } else if ($siteparticipantsnode = $this->rootnodes['site']->get('participants', self::TYPE_CUSTOM)) {
3136
            // The participants node was added for the site, but cannot be viewed inside the course itself, so remove.
3137
            $siteparticipantsnode->remove();
3138
        }
3139
 
3140
        // Badges.
3141
        if ($navoptions->badges) {
1441 ariadna 3142
            $url = new moodle_url('/badges/index.php', ['type' => 2, 'id' => $course->id]);
1 efrain 3143
 
3144
            $coursenode->add(get_string('coursebadges', 'badges'), $url,
3145
                    navigation_node::TYPE_SETTING, null, 'badgesview',
3146
                    new pix_icon('i/badge', get_string('coursebadges', 'badges')));
3147
        }
3148
 
3149
        // Check access to the course and competencies page.
3150
        if ($navoptions->competencies) {
3151
            // Just a link to course competency.
3152
            $title = get_string('competencies', 'core_competency');
3153
            $path = new moodle_url("/admin/tool/lp/coursecompetencies.php", array('courseid' => $course->id));
3154
            $coursenode->add($title, $path, navigation_node::TYPE_SETTING, null, 'competencies',
3155
                    new pix_icon('i/competencies', ''));
3156
        }
3157
        if ($navoptions->grades) {
3158
            $url = new moodle_url('/grade/report/index.php', array('id'=>$course->id));
3159
            $gradenode = $coursenode->add(get_string('grades'), $url, self::TYPE_SETTING, null,
3160
                'grades', new pix_icon('i/grades', ''));
3161
            // If the page type matches the grade part, then make the nav drawer grade node (incl. all sub pages) active.
3162
            if ($this->page->context->contextlevel < CONTEXT_MODULE && strpos($this->page->pagetype, 'grade-') === 0) {
3163
                $gradenode->make_active();
3164
            }
3165
        }
3166
 
3167
        // Add link for configuring communication.
3168
        if ($navoptions->communication) {
3169
            $url = new moodle_url('/communication/configure.php', [
3170
                'contextid' => \core\context\course::instance($course->id)->id,
3171
                'instanceid' => $course->id,
3172
                'instancetype' => 'coursecommunication',
3173
                'component' => 'core_course',
3174
            ]);
3175
            $coursenode->add(get_string('communication', 'communication'), $url,
3176
                navigation_node::TYPE_SETTING, null, 'communication');
3177
        }
3178
 
1441 ariadna 3179
        if ($navoptions->overview) {
3180
            $coursenode->add(
3181
                text: get_string('activities'),
3182
                action: new moodle_url('/course/overview.php', ['id' => $course->id]),
3183
                type: self::TYPE_CONTAINER,
3184
                key: 'courseoverview',
3185
                icon: new pix_icon('i/info', ''),
3186
            );
3187
        }
3188
 
1 efrain 3189
        return true;
3190
    }
3191
    /**
3192
     * This generates the structure of the course that won't be generated when
3193
     * the modules and sections are added.
3194
     *
3195
     * Things such as the reports branch, the participants branch, blogs... get
3196
     * added to the course node by this method.
3197
     *
3198
     * @param navigation_node $coursenode
3199
     * @param stdClass $course
3200
     * @return bool True for successfull generation
3201
     */
3202
    public function add_front_page_course_essentials(navigation_node $coursenode, stdClass $course) {
3203
        global $CFG, $USER, $COURSE, $SITE;
3204
        require_once($CFG->dirroot . '/course/lib.php');
3205
 
3206
        if ($coursenode == false || $coursenode->get('frontpageloaded', navigation_node::TYPE_CUSTOM)) {
3207
            return true;
3208
        }
3209
 
3210
        $systemcontext = context_system::instance();
3211
        $navoptions = course_get_user_navigation_options($systemcontext, $course);
3212
 
3213
        // Hidden node that we use to determine if the front page navigation is loaded.
3214
        // This required as there are not other guaranteed nodes that may be loaded.
3215
        $coursenode->add('frontpageloaded', null, self::TYPE_CUSTOM, null, 'frontpageloaded')->display = false;
3216
 
3217
        // Add My courses to the site pages within the navigation structure so the block can read it.
3218
        $coursenode->add(get_string('mycourses'), new moodle_url('/my/courses.php'), self::TYPE_CUSTOM, null, 'mycourses');
3219
 
3220
        // Participants.
3221
        if ($navoptions->participants) {
3222
            $coursenode->add(get_string('participants'), new moodle_url('/user/index.php?id='.$course->id),
3223
                self::TYPE_CUSTOM, get_string('participants'), 'participants');
3224
        }
3225
 
3226
        // Blogs.
3227
        if ($navoptions->blogs) {
3228
            $blogsurls = new moodle_url('/blog/index.php');
3229
            $coursenode->add(get_string('blogssite', 'blog'), $blogsurls->out(), self::TYPE_SYSTEM, null, 'siteblog');
3230
        }
3231
 
3232
        $filterselect = 0;
3233
 
3234
        // Badges.
3235
        if ($navoptions->badges) {
1441 ariadna 3236
            $url = new moodle_url($CFG->wwwroot . '/badges/index.php', ['type' => 1]);
1 efrain 3237
            $coursenode->add(get_string('sitebadges', 'badges'), $url, navigation_node::TYPE_CUSTOM);
3238
        }
3239
 
3240
        // Notes.
3241
        if ($navoptions->notes) {
3242
            $coursenode->add(get_string('notes', 'notes'), new moodle_url('/notes/index.php',
3243
                array('filtertype' => 'course', 'filterselect' => $filterselect)), self::TYPE_SETTING, null, 'notes');
3244
        }
3245
 
3246
        // Tags
3247
        if ($navoptions->tags) {
3248
            $node = $coursenode->add(get_string('tags', 'tag'), new moodle_url('/tag/search.php'),
3249
                    self::TYPE_SETTING, null, 'tags');
3250
        }
3251
 
3252
        // Search.
3253
        if ($navoptions->search) {
3254
            $node = $coursenode->add(get_string('search', 'search'), new moodle_url('/search/index.php'),
3255
                    self::TYPE_SETTING, null, 'search');
3256
        }
3257
 
3258
        if (isloggedin()) {
3259
            $usercontext = context_user::instance($USER->id);
3260
            if (has_capability('moodle/user:manageownfiles', $usercontext)) {
3261
                $url = new moodle_url('/user/files.php');
3262
                $node = $coursenode->add(get_string('privatefiles'), $url,
3263
                    self::TYPE_SETTING, null, 'privatefiles', new pix_icon('i/privatefiles', ''));
3264
                $node->display = false;
3265
                $node->showinflatnavigation = true;
3266
                $node->mainnavonly = true;
3267
            }
3268
        }
3269
 
3270
        if (isloggedin()) {
3271
            $context = $this->page->context;
3272
            switch ($context->contextlevel) {
3273
                case CONTEXT_COURSECAT:
3274
                    // OK, expected context level.
3275
                    break;
3276
                case CONTEXT_COURSE:
3277
                    // OK, expected context level if not on frontpage.
3278
                    if ($COURSE->id != $SITE->id) {
3279
                        break;
3280
                    }
3281
                default:
3282
                    // If this context is part of a course (excluding frontpage), use the course context.
3283
                    // Otherwise, use the system context.
3284
                    $coursecontext = $context->get_course_context(false);
3285
                    if ($coursecontext && $coursecontext->instanceid !== $SITE->id) {
3286
                        $context = $coursecontext;
3287
                    } else {
3288
                        $context = $systemcontext;
3289
                    }
3290
            }
3291
 
3292
            $params = ['contextid' => $context->id];
3293
            if (has_capability('moodle/contentbank:access', $context)) {
3294
                $url = new moodle_url('/contentbank/index.php', $params);
3295
                $node = $coursenode->add(get_string('contentbank'), $url,
3296
                    self::TYPE_CUSTOM, null, 'contentbank', new pix_icon('i/contentbank', ''));
3297
                $node->showinflatnavigation = true;
3298
            }
3299
        }
3300
 
3301
        return true;
3302
    }
3303
 
3304
    /**
3305
     * Clears the navigation cache
3306
     */
3307
    public function clear_cache() {
3308
        $this->cache->clear();
3309
    }
3310
 
3311
    /**
3312
     * Sets an expansion limit for the navigation
3313
     *
3314
     * The expansion limit is used to prevent the display of content that has a type
3315
     * greater than the provided $type.
3316
     *
3317
     * Can be used to ensure things such as activities or activity content don't get
3318
     * shown on the navigation.
3319
     * They are still generated in order to ensure the navbar still makes sense.
3320
     *
3321
     * @param int $type One of navigation_node::TYPE_*
3322
     * @return bool true when complete.
3323
     */
3324
    public function set_expansion_limit($type) {
3325
        global $SITE;
3326
        $nodes = $this->find_all_of_type($type);
3327
 
3328
        // We only want to hide specific types of nodes.
3329
        // Only nodes that represent "structure" in the navigation tree should be hidden.
3330
        // If we hide all nodes then we risk hiding vital information.
3331
        $typestohide = array(
3332
            self::TYPE_CATEGORY,
3333
            self::TYPE_COURSE,
3334
            self::TYPE_SECTION,
3335
            self::TYPE_ACTIVITY
3336
        );
3337
 
3338
        foreach ($nodes as $node) {
3339
            // We need to generate the full site node
3340
            if ($type == self::TYPE_COURSE && $node->key == $SITE->id) {
3341
                continue;
3342
            }
3343
            foreach ($node->children as $child) {
3344
                $child->hide($typestohide);
3345
            }
3346
        }
3347
        return true;
3348
    }
3349
    /**
3350
     * Attempts to get the navigation with the given key from this nodes children.
3351
     *
3352
     * This function only looks at this nodes children, it does NOT look recursivily.
3353
     * If the node can't be found then false is returned.
3354
     *
3355
     * If you need to search recursivily then use the {@link global_navigation::find()} method.
3356
     *
3357
     * Note: If you are trying to set the active node {@link navigation_node::override_active_url()}
3358
     * may be of more use to you.
3359
     *
3360
     * @param string|int $key The key of the node you wish to receive.
3361
     * @param int $type One of navigation_node::TYPE_*
3362
     * @return navigation_node|false
3363
     */
3364
    public function get($key, $type = null) {
3365
        if (!$this->initialised) {
3366
            $this->initialise();
3367
        }
3368
        return parent::get($key, $type);
3369
    }
3370
 
3371
    /**
3372
     * Searches this nodes children and their children to find a navigation node
3373
     * with the matching key and type.
3374
     *
3375
     * This method is recursive and searches children so until either a node is
3376
     * found or there are no more nodes to search.
3377
     *
3378
     * If you know that the node being searched for is a child of this node
3379
     * then use the {@link global_navigation::get()} method instead.
3380
     *
3381
     * Note: If you are trying to set the active node {@link navigation_node::override_active_url()}
3382
     * may be of more use to you.
3383
     *
3384
     * @param string|int $key The key of the node you wish to receive.
3385
     * @param ?int $type One of navigation_node::TYPE_*
3386
     * @return navigation_node|false
3387
     */
3388
    public function find($key, $type) {
3389
        if (!$this->initialised) {
3390
            $this->initialise();
3391
        }
3392
        if ($type == self::TYPE_ROOTNODE && array_key_exists($key, $this->rootnodes)) {
3393
            return $this->rootnodes[$key];
3394
        }
3395
        return parent::find($key, $type);
3396
    }
3397
 
3398
    /**
3399
     * They've expanded the 'my courses' branch.
3400
     */
3401
    protected function load_courses_enrolled() {
3402
        global $CFG;
3403
 
3404
        $limit = (int) $CFG->navcourselimit;
3405
 
3406
        $courses = enrol_get_my_courses('*');
3407
        $flatnavcourses = [];
3408
 
3409
        // Go through the courses and see which ones we want to display in the flatnav.
3410
        foreach ($courses as $course) {
3411
            $classify = course_classify_for_timeline($course);
3412
 
3413
            if ($classify == COURSE_TIMELINE_INPROGRESS) {
3414
                $flatnavcourses[$course->id] = $course;
3415
            }
3416
        }
3417
 
3418
        // Get the number of courses that can be displayed in the nav block and in the flatnav.
3419
        $numtotalcourses = count($courses);
3420
        $numtotalflatnavcourses = count($flatnavcourses);
3421
 
3422
        // Reduce the size of the arrays to abide by the 'navcourselimit' setting.
3423
        $courses = array_slice($courses, 0, $limit, true);
3424
        $flatnavcourses = array_slice($flatnavcourses, 0, $limit, true);
3425
 
3426
        // Get the number of courses we are going to show for each.
3427
        $numshowncourses = count($courses);
3428
        $numshownflatnavcourses = count($flatnavcourses);
3429
        if ($numshowncourses && $this->show_my_categories()) {
3430
            // Generate an array containing unique values of all the courses' categories.
3431
            $categoryids = array();
3432
            foreach ($courses as $course) {
3433
                if (in_array($course->category, $categoryids)) {
3434
                    continue;
3435
                }
3436
                $categoryids[] = $course->category;
3437
            }
3438
 
3439
            // Array of category IDs that include the categories of the user's courses and the related course categories.
3440
            $fullpathcategoryids = [];
3441
            // Get the course categories for the enrolled courses' category IDs.
3442
            $mycoursecategories = core_course_category::get_many($categoryids);
3443
            // Loop over each of these categories and build the category tree using each category's path.
3444
            foreach ($mycoursecategories as $mycoursecat) {
3445
                $pathcategoryids = explode('/', $mycoursecat->path);
3446
                // First element of the exploded path is empty since paths begin with '/'.
3447
                array_shift($pathcategoryids);
3448
                // Merge the exploded category IDs into the full list of category IDs that we will fetch.
3449
                $fullpathcategoryids = array_merge($fullpathcategoryids, $pathcategoryids);
3450
            }
3451
 
3452
            // Fetch all of the categories related to the user's courses.
3453
            $pathcategories = core_course_category::get_many($fullpathcategoryids);
3454
            // Loop over each of these categories and build the category tree.
3455
            foreach ($pathcategories as $coursecat) {
3456
                // No need to process categories that have already been added.
3457
                if (isset($this->addedcategories[$coursecat->id])) {
3458
                    continue;
3459
                }
3460
                // Skip categories that are not visible.
3461
                if (!$coursecat->is_uservisible()) {
3462
                    continue;
3463
                }
3464
 
3465
                // Get this course category's parent node.
3466
                $parent = null;
3467
                if ($coursecat->parent && isset($this->addedcategories[$coursecat->parent])) {
3468
                    $parent = $this->addedcategories[$coursecat->parent];
3469
                }
3470
                if (!$parent) {
3471
                    // If it has no parent, then it should be right under the My courses node.
3472
                    $parent = $this->rootnodes['mycourses'];
3473
                }
3474
 
3475
                // Build the category object based from the coursecat object.
3476
                $mycategory = new stdClass();
3477
                $mycategory->id = $coursecat->id;
3478
                $mycategory->name = $coursecat->name;
3479
                $mycategory->visible = $coursecat->visible;
3480
 
3481
                // Add this category to the nav tree.
3482
                $this->add_category($mycategory, $parent, self::TYPE_MY_CATEGORY);
3483
            }
3484
        }
3485
 
3486
        // Go through each course now and add it to the nav block, and the flatnav if applicable.
3487
        foreach ($courses as $course) {
3488
            $node = $this->add_course($course, false, self::COURSE_MY);
3489
            if ($node) {
3490
                $node->showinflatnavigation = false;
3491
                // Check if we should also add this to the flat nav as well.
3492
                if (isset($flatnavcourses[$course->id])) {
3493
                    $node->showinflatnavigation = true;
3494
                }
3495
            }
3496
        }
3497
 
3498
        // Go through each course in the flatnav now.
3499
        foreach ($flatnavcourses as $course) {
3500
            // Check if we haven't already added it.
3501
            if (!isset($courses[$course->id])) {
3502
                // Ok, add it to the flatnav only.
3503
                $node = $this->add_course($course, false, self::COURSE_MY);
3504
                $node->display = false;
3505
                $node->showinflatnavigation = true;
3506
            }
3507
        }
3508
 
3509
        $showmorelinkinnav = $numtotalcourses > $numshowncourses;
3510
        $showmorelinkinflatnav = $numtotalflatnavcourses > $numshownflatnavcourses;
3511
        // Show a link to the course page if there are more courses the user is enrolled in.
3512
        if ($showmorelinkinnav || $showmorelinkinflatnav) {
3513
            // Adding hash to URL so the link is not highlighted in the navigation when clicked.
3514
            $url = new moodle_url('/my/courses.php');
3515
            $parent = $this->rootnodes['mycourses'];
3516
            $coursenode = $parent->add(get_string('morenavigationlinks'), $url, self::TYPE_CUSTOM, null, self::COURSE_INDEX_PAGE);
3517
 
3518
            if ($showmorelinkinnav) {
3519
                $coursenode->display = true;
3520
            }
3521
 
3522
            if ($showmorelinkinflatnav) {
3523
                $coursenode->showinflatnavigation = true;
3524
            }
3525
        }
3526
    }
3527
}
3528
 
3529
/**
3530
 * The global navigation class used especially for AJAX requests.
3531
 *
3532
 * The primary methods that are used in the global navigation class have been overriden
3533
 * to ensure that only the relevant branch is generated at the root of the tree.
3534
 * This can be done because AJAX is only used when the backwards structure for the
3535
 * requested branch exists.
3536
 * This has been done only because it shortens the amounts of information that is generated
3537
 * which of course will speed up the response time.. because no one likes laggy AJAX.
3538
 *
3539
 * @package   core
3540
 * @category  navigation
3541
 * @copyright 2009 Sam Hemelryk
3542
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3543
 */
3544
class global_navigation_for_ajax extends global_navigation {
3545
 
3546
    /** @var int used for determining what type of navigation_node::TYPE_* is being used */
3547
    protected $branchtype;
3548
 
3549
    /** @var int the instance id */
3550
    protected $instanceid;
3551
 
3552
    /** @var array Holds an array of expandable nodes */
3553
    protected $expandable = array();
3554
 
3555
    /**
3556
     * Constructs the navigation for use in an AJAX request
3557
     *
3558
     * @param moodle_page $page moodle_page object
3559
     * @param int $branchtype
3560
     * @param int $id
3561
     */
3562
    public function __construct($page, $branchtype, $id) {
3563
        $this->page = $page;
3564
        $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
3565
        $this->children = new navigation_node_collection();
3566
        $this->branchtype = $branchtype;
3567
        $this->instanceid = $id;
3568
        $this->initialise();
3569
    }
3570
    /**
3571
     * Initialise the navigation given the type and id for the branch to expand.
3572
     *
3573
     * @return array An array of the expandable nodes
3574
     */
3575
    public function initialise() {
3576
        global $DB, $SITE;
3577
 
3578
        if ($this->initialised || during_initial_install()) {
3579
            return $this->expandable;
3580
        }
3581
        $this->initialised = true;
3582
 
3583
        $this->rootnodes = array();
3584
        $this->rootnodes['site']    = $this->add_course($SITE);
3585
        $this->rootnodes['mycourses'] = $this->add(
3586
            get_string('mycourses'),
3587
            new moodle_url('/my/courses.php'),
3588
            self::TYPE_ROOTNODE,
3589
            null,
3590
            'mycourses'
3591
        );
3592
        $this->rootnodes['courses'] = $this->add(get_string('courses'), null, self::TYPE_ROOTNODE, null, 'courses');
3593
        // The courses branch is always displayed, and is always expandable (although may be empty).
3594
        // This mimicks what is done during {@link global_navigation::initialise()}.
3595
        $this->rootnodes['courses']->isexpandable = true;
3596
 
3597
        // Branchtype will be one of navigation_node::TYPE_*
3598
        switch ($this->branchtype) {
3599
            case 0:
3600
                if ($this->instanceid === 'mycourses') {
3601
                    $this->load_courses_enrolled();
3602
                } else if ($this->instanceid === 'courses') {
3603
                    $this->load_courses_other();
3604
                }
3605
                break;
3606
            case self::TYPE_CATEGORY :
3607
                $this->load_category($this->instanceid);
3608
                break;
3609
            case self::TYPE_MY_CATEGORY :
3610
                $this->load_category($this->instanceid, self::TYPE_MY_CATEGORY);
3611
                break;
3612
            case self::TYPE_COURSE :
3613
                $course = $DB->get_record('course', array('id' => $this->instanceid), '*', MUST_EXIST);
3614
                if (!can_access_course($course, null, '', true)) {
3615
                    // Thats OK all courses are expandable by default. We don't need to actually expand it we can just
3616
                    // add the course node and break. This leads to an empty node.
3617
                    $this->add_course($course);
3618
                    break;
3619
                }
3620
                require_course_login($course, true, null, false, true);
3621
                $this->page->set_context(context_course::instance($course->id));
3622
                $coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
3623
                $this->add_course_essentials($coursenode, $course);
3624
                $this->load_course_sections($course, $coursenode);
3625
                break;
3626
            case self::TYPE_SECTION :
3627
                $sql = 'SELECT c.*, cs.section AS sectionnumber
3628
                        FROM {course} c
3629
                        LEFT JOIN {course_sections} cs ON cs.course = c.id
3630
                        WHERE cs.id = ?';
3631
                $course = $DB->get_record_sql($sql, array($this->instanceid), MUST_EXIST);
3632
                require_course_login($course, true, null, false, true);
3633
                $this->page->set_context(context_course::instance($course->id));
3634
                $coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
3635
                $this->add_course_essentials($coursenode, $course);
3636
                $this->load_course_sections($course, $coursenode, $course->sectionnumber);
3637
                break;
3638
            case self::TYPE_ACTIVITY :
3639
                $sql = "SELECT c.*
3640
                          FROM {course} c
3641
                          JOIN {course_modules} cm ON cm.course = c.id
3642
                         WHERE cm.id = :cmid";
3643
                $params = array('cmid' => $this->instanceid);
3644
                $course = $DB->get_record_sql($sql, $params, MUST_EXIST);
3645
                $modinfo = get_fast_modinfo($course);
3646
                $cm = $modinfo->get_cm($this->instanceid);
3647
                require_course_login($course, true, $cm, false, true);
3648
                $this->page->set_context(context_module::instance($cm->id));
3649
                $coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
3650
                $this->load_course_sections($course, $coursenode, null, $cm);
3651
                $activitynode = $coursenode->find($cm->id, self::TYPE_ACTIVITY);
3652
                if ($activitynode) {
3653
                    $modulenode = $this->load_activity($cm, $course, $activitynode);
3654
                }
3655
                break;
3656
            default:
3657
                throw new Exception('Unknown type');
3658
                return $this->expandable;
3659
        }
3660
 
3661
        if ($this->page->context->contextlevel == CONTEXT_COURSE && $this->page->context->instanceid != $SITE->id) {
3662
            $this->load_for_user(null, true);
3663
        }
3664
 
3665
        // Give the local plugins a chance to include some navigation if they want.
3666
        $this->load_local_plugin_navigation();
3667
 
3668
        $this->find_expandable($this->expandable);
3669
        return $this->expandable;
3670
    }
3671
 
3672
    /**
3673
     * They've expanded the general 'courses' branch.
3674
     */
3675
    protected function load_courses_other() {
3676
        $this->load_all_courses();
3677
    }
3678
 
3679
    /**
3680
     * Loads a single category into the AJAX navigation.
3681
     *
3682
     * This function is special in that it doesn't concern itself with the parent of
3683
     * the requested category or its siblings.
3684
     * This is because with the AJAX navigation we know exactly what is wanted and only need to
3685
     * request that.
3686
     *
3687
     * @global moodle_database $DB
3688
     * @param int $categoryid id of category to load in navigation.
3689
     * @param int $nodetype type of node, if category is under MyHome then it's TYPE_MY_CATEGORY
3690
     * @return void.
3691
     */
3692
    protected function load_category($categoryid, $nodetype = self::TYPE_CATEGORY) {
3693
        global $CFG, $DB;
3694
 
3695
        $limit = 20;
3696
        if (!empty($CFG->navcourselimit)) {
3697
            $limit = (int)$CFG->navcourselimit;
3698
        }
3699
 
3700
        $catcontextsql = context_helper::get_preload_record_columns_sql('ctx');
3701
        $sql = "SELECT cc.*, $catcontextsql
3702
                  FROM {course_categories} cc
3703
                  JOIN {context} ctx ON cc.id = ctx.instanceid
3704
                 WHERE ctx.contextlevel = ".CONTEXT_COURSECAT." AND
3705
                       (cc.id = :categoryid1 OR cc.parent = :categoryid2)
3706
              ORDER BY cc.depth ASC, cc.sortorder ASC, cc.id ASC";
3707
        $params = array('categoryid1' => $categoryid, 'categoryid2' => $categoryid);
3708
        $categories = $DB->get_recordset_sql($sql, $params, 0, $limit);
3709
        $categorylist = array();
3710
        $subcategories = array();
3711
        $basecategory = null;
3712
        foreach ($categories as $category) {
3713
            $categorylist[] = $category->id;
3714
            context_helper::preload_from_record($category);
3715
            if ($category->id == $categoryid) {
3716
                $this->add_category($category, $this, $nodetype);
3717
                $basecategory = $this->addedcategories[$category->id];
3718
            } else {
3719
                $subcategories[$category->id] = $category;
3720
            }
3721
        }
3722
        $categories->close();
3723
 
3724
 
3725
        // If category is shown in MyHome then only show enrolled courses and hide empty subcategories,
3726
        // else show all courses.
3727
        if ($nodetype === self::TYPE_MY_CATEGORY) {
3728
            $courses = enrol_get_my_courses('*');
3729
            $categoryids = array();
3730
 
3731
            // Only search for categories if basecategory was found.
3732
            if (!is_null($basecategory)) {
3733
                // Get course parent category ids.
3734
                foreach ($courses as $course) {
3735
                    $categoryids[] = $course->category;
3736
                }
3737
 
3738
                // Get a unique list of category ids which a part of the path
3739
                // to user's courses.
3740
                $coursesubcategories = array();
3741
                $addedsubcategories = array();
3742
 
3743
                list($sql, $params) = $DB->get_in_or_equal($categoryids);
3744
                $categories = $DB->get_recordset_select('course_categories', 'id '.$sql, $params, 'sortorder, id', 'id, path');
3745
 
3746
                foreach ($categories as $category){
3747
                    $coursesubcategories = array_merge($coursesubcategories, explode('/', trim($category->path, "/")));
3748
                }
3749
                $categories->close();
3750
                $coursesubcategories = array_unique($coursesubcategories);
3751
 
3752
                // Only add a subcategory if it is part of the path to user's course and
3753
                // wasn't already added.
3754
                foreach ($subcategories as $subid => $subcategory) {
3755
                    if (in_array($subid, $coursesubcategories) &&
3756
                            !in_array($subid, $addedsubcategories)) {
3757
                            $this->add_category($subcategory, $basecategory, $nodetype);
3758
                            $addedsubcategories[] = $subid;
3759
                    }
3760
                }
3761
            }
3762
 
3763
            foreach ($courses as $course) {
3764
                // Add course if it's in category.
3765
                if (in_array($course->category, $categorylist)) {
3766
                    $this->add_course($course, true, self::COURSE_MY);
3767
                }
3768
            }
3769
        } else {
3770
            if (!is_null($basecategory)) {
3771
                foreach ($subcategories as $key=>$category) {
3772
                    $this->add_category($category, $basecategory, $nodetype);
3773
                }
3774
            }
3775
            $courses = $DB->get_recordset('course', array('category' => $categoryid), 'sortorder', '*' , 0, $limit);
3776
            foreach ($courses as $course) {
3777
                $this->add_course($course);
3778
            }
3779
            $courses->close();
3780
        }
3781
    }
3782
 
3783
    /**
3784
     * Returns an array of expandable nodes
3785
     * @return array
3786
     */
3787
    public function get_expandable() {
3788
        return $this->expandable;
3789
    }
3790
}
3791
 
3792
/**
3793
 * Navbar class
3794
 *
3795
 * This class is used to manage the navbar, which is initialised from the navigation
3796
 * object held by PAGE
3797
 *
3798
 * @package   core
3799
 * @category  navigation
3800
 * @copyright 2009 Sam Hemelryk
3801
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3802
 */
3803
class navbar extends navigation_node {
3804
    /** @var bool A switch for whether the navbar is initialised or not */
3805
    protected $initialised = false;
3806
    /** @var mixed keys used to reference the nodes on the navbar */
3807
    protected $keys = array();
3808
    /** @var null|string content of the navbar */
3809
    protected $content = null;
3810
    /** @var moodle_page object the moodle page that this navbar belongs to */
3811
    protected $page;
3812
    /** @var bool A switch for whether to ignore the active navigation information */
3813
    protected $ignoreactive = false;
3814
    /** @var bool A switch to let us know if we are in the middle of an install */
3815
    protected $duringinstall = false;
3816
    /** @var bool A switch for whether the navbar has items */
3817
    protected $hasitems = false;
3818
    /** @var array An array of navigation nodes for the navbar */
3819
    protected $items;
3820
    /** @var array An array of child node objects */
3821
    public $children = array();
3822
    /** @var bool A switch for whether we want to include the root node in the navbar */
3823
    public $includesettingsbase = false;
3824
    /** @var breadcrumb_navigation_node[] $prependchildren */
3825
    protected $prependchildren = array();
3826
 
3827
    /**
3828
     * The almighty constructor
3829
     *
3830
     * @param moodle_page $page
3831
     */
3832
    public function __construct(moodle_page $page) {
3833
        global $CFG;
3834
        if (during_initial_install()) {
3835
            $this->duringinstall = true;
3836
            return;
3837
        }
3838
        $this->page = $page;
3839
        $this->text = get_string('home');
3840
        $this->shorttext = get_string('home');
3841
        $this->action = new moodle_url($CFG->wwwroot);
3842
        $this->nodetype = self::NODETYPE_BRANCH;
3843
        $this->type = self::TYPE_SYSTEM;
3844
    }
3845
 
3846
    /**
3847
     * Quick check to see if the navbar will have items in.
3848
     *
3849
     * @return bool Returns true if the navbar will have items, false otherwise
3850
     */
3851
    public function has_items() {
3852
        if ($this->duringinstall) {
3853
            return false;
3854
        } else if ($this->hasitems !== false) {
3855
            return true;
3856
        }
3857
        $outcome = false;
3858
        if (count($this->children) > 0 || count($this->prependchildren) > 0) {
3859
            // There have been manually added items - there are definitely items.
3860
            $outcome = true;
3861
        } else if (!$this->ignoreactive) {
3862
            // We will need to initialise the navigation structure to check if there are active items.
3863
            $this->page->navigation->initialise($this->page);
3864
            $outcome = ($this->page->navigation->contains_active_node() || $this->page->settingsnav->contains_active_node());
3865
        }
3866
        $this->hasitems = $outcome;
3867
        return $outcome;
3868
    }
3869
 
3870
    /**
3871
     * Turn on/off ignore active
3872
     *
3873
     * @param bool $setting
3874
     */
3875
    public function ignore_active($setting=true) {
3876
        $this->ignoreactive = ($setting);
3877
    }
3878
 
3879
    /**
3880
     * Gets a navigation node
3881
     *
3882
     * @param string|int $key for referencing the navbar nodes
3883
     * @param int $type breadcrumb_navigation_node::TYPE_*
3884
     * @return breadcrumb_navigation_node|bool
3885
     */
3886
    public function get($key, $type = null) {
3887
        foreach ($this->children as &$child) {
3888
            if ($child->key === $key && ($type == null || $type == $child->type)) {
3889
                return $child;
3890
            }
3891
        }
3892
        foreach ($this->prependchildren as &$child) {
3893
            if ($child->key === $key && ($type == null || $type == $child->type)) {
3894
                return $child;
3895
            }
3896
        }
3897
        return false;
3898
    }
3899
    /**
3900
     * Returns an array of breadcrumb_navigation_nodes that make up the navbar.
3901
     *
3902
     * @return array
3903
     */
3904
    public function get_items() {
3905
        global $CFG;
3906
        $items = array();
3907
        // Make sure that navigation is initialised
3908
        if (!$this->has_items()) {
3909
            return $items;
3910
        }
3911
        if ($this->items !== null) {
3912
            return $this->items;
3913
        }
3914
 
3915
        if (count($this->children) > 0) {
3916
            // Add the custom children.
3917
            $items = array_reverse($this->children);
3918
        }
3919
 
3920
        // Check if navigation contains the active node
3921
        if (!$this->ignoreactive) {
3922
            // We will need to ensure the navigation has been initialised.
3923
            $this->page->navigation->initialise($this->page);
3924
            // Now find the active nodes on both the navigation and settings.
3925
            $navigationactivenode = $this->page->navigation->find_active_node();
3926
            $settingsactivenode = $this->page->settingsnav->find_active_node();
3927
 
3928
            if ($navigationactivenode && $settingsactivenode) {
3929
                // Parse a combined navigation tree
3930
                while ($settingsactivenode && $settingsactivenode->parent !== null) {
3931
                    if (!$settingsactivenode->mainnavonly) {
3932
                        $items[] = new breadcrumb_navigation_node($settingsactivenode);
3933
                    }
3934
                    $settingsactivenode = $settingsactivenode->parent;
3935
                }
3936
                if (!$this->includesettingsbase) {
3937
                    // Removes the first node from the settings (root node) from the list
3938
                    array_pop($items);
3939
                }
3940
                while ($navigationactivenode && $navigationactivenode->parent !== null) {
3941
                    if (!$navigationactivenode->mainnavonly) {
3942
                        $items[] = new breadcrumb_navigation_node($navigationactivenode);
3943
                    }
3944
                    if (!empty($CFG->navshowcategories) &&
3945
                            $navigationactivenode->type === self::TYPE_COURSE &&
3946
                            $navigationactivenode->parent->key === 'currentcourse') {
3947
                        foreach ($this->get_course_categories() as $item) {
3948
                            $items[] = new breadcrumb_navigation_node($item);
3949
                        }
3950
                    }
3951
                    $navigationactivenode = $navigationactivenode->parent;
3952
                }
3953
            } else if ($navigationactivenode) {
3954
                // Parse the navigation tree to get the active node
3955
                while ($navigationactivenode && $navigationactivenode->parent !== null) {
3956
                    if (!$navigationactivenode->mainnavonly) {
3957
                        $items[] = new breadcrumb_navigation_node($navigationactivenode);
3958
                    }
3959
                    if (!empty($CFG->navshowcategories) &&
3960
                            $navigationactivenode->type === self::TYPE_COURSE &&
3961
                            $navigationactivenode->parent->key === 'currentcourse') {
3962
                        foreach ($this->get_course_categories() as $item) {
3963
                            $items[] = new breadcrumb_navigation_node($item);
3964
                        }
3965
                    }
3966
                    $navigationactivenode = $navigationactivenode->parent;
3967
                }
3968
            } else if ($settingsactivenode) {
3969
                // Parse the settings navigation to get the active node
3970
                while ($settingsactivenode && $settingsactivenode->parent !== null) {
3971
                    if (!$settingsactivenode->mainnavonly) {
3972
                        $items[] = new breadcrumb_navigation_node($settingsactivenode);
3973
                    }
3974
                    $settingsactivenode = $settingsactivenode->parent;
3975
                }
3976
            }
3977
        }
3978
 
3979
        $items[] = new breadcrumb_navigation_node(array(
3980
            'text' => $this->page->navigation->text,
3981
            'shorttext' => $this->page->navigation->shorttext,
3982
            'key' => $this->page->navigation->key,
3983
            'action' => $this->page->navigation->action
3984
        ));
3985
 
3986
        if (count($this->prependchildren) > 0) {
3987
            // Add the custom children
3988
            $items = array_merge($items, array_reverse($this->prependchildren));
3989
        }
3990
 
3991
        $last = reset($items);
3992
        if ($last) {
3993
            $last->set_last(true);
3994
        }
3995
        $this->items = array_reverse($items);
3996
        return $this->items;
3997
    }
3998
 
3999
    /**
4000
     * Get the list of categories leading to this course.
4001
     *
4002
     * This function is used by {@link navbar::get_items()} to add back the "courses"
4003
     * node and category chain leading to the current course.  Note that this is only ever
4004
     * called for the current course, so we don't need to bother taking in any parameters.
4005
     *
4006
     * @return array
4007
     */
4008
    private function get_course_categories() {
4009
        global $CFG;
4010
        require_once($CFG->dirroot.'/course/lib.php');
4011
 
4012
        $categories = array();
4013
        $cap = 'moodle/category:viewhiddencategories';
4014
        $showcategories = !core_course_category::is_simple_site();
4015
 
4016
        if ($showcategories) {
4017
            foreach ($this->page->categories as $category) {
4018
                $context = context_coursecat::instance($category->id);
4019
                if (!core_course_category::can_view_category($category)) {
4020
                    continue;
4021
                }
4022
 
4023
                $displaycontext = \context_helper::get_navigation_filter_context($context);
4024
                $url = new moodle_url('/course/index.php', ['categoryid' => $category->id]);
4025
                $name = format_string($category->name, true, ['context' => $displaycontext]);
4026
                $categorynode = breadcrumb_navigation_node::create($name, $url, self::TYPE_CATEGORY, null, $category->id);
4027
                if (!$category->visible) {
4028
                    $categorynode->hidden = true;
4029
                }
4030
                $categories[] = $categorynode;
4031
            }
4032
        }
4033
 
4034
        // Don't show the 'course' node if enrolled in this course.
4035
        $coursecontext = context_course::instance($this->page->course->id);
4036
        if (!is_enrolled($coursecontext, null, '', true)) {
4037
            $courses = $this->page->navigation->get('courses');
4038
            if (!$courses) {
4039
                // Courses node may not be present.
4040
                $courses = breadcrumb_navigation_node::create(
4041
                    get_string('courses'),
4042
                    new moodle_url('/course/index.php'),
4043
                    self::TYPE_CONTAINER
4044
                );
4045
            }
4046
            $categories[] = $courses;
4047
        }
4048
 
4049
        return $categories;
4050
    }
4051
 
4052
    /**
4053
     * Add a new breadcrumb_navigation_node to the navbar, overrides parent::add
4054
     *
4055
     * This function overrides {@link breadcrumb_navigation_node::add()} so that we can change
4056
     * the way nodes get added to allow us to simply call add and have the node added to the
4057
     * end of the navbar
4058
     *
4059
     * @param string $text
4060
     * @param string|moodle_url|action_link $action An action to associate with this node.
4061
     * @param int $type One of navigation_node::TYPE_*
4062
     * @param string $shorttext
4063
     * @param string|int $key A key to identify this node with. Key + type is unique to a parent.
4064
     * @param pix_icon $icon An optional icon to use for this node.
4065
     * @return navigation_node
4066
     */
1441 ariadna 4067
    public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, ?pix_icon $icon=null) {
1 efrain 4068
        if ($this->content !== null) {
4069
            debugging('Nav bar items must be printed before $OUTPUT->header() has been called', DEBUG_DEVELOPER);
4070
        }
4071
 
4072
        // Properties array used when creating the new navigation node
4073
        $itemarray = array(
4074
            'text' => $text,
4075
            'type' => $type
4076
        );
4077
        // Set the action if one was provided
4078
        if ($action!==null) {
4079
            $itemarray['action'] = $action;
4080
        }
4081
        // Set the shorttext if one was provided
4082
        if ($shorttext!==null) {
4083
            $itemarray['shorttext'] = $shorttext;
4084
        }
4085
        // Set the icon if one was provided
4086
        if ($icon!==null) {
4087
            $itemarray['icon'] = $icon;
4088
        }
4089
        // Default the key to the number of children if not provided
4090
        if ($key === null) {
4091
            $key = count($this->children);
4092
        }
4093
        // Set the key
4094
        $itemarray['key'] = $key;
4095
        // Set the parent to this node
4096
        $itemarray['parent'] = $this;
4097
        // Add the child using the navigation_node_collections add method
4098
        $this->children[] = new breadcrumb_navigation_node($itemarray);
4099
        return $this;
4100
    }
4101
 
4102
    /**
4103
     * Prepends a new navigation_node to the start of the navbar
4104
     *
4105
     * @param string $text
4106
     * @param string|moodle_url|action_link $action An action to associate with this node.
4107
     * @param int $type One of navigation_node::TYPE_*
4108
     * @param string $shorttext
4109
     * @param string|int $key A key to identify this node with. Key + type is unique to a parent.
4110
     * @param pix_icon $icon An optional icon to use for this node.
4111
     * @return navigation_node
4112
     */
1441 ariadna 4113
    public function prepend($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, ?pix_icon $icon=null) {
1 efrain 4114
        if ($this->content !== null) {
4115
            debugging('Nav bar items must be printed before $OUTPUT->header() has been called', DEBUG_DEVELOPER);
4116
        }
4117
        // Properties array used when creating the new navigation node.
4118
        $itemarray = array(
4119
            'text' => $text,
4120
            'type' => $type
4121
        );
4122
        // Set the action if one was provided.
4123
        if ($action!==null) {
4124
            $itemarray['action'] = $action;
4125
        }
4126
        // Set the shorttext if one was provided.
4127
        if ($shorttext!==null) {
4128
            $itemarray['shorttext'] = $shorttext;
4129
        }
4130
        // Set the icon if one was provided.
4131
        if ($icon!==null) {
4132
            $itemarray['icon'] = $icon;
4133
        }
4134
        // Default the key to the number of children if not provided.
4135
        if ($key === null) {
4136
            $key = count($this->children);
4137
        }
4138
        // Set the key.
4139
        $itemarray['key'] = $key;
4140
        // Set the parent to this node.
4141
        $itemarray['parent'] = $this;
4142
        // Add the child node to the prepend list.
4143
        $this->prependchildren[] = new breadcrumb_navigation_node($itemarray);
4144
        return $this;
4145
    }
4146
}
4147
 
4148
/**
4149
 * Subclass of navigation_node allowing different rendering for the breadcrumbs
4150
 * in particular adding extra metadata for search engine robots to leverage.
4151
 *
4152
 * @package   core
4153
 * @category  navigation
4154
 * @copyright 2015 Brendan Heywood
4155
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4156
 */
4157
class breadcrumb_navigation_node extends navigation_node {
4158
 
4159
    /** @var $last boolean A flag indicating this is the last item in the list of breadcrumbs. */
4160
    private $last = false;
4161
 
4162
    /**
4163
     * A proxy constructor
4164
     *
4165
     * @param mixed $navnode A navigation_node or an array
4166
     */
4167
    public function __construct($navnode) {
4168
        if (is_array($navnode)) {
4169
            parent::__construct($navnode);
4170
        } else if ($navnode instanceof navigation_node) {
4171
 
4172
            // Just clone everything.
4173
            $objvalues = get_object_vars($navnode);
4174
            foreach ($objvalues as $key => $value) {
4175
                 $this->$key = $value;
4176
            }
4177
        } else {
4178
            throw new coding_exception('Not a valid breadcrumb_navigation_node');
4179
        }
4180
    }
4181
 
4182
    /**
4183
     * Getter for "last"
4184
     * @return boolean
4185
     */
4186
    public function is_last() {
4187
        return $this->last;
4188
    }
4189
 
4190
    /**
4191
     * Setter for "last"
4192
     * @param $val boolean
4193
     */
4194
    public function set_last($val) {
4195
        $this->last = $val;
4196
    }
4197
}
4198
 
4199
/**
4200
 * Subclass of navigation_node allowing different rendering for the flat navigation
4201
 * in particular allowing dividers and indents.
4202
 *
4203
 * @deprecated since Moodle 4.0 - do not use any more. Leverage secondary/tertiary navigation concepts
4204
 * @package   core
4205
 * @category  navigation
4206
 * @copyright 2016 Damyon Wiese
4207
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4208
 */
4209
class flat_navigation_node extends navigation_node {
4210
 
4211
    /** @var $indent integer The indent level */
4212
    private $indent = 0;
4213
 
4214
    /** @var $showdivider bool Show a divider before this element */
4215
    private $showdivider = false;
4216
 
4217
    /** @var $collectionlabel string Label for a group of nodes */
4218
    private $collectionlabel = '';
4219
 
4220
    /**
4221
     * A proxy constructor
4222
     *
4223
     * @param mixed $navnode A navigation_node or an array
4224
     */
4225
    public function __construct($navnode, $indent) {
4226
        debugging("Flat nav has been deprecated in favour of primary/secondary navigation concepts");
4227
        if (is_array($navnode)) {
4228
            parent::__construct($navnode);
4229
        } else if ($navnode instanceof navigation_node) {
4230
 
4231
            // Just clone everything.
4232
            $objvalues = get_object_vars($navnode);
4233
            foreach ($objvalues as $key => $value) {
4234
                 $this->$key = $value;
4235
            }
4236
        } else {
4237
            throw new coding_exception('Not a valid flat_navigation_node');
4238
        }
4239
        $this->indent = $indent;
4240
    }
4241
 
4242
    /**
4243
     * Setter, a label is required for a flat navigation node that shows a divider.
4244
     *
4245
     * @param string $label
4246
     */
4247
    public function set_collectionlabel($label) {
4248
        $this->collectionlabel = $label;
4249
    }
4250
 
4251
    /**
4252
     * Getter, get the label for this flat_navigation node, or it's parent if it doesn't have one.
4253
     *
4254
     * @return string
4255
     */
4256
    public function get_collectionlabel() {
4257
        if (!empty($this->collectionlabel)) {
4258
            return $this->collectionlabel;
4259
        }
4260
        if ($this->parent && ($this->parent instanceof flat_navigation_node || $this->parent instanceof flat_navigation)) {
4261
            return $this->parent->get_collectionlabel();
4262
        }
4263
        debugging('Navigation region requires a label', DEBUG_DEVELOPER);
4264
        return '';
4265
    }
4266
 
4267
    /**
4268
     * Does this node represent a course section link.
4269
     * @return boolean
4270
     */
4271
    public function is_section() {
4272
        return $this->type == navigation_node::TYPE_SECTION;
4273
    }
4274
 
4275
    /**
4276
     * In flat navigation - sections are active if we are looking at activities in the section.
4277
     * @return boolean
4278
     */
4279
    public function isactive() {
4280
        global $PAGE;
4281
 
4282
        if ($this->is_section()) {
4283
            $active = $PAGE->navigation->find_active_node();
4284
            if ($active) {
4285
                while ($active = $active->parent) {
4286
                    if ($active->key == $this->key && $active->type == $this->type) {
4287
                        return true;
4288
                    }
4289
                }
4290
            }
4291
        }
4292
        return $this->isactive;
4293
    }
4294
 
4295
    /**
4296
     * Getter for "showdivider"
4297
     * @return boolean
4298
     */
4299
    public function showdivider() {
4300
        return $this->showdivider;
4301
    }
4302
 
4303
    /**
4304
     * Setter for "showdivider"
4305
     * @param $val boolean
4306
     * @param $label string Label for the group of nodes
4307
     */
4308
    public function set_showdivider($val, $label = '') {
4309
        $this->showdivider = $val;
4310
        if ($this->showdivider && empty($label)) {
4311
            debugging('Navigation region requires a label', DEBUG_DEVELOPER);
4312
        } else {
4313
            $this->set_collectionlabel($label);
4314
        }
4315
    }
4316
 
4317
    /**
4318
     * Getter for "indent"
4319
     * @return boolean
4320
     */
4321
    public function get_indent() {
4322
        return $this->indent;
4323
    }
4324
 
4325
    /**
4326
     * Setter for "indent"
4327
     * @param $val boolean
4328
     */
4329
    public function set_indent($val) {
4330
        $this->indent = $val;
4331
    }
4332
}
4333
 
4334
/**
4335
 * Class used to generate a collection of navigation nodes most closely related
4336
 * to the current page.
4337
 *
4338
 * @deprecated since Moodle 4.0 - do not use any more. Leverage secondary/tertiary navigation concepts
4339
 * @package core
4340
 * @copyright 2016 Damyon Wiese
4341
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4342
 */
4343
class flat_navigation extends navigation_node_collection {
4344
    /** @var moodle_page the moodle page that the navigation belongs to */
4345
    protected $page;
4346
 
4347
    /**
4348
     * Constructor.
4349
     *
4350
     * @param moodle_page $page
4351
     */
4352
    public function __construct(moodle_page &$page) {
4353
        if (during_initial_install()) {
4354
            return;
4355
        }
4356
        debugging("Flat navigation has been deprecated in favour of primary/secondary navigation concepts");
4357
        $this->page = $page;
4358
    }
4359
 
4360
    /**
4361
     * Build the list of navigation nodes based on the current navigation and settings trees.
4362
     *
4363
     */
4364
    public function initialise() {
4365
        global $PAGE, $USER, $OUTPUT, $CFG;
4366
        if (during_initial_install()) {
4367
            return;
4368
        }
4369
 
4370
        $current = false;
4371
 
4372
        $course = $PAGE->course;
4373
 
4374
        $this->page->navigation->initialise();
4375
 
4376
        // First walk the nav tree looking for "flat_navigation" nodes.
4377
        if ($course->id > 1) {
4378
            // It's a real course.
4379
            $url = new moodle_url('/course/view.php', array('id' => $course->id));
4380
 
4381
            $coursecontext = context_course::instance($course->id, MUST_EXIST);
4382
            $displaycontext = \context_helper::get_navigation_filter_context($coursecontext);
4383
            // This is the name that will be shown for the course.
4384
            $coursename = empty($CFG->navshowfullcoursenames) ?
4385
                format_string($course->shortname, true, ['context' => $displaycontext]) :
4386
                format_string($course->fullname, true, ['context' => $displaycontext]);
4387
 
4388
            $flat = new flat_navigation_node(navigation_node::create($coursename, $url), 0);
4389
            $flat->set_collectionlabel($coursename);
4390
            $flat->key = 'coursehome';
4391
            $flat->icon = new pix_icon('i/course', '');
4392
 
4393
            $courseformat = course_get_format($course);
4394
            $coursenode = $PAGE->navigation->find_active_node();
4395
            $targettype = navigation_node::TYPE_COURSE;
4396
 
4397
            // Single activity format has no course node - the course node is swapped for the activity node.
4398
            if (!$courseformat->has_view_page()) {
4399
                $targettype = navigation_node::TYPE_ACTIVITY;
4400
            }
4401
 
4402
            while (!empty($coursenode) && ($coursenode->type != $targettype)) {
4403
                $coursenode = $coursenode->parent;
4404
            }
4405
            // There is one very strange page in mod/feedback/view.php which thinks it is both site and course
4406
            // context at the same time. That page is broken but we need to handle it (hence the SITEID).
4407
            if ($coursenode && $coursenode->key != SITEID) {
4408
                $this->add($flat);
4409
                foreach ($coursenode->children as $child) {
4410
                    if ($child->action) {
4411
                        $flat = new flat_navigation_node($child, 0);
4412
                        $this->add($flat);
4413
                    }
4414
                }
4415
            }
4416
 
4417
            $this->page->navigation->build_flat_navigation_list($this, true, get_string('site'));
4418
        } else {
4419
            $this->page->navigation->build_flat_navigation_list($this, false, get_string('site'));
4420
        }
4421
 
4422
        $admin = $PAGE->settingsnav->find('siteadministration', navigation_node::TYPE_SITE_ADMIN);
4423
        if (!$admin) {
4424
            // Try again - crazy nav tree!
4425
            $admin = $PAGE->settingsnav->find('root', navigation_node::TYPE_SITE_ADMIN);
4426
        }
4427
        if ($admin) {
4428
            $flat = new flat_navigation_node($admin, 0);
4429
            $flat->set_showdivider(true, get_string('sitesettings'));
4430
            $flat->key = 'sitesettings';
4431
            $flat->icon = new pix_icon('t/preferences', '');
4432
            $this->add($flat);
4433
        }
4434
 
4435
        // Add-a-block in editing mode.
4436
        if (isset($this->page->theme->addblockposition) &&
4437
                $this->page->theme->addblockposition == BLOCK_ADDBLOCK_POSITION_FLATNAV &&
4438
                $PAGE->user_is_editing() && $PAGE->user_can_edit_blocks()) {
4439
            $url = new moodle_url($PAGE->url, ['bui_addblock' => '', 'sesskey' => sesskey()]);
4440
            $addablock = navigation_node::create(get_string('addblock'), $url);
4441
            $flat = new flat_navigation_node($addablock, 0);
4442
            $flat->set_showdivider(true, get_string('blocksaddedit'));
4443
            $flat->key = 'addblock';
4444
            $flat->icon = new pix_icon('i/addblock', '');
4445
            $this->add($flat);
4446
 
4447
            $addblockurl = "?{$url->get_query_string(false)}";
4448
 
4449
            $PAGE->requires->js_call_amd('core_block/add_modal', 'init',
4450
                [$addblockurl, $this->page->get_edited_page_hash()]);
4451
        }
4452
    }
4453
 
4454
    /**
4455
     * Override the parent so we can set a label for this collection if it has not been set yet.
4456
     *
4457
     * @param navigation_node $node Node to add
4458
     * @param string $beforekey If specified, adds before a node with this key,
4459
     *   otherwise adds at end
4460
     * @return navigation_node Added node
4461
     */
4462
    public function add(navigation_node $node, $beforekey=null) {
4463
        $result = parent::add($node, $beforekey);
4464
        // Extend the parent to get a name for the collection of nodes if required.
4465
        if (empty($this->collectionlabel)) {
4466
            if ($node instanceof flat_navigation_node) {
4467
                $this->set_collectionlabel($node->get_collectionlabel());
4468
            }
4469
        }
4470
 
4471
        return $result;
4472
    }
4473
}
4474
 
4475
/**
4476
 * Class used to manage the settings option for the current page
4477
 *
4478
 * This class is used to manage the settings options in a tree format (recursively)
4479
 * and was created initially for use with the settings blocks.
4480
 *
4481
 * @package   core
4482
 * @category  navigation
4483
 * @copyright 2009 Sam Hemelryk
4484
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4485
 */
4486
class settings_navigation extends navigation_node {
4487
    /** @var context the current context */
4488
    protected $context;
4489
    /** @var moodle_page the moodle page that the navigation belongs to */
4490
    protected $page;
4491
    /** @var string contains administration section navigation_nodes */
4492
    protected $adminsection;
4493
    /** @var bool A switch to see if the navigation node is initialised */
4494
    protected $initialised = false;
4495
    /** @var array An array of users that the nodes can extend for. */
4496
    protected $userstoextendfor = array();
4497
    /** @var navigation_cache **/
4498
    protected $cache;
4499
 
4500
    /**
4501
     * Sets up the object with basic settings and preparse it for use
4502
     *
4503
     * @param moodle_page $page
4504
     */
4505
    public function __construct(moodle_page &$page) {
4506
        if (during_initial_install()) {
4507
            return;
4508
        }
4509
        $this->page = $page;
4510
        // Initialise the main navigation. It is most important that this is done
4511
        // before we try anything
4512
        $this->page->navigation->initialise();
4513
        // Initialise the navigation cache
4514
        $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
4515
        $this->children = new navigation_node_collection();
4516
    }
4517
 
4518
    /**
4519
     * Initialise the settings navigation based on the current context
4520
     *
4521
     * This function initialises the settings navigation tree for a given context
4522
     * by calling supporting functions to generate major parts of the tree.
4523
     *
4524
     */
4525
    public function initialise() {
4526
        global $DB, $SESSION, $SITE;
4527
 
4528
        if (during_initial_install()) {
4529
            return false;
4530
        } else if ($this->initialised) {
4531
            return true;
4532
        }
4533
        $this->id = 'settingsnav';
4534
        $this->context = $this->page->context;
4535
 
4536
        $context = $this->context;
4537
        if ($context->contextlevel == CONTEXT_BLOCK) {
4538
            $this->load_block_settings();
4539
            $context = $context->get_parent_context();
4540
            $this->context = $context;
4541
        }
4542
        switch ($context->contextlevel) {
4543
            case CONTEXT_SYSTEM:
4544
                if ($this->page->url->compare(new moodle_url('/admin/settings.php', array('section'=>'frontpagesettings')))) {
4545
                    $this->load_front_page_settings(($context->id == $this->context->id));
4546
                }
4547
                break;
4548
            case CONTEXT_COURSECAT:
4549
                $this->load_category_settings();
4550
                break;
4551
            case CONTEXT_COURSE:
4552
                if ($this->page->course->id != $SITE->id) {
4553
                    $this->load_course_settings(($context->id == $this->context->id));
4554
                } else {
4555
                    $this->load_front_page_settings(($context->id == $this->context->id));
4556
                }
4557
                break;
4558
            case CONTEXT_MODULE:
4559
                $this->load_module_settings();
4560
                $this->load_course_settings();
4561
                break;
4562
            case CONTEXT_USER:
4563
                if ($this->page->course->id != $SITE->id) {
4564
                    $this->load_course_settings();
4565
                }
4566
                break;
4567
        }
4568
 
4569
        $usersettings = $this->load_user_settings($this->page->course->id);
4570
 
4571
        $adminsettings = false;
4572
        if (isloggedin() && !isguestuser() && (!isset($SESSION->load_navigation_admin) || $SESSION->load_navigation_admin)) {
4573
            $isadminpage = $this->is_admin_tree_needed();
4574
 
4575
            if (has_capability('moodle/site:configview', context_system::instance())) {
4576
                if (has_capability('moodle/site:config', context_system::instance())) {
4577
                    // Make sure this works even if config capability changes on the fly
4578
                    // and also make it fast for admin right after login.
4579
                    $SESSION->load_navigation_admin = 1;
4580
                    if ($isadminpage) {
4581
                        $adminsettings = $this->load_administration_settings();
4582
                    }
4583
 
4584
                } else if (!isset($SESSION->load_navigation_admin)) {
4585
                    $adminsettings = $this->load_administration_settings();
4586
                    $SESSION->load_navigation_admin = (int)($adminsettings->children->count() > 0);
4587
 
4588
                } else if ($SESSION->load_navigation_admin) {
4589
                    if ($isadminpage) {
4590
                        $adminsettings = $this->load_administration_settings();
4591
                    }
4592
                }
4593
 
4594
                // Print empty navigation node, if needed.
4595
                if ($SESSION->load_navigation_admin && !$isadminpage) {
4596
                    if ($adminsettings) {
4597
                        // Do not print settings tree on pages that do not need it, this helps with performance.
4598
                        $adminsettings->remove();
4599
                        $adminsettings = false;
4600
                    }
4601
                    $siteadminnode = $this->add(get_string('administrationsite'), new moodle_url('/admin/search.php'),
4602
                            self::TYPE_SITE_ADMIN, null, 'siteadministration');
4603
                    $siteadminnode->id = 'expandable_branch_' . $siteadminnode->type . '_' .
4604
                            clean_param($siteadminnode->key, PARAM_ALPHANUMEXT);
4605
                    $siteadminnode->requiresajaxloading = 'true';
4606
                }
4607
            }
4608
        }
4609
 
4610
        if ($context->contextlevel == CONTEXT_SYSTEM && $adminsettings) {
4611
            $adminsettings->force_open();
4612
        } else if ($context->contextlevel == CONTEXT_USER && $usersettings) {
4613
            $usersettings->force_open();
4614
        }
4615
 
4616
        // At this point we give any local plugins the ability to extend/tinker with the navigation settings.
4617
        $this->load_local_plugin_settings();
4618
 
4619
        foreach ($this->children as $key=>$node) {
4620
            if ($node->nodetype == self::NODETYPE_BRANCH && $node->children->count() == 0) {
4621
                // Site administration is shown as link.
4622
                if (!empty($SESSION->load_navigation_admin) && ($node->type === self::TYPE_SITE_ADMIN)) {
4623
                    continue;
4624
                }
4625
                $node->remove();
4626
            }
4627
        }
4628
        $this->initialised = true;
4629
    }
4630
    /**
4631
     * Override the parent function so that we can add preceeding hr's and set a
4632
     * root node class against all first level element
4633
     *
4634
     * It does this by first calling the parent's add method {@link navigation_node::add()}
4635
     * and then proceeds to use the key to set class and hr
4636
     *
4637
     * @param string $text text to be used for the link.
4638
     * @param string|moodle_url $url url for the new node
4639
     * @param int $type the type of node navigation_node::TYPE_*
4640
     * @param string $shorttext
4641
     * @param string|int $key a key to access the node by.
4642
     * @param pix_icon $icon An icon that appears next to the node.
4643
     * @return navigation_node with the new node added to it.
4644
     */
1441 ariadna 4645
    public function add($text, $url=null, $type=null, $shorttext=null, $key=null, ?pix_icon $icon=null) {
1 efrain 4646
        $node = parent::add($text, $url, $type, $shorttext, $key, $icon);
4647
        $node->add_class('root_node');
4648
        return $node;
4649
    }
4650
 
4651
    /**
4652
     * This function allows the user to add something to the start of the settings
4653
     * navigation, which means it will be at the top of the settings navigation block
4654
     *
4655
     * @param string $text text to be used for the link.
4656
     * @param string|moodle_url $url url for the new node
4657
     * @param int $type the type of node navigation_node::TYPE_*
4658
     * @param string $shorttext
4659
     * @param string|int $key a key to access the node by.
4660
     * @param pix_icon $icon An icon that appears next to the node.
4661
     * @return navigation_node $node with the new node added to it.
4662
     */
1441 ariadna 4663
    public function prepend($text, $url=null, $type=null, $shorttext=null, $key=null, ?pix_icon $icon=null) {
1 efrain 4664
        $children = $this->children;
4665
        $childrenclass = get_class($children);
4666
        $this->children = new $childrenclass;
4667
        $node = $this->add($text, $url, $type, $shorttext, $key, $icon);
4668
        foreach ($children as $child) {
4669
            $this->children->add($child);
4670
        }
4671
        return $node;
4672
    }
4673
 
4674
    /**
4675
     * Does this page require loading of full admin tree or is
4676
     * it enough rely on AJAX?
4677
     *
4678
     * @return bool
4679
     */
4680
    protected function is_admin_tree_needed() {
4681
        if (self::$loadadmintree) {
4682
            // Usually external admin page or settings page.
4683
            return true;
4684
        }
4685
 
4686
        if ($this->page->pagelayout === 'admin' or strpos($this->page->pagetype, 'admin-') === 0) {
4687
            // Admin settings tree is intended for system level settings and management only, use navigation for the rest!
4688
            if ($this->page->context->contextlevel != CONTEXT_SYSTEM) {
4689
                return false;
4690
            }
4691
            return true;
4692
        }
4693
 
4694
        return false;
4695
    }
4696
 
4697
    /**
4698
     * Load the site administration tree
4699
     *
4700
     * This function loads the site administration tree by using the lib/adminlib library functions
4701
     *
4702
     * @param navigation_node $referencebranch A reference to a branch in the settings
4703
     *      navigation tree
4704
     * @param part_of_admin_tree $adminbranch The branch to add, if null generate the admin
4705
     *      tree and start at the beginning
4706
     * @return mixed A key to access the admin tree by
4707
     */
1441 ariadna 4708
    protected function load_administration_settings(?navigation_node $referencebranch=null, ?part_of_admin_tree $adminbranch=null) {
1 efrain 4709
        global $CFG;
4710
 
4711
        // Check if we are just starting to generate this navigation.
4712
        if ($referencebranch === null) {
4713
 
4714
            // Require the admin lib then get an admin structure
4715
            if (!function_exists('admin_get_root')) {
4716
                require_once($CFG->dirroot.'/lib/adminlib.php');
4717
            }
4718
            $adminroot = admin_get_root(false, false);
4719
            // This is the active section identifier
4720
            $this->adminsection = $this->page->url->param('section');
4721
 
4722
            // Disable the navigation from automatically finding the active node
4723
            navigation_node::$autofindactive = false;
4724
            $referencebranch = $this->add(get_string('administrationsite'), '/admin/search.php', self::TYPE_SITE_ADMIN, null, 'root');
4725
            foreach ($adminroot->children as $adminbranch) {
4726
                $this->load_administration_settings($referencebranch, $adminbranch);
4727
            }
4728
            navigation_node::$autofindactive = true;
4729
 
4730
            // Use the admin structure to locate the active page
4731
            if (!$this->contains_active_node() && $current = $adminroot->locate($this->adminsection, true)) {
4732
                $currentnode = $this;
4733
                while (($pathkey = array_pop($current->path))!==null && $currentnode) {
4734
                    $currentnode = $currentnode->get($pathkey);
4735
                }
4736
                if ($currentnode) {
4737
                    $currentnode->make_active();
4738
                }
4739
            } else {
4740
                $this->scan_for_active_node($referencebranch);
4741
            }
4742
            return $referencebranch;
4743
        } else if ($adminbranch->check_access()) {
4744
            // We have a reference branch that we can access and is not hidden `hurrah`
4745
            // Now we need to display it and any children it may have
4746
            $url = null;
4747
            $icon = null;
4748
 
4749
            if ($adminbranch instanceof \core_admin\local\settings\linkable_settings_page) {
4750
                if (empty($CFG->linkadmincategories) && $adminbranch instanceof admin_category) {
4751
                    $url = null;
4752
                } else {
4753
                    $url = $adminbranch->get_settings_page_url();
4754
                }
4755
            }
4756
 
4757
            // Add the branch
4758
            $reference = $referencebranch->add($adminbranch->visiblename, $url, self::TYPE_SETTING, null, $adminbranch->name, $icon);
4759
 
4760
            if ($adminbranch->is_hidden()) {
4761
                if (($adminbranch instanceof admin_externalpage || $adminbranch instanceof admin_settingpage) && $adminbranch->name == $this->adminsection) {
4762
                    $reference->add_class('hidden');
4763
                } else {
4764
                    $reference->display = false;
4765
                }
4766
            }
4767
 
4768
            // Check if we are generating the admin notifications and whether notificiations exist
4769
            if ($adminbranch->name === 'adminnotifications' && admin_critical_warnings_present()) {
4770
                $reference->add_class('criticalnotification');
4771
            }
4772
            // Check if this branch has children
4773
            if ($reference && isset($adminbranch->children) && is_array($adminbranch->children) && count($adminbranch->children)>0) {
4774
                foreach ($adminbranch->children as $branch) {
4775
                    // Generate the child branches as well now using this branch as the reference
4776
                    $this->load_administration_settings($reference, $branch);
4777
                }
4778
            } else {
4779
                $reference->icon = new pix_icon('i/settings', '');
4780
            }
4781
        }
4782
    }
4783
 
4784
    /**
4785
     * This function recursivily scans nodes until it finds the active node or there
4786
     * are no more nodes.
4787
     * @param navigation_node $node
4788
     */
4789
    protected function scan_for_active_node(navigation_node $node) {
4790
        if (!$node->check_if_active() && $node->children->count()>0) {
4791
            foreach ($node->children as &$child) {
4792
                $this->scan_for_active_node($child);
4793
            }
4794
        }
4795
    }
4796
 
4797
    /**
4798
     * Gets a navigation node given an array of keys that represent the path to
4799
     * the desired node.
4800
     *
4801
     * @param array $path
4802
     * @return navigation_node|false
4803
     */
4804
    protected function get_by_path(array $path) {
4805
        $node = $this->get(array_shift($path));
4806
        foreach ($path as $key) {
4807
            $node->get($key);
4808
        }
4809
        return $node;
4810
    }
4811
 
4812
    /**
4813
     * This function loads the course settings that are available for the user
4814
     *
4815
     * @param bool $forceopen If set to true the course node will be forced open
4816
     * @return navigation_node|false
4817
     */
4818
    protected function load_course_settings($forceopen = false) {
4819
        global $CFG, $USER;
4820
        require_once($CFG->dirroot . '/course/lib.php');
4821
 
4822
        $course = $this->page->course;
4823
        $coursecontext = context_course::instance($course->id);
4824
        $adminoptions = course_get_user_administration_options($course, $coursecontext);
4825
 
4826
        // note: do not test if enrolled or viewing here because we need the enrol link in Course administration section
4827
 
4828
        $coursenode = $this->add(get_string('courseadministration'), null, self::TYPE_COURSE, null, 'courseadmin');
4829
        if ($forceopen) {
4830
            $coursenode->force_open();
4831
        }
4832
 
4833
        // MoodleNet links.
4834
        if ($this->page->user_is_editing()) {
4835
            $this->page->requires->js_call_amd('core/moodlenet/mutations', 'init');
4836
        }
4837
        $usercanshare = utilities::can_user_share($coursecontext, $USER->id, 'course');
4838
        $issuerid = get_config('moodlenet', 'oauthservice');
4839
        try {
4840
            $issuer = \core\oauth2\api::get_issuer($issuerid);
4841
            $isvalidinstance = utilities::is_valid_instance($issuer);
4842
            if ($usercanshare && $isvalidinstance) {
4843
                $this->page->requires->js_call_amd('core/moodlenet/send_resource', 'init');
4844
                $action = new action_link(new moodle_url(''), '', null, [
4845
                    'data-action' => 'sendtomoodlenet',
4846
                    'data-type' => 'course',
4847
                ]);
4848
                // Share course to MoodleNet link.
4849
                $coursenode->add(get_string('moodlenet:sharetomoodlenet', 'moodle'),
4850
                    $action, self::TYPE_SETTING, null, 'exportcoursetomoodlenet')->set_force_into_more_menu(true);
4851
                // MoodleNet share progress link.
4852
                $url = new moodle_url('/moodlenet/shareprogress.php');
4853
                $coursenode->add(get_string('moodlenet:shareprogress'),
4854
                    $url, self::TYPE_SETTING, null, 'moodlenetshareprogress')->set_force_into_more_menu(true);
4855
            }
4856
        } catch (dml_missing_record_exception $e) {
4857
            debugging("Invalid MoodleNet OAuth 2 service set in site administration: 'moodlenet | oauthservice'. " .
4858
                "This must be a valid issuer.");
4859
        }
4860
 
4861
        if ($adminoptions->update) {
4862
            // Add the course settings link
4863
            $url = new moodle_url('/course/edit.php', array('id'=>$course->id));
4864
            $coursenode->add(get_string('settings'), $url, self::TYPE_SETTING, null,
4865
                'editsettings', new pix_icon('i/settings', ''));
4866
        }
4867
 
4868
        if ($adminoptions->editcompletion) {
4869
            // Add the course completion settings link
4870
            $url = new moodle_url('/course/completion.php', array('id' => $course->id));
4871
            $coursenode->add(get_string('coursecompletion', 'completion'), $url, self::TYPE_SETTING, null, 'coursecompletion',
4872
                             new pix_icon('i/settings', ''));
4873
        }
4874
 
4875
        if (!$adminoptions->update && $adminoptions->tags) {
1441 ariadna 4876
            $url = \core\router\util::get_path_for_callable([
4877
                \core_course\route\controller\tags_controller::class,
4878
                'administer_tags',
4879
            ], ['course' => $course->id]);
1 efrain 4880
            $coursenode->add(get_string('coursetags', 'tag'), $url, self::TYPE_SETTING, null, 'coursetags', new pix_icon('i/settings', ''));
4881
            $coursenode->get('coursetags')->set_force_into_more_menu();
4882
        }
4883
 
4884
        // add enrol nodes
4885
        enrol_add_course_navigation($coursenode, $course);
4886
 
4887
        // Manage filters
4888
        if ($adminoptions->filters) {
4889
            $url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id));
4890
            $coursenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING,
4891
                null, 'filtermanagement', new pix_icon('i/filter', ''));
4892
        }
4893
 
4894
        // View course reports.
4895
        if ($adminoptions->reports) {
4896
            $reportnav = $coursenode->add(get_string('reports'),
4897
                new moodle_url('/report/view.php', ['courseid' => $coursecontext->instanceid]),
4898
                self::TYPE_CONTAINER, null, 'coursereports', new pix_icon('i/stats', ''));
4899
            $coursereports = core_component::get_plugin_list('coursereport');
4900
            foreach ($coursereports as $report => $dir) {
4901
                $libfile = $CFG->dirroot.'/course/report/'.$report.'/lib.php';
4902
                if (file_exists($libfile)) {
4903
                    require_once($libfile);
4904
                    $reportfunction = $report.'_report_extend_navigation';
4905
                    if (function_exists($report.'_report_extend_navigation')) {
4906
                        $reportfunction($reportnav, $course, $coursecontext);
4907
                    }
4908
                }
4909
            }
4910
 
4911
            $reports = get_plugin_list_with_function('report', 'extend_navigation_course', 'lib.php');
4912
            foreach ($reports as $reportfunction) {
4913
                $reportfunction($reportnav, $course, $coursecontext);
4914
            }
4915
 
4916
            if (!$reportnav->has_children()) {
4917
                $reportnav->remove();
4918
            }
4919
        }
4920
 
1441 ariadna 4921
        // Grade penalty navigation.
4922
        \core_grades\penalty_manager::extend_navigation_course($coursenode, $course, $coursecontext);
4923
 
1 efrain 4924
        // Check if we can view the gradebook's setup page.
4925
        if ($adminoptions->gradebook) {
4926
            $url = new moodle_url('/grade/edit/tree/index.php', array('id' => $course->id));
4927
            $coursenode->add(get_string('gradebooksetup', 'grades'), $url, self::TYPE_SETTING,
4928
                null, 'gradebooksetup', new pix_icon('i/settings', ''));
4929
        }
4930
 
4931
        // Add the context locking node.
4932
        $this->add_context_locking_node($coursenode, $coursecontext);
4933
 
4934
        //  Add outcome if permitted
4935
        if ($adminoptions->outcomes) {
4936
            $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$course->id));
4937
            $coursenode->add(get_string('outcomes', 'grades'), $url, self::TYPE_SETTING, null, 'outcomes', new pix_icon('i/outcomes', ''));
4938
        }
4939
 
4940
        //Add badges navigation
4941
        if ($adminoptions->badges) {
4942
            require_once($CFG->libdir .'/badgeslib.php');
4943
            badges_add_course_navigation($coursenode, $course);
4944
        }
4945
 
4946
        // Questions
4947
        require_once($CFG->libdir . '/questionlib.php');
1441 ariadna 4948
        $baseurl = \core_question\local\bank\question_bank_helper::get_url_for_qbank_list($course->id);
4949
        question_extend_settings_navigation($coursenode, $coursecontext, $baseurl)->trim_if_empty();
1 efrain 4950
 
4951
        if ($adminoptions->update) {
4952
            // Repository Instances
4953
            if (!$this->cache->cached('contexthasrepos'.$coursecontext->id)) {
4954
                require_once($CFG->dirroot . '/repository/lib.php');
4955
                $editabletypes = repository::get_editable_types($coursecontext);
4956
                $haseditabletypes = !empty($editabletypes);
4957
                unset($editabletypes);
4958
                $this->cache->set('contexthasrepos'.$coursecontext->id, $haseditabletypes);
4959
            } else {
4960
                $haseditabletypes = $this->cache->{'contexthasrepos'.$coursecontext->id};
4961
            }
4962
            if ($haseditabletypes) {
4963
                $url = new moodle_url('/repository/manage_instances.php', array('contextid' => $coursecontext->id));
4964
                $coursenode->add(get_string('repositories'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/repository', ''));
4965
            }
4966
        }
4967
 
4968
        // Manage files
4969
        if ($adminoptions->files) {
4970
            // hidden in new courses and courses where legacy files were turned off
4971
            $url = new moodle_url('/files/index.php', array('contextid'=>$coursecontext->id));
4972
            $coursenode->add(get_string('courselegacyfiles'), $url, self::TYPE_SETTING, null, 'coursefiles', new pix_icon('i/folder', ''));
4973
 
4974
        }
4975
 
4976
        // Let plugins hook into course navigation.
4977
        $pluginsfunction = get_plugins_with_function('extend_navigation_course', 'lib.php');
4978
        foreach ($pluginsfunction as $plugintype => $plugins) {
1441 ariadna 4979
            // Ignore the report and gradepenalty plugins as they were already loaded above.
4980
            if ($plugintype == 'report' || $plugintype == 'gradepenalty') {
1 efrain 4981
                continue;
4982
            }
4983
            foreach ($plugins as $pluginfunction) {
4984
                $pluginfunction($coursenode, $course, $coursecontext);
4985
            }
4986
        }
4987
 
4988
        // Prepare data for course content download functionality if it is enabled.
4989
        if (\core\content::can_export_context($coursecontext, $USER)) {
4990
            $linkattr = \core_course\output\content_export_link::get_attributes($coursecontext);
4991
            $actionlink = new action_link($linkattr->url, $linkattr->displaystring, null, $linkattr->elementattributes);
4992
 
4993
            $coursenode->add($linkattr->displaystring, $actionlink, self::TYPE_SETTING, null, 'download',
4994
                    new pix_icon('t/download', ''));
4995
            $coursenode->get('download')->set_force_into_more_menu(true);
4996
        }
4997
 
4998
        // Course reuse options.
4999
        if ($adminoptions->import
5000
                || $adminoptions->backup
5001
                || $adminoptions->restore
5002
                || $adminoptions->copy
5003
                || $adminoptions->reset) {
5004
            $coursereusenav = $coursenode->add(
5005
                get_string('coursereuse'),
5006
                new moodle_url('/backup/view.php', ['id' => $course->id]),
5007
                self::TYPE_CONTAINER, null, 'coursereuse', new pix_icon('t/edit', ''),
5008
            );
5009
 
5010
            // Import data from other courses.
5011
            if ($adminoptions->import) {
5012
                $url = new moodle_url('/backup/import.php', ['id' => $course->id]);
5013
                $coursereusenav->add(get_string('import'), $url, self::TYPE_SETTING, null, 'import', new pix_icon('i/import', ''));
5014
            }
5015
 
5016
            // Backup this course.
5017
            if ($adminoptions->backup) {
5018
                $url = new moodle_url('/backup/backup.php', ['id' => $course->id]);
5019
                $coursereusenav->add(get_string('backup'), $url, self::TYPE_SETTING, null, 'backup', new pix_icon('i/backup', ''));
5020
            }
5021
 
5022
            // Restore to this course.
5023
            if ($adminoptions->restore) {
5024
                $url = new moodle_url('/backup/restorefile.php', ['contextid' => $coursecontext->id]);
5025
                $coursereusenav->add(
5026
                    get_string('restore'),
5027
                    $url,
5028
                    self::TYPE_SETTING,
5029
                    null,
5030
                    'restore',
5031
                    new pix_icon('i/restore', ''),
5032
                );
5033
            }
5034
 
5035
            // Copy this course.
5036
            if ($adminoptions->copy) {
5037
                $url = new moodle_url('/backup/copy.php', ['id' => $course->id]);
5038
                $coursereusenav->add(get_string('copycourse'), $url, self::TYPE_SETTING, null, 'copy', new pix_icon('t/copy', ''));
5039
            }
5040
 
5041
            // Reset this course.
5042
            if ($adminoptions->reset) {
5043
                $url = new moodle_url('/course/reset.php', ['id' => $course->id]);
5044
                $coursereusenav->add(get_string('reset'), $url, self::TYPE_SETTING, null, 'reset', new pix_icon('i/return', ''));
5045
            }
5046
        }
5047
 
5048
        // Return we are done
5049
        return $coursenode;
5050
    }
5051
 
5052
    /**
5053
     * Get the moodle_page object associated to the current settings navigation.
5054
     *
5055
     * @return moodle_page
5056
     */
5057
    public function get_page(): moodle_page {
5058
        return $this->page;
5059
    }
5060
 
5061
    /**
5062
     * This function calls the module function to inject module settings into the
5063
     * settings navigation tree.
5064
     *
5065
     * This only gets called if there is a corrosponding function in the modules
5066
     * lib file.
5067
     *
5068
     * For examples mod/forum/lib.php {@link forum_extend_settings_navigation()}
5069
     *
5070
     * @return navigation_node|false
5071
     */
5072
    protected function load_module_settings() {
5073
        global $CFG, $USER;
5074
 
5075
        if (!$this->page->cm && $this->context->contextlevel == CONTEXT_MODULE && $this->context->instanceid) {
5076
            $cm = get_coursemodule_from_id(false, $this->context->instanceid, 0, false, MUST_EXIST);
5077
            $this->page->set_cm($cm, $this->page->course);
5078
        }
5079
 
5080
        $file = $CFG->dirroot.'/mod/'.$this->page->activityname.'/lib.php';
5081
        if (file_exists($file)) {
5082
            require_once($file);
5083
        }
5084
 
5085
        $modulenode = $this->add(get_string('pluginadministration', $this->page->activityname), null, self::TYPE_SETTING, null, 'modulesettings');
5086
        $modulenode->nodetype = navigation_node::NODETYPE_BRANCH;
5087
        $modulenode->force_open();
5088
 
5089
        // Settings for the module
5090
        if (has_capability('moodle/course:manageactivities', $this->page->cm->context)) {
5091
            $url = new moodle_url('/course/modedit.php', array('update' => $this->page->cm->id, 'return' => 1));
5092
            $modulenode->add(get_string('settings'), $url, self::TYPE_SETTING, null, 'modedit', new pix_icon('i/settings', ''));
5093
        }
5094
        // Assign local roles
5095
        if (count(get_assignable_roles($this->page->cm->context))>0) {
5096
            $url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->page->cm->context->id));
5097
            $modulenode->add(get_string('localroles', 'role'), $url, self::TYPE_SETTING, null, 'roleassign',
5098
                new pix_icon('i/role', ''));
5099
        }
5100
        // Override roles
5101
        if (has_capability('moodle/role:review', $this->page->cm->context) or count(get_overridable_roles($this->page->cm->context))>0) {
5102
            $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->page->cm->context->id));
5103
            $modulenode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, 'roleoverride',
5104
                new pix_icon('i/permissions', ''));
5105
        }
5106
        // Check role permissions
5107
        if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->page->cm->context)) {
5108
            $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->page->cm->context->id));
5109
            $modulenode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, 'rolecheck',
5110
                new pix_icon('i/checkpermissions', ''));
5111
        }
5112
 
5113
        // Add the context locking node.
5114
        $this->add_context_locking_node($modulenode, $this->page->cm->context);
5115
 
5116
        // Manage filters
5117
        if (has_capability('moodle/filter:manage', $this->page->cm->context) && count(filter_get_available_in_context($this->page->cm->context))>0) {
5118
            $url = new moodle_url('/filter/manage.php', array('contextid'=>$this->page->cm->context->id));
5119
            $modulenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, 'filtermanage',
5120
                new pix_icon('i/filter', ''));
5121
        }
5122
        // Add reports
5123
        $reports = get_plugin_list_with_function('report', 'extend_navigation_module', 'lib.php');
5124
        foreach ($reports as $reportfunction) {
5125
            $reportfunction($modulenode, $this->page->cm);
5126
        }
5127
        // Add a backup link
5128
        $featuresfunc = $this->page->activityname.'_supports';
5129
        if (function_exists($featuresfunc) && $featuresfunc(FEATURE_BACKUP_MOODLE2) && has_capability('moodle/backup:backupactivity', $this->page->cm->context)) {
5130
            $url = new moodle_url('/backup/backup.php', array('id'=>$this->page->cm->course, 'cm'=>$this->page->cm->id));
5131
            $modulenode->add(get_string('backup'), $url, self::TYPE_SETTING, null, 'backup', new pix_icon('i/backup', ''));
5132
        }
5133
 
5134
        // Restore this activity
5135
        $featuresfunc = $this->page->activityname.'_supports';
5136
        if (function_exists($featuresfunc) && $featuresfunc(FEATURE_BACKUP_MOODLE2) && has_capability('moodle/restore:restoreactivity', $this->page->cm->context)) {
5137
            $url = new moodle_url('/backup/restorefile.php', array('contextid'=>$this->page->cm->context->id));
5138
            $modulenode->add(get_string('restore'), $url, self::TYPE_SETTING, null, 'restore', new pix_icon('i/restore', ''));
5139
        }
5140
 
5141
        // Allow the active advanced grading method plugin to append its settings
5142
        $featuresfunc = $this->page->activityname.'_supports';
5143
        if (function_exists($featuresfunc) && $featuresfunc(FEATURE_ADVANCED_GRADING) && has_capability('moodle/grade:managegradingforms', $this->page->cm->context)) {
5144
            require_once($CFG->dirroot.'/grade/grading/lib.php');
5145
            $gradingman = get_grading_manager($this->page->cm->context, 'mod_'.$this->page->activityname);
5146
            $gradingman->extend_settings_navigation($this, $modulenode);
5147
        }
5148
 
5149
        $function = $this->page->activityname.'_extend_settings_navigation';
5150
        if (function_exists($function)) {
5151
            $function($this, $modulenode);
5152
        }
5153
 
5154
        // Send activity to MoodleNet.
5155
        $usercanshare = utilities::can_user_share($this->context->get_course_context(), $USER->id);
5156
        $issuerid = get_config('moodlenet', 'oauthservice');
5157
        try {
5158
            $issuer = \core\oauth2\api::get_issuer($issuerid);
5159
            $isvalidinstance = utilities::is_valid_instance($issuer);
5160
            if ($usercanshare && $isvalidinstance) {
5161
                $this->page->requires->js_call_amd('core/moodlenet/send_resource', 'init');
5162
                $action = new action_link(new moodle_url(''), '', null, [
5163
                    'data-action' => 'sendtomoodlenet',
5164
                    'data-type' => 'activity',
5165
                ]);
5166
                $modulenode->add(get_string('moodlenet:sharetomoodlenet', 'moodle'),
5167
                    $action, self::TYPE_SETTING, null, 'exportmoodlenet')->set_force_into_more_menu(true);
5168
            }
5169
        } catch (dml_missing_record_exception $e) {
5170
            debugging("Invalid MoodleNet OAuth 2 service set in site administration: 'moodlenet | oauthservice'. " .
5171
                "This must be a valid issuer.");
5172
        }
5173
 
5174
        // Remove the module node if there are no children.
5175
        if ($modulenode->children->count() <= 0) {
5176
            $modulenode->remove();
5177
        }
5178
 
5179
        return $modulenode;
5180
    }
5181
 
5182
    /**
5183
     * Loads the user settings block of the settings nav
5184
     *
5185
     * This function is simply works out the userid and whether we need to load
5186
     * just the current users profile settings, or the current user and the user the
5187
     * current user is viewing.
5188
     *
5189
     * This function has some very ugly code to work out the user, if anyone has
5190
     * any bright ideas please feel free to intervene.
5191
     *
5192
     * @param int $courseid The course id of the current course
5193
     * @return navigation_node|false
5194
     */
5195
    protected function load_user_settings($courseid = SITEID) {
5196
        global $USER, $CFG;
5197
 
5198
        if (isguestuser() || !isloggedin()) {
5199
            return false;
5200
        }
5201
 
5202
        $navusers = $this->page->navigation->get_extending_users();
5203
 
5204
        if (count($this->userstoextendfor) > 0 || count($navusers) > 0) {
5205
            $usernode = null;
5206
            foreach ($this->userstoextendfor as $userid) {
5207
                if ($userid == $USER->id) {
5208
                    continue;
5209
                }
5210
                $node = $this->generate_user_settings($courseid, $userid, 'userviewingsettings');
5211
                if (is_null($usernode)) {
5212
                    $usernode = $node;
5213
                }
5214
            }
5215
            foreach ($navusers as $user) {
5216
                if ($user->id == $USER->id) {
5217
                    continue;
5218
                }
5219
                $node = $this->generate_user_settings($courseid, $user->id, 'userviewingsettings');
5220
                if (is_null($usernode)) {
5221
                    $usernode = $node;
5222
                }
5223
            }
5224
            $this->generate_user_settings($courseid, $USER->id);
5225
        } else {
5226
            $usernode = $this->generate_user_settings($courseid, $USER->id);
5227
        }
5228
        return $usernode;
5229
    }
5230
 
5231
    /**
5232
     * Extends the settings navigation for the given user.
5233
     *
5234
     * Note: This method gets called automatically if you call
5235
     * $PAGE->navigation->extend_for_user($userid)
5236
     *
5237
     * @param int $userid
5238
     */
5239
    public function extend_for_user($userid) {
5240
        global $CFG;
5241
 
5242
        if (!in_array($userid, $this->userstoextendfor)) {
5243
            $this->userstoextendfor[] = $userid;
5244
            if ($this->initialised) {
5245
                $this->generate_user_settings($this->page->course->id, $userid, 'userviewingsettings');
5246
                $children = array();
5247
                foreach ($this->children as $child) {
5248
                    $children[] = $child;
5249
                }
5250
                array_unshift($children, array_pop($children));
5251
                $this->children = new navigation_node_collection();
5252
                foreach ($children as $child) {
5253
                    $this->children->add($child);
5254
                }
5255
            }
5256
        }
5257
    }
5258
 
5259
    /**
5260
     * This function gets called by {@link settings_navigation::load_user_settings()} and actually works out
5261
     * what can be shown/done
5262
     *
5263
     * @param int $courseid The current course' id
5264
     * @param int $userid The user id to load for
5265
     * @param string $gstitle The string to pass to get_string for the branch title
5266
     * @return navigation_node|false
5267
     */
5268
    protected function generate_user_settings($courseid, $userid, $gstitle='usercurrentsettings') {
5269
        global $DB, $CFG, $USER, $SITE;
5270
 
5271
        if ($courseid != $SITE->id) {
5272
            if (!empty($this->page->course->id) && $this->page->course->id == $courseid) {
5273
                $course = $this->page->course;
5274
            } else {
5275
                $select = context_helper::get_preload_record_columns_sql('ctx');
5276
                $sql = "SELECT c.*, $select
5277
                          FROM {course} c
5278
                          JOIN {context} ctx ON c.id = ctx.instanceid
5279
                         WHERE c.id = :courseid AND ctx.contextlevel = :contextlevel";
5280
                $params = array('courseid' => $courseid, 'contextlevel' => CONTEXT_COURSE);
5281
                $course = $DB->get_record_sql($sql, $params, MUST_EXIST);
5282
                context_helper::preload_from_record($course);
5283
            }
5284
        } else {
5285
            $course = $SITE;
5286
        }
5287
 
5288
        $coursecontext = context_course::instance($course->id);   // Course context
5289
        $systemcontext   = context_system::instance();
5290
        $currentuser = ($USER->id == $userid);
5291
 
5292
        if ($currentuser) {
5293
            $user = $USER;
5294
            $usercontext = context_user::instance($user->id);       // User context
5295
        } else {
5296
            $select = context_helper::get_preload_record_columns_sql('ctx');
5297
            $sql = "SELECT u.*, $select
5298
                      FROM {user} u
5299
                      JOIN {context} ctx ON u.id = ctx.instanceid
5300
                     WHERE u.id = :userid AND ctx.contextlevel = :contextlevel";
5301
            $params = array('userid' => $userid, 'contextlevel' => CONTEXT_USER);
5302
            $user = $DB->get_record_sql($sql, $params, IGNORE_MISSING);
5303
            if (!$user) {
5304
                return false;
5305
            }
5306
            context_helper::preload_from_record($user);
5307
 
5308
            // Check that the user can view the profile
5309
            $usercontext = context_user::instance($user->id); // User context
5310
            $canviewuser = has_capability('moodle/user:viewdetails', $usercontext);
5311
 
5312
            if ($course->id == $SITE->id) {
5313
                if ($CFG->forceloginforprofiles && !has_coursecontact_role($user->id) && !$canviewuser) {  // Reduce possibility of "browsing" userbase at site level
5314
                    // Teachers can browse and be browsed at site level. If not forceloginforprofiles, allow access (bug #4366)
5315
                    return false;
5316
                }
5317
            } else {
5318
                $canviewusercourse = has_capability('moodle/user:viewdetails', $coursecontext);
5319
                $userisenrolled = is_enrolled($coursecontext, $user->id, '', true);
5320
                if ((!$canviewusercourse && !$canviewuser) || !$userisenrolled) {
5321
                    return false;
5322
                }
5323
                $canaccessallgroups = has_capability('moodle/site:accessallgroups', $coursecontext);
5324
                if (!$canaccessallgroups && groups_get_course_groupmode($course) == SEPARATEGROUPS && !$canviewuser) {
5325
                    // If groups are in use, make sure we can see that group (MDL-45874). That does not apply to parents.
5326
                    if ($courseid == $this->page->course->id) {
5327
                        $mygroups = get_fast_modinfo($this->page->course)->groups;
5328
                    } else {
5329
                        $mygroups = groups_get_user_groups($courseid);
5330
                    }
5331
                    $usergroups = groups_get_user_groups($courseid, $userid);
5332
                    if (!array_intersect_key($mygroups[0], $usergroups[0])) {
5333
                        return false;
5334
                    }
5335
                }
5336
            }
5337
        }
5338
 
5339
        $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $this->page->context));
5340
 
5341
        $key = $gstitle;
5342
        $prefurl = new moodle_url('/user/preferences.php');
5343
        if ($gstitle != 'usercurrentsettings') {
5344
            $key .= $userid;
5345
            $prefurl->param('userid', $userid);
5346
        }
5347
 
5348
        // Add a user setting branch.
5349
        if ($gstitle == 'usercurrentsettings') {
5350
            $mainpage = $this->add(get_string('home'), new moodle_url('/'), self::TYPE_CONTAINER, null, 'site');
5351
 
5352
            // This should be set to false as we don't want to show this to the user. It's only for generating the correct
5353
            // breadcrumb.
5354
            $mainpage->display = false;
5355
            $homepage = get_home_page();
5356
            if (($homepage == HOMEPAGE_MY || $homepage == HOMEPAGE_MYCOURSES)) {
5357
                $mainpage->mainnavonly = true;
5358
            }
5359
 
5360
            $iscurrentuser = ($user->id == $USER->id);
5361
 
5362
            $baseargs = array('id' => $user->id);
5363
            if ($course->id != $SITE->id && !$iscurrentuser) {
5364
                $baseargs['course'] = $course->id;
5365
                $issitecourse = false;
5366
            } else {
5367
                // Load all categories and get the context for the system.
5368
                $issitecourse = true;
5369
            }
5370
 
5371
            // Add the user profile to the dashboard.
5372
            $profilenode = $mainpage->add(get_string('profile'), new moodle_url('/user/profile.php',
5373
                    array('id' => $user->id)), self::TYPE_SETTING, null, 'myprofile');
5374
 
5375
            // Add blog nodes.
5376
            if (!empty($CFG->enableblogs)) {
5377
                if (!$this->cache->cached('userblogoptions'.$user->id)) {
5378
                    require_once($CFG->dirroot.'/blog/lib.php');
5379
                    // Get all options for the user.
5380
                    $options = blog_get_options_for_user($user);
5381
                    $this->cache->set('userblogoptions'.$user->id, $options);
5382
                } else {
5383
                    $options = $this->cache->{'userblogoptions'.$user->id};
5384
                }
5385
 
5386
                if (count($options) > 0) {
5387
                    $blogs = $profilenode->add(get_string('blogs', 'blog'), null, navigation_node::TYPE_CONTAINER);
5388
                    foreach ($options as $type => $option) {
5389
                        if ($type == "rss") {
5390
                            $blogs->add($option['string'], $option['link'], self::TYPE_SETTING, null, null,
5391
                                    new pix_icon('i/rss', ''));
5392
                        } else {
5393
                            $blogs->add($option['string'], $option['link'], self::TYPE_SETTING, null, 'blog' . $type);
5394
                        }
5395
                    }
5396
                }
5397
            }
5398
 
5399
            // Add the messages link.
5400
            // It is context based so can appear in the user's profile and in course participants information.
5401
            if (!empty($CFG->messaging)) {
5402
                $messageargs = array('user1' => $USER->id);
5403
                if ($USER->id != $user->id) {
5404
                    $messageargs['user2'] = $user->id;
5405
                }
5406
                $url = new moodle_url('/message/index.php', $messageargs);
5407
                $mainpage->add(get_string('messages', 'message'), $url, self::TYPE_SETTING, null, 'messages');
5408
            }
5409
 
5410
            // Add the "My private files" link.
5411
            // This link doesn't have a unique display for course context so only display it under the user's profile.
5412
            if ($issitecourse && $iscurrentuser && has_capability('moodle/user:manageownfiles', $usercontext)) {
5413
                $url = new moodle_url('/user/files.php');
5414
                $mainpage->add(get_string('privatefiles'), $url, self::TYPE_SETTING, null, 'privatefiles');
5415
            }
5416
 
5417
            // Add a node to view the users notes if permitted.
5418
            if (!empty($CFG->enablenotes) &&
5419
                    has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $coursecontext)) {
5420
                $url = new moodle_url('/notes/index.php', array('user' => $user->id));
5421
                if ($coursecontext->instanceid != SITEID) {
5422
                    $url->param('course', $coursecontext->instanceid);
5423
                }
5424
                $profilenode->add(get_string('notes', 'notes'), $url);
5425
            }
5426
 
5427
            // Show the grades node.
5428
            if (($issitecourse && $iscurrentuser) || has_capability('moodle/user:viewdetails', $usercontext)) {
5429
                require_once($CFG->dirroot . '/user/lib.php');
5430
                // Set the grades node to link to the "Grades" page.
5431
                if ($course->id == SITEID) {
5432
                    $url = user_mygrades_url($user->id, $course->id);
5433
                } else { // Otherwise we are in a course and should redirect to the user grade report (Activity report version).
5434
                    $url = new moodle_url('/course/user.php', array('mode' => 'grade', 'id' => $course->id, 'user' => $user->id));
5435
                }
5436
                $mainpage->add(get_string('grades', 'grades'), $url, self::TYPE_SETTING, null, 'mygrades');
5437
            }
5438
 
5439
            // Let plugins hook into user navigation.
5440
            $pluginsfunction = get_plugins_with_function('extend_navigation_user', 'lib.php');
5441
            foreach ($pluginsfunction as $plugintype => $plugins) {
5442
                if ($plugintype != 'report') {
5443
                    foreach ($plugins as $pluginfunction) {
5444
                        $pluginfunction($profilenode, $user, $usercontext, $course, $coursecontext);
5445
                    }
5446
                }
5447
            }
5448
 
5449
            $usersetting = navigation_node::create(get_string('preferences', 'moodle'), $prefurl, self::TYPE_CONTAINER, null, $key);
5450
            $mainpage->add_node($usersetting);
5451
        } else {
5452
            $usersetting = $this->add(get_string('preferences', 'moodle'), $prefurl, self::TYPE_CONTAINER, null, $key);
5453
            $usersetting->display = false;
5454
        }
5455
        $usersetting->id = 'usersettings';
5456
 
5457
        // Check if the user has been deleted.
5458
        if ($user->deleted) {
5459
            if (!has_capability('moodle/user:update', $coursecontext)) {
5460
                // We can't edit the user so just show the user deleted message.
5461
                $usersetting->add(get_string('userdeleted'), null, self::TYPE_SETTING);
5462
            } else {
5463
                // We can edit the user so show the user deleted message and link it to the profile.
5464
                if ($course->id == $SITE->id) {
5465
                    $profileurl = new moodle_url('/user/profile.php', array('id'=>$user->id));
5466
                } else {
5467
                    $profileurl = new moodle_url('/user/view.php', array('id'=>$user->id, 'course'=>$course->id));
5468
                }
5469
                $usersetting->add(get_string('userdeleted'), $profileurl, self::TYPE_SETTING);
5470
            }
5471
            return true;
5472
        }
5473
 
5474
        $userauthplugin = false;
5475
        if (!empty($user->auth)) {
5476
            $userauthplugin = get_auth_plugin($user->auth);
5477
        }
5478
 
5479
        $useraccount = $usersetting->add(get_string('useraccount'), null, self::TYPE_CONTAINER, null, 'useraccount');
5480
 
5481
        // Add the profile edit link.
5482
        if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
5483
            if (($currentuser || is_siteadmin($USER) || !is_siteadmin($user)) &&
5484
                    has_capability('moodle/user:update', $systemcontext)) {
5485
                $url = new moodle_url('/user/editadvanced.php', array('id'=>$user->id, 'course'=>$course->id));
5486
                $useraccount->add(get_string('editmyprofile'), $url, self::TYPE_SETTING, null, 'editprofile');
5487
            } else if ((has_capability('moodle/user:editprofile', $usercontext) && !is_siteadmin($user)) ||
5488
                    ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext))) {
5489
                if ($userauthplugin && $userauthplugin->can_edit_profile()) {
5490
                    $url = $userauthplugin->edit_profile_url();
5491
                    if (empty($url)) {
5492
                        $url = new moodle_url('/user/edit.php', array('id'=>$user->id, 'course'=>$course->id));
5493
                    }
5494
                    $useraccount->add(get_string('editmyprofile'), $url, self::TYPE_SETTING, null, 'editprofile');
5495
                }
5496
            }
5497
        }
5498
 
5499
        // Change password link.
5500
        if ($userauthplugin && $currentuser && !\core\session\manager::is_loggedinas() && !isguestuser() &&
5501
                has_capability('moodle/user:changeownpassword', $systemcontext) && $userauthplugin->can_change_password()) {
5502
            $passwordchangeurl = $userauthplugin->change_password_url();
5503
            if (empty($passwordchangeurl)) {
5504
                $passwordchangeurl = new moodle_url('/login/change_password.php', array('id'=>$course->id));
5505
            }
5506
            $useraccount->add(get_string("changepassword"), $passwordchangeurl, self::TYPE_SETTING, null, 'changepassword');
5507
        }
5508
 
5509
        // Default homepage.
5510
        $defaulthomepageuser = (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_USER));
5511
        if (isloggedin() && !isguestuser($user) && $defaulthomepageuser) {
5512
            if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) ||
5513
                    has_capability('moodle/user:editprofile', $usercontext)) {
5514
                $url = new moodle_url('/user/defaulthomepage.php', ['id' => $user->id]);
5515
                $useraccount->add(get_string('defaulthomepageuser'), $url, self::TYPE_SETTING, null, 'defaulthomepageuser');
5516
            }
5517
        }
5518
 
5519
        if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
5520
            if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) ||
5521
                    has_capability('moodle/user:editprofile', $usercontext)) {
5522
                $url = new moodle_url('/user/language.php', array('id' => $user->id, 'course' => $course->id));
5523
                $useraccount->add(get_string('preferredlanguage'), $url, self::TYPE_SETTING, null, 'preferredlanguage');
5524
            }
5525
        }
5526
        $pluginmanager = core_plugin_manager::instance();
5527
        $enabled = $pluginmanager->get_enabled_plugins('mod');
5528
        if (isset($enabled['forum']) && isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
5529
            if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) ||
5530
                    has_capability('moodle/user:editprofile', $usercontext)) {
5531
                $url = new moodle_url('/user/forum.php', array('id' => $user->id, 'course' => $course->id));
5532
                $useraccount->add(get_string('forumpreferences'), $url, self::TYPE_SETTING);
5533
            }
5534
        }
5535
        $editors = editors_get_enabled();
5536
        if (count($editors) > 1) {
5537
            if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
5538
                if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) ||
5539
                        has_capability('moodle/user:editprofile', $usercontext)) {
5540
                    $url = new moodle_url('/user/editor.php', array('id' => $user->id, 'course' => $course->id));
5541
                    $useraccount->add(get_string('editorpreferences'), $url, self::TYPE_SETTING);
5542
                }
5543
            }
5544
        }
5545
 
5546
        // Add "Calendar preferences" link.
5547
        if (isloggedin() && !isguestuser($user)) {
5548
            if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) ||
5549
                    has_capability('moodle/user:editprofile', $usercontext)) {
5550
                $url = new moodle_url('/user/calendar.php', array('id' => $user->id));
5551
                $useraccount->add(get_string('calendarpreferences', 'calendar'), $url, self::TYPE_SETTING, null, 'preferredcalendar');
5552
            }
5553
        }
5554
 
5555
        // Add "Content bank preferences" link.
5556
        if (isloggedin() && !isguestuser($user)) {
5557
            if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) ||
5558
                has_capability('moodle/user:editprofile', $usercontext)) {
5559
                $url = new moodle_url('/user/contentbank.php', ['id' => $user->id]);
5560
                $useraccount->add(get_string('contentbankpreferences', 'core_contentbank'), $url, self::TYPE_SETTING,
5561
                        null, 'contentbankpreferences');
5562
            }
5563
        }
5564
 
5565
        // View the roles settings.
5566
        if (has_any_capability(['moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override',
5567
                'moodle/role:manage'], $usercontext)) {
5568
            $roles = $usersetting->add(get_string('roles'), null, self::TYPE_SETTING);
5569
 
5570
            $url = new moodle_url('/admin/roles/usersroles.php', ['userid' => $user->id, 'courseid' => $course->id]);
5571
            $roles->add(get_string('thisusersroles', 'role'), $url, self::TYPE_SETTING);
5572
 
5573
            $assignableroles = get_assignable_roles($usercontext, ROLENAME_BOTH);
5574
 
5575
            if (!empty($assignableroles)) {
5576
                $url = new moodle_url('/admin/roles/assign.php',
5577
                        array('contextid' => $usercontext->id, 'userid' => $user->id, 'courseid' => $course->id));
5578
                $roles->add(get_string('assignrolesrelativetothisuser', 'role'), $url, self::TYPE_SETTING);
5579
            }
5580
 
5581
            if (has_capability('moodle/role:review', $usercontext) || count(get_overridable_roles($usercontext, ROLENAME_BOTH))>0) {
5582
                $url = new moodle_url('/admin/roles/permissions.php',
5583
                        array('contextid' => $usercontext->id, 'userid' => $user->id, 'courseid' => $course->id));
5584
                $roles->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
5585
            }
5586
 
5587
            $url = new moodle_url('/admin/roles/check.php',
5588
                    array('contextid' => $usercontext->id, 'userid' => $user->id, 'courseid' => $course->id));
5589
            $roles->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
5590
        }
5591
 
5592
        // Repositories.
5593
        if (!$this->cache->cached('contexthasrepos'.$usercontext->id)) {
5594
            require_once($CFG->dirroot . '/repository/lib.php');
5595
            $editabletypes = repository::get_editable_types($usercontext);
5596
            $haseditabletypes = !empty($editabletypes);
5597
            unset($editabletypes);
5598
            $this->cache->set('contexthasrepos'.$usercontext->id, $haseditabletypes);
5599
        } else {
5600
            $haseditabletypes = $this->cache->{'contexthasrepos'.$usercontext->id};
5601
        }
5602
        if ($haseditabletypes) {
5603
            $repositories = $usersetting->add(get_string('repositories', 'repository'), null, self::TYPE_SETTING);
5604
            $repositories->add(get_string('manageinstances', 'repository'), new moodle_url('/repository/manage_instances.php',
5605
                array('contextid' => $usercontext->id)));
5606
        }
5607
 
5608
        // Portfolio.
5609
        if ($currentuser && !empty($CFG->enableportfolios) && has_capability('moodle/portfolio:export', $systemcontext)) {
5610
            require_once($CFG->libdir . '/portfoliolib.php');
5611
            if (portfolio_has_visible_instances()) {
5612
                $portfolio = $usersetting->add(get_string('portfolios', 'portfolio'), null, self::TYPE_SETTING);
5613
 
5614
                $url = new moodle_url('/user/portfolio.php', array('courseid'=>$course->id));
5615
                $portfolio->add(get_string('configure', 'portfolio'), $url, self::TYPE_SETTING);
5616
 
5617
                $url = new moodle_url('/user/portfoliologs.php', array('courseid'=>$course->id));
5618
                $portfolio->add(get_string('logs', 'portfolio'), $url, self::TYPE_SETTING);
5619
            }
5620
        }
5621
 
5622
        $enablemanagetokens = false;
5623
        if (!empty($CFG->enablerssfeeds)) {
5624
            $enablemanagetokens = true;
5625
        } else if (!is_siteadmin($USER->id)
5626
             && !empty($CFG->enablewebservices)
5627
             && has_capability('moodle/webservice:createtoken', context_system::instance()) ) {
5628
            $enablemanagetokens = true;
5629
        }
5630
        // Security keys.
5631
        if ($currentuser && $enablemanagetokens) {
5632
            $url = new moodle_url('/user/managetoken.php');
5633
            $useraccount->add(get_string('securitykeys', 'webservice'), $url, self::TYPE_SETTING);
5634
        }
5635
 
5636
        // Messaging.
5637
        if (($currentuser && has_capability('moodle/user:editownmessageprofile', $systemcontext)) || (!isguestuser($user) &&
5638
                has_capability('moodle/user:editmessageprofile', $usercontext) && !is_primary_admin($user->id))) {
5639
            $messagingurl = new moodle_url('/message/edit.php', array('id' => $user->id));
5640
            $notificationsurl = new moodle_url('/message/notificationpreferences.php', array('userid' => $user->id));
5641
            $useraccount->add(get_string('messagepreferences', 'message'), $messagingurl, self::TYPE_SETTING);
5642
            $useraccount->add(get_string('notificationpreferences', 'message'), $notificationsurl, self::TYPE_SETTING);
5643
        }
5644
 
5645
        // Blogs.
5646
        if ($currentuser && !empty($CFG->enableblogs)) {
5647
            $blog = $usersetting->add(get_string('blogs', 'blog'), null, navigation_node::TYPE_CONTAINER, null, 'blogs');
5648
            if (has_capability('moodle/blog:view', $systemcontext)) {
5649
                $blog->add(get_string('preferences', 'blog'), new moodle_url('/blog/preferences.php'),
5650
                        navigation_node::TYPE_SETTING);
5651
            }
5652
            if (!empty($CFG->useexternalblogs) && $CFG->maxexternalblogsperuser > 0 &&
5653
                    has_capability('moodle/blog:manageexternal', $systemcontext)) {
5654
                $blog->add(get_string('externalblogs', 'blog'), new moodle_url('/blog/external_blogs.php'),
5655
                        navigation_node::TYPE_SETTING);
5656
                $blog->add(get_string('addnewexternalblog', 'blog'), new moodle_url('/blog/external_blog_edit.php'),
5657
                        navigation_node::TYPE_SETTING);
5658
            }
5659
            // Remove the blog node if empty.
5660
            $blog->trim_if_empty();
5661
        }
5662
 
5663
        // Badges.
5664
        if ($currentuser && !empty($CFG->enablebadges)) {
5665
            $badges = $usersetting->add(get_string('badges'), null, navigation_node::TYPE_CONTAINER, null, 'badges');
5666
            if (has_capability('moodle/badges:manageownbadges', $usercontext)) {
5667
                $url = new moodle_url('/badges/mybadges.php');
5668
                $badges->add(get_string('managebadges', 'badges'), $url, self::TYPE_SETTING);
5669
            }
5670
            $badges->add(get_string('preferences', 'badges'), new moodle_url('/badges/preferences.php'),
5671
                    navigation_node::TYPE_SETTING);
5672
            if (!empty($CFG->badges_allowexternalbackpack)) {
5673
                $badges->add(get_string('backpackdetails', 'badges'), new moodle_url('/badges/mybackpack.php'),
5674
                        navigation_node::TYPE_SETTING);
5675
            }
5676
        }
5677
 
5678
        // Let plugins hook into user settings navigation.
5679
        $pluginsfunction = get_plugins_with_function('extend_navigation_user_settings', 'lib.php');
5680
        foreach ($pluginsfunction as $plugintype => $plugins) {
5681
            foreach ($plugins as $pluginfunction) {
5682
                $pluginfunction($usersetting, $user, $usercontext, $course, $coursecontext);
5683
            }
5684
        }
5685
 
5686
        return $usersetting;
5687
    }
5688
 
5689
    /**
5690
     * Loads block specific settings in the navigation
5691
     *
5692
     * @return navigation_node
5693
     */
5694
    protected function load_block_settings() {
5695
        global $CFG;
5696
 
5697
        $blocknode = $this->add($this->context->get_context_name(), null, self::TYPE_SETTING, null, 'blocksettings');
5698
        $blocknode->force_open();
5699
 
5700
        // Assign local roles
5701
        if (get_assignable_roles($this->context, ROLENAME_ORIGINAL)) {
5702
            $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $this->context->id));
5703
            $blocknode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING, null,
5704
                'roles', new pix_icon('i/assignroles', ''));
5705
        }
5706
 
5707
        // Override roles
5708
        if (has_capability('moodle/role:review', $this->context) or  count(get_overridable_roles($this->context))>0) {
5709
            $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->context->id));
5710
            $blocknode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null,
5711
                'permissions', new pix_icon('i/permissions', ''));
5712
        }
5713
        // Check role permissions
5714
        if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->context)) {
5715
            $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->context->id));
5716
            $blocknode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null,
5717
                'checkpermissions', new pix_icon('i/checkpermissions', ''));
5718
        }
5719
 
5720
        // Add the context locking node.
5721
        $this->add_context_locking_node($blocknode, $this->context);
5722
 
5723
        return $blocknode;
5724
    }
5725
 
5726
    /**
5727
     * Loads category specific settings in the navigation
5728
     *
5729
     * @return navigation_node
5730
     */
5731
    protected function load_category_settings() {
5732
        global $CFG;
5733
 
5734
        // We can land here while being in the context of a block, in which case we
5735
        // should get the parent context which should be the category one. See self::initialise().
5736
        if ($this->context->contextlevel == CONTEXT_BLOCK) {
5737
            $catcontext = $this->context->get_parent_context();
5738
        } else {
5739
            $catcontext = $this->context;
5740
        }
5741
 
5742
        // Let's make sure that we always have the right context when getting here.
5743
        if ($catcontext->contextlevel != CONTEXT_COURSECAT) {
5744
            throw new coding_exception('Unexpected context while loading category settings.');
5745
        }
5746
 
5747
        $categorynodetype = navigation_node::TYPE_CONTAINER;
5748
        $categorynode = $this->add($catcontext->get_context_name(), null, $categorynodetype, null, 'categorysettings');
5749
        $categorynode->nodetype = navigation_node::NODETYPE_BRANCH;
5750
        $categorynode->force_open();
5751
 
5752
        if (can_edit_in_category($catcontext->instanceid)) {
5753
            $url = new moodle_url('/course/management.php', array('categoryid' => $catcontext->instanceid));
5754
            $editstring = get_string('managecategorythis');
5755
            $node = $categorynode->add($editstring, $url, self::TYPE_SETTING, null, 'managecategory', new pix_icon('i/edit', ''));
5756
            $node->set_show_in_secondary_navigation(false);
5757
        }
5758
 
5759
        if (has_capability('moodle/category:manage', $catcontext)) {
5760
            $editurl = new moodle_url('/course/editcategory.php', array('id' => $catcontext->instanceid));
5761
            $categorynode->add(get_string('settings'), $editurl, self::TYPE_SETTING, null, 'edit', new pix_icon('i/edit', ''));
5762
 
5763
            $addsubcaturl = new moodle_url('/course/editcategory.php', array('parent' => $catcontext->instanceid));
5764
            $categorynode->add(get_string('addsubcategory'), $addsubcaturl, self::TYPE_SETTING, null,
5765
                'addsubcat', new pix_icon('i/withsubcat', ''))->set_show_in_secondary_navigation(false);
5766
        }
5767
 
5768
        // Assign local roles
5769
        $assignableroles = get_assignable_roles($catcontext);
5770
        if (!empty($assignableroles)) {
5771
            $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $catcontext->id));
5772
            $categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING, null, 'roles', new pix_icon('i/assignroles', ''));
5773
        }
5774
 
5775
        // Override roles
5776
        if (has_capability('moodle/role:review', $catcontext) or count(get_overridable_roles($catcontext)) > 0) {
5777
            $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid' => $catcontext->id));
5778
            $categorynode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, 'permissions', new pix_icon('i/permissions', ''));
5779
        }
5780
        // Check role permissions
5781
        if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride',
5782
                'moodle/role:override', 'moodle/role:assign'), $catcontext)) {
5783
            $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid' => $catcontext->id));
5784
            $categorynode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, 'rolecheck', new pix_icon('i/checkpermissions', ''));
5785
        }
5786
 
5787
        // Add the context locking node.
5788
        $this->add_context_locking_node($categorynode, $catcontext);
5789
 
5790
        // Cohorts
5791
        if (has_any_capability(array('moodle/cohort:view', 'moodle/cohort:manage'), $catcontext)) {
5792
            $categorynode->add(get_string('cohorts', 'cohort'), new moodle_url('/cohort/index.php',
5793
                array('contextid' => $catcontext->id)), self::TYPE_SETTING, null, 'cohort', new pix_icon('i/cohort', ''));
5794
        }
5795
 
5796
        // Manage filters
5797
        if (has_capability('moodle/filter:manage', $catcontext) && count(filter_get_available_in_context($catcontext)) > 0) {
5798
            $url = new moodle_url('/filter/manage.php', array('contextid' => $catcontext->id));
5799
            $categorynode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, 'filters', new pix_icon('i/filter', ''));
5800
        }
5801
 
5802
        // Restore.
5803
        if (has_capability('moodle/restore:restorecourse', $catcontext)) {
5804
            $url = new moodle_url('/backup/restorefile.php', array('contextid' => $catcontext->id));
5805
            $categorynode->add(get_string('restorecourse', 'admin'), $url, self::TYPE_SETTING, null, 'restorecourse', new pix_icon('i/restore', ''));
5806
        }
5807
 
5808
        // Let plugins hook into category settings navigation.
5809
        $pluginsfunction = get_plugins_with_function('extend_navigation_category_settings', 'lib.php');
5810
        foreach ($pluginsfunction as $plugintype => $plugins) {
5811
            foreach ($plugins as $pluginfunction) {
5812
                $pluginfunction($categorynode, $catcontext);
5813
            }
5814
        }
5815
 
5816
        $cb = new contentbank();
5817
        if ($cb->is_context_allowed($catcontext)
5818
            && has_capability('moodle/contentbank:access', $catcontext)) {
5819
            $url = new \moodle_url('/contentbank/index.php', ['contextid' => $catcontext->id]);
5820
            $categorynode->add(get_string('contentbank'), $url, self::TYPE_CUSTOM, null,
5821
                'contentbank', new \pix_icon('i/contentbank', ''));
5822
        }
5823
 
5824
        return $categorynode;
5825
    }
5826
 
5827
    /**
5828
     * Determine whether the user is assuming another role
5829
     *
5830
     * This function checks to see if the user is assuming another role by means of
5831
     * role switching. In doing this we compare each RSW key (context path) against
5832
     * the current context path. This ensures that we can provide the switching
5833
     * options against both the course and any page shown under the course.
5834
     *
5835
     * @return bool|int The role(int) if the user is in another role, false otherwise
5836
     */
5837
    protected function in_alternative_role() {
5838
        global $USER;
5839
        if (!empty($USER->access['rsw']) && is_array($USER->access['rsw'])) {
5840
            if (!empty($this->page->context) && !empty($USER->access['rsw'][$this->page->context->path])) {
5841
                return $USER->access['rsw'][$this->page->context->path];
5842
            }
5843
            foreach ($USER->access['rsw'] as $key=>$role) {
5844
                if (strpos($this->context->path,$key)===0) {
5845
                    return $role;
5846
                }
5847
            }
5848
        }
5849
        return false;
5850
    }
5851
 
5852
    /**
5853
     * This function loads all of the front page settings into the settings navigation.
5854
     * This function is called when the user is on the front page, or $COURSE==$SITE
5855
     * @param bool $forceopen (optional)
5856
     * @return navigation_node
5857
     */
5858
    protected function load_front_page_settings($forceopen = false) {
5859
        global $SITE, $CFG;
5860
        require_once($CFG->dirroot . '/course/lib.php');
5861
 
5862
        $course = clone($SITE);
5863
        $coursecontext = context_course::instance($course->id);   // Course context
5864
        $adminoptions = course_get_user_administration_options($course, $coursecontext);
5865
 
5866
        $frontpage = $this->add(get_string('frontpagesettings'), null, self::TYPE_SETTING, null, 'frontpage');
5867
        if ($forceopen) {
5868
            $frontpage->force_open();
5869
        }
5870
        $frontpage->id = 'frontpagesettings';
5871
 
5872
        if ($this->page->user_allowed_editing() && !$this->page->theme->haseditswitch) {
5873
 
5874
            // Add the turn on/off settings
5875
            $url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
5876
            if ($this->page->user_is_editing()) {
5877
                $url->param('edit', 'off');
5878
                $editstring = get_string('turneditingoff');
5879
            } else {
5880
                $url->param('edit', 'on');
5881
                $editstring = get_string('turneditingon');
5882
            }
5883
            $frontpage->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
5884
        }
5885
 
5886
        if ($adminoptions->update) {
5887
            // Add the course settings link
5888
            $url = new moodle_url('/admin/settings.php', array('section'=>'frontpagesettings'));
5889
            $frontpage->add(get_string('settings'), $url, self::TYPE_SETTING, null,
5890
                'editsettings', new pix_icon('i/settings', ''));
5891
        }
5892
 
5893
        // add enrol nodes
5894
        enrol_add_course_navigation($frontpage, $course);
5895
 
5896
        // Manage filters
5897
        if ($adminoptions->filters) {
5898
            $url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id));
5899
            $frontpage->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING,
5900
                null, 'filtermanagement', new pix_icon('i/filter', ''));
5901
        }
5902
 
5903
        // View course reports.
5904
        if ($adminoptions->reports) {
5905
            $frontpagenav = $frontpage->add(get_string('reports'), new moodle_url('/report/view.php',
5906
                ['courseid' => $coursecontext->instanceid]),
5907
                self::TYPE_CONTAINER, null, 'coursereports',
5908
                    new pix_icon('i/stats', ''));
5909
            $coursereports = core_component::get_plugin_list('coursereport');
5910
            foreach ($coursereports as $report=>$dir) {
5911
                $libfile = $CFG->dirroot.'/course/report/'.$report.'/lib.php';
5912
                if (file_exists($libfile)) {
5913
                    require_once($libfile);
5914
                    $reportfunction = $report.'_report_extend_navigation';
5915
                    if (function_exists($report.'_report_extend_navigation')) {
5916
                        $reportfunction($frontpagenav, $course, $coursecontext);
5917
                    }
5918
                }
5919
            }
5920
 
5921
            $reports = get_plugin_list_with_function('report', 'extend_navigation_course', 'lib.php');
5922
            foreach ($reports as $reportfunction) {
5923
                $reportfunction($frontpagenav, $course, $coursecontext);
5924
            }
5925
 
5926
            if (!$frontpagenav->has_children()) {
5927
                $frontpagenav->remove();
5928
            }
5929
        }
5930
 
5931
        // Questions
5932
        require_once($CFG->libdir . '/questionlib.php');
1441 ariadna 5933
        $baseurl = \core_question\local\bank\question_bank_helper::get_url_for_qbank_list($course->id);
5934
        question_extend_settings_navigation($frontpage, $coursecontext, $baseurl)->trim_if_empty();
1 efrain 5935
 
5936
        // Manage files
5937
        if ($adminoptions->files) {
5938
            //hiden in new installs
5939
            $url = new moodle_url('/files/index.php', array('contextid'=>$coursecontext->id));
5940
            $frontpage->add(get_string('sitelegacyfiles'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/folder', ''));
5941
        }
5942
 
5943
        // Let plugins hook into frontpage navigation.
5944
        $pluginsfunction = get_plugins_with_function('extend_navigation_frontpage', 'lib.php');
5945
        foreach ($pluginsfunction as $plugintype => $plugins) {
5946
            foreach ($plugins as $pluginfunction) {
5947
                $pluginfunction($frontpage, $course, $coursecontext);
5948
            }
5949
        }
5950
 
5951
        // Course reuse options.
5952
        if ($adminoptions->backup || $adminoptions->restore) {
5953
            $coursereusenav = $frontpage->add(
5954
                get_string('coursereuse'),
5955
                new moodle_url('/backup/view.php', ['id' => $course->id]),
5956
                self::TYPE_CONTAINER, null, 'coursereuse', new pix_icon('t/edit', ''),
5957
            );
5958
 
5959
            // Backup this course.
5960
            if ($adminoptions->backup) {
5961
                $url = new moodle_url('/backup/backup.php', ['id' => $course->id]);
5962
                $coursereusenav->add(get_string('backup'), $url, self::TYPE_SETTING, null, 'backup', new pix_icon('i/backup', ''));
5963
            }
5964
 
5965
            // Restore to this course.
5966
            if ($adminoptions->restore) {
5967
                $url = new moodle_url('/backup/restorefile.php', ['contextid' => $coursecontext->id]);
5968
                $coursereusenav->add(
5969
                    get_string('restore'),
5970
                    $url,
5971
                    self::TYPE_SETTING,
5972
                    null,
5973
                    'restore',
5974
                    new pix_icon('i/restore', ''),
5975
                );
5976
            }
5977
        }
5978
 
5979
        return $frontpage;
5980
    }
5981
 
5982
    /**
5983
     * This function gives local plugins an opportunity to modify the settings navigation.
5984
     */
5985
    protected function load_local_plugin_settings() {
5986
 
5987
        foreach (get_plugin_list_with_function('local', 'extend_settings_navigation') as $function) {
5988
            $function($this, $this->context);
5989
        }
5990
    }
5991
 
5992
    /**
5993
     * This function marks the cache as volatile so it is cleared during shutdown
5994
     */
5995
    public function clear_cache() {
5996
        $this->cache->volatile();
5997
    }
5998
 
5999
    /**
6000
     * Checks to see if there are child nodes available in the specific user's preference node.
6001
     * If so, then they have the appropriate permissions view this user's preferences.
6002
     *
6003
     * @since Moodle 2.9.3
6004
     * @param int $userid The user's ID.
6005
     * @return bool True if child nodes exist to view, otherwise false.
6006
     */
6007
    public function can_view_user_preferences($userid) {
6008
        if (is_siteadmin()) {
6009
            return true;
6010
        }
6011
        // See if any nodes are present in the preferences section for this user.
6012
        $preferencenode = $this->find('userviewingsettings' . $userid, null);
6013
        if ($preferencenode && $preferencenode->has_children()) {
6014
            // Run through each child node.
6015
            foreach ($preferencenode->children as $childnode) {
6016
                // If the child node has children then this user has access to a link in the preferences page.
6017
                if ($childnode->has_children()) {
6018
                    return true;
6019
                }
6020
            }
6021
        }
6022
        // No links found for the user to access on the preferences page.
6023
        return false;
6024
    }
6025
}
6026
 
6027
/**
6028
 * Class used to populate site admin navigation for ajax.
6029
 *
6030
 * @package   core
6031
 * @category  navigation
6032
 * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
6033
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6034
 */
6035
class settings_navigation_ajax extends settings_navigation {
6036
    /**
6037
     * Constructs the navigation for use in an AJAX request
6038
     *
6039
     * @param moodle_page $page
6040
     */
6041
    public function __construct(moodle_page &$page) {
6042
        $this->page = $page;
6043
        $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
6044
        $this->children = new navigation_node_collection();
6045
        $this->initialise();
6046
    }
6047
 
6048
    /**
6049
     * Initialise the site admin navigation.
6050
     */
6051
    public function initialise() {
6052
        if ($this->initialised || during_initial_install()) {
6053
            return false;
6054
        }
6055
        $this->context = $this->page->context;
6056
        $this->load_administration_settings();
6057
 
6058
        // Check if local plugins is adding node to site admin.
6059
        $this->load_local_plugin_settings();
6060
 
6061
        $this->initialised = true;
6062
    }
6063
}
6064
 
6065
/**
6066
 * Simple class used to output a navigation branch in XML
6067
 *
6068
 * @package   core
6069
 * @category  navigation
6070
 * @copyright 2009 Sam Hemelryk
6071
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6072
 */
6073
class navigation_json {
6074
    /** @var array An array of different node types */
6075
    protected $nodetype = array('node','branch');
6076
    /** @var array An array of node keys and types */
6077
    protected $expandable = array();
6078
    /**
6079
     * Turns a branch and all of its children into XML
6080
     *
6081
     * @param navigation_node $branch
6082
     * @return string XML string
6083
     */
6084
    public function convert($branch) {
6085
        $xml = $this->convert_child($branch);
6086
        return $xml;
6087
    }
6088
    /**
6089
     * Set the expandable items in the array so that we have enough information
6090
     * to attach AJAX events
6091
     * @param array $expandable
6092
     */
6093
    public function set_expandable($expandable) {
6094
        foreach ($expandable as $node) {
6095
            $this->expandable[$node['key'].':'.$node['type']] = $node;
6096
        }
6097
    }
6098
    /**
6099
     * Recusively converts a child node and its children to XML for output
6100
     *
6101
     * @param navigation_node $child The child to convert
6102
     * @param int $depth Pointlessly used to track the depth of the XML structure
6103
     * @return string JSON
6104
     */
6105
    protected function convert_child($child, $depth=1) {
6106
        if (!$child->display) {
6107
            return '';
6108
        }
6109
        $attributes = array();
6110
        $attributes['id'] = $child->id;
6111
        $attributes['name'] = (string)$child->text; // This can be lang_string object so typecast it.
6112
        $attributes['type'] = $child->type;
6113
        $attributes['key'] = $child->key;
6114
        $attributes['class'] = $child->get_css_type();
6115
        $attributes['requiresajaxloading'] = $child->requiresajaxloading;
6116
 
6117
        if ($child->icon instanceof pix_icon) {
6118
            $attributes['icon'] = array(
6119
                'component' => $child->icon->component,
6120
                'pix' => $child->icon->pix,
6121
            );
6122
            foreach ($child->icon->attributes as $key=>$value) {
6123
                if ($key == 'class') {
6124
                    $attributes['icon']['classes'] = explode(' ', $value);
6125
                } else if (!array_key_exists($key, $attributes['icon'])) {
6126
                    $attributes['icon'][$key] = $value;
6127
                }
6128
 
6129
            }
6130
        } else if (!empty($child->icon)) {
6131
            $attributes['icon'] = (string)$child->icon;
6132
        }
6133
 
6134
        if ($child->forcetitle || $child->title !== $child->text) {
6135
            $attributes['title'] = htmlentities($child->title ?? '', ENT_QUOTES, 'UTF-8');
6136
        }
6137
        if (array_key_exists($child->key.':'.$child->type, $this->expandable)) {
6138
            $attributes['expandable'] = $child->key;
6139
            $child->add_class($this->expandable[$child->key.':'.$child->type]['id']);
6140
        }
6141
 
6142
        if (count($child->classes)>0) {
6143
            $attributes['class'] .= ' '.join(' ',$child->classes);
6144
        }
6145
        if (is_string($child->action)) {
6146
            $attributes['link'] = $child->action;
6147
        } else if ($child->action instanceof moodle_url) {
6148
            $attributes['link'] = $child->action->out();
6149
        } else if ($child->action instanceof action_link) {
6150
            $attributes['link'] = $child->action->url->out();
6151
        }
6152
        $attributes['hidden'] = ($child->hidden);
6153
        $attributes['haschildren'] = ($child->children->count()>0 || $child->type == navigation_node::TYPE_CATEGORY);
6154
        $attributes['haschildren'] = $attributes['haschildren'] || $child->type == navigation_node::TYPE_MY_CATEGORY;
6155
 
6156
        if ($child->children->count() > 0) {
6157
            $attributes['children'] = array();
6158
            foreach ($child->children as $subchild) {
6159
                $attributes['children'][] = $this->convert_child($subchild, $depth+1);
6160
            }
6161
        }
6162
 
6163
        if ($depth > 1) {
6164
            return $attributes;
6165
        } else {
6166
            return json_encode($attributes);
6167
        }
6168
    }
6169
}
6170
 
6171
/**
1441 ariadna 6172
 * The navigation_cache class is used for global and settings navigation data.
1 efrain 6173
 *
1441 ariadna 6174
 * It provides an easy access to the session cache with TTL of 1800 seconds.
1 efrain 6175
 *
6176
 * Example use:
6177
 * <code php>
6178
 * if (!$cache->viewdiscussion()) {
6179
 *     // Code to do stuff and produce cachable content
6180
 *     $cache->viewdiscussion = has_capability('mod/forum:viewdiscussion', $coursecontext);
6181
 * }
6182
 * $content = $cache->viewdiscussion;
6183
 * </code>
6184
 *
6185
 * @package   core
6186
 * @category  navigation
6187
 * @copyright 2009 Sam Hemelryk
6188
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6189
 */
6190
class navigation_cache {
1441 ariadna 6191
    /** @var cache_session The session cache instance */
6192
    protected $cache;
6193
    /** @var array The current cache area data */
6194
    protected $session = [];
6195
 
1 efrain 6196
    /**
1441 ariadna 6197
     * @var string A unique string to segregate this particular cache.
6198
     * It can either be unique to start a fresh cache or shared to use an existing cache.
1 efrain 6199
     */
6200
    protected $area;
6201
    /** @var int cache time information */
1441 ariadna 6202
    #[\core\attribute\deprecated(null, since: '4.5', reason: 'This constant is no longer needed.', mdl: 'MDL-79628')]
1 efrain 6203
    const CACHETIME = 0;
6204
    /** @var int cache user id */
1441 ariadna 6205
    #[\core\attribute\deprecated(null, since: '4.5', reason: 'This constant is no longer needed.', mdl: 'MDL-79628')]
1 efrain 6206
    const CACHEUSERID = 1;
6207
    /** @var int cache value */
6208
    const CACHEVALUE = 2;
1441 ariadna 6209
    /** @var null|array An array of cache areas to expire on shutdown */
6210
    public static $volatilecaches = null;
1 efrain 6211
 
6212
    /**
1441 ariadna 6213
     * Contructor for the cache. Requires a area string be passed in.
1 efrain 6214
     *
1441 ariadna 6215
     * @param string $area The unique string to segregate this particular cache.
6216
     * @param int $timeout Deprecated since Moodle 4.5. The number of seconds to time the information out after
1 efrain 6217
     */
1441 ariadna 6218
    public function __construct($area, $timeout = null) {
6219
        if ($timeout !== null) {
6220
            debugging(
6221
                'The timeout argument has been deprecated. Please remove it from your method calls.',
6222
                DEBUG_DEVELOPER,
6223
            );
1 efrain 6224
        }
1441 ariadna 6225
        global $USER;
6226
        $this->area = "user_{$USER->id}_{$area}";
6227
        $this->cache = cache::make('core', 'navigation_cache');
1 efrain 6228
    }
6229
 
6230
    /**
1441 ariadna 6231
     * Ensure the navigation cache is initialised
1 efrain 6232
     *
1441 ariadna 6233
     * This is called for each access and ensures that no data is put into the cache before it is required.
1 efrain 6234
     */
1441 ariadna 6235
    protected function ensure_navigation_cache_initialised() {
1 efrain 6236
        if (empty($this->session)) {
1441 ariadna 6237
            $this->session = $this->cache->get($this->area);
6238
            if (!is_array($this->session)) {
6239
                $this->session = [];
1 efrain 6240
            }
6241
        }
6242
    }
6243
 
6244
    /**
1441 ariadna 6245
     * Magic Method to retrieve a cached item by simply calling using = cache->key
1 efrain 6246
     *
1441 ariadna 6247
     * @param mixed $key The identifier for the cached information
6248
     * @return mixed|void The cached information or void if not found
1 efrain 6249
     */
6250
    public function __get($key) {
6251
        if (!$this->cached($key)) {
6252
            return;
6253
        }
1441 ariadna 6254
        return unserialize($this->session[$key][self::CACHEVALUE]);
1 efrain 6255
    }
6256
 
6257
    /**
1441 ariadna 6258
     * Magic method that simply uses {@see navigation_cache::set()} to store an item in the cache
1 efrain 6259
     *
1441 ariadna 6260
     * @param string|int $key The key to store the information against
6261
     * @param mixed $information The information to cache
1 efrain 6262
     */
6263
    public function __set($key, $information) {
6264
        $this->set($key, $information);
6265
    }
6266
 
6267
    /**
1441 ariadna 6268
     * Sets some information in the session cache for later retrieval
1 efrain 6269
     *
6270
     * @param string|int $key
6271
     * @param mixed $information
6272
     */
6273
    public function set($key, $information) {
1441 ariadna 6274
        $this->ensure_navigation_cache_initialised();
1 efrain 6275
        $information = serialize($information);
1441 ariadna 6276
        $this->session[$key] = [self::CACHEVALUE => $information];
6277
        $this->cache->set($this->area, $this->session);
1 efrain 6278
    }
6279
    /**
6280
     * Check the existence of the identifier in the cache
6281
     *
1441 ariadna 6282
     * @param string|int $key The identifier to check
6283
     * @return bool True if the item exists in the cache, false otherwise
1 efrain 6284
     */
6285
    public function cached($key) {
1441 ariadna 6286
        $this->ensure_navigation_cache_initialised();
6287
        return isset($this->session[$key]) &&
6288
            is_array($this->session[$key]);
1 efrain 6289
    }
6290
    /**
6291
     * Compare something to it's equivilant in the cache
6292
     *
1441 ariadna 6293
     * @param string $key  The key to check
6294
     * @param mixed $value The value to compare
1 efrain 6295
     * @param bool $serialise Whether to serialise the value before comparison
6296
     *              this should only be set to false if the value is already
6297
     *              serialised
1441 ariadna 6298
     * @return bool True if the value is the same as the cached one, false otherwise
1 efrain 6299
     */
6300
    public function compare($key, $value, $serialise = true) {
6301
        if ($this->cached($key)) {
6302
            if ($serialise) {
6303
                $value = serialize($value);
6304
            }
1441 ariadna 6305
            return $this->session[$key][self::CACHEVALUE] === $value;
1 efrain 6306
        }
6307
        return false;
6308
    }
6309
    /**
1441 ariadna 6310
     * Deletes the entire cache area, forcing a fresh cache to be created
1 efrain 6311
     */
6312
    public function clear() {
1441 ariadna 6313
        $this->cache->delete($this->area);
6314
        $this->session = [];
1 efrain 6315
    }
6316
    /**
1441 ariadna 6317
     * Marks the cache as volatile (likely to change)
1 efrain 6318
     *
1441 ariadna 6319
     * Any caches marked as volatile will be destroyed on shutdown by {@see navigation_node::destroy_volatile_caches()}
1 efrain 6320
     *
1441 ariadna 6321
     * @param bool $setting True to mark the cache as volatile, false to remove the volatile flag
1 efrain 6322
     */
6323
    public function volatile($setting = true) {
1441 ariadna 6324
        if (self::$volatilecaches === null) {
6325
            self::$volatilecaches = [];
6326
            core_shutdown_manager::register_function(['navigation_cache', 'destroy_volatile_caches']);
1 efrain 6327
        }
6328
 
6329
        if ($setting) {
6330
            self::$volatilecaches[$this->area] = $this->area;
6331
        } else if (array_key_exists($this->area, self::$volatilecaches)) {
6332
            unset(self::$volatilecaches[$this->area]);
6333
        }
6334
    }
6335
 
6336
    /**
6337
     * Destroys all caches marked as volatile
6338
     *
1441 ariadna 6339
     * This function is static and works with the static volatilecaches property of navigation cache.
6340
     * It manually resets the cached areas back to an empty array.
1 efrain 6341
     */
6342
    public static function destroy_volatile_caches() {
1441 ariadna 6343
        if (is_array(self::$volatilecaches) && count(self::$volatilecaches) > 0) {
6344
            $cache = cache::make('core', 'navigation_cache');
1 efrain 6345
            foreach (self::$volatilecaches as $area) {
1441 ariadna 6346
                $cache->delete($area);
1 efrain 6347
            }
1441 ariadna 6348
            self::$volatilecaches = null;
1 efrain 6349
        }
6350
    }
6351
}