MDL-20204 fixed html-writer::tag() parameter order to match the rest of the api
[moodle.git] / lib / navigationlib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * This file contains classes used to manage the navigation structures in Moodle
20  * and was introduced as part of the changes occuring in Moodle 2.0
21  *
22  * @since 2.0
23  * @package moodlecore
24  * @subpackage navigation
25  * @copyright 2009 Sam Hemelryk
26  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 if (!function_exists('get_all_sections')) {
30     /** Include course lib for its functions */
31     require_once($CFG->dirroot.'/course/lib.php');
32 }
34 /**
35  * The name that will be used to separate the navigation cache within SESSION
36  */
37 define('NAVIGATION_CACHE_NAME', 'navigation');
39 /**
40  * This class is used to represent a node in a navigation tree
41  *
42  * This class is used to represent a node in a navigation tree within Moodle,
43  * the tree could be one of global navigation, settings navigation, or the navbar.
44  * Each node can be one of two types either a Leaf (default) or a branch.
45  * When a node is first created it is created as a leaf, when/if children are added
46  * the node then becomes a branch.
47  *
48  * @package moodlecore
49  * @subpackage navigation
50  * @copyright 2009 Sam Hemelryk
51  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
52  */
53 class navigation_node {
54     /** Used to identify this node a leaf (default) */
55     const NODETYPE_LEAF =   0;
56     /** Used to identify this node a branch, happens with children */
57     const NODETYPE_BRANCH = 1;
58     /** Unknown node type */
59     const TYPE_UNKNOWN =    null;
60     /**  System node type */
61     const TYPE_SYSTEM =     0;
62     /**  Category node type */
63     const TYPE_CATEGORY =   10;
64     /**  Course node type */
65     const TYPE_COURSE =     20;
66     /**  Course Structure node type */
67     const TYPE_SECTION =    30;
68     /**  Activity node type, e.g. Forum, Quiz */
69     const TYPE_ACTIVITY =   40;
70     /**  Resource node type, e.g. Link to a file, or label */
71     const TYPE_RESOURCE =   50;
72     /**  A custom node type, default when adding without specifing type */
73     const TYPE_CUSTOM =     60;
74     /**  Setting node type, used only within settings nav */
75     const TYPE_SETTING =    70;
76     /**  Setting node type, used only within settings nav */
77     const TYPE_USER =       80;
79     /** @var int Parameter to aid the coder in tracking [optional] */
80     public $id = null;
81     /** @var string|int The identifier for the node, used to retrieve the node */
82     public $key = null;
83     /** @var string The text to use for the node */
84     public $text = null;
85     /** @var string Short text to use if requested [optional] */
86     public $shorttext = null;
87     /** @var string The title attribute for an action if one is defined */
88     public $title = null;
89     /** @var string A string that can be used to build a help button */
90     public $helpbutton = null;
91     /** @var moodle_url|string|null An action for the node (link) */
92     public $action = null;
93     /** @var pix_icon The path to an icon to use for this node */
94     public $icon = null;
95     /** @var int See TYPE_* constants defined for this class */
96     public $type = self::TYPE_UNKNOWN;
97     /** @var int See NODETYPE_* constants defined for this class */
98     public $nodetype = self::NODETYPE_LEAF;
99     /** @var bool If set to true the node will be collapsed by default */
100     public $collapse = false;
101     /** @var bool If set to true the node will be expanded by default */
102     public $forceopen = false;
103     /** @var string An array of CSS classes for the node */
104     public $classes = array();
105     /** @var array An array of child nodes */
106     public $children = array();
107     /** @var bool If set to true the node will be recognised as active */
108     public $isactive = false;
109     /** @var string If set to true the node will be dimmed */
110     public $hidden = false;
111     /** @var bool If set to false the node will not be displayed */
112     public $display = true;
113     /** @var bool If set to true then an HR will be printed before the node */
114     public $preceedwithhr = false;
115     /** @var bool If set to true the the navigation bar should ignore this node */
116     public $mainnavonly = false;
117     /** @var bool If set to true a title will be added to the action no matter what */
118     public $forcetitle = false;
119     /** @var array */
120     protected $namedtypes = array(0=>'system',10=>'category',20=>'course',30=>'structure',40=>'activity',50=>'resource',60=>'custom',70=>'setting', 80=>'user');
121     /** @var moodle_url */
122     protected static $fullmeurl = null;
124     /**
125      * Establish the node, with either text string or array or properites
126      *
127      * Called when first creating the node, requires one argument which can be either
128      * a string containing the text for the node or an array or properties one of
129      * which must be text.
130      *
131      * <code>
132      * $PAGE->navigation->newitem = 'This is a new nav item';
133      *  // or
134      * $properties = array()
135      * $properties['text'] = 'This is a new nav item';
136      * $properties['short'] = 'This is a new nav item';
137      * $properties['action'] = moodle_url($CFG->wwwroot.'/course/category.php');
138      * $properties['icon'] = new pix_icon('i/course', '');
139      * $properties['type'] = navigation_node::TYPE_COURSE;
140      * $properties['key'] = 'newitem';
141      * $PAGE->navigation->newitem = $properties;
142      * </code>
143      *
144      * The following are properties that must/can be set in the properties array
145      * <ul>
146      * <li><b>text</b>: You must set text, if this is not set a coding exception is thrown.</li>
147      * <li><b>short</b> <i>optional</i>: A short description used for navbar optional.</li>
148      * <li><b>action</b> <i>optional</i>: This can be either a {@link moodle_url} for a link, or string that can be directly output in instead of the text.</li>
149      * <li><b>icon</b> <i>optional</i>: The path to an icon to display with the node.</li>
150      * <li><b>type</b> <i>optional</i>: This type of the node, defaults to TYPE_CUSTOM.</li>
151      * <li><b>key</b> <i>optional</i>: This can be set to allow you to easily retrieve a node you have created.</li>
152      * </ul>
153      *
154      * @param string|array $properties
155      */
156     public function __construct($properties) {
157         if (is_array($properties)) {
158             if (array_key_exists('text', $properties)) {
159                 $this->text = $properties['text'];
160             }
161             if (array_key_exists('shorttext', $properties)) {
162                 $this->shorttext = $properties['shorttext'];
163             }
164             if (array_key_exists('action', $properties)) {
165                 $this->action = $properties['action'];
166                 if (is_string($this->action)) {
167                     $this->action = new moodle_url($this->action);
168                 }
169                 $this->check_if_active();
170             }
171             if (array_key_exists('icon', $properties)) {
172                 $this->icon = $properties['icon'];
173             }
174             if (array_key_exists('type', $properties)) {
175                 $this->type = $properties['type'];
176             } else {
177                 $this->type = self::TYPE_CUSTOM;
178             }
179             if (array_key_exists('key', $properties)) {
180                 $this->key = $properties['key'];
181             }
182         } else if (is_string($properties)) {
183             $this->text = $properties;
184         }
185         if ($this->text === null) {
186             throw new coding_exception('You must set the text for the node when you create it.');
187         }
188         $this->title = $this->text;
189         $textlib = textlib_get_instance();
190         if (strlen($this->text)>50) {
191             $this->text = $textlib->substr($this->text, 0, 50).'...';
192         }
193         if (is_string($this->shorttext) && strlen($this->shorttext)>25) {
194             $this->shorttext = $textlib->substr($this->shorttext, 0, 25).'...';
195         }
196     }
198     /**
199      * This function overrides the active URL that is used to compare new nodes
200      * to find out if they are active.
201      *
202      * If null is passed then $fullmeurl will be regenerated when the next node
203      * is created/added
204      */
205     public static function override_active_url(moodle_url $url=null) {
206         self::$fullmeurl = $url;
207     }
209     /**
210      * This function checks if the node is the active child by comparing its action
211      * to the current page URL obtained via $PAGE->url
212      *
213      * This function compares the nodes url to the static var {@link navigation_node::fullmeurl}
214      * and if they match (based on $strenght) then the node is considered active.
215      *
216      * Note: This function is recursive, when you call it it will check itself and all
217      * children recursivily.
218      *
219      * @staticvar moodle_url $fullmeurl
220      * @param int $strength When using the moodle_url compare function how strictly
221      *                       to check for a match. Defaults to URL_MATCH_EXACT
222      *                       Can be URL_MATCH_EXACT or URL_MATCH_BASE
223      * @return bool True is active, false otherwise
224      */
225     public function check_if_active($strength=URL_MATCH_EXACT) {
226         global $FULLME, $PAGE;
227         if (self::$fullmeurl == null) {
228             if ($PAGE->has_set_url()) {
229                 $this->override_active_url(new moodle_url($PAGE->url));
230             } else {
231                 $this->override_active_url(new moodle_url($FULLME));
232             }
233         }
235         if ($this->action instanceof moodle_url && $this->action->compare(self::$fullmeurl, $strength)) {
236             $this->make_active();
237             return true;
238         } else if (is_string($this->action) && $this->action==self::$fullmeurl->out()) {
239             $this->make_active();
240             return true;
241         }
242         return false;
243     }
244     /**
245      * This function allows the user to add a child node to this node.
246      *
247      * @param string $text The text to display in the node
248      * @param string $action Either a moodle_url or a bit of html to use instead of the text <i>optional</i>
249      * @param int $type The type of node should be one of the const types of navigation_node <i>optional</i>
250      * @param string $shorttext The short text to use for this node
251      * @param string|int $key Sets the key that can be used to retrieve this node <i>optional</i>
252      * @param string $icon The path to an icon to use for this node <i>optional</i>
253      * @return string The key that was used for this node
254      */
255     public function add($text, $action=null, $type=null, $shorttext=null, $key=null, pix_icon $icon=null) {
256         if ($this->nodetype !== self::NODETYPE_BRANCH) {
257             $this->nodetype = self::NODETYPE_BRANCH;
258         }
259         $itemarray = array('text'=>$text);
260         if ($type!==null) {
261             $itemarray['type'] = $type;
262         } else {
263             $type = self::TYPE_CUSTOM;
264         }
265         if ($action!==null) {
266             $itemarray['action'] = $action;
267         }
269         if ($shorttext!==null) {
270             $itemarray['shorttext'] = $shorttext;
271         }
272         if ($icon!==null) {
273             $itemarray['icon'] = $icon;
274         }
275         if ($key===null) {
276             $key = count($this->children);
277         }
279         $key = $key.':'.$type;
281         $itemarray['key'] = $key;
282         $this->children[$key] = new navigation_node($itemarray);
283         if (($type==self::TYPE_CATEGORY) || (isloggedin() && $type==self::TYPE_COURSE)) {
284             $this->children[$key]->nodetype = self::NODETYPE_BRANCH;
285         }
286         if ($this->hidden) {
287             $this->children[$key]->hidden = true;
288         }
289         return $key;
290     }
292     /**
293      * Adds a new node to a particular point by recursing through an array of node keys
294      *
295      * @param array $patharray An array of keys to recurse to find the correct node
296      * @param string $text The text to display in the node
297      * @param string|int $key Sets the key that can be used to retrieve this node <i>optional</i>
298      * @param int $type The type of node should be one of the const types of navigation_node <i>optional</i>
299      * @param string $action Either a moodle_url or a bit of html to use instead of the text <i>optional</i>
300      * @param string $icon The path to an icon to use for this node <i>optional</i>
301      * @return mixed Either the key used for the node once added or false for failure
302      */
303     public function add_to_path($patharray, $key=null, $text=null, $shorttext=null, $type=null, $action=null, pix_icon $icon=null) {
304         if (count($patharray)==0) {
305             $key = $this->add($text, $action, $type, $shorttext, $key, $icon);
306             return $key;
307         } else {
308             $pathkey = array_shift($patharray);
309             $child = $this->get($pathkey);
310             if ($child!==false) {
311                 return $child->add_to_path($patharray, $key, $text, $shorttext, $type, $action, $icon);
312             } else {
313                 return false;
314             }
315         }
316     }
318     /**
319      * Add a css class to this particular node
320      *
321      * @param string $class The css class to add
322      * @return bool Returns true
323      */
324     public function add_class($class) {
325         if (!in_array($class, $this->classes)) {
326             $this->classes[] = $class;
327         }
328         return true;
329     }
331     /**
332      * Removes a given class from this node if it exists
333      *
334      * @param string $class
335      * @return bool
336      */
337     public function remove_class($class) {
338         if (in_array($class, $this->classes)) {
339             $key = array_search($class,$this->classes);
340             if ($key!==false) {
341                 unset($this->classes[$key]);
342                 return true;
343             }
344         }
345         return false;
346     }
348     /**
349      * Recurse down child nodes and collapse everything once a given
350      * depth of recursion has been reached.
351      *
352      * This function is used internally during the initialisation of the nav object
353      * after the tree has been generated to collapse it to a suitable depth.
354      *
355      * @param int $depth defualts to 2
356      * @return bool Returns true
357      */
358     protected function collapse_at_depth($depth=2) {
359         if ($depth>0 && $this->nodetype===self::NODETYPE_BRANCH) {
360             foreach (array_keys($this->children) as $key) {
361                 $this->children[$key]->collapse_at_depth($depth-1);
362             }
363             return true;
364         } else {
365             $this->collapse_children();
366             return true;
367         }
368     }
370     /**
371      * Collapses all of the child nodes recursion optional
372      *
373      * @param bool $recurse If set to true child nodes are closed recursively
374      * @return bool Returns true
375      */
376     protected function collapse_children($recurse=true) {
377         if ($this->nodetype === self::NODETYPE_BRANCH && count($this->children)>0) {
378             foreach ($this->children as &$child) {
379                 if (!$this->forceopen) {
380                     $child->collapse = true;
381                 }
382                 if ($recurse && $child instanceof navigation_node) {
383                     $child->collapse_children($recurse);
384                 }
385             }
386             unset($child);
387         }
388         return true;
389     }
391     /**
392      * Produce the actual HTML content for the node including any action or icon
393      *
394      * @param bool $shorttext If true then short text is used rather than text if it has been set
395      * @return string The HTML content
396      */
397     public function content($shorttext=false) {
398         global $OUTPUT;
399         if (!$this->display) {
400             return '';
401         }
402         if ($shorttext && $this->shorttext!==null) {
403             $content = format_string($this->shorttext);
404         } else {
405             $content = format_string($this->text);
406         }
407         $title = '';
408         if ($this->forcetitle || ($this->shorttext!==null && $this->title !== $this->shorttext) || $this->title !== $this->text) {
409              $title = $this->title;
410         }
412         if ($this->icon instanceof renderable) {
413             $icon = $OUTPUT->render($this->icon);
414             $content = $icon.$content; // use CSS for spacing of icons
415         } else if ($this->helpbutton !== null) {
416             $content = trim($this->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton'));
417         }
419         if ($content === '') {
420             return '';
421         }
423         if ($this->action instanceof action_link) {
424             //TODO: to be replaced with something else
425             $link = $this->action;
426             if ($this->hidden) {
427                 $link->add_class('dimmed');
428             }
429             $content = $OUTPUT->render($link);
430             
431         } else if ($this->action instanceof moodle_url) {
432             $attributes = array();
433             if ($title !== '') {
434                 $attributes['title'] = $title;
435             }
436             if ($this->hidden) {
437                 $attributes['class'] = 'dimmed_text';
438             }
439             $content = html_writer::link($this->action, $content, $attributes);
441         } else if (is_string($this->action) || empty($this->action)) {
442             $attributes = array();
443             if ($title !== '') {
444                 $attributes['title'] = $title;
445             }
446             if ($this->hidden) {
447                 $attributes['class'] = 'dimmed_text';
448             }
449             $content = html_writer::tag('span', $content, $attributes);
450         }
452         return $content;
453     }
455     /**
456      * Get the CSS type for this node
457      *
458      * @return string
459      */
460     public function get_css_type() {
461         if (array_key_exists($this->type, $this->namedtypes)) {
462             return 'type_'.$this->namedtypes[$this->type];
463         }
464         return 'type_unknown';
465     }
467     /**
468      * Find and return a child node if it exists (returns a reference to the child)
469      *
470      * This function is used to search for and return a reference to a child node when provided
471      * with the child nodes key and type.
472      * If the child is found a reference to it is returned otherwise the default is returned.
473      *
474      * @param string|int $key The key of the child node you are searching for.
475      * @param int $type The type of the node you are searching for. Defaults to TYPE_CATEGORY
476      * @param mixed $default The value to return if the child cannot be found
477      * @return mixed The child node or what ever default contains (usually false)
478      */
479     public function find_child($key, $type=self::TYPE_CATEGORY, $default = false) {
480         list($key, $type) = $this->split_key_type($key, $type);
481         if (array_key_exists($key.":".$type, $this->children)) {
482             return $this->children[$key.":".$type];
483         } else if ($this->nodetype === self::NODETYPE_BRANCH && count($this->children)>0 && $this->type<=$type) {
484             foreach ($this->children as &$child) {
485                 $outcome = $child->find_child($key, $type);
486                 if ($outcome !== false) {
487                     return $outcome;
488                 }
489             }
490         }
491         return $default;
492     }
494     /**
495      * Find the active child
496      *
497      * @param null|int $type
498      * @return navigation_node|bool
499      */
500     public function find_active_node($type=null) {
501         if ($this->contains_active_node()) {
502             if ($type!==null && $this->type===$type) {
503                 return $this;
504             }
505             if ($this->nodetype === self::NODETYPE_BRANCH && count($this->children)>0) {
506                 foreach ($this->children as $child) {
507                     if ($child->isactive && !$child->contains_active_node()) {
508                         return $child;
509                     } else {
510                         $outcome = $child->find_active_node($type);
511                         if ($outcome!==false) {
512                             return $outcome;
513                         }
514                     }
515                 }
516             }
517         }
518         return false;
519     }
521     /**
522      * Returns the depth of a child
523      *
524      * @param string|int $key The key for the child we are looking for
525      * @param int $type The type of the child we are looking for
526      * @return int The depth of the child once found
527      */
528     public function find_child_depth($key, $type=self::TYPE_CATEGORY) {
529         $depth = 0;
530         list($key, $type) = $this->split_key_type($key, $type);
531         if (array_key_exists($key.':'.$type, $this->children)) {
532             $depth = 1;
533         } else if ($this->nodetype === self::NODETYPE_BRANCH && count($this->children)>0 && $this->type<=$type) {
534             foreach ($this->children as $child) {
535                 $depth += $child->find_child_depth($key, $type);
536             }
537         }
538         return $depth;
539     }
541     /**
542      * Finds all nodes that have the specified type
543      *
544      * @param int $type One of navigation_node::TYPE_*
545      * @return array An array of navigation_node references for nodes of type $type
546      */
547     public function get_children_by_type($type) {
548         $nodes = array();
549         if (count($this->children)>0) {
550             foreach ($this->children as &$child) {
551                 if ($child->type === $type) {
552                     $nodes[] = $child;
553                 }
554             }
555         }
556         return $nodes;
557     }
559     /**
560      * Finds all nodes (recursivily) that have the specified type, regardless of
561      * assumed order or position.
562      *
563      * @param int $type One of navigation_node::TYPE_*
564      * @return array An array of navigation_node references for nodes of type $type
565      */
566     public function find_children_by_type($type) {
567         $nodes = array();
568         if (count($this->children)>0) {
569             foreach ($this->children as &$child) {
570                 if ($child->type === $type) {
571                     $nodes[] = $child;
572                 }
573                 if (count($child->children)>0) {
574                     $nodes = array_merge($nodes, $child->find_children_by_type($type));
575                 }
576             }
577         }
578         return $nodes;
579     }
581     /**
582      * Toogles display of nodes and child nodes based on type
583      *
584      * If the type of a node if more than the type specified it's display property is set to false
585      * and it is not shown
586      *
587      * @param int $type
588      * @param bool $display
589      */
590     public function toggle_type_display($type=self::TYPE_COURSE, $display=false) {
591         if ((int)$this->type > $type) {
592             $this->display = $display;
593         }
594         if (count($this->children)>0) {
595             foreach ($this->children as $child) {
596                 $child->toggle_type_display($type, $display);
597             }
598         }
599     }
601     /**
602      * Find out if a child (or subchild) of this node contains an active node
603      *
604      * @return bool True if it does fales otherwise
605      */
606     public function contains_active_node() {
607         if ($this->nodetype === self::NODETYPE_BRANCH && count($this->children)>0) {
608             foreach ($this->children as $child) {
609                 if ($child->isactive || $child->contains_active_node()) {
610                     return true;
611                 }
612             }
613         }
614         return false;
615     }
617     /**
618      * Find all nodes that are expandable for this node and its given children.
619      *
620      * This function recursively finds all nodes that are expandable by AJAX within
621      * [and including] this child.
622      *
623      * @param array $expandable An array to fill with the HTML id's of all branches
624      * that can be expanded by AJAX. This is a forced reference.
625      * @param int $expansionlimit Optional/used internally can be one of navigation_node::TYPE_*
626      */
627     public function find_expandable(&$expandable, $expansionlimit = null) {
628         static $branchcount;
629         if ($branchcount==null) {
630             $branchcount=1;
631         }
632         if ($this->nodetype == self::NODETYPE_BRANCH && count($this->children)==0 && ($expansionlimit === null || $this->type < $expansionlimit)) {
633             $this->id = 'expandable_branch_'.$branchcount;
634             $this->add_class('canexpand');
635             $branchcount++;
636             $expandable[] = array('id'=>$this->id,'branchid'=>$this->key,'type'=>$this->type);
637         } else if ($this->nodetype==self::NODETYPE_BRANCH && ($expansionlimit === null || $this->type <= $expansionlimit)) {
638             foreach ($this->children as $child) {
639                 $child->find_expandable($expandable, $expansionlimit);
640             }
641         }
642     }
644     /**
645      * Used to return a child node with a given key
646      *
647      * This function searchs for a child node with the provided key and returns the
648      * child. If the child doesn't exist then this function returns false.
649      *
650      * @param int|string $key The key to search for
651      * @param int $type Optional one of TYPE_* constants
652      * @param navigation_node|bool The child if it exists or false
653      */
654     public function get($key, $type=null) {
655         if ($key===false) {
656             return false;
657         }
658         list($key, $type) = $this->split_key_type($key);
659         if ($this->nodetype === self::NODETYPE_BRANCH && count($this->children)>0) {
660             if ($type!==null) {
661                 if (array_key_exists($key.':'.$type, $this->children)) {
662                     return $this->children[$key.':'.$type];
663                 }
664             } else {
665                 foreach (array_keys($this->children) as $childkey) {
666                     if (strpos($childkey, $key.':')===0) {
667                         return $this->children[$childkey];
668                     }
669                 }
670             }
671         }
672         return false;
673     }
675     /**
676      * This function is used to split a key into its key and value parts if the
677      * key is a combination of the two.
678      *
679      * Was introduced to help resolve MDL-20543
680      *
681      * @param string $key
682      * @param int|null $type
683      * @return array
684      */
685     protected function split_key_type($key, $type=null) {
686         /**
687          * If the key is a combination it will be of the form `key:type` where key
688          * could be anything and type will be an int value
689          */
690         if (preg_match('#^(.*)\:(\d{1,3})$#', $key, $match)) {
691             /**
692              * If type is null then we want to collect and return the type otherwise
693              * we will use the provided type. This ensures that if a type was specified
694              * it is not lost
695              */
696             if ($type===null) {
697                 $type = $match[2];
698             }
699             $key = $match[1];
700         }
701         return array($key, $type);
702     }
704     /**
705      * Fetch a node given a set of keys that describe its path
706      *
707      * @param array $keys An array of keys
708      * @return navigation_node|bool The node or false
709      */
710     public function get_by_path($keys) {
711         if (count($keys)==1) {
712             $key = array_shift($keys);
713             return $this->get($key);
714         } else {
715             $key = array_shift($keys);
716             $child = $this->get($key);
717             if ($child !== false) {
718                 return $child->get_by_path($keys);
719             }
720             return false;
721         }
722     }
724     /**
725      * Returns the child marked as active if there is one, false otherwise.
726      *
727      * @return navigation_node|bool The active node or false
728      */
729     public function get_active_node() {
730         foreach ($this->children as $child) {
731             if ($child->isactive) {
732                 return $child;
733             }
734         }
735         return false;
736     }
738     /**
739      * Mark this node as active
740      *
741      * This function marks the node as active my forcing the node to be open,
742      * setting isactive to true, and adding the class active_tree_node
743      */
744      public function make_active() {
745         $this->forceopen = true;
746         $this->isactive = true;
747         $this->add_class('active_tree_node');
748      }
750     /**
751      * This intense little function looks for branches that are forced open
752      * and checks to ensure that all parent nodes are also forced open.
753      */
754     public function respect_forced_open() {
755         foreach ($this->children as $child) {
756             $child->respect_forced_open();
757             if ($child->forceopen) {
758                 $this->forceopen = true;
759             }
760         }
761     }
763     /**
764      * This function simply removes a given child node
765      *
766      * @param string|int $key The key that identifies a child node
767      * @return bool
768      */
769     public function remove_child($key, $type=null) {
770         if ($key instanceof navigation_node) {
771             $key = $key->key;
772         }
773         $child = $this->get($key, $type);
774         if ($child) {
775             unset($this->children[$child->key]);
776             return true;
777         }
778         return false;
779     }
781     /**
782      * Iterate all children and check if any of them are active
783      *
784      * This function iterates all children recursively until it sucecssfully marks
785      * a node as active, or gets to the end of the tree.
786      * This can be used on a cached branch to mark the active child.
787      *
788      * @param int $strength When using the moodle_url compare function how strictly
789      *                       to check for a match. Defaults to URL_MATCH_EXACTLY
790      * @return bool True is a node was marked active false otherwise
791      */
792     public function reiterate_active_nodes($strength=URL_MATCH_EXACT) {
793         if ($this->nodetype !== self::NODETYPE_BRANCH) {
794             return false;
795         }
796         foreach ($this->children as $child) {
797             $outcome = $child->check_if_active($strength);
798             if (!$outcome && $child->nodetype === self::NODETYPE_BRANCH) {
799                 $outcome = $child->reiterate_active_nodes($strength);
800             }
801             if ($outcome) {
802                 $this->forceopen = true;
803                 return true;
804             }
805         }
806     }
808     /**
809      * This function sets the title for the node and at the same time sets
810      * forcetitle to true to ensure that it is used if possible
811      *
812      * @param string $title
813      */
814     public function title($title) {
815         $this->title = $title;
816         $this->forcetitle = true;
817     }
819     /**
820      * Magic Method: When we unserialise an object make it `unactive`
821      *
822      * This is to ensure that when we take a branch out of the cache it is not marked
823      * active anymore, as we can't be sure it still is (infact it most likely isnt)
824      */
825     public function __wakeup(){
826         $this->forceopen = false;
827         $this->isactive = false;
828         $this->remove_class('active_tree_node');
829     }
832 /**
833  * The global navigation class used for... the global navigation
834  *
835  * This class is used by PAGE to store the global navigation for the site
836  * and is then used by the settings nav and navbar to save on processing and DB calls
837  *
838  * See
839  * <ul>
840  * <li><b>{@link lib/pagelib.php}</b> {@link moodle_page::initialise_theme_and_output()}<li>
841  * <li><b>{@link lib/ajax/getnavbranch.php}</b> Called by ajax<li>
842  * </ul>
843  *
844  * @package moodlecore
845  * @subpackage navigation
846  * @copyright 2009 Sam Hemelryk
847  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
848  */
849 class global_navigation extends navigation_node {
850     /** @var int */
851     protected $depthforward = 1;
852     /** @var cache */
853     protected $cache = null;
854     /** @var bool */
855     protected $initialised = false;
857     /** @var null|int */
858     public $expansionlimit = null;
859     /** @var stdClass */
860     public $context = null;
861     /** @var mixed */
862     public $expandable = null;
863     /** @var bool */
864     public $showemptybranches = true;
865     /** @var bool  */
866     protected $isloggedin = false;
867     /** @var array Array of user objects to extend the navigation with */
868     protected $extendforuser = array();
869     /** @var bool Gets set to true if all categories have been loaded */
870     protected $allcategoriesloaded = false;
872     /**
873      * Sets up the object with basic settings and preparse it for use
874      */
875     public function __construct() {
876         global $CFG;
877         if (during_initial_install()) {
878             return false;
879         }
880         $this->key = 0;
881         $this->type = self::TYPE_SYSTEM;
882         $this->isloggedin = isloggedin();
883         $this->text = get_string('home');
884         $this->forceopen = true;
885         $this->action = new moodle_url($CFG->wwwroot);
886         $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
887         $regenerate = optional_param('regenerate', null, PARAM_TEXT);
888         if ($regenerate==='navigation') {
889             $this->cache->clear();
890         }
891     }
893     /**
894      * Override: This function generated the content of the navigation
895      *
896      * If an expansion limit has been set then we hide everything to after that
897      * set limit type
898      *
899      * @return string
900      */
901     public function content() {
902         if ($this->expansionlimit!==null) {
903             $this->toggle_type_display($this->expansionlimit);
904         }
905         return parent::content();
906     }
908     /**
909      * Initialise the navigation object, calling it to auto generate
910      *
911      * This function starts the navigation object automatically retrieving what it
912      * needs from Moodle objects.
913      *
914      * It also passed Javascript args and function calls as required
915      *
916      * @return bool Returns true
917      */
918     public function initialise($jsargs = null) {
919         global $PAGE, $SITE, $USER;
920         if ($this->initialised || during_initial_install()) {
921             return true;
922         }
923         $start = microtime(false);
924         $this->depthforward = 1;
925         $this->context = $PAGE->context;
926         $contextlevel = $this->context->contextlevel;
927         if ($contextlevel == CONTEXT_COURSE && $PAGE->course->id==$SITE->id) {
928             $contextlevel = 10;
929         }
930         $depth = 0;
932         /**
933          * We always want to load the front page activities into the tree, these
934          * will appear at the bottom of the opening (site) node.
935          */
936         $sitekeys = array();
937         $this->load_course_activities($sitekeys, $SITE);
938         $this->load_section_activities($sitekeys, false, $SITE);
939         switch ($contextlevel) {
940             case CONTEXT_SYSTEM:
941                 $this->cache->volatile();
942                 $depth = $this->load_for_category(false);
943                 break;
944             case CONTEXT_COURSECAT:
945                 $depth = $this->load_for_category();
946                 break;
947             case CONTEXT_BLOCK:
948             case CONTEXT_COURSE:
949                 $depth = $this->load_for_course();
950                 break;
951             case CONTEXT_MODULE:
952                 $depth = $this->load_for_activity();
953                 break;
954             case CONTEXT_USER:
955                 // If the PAGE course is not the site then add the content of the
956                 // course
957                 if ($PAGE->course->id !== SITEID) {
958                     $depth = $this->load_for_course();
959                 }
960                 break;
961         }
963         // Load each extending user into the navigation.
964         foreach ($this->extendforuser as $user) {
965             if ($user->id !== $USER->id) {
966                 $this->load_for_user($user);
967             }
968         }
969         // Load the current user into the the navigation
970         $this->load_for_user();
972         $this->collapse_at_depth($this->depthforward+$depth);
973         $this->respect_forced_open();
974         $this->expandable = array();
975         $this->find_expandable($this->expandable);
976         $this->initialised = true;
977         return true;
978     }
979     /**
980      * This function loads the global navigation structure for a user.
981      *
982      * This gets called by {@link initialise()} when the context is CONTEXT_USER
983      * @param object|int|null $user
984      */
985     protected function load_for_user($user=null) {
986         global $DB, $PAGE, $CFG, $USER;
987         
988         $iscurrentuser = false;
989         if ($user === null) {
990             // We can't require login here but if the user isn't logged in we don't
991             // want to show anything
992             if (!isloggedin()) {
993                 return false;
994             }
995             $user = $USER;
996             $iscurrentuser = true;
997         } else if (!is_object($user)) {
998             // If the user is not an object then get them from the database
999             $user = $DB->get_record('user', array('id'=>(int)$user), '*', MUST_EXIST);
1000         }
1001         $baseargs = array('id'=>$user->id);
1002         $usercontext = get_context_instance(CONTEXT_USER, $user->id);
1004         // Get the course set against the page, by default this will be the site
1005         $course = $PAGE->course;
1006         if ($course->id !== SITEID) {
1007             // Load all categories if required.
1008             if (!empty($CFG->navshowallcourses)) {
1009                 $this->load_categories();
1010             }
1011             // Attempt to find the course node within the navigation structure.
1012             $coursetab = $this->find_child($course->id, self::TYPE_COURSE);
1013             if (!$coursetab) {
1014                 // Load for the course.... this should never happen but is here to
1015                 // ensure if it ever does things don't break.
1016                 $this->load_for_course();
1017                 $coursetab = $this->find_child($course->id, self::TYPE_COURSE);
1018             }
1019             // Get the context for the course
1020             $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1021             $baseargs['course'] = $course->id;
1022             $issitecourse = false;
1023         } else {
1024             // Load all categories and get the context for the system
1025             $this->load_categories();
1026             $coursecontext = get_context_instance(CONTEXT_SYSTEM);
1027             $issitecourse = true;
1028         }
1030         // Create a node to add user information under.
1031         if ($iscurrentuser) {
1032             // If it's the current user the information will go under the my moodle dashboard node
1033             $usernode = $this->add(get_string('mymoodledashboard'), null, navigation_node::TYPE_CUSTOM, get_string('mymoodledashboard'), 'mymoodle');
1034             $usernode = $this->get($usernode);
1035         } else {
1036             if (!$issitecourse) {
1037                 // Not the current user so add it to the participants node for the current course
1038                 $usersnode = $coursetab->find_child('participants', self::TYPE_SETTING);
1039             } else {
1040                 // This is the site so add a users node to the root branch
1041                 $usersnode = $this->find_child('users', self::TYPE_CUSTOM);
1042                 if (!$usersnode) {
1043                     $usersnode = $this->add(get_string('users'), new moodle_url('/user/index.php', array('id'=>SITEID)), self::TYPE_CUSTOM, null, 'users');
1044                     $usersnode = $this->get($usersnode);
1045                 }
1046             }
1047             // Add a branch for the current user
1048             $usernode = $usersnode->add(fullname($user, true));
1049             $usernode = $usersnode->get($usernode);
1050         }
1052         // If the user is the current user or has permission to view the details of the requested
1053         // user than add a view profile link.
1054         if ($iscurrentuser || has_capability('moodle/user:viewdetails', $coursecontext) || has_capability('moodle/user:viewdetails', $usercontext)) {
1055             $usernode->add(get_string('viewprofile'), new moodle_url('/user/view.php',$baseargs));
1056         }
1058         // Add nodes for forum posts and discussions if the user can view either or both
1059         $canviewposts = has_capability('moodle/user:readuserposts', $usercontext);
1060         $canviewdiscussions = has_capability('mod/forum:viewdiscussion', $coursecontext);
1061         if ($canviewposts || $canviewdiscussions) {
1062             $forumtab = $usernode->add(get_string('forumposts', 'forum'));
1063             $forumtab = $usernode->get($forumtab);
1064             if ($canviewposts) {
1065                 $forumtab->add(get_string('posts', 'forum'), new moodle_url('/mod/forum/user.php', $baseargs));
1066             }
1067             if ($canviewdiscussions) {
1068                 $forumtab->add(get_string('discussions', 'forum'), new moodle_url('/mod/forum/user.php', array_merge($baseargs, array('mode'=>'discussions'))));
1069             }
1070         }
1072         // Add a node to view the users notes if permitted
1073         if (!empty($CFG->enablenotes) && has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $coursecontext)) {
1074             $usernode->add(get_string('notes', 'notes'), new moodle_url('/notes/index.php',array('user'=>$user->id, 'course'=>$coursecontext->instanceid)));
1075         }
1077         // Add a reports tab and then add reports the the user has permission to see.
1078         $reporttab = $usernode->add(get_string('activityreports'));
1079         $reporttab = $usernode->get($reporttab);
1080         $anyreport  = has_capability('moodle/user:viewuseractivitiesreport', $usercontext);
1081         $viewreports = ($anyreport || ($course->showreports && $iscurrentuser));
1082         $reportargs = array('user'=>$user->id);
1083         if (!empty($course->id)) {
1084             $reportargs['id'] = $course->id;
1085         } else {
1086             $reportargs['id'] = SITEID;
1087         }
1088         if ($viewreports || has_capability('coursereport/outline:view', $coursecontext)) {
1089             $reporttab->add(get_string('outlinereport'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'outline'))));
1090             $reporttab->add(get_string('completereport'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'complete'))));
1091         }
1092         
1093         if ($viewreports || has_capability('coursereport/log:viewtoday', $coursecontext)) {
1094             $reporttab->add(get_string('todaylogs'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'todaylogs'))));
1095         }
1097         if ($viewreports || has_capability('coursereport/log:view', $coursecontext)) {
1098             $reporttab->add(get_string('alllogs'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'alllogs'))));
1099         }
1101         if (!empty($CFG->enablestats)) {
1102             if ($viewreports || has_capability('coursereport/stats:view', $coursecontext)) {
1103                 $reporttab->add(get_string('stats'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'stats'))));
1104             }
1105         }
1107         $gradeaccess = false;
1108         if (has_capability('moodle/grade:viewall', $coursecontext)) {
1109             //ok - can view all course grades
1110             $gradeaccess = true;
1111         } else if ($course->showgrades) {
1112             if ($iscurrentuser && has_capability('moodle/grade:view', $coursecontext)) {
1113                 //ok - can view own grades
1114                 $gradeaccess = true;
1115             } else if (has_capability('moodle/grade:viewall', $usercontext)) {
1116                 // ok - can view grades of this user - parent most probably
1117                 $gradeaccess = true;
1118             } else if ($anyreport) {
1119                 // ok - can view grades of this user - parent most probably
1120                 $gradeaccess = true;
1121             }
1122         }
1123         if ($gradeaccess) {
1124             $reporttab->add(get_string('grade'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'grade'))));
1125         }
1127         // Check the number of nodes in the report node... if there are none remove
1128         // the node
1129         if (count($reporttab->children)===0) {
1130             $usernode->remove_child($reporttab);
1131         }
1133         // If the user is the current user add the repositories for the current user
1134         if ($iscurrentuser) {
1135             require_once($CFG->dirroot . '/repository/lib.php');
1136             $editabletypes = repository::get_editable_types($usercontext);
1137             if (!empty($editabletypes)) {
1138                 $usernode->add(get_string('repositories', 'repository'), new moodle_url('/repository/manage_instances.php', array('contextid', $usercontext->id)));
1139             }
1140         }
1141         return true;
1142     }
1144     /**
1145      * Adds the provided user to an array and when the navigation is generated
1146      * it is extended for this user.
1147      * 
1148      * @param object $user
1149      */
1150     public function extend_for_user($user) {
1151         $this->extendforuser[] = $user;
1152     }
1154     /**
1155      * Called by the initalise methods if the context was system or category
1156      *
1157      * @param bool $lookforid If system context then we dont want ID because
1158      *      it could be userid, courseid, or anything else
1159      * @return int The depth to the active(requested) node
1160      */
1161     protected function load_for_category($lookforid=true) {
1162         global $PAGE, $CFG;
1163         $id = optional_param('id', null);
1164         if ($lookforid && $id!==null) {
1165             if (!empty($CFG->navshowallcourses)) {
1166                 $this->load_categories();
1167             }
1168             $this->load_categories($id);
1169             $depth = $this->find_child_depth($id);
1170         } else {
1171             $depth = $this->load_categories();
1172         }
1173         return $depth;
1174     }
1176     /**
1177      * Called by the initialise methods if the context was course
1178      *
1179      * @return int The depth to the active(requested) node
1180      */
1181     protected function load_for_course() {
1182         global $PAGE, $CFG, $USER;
1183         $keys = array();
1184         if (!empty($CFG->navshowallcourses)) {
1185             $this->load_categories();
1186         }
1187         $depth = $this->load_course_categories($keys);
1188         $depth += $this->load_course($keys);
1189         if (!$this->format_display_course_content($PAGE->course->format)) {
1190             $child = $this->get_by_path($keys);
1191             if ($child!==false) {
1192                 $child->nodetype = self::NODETYPE_LEAF;
1193             }
1194             return $depth;
1195         }
1197         if (isloggedin() && has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $PAGE->course->id))) {
1198             $depth += $this->load_course_activities($keys);
1199             $depth += $this->load_course_sections($keys);
1200         }
1201         return $depth;
1202     }
1204     /**
1205      * Check whether the course format defines a display_course_content function
1206      * that can be used to toggle whether or not to display course content
1207      *
1208      * $default is set to true, which may seem counter productive, however it ensures
1209      * backwards compatibility for course types that havn't yet defined the callback
1210      *
1211      * @param string $format
1212      * @param bool $default
1213      * @return bool
1214      */
1215     protected function format_display_course_content($format, $default=true) {
1216         global $CFG;
1217         $formatlib = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
1218         if (file_exists($formatlib)) {
1219             require_once($formatlib);
1220             $displayfunc = 'callback_'.$format.'_display_content';
1221             if (function_exists($displayfunc) && !$displayfunc()) {
1222                 return $displayfunc();
1223             }
1224         }
1225         return $default;
1226     }
1228     /**
1229      * Internal method to load course activities into the global navigation structure
1230      * Course activities are activities that are in section 0
1231      *
1232      * @param array $keys By reference
1233      */
1234     protected function load_course_activities(&$keys, $course=null) {
1235         global $PAGE, $CFG, $FULLME;
1237         if ($course === null) {
1238             $course = $PAGE->course;
1239         }
1241         if (!$this->cache->compare('modinfo'.$course->id, $course->modinfo, false)) {
1242             $this->cache->{'modinfo'.$course->id} = get_fast_modinfo($course);
1243         }
1244         $modinfo =  $this->cache->{'modinfo'.$course->id};
1246         if (!$this->cache->cached('canviewhiddenactivities')) {
1247             $this->cache->canviewhiddenactivities = has_capability('moodle/course:viewhiddenactivities', $this->context);
1248         }
1249         $viewhiddenactivities = $this->cache->canviewhiddenactivities;
1251         $labelformatoptions = new object();
1252         $labelformatoptions->noclean = true;
1253         $labelformatoptions->para = false;
1255         foreach ($modinfo->cms as $module) {
1256             if ($module->sectionnum!='0' || (!$viewhiddenactivities && !$module->visible)) {
1257                 continue;
1258             }
1259             $icon = null;
1260             if ($module->icon) {
1261                 $icon = new pix_icon($module->icon, '', $module->iconcomponent);
1262             } else {
1263                 $icon = new pix_icon('icon', '', $module->modname);
1264             }
1265             $url = new moodle_url('/mod/'.$module->modname.'/view.php', array('id'=>$module->id));
1266             $this->add_to_path($keys, $module->id, $module->name, $module->name, self::TYPE_ACTIVITY, $url, $icon);
1267             $child = $this->find_child($module->id, self::TYPE_ACTIVITY);
1268             if ($child != false) {
1269                 $child->title(get_string('modulename', $module->modname));
1270                 if ($this->module_extends_navigation($module->modname)) {
1271                     $child->nodetype = self::NODETYPE_BRANCH;
1272                 }
1273                 if (!$module->visible) {
1274                     $child->hidden = true;
1275                 }
1276             }
1277         }
1278     }
1279     /**
1280      * Internal function to load the activities within sections
1281      *
1282      * @param array $keys By reference
1283      */
1284     protected function load_section_activities(&$keys, $singlesectionid=false, $course=null) {
1285         global $PAGE, $CFG, $FULLME;
1287         if ($course === null) {
1288             $course = $PAGE->course;
1289         }
1291         if (!$this->cache->compare('modinfo'.$course->id, $course->modinfo, false)) {
1292             $this->cache->{'modinfo'.$course->id} = get_fast_modinfo($course);
1293         }
1294         $modinfo =  $this->cache->{'modinfo'.$course->id};
1296         if (!$this->cache->cached('coursesections'.$course->id)) {
1297             $this->cache->{'coursesections'.$course->id} = get_all_sections($course->id);
1298         }
1299         $sections = $this->cache->{'coursesections'.$course->id};
1301         if (!$this->cache->cached('canviewhiddenactivities')) {
1302             $this->cache->canviewhiddenactivities = has_capability('moodle/course:viewhiddenactivities', $this->context);
1303         }
1304         $viewhiddenactivities = $this->cache->viewhiddenactivities;
1306         $labelformatoptions = new object();
1307         $labelformatoptions->noclean = true;
1308         $labelformatoptions->para = false;
1310         foreach ($modinfo->cms as $module) {
1311             if ($module->sectionnum=='0' || (!$viewhiddenactivities && !$module->visible) || ($singlesectionid!=false && $module->sectionnum!==$singlesectionid)) {
1312                 continue;
1313             }
1314             $icon = null;
1315             if ($module->icon) {
1316                 $icon = new pix_icon($module->icon, '', $module->iconcomponent);
1317             } else {
1318                 $icon = new pix_icon('icon', '', $module->modname);
1319             }
1320             $url = new moodle_url('/mod/'.$module->modname.'/view.php', array('id'=>$module->id));
1322             $path = $keys;
1323             if ($course->id !== SITEID) {
1324                 $path[] = $sections[$module->sectionnum]->id;
1325             }
1326             $this->add_to_path($path, $module->id, $module->name, $module->name, navigation_node::TYPE_ACTIVITY, $url, $icon);
1327             $child = $this->find_child($module->id, navigation_node::TYPE_ACTIVITY);
1328             if ($child != false) {
1329                 $child->title(get_string('modulename', $module->modname));
1330                 if (!$module->visible) {
1331                     $child->hidden = true;
1332                 }
1333                 if ($this->module_extends_navigation($module->modname)) {
1334                     $child->nodetype = self::NODETYPE_BRANCH;
1335                 }
1336             }
1337         }
1338     }
1340     /**
1341      * Check if a given module has a method to extend the navigation
1342      *
1343      * @param string $modname
1344      * @return bool
1345      */
1346     protected function module_extends_navigation($modname) {
1347         global $CFG;
1348         if ($this->cache->cached($modname.'_extends_navigation')) {
1349             return $this->cache->{$modname.'_extends_navigation'};
1350         }
1351         $file = $CFG->dirroot.'/mod/'.$modname.'/lib.php';
1352         $function = $modname.'_extend_navigation';
1353         if (function_exists($function)) {
1354             $this->cache->{$modname.'_extends_navigation'} = true;
1355             return true;
1356         } else if (file_exists($file)) {
1357             require_once($file);
1358             if (function_exists($function)) {
1359                 $this->cache->{$modname.'_extends_navigation'} = true;
1360                 return true;
1361             }
1362         }
1363         $this->cache->{$modname.'_extends_navigation'} = false;
1364         return false;
1365     }
1366     /**
1367      * Load the global navigation structure for an activity
1368      *
1369      * @return int
1370      */
1371     protected function load_for_activity() {
1372         global $PAGE, $DB, $CFG;
1373         $keys = array();
1375         $sectionnum = false;
1376         if (!empty($PAGE->cm->section)) {
1377             $section = $DB->get_record('course_sections', array('id'=>$PAGE->cm->section));
1378             if (!empty($section->section)) {
1379                 $sectionnum = $section->section;
1380             }
1381         }
1383         if (!empty($CFG->navshowallcourses)) {
1384             $this->load_categories();
1385         }
1387         $depth = $this->load_course_categories($keys);
1388         $depth += $this->load_course($keys);
1389         $depth += $this->load_course_activities($keys);
1390         $depth += $this->load_course_sections($keys);
1391         $depth += $this->load_section_activities($keys,$sectionnum);
1392         $depth += $this->load_activity($keys);
1393         return $depth;
1394     }
1396     /**
1397      * This function loads any navigation items that might exist for an activity
1398      * by looking for and calling a function within the modules lib.php
1399      *
1400      * @param int $instanceid
1401      * @return void
1402      */
1403     protected function load_activity($keys) {
1404         global $CFG, $PAGE;
1406         if (!$PAGE->cm && $this->context->contextlevel == CONTEXT_MODULE && $this->context->instanceid) {
1407             // This is risky but we have no other choice... we need that module and the module
1408             // itself hasn't set PAGE->cm (usually set by require_login)
1409             // Chances are this is a front page module.
1410             $cm = get_coursemodule_from_id(false, $this->context->instanceid);
1411             if ($cm) {
1412                 $cm->context = $this->context;
1413                 $PAGE->set_cm($cm, $PAGE->course);
1414             } else {
1415                 debugging('The module has not been set against the page but we are attempting to generate module specific information for navigation', DEBUG_DEVELOPER);
1416                 return;
1417             }
1418         }
1420         $node = $this->find_child($PAGE->cm->id, self::TYPE_ACTIVITY);
1421         if ($node) {
1422             $node->make_active();
1423             if (!isset($PAGE->course->context)) {
1424                 // If we get here chances we are on a front page module
1425                 $this->context = $PAGE->context;
1426             } else {
1427                 $this->context = $PAGE->course->context;
1428             }
1429             $file = $CFG->dirroot.'/mod/'.$PAGE->activityname.'/lib.php';
1430             $function = $PAGE->activityname.'_extend_navigation';
1432             if (empty($PAGE->cm->context)) {
1433                 $PAGE->cm->context = get_context_instance(CONTEXT_MODULE, $PAGE->cm->instance);
1434             }
1436             if (file_exists($file)) {
1437                 require_once($file);
1438                 if (function_exists($function)) {
1439                     $function($node, $PAGE->course, $PAGE->activityrecord, $PAGE->cm);
1440                 }
1441             }
1442         }
1443     }
1445     /**
1446      * Recursively adds an array of category objexts to the path provided by $keys
1447      *
1448      * @param array $keys An array of keys representing the path to add to
1449      * @param array $categories An array of [nested] categories to add
1450      * @param int $depth The current depth, this ensures we don't generate more than
1451      *      we need to
1452      */
1453     protected function add_categories(&$keys, $categories, $depth=0) {
1454         if (is_array($categories) && count($categories)>0) {
1455             foreach ($categories as $category) {
1456                 $url = new moodle_url('/course/category.php', array('id'=>$category->id, 'categoryedit'=>'on', 'sesskey'=>sesskey()));
1457                 $categorykey = $this->add_to_path($keys,  $category->id, $category->name, $category->name, self::TYPE_CATEGORY, $url);
1458                 if ($depth < $this->depthforward) {
1459                     $this->add_categories(array_merge($keys, array($categorykey)), $category->id, $depth+1);
1460                 }
1461             }
1462         }
1463     }
1465     /**
1466      * This function adds a category to the nav tree based on the categories path
1467      *
1468      * @param stdClass $category
1469      */
1470     protected function add_category_by_path($category) {
1471         $url = new moodle_url('/course/category.php', array('id'=>$category->id, 'categoryedit'=>'on', 'sesskey'=>sesskey()));
1472         $keys = explode('/',trim($category->path,'/ '));
1473         // Check this category hadsn't already been added
1474         if (!$this->get_by_path($keys)) {
1475             $currentcategory = array_pop($keys);
1476             $categorykey = $this->add_to_path($keys,  $category->id, $category->name, $category->name, self::TYPE_CATEGORY, $url);
1477         } else {
1478             $currentcategory = array_pop($keys);
1479             $categorykey = $currentcategory.':'.self::TYPE_CATEGORY;
1480         }
1481         return $categorykey;
1482     }
1484     /**
1485      * Adds an array of courses to thier correct categories if the categories exist
1486      *
1487      * @param array $courses An array of course objects
1488      * @param int $categoryid An override to add the courses to
1489      * @return bool
1490      */
1491     public function add_courses($courses, $categoryid=null) {
1492         global $SITE;
1493         if (is_array($courses) && count($courses)>0) {
1494             // Work out if the user can view hidden courses, just incase
1495             if (!$this->cache->cached('canviewhiddencourses')) {
1496                 $this->cache->canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $this->context);
1497             }
1498             $canviewhidden = $this->cache->canviewhiddencourses;
1499             $expandcourse = $this->can_display_type(self::TYPE_SECTION);
1500             foreach ($courses as $course) {
1501                 // Check if the user can't view hidden courses and if the course is hidden, if so skip and continue
1502                 if ($course->id!=$SITE->id && !$canviewhidden && (!$course->visible || !course_parent_visible($course))) {
1503                     continue;
1504                 }
1505                 // Process this course into the nav structure
1506                 $url = new moodle_url('/course/view.php', array('id'=>$course->id));
1507                 if ($categoryid===null) {
1508                     $category = $this->find_child($course->category, self::TYPE_CATEGORY);
1509                 } else if ($categoryid === false) {
1510                     $category = $this;
1511                 } else {
1512                     $category = $this->find_child($categoryid);
1513                 }
1514                 if ($category!==false) {
1515                     // Check that this course hasn't already been added.
1516                     // This function only adds a skeleton and we don't want to overwrite
1517                     // a fully built course object
1518                     if (!$category->get($course->id, self::TYPE_COURSE)) {
1519                         $coursekey = $category->add($course->fullname, $url, self::TYPE_COURSE, $course->shortname, $course->id, new pix_icon('i/course', ''));
1520                         if (!$course->visible) {
1521                             $category->get($course->id)->hidden = true;
1522                         }
1523                         if ($expandcourse!==true) {
1524                             $category->get($course->id)->nodetype = self::NODETYPE_LEAF;
1525                         }
1526                     }
1527                 }
1528             }
1529         }
1530         return true;
1531     }
1533     /**
1534      * Loads the current course into the navigation structure
1535      *
1536      * Loads the current course held by $PAGE {@link moodle_page()} into the navigation
1537      * structure.
1538      * If the course structure has an appropriate display method then the course structure
1539      * will also be displayed.
1540      *
1541      * @param array $keys The path to add the course to
1542      * @return bool
1543      */
1544     protected function load_course(&$keys, $course=null) {
1545         global $PAGE, $CFG;
1546         if ($course===null) {
1547             $course = $PAGE->course;
1548         }
1549         if (is_object($course) && $course->id !== SITEID) {
1551             if (!$this->cache->cached('canviewhiddencourses')) {
1552                 $this->cache->canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $this->context);
1553             }
1554             $canviewhidden = $this->cache->canviewhiddencourses;
1556             if (!$canviewhidden && (!$course->visible || !course_parent_visible($course))) {
1557                 return;
1558             }
1559             $url = new moodle_url('/course/view.php', array('id'=>$course->id));
1560             $keys[] = $this->add_to_path($keys, $course->id, $course->fullname, $course->shortname, self::TYPE_COURSE, $url, new pix_icon('i/course', ''));
1561             $currentcourse = $this->find_child($course->id, self::TYPE_COURSE);
1562             if ($currentcourse!==false){
1563                 $currentcourse->make_active();
1564                 if (!$course->visible) {
1565                     $currentcourse->hidden = true;
1566                 }
1568                 //Participants
1569                 if (has_capability('moodle/course:viewparticipants', $this->context)) {
1570                     $participantskey = $currentcourse->add(get_string('participants'), new moodle_url('/user/index.php?id='.$course->id), self::TYPE_SETTING, get_string('participants'), 'participants');
1571                     $participants = $currentcourse->get($participantskey);
1572                     if ($participants) {
1574                         require_once($CFG->dirroot.'/blog/lib.php');
1576                         $currentgroup = groups_get_course_group($course, true);
1577                         if ($course->id == SITEID) {
1578                             $filterselect = '';
1579                         } else if ($course->id && !$currentgroup) {
1580                             $filterselect = $course->id;
1581                         } else {
1582                             $filterselect = $currentgroup;
1583                         }
1584                         $filterselect = clean_param($filterselect, PARAM_INT);
1586                         if ($CFG->bloglevel >= 3) {
1587                             $blogsurls = new moodle_url('/blog/index.php', array('courseid' => $filterselect));
1588                             $participants->add(get_string('blogs','blog'), $blogsurls->out());
1589                         }
1591                         if (!empty($CFG->enablenotes) && (has_capability('moodle/notes:manage', $this->context) || has_capability('moodle/notes:view', $this->context))) {
1592                             $participants->add(get_string('notes','notes'), new moodle_url('/notes/index.php', array('filtertype'=>'course', 'filterselect'=>$filterselect)));
1593                         }
1594                     }
1595                 }
1597                 // View course reports
1598                 if (has_capability('moodle/site:viewreports', $this->context)) { // basic capability for listing of reports
1599                     $reportkey = $currentcourse->add(get_string('reports'), new moodle_url('/course/report.php', array('id'=>$course->id)), self::TYPE_SETTING, null, null, new pix_icon('i/stats', ''));
1600                     $reportnav = $currentcourse->get($reportkey);
1601                     if ($reportnav) {
1602                         $coursereports = get_plugin_list('coursereport');
1603                         foreach ($coursereports as $report=>$dir) {
1604                             $libfile = $CFG->dirroot.'/course/report/'.$report.'/lib.php';
1605                             if (file_exists($libfile)) {
1606                                 require_once($libfile);
1607                                 $reportfunction = $report.'_report_extend_navigation';
1608                                 if (function_exists($report.'_report_extend_navigation')) {
1609                                     $reportfunction($reportnav, $course, $this->context);
1610                                 }
1611                             }
1612                         }
1613                     }
1614                 }
1615             }
1617             if (!$this->can_display_type(self::TYPE_SECTION)) {
1618                 if ($currentcourse!==false) {
1619                     $currentcourse->nodetype = self::NODETYPE_LEAF;
1620                 }
1621                 return true;
1622             }
1623         }
1624     }
1625     /**
1626      * Loads the sections for a course
1627      *
1628      * @param array $keys By reference
1629      * @param stdClass $course The course that we are loading sections for
1630      */
1631     protected function load_course_sections(&$keys, $course=null) {
1632         global $PAGE, $CFG;
1633         if ($course === null) {
1634             $course = $PAGE->course;
1635         }
1636         $structurefile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
1637         $structurefunc = 'callback_'.$course->format.'_load_content';
1638         if (function_exists($structurefunc)) {
1639             $structurefunc($this, $keys, $course);
1640         } else if (file_exists($structurefile)) {
1641             require_once $structurefile;
1642             if (function_exists($structurefunc)) {
1643                 $structurefunc($this, $keys, $course);
1644             } else {
1645                 $this->add_course_section_generic($keys, $course);
1646             }
1647         } else {
1648             $this->add_course_section_generic($keys, $course);
1649         }
1650     }
1651     /**
1652      * This function loads the sections for a course if no given course format
1653      * methods have been defined to do so. Thus generic
1654      *
1655      * @param array $keys By reference
1656      * @param stdClass $course The course object to load for
1657      * @param string $name String to use to describe the current section to the user
1658      * @param string $activeparam Request variable to look for to determine the current section
1659      * @return bool
1660      */
1661     public function add_course_section_generic(&$keys, $course=null, $name=null, $activeparam = null) {
1662         global $PAGE;
1664         if ($course === null) {
1665             $course = $PAGE->course;
1666         }
1668         $coursesecstr = 'coursesections'.$course->id;
1669         if (!$this->cache->cached($coursesecstr)) {
1670             $sections = get_all_sections($course->id);
1671             $this->cache->$coursesecstr = $sections;
1672         } else {
1673             $sections = $this->cache->$coursesecstr;
1674         }
1676         if (!$this->cache->compare('modinfo'.$course->id, $course->modinfo, false)) {
1677             $this->cache->{'modinfo'.$course->id} = get_fast_modinfo($course);
1678         }
1679         $modinfo =  $this->cache->{'modinfo'.$course->id};
1681         $depthforward = 0;
1682         if (!is_array($modinfo->sections)) {
1683             return $keys;
1684         }
1686         if ($name === null) {
1687             $name = get_string('topic');
1688         }
1690         if ($activeparam === null) {
1691             $activeparam = 'topic';
1692         }
1694         $coursenode = $this->find_child($course->id, navigation_node::TYPE_COURSE);
1695         if ($coursenode!==false) {
1696             $coursenode->action->param($activeparam,'0');
1697         }
1699         if (!$this->cache->cached('canviewhiddenactivities')) {
1700             $this->cache->canviewhiddenactivities = has_capability('moodle/course:viewhiddenactivities', $this->context);
1701         }
1702         $viewhiddenactivities = $this->cache->canviewhiddenactivities;
1704         if (!$this->cache->cached('canviewhiddensections')) {
1705             $this->cache->canviewhiddensections = has_capability('moodle/course:viewhiddensections', $this->context);
1706         }
1707         $viewhiddensections = $this->cache->canviewhiddensections;
1709         // MDL-20242 + MDL-21564
1710         $sections = array_slice($sections, 0, $course->numsections+1, true);
1712         foreach ($sections as $section) {
1713             if ((!$viewhiddensections && !$section->visible) || (!$this->showemptybranches && !array_key_exists($section->section, $modinfo->sections))) {
1714                 continue;
1715             }
1716             if ($section->section!=0) {
1717                 $sectionkeys = $keys;
1718                 $url = new moodle_url('/course/view.php', array('id'=>$course->id, $activeparam=>$section->section));
1719                 $this->add_to_path($sectionkeys, $section->id, $name.' '.$section->section, null, navigation_node::TYPE_SECTION, $url);
1720                 $sectionchild = $this->find_child($section->id, navigation_node::TYPE_SECTION);
1721                 if ($sectionchild !== false) {
1722                     $sectionchild->nodetype = self::NODETYPE_BRANCH;
1723                     if ($sectionchild->isactive) {
1724                         $this->load_section_activities($sectionkeys, $section->section);
1725                     }
1726                     if (!$section->visible) {
1727                         $sectionchild->hidden = true;
1728                     }
1729                 }
1730             }
1731         }
1732         return true;
1733     }
1735     /**
1736      * Check if we are permitted to display a given type
1737      *
1738      * @return bool True if we are, False otherwise
1739      */
1740     protected function can_display_type($type) {
1741         if (!is_null($this->expansionlimit) && $this->expansionlimit < $type) {
1742             return false;
1743         }
1744         return true;
1745     }
1747     /**
1748      * Loads the categories for the current course into the navigation structure
1749      *
1750      * @param array $keys Forced reference to and array to use for the keys
1751      * @return int The number of categories
1752      */
1753     protected function load_course_categories(&$keys) {
1754         global $PAGE;
1755         $categories = $PAGE->categories;
1756         if (is_array($categories) && count($categories)>0) {
1757             $categories = array_reverse($categories);
1758             foreach ($categories as $category) {
1759                 $key = $category->id.':'.self::TYPE_CATEGORY;
1760                 if (!$this->get_by_path(array_merge($keys, array($key)))) {
1761                     $url = new moodle_url('/course/category.php', array('id'=>$category->id, 'categoryedit'=>'on', 'sesskey'=>sesskey()));
1762                     $keys[] = $this->add_to_path($keys, $category->id, $category->name, $category->name, self::TYPE_CATEGORY, $url);
1763                 } else {
1764                     $keys[] = $key;
1765                 }
1766             }
1767         }
1768         return count($categories);
1769     }
1771     /**
1772      * This is called by load_for_category to load categories into the navigation structure
1773      *
1774      * @param int $categoryid The specific category to load
1775      * @return int The depth of categories that were loaded
1776      */
1777     protected function load_categories($categoryid=0) {
1778         global $CFG, $DB, $USER;
1780         if ($categoryid === 0 && $this->allcategoriesloaded) {
1781             return 0;
1782         }
1784         $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1786         // Cache capability moodle/site:config we use this in the next bit of code
1787         if (!$this->cache->cached('hassiteconfig')) {
1788             $this->cache->hassiteconfig = has_capability('moodle/site:config', $systemcontext);
1789         }
1791         // If the user is logged in (but not as a guest), doesnt have the site config capability,
1792         // and my courses havn't been disabled then we will show the user's courses in the
1793         // global navigation, otherwise we will show up to FRONTPAGECOURSELIMIT available courses
1794         if (isloggedin() && !$this->cache->hassiteconfig && !isguestuser() && empty($CFG->disablemycourses)) {
1795             if (!$this->cache->cached('mycourses')) {
1796                 $this->cache->mycourses = get_my_courses($USER->id);
1797             }
1798             $courses = $this->cache->mycourses;
1799         } else {
1800             // Check whether we have already cached the available courses
1801             if (!$this->cache->cached('availablecourses')) {
1802                 // Non-cached - get accessinfo
1803                 if (isset($USER->access)) {
1804                     $accessinfo = $USER->access;
1805                 } else {
1806                     $accessinfo = get_user_access_sitewide($USER->id);
1807                 }
1808                 // Get the available courses using get_user_courses_bycap
1809                 $this->cache->availablecourses = get_user_courses_bycap($USER->id, 'moodle/course:view',
1810                                                                         $accessinfo, true, 'c.sortorder ASC',
1811                                                                         array('fullname','visible', 'category'),
1812                                                                         FRONTPAGECOURSELIMIT);
1813             }
1814             // Cache the available courses for a refresh
1815             $courses = $this->cache->availablecourses;
1816         }
1818         // Iterate through all courses, and explode thier course category paths so that
1819         // we can retrieve all of the individual category id's that are required
1820         // to display the list of courses in the tree
1821         $categoryids = array();
1822         foreach ($courses as $course) {
1823             // If a category id has been specified and the current course is not within
1824             // that category or one of its children then skip this course
1825             if ($categoryid!==0 && !preg_match('#/('.$categoryid.')(/|$)#', $course->categorypath)) {
1826                 continue;
1827             }
1828             $categorypathids = explode('/',trim($course->categorypath,' /'));
1829             // If no category has been specified limit the depth we display immediatly to
1830             // that of the nav var depthforwards
1831             if ($categoryid===0 && count($categorypathids)>($this->depthforward+1) && empty($CFG->navshowallcourses)) {
1832                 $categorypathids = array_slice($categorypathids, 0, ($this->depthforward+1));
1833             }
1834             $categoryids = array_merge($categoryids, $categorypathids);
1835         }
1836         // Remove duplicate categories (and there will be a few)
1837         $categoryids = array_unique($categoryids);
1839         // Check whether we have some category ids to display and make sure that either
1840         // no category has been specified ($categoryid===0) or that the category that
1841         // has been specified is in the list.
1842         if (count($categoryids)>0 && ($categoryid===0 || in_array($categoryid, $categoryids))) {
1843             $catcachestr = 'categories'.join($categoryids);
1844             if (!$this->cache->cached($catcachestr)) {
1845                 $this->cache->{$catcachestr} = $DB->get_records_select('course_categories', 'id IN ('.join(',', $categoryids).')', array(), 'path ASC, sortorder ASC');
1846             }
1847             $categories = $this->cache->{$catcachestr};
1848             // Retrieve the nessecary categories and then proceed to add them to the tree
1849             foreach ($categories as $category) {
1850                 $this->add_category_by_path($category);
1851             }
1852             // Add the courses that were retrieved earlier to the
1853             $this->add_courses($courses);
1854         } else if ($categoryid === 0) {
1855             $keys = array();
1856             $categories = $DB->get_records('course_categories', array('parent' => $categoryid), 'sortorder ASC');
1857             $this->add_categories($keys, $categories);
1858             $this->add_courses($courses, $categoryid);
1859         }
1860         $this->allcategoriesloaded = true;
1861         return 0;
1862     }
1864     /**
1865      * This function marks the cache as volatile so it is cleared during shutdown
1866      */
1867     public function clear_cache() {
1868         $this->cache->volatile();
1869     }
1871     /**
1872      * Finds all expandable nodes whilst ensuring that expansion limit is respected
1873      *
1874      * @param array $expandable A reference to an array that will be populated as
1875      * we go.
1876      */
1877     public function find_expandable(&$expandable) {
1878         parent::find_expandable($expandable, $this->expansionlimit);
1879     }
1881     /**
1882      * Loads categories that contain no courses into the structure.
1883      *
1884      * These categories would normally be skipped, as such this function is purely
1885      * for the benefit of code external to navigationlib
1886      */
1887     public function load_empty_categories() {
1888         $categories = array();
1889         $categorynames = array();
1890         $categoryparents = array();
1891         make_categories_list($categorynames, $categoryparents, '', 0, $category = NULL);
1892         foreach ($categorynames as $id=>$name) {
1893             if (!$this->find_child($id, self::TYPE_CATEGORY)) {
1894                 $category = new stdClass;
1895                 $category->id = $id;
1896                 if (array_key_exists($id, $categoryparents)) {
1897                     $category->path = '/'.join('/',array_merge($categoryparents[$id],array($id)));
1898                     $name = explode('/', $name);
1899                     $category->name = join('/', array_splice($name, count($categoryparents[$id])));
1900                 } else {
1901                     $category->path = '/'.$id;
1902                     $category->name = $name;
1903                 }
1904                 $this->add_category_by_path($category);
1905             }
1906         }
1907     }
1909     public function collapse_course_categories() {
1910         $categories = $this->get_children_by_type(self::TYPE_CATEGORY);
1911         while (count($categories) > 0) {
1912             foreach ($categories as $category) {
1913                 $this->children = array_merge($this->children, $category->children);
1914                 $this->remove_child($category->key, self::TYPE_CATEGORY);
1915             }
1916             $categories = $this->get_children_by_type(self::TYPE_CATEGORY);
1917         }
1918     }
1921 /**
1922  * The limited global navigation class used for the AJAX extension of the global
1923  * navigation class.
1924  *
1925  * The primary methods that are used in the global navigation class have been overriden
1926  * to ensure that only the relevant branch is generated at the root of the tree.
1927  * This can be done because AJAX is only used when the backwards structure for the
1928  * requested branch exists.
1929  * This has been done only because it shortens the amounts of information that is generated
1930  * which of course will speed up the response time.. because no one likes laggy AJAX.
1931  *
1932  * @package moodlecore
1933  * @subpackage navigation
1934  * @copyright 2009 Sam Hemelryk
1935  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1936  */
1937 class global_navigation_for_ajax extends global_navigation {
1938     /**
1939      * Initialise the limited navigation object, calling it to auto generate
1940      *
1941      * This function can be used to initialise the global navigation object more
1942      * flexibly by providing a couple of overrides.
1943      * This is used when the global navigation is being generated without other fully
1944      * initialised Moodle objects
1945      *
1946      * @param int $branchtype What to load for e.g. TYPE_SYSTEM
1947      * @param int $id The instance id for what ever is being loaded
1948      * @return array An array of nodes that are expandable by AJAX
1949      */
1950     public function initialise($branchtype, $id) {
1951         global $DB;
1953         if ($this->initialised || during_initial_install()) {
1954             return $this->expandable;
1955         }
1957         // Branchtype will be one of navigation_node::TYPE_*
1958         switch ($branchtype) {
1959             case self::TYPE_CATEGORY :
1960                 require_login();
1961                 $depth = $this->load_category($id);
1962                 break;
1963             case self::TYPE_COURSE :
1964                 $course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST);
1965                 require_course_login($course);
1966                 $depth = $this->load_course($id);
1967                 break;
1968             case self::TYPE_SECTION :
1969                 $sql = 'SELECT c.*
1970                         FROM {course} c
1971                         LEFT JOIN {course_sections} cs ON cs.course = c.id
1972                         WHERE cs.id = ?';
1973                 $course = $DB->get_record_sql($sql, array($id), MUST_EXIST);
1974                 require_course_login($course);
1975                 $depth = $this->load_section($id);
1976                 break;
1977             case self::TYPE_ACTIVITY :
1978                 $cm = get_coursemodule_from_id(false, $id, 0, false, MUST_EXIST);
1979                 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1980                 require_course_login($course, true, $cm);
1981                 $depth = $this->load_activity($id);
1982                 break;
1983             default:
1984                 return $this->expandable;
1985                 break;
1986         }
1988         $this->collapse_at_depth($this->depthforward+$depth);
1989         $this->respect_forced_open();
1990         $this->expandable = array();
1991         $this->find_expandable($this->expandable);
1992         $this->initialised = true;
1993         return $this->expandable;
1994     }
1996     /**
1997      * Loads the content (sub categories and courses) for a given a category
1998      *
1999      * @param int $instanceid
2000      */
2001     protected function load_category($instanceid) {
2002         if (!$this->cache->cached('coursecatcontext'.$instanceid)) {
2003             $this->cache->{'coursecatcontext'.$instanceid} = get_context_instance(CONTEXT_COURSECAT, $instanceid);
2004         }
2005         $this->context = $this->cache->{'coursecatcontext'.$instanceid};
2006         $this->load_categories($instanceid);
2007     }
2009     /**
2010      * Use the instance id to load a course
2011      *
2012      * {@link global_navigation::load_course()}
2013      * @param int $instanceid
2014      */
2015     protected function load_course($instanceid) {
2016         global $DB, $PAGE;
2018         if (!$this->cache->cached('course'.$instanceid)) {
2019             if ($PAGE->course->id == $instanceid) {
2020                 $this->cache->{'course'.$instanceid} = $PAGE->course;
2021             } else {
2022                 $this->cache->{'course'.$instanceid} = $DB->get_record('course', array('id'=>$instanceid), '*', MUST_EXIST);
2023             }
2024         }
2025         $course = $this->cache->{'course'.$instanceid};
2027         if (!$this->format_display_course_content($course->format)) {
2028             return true;
2029         }
2031         if (!$this->cache->cached('coursecontext'.$course->id)) {
2032             $this->cache->{'coursecontext'.$course->id} = get_context_instance(CONTEXT_COURSE, $course->id);
2033         }
2034         $this->context = $this->cache->{'coursecontext'.$course->id};
2036         $keys = array();
2037         parent::load_course($keys, $course);
2039         if (isloggedin() && has_capability('moodle/course:view', $this->context)) {
2040             if (!$this->cache->cached('course'.$course->id.'section0')) {
2041                 $this->cache->{'course'.$course->id.'section0'} = get_course_section('0', $course->id);
2042             }
2043             $section = $this->cache->{'course'.$course->id.'section0'};
2044             $this->load_section_activities($course, $section);
2045             if ($this->depthforward>0) {
2046                 $this->load_course_sections($keys, $course);
2047             }
2048         }
2049     }
2050     /**
2051      * Use the instance id to load a specific course section
2052      *
2053      * @param int $instanceid
2054      */
2055     protected function load_section($instanceid=0) {
2056         global $DB, $PAGE, $CFG;
2057         
2058         $section = $DB->get_record('course_sections', array('id'=>$instanceid), '*', MUST_EXIST);
2060         if (!$this->cache->cached('course'.$section->course)) {
2061             if ($PAGE->course->id == $section->course) {
2062                 $this->cache->{'course'.$section->course} = $PAGE->course;
2063             } else {
2064                 $this->cache->{'course'.$section->course} = $DB->get_record('course', array('id'=>$section->course), '*', MUST_EXIST);
2065             }
2066         }
2067         $course = $this->cache->{'course'.$section->course};
2069         if (!$this->cache->cached('coursecontext'.$course->id)) {
2070             $this->cache->{'coursecontext'.$course->id} = get_context_instance(CONTEXT_COURSE, $course->id);
2071         }
2072         $this->context = $this->cache->{'coursecontext'.$course->id};
2074         // Call the function to generate course section
2075         $keys = array();
2076         $structurefile = $CFG->dirroot.'/course/format/'.$course->format.'/navigation_format.php';
2077         $structurefunc = 'callback_'.$course->format.'_load_limited_section';
2078         if (function_exists($structurefunc)) {
2079             $sectionnode = $structurefunc($this, $keys, $course, $section);
2080         } else if (file_exists($structurefile)) {
2081             include $structurefile;
2082             if (function_exists($structurefunc)) {
2083                 $sectionnode = $structurefunc($this, $keys, $course, $section);
2084             } else {
2085                 $sectionnode = $this->limited_load_section_generic($keys, $course, $section);
2086             }
2087         } else {
2088             $sectionnode = $this->limited_load_section_generic($keys, $course, $section);
2089         }
2090         if ($this->depthforward>0) {
2091             $this->load_section_activities($course, $section);
2092         }
2093     }
2094     /**
2095      * This function is called if there is no specific course format function set
2096      * up to load sections into the global navigation.
2097      *
2098      * Note that if you are writing a course format you can call this function from your
2099      * callback function if you don't want to load anything special but just specify the
2100      * GET argument that identifies the current section as well as the string that
2101      * can be used to describve the section. e.g. weeks or topic
2102      *
2103      * @param array $keys
2104      * @param stdClass $course
2105      * @param stdClass $section
2106      * @param string $name
2107      * @param string $activeparam
2108      * @return navigation_node|bool
2109      */
2110     public function limited_load_section_generic($keys, $course, $section, $name=null, $activeparam = 'topic') {
2111         global $PAGE, $CFG;
2113         if ($name === null) {
2114             $name = get_string('topic');
2115         }
2117         if (!$this->cache->cached('canviewhiddensections')) {
2118             $this->cache->canviewhiddensections = has_capability('moodle/course:viewhiddensections', $this->context);
2119         }
2120         $viewhiddensections = $this->cache->canviewhiddensections;
2122         if (!$viewhiddensections && !$section->visible) {
2123             return false;
2124         }
2125         if ($section->section!=0) {
2126             $url = new moodle_url('/course/view.php', array('id'=>$course->id, $activeparam=>$section->id));
2127             $keys[] = $this->add_to_path($keys, $section->id, $name.' '.$section->section, null, navigation_node::TYPE_SECTION, $url);
2128             $sectionchild = $this->find_child($section->id, navigation_node::TYPE_SECTION);
2129             if ($sectionchild !== false) {
2130                 $sectionchild->nodetype = self::NODETYPE_BRANCH;
2131                 $sectionchild->make_active();
2132                 if (!$section->visible) {
2133                     $sectionchild->hidden = true;
2134                 }
2135                 return $sectionchild;
2136             }
2137         }
2138         return false;
2139     }
2141     /**
2142      * This function is used to load a course sections activities
2143      *
2144      * @param stdClass $course
2145      * @param stdClass $section
2146      * @return void
2147      */
2148     protected function load_section_activities($course, $section) {
2149         global $CFG;
2150         if (!is_object($section)) {
2151             return;
2152         }
2153         if ($section->section=='0') {
2154             $keys = array($section->course);
2155         } else {
2156             $keys = array($section->id);
2157         }
2159         $modinfo = get_fast_modinfo($course);
2161         if (!$this->cache->cached('canviewhiddenactivities')) {
2162             $this->cache->canviewhiddenactivities = has_capability('moodle/course:viewhiddenactivities', $this->context);
2163         }
2164         $viewhiddenactivities = $this->cache->canviewhiddenactivities;
2166         foreach ($modinfo->cms as $module) {
2167             if ((!$viewhiddenactivities && !$module->visible) || $module->sectionnum != $section->section) {
2168                 continue;
2169             }
2170             $icon = null;
2171             if ($module->icon) {
2172                 $icon = new pix_icon($module->icon, '', $module->iconcomponent);
2173             } else {
2174                 $icon = new pix_icon('icon', '', $module->modname);
2175             }
2176             $url = new moodle_url('/mod/'.$module->modname.'/view.php', array('id'=>$module->id));
2177             $this->add_to_path($keys, $module->id, $module->name, $module->name, navigation_node::TYPE_ACTIVITY, $url, $icon);
2178             $child = $this->find_child($module->id, navigation_node::TYPE_ACTIVITY);
2179             if ($child != false) {
2180                 $child->title(get_string('modulename', $module->modname));
2181                 if (!$module->visible) {
2182                     $child->hidden = true;
2183                 }
2184                 if ($this->module_extends_navigation($module->modname)) {
2185                     $child->nodetype = self::NODETYPE_BRANCH;
2186                 }
2187             }
2188         }
2189     }
2191     /**
2192      * This function loads any navigation items that might exist for an activity
2193      * by looking for and calling a function within the modules lib.php
2194      *
2195      * @param int $instanceid
2196      * @return void
2197      */
2198     protected function load_activity($instanceid) {
2199         global $CFG, $PAGE;
2201         $this->context = get_context_instance(CONTEXT_COURSE, $PAGE->course->id);
2202         $key = $this->add($PAGE->activityname, null, self::TYPE_ACTIVITY, null, $instanceid);
2204         $file = $CFG->dirroot.'/mod/'.$PAGE->activityname.'/lib.php';
2205         $function = $PAGE->activityname.'_extend_navigation';
2207         if (file_exists($file)) {
2208             require_once($file);
2209             if (function_exists($function)) {
2210                 $function($this->get($key), $PAGE->course, $PAGE->activityrecord, $PAGE->cm);
2211             }
2212         }
2213     }
2216 /**
2217  * Navbar class
2218  *
2219  * This class is used to manage the navbar, which is initialised from the navigation
2220  * object held by PAGE
2221  *
2222  * @package moodlecore
2223  * @subpackage navigation
2224  * @copyright 2009 Sam Hemelryk
2225  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2226  */
2227 class navbar extends navigation_node {
2228     /** @var bool */
2229     protected $initialised = false;
2230     /** @var mixed */
2231     protected $keys = array();
2232     /** @var null|string */
2233     protected $content = null;
2234     /** @var page object */
2235     protected $page;
2236     /** @var bool */
2237     protected $ignoreactive = false;
2238     /** @var bool */
2239     protected $duringinstall = false;
2240     /** @var bool */
2241     protected $hasitems = false;
2243     /**
2244      * The almighty constructor
2245      */
2246     public function __construct(&$page) {
2247         global $CFG;
2248         if (during_initial_install()) {
2249             $this->duringinstall = true;
2250             return false;
2251         }
2252         $this->page = $page;
2253         $this->text = get_string('home');
2254         $this->shorttext = get_string('home');
2255         $this->action = new moodle_url($CFG->wwwroot);
2256         $this->nodetype = self::NODETYPE_BRANCH;
2257         $this->type = self::TYPE_SYSTEM;
2258     }
2260     /**
2261      * Quick check to see if the navbar will have items in.
2262      *
2263      * @return bool Returns true if the navbar will have items, false otherwise
2264      */
2265     public function has_items() {
2266         if ($this->duringinstall) {
2267             return false;
2268         } else if ($this->hasitems !== false) {
2269             return true;
2270         }
2271         $this->page->navigation->initialise();
2273         $activenodefound = ($this->page->navigation->contains_active_node() ||
2274                             $this->page->settingsnav->contains_active_node() ||
2275                             $this->page->navigation->reiterate_active_nodes(URL_MATCH_BASE) ||
2276                             $this->page->settingsnav->reiterate_active_nodes(URL_MATCH_BASE));
2278         $outcome = (count($this->page->navbar->children)>0 || (!$this->ignoreactive && $activenodefound));
2279         $this->hasitems = $outcome;
2280         return $outcome;
2281     }
2283     public function ignore_active($setting=true) {
2284         $this->ignoreactive = ($setting);
2285     }
2287     /**
2288      * Generate the XHTML content for the navbar and return it
2289      *
2290      * We are lucky in that we can rely on PAGE->navigation for the structure
2291      * we simply need to look for the `active` path through the tree. We make this
2292      * easier by calling {@link strip_down_to_final_active()}.
2293      *
2294      * This function should in the future be refactored to work with a copy of the
2295      * PAGE->navigation object and strip it down to just this the active nodes using
2296      * a function that could be written again navigation_node called something like
2297      * strip_inactive_nodes(). I wrote this originally but currently the navigation
2298      * object is managed via references.
2299      *
2300      * @return string XHTML navbar content
2301      */
2302     public function content() {
2303         if ($this->duringinstall) {
2304             return '';
2305         }
2307         // Make sure that navigation is initialised
2308         if (!$this->has_items()) {
2309             return '';
2310         }
2312         if ($this->content !== null) {
2313             return $this->content;
2314         }
2316         // For screen readers
2317         $output = get_accesshide(get_string('youarehere','access'), 'h2').html_writer::start_tag('ul');
2319         // Check if navigation contains the active node
2320         if (!$this->ignoreactive && $this->page->navigation->contains_active_node()) {
2321             // Parse the navigation tree to get the active node
2322             $output .= $this->parse_branch_to_html($this->page->navigation->children, true);
2323         } else if (!$this->ignoreactive && $this->page->settingsnav->contains_active_node()) {
2324             // Parse the settings navigation to get the active node
2325             $output .= $this->parse_branch_to_html($this->page->settingsnav->children, true);
2326         } else {
2327             $output .= $this->parse_branch_to_html($this, true);
2328         }
2330         if (!empty($this->children)) {
2331             // Add the custom children
2332             $output .= $this->parse_branch_to_html($this->children, false, false);
2333         }
2335         $output .= html_writer::end_tag('ul');
2336         $this->content = $output;
2337         return $output;
2338     }
2339     /**
2340      * This function converts an array of nodes into XHTML for the navbar
2341      *
2342      * @param array $navarray
2343      * @param bool $firstnode
2344      * @return string HTML
2345      */
2346     protected function parse_branch_to_html($navarray, $firstnode=true) {
2347         global $CFG;
2348         $separator = get_separator();
2349         $output = '';
2350         if ($firstnode===true) {
2351             // If this is the first node add the class first and display the
2352             // navbar properties (normally sitename)
2353             $output .= html_writer::tag('li', parent::content(true), array('class'=>'first'));
2354         }
2355         $count = 0;
2356         if (!is_array($navarray)) {
2357             return $output;
2358         }
2359         // Iterate the navarray and display each node
2360         while (count($navarray)>0) {
2361             // Sanity check make sure we don't display WAY too much information
2362             // on the navbar. If we get to 20 items just stop!
2363             $count++;
2364             if ($count>20) {
2365                 // Maximum number of nodes in the navigation branch
2366                 return $output;
2367             }
2368             $child = false;
2369             // Iterate the nodes in navarray and find the active node
2370             foreach ($navarray as $tempchild) {
2371                 if ($tempchild->isactive || $tempchild->contains_active_node()) {
2372                     $child = $tempchild;
2373                     // We've got the first child we can break out of this foreach
2374                     break;
2375                 }
2376             }
2377             // Check if we found the child
2378             if ($child===false || $child->mainnavonly) {
2379                 // Set navarray to an empty array so that we complete the while
2380                 $navarray = array();
2381             } else {
2382                 // We found an/the active node, set navarray to it's children so that
2383                 // we come back through this while with the children of the active node
2384                 $navarray = $child->children;
2385                 // If there are not more arrays being processed after this AND this is the last element
2386                 // then we want to set the action to null so that it is not used
2387                 if (empty($this->children) && (!$child->contains_active_node() || ($child->find_active_node()==false || $child->find_active_node()->mainnavonly))) {
2388                     $oldaction = $child->action;
2389                     $child->action = null;
2390                 }
2391                 if ($child->type !== navigation_node::TYPE_CATEGORY || !isset($CFG->navshowcategories) || !empty($CFG->navshowcategories)) {
2392                     // Now display the node
2393                     $output .= html_writer::tag('li', $separator.' '.$child->content(true));
2394                 }
2395                 if (isset($oldaction)) {
2396                     $child->action = $oldaction;
2397                 }
2398             }
2399         }
2401         // XHTML
2402         return $output;
2403     }
2404     /**
2405      * Add a new node to the navbar, overrides parent::add
2406      *
2407      * This function overrides {@link navigation_node::add()} so that we can change
2408      * the way nodes get added to allow us to simply call add and have the node added to the
2409      * end of the navbar
2410      *
2411      * @param string $text
2412      * @param string|moodle_url $action
2413      * @param int $type
2414      * @param string|int $key
2415      * @param string $shorttext
2416      * @param string $icon
2417      * @return string|int Identifier for this particular node
2418      */
2419     public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) {
2420         // Check if there are any keys in the objects keys array
2421         if (count($this->keys)===0) {
2422             // If there are no keys then we can use the add method
2423             $key = parent::add($text, $action, $type, $shorttext, $key, $icon);
2424         } else {
2425             $key = $this->add_to_path($this->keys, $key, $text, $shorttext, $type, $action, $icon);
2426         }
2427         $this->keys[] = $key;
2428         $child = $this->get_by_path($this->keys);
2429         if ($child!==false) {
2430             // This ensure that the child will be shown
2431             $child->make_active();
2432         }
2433         return $key;
2434     }
2437 /**
2438  * Class used to manage the settings option for the current page
2439  *
2440  * This class is used to manage the settings options in a tree format (recursively)
2441  * and was created initially for use with the settings blocks.
2442  *
2443  * @package moodlecore
2444  * @subpackage navigation
2445  * @copyright 2009 Sam Hemelryk
2446  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2447  */
2448 class settings_navigation extends navigation_node {
2449     /** @var stdClass */
2450     protected $context;
2451     /** @var cache */
2452     protected $cache;
2453     /** @var page object */
2454     protected $page;
2455     /**
2456      * Sets up the object with basic settings and preparse it for use
2457      */
2458     public function __construct(moodle_page &$page) {
2459         if (during_initial_install()) {
2460             return false;
2461         }
2462         $this->page = $page;
2463         // Initialise the main navigation. It is most important that this is done
2464         // before we try anything
2465         $this->page->navigation->initialise();
2466         // Initialise the navigation cache
2467         $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
2468     }
2469     /**
2470      * Initialise the settings navigation based on the current context
2471      *
2472      * This function initialises the settings navigation tree for a given context
2473      * by calling supporting functions to generate major parts of the tree.
2474      */
2475     public function initialise() {
2476         if (during_initial_install()) {
2477             return false;
2478         }
2479         $this->id = 'settingsnav';
2480         $this->context = $this->page->context;
2481         switch ($this->context->contextlevel) {
2482             case CONTEXT_SYSTEM:
2483                 $this->cache->volatile();
2484                 $adminkey = $this->load_administration_settings();
2485                 $settingskey = $this->load_user_settings(SITEID);
2486                 break;
2487             case CONTEXT_COURSECAT:
2488                 $adminkey = $this->load_administration_settings();
2489                 $adminnode = $this->get($adminkey);
2490                 if ($adminnode!==false) {
2491                     $adminnode->forceopen =  true;
2492                 }
2493                 $settingskey = $this->load_user_settings(SITEID);
2494                 break;
2495             case CONTEXT_COURSE:
2496             case CONTEXT_BLOCK:
2497                 if ($this->page->course->id!==SITEID) {
2498                     $coursekey = $this->load_course_settings();
2499                     $coursenode = $this->get($coursekey);
2500                     if ($coursenode!==false) {
2501                         $coursenode->forceopen =  true;
2502                     }
2503                     $settingskey = $this->load_user_settings($this->page->course->id);
2504                     $adminkey = $this->load_administration_settings();
2505                 } else {
2506                     $this->load_front_page_settings();
2507                     $settingskey = $this->load_user_settings(SITEID);
2508                     $adminkey = $this->load_administration_settings();
2509                 }
2510                 break;
2511             case CONTEXT_MODULE:
2512                 $modulekey = $this->load_module_settings();
2513                 $modulenode = $this->get($modulekey);
2514                 if ($modulenode!==false) {
2515                     $modulenode->forceopen =  true;
2516                 }
2517                 $coursekey = $this->load_course_settings();
2518                 $settingskey = $this->load_user_settings($this->page->course->id);
2519                 $adminkey = $this->load_administration_settings();
2520                 break;
2521             case CONTEXT_USER:
2522                 $settingskey = $this->load_user_settings($this->page->course->id);
2523                 $settingsnode = $this->get($settingskey);
2524                 if ($settingsnode!==false) {
2525                     $settingsnode->forceopen =  true;
2526                 }
2527                 if ($this->page->course->id!==SITEID) {
2528                     $coursekey = $this->load_course_settings();
2529                 }
2530                 $adminkey = $this->load_administration_settings();
2531                 break;
2532             default:
2533                 debugging('An unknown context has passed into settings_navigation::initialise', DEBUG_DEVELOPER);
2534                 break;
2535         }
2537         // Check if the user is currently logged in as another user
2538         if (session_is_loggedinas()) {
2539             // Get the actual user, we need this so we can display an informative return link
2540             $realuser = session_get_realuser();
2541             // Add the informative return to original user link
2542             $url = new moodle_url('/course/loginas.php',array('id'=>$this->page->course->id, 'return'=>1,'sesskey'=>sesskey()));
2543             $this->add(get_string('returntooriginaluser', 'moodle', fullname($realuser, true)), $url, self::TYPE_SETTING, null, null, new pix_icon('t/left', ''));
2544         }
2546         // Make sure the first child doesnt have proceed with hr set to true
2547         reset($this->children);
2548         current($this->children)->preceedwithhr = false;
2550         $this->remove_empty_root_branches();
2551         $this->respect_forced_open();
2552     }
2553     /**
2554      * Override the parent function so that we can add preceeding hr's and set a
2555      * root node class against all first level element
2556      *
2557      * It does this by first calling the parent's add method {@link navigation_node::add()}
2558      * and then proceeds to use the key to set class and hr
2559      *
2560      * @param string $text
2561      * @param sting|moodle_url $url
2562      * @param string $shorttext
2563      * @param string|int $key
2564      * @param int $type
2565      * @param string $icon
2566      * @return sting|int A key that can be used to reference the newly added node
2567      */
2568     public function add($text, $url=null, $type=null, $shorttext=null, $key=null, pix_icon $icon=null) {
2569         $key = parent::add($text, $url, $type, $shorttext, $key, $icon);
2570         $this->get($key)->add_class('root_node');
2571         $this->get($key)->preceedwithhr = true;
2572         return $key;
2573     }
2575     /**
2576      * This function allows the user to add something to the start of the settings
2577      * navigation, which means it will be at the top of the settings navigation block
2578      *
2579      * @param string $text
2580      * @param sting|moodle_url $url
2581      * @param string $shorttext
2582      * @param string|int $key
2583      * @param int $type
2584      * @param string $icon
2585      * @return sting|int A key that can be used to reference the newly added node
2586      */
2587     public function prepend($text, $url=null, $type=null, $shorttext=null, $key=null, pix_icon $icon=null) {
2588         $key = $this->add($text, $url, $type, $shorttext, $key, $icon);
2589         $children = $this->children;
2590         $this->children = array();
2591         $this->children[$key] = array_pop($children);
2592         foreach ($children as $k=>$child) {
2593             $this->children[$k] = $child;
2594             $this->get($k)->add_class('root_node');
2595             $this->get($k)->preceedwithhr = true;
2596         }
2597         return $key;
2598     }
2599     /**
2600      * Load the site administration tree
2601      *
2602      * This function loads the site administration tree by using the lib/adminlib library functions
2603      *
2604      * @param null|navigation_node $referencebranch A reference to a branch in the settings
2605      *      navigation tree
2606      * @param null|part_of_admin_tree $adminbranch The branch to add, if null generate the admin
2607      *      tree and start at the beginning
2608      * @return mixed A key to access the admin tree by
2609      */
2610     protected function load_administration_settings(navigation_node $referencebranch=null, part_of_admin_tree $adminbranch=null) {
2611         global $CFG;
2613         // Check if we are just starting to generate this navigation.
2614         if ($referencebranch === null) {
2616             if (!function_exists('admin_get_root')) {
2617                 require_once($CFG->dirroot.'/lib/adminlib.php');
2618             }
2619             $adminroot = admin_get_root(false, false);
2620             $branchkey = $this->add(get_string('administrationsite'), null, self::TYPE_SETTING);
2621             $referencebranch = $this->get($branchkey);
2622             foreach ($adminroot->children as $adminbranch) {
2623                 $this->load_administration_settings($referencebranch, $adminbranch);
2624             }
2626             if (!$this->contains_active_node()) {
2627                 $section = $this->page->url->param('section');
2628                 if ($current = $adminroot->locate($section, true)) {
2629                     if ($child = $this->find_child($current->name, self::TYPE_SETTING)) {
2630                         $child->make_active();
2631                     }
2632                 }
2633             }
2635             return $branchkey;
2636         } else if ($adminbranch->check_access()) {
2637             // We have a reference branch that we can access and is not hidden `hurrah`
2638             // Now we need to display it and any children it may have
2639             $url = null;
2640             $icon = null;
2641             if ($adminbranch instanceof admin_settingpage) {
2642                 $url = new moodle_url('/admin/settings.php', array('section'=>$adminbranch->name));
2643             } else if ($adminbranch instanceof admin_externalpage) {
2644                 $url = $adminbranch->url;
2645             }
2647             // Add the branch
2648             $branchkey = $referencebranch->add($adminbranch->visiblename, $url, self::TYPE_SETTING, null, $adminbranch->name, $icon);
2649             $reference = $referencebranch->get($branchkey);
2651             if ($adminbranch->is_hidden()) {
2652                 $reference->display = false;
2653             }
2655             // Check if we are generating the admin notifications and whether notificiations exist
2656             if ($adminbranch->name === 'adminnotifications' && admin_critical_warnings_present()) {
2657                 $reference->add_class('criticalnotification');
2658             }
2659             // Check if this branch has children
2660             if ($reference && isset($adminbranch->children) && is_array($adminbranch->children) && count($adminbranch->children)>0) {
2661                 foreach ($adminbranch->children as $branch) {
2662                     // Generate the child branches as well now using this branch as the reference
2663                     $this->load_administration_settings($reference, $branch);
2664                 }
2665             } else {
2666                 $reference->icon = new pix_icon('i/settings', '');
2667             }
2668         }
2669     }
2671     /**
2672      * Generate the list of modules for the given course.
2673      *
2674      * The array of resources and activities that can be added to a course is then
2675      * stored in the cache so that we can access it for anywhere.
2676      * It saves us generating it all the time
2677      *
2678      * <code php>
2679      * // To get resources:
2680      * $this->cache->{'course'.$courseid.'resources'}
2681      * // To get activities:
2682      * $this->cache->{'course'.$courseid.'activities'}
2683      * </code>
2684      *
2685      * @param stdClass $course The course to get modules for
2686      */
2687     protected function get_course_modules($course) {
2688         global $CFG;
2689         $mods = $modnames = $modnamesplural = $modnamesused = array();
2690         // This function is included when we include course/lib.php at the top
2691         // of this file
2692         get_all_mods($course->id, $mods, $modnames, $modnamesplural, $modnamesused);
2693         $resources = array();
2694         $activities = array();
2695         foreach($modnames as $modname=>$modnamestr) {
2696             if (!course_allowed_module($course, $modname)) {
2697                 continue;
2698             }
2700             $libfile = "$CFG->dirroot/mod/$modname/lib.php";
2701             if (!file_exists($libfile)) {
2702                 continue;
2703             }
2704             include_once($libfile);
2705             $gettypesfunc =  $modname.'_get_types';
2706             if (function_exists($gettypesfunc)) {
2707                 $types = $gettypesfunc();
2708                 foreach($types as $type) {
2709                     if (!isset($type->modclass) || !isset($type->typestr)) {
2710                         debugging('Incorrect activity type in '.$modname);
2711                         continue;
2712                     }
2713                     if ($type->modclass == MOD_CLASS_RESOURCE) {
2714                         $resources[html_entity_decode($type->type)] = $type->typestr;
2715                     } else {
2716                         $activities[html_entity_decode($type->type)] = $type->typestr;
2717                     }
2718                 }
2719             } else {
2720                 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
2721                 if ($archetype == MOD_ARCHETYPE_RESOURCE) {
2722                     $resources[$modname] = $modnamestr;
2723                 } else {
2724                     // all other archetypes are considered activity
2725                     $activities[$modname] = $modnamestr;
2726                 }
2727             }
2728         }
2729         $this->cache->{'course'.$course->id.'resources'} = $resources;
2730         $this->cache->{'course'.$course->id.'activities'} = $activities;
2731     }
2733     /**
2734      * This function loads the course settings that are available for the user
2735      *
2736      * @return bool|mixed Either false of a key to access the course tree by
2737      */
2738     protected function load_course_settings() {
2739         global $CFG, $USER, $SESSION;
2741         $course = $this->page->course;
2742         if (empty($course->context)) {
2743             if (!$this->cache->cached('coursecontext'.$course->id)) {
2744                 $this->cache->{'coursecontext'.$course->id} = get_context_instance(CONTEXT_COURSE, $course->id);   // Course context
2745             }
2746             $course->context = $this->cache->{'coursecontext'.$course->id};
2747         }
2748         if (!$this->cache->cached('canviewcourse'.$course->id)) {
2749             $this->cache->{'canviewcourse'.$course->id} = has_capability('moodle/course:view', $course->context);
2750         }
2751         if ($course->id === SITEID || !$this->cache->{'canviewcourse'.$course->id}) {
2752             return false;
2753         }
2755         $coursenode = $this->page->navigation->find_child($course->id, global_navigation::TYPE_COURSE);
2757         $coursenodekey = $this->add(get_string('courseadministration'), null, $coursenode->type, null, 'courseadmin');
2758         $coursenode = $this->get($coursenodekey);
2760         if (has_capability('moodle/course:update', $course->context)) {
2761             // Add the turn on/off settings
2762             $url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
2763             if ($this->page->user_is_editing()) {
2764                 $url->param('edit', 'off');
2765                 $editstring = get_string('turneditingoff');
2766             } else {
2767                 $url->param('edit', 'on');
2768                 $editstring = get_string('turneditingon');
2769             }
2770             $coursenode->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
2772             if ($this->page->user_is_editing()) {
2773                 // Add `add` resources|activities branches
2774                 $structurefile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
2775                 if (file_exists($structurefile)) {
2776                     require_once($structurefile);
2777                     $formatstring = call_user_func('callback_'.$course->format.'_definition');
2778                     $formatidentifier = optional_param(call_user_func('callback_'.$course->format.'_request_key'), 0, PARAM_INT);
2779                 } else {
2780                     $formatstring = get_string('topic');
2781                     $formatidentifier = optional_param('topic', 0, PARAM_INT);
2782                 }
2783                 if (!$this->cache->cached('coursesections'.$course->id)) {
2784                     $this->cache->{'coursesections'.$course->id} = get_all_sections($course->id);
2785                 }
2786                 $sections = $this->cache->{'coursesections'.$course->id};
2788                 $addresource = $this->get($this->add(get_string('addresource')));
2789                 $addactivity = $this->get($this->add(get_string('addactivity')));
2790                 if ($formatidentifier!==0) {
2791                     $addresource->forceopen = true;
2792                     $addactivity->forceopen = true;
2793                 }
2795                 if (!$this->cache->cached('course'.$course->id.'resources')) {
2796                     $this->get_course_modules($course);
2797                 }
2798                 $resources = $this->cache->{'course'.$course->id.'resources'};
2799                 $activities = $this->cache->{'course'.$course->id.'activities'};
2801                 $textlib = textlib_get_instance();
2803                 foreach ($sections as $section) {
2804                     if ($formatidentifier !== 0 && $section->section != $formatidentifier) {
2805                         continue;
2806                     }
2807                     $sectionurl = new moodle_url('/course/view.php', array('id'=>$course->id, $formatstring=>$section->section));
2808                     if ($section->section == 0) {
2809                         $sectionresources = $addresource->add(get_string('course'), $sectionurl, self::TYPE_SETTING);
2810                         $sectionactivities = $addactivity->add(get_string('course'), $sectionurl, self::TYPE_SETTING);
2811                     } else {
2812                         $sectionresources = $addresource->add($formatstring.' '.$section->section, $sectionurl, self::TYPE_SETTING);
2813                         $sectionactivities = $addactivity->add($formatstring.' '.$section->section, $sectionurl, self::TYPE_SETTING);
2814                     }
2815                     foreach ($resources as $value=>$resource) {
2816                         $url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'sesskey'=>sesskey(), 'section'=>$section->section));
2817                         $pos = strpos($value, '&type=');
2818                         if ($pos!==false) {
2819                             $url->param('add', $textlib->substr($value, 0,$pos));
2820                             $url->param('type', $textlib->substr($value, $pos+6));
2821                         } else {
2822                             $url->param('add', $value);
2823                         }
2824                         $addresource->get($sectionresources)->add($resource, $url, self::TYPE_SETTING);
2825                     }
2826                     $subbranch = false;
2827                     foreach ($activities as $activityname=>$activity) {
2828                         if ($activity==='--') {
2829                             $subbranch = false;
2830                             continue;
2831                         }
2832                         if (strpos($activity, '--')===0) {
2833                             $subbranch = $addactivity->get($sectionactivities)->add(trim($activity, '-'));
2834                             continue;
2835                         }
2836                         $url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'sesskey'=>sesskey(), 'section'=>$section->section));
2837                         $pos = strpos($activityname, '&type=');
2838                         if ($pos!==false) {
2839                             $url->param('add', $textlib->substr($activityname, 0,$pos));
2840                             $url->param('type', $textlib->substr($activityname, $pos+6));
2841                         } else {
2842                             $url->param('add', $activityname);
2843                         }
2844                         if ($subbranch !== false) {
2845                             $addactivity->get($sectionactivities)->get($subbranch)->add($activity, $url, self::TYPE_SETTING);
2846                         } else {
2847                             $addactivity->get($sectionactivities)->add($activity, $url, self::TYPE_SETTING);
2848                         }
2849                     }
2850                 }
2851             }
2853             // Add the course settings link
2854             $url = new moodle_url('/course/edit.php', array('id'=>$course->id));
2855             $coursenode->add(get_string('settings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
2856         }
2858         // Add assign or override roles if allowed
2859         if (has_capability('moodle/role:assign', $course->context)) {
2860             $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$course->context->id));
2861             $coursenode->add(get_string('assignroles', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2862         } else if (get_overridable_roles($course->context, ROLENAME_ORIGINAL)) {
2863             $url = new moodle_url('/admin/roles/override.php', array('contextid'=>$course->context->id));
2864             $coursenode->add(get_string('overridepermissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2865         }
2867         // Add view grade report is permitted
2868         $reportavailable = false;
2869         if (has_capability('moodle/grade:viewall', $course->context)) {
2870             $reportavailable = true;
2871         } else if (!empty($course->showgrades)) {
2872             $reports = get_plugin_list('gradereport');
2873             if (is_array($reports) && count($reports)>0) {     // Get all installed reports
2874                 arsort($reports); // user is last, we want to test it first
2875                 foreach ($reports as $plugin => $plugindir) {
2876                     if (has_capability('gradereport/'.$plugin.':view', $course->context)) {
2877                         //stop when the first visible plugin is found
2878                         $reportavailable = true;
2879                         break;
2880                     }
2881                 }
2882             }
2883         }
2884         if ($reportavailable) {
2885             $url = new moodle_url('/grade/report/index.php', array('id'=>$course->id));
2886             $coursenode->add(get_string('grades'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/grades', ''));
2887         }
2889         //  Add outcome if permitted
2890         if (!empty($CFG->enableoutcomes) && has_capability('moodle/course:update', $course->context)) {
2891             $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$course->id));
2892             $coursenode->add(get_string('outcomes', 'grades'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/outcomes', ''));
2893         }
2895         // Add meta course links
2896         if ($course->metacourse) {
2897             if (has_capability('moodle/course:managemetacourse', $course->context)) {
2898                 $url = new moodle_url('/course/importstudents.php', array('id'=>$course->id));
2899                 $coursenode->add(get_string('childcourses'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/course', ''));
2900             } else if (has_capability('moodle/role:assign', $course->context)) {
2901                 $key = $coursenode->add(get_string('childcourses'), null,  self::TYPE_SETTING, null, null, new pix_icon('i/course', ''));
2902                 $coursenode->get($key)->hidden = true;;
2903             }
2904         }
2906         // Manage groups in this course
2907         if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $course->context)) {
2908             $url = new moodle_url('/group/index.php', array('id'=>$course->id));
2909             $coursenode->add(get_string('groups'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/group', ''));
2910         }
2912         // Backup this course
2913         if (has_capability('moodle/site:backup', $course->context)) {
2914             $url = new moodle_url('/backup/backup.php', array('id'=>$course->id));
2915             $coursenode->add(get_string('backup'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/backup', ''));
2916         }
2918         // Restore to this course
2919         if (has_capability('moodle/site:restore', $course->context)) {
2920             $url = new moodle_url('/files/index.php', array('id'=>$course->id, 'wdir'=>'/backupdata'));
2921             $coursenode->add(get_string('restore'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
2922         }
2924         // Import data from other courses
2925         if (has_capability('moodle/site:import', $course->context)) {
2926             $url = new moodle_url('/course/import.php', array('id'=>$course->id));
2927             $coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
2928         }
2930         // Reset this course
2931         if (has_capability('moodle/course:reset', $course->context)) {
2932             $url = new moodle_url('/course/reset.php', array('id'=>$course->id));
2933             $coursenode->add(get_string('reset'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/return', ''));
2934         }
2936         // Manage questions
2937         $questioncaps = array('moodle/question:add',
2938                               'moodle/question:editmine',
2939                               'moodle/question:editall',
2940                               'moodle/question:viewmine',
2941                               'moodle/question:viewall',
2942                               'moodle/question:movemine',
2943                               'moodle/question:moveall');
2944         if (has_any_capability($questioncaps, $this->context)) {
2945             $questionlink = $CFG->wwwroot.'/question/edit.php';
2946         } else if (has_capability('moodle/question:managecategory', $this->context)) {
2947             $questionlink = $CFG->wwwroot.'/question/category.php';
2948         }
2949         if (isset($questionlink)) {
2950             $url = new moodle_url($questionlink, array('courseid'=>$course->id));
2951             $coursenode->add(get_string('questions','quiz'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/questions', ''));
2952         }
2954         // Repository Instances
2955         require_once($CFG->dirroot.'/repository/lib.php');
2956         $editabletypes = repository::get_editable_types($this->context);
2957         if (has_capability('moodle/course:update', $this->context) && !empty($editabletypes)) {
2958             $url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$this->context->id));
2959             $coursenode->add(get_string('repositories'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/repository', ''));
2960         }
2962         // Manage files
2963         if (has_capability('moodle/course:managefiles', $this->context)) {
2964             $url = new moodle_url('/files/index.php', array('id'=>$course->id));
2965             $coursenode->add(get_string('files'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/files', ''));
2966         }
2968         // Authorize hooks
2969         if ($course->enrol == 'authorize' || (empty($course->enrol) && $CFG->enrol == 'authorize')) {
2970             require_once($CFG->dirroot.'/enrol/authorize/const.php');
2971             $url = new moodle_url('/enrol/authorize/index.php', array('course'=>$course->id));
2972             $coursenode->add(get_string('payments'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/payment', ''));
2973             if (has_capability('enrol/authorize:managepayments', $this->page->context)) {
2974                 $cnt = $DB->count_records('enrol_authorize', array('status'=>AN_STATUS_AUTH, 'courseid'=>$course->id));
2975                 if ($cnt) {
2976                     $url = new moodle_url('/enrol/authorize/index.php', array('course'=>$course->id,'status'=>AN_STATUS_AUTH));
2977                     $coursenode->add(get_string('paymentpending', 'moodle', $cnt), $url, self::TYPE_SETTING, null, null, new pix_icon('i/payment', ''));
2978                 }
2979             }
2980         }
2982         // Unenrol link
2983         if (empty($course->metacourse)) {
2984             if (has_capability('moodle/legacy:guest', $this->context, NULL, false)) {   // Are a guest now
2985                 $url = new moodle_url('/course/enrol.php', array('id'=>$course->id));
2986                 $coursenode->add(get_string('enrolme', '', format_string($course->shortname)), $url, self::TYPE_SETTING, null, null, new pix_icon('i/user', ''));
2987             } else if (has_capability('moodle/role:unassignself', $this->context, NULL, false) && get_user_roles($this->context, 0, false)) {  // Have some role
2988                 $url = new moodle_url('/course/unenrol.php', array('id'=>$course->id));
2989                 $coursenode->add(get_string('unenrolme', '', format_string($course->shortname)), $url, self::TYPE_SETTING, null, null, new pix_icon('i/user', ''));
2990             }
2991         }
2993         // Link to the user own profile (except guests)
2994         if (!isguestuser() and isloggedin()) {
2995             $url = new moodle_url('/user/view.php', array('id'=>$USER->id, 'course'=>$course->id));
2996             $coursenode->add(get_string('profile'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/user', ''));
2997         }
2999         // Switch roles
3000         $roles = array();
3001         $assumedrole = $this->in_alternative_role();
3002         if ($assumedrole!==false) {
3003             $roles[0] = get_string('switchrolereturn');
3004         }
3005         if (has_capability('moodle/role:switchroles', $this->context)) {
3006             $availableroles = get_switchable_roles($this->context);
3007             if (is_array($availableroles)) {
3008                 foreach ($availableroles as $key=>$role) {
3009                     if ($key == $CFG->guestroleid || $assumedrole===(int)$key) {
3010                         continue;
3011                     }
3012                     $roles[$key] = $role;
3013                 }
3014             }
3015         }
3016         if (is_array($roles) && count($roles)>0) {
3017             $switchroleskey = $this->add(get_string('switchroleto'));
3018             if ((count($roles)==1 && array_key_exists(0, $roles))|| $assumedrole!==false) {
3019                 $this->get($switchroleskey)->forceopen = true;
3020             }
3021             $returnurl = $this->page->url;
3022             $returnurl->param('sesskey', sesskey());
3023             $SESSION->returnurl = serialize($returnurl);
3024             foreach ($roles as $key=>$name) {
3025                 $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>$key, 'returnurl'=>'1'));
3026                 $this->get($switchroleskey)->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/roles', ''));
3027             }
3028         }
3029         // Return we are done
3030         return $coursenodekey;
3031     }
3033     /**
3034      * This function calls the module function to inject module settings into the
3035      * settings navigation tree.
3036      *
3037      * This only gets called if there is a corrosponding function in the modules
3038      * lib file.
3039      *
3040      * For examples mod/forum/lib.php ::: forum_extend_settings_navigation()
3041      *
3042      * @return void|mixed The key to access the module method by
3043      */
3044     protected function load_module_settings() {
3045         global $CFG;
3047         if (!$this->page->cm && $this->context->contextlevel == CONTEXT_MODULE && $this->context->instanceid) {
3048             $cm = get_coursemodule_from_id('chat', $this->context->instanceid, 0, false, MUST_EXIST);
3049             $cm->context = $this->context;
3050             $this->page->set_cm($cm, $this->page->course);
3051         }
3053         if (empty($this->page->cm->context)) {
3054             if ($this->context->instanceid === $this->page->cm->id) {
3055                 $this->page->cm->context = $this->context;
3056             } else {
3057                 $this->page->cm->context = get_context_instance(CONTEXT_MODULE, $this->page->cm->instance);
3058             }
3059         }
3061         $file = $CFG->dirroot.'/mod/'.$this->page->activityname.'/lib.php';
3062         $function = $this->page->activityname.'_extend_settings_navigation';
3064         if (file_exists($file)) {
3065             require_once($file);
3066         }
3067         if (!function_exists($function)) {
3068             return;
3069         }
3070         return $function($this,$this->page->activityrecord);
3071     }
3073     /**
3074      * Loads the user settings block of the settings nav
3075      *
3076      * This function is simply works out the userid and whether we need to load
3077      * just the current users profile settings, or the current user and the user the
3078      * current user is viewing.
3079      *
3080      * This function has some very ugly code to work out the user, if anyone has
3081      * any bright ideas please feel free to intervene.
3082      *
3083      * @param int $courseid The course id of the current course
3084      */
3085     protected function load_user_settings($courseid=SITEID) {
3086         global $USER, $FULLME;
3088         if (isguestuser() || !isloggedin()) {
3089             return false;
3090         }
3092         // This is terribly ugly code, but I couldn't see a better way around it
3093         // we need to pick up the user id, it can be the current user or someone else
3094         // and the key depends on the current location
3095         // Default to look at id
3096         $userkey='id';
3097         if ($this->context->contextlevel >= CONTEXT_COURSECAT && strpos($FULLME, '/message/')===false && strpos($FULLME, '/mod/forum/user')===false) {
3098             // If we have a course context and we are not in message or forum
3099             // Message and forum both pick the user up from `id`
3100             $userkey = 'user';
3101         } else if (strpos($FULLME,'/blog/') || strpos($FULLME, '/roles/')) {
3102             // And blog and roles just do thier own thing using `userid`
3103             $userkey = 'userid';
3104         }
3106         $userid = optional_param($userkey, $USER->id, PARAM_INT);
3107         if ($userid!=$USER->id) {
3108             $this->generate_user_settings($courseid,$userid,'userviewingsettings');
3109             $this->generate_user_settings($courseid,$USER->id);
3110         } else {
3111             $this->generate_user_settings($courseid,$USER->id);
3112         }
3113     }
3115     /**
3116      * This function gets called by {@link load_user_settings()} and actually works out
3117      * what can be shown/done
3118      *
3119      * @param int $courseid The current course' id
3120      * @param int $userid The user id to load for
3121      * @param string $gstitle The string to pass to get_string for the branch title
3122      * @return string|int The key to reference this user's settings
3123      */
3124     protected function generate_user_settings($courseid, $userid, $gstitle='usercurrentsettings') {
3125         global $DB, $CFG, $USER, $SITE;
3127         if ($courseid != SITEID) {
3128             if (!empty($this->page->course->id) && $this->page->course->id == $courseid) {
3129                 $course = $this->page->course;
3130             } else {
3131                 $course = $DB->get_record("course", array("id"=>$courseid), '*', MUST_EXIST);
3132             }
3133         } else {
3134             $course = $SITE;
3135         }
3137         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);   // Course context
3138         $systemcontext   = get_system_context();
3139         $currentuser = ($USER->id == $userid);
3140         
3141         if ($currentuser) {
3142             $user = $USER;
3143             $usercontext = get_context_instance(CONTEXT_USER, $user->id);       // User context
3144         } else {
3145             if (!$user = $DB->get_record('user', array('id'=>$userid))) {
3146                 return false;
3147             }
3148             // Check that the user can view the profile
3149             $usercontext = get_context_instance(CONTEXT_USER, $user->id);       // User context
3150             if ($course->id==SITEID) {
3151                 if ($CFG->forceloginforprofiles && !isteacherinanycourse() && !isteacherinanycourse($user->id) && !has_capability('moodle/user:viewdetails', $usercontext)) {  // Reduce possibility of "browsing" userbase at site level
3152                     // Teachers can browse and be browsed at site level. If not forceloginforprofiles, allow access (bug #4366)
3153                     return false;
3154                 }
3155             } else {
3156                 if ((!has_capability('moodle/user:viewdetails', $coursecontext) && !has_capability('moodle/user:viewdetails', $usercontext)) || !has_capability('moodle/course:view', $coursecontext, $user->id, false)) {
3157                     return false;
3158                 }
3159                 if (groups_get_course_groupmode($course) == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $coursecontext)) {
3160                     // If groups are in use, make sure we can see that group
3161                     return false;
3162                 }
3163             }
3164         }
3166         $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $this->page->context));
3168         // Add a user setting branch
3169         $usersettingskey = $this->add(get_string($gstitle, 'moodle', $fullname));
3170         $usersetting = $this->get($usersettingskey);
3171         $usersetting->id = 'usersettings';
3173         // Check if the user has been deleted
3174         if ($user->deleted) {
3175             if (!has_capability('moodle/user:update', $coursecontext)) {
3176                 // We can't edit the user so just show the user deleted message
3177                 $usersetting->add(get_string('userdeleted'), null, self::TYPE_SETTING);
3178             } else {
3179                 // We can edit the user so show the user deleted message and link it to the profile
3180                 $profileurl = new moodle_url('/user/view.php', array('id'=>$user->id, 'course'=>$course->id));
3181                 $usersetting->add(get_string('userdeleted'), $profileurl, self::TYPE_SETTING);
3182             }
3183             return true;
3184         }
3186         // Add the profile edit link
3187         if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
3188             if (($currentuser || !is_primary_admin($user->id)) && has_capability('moodle/user:update', $systemcontext)) {
3189                 $url = new moodle_url('/user/editadvanced.php', array('id'=>$user->id, 'course'=>$course->id));
3190                 $usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
3191             } else if ((has_capability('moodle/user:editprofile', $usercontext) && !is_primary_admin($user->id)) || ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext))) {
3192                 $url = new moodle_url('/user/edit.php', array('id'=>$user->id, 'course'=>$course->id));
3193                 $usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
3194             }
3195         }
3197         // Change password link
3198         if (!empty($user->auth)) {
3199             $userauth = get_auth_plugin($user->auth);
3200             if ($currentuser && !session_is_loggedinas() && $userauth->can_change_password() && !isguestuser() && has_capability('moodle/user:changeownpassword', $systemcontext)) {
3201                 $passwordchangeurl = $userauth->change_password_url();
3202                 if (!$passwordchangeurl) {
3203                     if (empty($CFG->loginhttps)) {
3204                         $wwwroot = $CFG->wwwroot;
3205                     } else {
3206                         $wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
3207                     }
3208                     $passwordchangeurl = new moodle_url('/login/change_password.php');
3209                 } else {
3210                     $urlbits = explode($passwordchangeurl. '?', 1);
3211                     $passwordchangeurl = new moodle_url($urlbits[0]);
3212                     if (count($urlbits)==2 && preg_match_all('#\&([^\=]*?)\=([^\&]*)#si', '&'.$urlbits[1], $matches)) {
3213                         foreach ($matches as $pair) {
3214                             $fullmeurl->param($pair[1],$pair[2]);
3215                         }
3216                     }
3217                 }
3218                 $passwordchangeurl->param('id', $course->id);
3219                 $usersetting->add(get_string("changepassword"), $passwordchangeurl, self::TYPE_SETTING);
3220             }
3221         }
3223         // View the roles settings
3224         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:manage'), $usercontext)) {
3225             $roleskey = $usersetting->add(get_string('roles'), null, self::TYPE_SETTING);
3227             $url = new moodle_url('/admin/roles/usersroles.php', array('userid'=>$user->id, 'courseid'=>$course->id));
3228             $usersetting->get($roleskey)->add(get_string('thisusersroles', 'role'), $url, self::TYPE_SETTING);
3230             $assignableroles = get_assignable_roles($usercontext, ROLENAME_BOTH);
3231             $overridableroles = get_overridable_roles($usercontext, ROLENAME_BOTH);
3233             if (!empty($assignableroles)) {
3234                 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
3235                 $usersetting->get($roleskey)->add(get_string('assignrolesrelativetothisuser', 'role'), $url, self::TYPE_SETTING);
3236             }
3238             if (!empty($overridableroles)) {
3239                 $url = new moodle_url('/admin/roles/override.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
3240                 $usersetting->get($roleskey)->add(get_string('overridepermissions', 'role'), $url, self::TYPE_SETTING);
3241             }
3243             $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
3244             $usersetting->get($roleskey)->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
3245         }
3247         // Portfolio
3248         if ($currentuser && !empty($CFG->enableportfolios) && has_capability('moodle/portfolio:export', $systemcontext)) {
3249             require_once($CFG->libdir . '/portfoliolib.php');
3250             if (portfolio_instances(true, false)) {
3251                 $portfoliokey = $usersetting->add(get_string('portfolios', 'portfolio'), null, self::TYPE_SETTING);
3252                 $usersetting->get($portfoliokey)->add(get_string('configure', 'portfolio'), new moodle_url('/user/portfolio.php'), self::TYPE_SETTING);
3253                 $usersetting->get($portfoliokey)->add(get_string('logs', 'portfolio'), new moodle_url('/user/portfoliologs.php'), self::TYPE_SETTING);
3254             }
3255         }
3257         // Security keys
3258         if ($currentuser && !is_siteadmin($USER->id) && !empty($CFG->enablewebservices) && has_capability('moodle/webservice:createtoken', $systemcontext)) {
3259             $url = new moodle_url('/user/managetoken.php', array('sesskey'=>sesskey()));
3260             $usersetting->add(get_string('securitykeys', 'webservice'), $url, self::TYPE_SETTING);
3261         }
3263         // Repository
3264         if (!$currentuser) {
3265             require_once($CFG->dirroot . '/repository/lib.php');
3266             $editabletypes = repository::get_editable_types($usercontext);
3267             if ($usercontext->contextlevel == CONTEXT_USER && !empty($editabletypes)) {
3268                 $url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$usercontext->id));
3269                 $usersetting->add(get_string('repositories', 'repository'), $url, self::TYPE_SETTING);
3270             }
3271         }
3273         // Messaging
3274         if (has_capability('moodle/user:editownmessageprofile', $systemcontext)) {
3275             $url = new moodle_url('/message/edit.php', array('id'=>$user->id, 'course'=>$course->id));
3276             $usersetting->add(get_string('editmymessage', 'message'), $url, self::TYPE_SETTING);
3277         }
3279         return $usersettingskey;
3280     }
3282     /**
3283      * Determine whether the user is assuming another role
3284      *
3285      * This function checks to see if the user is assuming another role by means of
3286      * role switching. In doing this we compare each RSW key (context path) against
3287      * the current context path. This ensures that we can provide the switching
3288      * options against both the course and any page shown under the course.
3289      *
3290      * @return bool|int The role(int) if the user is in another role, false otherwise
3291      */
3292     protected function in_alternative_role() {
3293         global $USER;
3294         if (!empty($USER->access['rsw']) && is_array($USER->access['rsw'])) {
3295             if (!empty($this->page->context) && !empty($USER->access['rsw'][$this->page->context->path])) {
3296                 return $USER->access['rsw'][$this->page->context->path];
3297             }
3298             foreach ($USER->access['rsw'] as $key=>$role) {