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