dad343ccee4e27d47792021f6090de665a6deafa
[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 implements renderable {
54     /** @var int Used to identify this node a leaf (default) 0 */
55     const NODETYPE_LEAF =   0;
56     /** @var int Used to identify this node a branch, happens with children  1 */
57     const NODETYPE_BRANCH = 1;
58     /** @var null Unknown node type null */
59     const TYPE_UNKNOWN =    null;
60     /** @var int System node type 0 */
61     const TYPE_ROOTNODE =   0;
62     /** @var int System node type 1 */
63     const TYPE_SYSTEM =     1;
64     /** @var int Category node type 10 */
65     const TYPE_CATEGORY =   10;
66     /** @var int Course node type 20 */
67     const TYPE_COURSE =     20;
68     /** @var int Course Structure node type 30 */
69     const TYPE_SECTION =    30;
70     /** @var int Activity node type, e.g. Forum, Quiz 40 */
71     const TYPE_ACTIVITY =   40;
72     /** @var int Resource node type, e.g. Link to a file, or label 50 */
73     const TYPE_RESOURCE =   50;
74     /** @var int A custom node type, default when adding without specifing type 60 */
75     const TYPE_CUSTOM =     60;
76     /** @var int Setting node type, used only within settings nav 70 */
77     const TYPE_SETTING =    70;
78     /** @var int Setting node type, used only within settings nav 80 */
79     const TYPE_USER =       80;
80     /** @var int Setting node type, used for containers of no importance 90 */
81     const TYPE_CONTAINER =  90;
83     /** @var int Parameter to aid the coder in tracking [optional] */
84     public $id = null;
85     /** @var string|int The identifier for the node, used to retrieve the node */
86     public $key = null;
87     /** @var string The text to use for the node */
88     public $text = null;
89     /** @var string Short text to use if requested [optional] */
90     public $shorttext = null;
91     /** @var string The title attribute for an action if one is defined */
92     public $title = null;
93     /** @var string A string that can be used to build a help button */
94     public $helpbutton = null;
95     /** @var moodle_url|action_link|null An action for the node (link) */
96     public $action = null;
97     /** @var pix_icon The path to an icon to use for this node */
98     public $icon = null;
99     /** @var int See TYPE_* constants defined for this class */
100     public $type = self::TYPE_UNKNOWN;
101     /** @var int See NODETYPE_* constants defined for this class */
102     public $nodetype = self::NODETYPE_LEAF;
103     /** @var bool If set to true the node will be collapsed by default */
104     public $collapse = false;
105     /** @var bool If set to true the node will be expanded by default */
106     public $forceopen = false;
107     /** @var array An array of CSS classes for the node */
108     public $classes = array();
109     /** @var navigation_node_collection An array of child nodes */
110     public $children = array();
111     /** @var bool If set to true the node will be recognised as active */
112     public $isactive = false;
113     /** @var bool If set to true the node will be dimmed */
114     public $hidden = false;
115     /** @var bool If set to false the node will not be displayed */
116     public $display = true;
117     /** @var bool If set to true then an HR will be printed before the node */
118     public $preceedwithhr = false;
119     /** @var bool If set to true the the navigation bar should ignore this node */
120     public $mainnavonly = false;
121     /** @var bool If set to true a title will be added to the action no matter what */
122     public $forcetitle = false;
123     /** @var navigation_node A reference to the node parent */
124     public $parent = null;
125     /** @var bool Override to not display the icon even if one is provided **/
126     public $hideicon = false;
127     /** @var array */
128     protected $namedtypes = array(0=>'system',10=>'category',20=>'course',30=>'structure',40=>'activity',50=>'resource',60=>'custom',70=>'setting', 80=>'user');
129     /** @var moodle_url */
130     protected static $fullmeurl = null;
131     /** @var bool toogles auto matching of active node */
132     public static $autofindactive = true;
134     /**
135      * Constructs a new navigation_node
136      *
137      * @param array|string $properties Either an array of properties or a string to use
138      *                     as the text for the node
139      */
140     public function __construct($properties) {
141         if (is_array($properties)) {
142             // Check the array for each property that we allow to set at construction.
143             // text         - The main content for the node
144             // shorttext    - A short text if required for the node
145             // icon         - The icon to display for the node
146             // type         - The type of the node
147             // key          - The key to use to identify the node
148             // parent       - A reference to the nodes parent
149             // action       - The action to attribute to this node, usually a URL to link to
150             if (array_key_exists('text', $properties)) {
151                 $this->text = $properties['text'];
152             }
153             if (array_key_exists('shorttext', $properties)) {
154                 $this->shorttext = $properties['shorttext'];
155             }
156             if (array_key_exists('icon', $properties)) {
157                 $this->icon = $properties['icon'];
158                 if ($this->icon instanceof pix_icon) {
159                     if (empty($this->icon->attributes['class'])) {
160                         $this->icon->attributes['class'] = 'navicon';
161                     } else {
162                         $this->icon->attributes['class'] .= ' navicon';
163                     }
164                 }
165             }
166             if (array_key_exists('type', $properties)) {
167                 $this->type = $properties['type'];
168             } else {
169                 $this->type = self::TYPE_CUSTOM;
170             }
171             if (array_key_exists('key', $properties)) {
172                 $this->key = $properties['key'];
173             }
174             if (array_key_exists('parent', $properties)) {
175                 $this->parent = $properties['parent'];
176             }
177             // This needs to happen last because of the check_if_active call that occurs
178             if (array_key_exists('action', $properties)) {
179                 $this->action = $properties['action'];
180                 if (is_string($this->action)) {
181                     $this->action = new moodle_url($this->action);
182                 }
183                 if (self::$autofindactive) {
184                     $this->check_if_active();
185                 }
186             }
187         } else if (is_string($properties)) {
188             $this->text = $properties;
189         }
190         if ($this->text === null) {
191             throw new coding_exception('You must set the text for the node when you create it.');
192         }
193         // Default the title to the text
194         $this->title = $this->text;
195         // Instantiate a new navigation node collection for this nodes children
196         $this->children = new navigation_node_collection();
197     }
199     /**
200      * Checks if this node is the active node.
201      *
202      * This is determined by comparing the action for the node against the
203      * defined URL for the page. A match will see this node marked as active.
204      *
205      * @param int $strength One of URL_MATCH_EXACT, URL_MATCH_PARAMS, or URL_MATCH_BASE
206      * @return bool
207      */
208     public function check_if_active($strength=URL_MATCH_EXACT) {
209         global $FULLME, $PAGE;
210         // Set fullmeurl if it hasn't already been set
211         if (self::$fullmeurl == null) {
212             if ($PAGE->has_set_url()) {
213                 self::override_active_url(new moodle_url($PAGE->url));
214             } else {
215                 self::override_active_url(new moodle_url($FULLME));
216             }
217         }
219         // Compare the action of this node against the fullmeurl
220         if ($this->action instanceof moodle_url && $this->action->compare(self::$fullmeurl, $strength)) {
221             $this->make_active();
222             return true;
223         }
224         return false;
225     }
227     /**
228      * Overrides the fullmeurl variable providing
229      *
230      * @param moodle_url $url The url to use for the fullmeurl.
231      */
232     public static function override_active_url(moodle_url $url) {
233         self::$fullmeurl = $url;
234     }
236     /**
237      * Adds a navigation node as a child of this node.
238      * 
239      * @param string $text
240      * @param moodle_url|action_link $action
241      * @param int $type
242      * @param string $shorttext
243      * @param string|int $key
244      * @param pix_icon $icon
245      * @return navigation_node
246      */
247     public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) {
248         // First convert the nodetype for this node to a branch as it will now have children
249         if ($this->nodetype !== self::NODETYPE_BRANCH) {
250             $this->nodetype = self::NODETYPE_BRANCH;
251         }
252         // Properties array used when creating the new navigation node
253         $itemarray = array(
254             'text' => $text,
255             'type' => $type
256         );
257         // Set the action if one was provided
258         if ($action!==null) {
259             $itemarray['action'] = $action;
260         }
261         // Set the shorttext if one was provided
262         if ($shorttext!==null) {
263             $itemarray['shorttext'] = $shorttext;
264         }
265         // Set the icon if one was provided
266         if ($icon!==null) {
267             $itemarray['icon'] = $icon;
268         }
269         // Default the key to the number of children if not provided
270         if ($key === null) {
271             $key = $this->children->count();
272         }
273         // Set the key
274         $itemarray['key'] = $key;
275         // Set the parent to this node
276         $itemarray['parent'] = $this;
277         // Add the child using the navigation_node_collections add method
278         $node = $this->children->add(new navigation_node($itemarray));
279         // If the node is a category node or the user is logged in and its a course
280         // then mark this node as a branch (makes it expandable by AJAX)
281         if (($type==self::TYPE_CATEGORY) || (isloggedin() && $type==self::TYPE_COURSE)) {
282             $node->nodetype = self::NODETYPE_BRANCH;
283         }
284         // If this node is hidden mark it's children as hidden also
285         if ($this->hidden) {
286             $node->hidden = true;
287         }
288         // Return the node (reference returned by $this->children->add()
289         return $node;
290     }
292     /**
293      * Searches for a node of the given type with the given key.
294      *
295      * This searches this node plus all of its children, and their children....
296      * If you know the node you are looking for is a child of this node then please
297      * use the get method instead.
298      *
299      * @param int|string $key The key of the node we are looking for
300      * @param int $type One of navigation_node::TYPE_*
301      * @return navigation_node|false
302      */
303     public function find($key, $type) {
304         return $this->children->find($key, $type);
305     }
307     /**
308      * Get ths child of this node that has the given key + (optional) type.
309      *
310      * If you are looking for a node and want to search all children + thier children
311      * then please use the find method instead.
312      *
313      * @param int|string $key The key of the node we are looking for
314      * @param int $type One of navigation_node::TYPE_*
315      * @return navigation_node|false
316      */
317     public function get($key, $type=null) {
318         return $this->children->get($key, $type);
319     }
321     /**
322      * Removes this node.
323      *
324      * @return bool
325      */
326     public function remove() {
327         return $this->parent->children->remove($this->key, $this->type);
328     }
330     /**
331      * Checks if this node has or could have any children
332      *
333      * @return bool Returns true if it has children or could have (by AJAX expansion)
334      */
335     public function has_children() {
336         return ($this->nodetype === navigation_node::NODETYPE_BRANCH || $this->children->count()>0);
337     }
339     /**
340      * Marks this node as active and forces it open.
341      */
342     public function make_active() {
343         $this->isactive = true;
344         $this->add_class('active_tree_node');
345         $this->force_open();
346         if ($this->parent !== null) {
347             $this->parent->make_inactive();
348         }
349     }
351     /**
352      * Marks a node as inactive and recusised back to the base of the tree
353      * doing the same to all parents.
354      */
355     public function make_inactive() {
356         $this->isactive = false;
357         $this->remove_class('active_tree_node');
358         if ($this->parent !== null) {
359             $this->parent->make_inactive();
360         }
361     }
363     /**
364      * Forces this node to be open and at the same time forces open all
365      * parents until the root node.
366      *
367      * Recursive.
368      */
369     public function force_open() {
370         $this->forceopen = true;
371         if ($this->parent !== null) {
372             $this->parent->force_open();
373         }
374     }
376     /**
377      * Adds a CSS class to this node.
378      *
379      * @param string $class
380      * @return bool
381      */
382     public function add_class($class) {
383         if (!in_array($class, $this->classes)) {
384             $this->classes[] = $class;
385         }
386         return true;
387     }
389     /**
390      * Removes a CSS class from this node.
391      *
392      * @param string $class
393      * @return bool True if the class was successfully removed.
394      */
395     public function remove_class($class) {
396         if (in_array($class, $this->classes)) {
397             $key = array_search($class,$this->classes);
398             if ($key!==false) {
399                 unset($this->classes[$key]);
400                 return true;
401             }
402         }
403         return false;
404     }
406     /**
407      * Sets the title for this node and forces Moodle to utilise it.
408      * @param string $title
409      */
410     public function title($title) {
411         $this->title = $title;
412         $this->forcetitle = true;
413     }
415     /**
416      * Resets the page specific information on this node if it is being unserialised.
417      */
418     public function __wakeup(){
419         $this->forceopen = false;
420         $this->isactive = false;
421         $this->remove_class('active_tree_node');
422     }
424     /**
425      * Checks if this node or any of its children contain the active node.
426      * 
427      * Recursive.
428      *
429      * @return bool
430      */
431     public function contains_active_node() {
432         if ($this->isactive) {
433             return true;
434         } else {
435             foreach ($this->children as $child) {
436                 if ($child->isactive || $child->contains_active_node()) {
437                     return true;
438                 }
439             }
440         }
441         return false;
442     }
444     /**
445      * Finds the active node.
446      *
447      * Searches this nodes children plus all of the children for the active node
448      * and returns it if found.
449      *
450      * Recursive.
451      *
452      * @return navigation_node|false
453      */
454     public function find_active_node() {
455         if ($this->isactive) {
456             return $this;
457         } else {
458             foreach ($this->children as &$child) {
459                 $outcome = $child->find_active_node();
460                 if ($outcome !== false) {
461                     return $outcome;
462                 }
463             }
464         }
465         return false;
466     }
468     /**
469      * Gets the content for this node.
470      *
471      * @param bool $shorttext If true shorttext is used rather than the normal text
472      * @return string
473      */
474     public function get_content($shorttext=false) {
475         if ($shorttext && $this->shorttext!==null) {
476             return format_string($this->shorttext);
477         } else {
478             return format_string($this->text);
479         }
480     }
482     /**
483      * Gets the title to use for this node.
484      * 
485      * @return string
486      */
487     public function get_title() {
488         if ($this->forcetitle || ($this->shorttext!==null && $this->title !== $this->shorttext) || $this->title !== $this->text) {
489             return $this->title;
490         } else {
491             return '';
492         }
493     }
495     /**
496      * Gets the CSS class to add to this node to describe its type
497      * 
498      * @return string
499      */
500     public function get_css_type() {
501         if (array_key_exists($this->type, $this->namedtypes)) {
502             return 'type_'.$this->namedtypes[$this->type];
503         }
504         return 'type_unknown';
505     }
507     /**
508      * Finds all nodes that are expandable by AJAX
509      *
510      * @param array $expandable An array by reference to populate with expandable nodes.
511      */
512     public function find_expandable(array &$expandable) {
513         if (!isloggedin()) {
514             return;
515         }
516         foreach ($this->children as &$child) {
517             if ($child->nodetype == self::NODETYPE_BRANCH && $child->children->count()==0 && $child->display) {
518                 $child->id = 'expandable_branch_'.(count($expandable)+1);
519                 $this->add_class('canexpand');
520                 $expandable[] = array('id'=>$child->id,'branchid'=>$child->key,'type'=>$child->type);
521             }
522             $child->find_expandable($expandable);
523         }
524     }
526     public function find_all_of_type($type) {
527         $nodes = $this->children->type($type);
528         foreach ($this->children as &$node) {
529             $childnodes = $node->find_all_of_type($type);
530             $nodes = array_merge($nodes, $childnodes);
531         }
532         return $nodes;
533     }
536 /**
537  * Navigation node collection
538  *
539  * This class is responsible for managing a collection of navigation nodes.
540  * It is required because a node's unique identifier is a combination of both its
541  * key and its type.
542  *
543  * Originally an array was used with a string key that was a combination of the two
544  * however it was decided that a better solution would be to use a class that
545  * implements the standard IteratorAggregate interface.
546  *
547  * @package moodlecore
548  * @subpackage navigation
549  * @copyright 2010 Sam Hemelryk
550  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
551  */
552 class navigation_node_collection implements IteratorAggregate {
553     /**
554      * A multidimensional array to where the first key is the type and the second
555      * key is the nodes key.
556      * @var array
557      */
558     protected $collection = array();
559     /**
560      * An array that contains references to nodes in the same order they were added.
561      * This is maintained as a progressive array.
562      * @var array
563      */
564     protected $orderedcollection = array();
565     /**
566      * A reference to the last node that was added to the collection
567      * @var navigation_node
568      */
569     protected $last = null;
570     /**
571      * The total number of items added to this array.
572      * @var int
573      */
574     protected $count = 0;
575     /**
576      * Adds a navigation node to the collection
577      *
578      * @param navigation_node $node
579      * @return navigation_node
580      */
581     public function add(navigation_node $node) {
582         global $CFG;
583         $key = $node->key;
584         $type = $node->type;
585         // First check we have a 2nd dimension for this type
586         if (!array_key_exists($type, $this->orderedcollection)) {
587             $this->orderedcollection[$type] = array();
588         }
589         // Check for a collision and report if debugging is turned on
590         if ($CFG->debug && array_key_exists($key, $this->orderedcollection[$type])) {
591             debugging('Navigation node intersect: Adding a node that already exists '.$key, DEBUG_DEVELOPER);
592         }
593         // Add the node to the appropriate place in the ordered structure.
594         $this->orderedcollection[$type][$key] = $node;
595         // Add a reference to the node to the progressive collection.
596         $this->collection[$this->count] = &$this->orderedcollection[$type][$key];
597         // Update the last property to a reference to this new node.
598         $this->last = &$this->orderedcollection[$type][$key];
599         $this->count++;
600         // Return the reference to the now added node
601         return $this->last;
602     }
604     /**
605      * Fetches a node from this collection.
606      *
607      * @param string|int $key The key of the node we want to find.
608      * @param int $type One of navigation_node::TYPE_*.
609      * @return navigation_node|null
610      */
611     public function get($key, $type=null) {
612         if ($type !== null) {
613             // If the type is known then we can simply check and fetch
614             if (!empty($this->orderedcollection[$type][$key])) {
615                 return $this->orderedcollection[$type][$key];
616             }
617         } else {
618             // Because we don't know the type we look in the progressive array
619             foreach ($this->collection as $node) {
620                 if ($node->key === $key) {
621                     return $node;
622                 }
623             }
624         }
625         return false;
626     }
627     /**
628      * Searches for a node with matching key and type.
629      *
630      * This function searches both the nodes in this collection and all of
631      * the nodes in each collection belonging to the nodes in this collection.
632      *
633      * Recursive.
634      *
635      * @param string|int $key  The key of the node we want to find.
636      * @param int $type  One of navigation_node::TYPE_*.
637      * @return navigation_node|null
638      */
639     public function find($key, $type=null) {
640         if ($type !== null && array_key_exists($type, $this->orderedcollection) && array_key_exists($key, $this->orderedcollection[$type])) {
641             return $this->orderedcollection[$type][$key];
642         } else {
643             $nodes = $this->getIterator();
644             // Search immediate children first
645             foreach ($nodes as &$node) {
646                 if ($node->key == $key && ($type == null || $type === $node->type)) {
647                     return $node;
648                 }
649             }
650             // Now search each childs children
651             foreach ($nodes as &$node) {
652                 $result = $node->children->find($key, $type);
653                 if ($result !== false) {
654                     return $result;
655                 }
656             }
657         }
658         return false;
659     }
661     /**
662      * Fetches the last node that was added to this collection
663      * 
664      * @return navigation_node
665      */
666     public function last() {
667         return $this->last;
668     }
669     /**
670      * Fetches all nodes of a given type from this collection
671      */
672     public function type($type) {
673         if (!array_key_exists($type, $this->orderedcollection)) {
674             $this->orderedcollection[$type] = array();
675         }
676         return $this->orderedcollection[$type];
677     }
678     /**
679      * Removes the node with the given key and type from the collection
680      *
681      * @param string|int $key
682      * @param int $type
683      * @return bool
684      */
685     public function remove($key, $type=null) {
686         $child = $this->get($key, $type);
687         if ($child !== false) {
688             foreach ($this->collection as $colkey => $node) {
689                 if ($node->key == $key && $node->type == $type) {
690                     unset($this->collection[$colkey]);
691                     break;
692                 }
693             }
694             unset($this->orderedcollection[$child->type][$child->key]);
695             $this->count--;
696             return true;
697         }
698         return false;
699     }
701     /**
702      * Gets the number of nodes in this collection
703      * @return int
704      */
705     public function count() {
706         return count($this->collection);
707     }
708     /**
709      * Gets an array iterator for the collection.
710      *
711      * This is required by the IteratorAggregator interface and is used by routines
712      * such as the foreach loop.
713      *
714      * @return ArrayIterator
715      */
716     public function getIterator() {
717         return new ArrayIterator($this->collection);
718     }
721 /**
722  * The global navigation class used for... the global navigation
723  *
724  * This class is used by PAGE to store the global navigation for the site
725  * and is then used by the settings nav and navbar to save on processing and DB calls
726  *
727  * See
728  * <ul>
729  * <li><b>{@link lib/pagelib.php}</b> {@link moodle_page::initialise_theme_and_output()}<li>
730  * <li><b>{@link lib/ajax/getnavbranch.php}</b> Called by ajax<li>
731  * </ul>
732  *
733  * @package moodlecore
734  * @subpackage navigation
735  * @copyright 2009 Sam Hemelryk
736  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
737  */
738 class global_navigation extends navigation_node {
739     /**
740      * The Moodle page this navigation object belongs to.
741      * @var moodle_page
742      */
743     protected $page;
744     /** @var bool */
745     protected $initialised = false;
746     /** @var array */
747     protected $mycourses = array();
748     /** @var array */
749     protected $rootnodes = array();
750     /** @var bool */
751     protected $showemptysections = false;
752     /** @var array */
753     protected $extendforuser = array();
754     /** @var navigation_cache */
755     protected $cache;
756     /** @var array */
757     protected $addedcourses = array();
758     /** @var int */
759     protected $expansionlimit = 0;
761     /**
762      * Constructs a new global navigation
763      *
764      * @param moodle_page $page The page this navigation object belongs to
765      */
766     public function __construct(moodle_page $page) {
767         global $SITE, $USER;
769         if (during_initial_install()) {
770             return;
771         }
773         // Use the parents consturctor.... good good reuse
774         $properties = array(
775             'key' => 'home',
776             'type' => navigation_node::TYPE_SYSTEM,
777             'text' => get_string('myhome'),
778             'action' => new moodle_url('/my/')
779         );
780         if (!isloggedin()) {
781             $properties['text'] = get_string('home');
782             $properties['action'] = new moodle_url('/');
783         }
784         parent::__construct($properties);
786         // Initalise and set defaults
787         $this->page = $page;
788         $this->forceopen = true;
789         $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
791         // Check if we need to clear the cache
792         $regenerate = optional_param('regenerate', null, PARAM_TEXT);
793         if ($regenerate === 'navigation') {
794             $this->cache->clear();
795         }
796     }
798     /**
799      * Initialises the navigation object.
800      *
801      * This causes the navigation object to look at the current state of the page
802      * that it is associated with and then load the appropriate content.
803      *
804      * This should only occur the first time that the navigation structure is utilised
805      * which will normally be either when the navbar is called to be displayed or
806      * when a block makes use of it.
807      *
808      * @return bool
809      */
810     public function initialise() {
811         global $CFG, $SITE, $USER;
812         // Check if it has alread been initialised
813         if ($this->initialised || during_initial_install()) {
814             return true;
815         }
817         // Set up the five base root nodes. These are nodes where we will put our
818         // content and are as follows:
819         // site:        Navigation for the front page.
820         // myprofile:     User profile information goes here.
821         // mycourses:   The users courses get added here.
822         // courses:     Additional courses are added here.
823         // users:       Other users information loaded here.
824         $this->rootnodes = array();
825         $this->rootnodes['site']      = $this->add_course($SITE);
826         $this->rootnodes['myprofile']   = $this->add(get_string('myprofile'), null, self::TYPE_USER, null, 'myprofile');
827         $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), null, self::TYPE_ROOTNODE, null, 'mycourses');
828         $this->rootnodes['courses']   = $this->add(get_string('courses'), null, self::TYPE_ROOTNODE, null, 'courses');
829         $this->rootnodes['users']     = $this->add(get_string('users'), null, self::TYPE_ROOTNODE, null, 'users');
831         // Fetch all of the users courses.
832         $this->mycourses = get_my_courses($USER->id);
833         // Check if any courses were returned.
834         if (count($this->mycourses) > 0) {
835             // Add all of the users courses to the navigation
836             foreach ($this->mycourses as &$course) {
837               $course->coursenode = $this->add_course($course);
838             }
839         } else {
840             // The user had no specific courses! they could be no logged in, guest
841             // or admin so load all courses instead.
842             $this->load_all_courses();
843         }
845         // Next load context specific content into the navigation
846         switch ($this->page->context->contextlevel) {
847             case CONTEXT_SYSTEM :
848             case CONTEXT_COURSECAT :
849                 // Load the front page course navigation
850                 $this->load_course($SITE);
851                 break;
852             case CONTEXT_BLOCK :
853             case CONTEXT_COURSE :
854                 // Load the course associated with the page into the navigation
855                 $course = $this->page->course;
856                 $coursenode = $this->load_course($course);
857                 // Make it active
858                 $coursenode->make_active();
859                 // Add the essentials such as reports etc...
860                 $this->add_course_essentials($coursenode, $course);
861                 if ($this->format_display_course_content($course->format)) {
862                     // Load the course sections
863                     $sections = $this->load_course_sections($course, $coursenode);
864                 }
865                 break;
866             case CONTEXT_MODULE :
867                 $course = $this->page->course;
868                 $cm = $this->page->cm;
869                 // Load the course associated with the page into the navigation
870                 $coursenode = $this->load_course($course);
871                 $this->add_course_essentials($coursenode, $course);
872                 // Load the course sections into the page
873                 $sections = $this->load_course_sections($course, $coursenode);
874                 if ($course->id !== SITEID) {
875                     // Find the section for the $CM associated with the page and collect
876                     // its section number.
877                     foreach ($sections as $section) {
878                         if ($section->id == $cm->section) {
879                             $cm->sectionnumber = $section->section;
880                             break;
881                         }
882                     }
884                     // Load all of the section activities for the section the cm belongs to.
885                     $activities = $this->load_section_activities($sections[$cm->sectionnumber]->sectionnode, $cm->sectionnumber, get_fast_modinfo($course));
886                 } else {
887                     $activities = array();
888                     $activities[$cm->id] = $coursenode->get($cm->id, navigation_node::TYPE_ACTIVITY);
889                 }
890                 // Finally load the cm specific navigaton information
891                 $this->load_activity($cm, $course, $activities[$cm->id]);
892                 // And make the activity node active.
893                 $activities[$cm->id]->make_active();
894                 break;
895             case CONTEXT_USER :
896                 $course = $this->page->course;
897                 if ($course->id != SITEID) {
898                     // Load the course associated with the user into the navigation
899                     $coursenode = $this->load_course($course);
900                     $this->add_course_essentials($coursenode, $course);
901                     $sections = $this->load_course_sections($course, $coursenode);
902                 }
903                 break;
904         }
906         // Load for the current user
907         $this->load_for_user();
908         // Load each extending user into the navigation.
909         foreach ($this->extendforuser as $user) {
910             if ($user->id !== $USER->id) {
911                 $this->load_for_user($user);
912             }
913         }
915         // Give the local plugins a chance to include some navigation if they want.
916         foreach (get_list_of_plugins('local') as $plugin) {
917             if (!file_exists($CFG->dirroot.'/local/'.$plugin.'/lib.php')) {
918                 continue;
919             }
920             require_once($CFG->dirroot.'/local/'.$plugin.'/lib.php');
921             $function = $plugin.'_extends_navigation';
922             if (function_exists($function)) {
923                 $function($this);
924             }
925         }
927         // Remove any empty root nodes
928         foreach ($this->rootnodes as $node) {
929             if (!$node->has_children()) {
930                 $node->remove();
931             }
932         }
934         // If the user is not logged in modify the navigation structure as detailed
935         // in {@link http://docs.moodle.org/en/Development:Navigation_2.0_structure}
936         if (!isloggedin()) {
937             $activities = clone($this->rootnodes['site']->children);
938             $this->rootnodes['site']->remove();
939             $children = clone($this->children);
940             $this->children = new navigation_node_collection();
941             foreach ($activities as $child) {
942                 $this->children->add($child);
943             }
944             foreach ($children as $child) {
945                 $this->children->add($child);
946             }
947         }
949         $this->initialised = true;
950         return true;
951     }
952     /**
953      * Checks the course format to see whether it wants the navigation to load
954      * additional information for the course.
955      *
956      * This function utilises a callback that can exist within the course format lib.php file
957      * The callback should be a function called:
958      * callback_{formatname}_display_content()
959      * It doesn't get any arguments and should return true if additional content is
960      * desired. If the callback doesn't exist we assume additional content is wanted.
961      *
962      * @param string $format The course format
963      * @return bool
964      */
965     protected function format_display_course_content($format) {
966         global $CFG;
967         $formatlib = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
968         if (file_exists($formatlib)) {
969             require_once($formatlib);
970             $displayfunc = 'callback_'.$format.'_display_content';
971             if (function_exists($displayfunc) && !$displayfunc()) {
972                 return $displayfunc();
973             }
974         }
975         return true;
976     }
978     /**
979      * Loads of the the courses in Moodle into the navigation.
980      *
981      * @return array An array of navigation_node
982      */
983     protected function load_all_courses() {
984         global $DB, $USER;
985         list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
986         $sql = "SELECT c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.category,cat.path AS categorypath $ccselect
987                 FROM {course} c
988                 $ccjoin
989                 LEFT JOIN {course_categories} cat ON cat.id=c.category
990                 WHERE c.id != :siteid
991                 ORDER BY c.sortorder ASC";
992         $courses = $DB->get_records_sql($sql, array('siteid'=>SITEID));
993         $coursenodes = array();
994         foreach ($courses as $course) {
995             context_instance_preload($course);
996             $coursenodes[$course->id] = $this->add_course($course);
997         }
998         return $coursenodes;
999     }
1001     /**
1002      * Loads the given course into the navigation
1003      *
1004      * @param stdClass $course
1005      * @return navigation_node
1006      */
1007     protected function load_course(stdClass $course) {
1008         if ($course->id == SITEID) {
1009             $coursenode = $this->rootnodes['site'];
1010         } else if (array_key_exists($course->id, $this->mycourses)) {
1011             if (!isset($this->mycourses[$course->id]->coursenode)) {
1012                 $this->mycourses[$course->id]->coursenode = $this->add_course($course);
1013             }
1014             $coursenode = $this->mycourses[$course->id]->coursenode;
1015         } else {
1016             $coursenode = $this->add_course($course);
1017         }
1018         return $coursenode;
1019     }
1021     /**
1022      * Loads all of the courses section into the navigation.
1023      *
1024      * This function utilisies a callback that can be implemented within the course
1025      * formats lib.php file to customise the navigation that is generated at this
1026      * point for the course.
1027      *
1028      * By default (if not defined) the method {@see load_generic_course_sections} is
1029      * called instead.
1030      *
1031      * @param stdClass $course Database record for the course
1032      * @param navigation_node $coursenode The course node within the navigation
1033      * @return array Array of navigation nodes for the section with key = section id
1034      */
1035     protected function load_course_sections(stdClass $course, navigation_node $coursenode) {
1036         global $CFG;
1037         $structurefile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
1038         $structurefunc = 'callback_'.$course->format.'_load_content';
1039         if (function_exists($structurefunc)) {
1040             return $structurefunc($this, $course, $coursenode);
1041         } else if (file_exists($structurefile)) {
1042             require_once $structurefile;
1043             if (function_exists($structurefunc)) {
1044                 return $structurefunc($this, $course, $coursenode);
1045             } else {
1046                 return $this->load_generic_course_sections($course, $coursenode);
1047             }
1048         } else {
1049             return $this->load_generic_course_sections($course, $coursenode);
1050         }
1051     }
1053     /**
1054      * Generically loads the course sections into the course's navigation.
1055      *
1056      * @param stdClass $course
1057      * @param navigation_node $coursenode
1058      * @param string $name The string that identifies each section. e.g Topic, or Week
1059      * @param string $activeparam The url used to identify the active section
1060      * @return array An array of course section nodes
1061      */
1062     public function load_generic_course_sections(stdClass $course, navigation_node $coursenode, $courseformat='unknown') {
1063         global $DB, $USER;
1064         
1065         $modinfo = get_fast_modinfo($course);
1066         $sections = array_slice(get_all_sections($course->id), 0, $course->numsections+1, true);
1067         $viewhiddensections = has_capability('moodle/course:viewhiddensections', $this->page->context);
1069         if (isloggedin() && !isguestuser()) {
1070             $activesection = $DB->get_field("course_display", "display", array("userid"=>$USER->id, "course"=>$course->id));
1071         } else {
1072             $activesection = null;
1073         }
1075         $namingfunction = 'callback_'.$courseformat.'_get_section_name';
1076         $namingfunctionexists = (function_exists($namingfunction));
1077         
1078         $activeparamfunction = 'callback_'.$courseformat.'_request_key';
1079         if (function_exists($activeparamfunction)) {
1080             $activeparam = $activeparamfunction();
1081         } else {
1082             $activeparam = 'section';
1083         }
1085         foreach ($sections as &$section) {
1086             if ($course->id == SITEID) {
1087                 $this->load_section_activities($coursenode, $section->section, $modinfo);
1088             } else {
1089                 if ((!$viewhiddensections && !$section->visible) || (!$this->showemptysections && !array_key_exists($section->section, $modinfo->sections))) {
1090                     continue;
1091                 }
1092                 if ($namingfunctionexists) {
1093                     $sectionname = $namingfunction($course, $section, $sections);
1094                 } else {
1095                     $sectionname = get_string('section').' '.$section->section;
1096                 }
1097                 $url = new moodle_url('/course/view.php', array('id'=>$course->id, $activeparam=>$section->section));
1098                 $sectionnode = $coursenode->add($sectionname, $url, navigation_node::TYPE_SECTION, null, $section->id);
1099                 $sectionnode->nodetype = navigation_node::NODETYPE_BRANCH;
1100                 $sectionnode->hidden = (!$section->visible);
1101                 if ($this->page->context->contextlevel != CONTEXT_MODULE && ($sectionnode->isactive || ($activesection != null && $section->section == $activesection))) {
1102                     $sectionnode->force_open();
1103                     $this->load_section_activities($sectionnode, $section->section, $modinfo);
1104                 }
1105                 $section->sectionnode = $sectionnode;
1106             }
1107         }
1108         return $sections;
1109     }
1110     /**
1111      * Loads all of the activities for a section into the navigation structure.
1112      *
1113      * @param navigation_node $sectionnode
1114      * @param int $sectionnumber
1115      * @param stdClass $modinfo Object returned from {@see get_fast_modinfo()}
1116      * @return array Array of activity nodes
1117      */
1118     protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, $modinfo) {
1119         if (!array_key_exists($sectionnumber, $modinfo->sections)) {
1120             return true;
1121         }
1123         $viewhiddenactivities = has_capability('moodle/course:viewhiddenactivities', $this->page->context);
1125         $activities = array();
1127         foreach ($modinfo->sections[$sectionnumber] as $cmid) {
1128             $cm = $modinfo->cms[$cmid];
1129             if (!$viewhiddenactivities && !$cm->visible) {
1130                 continue;
1131             }
1132             if ($cm->icon) {
1133                 $icon = new pix_icon($cm->icon, '', $cm->iconcomponent);
1134             } else {
1135                 $icon = new pix_icon('icon', '', $cm->modname);
1136             }
1137             $url = new moodle_url('/mod/'.$cm->modname.'/view.php', array('id'=>$cm->id));
1138             $activitynode = $sectionnode->add($cm->name, $url, navigation_node::TYPE_ACTIVITY, $cm->name, $cm->id, $icon);
1139             $activitynode->title(get_string('modulename', $cm->modname));
1140             $activitynode->hidden = (!$cm->visible);
1141             if ($this->module_extends_navigation($cm->modname)) {
1142                 $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
1143             }
1144             $activities[$cmid] = $activitynode;
1145         }
1147         return $activities;
1148     }
1149     /**
1150      * Loads the navigation structure for the given activity into the activities node.
1151      *
1152      * This method utilises a callback within the modules lib.php file to load the
1153      * content specific to activity given.
1154      *
1155      * The callback is a method: {modulename}_extend_navigation()
1156      * Examples:
1157      *  * {@see forum_extend_navigation()}
1158      *  * {@see workshop_extend_navigation()}
1159      *
1160      * @param stdClass $cm
1161      * @param stdClass $course
1162      * @param navigation_node $activity
1163      * @return bool
1164      */
1165     protected function load_activity(stdClass $cm, stdClass $course, navigation_node $activity) {
1166         global $CFG, $DB;
1168         $activity->make_active();
1169         $file = $CFG->dirroot.'/mod/'.$cm->modname.'/lib.php';
1170         $function = $cm->modname.'_extend_navigation';
1172         if (file_exists($file)) {
1173             require_once($file);
1174             if (function_exists($function)) {
1175                 $activtyrecord = $DB->get_record($cm->modname, array('id' => $cm->instance), '*', MUST_EXIST);
1176                 $function($activity, $course, $activtyrecord, $cm);
1177                 return true;
1178             }
1179         }
1180         $activity->nodetype = navigation_node::NODETYPE_LEAF;
1181         return false;
1182     }
1183     /**
1184      * Loads user specific information into the navigation in the appopriate place.
1185      *
1186      * If no user is provided the current user is assumed.
1187      *
1188      * @param stdClass $user
1189      * @return bool
1190      */
1191     protected function load_for_user($user=null) {
1192         global $DB, $CFG, $USER;
1194         $iscurrentuser = false;
1195         if ($user === null) {
1196             // We can't require login here but if the user isn't logged in we don't
1197             // want to show anything
1198             if (!isloggedin()) {
1199                 return false;
1200             }
1201             $user = $USER;
1202             $iscurrentuser = true;
1203         } else if (!is_object($user)) {
1204             // If the user is not an object then get them from the database
1205             $user = $DB->get_record('user', array('id'=>(int)$user), '*', MUST_EXIST);
1206         }
1207         $usercontext = get_context_instance(CONTEXT_USER, $user->id);
1209         // Get the course set against the page, by default this will be the site
1210         $course = $this->page->course;
1211         $baseargs = array('id'=>$user->id);
1212         if ($course->id !== SITEID) {
1213             if (array_key_exists($course->id, $this->mycourses)) {
1214                 $coursenode = $this->mycourses[$course->id]->coursenode;
1215             } else {
1216                 $coursenode = $this->rootnodes['courses']->find($course->id, navigation_node::TYPE_COURSE);
1217                 if (!$coursenode) {
1218                     $coursenode = $this->load_course($course);
1219                 }
1220             }
1221             $baseargs['course'] = $course->id;
1222             $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1223             $issitecourse = false;
1224         } else {
1225             // Load all categories and get the context for the system
1226             $coursecontext = get_context_instance(CONTEXT_SYSTEM);
1227             $issitecourse = true;
1228         }
1230         // Create a node to add user information under.
1231         if ($iscurrentuser) {
1232             // If it's the current user the information will go under the profile root node
1233             $usernode = $this->rootnodes['myprofile'];
1234         } else {
1235             if (!$issitecourse) {
1236                 // Not the current user so add it to the participants node for the current course
1237                 $usersnode = $coursenode->get('participants', navigation_node::TYPE_CONTAINER);
1238             } else {
1239                 // This is the site so add a users node to the root branch
1240                 $usersnode = $this->rootnodes['users'];
1241                 $usersnode->action = new moodle_url('/user/index.php', array('id'=>$course->id));
1242             }
1243             // Add a branch for the current user
1244             $usernode = $usersnode->add(fullname($user, true));
1245         }
1247         if ($this->page->context->contextlevel == CONTEXT_USER && $user->id == $this->page->context->instanceid) {
1248             $usernode->force_open();
1249         }
1251         // If the user is the current user or has permission to view the details of the requested
1252         // user than add a view profile link.
1253         if ($iscurrentuser || has_capability('moodle/user:viewdetails', $coursecontext) || has_capability('moodle/user:viewdetails', $usercontext)) {
1254             $usernode->add(get_string('viewprofile'), new moodle_url('/user/view.php',$baseargs));
1255         }
1257         // Add nodes for forum posts and discussions if the user can view either or both
1258         $canviewposts = has_capability('moodle/user:readuserposts', $usercontext);
1259         $canviewdiscussions = has_capability('mod/forum:viewdiscussion', $coursecontext);
1260         if ($canviewposts || $canviewdiscussions) {
1261             $forumtab = $usernode->add(get_string('forumposts', 'forum'));
1262             if ($canviewposts) {
1263                 $forumtab->add(get_string('posts', 'forum'), new moodle_url('/mod/forum/user.php', $baseargs));
1264             }
1265             if ($canviewdiscussions) {
1266                 $forumtab->add(get_string('discussions', 'forum'), new moodle_url('/mod/forum/user.php', array_merge($baseargs, array('mode'=>'discussions'))));
1267             }
1268         }
1270         // Add blog nodes
1271         if (!empty($CFG->bloglevel)) {
1272             require_once($CFG->dirroot.'/blog/lib.php');
1273             // Get all options for the user
1274             $options = blog_get_options_for_user($user);
1275             if (count($options) > 0) {
1276                 $blogs = $usernode->add(get_string('blogs', 'blog'), null, navigation_node::TYPE_CONTAINER);
1277                 foreach ($options as $option) {
1278                     $blogs->add($option['string'], $option['link']);
1279                 }
1280             }
1281         }
1283         // Add a node to view the users notes if permitted
1284         if (!empty($CFG->enablenotes) && has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $coursecontext)) {
1285             $url = new moodle_url('/notes/index.php',array('user'=>$user->id));
1286             if ($coursecontext->instanceid) {
1287                 $url->param('course', $coursecontext->instanceid);
1288             }
1289             $usernode->add(get_string('notes', 'notes'), $url);
1290         }
1292         // Add a reports tab and then add reports the the user has permission to see.
1293         $reporttab = $usernode->add(get_string('activityreports'));
1294         $anyreport  = has_capability('moodle/user:viewuseractivitiesreport', $usercontext);
1295         $viewreports = ($anyreport || ($course->showreports && $iscurrentuser));
1296         $reportargs = array('user'=>$user->id);
1297         if (!empty($course->id)) {
1298             $reportargs['id'] = $course->id;
1299         } else {
1300             $reportargs['id'] = SITEID;
1301         }
1302         if ($viewreports || has_capability('coursereport/outline:view', $coursecontext)) {
1303             $reporttab->add(get_string('outlinereport'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'outline'))));
1304             $reporttab->add(get_string('completereport'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'complete'))));
1305         }
1307         if ($viewreports || has_capability('coursereport/log:viewtoday', $coursecontext)) {
1308             $reporttab->add(get_string('todaylogs'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'todaylogs'))));
1309         }
1311         if ($viewreports || has_capability('coursereport/log:view', $coursecontext)) {
1312             $reporttab->add(get_string('alllogs'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'alllogs'))));
1313         }
1315         if (!empty($CFG->enablestats)) {
1316             if ($viewreports || has_capability('coursereport/stats:view', $coursecontext)) {
1317                 $reporttab->add(get_string('stats'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'stats'))));
1318             }
1319         }
1321         $gradeaccess = false;
1322         if (has_capability('moodle/grade:viewall', $coursecontext)) {
1323             //ok - can view all course grades
1324             $gradeaccess = true;
1325         } else if ($course->showgrades) {
1326             if ($iscurrentuser && has_capability('moodle/grade:view', $coursecontext)) {
1327                 //ok - can view own grades
1328                 $gradeaccess = true;
1329             } else if (has_capability('moodle/grade:viewall', $usercontext)) {
1330                 // ok - can view grades of this user - parent most probably
1331                 $gradeaccess = true;
1332             } else if ($anyreport) {
1333                 // ok - can view grades of this user - parent most probably
1334                 $gradeaccess = true;
1335             }
1336         }
1337         if ($gradeaccess) {
1338             $reporttab->add(get_string('grade'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'grade'))));
1339         }
1341         // Check the number of nodes in the report node... if there are none remove
1342         // the node
1343         if (count($reporttab->children)===0) {
1344             $usernode->remove_child($reporttab);
1345         }
1347         // If the user is the current user add the repositories for the current user
1348         if ($iscurrentuser) {
1349             require_once($CFG->dirroot . '/repository/lib.php');
1350             $editabletypes = repository::get_editable_types($usercontext);
1351             if (!empty($editabletypes)) {
1352                 $usernode->add(get_string('repositories', 'repository'), new moodle_url('/repository/manage_instances.php', array('contextid' => $usercontext->id)));
1353             }
1354         }
1355         return true;
1356     }
1358     /**
1359      * This method simply checks to see if a given module can extend the navigation.
1360      *
1361      * @param string $modname
1362      * @return bool
1363      */
1364     protected function module_extends_navigation($modname) {
1365         global $CFG;
1366         if ($this->cache->cached($modname.'_extends_navigation')) {
1367             return $this->cache->{$modname.'_extends_navigation'};
1368         }
1369         $file = $CFG->dirroot.'/mod/'.$modname.'/lib.php';
1370         $function = $modname.'_extend_navigation';
1371         if (function_exists($function)) {
1372             $this->cache->{$modname.'_extends_navigation'} = true;
1373             return true;
1374         } else if (file_exists($file)) {
1375             require_once($file);
1376             if (function_exists($function)) {
1377                 $this->cache->{$modname.'_extends_navigation'} = true;
1378                 return true;
1379             }
1380         }
1381         $this->cache->{$modname.'_extends_navigation'} = false;
1382         return false;
1383     }
1384     /**
1385      * Extends the navigation for the given user.
1386      * 
1387      * @param stdClass $user A user from the database
1388      */
1389     public function extend_for_user($user) {
1390         $this->extendforuser[] = $user;
1391     }
1392     /**
1393      * Adds the given course to the navigation structure.
1394      *
1395      * @param stdClass $course
1396      * @return navigation_node
1397      */
1398     public function add_course(stdClass $course) {
1399         if (array_key_exists($course->id, $this->addedcourses)) {
1400             return $this->addedcourses[$course->id];
1401         }
1403         $canviewhidden = has_capability('moodle/course:viewhiddencourses', $this->page->context);
1404         if ($course->id !== SITEID && !$canviewhidden && (!$course->visible || !course_parent_visible($course))) {
1405             return false;
1406         }
1408         if ($course->id == SITEID) {
1409             $parent = $this;
1410             $url = new moodle_url('/');
1411         } else if (array_key_exists($course->id, $this->mycourses)) {
1412             $parent = $this->rootnodes['mycourses'];
1413             $url = new moodle_url('/course/view.php', array('id'=>$course->id));
1414         } else {
1415             $parent = $this->rootnodes['courses'];
1416             $url = new moodle_url('/course/view.php', array('id'=>$course->id));
1417         }
1418         $coursenode = $parent->add($course->shortname, $url, self::TYPE_COURSE, $course->shortname, $course->id);
1419         $coursenode->nodetype = self::NODETYPE_BRANCH;
1420         $coursenode->hidden = (!$course->visible);
1421         $this->addedcourses[$course->id] = &$coursenode;
1422         return $coursenode;
1423     }
1424     /**
1425      * Adds essential course nodes to the navigation for the given course.
1426      *
1427      * This method adds nodes such as reports, blogs and participants
1428      *
1429      * @param navigation_node $coursenode
1430      * @param stdClass $course
1431      * @return bool
1432      */
1433     public function add_course_essentials(navigation_node $coursenode, stdClass $course) {
1434         global $CFG;
1436         if ($course->id === SITEID) {
1437             return $this->add_front_page_course_essentials($coursenode, $course);
1438         }
1440         if ($coursenode == false || $coursenode->get('participants', navigation_node::TYPE_CONTAINER)) {
1441             return true;
1442         }
1444         //Participants
1445         if (has_capability('moodle/course:viewparticipants', $this->page->context)) {
1446             require_once($CFG->dirroot.'/blog/lib.php');
1447             $participants = $coursenode->add(get_string('participants'), new moodle_url('/user/index.php?id='.$course->id), self::TYPE_CONTAINER, get_string('participants'), 'participants');
1448             $currentgroup = groups_get_course_group($course, true);
1449             if ($course->id == SITEID) {
1450                 $filterselect = '';
1451             } else if ($course->id && !$currentgroup) {
1452                 $filterselect = $course->id;
1453             } else {
1454                 $filterselect = $currentgroup;
1455             }
1456             $filterselect = clean_param($filterselect, PARAM_INT);
1457             if ($CFG->bloglevel >= 3) {
1458                 $blogsurls = new moodle_url('/blog/index.php', array('courseid' => $filterselect));
1459                 $participants->add(get_string('blogs','blog'), $blogsurls->out());
1460             }
1461             if (!empty($CFG->enablenotes) && (has_capability('moodle/notes:manage', $this->page->context) || has_capability('moodle/notes:view', $this->page->context))) {
1462                 $participants->add(get_string('notes','notes'), new moodle_url('/notes/index.php', array('filtertype'=>'course', 'filterselect'=>$filterselect)));
1463             }
1464         } else if (count($this->extendforuser) > 0) {
1465             $participants = $coursenode->add(get_string('participants'), null, self::TYPE_CONTAINER, get_string('participants'), 'participants');
1466         }
1468         // View course reports
1469         if (has_capability('moodle/site:viewreports', $this->page->context)) { // basic capability for listing of reports
1470             $reportnav = $coursenode->add(get_string('reports'), new moodle_url('/course/report.php', array('id'=>$course->id)), self::TYPE_CONTAINER, null, null, new pix_icon('i/stats', ''));
1471             $coursereports = get_plugin_list('coursereport');
1472             foreach ($coursereports as $report=>$dir) {
1473                 $libfile = $CFG->dirroot.'/course/report/'.$report.'/lib.php';
1474                 if (file_exists($libfile)) {
1475                     require_once($libfile);
1476                     $reportfunction = $report.'_report_extend_navigation';
1477                     if (function_exists($report.'_report_extend_navigation')) {
1478                         $reportfunction($reportnav, $course, $this->page->context);
1479                     }
1480                 }
1481             }
1482         }
1483         return true;
1484     }
1486     public function add_front_page_course_essentials(navigation_node $coursenode, stdClass $course) {
1487         global $CFG;
1489         if ($coursenode == false || $coursenode->get('participants', navigation_node::TYPE_CUSTOM)) {
1490             return true;
1491         }
1493         //Participants
1494         if (has_capability('moodle/course:viewparticipants', $this->page->context)) {
1495             $coursenode->add(get_string('participants'), new moodle_url('/user/index.php?id='.$course->id), self::TYPE_CUSTOM, get_string('participants'), 'participants');
1496         }
1497         
1498         $currentgroup = groups_get_course_group($course, true);
1499         if ($course->id == SITEID) {
1500             $filterselect = '';
1501         } else if ($course->id && !$currentgroup) {
1502             $filterselect = $course->id;
1503         } else {
1504             $filterselect = $currentgroup;
1505         }
1506         $filterselect = clean_param($filterselect, PARAM_INT);
1508         // Blogs
1509         if (has_capability('moodle/blog:view', $this->page->context)) {
1510             require_once($CFG->dirroot.'/blog/lib.php');
1511             if (blog_is_enabled_for_user()) {
1512                 $blogsurls = new moodle_url('/blog/index.php', array('courseid' => $filterselect));
1513                 $coursenode->add(get_string('blogs','blog'), $blogsurls->out());
1514             }
1515         }
1517         // Notes
1518         if (!empty($CFG->enablenotes) && (has_capability('moodle/notes:manage', $this->page->context) || has_capability('moodle/notes:view', $this->page->context))) {
1519             $coursenode->add(get_string('notes','notes'), new moodle_url('/notes/index.php', array('filtertype'=>'course', 'filterselect'=>$filterselect)));
1520         }
1522         // Tags
1523         if (!empty($CFG->usetags) && isloggedin()) {
1524             $coursenode->add(get_string('tags', 'tag'), new moodle_url('/tag/search.php'));
1525         }
1528         // View course reports
1529         if (has_capability('moodle/site:viewreports', $this->page->context)) { // basic capability for listing of reports
1530             $reportnav = $coursenode->add(get_string('reports'), new moodle_url('/course/report.php', array('id'=>$course->id)), self::TYPE_CONTAINER, null, null, new pix_icon('i/stats', ''));
1531             $coursereports = get_plugin_list('coursereport');
1532             foreach ($coursereports as $report=>$dir) {
1533                 $libfile = $CFG->dirroot.'/course/report/'.$report.'/lib.php';
1534                 if (file_exists($libfile)) {
1535                     require_once($libfile);
1536                     $reportfunction = $report.'_report_extend_navigation';
1537                     if (function_exists($report.'_report_extend_navigation')) {
1538                         $reportfunction($reportnav, $course, $this->page->context);
1539                     }
1540                 }
1541             }
1542         }
1543         return true;
1544     }
1546     /**
1547      * Clears the navigation cache
1548      */
1549     public function clear_cache() {
1550         $this->cache->clear();
1551     }
1553     public function set_expansion_limit($type) {
1554         $nodes = $this->find_all_of_type($type);
1555         foreach ($nodes as &$node) {
1556             foreach ($node->children as &$child) {
1557                 $child->display = false;
1558             }
1559         }
1560         return true;
1561     }
1564 /**
1565  * The limited global navigation class used for the AJAX extension of the global
1566  * navigation class.
1567  *
1568  * The primary methods that are used in the global navigation class have been overriden
1569  * to ensure that only the relevant branch is generated at the root of the tree.
1570  * This can be done because AJAX is only used when the backwards structure for the
1571  * requested branch exists.
1572  * This has been done only because it shortens the amounts of information that is generated
1573  * which of course will speed up the response time.. because no one likes laggy AJAX.
1574  *
1575  * @package moodlecore
1576  * @subpackage navigation
1577  * @copyright 2009 Sam Hemelryk
1578  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1579  */
1580 class global_navigation_for_ajax extends global_navigation {
1582     /** @var array */
1583     protected $expandable = array();
1585     /**
1586      * Constructs the navigation for use in AJAX request
1587      */
1588     public function __construct() {
1589         global $SITE;
1590         $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
1591         $this->children = new navigation_node_collection();
1592         $this->rootnodes = array();
1593         //$this->rootnodes['site']      = $this->add_course($SITE);
1594         $this->rootnodes['courses'] = $this->add(get_string('courses'), null, self::TYPE_ROOTNODE, null, 'courses');
1595     }
1596     /**
1597      * Initialise the navigation given the type and id for the branch to expand.
1598      *
1599      * @param int $branchtype One of navigation_node::TYPE_*
1600      * @param int $id
1601      * @return array The expandable nodes
1602      */
1603     public function initialise($branchtype, $id) {
1604         global $DB, $PAGE;
1606         if ($this->initialised || during_initial_install()) {
1607             return $this->expandable;
1608         }
1610         // Branchtype will be one of navigation_node::TYPE_*
1611         switch ($branchtype) {
1612             case self::TYPE_COURSE :
1613                 $course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST);
1614                 require_course_login($course);
1615                 $this->page = $PAGE;
1616                 $coursenode = $this->add_course($course);
1617                 $this->add_course_essentials($coursenode, $course);
1618                 if ($this->format_display_course_content($course->format)) {
1619                     $this->load_course_sections($course, $coursenode);
1620                 }
1621                 break;
1622             case self::TYPE_SECTION :
1623                 $sql = 'SELECT c.*, cs.section AS sectionnumber
1624                         FROM {course} c
1625                         LEFT JOIN {course_sections} cs ON cs.course = c.id
1626                         WHERE cs.id = ?';
1627                 $course = $DB->get_record_sql($sql, array($id), MUST_EXIST);
1628                 require_course_login($course);
1629                 $this->page = $PAGE;
1630                 $coursenode = $this->add_course($course);
1631                 $this->add_course_essentials($coursenode, $course);
1632                 $sections = $this->load_course_sections($course, $coursenode);
1633                 $this->load_section_activities($sections[$course->sectionnumber]->sectionnode, $course->sectionnumber, get_fast_modinfo($course));
1634                 break;
1635             case self::TYPE_ACTIVITY :
1636                 $cm = get_coursemodule_from_id(false, $id, 0, false, MUST_EXIST);
1637                 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1638                 require_course_login($course, true, $cm);
1639                 $this->page = $PAGE;
1640                 $coursenode = $this->load_course($course);
1641                 $sections = $this->load_course_sections($course, $coursenode);
1642                 foreach ($sections as $section) {
1643                     if ($section->id == $cm->section) {
1644                         $cm->sectionnumber = $section->section;
1645                         break;
1646                     }
1647                 }
1648                 $activities = $this->load_section_activities($sections[$cm->sectionnumber]->sectionnode, $cm->sectionnumber, get_fast_modinfo($course));
1649                 $modulenode = $this->load_activity($cm, $course, $activities[$cm->id]);
1650                 break;
1651             default:
1652                 throw new Exception('Unknown type');
1653                 return $this->expandable;
1654         }
1656         $this->find_expandable($this->expandable);
1657         return $this->expandable;
1658     }
1661 /**
1662  * Navbar class
1663  *
1664  * This class is used to manage the navbar, which is initialised from the navigation
1665  * object held by PAGE
1666  *
1667  * @package moodlecore
1668  * @subpackage navigation
1669  * @copyright 2009 Sam Hemelryk
1670  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1671  */
1672 class navbar extends navigation_node {
1673     /** @var bool */
1674     protected $initialised = false;
1675     /** @var mixed */
1676     protected $keys = array();
1677     /** @var null|string */
1678     protected $content = null;
1679     /** @var moodle_page object */
1680     protected $page;
1681     /** @var bool */
1682     protected $ignoreactive = false;
1683     /** @var bool */
1684     protected $duringinstall = false;
1685     /** @var bool */
1686     protected $hasitems = false;
1687     /** @var array */
1688     protected $items;
1689     /** @var array */
1690     public $children = array();
1691     /**
1692      * The almighty constructor
1693      *
1694      * @param moodle_page $page
1695      */
1696     public function __construct(moodle_page $page) {
1697         global $CFG;
1698         if (during_initial_install()) {
1699             $this->duringinstall = true;
1700             return false;
1701         }
1702         $this->page = $page;
1703         $this->text = get_string('home');
1704         $this->shorttext = get_string('home');
1705         $this->action = new moodle_url($CFG->wwwroot);
1706         $this->nodetype = self::NODETYPE_BRANCH;
1707         $this->type = self::TYPE_SYSTEM;
1708     }
1710     /**
1711      * Quick check to see if the navbar will have items in.
1712      *
1713      * @return bool Returns true if the navbar will have items, false otherwise
1714      */
1715     public function has_items() {
1716         if ($this->duringinstall) {
1717             return false;
1718         } else if ($this->hasitems !== false) {
1719             return true;
1720         }
1721         $this->page->navigation->initialise($this->page);
1723         $activenodefound = ($this->page->navigation->contains_active_node() ||
1724                             $this->page->settingsnav->contains_active_node());
1726         $outcome = (count($this->children)>0 || (!$this->ignoreactive && $activenodefound));
1727         $this->hasitems = $outcome;
1728         return $outcome;
1729     }
1731     /**
1732      * Turn on/off ignore active
1733      *
1734      * @param bool $setting
1735      */
1736     public function ignore_active($setting=true) {
1737         $this->ignoreactive = ($setting);
1738     }
1739     public function get($key, $type = null) {
1740         foreach ($this->children as &$child) {
1741             if ($child->key === $key && ($type == null || $type == $child->type)) {
1742                 return $child;
1743             }
1744         }
1745         return false;
1746     }
1747     /**
1748      * Returns an array of navigation_node's that make up the navbar.
1749      * 
1750      * @return array
1751      */
1752     public function get_items() {
1753         $items = array();
1754         // Make sure that navigation is initialised
1755         if (!$this->has_items()) {
1756             return $items;
1757         }
1758         if ($this->items !== null) {
1759             return $this->items;
1760         }
1762         if (count($this->children) > 0) {
1763             // Add the custom children
1764             $items = array_reverse($this->children);
1765         }
1767         $navigationactivenode = $this->page->navigation->find_active_node();
1768         $settingsactivenode = $this->page->settingsnav->find_active_node();
1770         // Check if navigation contains the active node
1771         if (!$this->ignoreactive) {
1772             
1773             if ($navigationactivenode && $settingsactivenode) {
1774                 // Parse a combined navigation tree
1775                 while ($settingsactivenode && $settingsactivenode->parent !== null) {
1776                     if (!$settingsactivenode->mainnavonly) {
1777                         $items[] = $settingsactivenode;
1778                     }
1779                     $settingsactivenode = $settingsactivenode->parent;
1780                 }
1781                 // Removes the first node from the settings (root node) from the list
1782                 array_pop($items);
1783                 while ($navigationactivenode && $navigationactivenode->parent !== null) {
1784                     if (!$navigationactivenode->mainnavonly) {
1785                         $items[] = $navigationactivenode;
1786                     }
1787                     $navigationactivenode = $navigationactivenode->parent;
1788                 }
1789             } else if ($navigationactivenode) {
1790                 // Parse the navigation tree to get the active node
1791                 while ($navigationactivenode && $navigationactivenode->parent !== null) {
1792                     if (!$navigationactivenode->mainnavonly) {
1793                         $items[] = $navigationactivenode;
1794                     }
1795                     $navigationactivenode = $navigationactivenode->parent;
1796                 }
1797             } else if ($settingsactivenode) {
1798                 // Parse the settings navigation to get the active node
1799                 while ($settingsactivenode && $settingsactivenode->parent !== null) {
1800                     if (!$settingsactivenode->mainnavonly) {
1801                         $items[] = $settingsactivenode;
1802                     }
1803                     $settingsactivenode = $settingsactivenode->parent;
1804                 }
1805             }
1806         }
1808         $items[] = new navigation_node(array(
1809             'text'=>$this->page->navigation->text,
1810             'shorttext'=>$this->page->navigation->shorttext,
1811             'key'=>$this->page->navigation->key,
1812             'action'=>$this->page->navigation->action
1813         ));
1815         $this->items = array_reverse($items);
1816         return $this->items;
1817     }
1819     /**
1820      * Add a new navigation_node to the navbar, overrides parent::add
1821      *
1822      * This function overrides {@link navigation_node::add()} so that we can change
1823      * the way nodes get added to allow us to simply call add and have the node added to the
1824      * end of the navbar
1825      *
1826      * @param string $text
1827      * @param string|moodle_url $action
1828      * @param int $type
1829      * @param string|int $key
1830      * @param string $shorttext
1831      * @param string $icon
1832      * @return navigation_node
1833      */
1834     public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) {
1835         if ($this->content !== null) {
1836             debugging('Nav bar items must be printed before $OUTPUT->header() has been called', DEBUG_DEVELOPER);
1837         }
1838         
1839         // Properties array used when creating the new navigation node
1840         $itemarray = array(
1841             'text' => $text,
1842             'type' => $type
1843         );
1844         // Set the action if one was provided
1845         if ($action!==null) {
1846             $itemarray['action'] = $action;
1847         }
1848         // Set the shorttext if one was provided
1849         if ($shorttext!==null) {
1850             $itemarray['shorttext'] = $shorttext;
1851         }
1852         // Set the icon if one was provided
1853         if ($icon!==null) {
1854             $itemarray['icon'] = $icon;
1855         }
1856         // Default the key to the number of children if not provided
1857         if ($key === null) {
1858             $key = count($this->children);
1859         }
1860         // Set the key
1861         $itemarray['key'] = $key;
1862         // Set the parent to this node
1863         $itemarray['parent'] = $this;
1864         // Add the child using the navigation_node_collections add method
1865         $this->children[] = new navigation_node($itemarray);
1866         return $this;
1867     }
1870 /**
1871  * Class used to manage the settings option for the current page
1872  *
1873  * This class is used to manage the settings options in a tree format (recursively)
1874  * and was created initially for use with the settings blocks.
1875  *
1876  * @package moodlecore
1877  * @subpackage navigation
1878  * @copyright 2009 Sam Hemelryk
1879  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1880  */
1881 class settings_navigation extends navigation_node {
1882     /** @var stdClass */
1883     protected $context;
1884     /** @var navigation_cache */
1885     protected $cache;
1886     /** @var moodle_page */
1887     protected $page;
1888     /** @var string */
1889     protected $adminsection;
1890     /**
1891      * Sets up the object with basic settings and preparse it for use
1892      * 
1893      * @param moodle_page $page
1894      */
1895     public function __construct(moodle_page &$page) {
1896         if (during_initial_install()) {
1897             return false;
1898         }
1899         $this->page = $page;
1900         // Initialise the main navigation. It is most important that this is done
1901         // before we try anything
1902         $this->page->navigation->initialise();
1903         // Initialise the navigation cache
1904         $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
1905         $this->children = new navigation_node_collection();
1906     }
1907     /**
1908      * Initialise the settings navigation based on the current context
1909      *
1910      * This function initialises the settings navigation tree for a given context
1911      * by calling supporting functions to generate major parts of the tree.
1912      *
1913      */
1914     public function initialise() {
1915         global $DB;
1917         if (during_initial_install()) {
1918             return false;
1919         }
1920         $this->id = 'settingsnav';
1921         $this->context = $this->page->context;
1923         $context = $this->context;
1924         if ($context->contextlevel == CONTEXT_BLOCK) {
1925             $this->load_block_settings();
1926             $context = $DB->get_record_sql('SELECT ctx.* FROM {block_instances} bi LEFT JOIN {context} ctx ON ctx.id=bi.parentcontextid WHERE bi.id=?', array($context->instanceid));
1927         }
1929         switch ($context->contextlevel) {
1930             case CONTEXT_SYSTEM:
1931                 if ($this->page->url->compare(new moodle_url('/admin/settings.php', array('section'=>'frontpagesettings')))) {
1932                     $this->load_front_page_settings(($context->id == $this->context->id));
1933                 }
1934                 break;
1935             case CONTEXT_COURSECAT:
1936                 $this->load_category_settings();
1937                 break;
1938             case CONTEXT_COURSE:
1939                 if ($this->page->course->id != SITEID) {
1940                     $this->load_course_settings(($context->id == $this->context->id));
1941                 } else {
1942                     $this->load_front_page_settings(($context->id == $this->context->id));
1943                 }
1944                 break;
1945             case CONTEXT_MODULE:
1946                 $this->load_module_settings();
1947                 $this->load_course_settings();
1948                 break;
1949             case CONTEXT_USER:
1950                 if ($this->page->course->id != SITEID) {
1951                     $this->load_course_settings();
1952                 }
1953                 break;
1954         }
1956         $settings = $this->load_user_settings($this->page->course->id);
1957         $admin = $this->load_administration_settings();
1959         if ($context->contextlevel == CONTEXT_SYSTEM && $admin) {
1960             $admin->force_open();
1961         } else if ($context->contextlevel == CONTEXT_USER && $settings) {
1962             $settings->force_open();
1963         }
1965         // Check if the user is currently logged in as another user
1966         if (session_is_loggedinas()) {
1967             // Get the actual user, we need this so we can display an informative return link
1968             $realuser = session_get_realuser();
1969             // Add the informative return to original user link
1970             $url = new moodle_url('/course/loginas.php',array('id'=>$this->page->course->id, 'return'=>1,'sesskey'=>sesskey()));
1971             $this->add(get_string('returntooriginaluser', 'moodle', fullname($realuser, true)), $url, self::TYPE_SETTING, null, null, new pix_icon('t/left', ''));
1972         }
1974         // Make sure the first child doesnt have proceed with hr set to true
1976         foreach ($this->children as $key=>$node) {
1977             if ($node->nodetype != self::NODETYPE_BRANCH || $node->children->count()===0) {
1978                 $node->remove();
1979             }
1980         }
1981     }
1982     /**
1983      * Override the parent function so that we can add preceeding hr's and set a
1984      * root node class against all first level element
1985      *
1986      * It does this by first calling the parent's add method {@link navigation_node::add()}
1987      * and then proceeds to use the key to set class and hr
1988      *
1989      * @param string $text
1990      * @param sting|moodle_url $url
1991      * @param string $shorttext
1992      * @param string|int $key
1993      * @param int $type
1994      * @param string $icon
1995      * @return navigation_node
1996      */
1997     public function add($text, $url=null, $type=null, $shorttext=null, $key=null, pix_icon $icon=null) {
1998         $node = parent::add($text, $url, $type, $shorttext, $key, $icon);
1999         $node->add_class('root_node');
2000         return $node;
2001     }
2003     /**
2004      * This function allows the user to add something to the start of the settings
2005      * navigation, which means it will be at the top of the settings navigation block
2006      *
2007      * @param string $text
2008      * @param sting|moodle_url $url
2009      * @param string $shorttext
2010      * @param string|int $key
2011      * @param int $type
2012      * @param string $icon
2013      * @return navigation_node
2014      */
2015     public function prepend($text, $url=null, $type=null, $shorttext=null, $key=null, pix_icon $icon=null) {
2016         $children = $this->children;
2017         $childrenclass = get_class($children);
2018         $this->children = new $childrenclass;
2019         $node = $this->add($text, $url, $type, $shorttext, $key, $icon);
2020         foreach ($children as $child) {
2021             $this->children->add($child);
2022         }
2023         return $node;
2024     }
2025     /**
2026      * Load the site administration tree
2027      *
2028      * This function loads the site administration tree by using the lib/adminlib library functions
2029      *
2030      * @param navigation_node $referencebranch A reference to a branch in the settings
2031      *      navigation tree
2032      * @param part_of_admin_tree $adminbranch The branch to add, if null generate the admin
2033      *      tree and start at the beginning
2034      * @return mixed A key to access the admin tree by
2035      */
2036     protected function load_administration_settings(navigation_node $referencebranch=null, part_of_admin_tree $adminbranch=null) {
2037         global $CFG;
2039         // Check if we are just starting to generate this navigation.
2040         if ($referencebranch === null) {
2042             // Require the admin lib then get an admin structure
2043             if (!function_exists('admin_get_root')) {
2044                 require_once($CFG->dirroot.'/lib/adminlib.php');
2045             }
2046             $adminroot = admin_get_root(false, false);
2047             // This is the active section identifier
2048             $this->adminsection = $this->page->url->param('section');
2050             // Disable the navigation from automatically finding the active node
2051             navigation_node::$autofindactive = false;
2052             $referencebranch = $this->add(get_string('administrationsite'), null, self::TYPE_SETTING, null, 'root');
2053             foreach ($adminroot->children as $adminbranch) {
2054                 $this->load_administration_settings($referencebranch, $adminbranch);
2055             }
2056             navigation_node::$autofindactive = true;
2058             // Use the admin structure to locate the active page
2059             if (!$this->contains_active_node() && $current = $adminroot->locate($this->adminsection, true)) {
2060                 $currentnode = $this;
2061                 while (($pathkey = array_pop($current->path))!==null && $currentnode) {
2062                     $currentnode = $currentnode->get($pathkey);
2063                 }
2064                 if ($currentnode) {
2065                     $currentnode->make_active();
2066                 }
2067             }
2068             return $referencebranch;
2069         } else if ($adminbranch->check_access()) {
2070             // We have a reference branch that we can access and is not hidden `hurrah`
2071             // Now we need to display it and any children it may have
2072             $url = null;
2073             $icon = null;
2074             if ($adminbranch instanceof admin_settingpage) {
2075                 $url = new moodle_url('/admin/settings.php', array('section'=>$adminbranch->name));
2076             } else if ($adminbranch instanceof admin_externalpage) {
2077                 $url = $adminbranch->url;
2078             }
2080             // Add the branch
2081             $reference = $referencebranch->add($adminbranch->visiblename, $url, self::TYPE_SETTING, null, $adminbranch->name, $icon);
2083             if ($adminbranch->is_hidden()) {
2084                 if (($adminbranch instanceof admin_externalpage || $adminbranch instanceof admin_settingpage) && $adminbranch->name == $this->adminsection) {
2085                     $reference->add_class('hidden');
2086                 } else {
2087                     $reference->display = false;
2088                 }
2089             }
2091             // Check if we are generating the admin notifications and whether notificiations exist
2092             if ($adminbranch->name === 'adminnotifications' && admin_critical_warnings_present()) {
2093                 $reference->add_class('criticalnotification');
2094             }
2095             // Check if this branch has children
2096             if ($reference && isset($adminbranch->children) && is_array($adminbranch->children) && count($adminbranch->children)>0) {
2097                 foreach ($adminbranch->children as $branch) {
2098                     // Generate the child branches as well now using this branch as the reference
2099                     $this->load_administration_settings($reference, $branch);
2100                 }
2101             } else {
2102                 $reference->icon = new pix_icon('i/settings', '');
2103             }
2104         }
2105     }
2107     /**
2108      * Gets a navigation node given an array of keys that represent the path to
2109      * the desired node.
2110      *
2111      * @param array $path
2112      * @return navigation_node|false
2113      */
2114     protected function get_by_path(array $path) {
2115         $node = $this->get(array_shift($path));
2116         foreach ($path as $key) {
2117             $node->get($key);
2118         }
2119         return $node;
2120     }
2122     /**
2123      * Generate the list of modules for the given course.
2124      *
2125      * The array of resources and activities that can be added to a course is then
2126      * stored in the cache so that we can access it for anywhere.
2127      * It saves us generating it all the time
2128      *
2129      * <code php>
2130      * // To get resources:
2131      * $this->cache->{'course'.$courseid.'resources'}
2132      * // To get activities:
2133      * $this->cache->{'course'.$courseid.'activities'}
2134      * </code>
2135      *
2136      * @param stdClass $course The course to get modules for
2137      */
2138     protected function get_course_modules($course) {
2139         global $CFG;
2140         $mods = $modnames = $modnamesplural = $modnamesused = array();
2141         // This function is included when we include course/lib.php at the top
2142         // of this file
2143         get_all_mods($course->id, $mods, $modnames, $modnamesplural, $modnamesused);
2144         $resources = array();
2145         $activities = array();
2146         foreach($modnames as $modname=>$modnamestr) {
2147             if (!course_allowed_module($course, $modname)) {
2148                 continue;
2149             }
2151             $libfile = "$CFG->dirroot/mod/$modname/lib.php";
2152             if (!file_exists($libfile)) {
2153                 continue;
2154             }
2155             include_once($libfile);
2156             $gettypesfunc =  $modname.'_get_types';
2157             if (function_exists($gettypesfunc)) {
2158                 $types = $gettypesfunc();
2159                 foreach($types as $type) {
2160                     if (!isset($type->modclass) || !isset($type->typestr)) {
2161                         debugging('Incorrect activity type in '.$modname);
2162                         continue;
2163                     }
2164                     if ($type->modclass == MOD_CLASS_RESOURCE) {
2165                         $resources[html_entity_decode($type->type)] = $type->typestr;
2166                     } else {
2167                         $activities[html_entity_decode($type->type)] = $type->typestr;
2168                     }
2169                 }
2170             } else {
2171                 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
2172                 if ($archetype == MOD_ARCHETYPE_RESOURCE) {
2173                     $resources[$modname] = $modnamestr;
2174                 } else {
2175                     // all other archetypes are considered activity
2176                     $activities[$modname] = $modnamestr;
2177                 }
2178             }
2179         }
2180         $this->cache->{'course'.$course->id.'resources'} = $resources;
2181         $this->cache->{'course'.$course->id.'activities'} = $activities;
2182     }
2184     /**
2185      * This function loads the course settings that are available for the user
2186      *
2187      * @param bool $forceopen If set to true the course node will be forced open
2188      * @return navigation_node|false
2189      */
2190     protected function load_course_settings($forceopen = false) {
2191         global $CFG, $USER, $SESSION, $OUTPUT;
2193         $course = $this->page->course;
2194         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
2195         if (!$this->cache->cached('canviewcourse'.$course->id)) {
2196             $this->cache->{'canviewcourse'.$course->id} = has_capability('moodle/course:participate', $coursecontext);
2197         }
2198         if ($course->id === SITEID || !$this->cache->{'canviewcourse'.$course->id}) {
2199             return false;
2200         }
2202         $coursenode = $this->add(get_string('courseadministration'), null, self::TYPE_COURSE, null, 'courseadmin');
2203         if ($forceopen) {
2204             $coursenode->force_open();
2205         }
2207         if (has_capability('moodle/course:update', $coursecontext)) {
2208             // Add the turn on/off settings
2209             $url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
2210             if ($this->page->user_is_editing()) {
2211                 $url->param('edit', 'off');
2212                 $editstring = get_string('turneditingoff');
2213             } else {
2214                 $url->param('edit', 'on');
2215                 $editstring = get_string('turneditingon');
2216             }
2217             $coursenode->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
2219             if ($this->page->user_is_editing()) {
2220                 // Add `add` resources|activities branches
2221                 $structurefile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
2222                 if (file_exists($structurefile)) {
2223                     require_once($structurefile);
2224                     $formatstring = call_user_func('callback_'.$course->format.'_definition');
2225                     $formatidentifier = optional_param(call_user_func('callback_'.$course->format.'_request_key'), 0, PARAM_INT);
2226                 } else {
2227                     $formatstring = get_string('topic');
2228                     $formatidentifier = optional_param('topic', 0, PARAM_INT);
2229                 }
2230                 if (!$this->cache->cached('coursesections'.$course->id)) {
2231                     $this->cache->{'coursesections'.$course->id} = get_all_sections($course->id);
2232                 }
2233                 $sections = $this->cache->{'coursesections'.$course->id};
2235                 $addresource = $this->add(get_string('addresource'));
2236                 $addactivity = $this->add(get_string('addactivity'));
2237                 if ($formatidentifier!==0) {
2238                     $addresource->force_open();
2239                     $addactivity->force_open();
2240                 }
2242                 if (!$this->cache->cached('course'.$course->id.'resources')) {
2243                     $this->get_course_modules($course);
2244                 }
2245                 $resources = $this->cache->{'course'.$course->id.'resources'};
2246                 $activities = $this->cache->{'course'.$course->id.'activities'};
2248                 $textlib = textlib_get_instance();
2250                 foreach ($sections as $section) {
2251                     if ($formatidentifier !== 0 && $section->section != $formatidentifier) {
2252                         continue;
2253                     }
2254                     $sectionurl = new moodle_url('/course/view.php', array('id'=>$course->id, $formatstring=>$section->section));
2255                     if ($section->section == 0) {
2256                         $sectionresources = $addresource->add(get_string('course'), $sectionurl, self::TYPE_SETTING);
2257                         $sectionactivities = $addactivity->add(get_string('course'), $sectionurl, self::TYPE_SETTING);
2258                     } else {
2259                         $sectionresources = $addresource->add($formatstring.' '.$section->section, $sectionurl, self::TYPE_SETTING);
2260                         $sectionactivities = $addactivity->add($formatstring.' '.$section->section, $sectionurl, self::TYPE_SETTING);
2261                     }
2262                     foreach ($resources as $value=>$resource) {
2263                         $url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'sesskey'=>sesskey(), 'section'=>$section->section));
2264                         $pos = strpos($value, '&type=');
2265                         if ($pos!==false) {
2266                             $url->param('add', $textlib->substr($value, 0,$pos));
2267                             $url->param('type', $textlib->substr($value, $pos+6));
2268                         } else {
2269                             $url->param('add', $value);
2270                         }
2271                         $sectionresources->add($resource, $url, self::TYPE_SETTING);
2272                     }
2273                     $subbranch = false;
2274                     foreach ($activities as $activityname=>$activity) {
2275                         if ($activity==='--') {
2276                             $subbranch = false;
2277                             continue;
2278                         }
2279                         if (strpos($activity, '--')===0) {
2280                             $subbranch = $sectionresources->add(trim($activity, '-'));
2281                             continue;
2282                         }
2283                         $url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'sesskey'=>sesskey(), 'section'=>$section->section));
2284                         $pos = strpos($activityname, '&type=');
2285                         if ($pos!==false) {
2286                             $url->param('add', $textlib->substr($activityname, 0,$pos));
2287                             $url->param('type', $textlib->substr($activityname, $pos+6));
2288                         } else {
2289                             $url->param('add', $activityname);
2290                         }
2291                         if ($subbranch !== false) {
2292                             $subbranch->add($activity, $url, self::TYPE_SETTING);
2293                         } else {
2294                             $sectionresources->add($activity, $url, self::TYPE_SETTING);
2295                         }
2296                     }
2297                 }
2298             }
2300             // Add the course settings link
2301             $url = new moodle_url('/course/edit.php', array('id'=>$course->id));
2302             $coursenode->add(get_string('settings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
2304             // Add the course completion settings link
2305             if ($CFG->enablecompletion && $course->enablecompletion) {
2306                 $url = new moodle_url('/course/completion.php', array('id'=>$course->id));
2307                 $coursenode->add(get_string('completion', 'completion'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
2308             }
2309         }
2311         if (has_capability('moodle/role:assign', $coursecontext)) {
2312             // Add assign or override roles if allowed
2313             $url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$coursecontext->id));
2314             $coursenode->add(get_string('assignroles', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2315             // Override roles
2316             if (has_capability('moodle/role:review', $coursecontext) or count(get_overridable_roles($coursecontext))>0) {
2317                 $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$coursecontext->id));
2318                 $coursenode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2319             }
2320             // Check role permissions
2321             if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
2322                 $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$coursecontext->id));
2323                 $coursenode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2324             }
2325             // Manage filters
2326             if (has_capability('moodle/filter:manage', $coursecontext) && count(filter_get_available_in_context($coursecontext))>0) {
2327                 $url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id));
2328                 $coursenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/filter', ''));
2329             }
2330         }
2332         // Add view grade report is permitted
2333         $reportavailable = false;
2334         if (has_capability('moodle/grade:viewall', $coursecontext)) {
2335             $reportavailable = true;
2336         } else if (!empty($course->showgrades)) {
2337             $reports = get_plugin_list('gradereport');
2338             if (is_array($reports) && count($reports)>0) {     // Get all installed reports
2339                 arsort($reports); // user is last, we want to test it first
2340                 foreach ($reports as $plugin => $plugindir) {
2341                     if (has_capability('gradereport/'.$plugin.':view', $coursecontext)) {
2342                         //stop when the first visible plugin is found
2343                         $reportavailable = true;
2344                         break;
2345                     }
2346                 }
2347             }
2348         }
2349         if ($reportavailable) {
2350             $url = new moodle_url('/grade/report/index.php', array('id'=>$course->id));
2351             $gradenode = $coursenode->add(get_string('grades'), $url, self::TYPE_SETTING, null, 'grades', new pix_icon('i/grades', ''));
2352         }
2354         //  Add outcome if permitted
2355         if (!empty($CFG->enableoutcomes) && has_capability('moodle/course:update', $coursecontext)) {
2356             $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$course->id));
2357             $coursenode->add(get_string('outcomes', 'grades'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/outcomes', ''));
2358         }
2360         // Add meta course links
2361         if ($course->metacourse) {
2362             if (has_capability('moodle/course:managemetacourse', $coursecontext)) {
2363                 $url = new moodle_url('/course/importstudents.php', array('id'=>$course->id));
2364                 $coursenode->add(get_string('childcourses'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/course', ''));
2365             } else if (has_capability('moodle/role:assign', $coursecontext)) {
2366                 $roleassign = $coursenode->add(get_string('childcourses'), null,  self::TYPE_SETTING, null, null, new pix_icon('i/course', ''));
2367                 $roleassign->hidden = true;
2368             }
2369         }
2371         // Manage groups in this course
2372         if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
2373             $url = new moodle_url('/group/index.php', array('id'=>$course->id));
2374             $coursenode->add(get_string('groups'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/group', ''));
2375         }
2377         // Backup this course
2378         if (has_capability('moodle/backup:backupcourse', $coursecontext)) {
2379             $url = new moodle_url('/backup/backup.php', array('id'=>$course->id));
2380             $coursenode->add(get_string('backup'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/backup', ''));
2381         }
2383         // Restore to this course
2384         if (has_capability('moodle/restore:restorecourse', $coursecontext)) {
2385             $url = new moodle_url('/files/index.php', array('id'=>$course->id, 'wdir'=>'/backupdata'));
2386             $url = null; // Disabled until restore is implemented. MDL-21432
2387             $coursenode->add(get_string('restore'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
2388         }
2390         // Import data from other courses
2391         if (has_capability('moodle/restore:restoretargetimport', $coursecontext)) {
2392             $url = new moodle_url('/course/import.php', array('id'=>$course->id));
2393             $url = null; // Disabled until restore is implemented. MDL-21432
2394             $coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
2395         }
2397         // Publish course on a hub
2398         if (has_capability('moodle/course:publish', $coursecontext)) {
2399             $url = new moodle_url('/course/publish/index.php', array('id'=>$course->id));
2400             $coursenode->add(get_string('publish'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/publish', ''));
2401         }
2403         // Reset this course
2404         if (has_capability('moodle/course:reset', $coursecontext)) {
2405             $url = new moodle_url('/course/reset.php', array('id'=>$course->id));
2406             $coursenode->add(get_string('reset'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/return', ''));
2407         }
2409         // Manage questions
2410         $questioncaps = array('moodle/question:add',
2411                               'moodle/question:editmine',
2412                               'moodle/question:editall',
2413                               'moodle/question:viewmine',
2414                               'moodle/question:viewall',
2415                               'moodle/question:movemine',
2416                               'moodle/question:moveall');
2417         if (has_any_capability($questioncaps, $this->context)) {
2418             $questionlink = $CFG->wwwroot.'/question/edit.php';
2419         } else if (has_capability('moodle/question:managecategory', $this->context)) {
2420             $questionlink = $CFG->wwwroot.'/question/category.php';
2421         }
2422         if (isset($questionlink)) {
2423             $url = new moodle_url($questionlink, array('courseid'=>$course->id));
2424             $coursenode->add(get_string('questions','quiz'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/questions', ''));
2425         }
2427         // Repository Instances
2428         require_once($CFG->dirroot.'/repository/lib.php');
2429         $editabletypes = repository::get_editable_types($this->context);
2430         if (has_capability('moodle/course:update', $this->context) && !empty($editabletypes)) {
2431             $url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$this->context->id));
2432             $coursenode->add(get_string('repositories'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/repository', ''));
2433         }
2435         // Manage files
2436         if (has_capability('moodle/course:managefiles', $this->context)) {
2437             $url = new moodle_url('/files/index.php', array('id'=>$course->id));
2438             $coursenode->add(get_string('files'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/files', ''));
2439         }
2441         // Authorize hooks
2442         if ($course->enrol == 'authorize' || (empty($course->enrol) && $CFG->enrol == 'authorize')) {
2443             require_once($CFG->dirroot.'/enrol/authorize/const.php');
2444             $url = new moodle_url('/enrol/authorize/index.php', array('course'=>$course->id));
2445             $coursenode->add(get_string('payments'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/payment', ''));
2446             if (has_capability('enrol/authorize:managepayments', $this->page->context)) {
2447                 $cnt = $DB->count_records('enrol_authorize', array('status'=>AN_STATUS_AUTH, 'courseid'=>$course->id));
2448                 if ($cnt) {
2449                     $url = new moodle_url('/enrol/authorize/index.php', array('course'=>$course->id,'status'=>AN_STATUS_AUTH));
2450                     $coursenode->add(get_string('paymentpending', 'moodle', $cnt), $url, self::TYPE_SETTING, null, null, new pix_icon('i/payment', ''));
2451                 }
2452             }
2453         }
2455         // Unenrol link
2456         if (empty($course->metacourse) && ($course->id!==SITEID)) {
2457             if (is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id))) {
2458                 if (has_capability('moodle/role:unassignself', $this->page->context, NULL, false) and get_user_roles($this->page->context, $USER->id, false)) {  // Have some role
2459                     $this->content->items[]='<a href="'.$CFG->wwwroot.'/course/unenrol.php?id='.$course->id.'">'.get_string('unenrolme', '', format_string($course->shortname)).'</a>';
2460                     $this->content->icons[]='<img src="'.$OUTPUT->pix_url('i/user') . '" class="icon" alt="" />';
2461                 }
2463             } else if (is_viewing(get_context_instance(CONTEXT_COURSE, $course->id))) {
2464                 // inspector, manager, etc. - do not show anything
2465             } else {
2466                 // access because otherwise they would not get into this course at all
2467                 $this->content->items[]='<a href="'.$CFG->wwwroot.'/course/enrol.php?id='.$course->id.'">'.get_string('enrolme', '', format_string($course->shortname)).'</a>';
2468                 $this->content->icons[]='<img src="'.$OUTPUT->pix_url('i/user') . '" class="icon" alt="" />';
2469             }
2470         }
2472         // Switch roles
2473         $roles = array();
2474         $assumedrole = $this->in_alternative_role();
2475         if ($assumedrole!==false) {
2476             $roles[0] = get_string('switchrolereturn');
2477         }
2478         if (has_capability('moodle/role:switchroles', $this->context)) {
2479             $availableroles = get_switchable_roles($this->context);
2480             if (is_array($availableroles)) {
2481                 foreach ($availableroles as $key=>$role) {
2482                     if ($key == $CFG->guestroleid || $assumedrole===(int)$key) {
2483                         continue;
2484                     }
2485                     $roles[$key] = $role;
2486                 }
2487             }
2488         }
2489         if (is_array($roles) && count($roles)>0) {
2490             $switchroles = $this->add(get_string('switchroleto'));
2491             if ((count($roles)==1 && array_key_exists(0, $roles))|| $assumedrole!==false) {
2492                 $switchroles->force_open();
2493             }
2494             $returnurl = $this->page->url;
2495             $returnurl->param('sesskey', sesskey());
2496             $SESSION->returnurl = serialize($returnurl);
2497             foreach ($roles as $key=>$name) {
2498                 $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>$key, 'returnurl'=>'1'));
2499                 $switchroles->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/roles', ''));
2500             }
2501         }
2502         // Return we are done
2503         return $coursenode;
2504     }
2506     /**
2507      * This function calls the module function to inject module settings into the
2508      * settings navigation tree.
2509      *
2510      * This only gets called if there is a corrosponding function in the modules
2511      * lib file.
2512      *
2513      * For examples mod/forum/lib.php ::: forum_extend_settings_navigation()
2514      *
2515      * @return navigation_node|false
2516      */
2517     protected function load_module_settings() {
2518         global $CFG;
2520         if (!$this->page->cm && $this->context->contextlevel == CONTEXT_MODULE && $this->context->instanceid) {
2521             $cm = get_coursemodule_from_id(false, $this->context->instanceid, 0, false, MUST_EXIST);
2522             $this->page->set_cm($cm, $this->page->course);
2523         }
2525         $modulenode = $this->add(get_string($this->page->activityname.'administration', $this->page->activityname));
2526         $modulenode->force_open();
2528         // Settings for the module
2529         if (has_capability('moodle/course:manageactivities', $this->page->cm->context)) {
2530             $url = new moodle_url('/course/modedit.php', array('update' => $this->page->cm->id, 'return' => true, 'sesskey' => sesskey()));
2531             $modulenode->add(get_string('settings'), $url, navigation_node::TYPE_SETTING);
2532         }
2533         // Assign local roles
2534         if (count(get_assignable_roles($this->page->cm->context))>0) {
2535             $url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->page->cm->context->id));
2536             $modulenode->add(get_string('localroles', 'role'), $url, self::TYPE_SETTING);
2537         }
2538         // Override roles
2539         if (has_capability('moodle/role:review', $this->page->cm->context) or count(get_overridable_roles($this->page->cm->context))>0) {
2540             $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->page->cm->context->id));
2541             $modulenode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
2542         }
2543         // Check role permissions
2544         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->page->cm->context)) {
2545             $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->page->cm->context->id));
2546             $modulenode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
2547         }
2548         // Manage filters
2549         if (has_capability('moodle/filter:manage', $this->page->cm->context) && count(filter_get_available_in_context($this->page->cm->context))>0) {
2550             $url = new moodle_url('/filter/manage.php', array('contextid'=>$this->page->cm->context->id));
2551             $modulenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING);
2552         }
2554         $file = $CFG->dirroot.'/mod/'.$this->page->activityname.'/lib.php';
2555         $function = $this->page->activityname.'_extend_settings_navigation';
2557         if (file_exists($file)) {
2558             require_once($file);
2559         }
2560         if (!function_exists($function)) {
2561             return $modulenode;
2562         }
2564         $function($this, $modulenode);
2566         // Remove the module node if there are no children
2567         if (empty($modulenode->children)) {
2568             $modulenode->remove();
2569         }
2571         return $modulenode;
2572     }
2574     /**
2575      * Loads the user settings block of the settings nav
2576      *
2577      * This function is simply works out the userid and whether we need to load
2578      * just the current users profile settings, or the current user and the user the
2579      * current user is viewing.
2580      *
2581      * This function has some very ugly code to work out the user, if anyone has
2582      * any bright ideas please feel free to intervene.
2583      *
2584      * @param int $courseid The course id of the current course
2585      * @return navigation_node|false
2586      */
2587     protected function load_user_settings($courseid=SITEID) {
2588         global $USER, $FULLME, $CFG;
2590         if (isguestuser() || !isloggedin()) {
2591             return false;
2592         }
2594         // This is terribly ugly code, but I couldn't see a better way around it
2595         // we need to pick up the user id, it can be the current user or someone else
2596         // and the key depends on the current location
2597         // Default to look at id
2598         $userkey='id';
2599         if (strpos($FULLME,'/blog/') || strpos($FULLME, $CFG->admin.'/roles/')) {
2600             // And blog and roles just do thier own thing using `userid`
2601             $userkey = 'userid';
2602         } else if ($this->context->contextlevel >= CONTEXT_COURSECAT && strpos($FULLME, '/message/')===false && strpos($FULLME, '/mod/forum/user')===false && strpos($FULLME, '/user/editadvanced')===false) {
2603             // If we have a course context and we are not in message or forum
2604             // Message and forum both pick the user up from `id`
2605             $userkey = 'user';
2606         }
2608         $userid = optional_param($userkey, $USER->id, PARAM_INT);
2609         if ($userid!=$USER->id) {
2610             $usernode = $this->generate_user_settings($courseid, $userid, 'userviewingsettings');
2611             $this->generate_user_settings($courseid, $USER->id);
2612         } else {
2613             $usernode = $this->generate_user_settings($courseid, $USER->id);
2614         }
2615         return $usernode;
2616     }
2618     /**
2619      * This function gets called by {@link load_user_settings()} and actually works out
2620      * what can be shown/done
2621      *
2622      * @param int $courseid The current course' id
2623      * @param int $userid The user id to load for
2624      * @param string $gstitle The string to pass to get_string for the branch title
2625      * @return navigation_node|false
2626      */
2627     protected function generate_user_settings($courseid, $userid, $gstitle='usercurrentsettings') {
2628         global $DB, $CFG, $USER, $SITE;
2630         if ($courseid != SITEID) {
2631             if (!empty($this->page->course->id) && $this->page->course->id == $courseid) {
2632                 $course = $this->page->course;
2633             } else {
2634                 $course = $DB->get_record("course", array("id"=>$courseid), '*', MUST_EXIST);
2635             }
2636         } else {
2637             $course = $SITE;
2638         }
2640         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);   // Course context
2641         $systemcontext   = get_system_context();
2642         $currentuser = ($USER->id == $userid);
2644         if ($currentuser) {
2645             $user = $USER;
2646             $usercontext = get_context_instance(CONTEXT_USER, $user->id);       // User context
2647         } else {
2648             if (!$user = $DB->get_record('user', array('id'=>$userid))) {
2649                 return false;
2650             }
2651             // Check that the user can view the profile
2652             $usercontext = get_context_instance(CONTEXT_USER, $user->id);       // User context
2653             if ($course->id==SITEID) {
2654                 if ($CFG->forceloginforprofiles && !!has_coursemanager_role($user->id) && !has_capability('moodle/user:viewdetails', $usercontext)) {  // Reduce possibility of "browsing" userbase at site level
2655                     // Teachers can browse and be browsed at site level. If not forceloginforprofiles, allow access (bug #4366)
2656                     return false;
2657                 }
2658             } else {
2659                 if ((!has_capability('moodle/user:viewdetails', $coursecontext) && !has_capability('moodle/user:viewdetails', $usercontext)) || !has_capability('moodle/course:participate', $coursecontext, $user->id, false)) {
2660                     return false;
2661                 }
2662                 if (groups_get_course_groupmode($course) == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $coursecontext)) {
2663                     // If groups are in use, make sure we can see that group
2664                     return false;
2665                 }
2666             }
2667         }
2669         $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $this->page->context));
2671         // Add a user setting branch
2672         $usersetting = $this->add(get_string($gstitle, 'moodle', $fullname));
2673         $usersetting->id = 'usersettings';
2675         // Check if the user has been deleted
2676         if ($user->deleted) {
2677             if (!has_capability('moodle/user:update', $coursecontext)) {
2678                 // We can't edit the user so just show the user deleted message
2679                 $usersetting->add(get_string('userdeleted'), null, self::TYPE_SETTING);
2680             } else {
2681                 // We can edit the user so show the user deleted message and link it to the profile
2682                 $profileurl = new moodle_url('/user/view.php', array('id'=>$user->id, 'course'=>$course->id));
2683                 $usersetting->add(get_string('userdeleted'), $profileurl, self::TYPE_SETTING);
2684             }
2685             return true;
2686         }
2688         // Add the profile edit link
2689         if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
2690             if (($currentuser || !is_primary_admin($user->id)) && has_capability('moodle/user:update', $systemcontext)) {
2691                 $url = new moodle_url('/user/editadvanced.php', array('id'=>$user->id, 'course'=>$course->id));
2692                 $usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
2693             } else if ((has_capability('moodle/user:editprofile', $usercontext) && !is_primary_admin($user->id)) || ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext))) {
2694                 $url = new moodle_url('/user/edit.php', array('id'=>$user->id, 'course'=>$course->id));
2695                 $usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
2696             }
2697         }
2699         // Change password link
2700         if (!empty($user->auth)) {
2701             $userauth = get_auth_plugin($user->auth);
2702             if ($currentuser && !session_is_loggedinas() && $userauth->can_change_password() && !isguestuser() && has_capability('moodle/user:changeownpassword', $systemcontext)) {
2703                 $passwordchangeurl = $userauth->change_password_url();
2704                 if (!$passwordchangeurl) {
2705                     if (empty($CFG->loginhttps)) {
2706                         $wwwroot = $CFG->wwwroot;
2707                     } else {
2708                         $wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
2709                     }
2710                     $passwordchangeurl = new moodle_url('/login/change_password.php');
2711                 } else {
2712                     $urlbits = explode($passwordchangeurl. '?', 1);
2713                     $passwordchangeurl = new moodle_url($urlbits[0]);
2714                     if (count($urlbits)==2 && preg_match_all('#\&([^\=]*?)\=([^\&]*)#si', '&'.$urlbits[1], $matches)) {
2715                         foreach ($matches as $pair) {
2716                             $fullmeurl->param($pair[1],$pair[2]);
2717                         }
2718                     }
2719                 }
2720                 $passwordchangeurl->param('id', $course->id);
2721                 $usersetting->add(get_string("changepassword"), $passwordchangeurl, self::TYPE_SETTING);
2722             }
2723         }
2725         // View the roles settings
2726         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:manage'), $usercontext)) {
2727             $roles = $usersetting->add(get_string('roles'), null, self::TYPE_SETTING);
2729             $url = new moodle_url('/admin/roles/usersroles.php', array('userid'=>$user->id, 'courseid'=>$course->id));
2730             $roles->add(get_string('thisusersroles', 'role'), $url, self::TYPE_SETTING);
2732             $assignableroles = get_assignable_roles($usercontext, ROLENAME_BOTH);
2734             if (!empty($assignableroles)) {
2735                 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
2736                 $roles->add(get_string('assignrolesrelativetothisuser', 'role'), $url, self::TYPE_SETTING);
2737             }
2739             if (has_capability('moodle/role:review', $usercontext) || count(get_overridable_roles($usercontext, ROLENAME_BOTH))>0) {
2740                 $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
2741                 $roles->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
2742             }
2744             $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
2745             $roles->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
2746         }
2748         // Portfolio
2749         if ($currentuser && !empty($CFG->enableportfolios) && has_capability('moodle/portfolio:export', $systemcontext)) {
2750             require_once($CFG->libdir . '/portfoliolib.php');
2751             if (portfolio_instances(true, false)) {
2752                 $portfolio = $usersetting->add(get_string('portfolios', 'portfolio'), null, self::TYPE_SETTING);
2753                 $portfolio->add(get_string('configure', 'portfolio'), new moodle_url('/user/portfolio.php'), self::TYPE_SETTING);
2754                 $portfolio->add(get_string('logs', 'portfolio'), new moodle_url('/user/portfoliologs.php'), self::TYPE_SETTING);
2755             }
2756         }
2758         // Security keys
2759         if ($currentuser && !is_siteadmin($USER->id) && !empty($CFG->enablewebservices) && has_capability('moodle/webservice:createtoken', $systemcontext)) {
2760             $url = new moodle_url('/user/managetoken.php', array('sesskey'=>sesskey()));
2761             $usersetting->add(get_string('securitykeys', 'webservice'), $url, self::TYPE_SETTING);
2762         }
2764         // Repository
2765         if (!$currentuser) {
2766             require_once($CFG->dirroot . '/repository/lib.php');
2767             $editabletypes = repository::get_editable_types($usercontext);
2768             if ($usercontext->contextlevel == CONTEXT_USER && !empty($editabletypes)) {
2769                 $url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$usercontext->id));
2770                 $usersetting->add(get_string('repositories', 'repository'), $url, self::TYPE_SETTING);
2771             }
2772         }
2774         // Messaging
2775         if (has_capability('moodle/user:editownmessageprofile', $systemcontext)) {
2776             $url = new moodle_url('/message/edit.php', array('id'=>$user->id, 'course'=>$course->id));
2777             $usersetting->add(get_string('editmymessage', 'message'), $url, self::TYPE_SETTING);
2778         }
2780         return $usersetting;
2781     }
2783     /**
2784      * Loads block specific settings in the navigation
2785      *
2786      * @return navigation_node
2787      */
2788     protected function load_block_settings() {
2789         global $CFG;
2791         $blocknode = $this->add(print_context_name($this->context));
2792         $blocknode->force_open();
2794         // Assign local roles
2795         $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->context->id));
2796         $blocknode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING);
2798         // Override roles
2799         if (has_capability('moodle/role:review', $this->context) or  count(get_overridable_roles($this->context))>0) {
2800             $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->context->id));
2801             $blocknode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
2802         }
2803         // Check role permissions
2804         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->context)) {
2805             $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->context->id));
2806             $blocknode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
2807         }
2809         return $blocknode;
2810     }
2812     /**
2813      * Loads category specific settings in the navigation
2814      *
2815      * @return navigation_node
2816      */
2817     protected function load_category_settings() {
2818         global $CFG;
2820         $categorynode = $this->add(print_context_name($this->context));
2821         $categorynode->force_open();
2823         if ($this->page->user_is_editing() && has_capability('moodle/category:manage', $this->context)) {
2824             $categorynode->add(get_string('editcategorythis'), new moodle_url('/course/editcategory.php', array('id' => $this->context->instanceid)));
2825             $categorynode->add(get_string('addsubcategory'), new moodle_url('/course/editcategory.php', array('parent' => $this->context->instanceid)));
2826         }
2828         // Assign local roles
2829         $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->context->id));
2830         $categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING);
2832         // Override roles
2833         if (has_capability('moodle/role:review', $this->context) or count(get_overridable_roles($this->context))>0) {
2834             $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->context->id));
2835             $categorynode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
2836         }
2837         // Check role permissions
2838         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->context)) {
2839             $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->context->id));
2840             $categorynode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
2841         }
2842         // Manage filters
2843         if (has_capability('moodle/filter:manage', $this->context) && count(filter_get_available_in_context($this->context))>0) {
2844             $url = new moodle_url('/filter/manage.php', array('contextid'=>$this->context->id));
2845             $categorynode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING);
2846         }
2848         return $categorynode;
2849     }
2851     /**
2852      * Determine whether the user is assuming another role
2853      *
2854      * This function checks to see if the user is assuming another role by means of
2855      * role switching. In doing this we compare each RSW key (context path) against
2856      * the current context path. This ensures that we can provide the switching
2857      * options against both the course and any page shown under the course.
2858      *
2859      * @return bool|int The role(int) if the user is in another role, false otherwise
2860      */
2861     protected function in_alternative_role() {
2862         global $USER;
2863         if (!empty($USER->access['rsw']) && is_array($USER->access['rsw'])) {
2864             if (!empty($this->page->context) && !empty($USER->access['rsw'][$this->page->context->path])) {
2865                 return $USER->access['rsw'][$this->page->context->path];
2866             }
2867             foreach ($USER->access['rsw'] as $key=>$role) {
2868                 if (strpos($this->context->path,$key)===0) {
2869                     return $role;
2870                 }
2871             }
2872         }
2873         return false;
2874     }
2876     /**
2877      * This function loads all of the front page settings into the settings navigation.
2878      * This function is called when the user is on the front page, or $COURSE==$SITE
2879      * @return navigation_node
2880      */
2881     protected function load_front_page_settings($forceopen = false) {
2882         global $SITE, $CFG;
2884         $course = clone($SITE);
2885         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);   // Course context
2887         $frontpage = $this->add(get_string('frontpagesettings'), null, self::TYPE_SETTING, null, 'frontpage');
2888         if ($forceopen) {
2889             $frontpage->force_open();
2890         }
2891         $frontpage->id = 'frontpagesettings';
2893         if (has_capability('moodle/course:update', $coursecontext)) {
2895             // Add the turn on/off settings
2896             $url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
2897             if ($this->page->user_is_editing()) {
2898                 $url->param('edit', 'off');
2899                 $editstring = get_string('turneditingoff');
2900             } else {
2901                 $url->param('edit', 'on');
2902                 $editstring = get_string('turneditingon');
2903             }
2904             $frontpage->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
2906             // Add the course settings link
2907             $url = new moodle_url('/admin/settings.php', array('section'=>'frontpagesettings'));
2908             $frontpage->add(get_string('settings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
2909         }
2911         //Participants
2912         if (has_capability('moodle/site:viewparticipants', $coursecontext)) {
2913             $url = new moodle_url('/user/index.php', array('contextid'=>$coursecontext->id));
2914             $frontpage->add(get_string('participants'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/users', ''));
2915         }
2917         // Roles
2918         if (has_capability('moodle/role:assign', $coursecontext)) {
2919             // Add assign or override roles if allowed
2920             $url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$coursecontext->id));
2921             $frontpage->add(get_string('assignroles', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2922             // Override roles
2923             if (has_capability('moodle/role:review', $coursecontext) or count(get_overridable_roles($coursecontext))>0) {
2924                 $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$coursecontext->id));
2925                 $frontpage->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2926             }
2927             // Check role permissions
2928             if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
2929                 $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$coursecontext->id));
2930                 $frontpage->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2931             }
2932             // Manage filters
2933             if (has_capability('moodle/filter:manage', $coursecontext) && count(filter_get_available_in_context($coursecontext))>0) {
2934                 $url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id));
2935                 $frontpage->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/filter', ''));
2936             }
2937         }
2939         // Backup this course
2940         if (has_capability('moodle/backup:backupcourse', $coursecontext)) {
2941             $url = new moodle_url('/backup/backup.php', array('id'=>$course->id));
2942             $frontpage->add(get_string('backup'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/backup', ''));
2943         }
2945         // Restore to this course
2946         if (has_capability('moodle/restore:restorecourse', $coursecontext)) {
2947             $url = new moodle_url('/files/index.php', array('id'=>$course->id, 'wdir'=>'/backupdata'));
2948             $frontpage->add(get_string('restore'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
2949         }
2951         // Manage questions
2952         $questioncaps = array('moodle/question:add',
2953                               'moodle/question:editmine',
2954                               'moodle/question:editall',
2955                               'moodle/question:viewmine',
2956                               'moodle/question:viewall',
2957                               'moodle/question:movemine',
2958                               'moodle/question:moveall');
2959         if (has_any_capability($questioncaps, $this->context)) {
2960             $questionlink = $CFG->wwwroot.'/question/edit.php';
2961         } else if (has_capability('moodle/question:managecategory', $this->context)) {
2962             $questionlink = $CFG->wwwroot.'/question/category.php';
2963         }
2964         if (isset($questionlink)) {
2965             $url = new moodle_url($questionlink, array('courseid'=>$course->id));
2966             $frontpage->add(get_string('questions','quiz'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/questions', ''));
2967         }
2969         // Manage files
2970         if (has_capability('moodle/course:managefiles', $this->context)) {
2971             $url = new moodle_url('/files/index.php', array('id'=>$course->id));
2972             $frontpage->add(get_string('files'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/files', ''));
2973         }
2974         return $frontpage;
2975     }
2977     /**
2978      * This function marks the cache as volatile so it is cleared during shutdown
2979      */
2980     public function clear_cache() {
2981         $this->cache->volatile();
2982     }
2985 /**
2986  * Simple class used to output a navigation branch in XML
2987  *
2988  * @package moodlecore
2989  * @subpackage navigation
2990  * @copyright 2009 Sam Hemelryk
2991  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2992  */
2993 class navigation_json {
2994     /** @var array */
2995     protected $nodetype = array('node','branch');
2996     /** @var array */
2997     protected $expandable = array();
2998     /**
2999      * Turns a branch and all of its children into XML
3000      *
3001      * @param navigation_node $branch
3002      * @return string XML string
3003      */
3004     public function convert($branch) {
3005         $xml = $this->convert_child($branch);
3006         return $xml;
3007     }
3008     /**
3009      * Set the expandable items in the array so that we have enough information
3010      * to attach AJAX events
3011      * @param array $expandable
3012      */
3013     public function set_expandable($expandable) {
3014         foreach ($expandable as $node) {
3015             $this->expandable[(string)$node['branchid']] = $node;
3016         }
3017     }
3018     /**
3019      * Recusively converts a child node and its children to XML for output
3020      *
3021      * @param navigation_node $child The child to convert
3022      * @param int $depth Pointlessly used to track the depth of the XML structure
3023      * @return string JSON
3024      */
3025     protected function convert_child($child, $depth=1) {
3026         global $OUTPUT;
3028         if (!$child->display) {
3029             return '';
3030         }
3031         $attributes = array();
3032         $attributes['id'] = $child->id;
3033         $attributes['name'] = $child->text;
3034         $attributes['type'] = $child->type;
3035         $attributes['key'] = $child->key;
3036         $attributes['class'] = $child->get_css_type();
3038         if ($child->icon instanceof pix_icon) {
3039             $attributes['icon'] = array(
3040                 'component' => $child->icon->component,
3041                 'pix' => $child->icon->pix,
3042             );
3043             foreach ($child->icon->attributes as $key=>$value) {
3044                 if ($key == 'class') {
3045                     $attributes['icon']['classes'] = explode(' ', $value);
3046                 } else if (!array_key_exists($key, $attributes['icon'])) {
3047                     $attributes['icon'][$key] = $value;
3048                 }
3050             }
3051         } else if (!empty($child->icon)) {
3052             $attributes['icon'] = (string)$child->icon;
3053         }
3055         if ($child->forcetitle || $child->title !== $child->text) {
3056             $attributes['title'] = htmlentities($child->title);
3057         }
3058         if (array_key_exists((string)$child->key, $this->expandable)) {
3059             $attributes['expandable'] = $child->key;
3060             $child->add_class($this->expandable[$child->key]['id']);
3061         }
3063         if (count($child->classes)>0) {
3064             $attributes['class'] .= ' '.join(' ',$child->classes);
3065         }
3066         if (is_string($child->action)) {
3067             $attributes['link'] = $child->action;
3068         } else if ($child->action instanceof moodle_url) {
3069             $attributes['link'] = $child->action->out();
3070         }
3071         $attributes['hidden'] = ($child->hidden);
3072         $attributes['haschildren'] = ($child->children->count()>0 || $child->type == navigation_node::TYPE_CATEGORY);
3074         if (count($child->children)>0) {
3075             $attributes['children'] = array();
3076             foreach ($child->children as $subchild) {
3077                 $attributes['children'][] = $this->convert_child($subchild, $depth+1);
3078             }
3079         }
3081         if ($depth > 1) {
3082             return $attributes;
3083         } else {
3084             return json_encode($attributes);
3085         }
3086     }
3089 /**
3090  * The cache class used by global navigation and settings navigation to cache bits
3091  * and bobs that are used during their generation.
3092  *
3093  * It is basically an easy access point to session with a bit of smarts to make
3094  * sure that the information that is cached is valid still.
3095  *
3096  * Example use:
3097  * <code php>
3098  * if (!$cache->viewdiscussion()) {
3099  *     // Code to do stuff and produce cachable content
3100  *     $cache->viewdiscussion = has_capability('mod/forum:viewdiscussion', $coursecontext);
3101  * }
3102  * $content = $cache->viewdiscussion;
3103  * </code>
3104  *
3105  * @package moodlecore
3106  * @subpackage navigation
3107  * @copyright 2009 Sam Hemelryk
3108  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3109  */
3110 class navigation_cache {
3111     /** @var int */
3112     protected $creation;
3113     /** @var array */
3114     protected $session;
3115     /** @var string */
3116     protected $area;
3117     /** @var int */
3118     protected $timeout;
3119     /** @var stdClass */
3120     protected $currentcontext;
3121     /** @var int */
3122     const CACHETIME = 0;
3123     /** @var int */
3124     const CACHEUSERID = 1;
3125     /** @var int */
3126     const CACHEVALUE = 2;
3127     /** @var null|array An array of navigation cache areas to expire on shutdown */
3128     public static $volatilecaches;
3130     /**
3131      * Contructor for the cache. Requires two arguments
3132      *
3133      * @param string $area The string to use to segregate this particular cache
3134      *                it can either be unique to start a fresh cache or if you want
3135      *                to share a cache then make it the string used in the original
3136      *                cache
3137      * @param int $timeout The number of seconds to time the information out after
3138      */
3139     public function __construct($area, $timeout=60) {
3140         global $SESSION;
3141         $this->creation = time();
3142         $this->area = $area;
3144         if (!isset($SESSION->navcache)) {
3145             $SESSION->navcache = new stdClass;
3146         }
3148         if (!isset($SESSION->navcache->{$area})) {
3149             $SESSION->navcache->{$area} = array();
3150         }
3151         $this->session = &$SESSION->navcache->{$area};
3152         $this->timeout = time()-$timeout;
3153         if (rand(0,10)===0) {
3154             $this->garbage_collection();
3155         }
3156     }
3158     /**
3159      * Magic Method to retrieve something by simply calling using = cache->key
3160      *
3161      * @param mixed $key The identifier for the information you want out again
3162      * @return void|mixed Either void or what ever was put in
3163      */
3164     public function __get($key) {
3165         if (!$this->cached($key)) {
3166             return;
3167         }
3168         $information = $this->session[$key][self::CACHEVALUE];
3169         return unserialize($information);
3170     }
3172     /**
3173      * Magic method that simply uses {@link set();} to store something in the cache
3174      *
3175      * @param string|int $key
3176      * @param mixed $information
3177      */
3178     public function __set($key, $information) {
3179         $this->set($key, $information);
3180     }
3182     /**
3183      * Sets some information against the cache (session) for later retrieval
3184      *
3185      * @param string|int $key
3186      * @param mixed $information
3187      */
3188     public function set($key, $information) {
3189         global $USER;
3190         $information = serialize($information);
3191         $this->session[$key]= array(self::CACHETIME=>time(), self::CACHEUSERID=>$USER->id, self::CACHEVALUE=>$information);
3192     }
3193     /**
3194      * Check the existence of the identifier in the cache
3195      *
3196      * @param string|int $key
3197      * @return bool
3198      */
3199     public function cached($key) {
3200         global $USER;
3201         if (!array_key_exists($key, $this->session) || !is_array($this->session[$key]) || $this->session[$key][self::CACHEUSERID]!=$USER->id || $this->session[$key][self::CACHETIME] < $this->timeout) {
3202             return false;
3203         }
3204         return true;
3205     }
3206     /**
3207      * Compare something to it's equivilant in the cache
3208      *
3209      * @param string $key
3210      * @param mixed $value
3211      * @param bool $serialise Whether to serialise the value before comparison
3212      *              this should only be set to false if the value is already
3213      *              serialised
3214      * @return bool If the value is the same false if it is not set or doesn't match
3215      */
3216     public function compare($key, $value, $serialise=true) {
3217         if ($this->cached($key)) {
3218             if ($serialise) {
3219                 $value = serialize($value);
3220             }
3221             if ($this->session[$key][self::CACHEVALUE] === $value) {
3222                 return true;
3223             }
3224         }
3225         return false;
3226     }
3227     /**
3228      * Wipes the entire cache, good to force regeneration
3229      */
3230     public function clear() {
3231         $this->session = array();
3232     }
3233     /**
3234      * Checks all cache entries and removes any that have expired, good ole cleanup
3235      */
3236     protected function garbage_collection() {
3237         foreach ($this->session as $key=>$cachedinfo) {
3238             if (is_array($cachedinfo) && $cachedinfo[self::CACHETIME]<$this->timeout) {
3239                 unset($this->session[$key]);
3240             }
3241         }
3242     }
3244     /**
3245      * Marks the cache as being volatile (likely to change)
3246      *
3247      * Any caches marked as volatile will be destroyed at the on shutdown by
3248      * {@link navigation_node::destroy_volatile_caches()} which is registered
3249      * as a shutdown function if any caches are marked as volatile.
3250      *
3251      * @param bool $setting True to destroy the cache false not too
3252      */
3253     public function volatile($setting = true) {
3254         if (self::$volatilecaches===null) {
3255             self::$volatilecaches = array();
3256             register_shutdown_function(array('navigation_cache','destroy_volatile_caches'));
3257         }
3259         if ($setting) {
3260             self::$volatilecaches[$this->area] = $this->area;
3261         } else if (array_key_exists($this->area, self::$volatilecaches)) {
3262             unset(self::$volatilecaches[$this->area]);
3263         }
3264     }
3266     /**
3267      * Destroys all caches marked as volatile
3268      *
3269      * This function is static and works in conjunction with the static volatilecaches
3270      * property of navigation cache.
3271      * Because this function is static it manually resets the cached areas back to an
3272      * empty array.
3273      */
3274     public static function destroy_volatile_caches() {
3275         global $SESSION;
3276         if (is_array(self::$volatilecaches) && count(self::$volatilecaches)>0) {
3277             foreach (self::$volatilecaches as $area) {
3278                 $SESSION->navcache->{$area} = array();
3279             }
3280         }
3281     }