MDL-21432 restore & import - disabled until ready
[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->fullname, $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         $this->children = new get_class($children);
2018         $node = $this->add($text, $url, $type, $shorttext, $key, $icon);
2019         foreach ($children as $child) {
2020             $this->children->add($child);
2021         }
2022         return $node;
2023     }
2024     /**
2025      * Load the site administration tree
2026      *
2027      * This function loads the site administration tree by using the lib/adminlib library functions
2028      *
2029      * @param navigation_node $referencebranch A reference to a branch in the settings
2030      *      navigation tree
2031      * @param part_of_admin_tree $adminbranch The branch to add, if null generate the admin
2032      *      tree and start at the beginning
2033      * @return mixed A key to access the admin tree by
2034      */
2035     protected function load_administration_settings(navigation_node $referencebranch=null, part_of_admin_tree $adminbranch=null) {
2036         global $CFG;
2038         // Check if we are just starting to generate this navigation.
2039         if ($referencebranch === null) {
2041             // Require the admin lib then get an admin structure
2042             if (!function_exists('admin_get_root')) {
2043                 require_once($CFG->dirroot.'/lib/adminlib.php');
2044             }
2045             $adminroot = admin_get_root(false, false);
2046             // This is the active section identifier
2047             $this->adminsection = $this->page->url->param('section');
2049             // Disable the navigation from automatically finding the active node
2050             navigation_node::$autofindactive = false;
2051             $referencebranch = $this->add(get_string('administrationsite'), null, self::TYPE_SETTING, null, 'root');
2052             foreach ($adminroot->children as $adminbranch) {
2053                 $this->load_administration_settings($referencebranch, $adminbranch);
2054             }
2055             navigation_node::$autofindactive = true;
2057             // Use the admin structure to locate the active page
2058             if (!$this->contains_active_node() && $current = $adminroot->locate($this->adminsection, true)) {
2059                 $currentnode = $this;
2060                 while (($pathkey = array_pop($current->path))!==null && $currentnode) {
2061                     $currentnode = $currentnode->get($pathkey);
2062                 }
2063                 if ($currentnode) {
2064                     $currentnode->make_active();
2065                 }
2066             }
2067             return $referencebranch;
2068         } else if ($adminbranch->check_access()) {
2069             // We have a reference branch that we can access and is not hidden `hurrah`
2070             // Now we need to display it and any children it may have
2071             $url = null;
2072             $icon = null;
2073             if ($adminbranch instanceof admin_settingpage) {
2074                 $url = new moodle_url('/admin/settings.php', array('section'=>$adminbranch->name));
2075             } else if ($adminbranch instanceof admin_externalpage) {
2076                 $url = $adminbranch->url;
2077             }
2079             // Add the branch
2080             $reference = $referencebranch->add($adminbranch->visiblename, $url, self::TYPE_SETTING, null, $adminbranch->name, $icon);
2082             if ($adminbranch->is_hidden()) {
2083                 if (($adminbranch instanceof admin_externalpage || $adminbranch instanceof admin_settingpage) && $adminbranch->name == $this->adminsection) {
2084                     $reference->add_class('hidden');
2085                 } else {
2086                     $reference->display = false;
2087                 }
2088             }
2090             // Check if we are generating the admin notifications and whether notificiations exist
2091             if ($adminbranch->name === 'adminnotifications' && admin_critical_warnings_present()) {
2092                 $reference->add_class('criticalnotification');
2093             }
2094             // Check if this branch has children
2095             if ($reference && isset($adminbranch->children) && is_array($adminbranch->children) && count($adminbranch->children)>0) {
2096                 foreach ($adminbranch->children as $branch) {
2097                     // Generate the child branches as well now using this branch as the reference
2098                     $this->load_administration_settings($reference, $branch);
2099                 }
2100             } else {
2101                 $reference->icon = new pix_icon('i/settings', '');
2102             }
2103         }
2104     }
2106     /**
2107      * Gets a navigation node given an array of keys that represent the path to
2108      * the desired node.
2109      *
2110      * @param array $path
2111      * @return navigation_node|false
2112      */
2113     protected function get_by_path(array $path) {
2114         $node = $this->get(array_shift($path));
2115         foreach ($path as $key) {
2116             $node->get($key);
2117         }
2118         return $node;
2119     }
2121     /**
2122      * Generate the list of modules for the given course.
2123      *
2124      * The array of resources and activities that can be added to a course is then
2125      * stored in the cache so that we can access it for anywhere.
2126      * It saves us generating it all the time
2127      *
2128      * <code php>
2129      * // To get resources:
2130      * $this->cache->{'course'.$courseid.'resources'}
2131      * // To get activities:
2132      * $this->cache->{'course'.$courseid.'activities'}
2133      * </code>
2134      *
2135      * @param stdClass $course The course to get modules for
2136      */
2137     protected function get_course_modules($course) {
2138         global $CFG;
2139         $mods = $modnames = $modnamesplural = $modnamesused = array();
2140         // This function is included when we include course/lib.php at the top
2141         // of this file
2142         get_all_mods($course->id, $mods, $modnames, $modnamesplural, $modnamesused);
2143         $resources = array();
2144         $activities = array();
2145         foreach($modnames as $modname=>$modnamestr) {
2146             if (!course_allowed_module($course, $modname)) {
2147                 continue;
2148             }
2150             $libfile = "$CFG->dirroot/mod/$modname/lib.php";
2151             if (!file_exists($libfile)) {
2152                 continue;
2153             }
2154             include_once($libfile);
2155             $gettypesfunc =  $modname.'_get_types';
2156             if (function_exists($gettypesfunc)) {
2157                 $types = $gettypesfunc();
2158                 foreach($types as $type) {
2159                     if (!isset($type->modclass) || !isset($type->typestr)) {
2160                         debugging('Incorrect activity type in '.$modname);
2161                         continue;
2162                     }
2163                     if ($type->modclass == MOD_CLASS_RESOURCE) {
2164                         $resources[html_entity_decode($type->type)] = $type->typestr;
2165                     } else {
2166                         $activities[html_entity_decode($type->type)] = $type->typestr;
2167                     }
2168                 }
2169             } else {
2170                 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
2171                 if ($archetype == MOD_ARCHETYPE_RESOURCE) {
2172                     $resources[$modname] = $modnamestr;
2173                 } else {
2174                     // all other archetypes are considered activity
2175                     $activities[$modname] = $modnamestr;
2176                 }
2177             }
2178         }
2179         $this->cache->{'course'.$course->id.'resources'} = $resources;
2180         $this->cache->{'course'.$course->id.'activities'} = $activities;
2181     }
2183     /**
2184      * This function loads the course settings that are available for the user
2185      *
2186      * @param bool $forceopen If set to true the course node will be forced open
2187      * @return navigation_node|false
2188      */
2189     protected function load_course_settings($forceopen = false) {
2190         global $CFG, $USER, $SESSION, $OUTPUT;
2192         $course = $this->page->course;
2193         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
2194         if (!$this->cache->cached('canviewcourse'.$course->id)) {
2195             $this->cache->{'canviewcourse'.$course->id} = has_capability('moodle/course:participate', $coursecontext);
2196         }
2197         if ($course->id === SITEID || !$this->cache->{'canviewcourse'.$course->id}) {
2198             return false;
2199         }
2201         $coursenode = $this->add(get_string('courseadministration'), null, self::TYPE_COURSE, null, 'courseadmin');
2202         if ($forceopen) {
2203             $coursenode->force_open();
2204         }
2206         if (has_capability('moodle/course:update', $coursecontext)) {
2207             // Add the turn on/off settings
2208             $url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
2209             if ($this->page->user_is_editing()) {
2210                 $url->param('edit', 'off');
2211                 $editstring = get_string('turneditingoff');
2212             } else {
2213                 $url->param('edit', 'on');
2214                 $editstring = get_string('turneditingon');
2215             }
2216             $coursenode->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
2218             if ($this->page->user_is_editing()) {
2219                 // Add `add` resources|activities branches
2220                 $structurefile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
2221                 if (file_exists($structurefile)) {
2222                     require_once($structurefile);
2223                     $formatstring = call_user_func('callback_'.$course->format.'_definition');
2224                     $formatidentifier = optional_param(call_user_func('callback_'.$course->format.'_request_key'), 0, PARAM_INT);
2225                 } else {
2226                     $formatstring = get_string('topic');
2227                     $formatidentifier = optional_param('topic', 0, PARAM_INT);
2228                 }
2229                 if (!$this->cache->cached('coursesections'.$course->id)) {
2230                     $this->cache->{'coursesections'.$course->id} = get_all_sections($course->id);
2231                 }
2232                 $sections = $this->cache->{'coursesections'.$course->id};
2234                 $addresource = $this->add(get_string('addresource'));
2235                 $addactivity = $this->add(get_string('addactivity'));
2236                 if ($formatidentifier!==0) {
2237                     $addresource->force_open();
2238                     $addactivity->force_open();
2239                 }
2241                 if (!$this->cache->cached('course'.$course->id.'resources')) {
2242                     $this->get_course_modules($course);
2243                 }
2244                 $resources = $this->cache->{'course'.$course->id.'resources'};
2245                 $activities = $this->cache->{'course'.$course->id.'activities'};
2247                 $textlib = textlib_get_instance();
2249                 foreach ($sections as $section) {
2250                     if ($formatidentifier !== 0 && $section->section != $formatidentifier) {
2251                         continue;
2252                     }
2253                     $sectionurl = new moodle_url('/course/view.php', array('id'=>$course->id, $formatstring=>$section->section));
2254                     if ($section->section == 0) {
2255                         $sectionresources = $addresource->add(get_string('course'), $sectionurl, self::TYPE_SETTING);
2256                         $sectionactivities = $addactivity->add(get_string('course'), $sectionurl, self::TYPE_SETTING);
2257                     } else {
2258                         $sectionresources = $addresource->add($formatstring.' '.$section->section, $sectionurl, self::TYPE_SETTING);
2259                         $sectionactivities = $addactivity->add($formatstring.' '.$section->section, $sectionurl, self::TYPE_SETTING);
2260                     }
2261                     foreach ($resources as $value=>$resource) {
2262                         $url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'sesskey'=>sesskey(), 'section'=>$section->section));
2263                         $pos = strpos($value, '&type=');
2264                         if ($pos!==false) {
2265                             $url->param('add', $textlib->substr($value, 0,$pos));
2266                             $url->param('type', $textlib->substr($value, $pos+6));
2267                         } else {
2268                             $url->param('add', $value);
2269                         }
2270                         $sectionresources->add($resource, $url, self::TYPE_SETTING);
2271                     }
2272                     $subbranch = false;
2273                     foreach ($activities as $activityname=>$activity) {
2274                         if ($activity==='--') {
2275                             $subbranch = false;
2276                             continue;
2277                         }
2278                         if (strpos($activity, '--')===0) {
2279                             $subbranch = $sectionresources->add(trim($activity, '-'));
2280                             continue;
2281                         }
2282                         $url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'sesskey'=>sesskey(), 'section'=>$section->section));
2283                         $pos = strpos($activityname, '&type=');
2284                         if ($pos!==false) {
2285                             $url->param('add', $textlib->substr($activityname, 0,$pos));
2286                             $url->param('type', $textlib->substr($activityname, $pos+6));
2287                         } else {
2288                             $url->param('add', $activityname);
2289                         }
2290                         if ($subbranch !== false) {
2291                             $subbranch->add($activity, $url, self::TYPE_SETTING);
2292                         } else {
2293                             $sectionresources->add($activity, $url, self::TYPE_SETTING);
2294                         }
2295                     }
2296                 }
2297             }
2299             // Add the course settings link
2300             $url = new moodle_url('/course/edit.php', array('id'=>$course->id));
2301             $coursenode->add(get_string('settings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
2303             // Add the course completion settings link
2304             if ($CFG->enablecompletion && $course->enablecompletion) {
2305                 $url = new moodle_url('/course/completion.php', array('id'=>$course->id));
2306                 $coursenode->add(get_string('completion', 'completion'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
2307             }
2308         }
2310         if (has_capability('moodle/role:assign', $coursecontext)) {
2311             // Add assign or override roles if allowed
2312             $url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$coursecontext->id));
2313             $coursenode->add(get_string('assignroles', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2314             // Override roles
2315             if (has_capability('moodle/role:review', $coursecontext) or count(get_overridable_roles($coursecontext))>0) {
2316                 $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$coursecontext->id));
2317                 $coursenode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2318             }
2319             // Check role permissions
2320             if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
2321                 $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$coursecontext->id));
2322                 $coursenode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2323             }
2324             // Manage filters
2325             if (has_capability('moodle/filter:manage', $coursecontext) && count(filter_get_available_in_context($coursecontext))>0) {
2326                 $url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id));
2327                 $coursenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/filter', ''));
2328             }
2329         }
2331         // Add view grade report is permitted
2332         $reportavailable = false;
2333         if (has_capability('moodle/grade:viewall', $coursecontext)) {
2334             $reportavailable = true;
2335         } else if (!empty($course->showgrades)) {
2336             $reports = get_plugin_list('gradereport');
2337             if (is_array($reports) && count($reports)>0) {     // Get all installed reports
2338                 arsort($reports); // user is last, we want to test it first
2339                 foreach ($reports as $plugin => $plugindir) {
2340                     if (has_capability('gradereport/'.$plugin.':view', $coursecontext)) {
2341                         //stop when the first visible plugin is found
2342                         $reportavailable = true;
2343                         break;
2344                     }
2345                 }
2346             }
2347         }
2348         if ($reportavailable) {
2349             $url = new moodle_url('/grade/report/index.php', array('id'=>$course->id));
2350             $coursenode->add(get_string('grades'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/grades', ''));
2351         }
2353         //  Add outcome if permitted
2354         if (!empty($CFG->enableoutcomes) && has_capability('moodle/course:update', $coursecontext)) {
2355             $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$course->id));
2356             $coursenode->add(get_string('outcomes', 'grades'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/outcomes', ''));
2357         }
2359         // Add meta course links
2360         if ($course->metacourse) {
2361             if (has_capability('moodle/course:managemetacourse', $coursecontext)) {
2362                 $url = new moodle_url('/course/importstudents.php', array('id'=>$course->id));
2363                 $coursenode->add(get_string('childcourses'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/course', ''));
2364             } else if (has_capability('moodle/role:assign', $coursecontext)) {
2365                 $roleassign = $coursenode->add(get_string('childcourses'), null,  self::TYPE_SETTING, null, null, new pix_icon('i/course', ''));
2366                 $roleassign->hidden = true;
2367             }
2368         }
2370         // Manage groups in this course
2371         if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
2372             $url = new moodle_url('/group/index.php', array('id'=>$course->id));
2373             $coursenode->add(get_string('groups'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/group', ''));
2374         }
2376         // Backup this course
2377         if (has_capability('moodle/backup:backupcourse', $coursecontext)) {
2378             $url = new moodle_url('/backup/backup.php', array('id'=>$course->id));
2379             $coursenode->add(get_string('backup'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/backup', ''));
2380         }
2382         // Restore to this course
2383         if (has_capability('moodle/restore:restorecourse', $coursecontext)) {
2384             $url = new moodle_url('/files/index.php', array('id'=>$course->id, 'wdir'=>'/backupdata'));
2385             $url = null; // Disabled until restore is implemented. MDL-21432
2386             $coursenode->add(get_string('restore'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
2387         }
2389         // Import data from other courses
2390         if (has_capability('moodle/restore:restoretargetimport', $coursecontext)) {
2391             $url = new moodle_url('/course/import.php', array('id'=>$course->id));
2392             $url = null; // Disabled until restore is implemented. MDL-21432
2393             $coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
2394         }
2396         // Publish course on a hub
2397         if (has_capability('moodle/course:publish', $coursecontext)) {
2398             $url = new moodle_url('/course/publish/index.php', array('id'=>$course->id));
2399             $coursenode->add(get_string('publish'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/publish', ''));
2400         }
2402         // Reset this course
2403         if (has_capability('moodle/course:reset', $coursecontext)) {
2404             $url = new moodle_url('/course/reset.php', array('id'=>$course->id));
2405             $coursenode->add(get_string('reset'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/return', ''));
2406         }
2408         // Manage questions
2409         $questioncaps = array('moodle/question:add',
2410                               'moodle/question:editmine',
2411                               'moodle/question:editall',
2412                               'moodle/question:viewmine',
2413                               'moodle/question:viewall',
2414                               'moodle/question:movemine',
2415                               'moodle/question:moveall');
2416         if (has_any_capability($questioncaps, $this->context)) {
2417             $questionlink = $CFG->wwwroot.'/question/edit.php';
2418         } else if (has_capability('moodle/question:managecategory', $this->context)) {
2419             $questionlink = $CFG->wwwroot.'/question/category.php';
2420         }
2421         if (isset($questionlink)) {
2422             $url = new moodle_url($questionlink, array('courseid'=>$course->id));
2423             $coursenode->add(get_string('questions','quiz'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/questions', ''));
2424         }
2426         // Repository Instances
2427         require_once($CFG->dirroot.'/repository/lib.php');
2428         $editabletypes = repository::get_editable_types($this->context);
2429         if (has_capability('moodle/course:update', $this->context) && !empty($editabletypes)) {
2430             $url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$this->context->id));
2431             $coursenode->add(get_string('repositories'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/repository', ''));
2432         }
2434         // Manage files
2435         if (has_capability('moodle/course:managefiles', $this->context)) {
2436             $url = new moodle_url('/files/index.php', array('id'=>$course->id));
2437             $coursenode->add(get_string('files'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/files', ''));
2438         }
2440         // Authorize hooks
2441         if ($course->enrol == 'authorize' || (empty($course->enrol) && $CFG->enrol == 'authorize')) {
2442             require_once($CFG->dirroot.'/enrol/authorize/const.php');
2443             $url = new moodle_url('/enrol/authorize/index.php', array('course'=>$course->id));
2444             $coursenode->add(get_string('payments'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/payment', ''));
2445             if (has_capability('enrol/authorize:managepayments', $this->page->context)) {
2446                 $cnt = $DB->count_records('enrol_authorize', array('status'=>AN_STATUS_AUTH, 'courseid'=>$course->id));
2447                 if ($cnt) {
2448                     $url = new moodle_url('/enrol/authorize/index.php', array('course'=>$course->id,'status'=>AN_STATUS_AUTH));
2449                     $coursenode->add(get_string('paymentpending', 'moodle', $cnt), $url, self::TYPE_SETTING, null, null, new pix_icon('i/payment', ''));
2450                 }
2451             }
2452         }
2454         // Unenrol link
2455         if (empty($course->metacourse) && ($course->id!==SITEID)) {
2456             if (is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id))) {
2457                 if (has_capability('moodle/role:unassignself', $this->page->context, NULL, false) and get_user_roles($this->page->context, $USER->id, false)) {  // Have some role
2458                     $this->content->items[]='<a href="'.$CFG->wwwroot.'/course/unenrol.php?id='.$course->id.'">'.get_string('unenrolme', '', format_string($course->shortname)).'</a>';
2459                     $this->content->icons[]='<img src="'.$OUTPUT->pix_url('i/user') . '" class="icon" alt="" />';
2460                 }
2462             } else if (is_viewing(get_context_instance(CONTEXT_COURSE, $course->id))) {
2463                 // inspector, manager, etc. - do not show anything
2464             } else {
2465                 // access because otherwise they would not get into this course at all
2466                 $this->content->items[]='<a href="'.$CFG->wwwroot.'/course/enrol.php?id='.$course->id.'">'.get_string('enrolme', '', format_string($course->shortname)).'</a>';
2467                 $this->content->icons[]='<img src="'.$OUTPUT->pix_url('i/user') . '" class="icon" alt="" />';
2468             }
2469         }
2471         // Switch roles
2472         $roles = array();
2473         $assumedrole = $this->in_alternative_role();
2474         if ($assumedrole!==false) {
2475             $roles[0] = get_string('switchrolereturn');
2476         }
2477         if (has_capability('moodle/role:switchroles', $this->context)) {
2478             $availableroles = get_switchable_roles($this->context);
2479             if (is_array($availableroles)) {
2480                 foreach ($availableroles as $key=>$role) {
2481                     if ($key == $CFG->guestroleid || $assumedrole===(int)$key) {
2482                         continue;
2483                     }
2484                     $roles[$key] = $role;
2485                 }
2486             }
2487         }
2488         if (is_array($roles) && count($roles)>0) {
2489             $switchroles = $this->add(get_string('switchroleto'));
2490             if ((count($roles)==1 && array_key_exists(0, $roles))|| $assumedrole!==false) {
2491                 $switchroles->force_open();
2492             }
2493             $returnurl = $this->page->url;
2494             $returnurl->param('sesskey', sesskey());
2495             $SESSION->returnurl = serialize($returnurl);
2496             foreach ($roles as $key=>$name) {
2497                 $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>$key, 'returnurl'=>'1'));
2498                 $switchroles->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/roles', ''));
2499             }
2500         }
2501         // Return we are done
2502         return $coursenode;
2503     }
2505     /**
2506      * This function calls the module function to inject module settings into the
2507      * settings navigation tree.
2508      *
2509      * This only gets called if there is a corrosponding function in the modules
2510      * lib file.
2511      *
2512      * For examples mod/forum/lib.php ::: forum_extend_settings_navigation()
2513      *
2514      * @return navigation_node|false
2515      */
2516     protected function load_module_settings() {
2517         global $CFG;
2519         if (!$this->page->cm && $this->context->contextlevel == CONTEXT_MODULE && $this->context->instanceid) {
2520             $cm = get_coursemodule_from_id(false, $this->context->instanceid, 0, false, MUST_EXIST);
2521             $this->page->set_cm($cm, $this->page->course);
2522         }
2524         $modulenode = $this->add(get_string($this->page->activityname.'administration', $this->page->activityname));
2525         $modulenode->force_open();
2527         // Settings for the module
2528         if (has_capability('moodle/course:manageactivities', $this->page->cm->context)) {
2529             $url = new moodle_url('/course/modedit.php', array('update' => $this->page->cm->id, 'return' => true, 'sesskey' => sesskey()));
2530             $modulenode->add(get_string('settings'), $url, navigation_node::TYPE_SETTING);
2531         }
2532         // Assign local roles
2533         if (count(get_assignable_roles($this->page->cm->context))>0) {
2534             $url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->page->cm->context->id));
2535             $modulenode->add(get_string('localroles', 'role'), $url, self::TYPE_SETTING);
2536         }
2537         // Override roles
2538         if (has_capability('moodle/role:review', $this->page->cm->context) or count(get_overridable_roles($this->page->cm->context))>0) {
2539             $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->page->cm->context->id));
2540             $modulenode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
2541         }
2542         // Check role permissions
2543         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->page->cm->context)) {
2544             $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->page->cm->context->id));
2545             $modulenode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
2546         }
2547         // Manage filters
2548         if (has_capability('moodle/filter:manage', $this->page->cm->context) && count(filter_get_available_in_context($this->page->cm->context))>0) {
2549             $url = new moodle_url('/filter/manage.php', array('contextid'=>$this->page->cm->context->id));
2550             $modulenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING);
2551         }
2553         $file = $CFG->dirroot.'/mod/'.$this->page->activityname.'/lib.php';
2554         $function = $this->page->activityname.'_extend_settings_navigation';
2556         if (file_exists($file)) {
2557             require_once($file);
2558         }
2559         if (!function_exists($function)) {
2560             return $modulenode;
2561         }
2563         $function($this, $modulenode);
2565         // Remove the module node if there are no children
2566         if (empty($modulenode->children)) {
2567             $modulenode->remove();
2568         }
2570         return $modulenode;
2571     }
2573     /**
2574      * Loads the user settings block of the settings nav
2575      *
2576      * This function is simply works out the userid and whether we need to load
2577      * just the current users profile settings, or the current user and the user the
2578      * current user is viewing.
2579      *
2580      * This function has some very ugly code to work out the user, if anyone has
2581      * any bright ideas please feel free to intervene.
2582      *
2583      * @param int $courseid The course id of the current course
2584      * @return navigation_node|false
2585      */
2586     protected function load_user_settings($courseid=SITEID) {
2587         global $USER, $FULLME, $CFG;
2589         if (isguestuser() || !isloggedin()) {
2590             return false;
2591         }
2593         // This is terribly ugly code, but I couldn't see a better way around it
2594         // we need to pick up the user id, it can be the current user or someone else
2595         // and the key depends on the current location
2596         // Default to look at id
2597         $userkey='id';
2598         if (strpos($FULLME,'/blog/') || strpos($FULLME, $CFG->admin.'/roles/')) {
2599             // And blog and roles just do thier own thing using `userid`
2600             $userkey = 'userid';
2601         } else if ($this->context->contextlevel >= CONTEXT_COURSECAT && strpos($FULLME, '/message/')===false && strpos($FULLME, '/mod/forum/user')===false && strpos($FULLME, '/user/editadvanced')===false) {
2602             // If we have a course context and we are not in message or forum
2603             // Message and forum both pick the user up from `id`
2604             $userkey = 'user';
2605         }
2607         $userid = optional_param($userkey, $USER->id, PARAM_INT);
2608         if ($userid!=$USER->id) {
2609             $usernode = $this->generate_user_settings($courseid, $userid, 'userviewingsettings');
2610             $this->generate_user_settings($courseid, $USER->id);
2611         } else {
2612             $usernode = $this->generate_user_settings($courseid, $USER->id);
2613         }
2614         return $usernode;
2615     }
2617     /**
2618      * This function gets called by {@link load_user_settings()} and actually works out
2619      * what can be shown/done
2620      *
2621      * @param int $courseid The current course' id
2622      * @param int $userid The user id to load for
2623      * @param string $gstitle The string to pass to get_string for the branch title
2624      * @return navigation_node|false
2625      */
2626     protected function generate_user_settings($courseid, $userid, $gstitle='usercurrentsettings') {
2627         global $DB, $CFG, $USER, $SITE;
2629         if ($courseid != SITEID) {
2630             if (!empty($this->page->course->id) && $this->page->course->id == $courseid) {
2631                 $course = $this->page->course;
2632             } else {
2633                 $course = $DB->get_record("course", array("id"=>$courseid), '*', MUST_EXIST);
2634             }
2635         } else {
2636             $course = $SITE;
2637         }
2639         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);   // Course context
2640         $systemcontext   = get_system_context();
2641         $currentuser = ($USER->id == $userid);
2643         if ($currentuser) {
2644             $user = $USER;
2645             $usercontext = get_context_instance(CONTEXT_USER, $user->id);       // User context
2646         } else {
2647             if (!$user = $DB->get_record('user', array('id'=>$userid))) {
2648                 return false;
2649             }
2650             // Check that the user can view the profile
2651             $usercontext = get_context_instance(CONTEXT_USER, $user->id);       // User context
2652             if ($course->id==SITEID) {
2653                 if ($CFG->forceloginforprofiles && !!has_coursemanager_role($user->id) && !has_capability('moodle/user:viewdetails', $usercontext)) {  // Reduce possibility of "browsing" userbase at site level
2654                     // Teachers can browse and be browsed at site level. If not forceloginforprofiles, allow access (bug #4366)
2655                     return false;
2656                 }
2657             } else {
2658                 if ((!has_capability('moodle/user:viewdetails', $coursecontext) && !has_capability('moodle/user:viewdetails', $usercontext)) || !has_capability('moodle/course:participate', $coursecontext, $user->id, false)) {
2659                     return false;
2660                 }
2661                 if (groups_get_course_groupmode($course) == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $coursecontext)) {
2662                     // If groups are in use, make sure we can see that group
2663                     return false;
2664                 }
2665             }
2666         }
2668         $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $this->page->context));
2670         // Add a user setting branch
2671         $usersetting = $this->add(get_string($gstitle, 'moodle', $fullname));
2672         $usersetting->id = 'usersettings';
2674         // Check if the user has been deleted
2675         if ($user->deleted) {
2676             if (!has_capability('moodle/user:update', $coursecontext)) {
2677                 // We can't edit the user so just show the user deleted message
2678                 $usersetting->add(get_string('userdeleted'), null, self::TYPE_SETTING);
2679             } else {
2680                 // We can edit the user so show the user deleted message and link it to the profile
2681                 $profileurl = new moodle_url('/user/view.php', array('id'=>$user->id, 'course'=>$course->id));
2682                 $usersetting->add(get_string('userdeleted'), $profileurl, self::TYPE_SETTING);
2683             }
2684             return true;
2685         }
2687         // Add the profile edit link
2688         if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
2689             if (($currentuser || !is_primary_admin($user->id)) && has_capability('moodle/user:update', $systemcontext)) {
2690                 $url = new moodle_url('/user/editadvanced.php', array('id'=>$user->id, 'course'=>$course->id));
2691                 $usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
2692             } else if ((has_capability('moodle/user:editprofile', $usercontext) && !is_primary_admin($user->id)) || ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext))) {
2693                 $url = new moodle_url('/user/edit.php', array('id'=>$user->id, 'course'=>$course->id));
2694                 $usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
2695             }
2696         }
2698         // Change password link
2699         if (!empty($user->auth)) {
2700             $userauth = get_auth_plugin($user->auth);
2701             if ($currentuser && !session_is_loggedinas() && $userauth->can_change_password() && !isguestuser() && has_capability('moodle/user:changeownpassword', $systemcontext)) {
2702                 $passwordchangeurl = $userauth->change_password_url();
2703                 if (!$passwordchangeurl) {
2704                     if (empty($CFG->loginhttps)) {
2705                         $wwwroot = $CFG->wwwroot;
2706                     } else {
2707                         $wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
2708                     }
2709                     $passwordchangeurl = new moodle_url('/login/change_password.php');
2710                 } else {
2711                     $urlbits = explode($passwordchangeurl. '?', 1);
2712                     $passwordchangeurl = new moodle_url($urlbits[0]);
2713                     if (count($urlbits)==2 && preg_match_all('#\&([^\=]*?)\=([^\&]*)#si', '&'.$urlbits[1], $matches)) {
2714                         foreach ($matches as $pair) {
2715                             $fullmeurl->param($pair[1],$pair[2]);
2716                         }
2717                     }
2718                 }
2719                 $passwordchangeurl->param('id', $course->id);
2720                 $usersetting->add(get_string("changepassword"), $passwordchangeurl, self::TYPE_SETTING);
2721             }
2722         }
2724         // View the roles settings
2725         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:manage'), $usercontext)) {
2726             $roles = $usersetting->add(get_string('roles'), null, self::TYPE_SETTING);
2728             $url = new moodle_url('/admin/roles/usersroles.php', array('userid'=>$user->id, 'courseid'=>$course->id));
2729             $roles->add(get_string('thisusersroles', 'role'), $url, self::TYPE_SETTING);
2731             $assignableroles = get_assignable_roles($usercontext, ROLENAME_BOTH);
2733             if (!empty($assignableroles)) {
2734                 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
2735                 $roles->add(get_string('assignrolesrelativetothisuser', 'role'), $url, self::TYPE_SETTING);
2736             }
2738             if (has_capability('moodle/role:review', $usercontext) || count(get_overridable_roles($usercontext, ROLENAME_BOTH))>0) {
2739                 $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
2740                 $roles->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
2741             }
2743             $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
2744             $roles->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
2745         }
2747         // Portfolio
2748         if ($currentuser && !empty($CFG->enableportfolios) && has_capability('moodle/portfolio:export', $systemcontext)) {
2749             require_once($CFG->libdir . '/portfoliolib.php');
2750             if (portfolio_instances(true, false)) {
2751                 $portfolio = $usersetting->add(get_string('portfolios', 'portfolio'), null, self::TYPE_SETTING);
2752                 $portfolio->add(get_string('configure', 'portfolio'), new moodle_url('/user/portfolio.php'), self::TYPE_SETTING);
2753                 $portfolio->add(get_string('logs', 'portfolio'), new moodle_url('/user/portfoliologs.php'), self::TYPE_SETTING);
2754             }
2755         }
2757         // Security keys
2758         if ($currentuser && !is_siteadmin($USER->id) && !empty($CFG->enablewebservices) && has_capability('moodle/webservice:createtoken', $systemcontext)) {
2759             $url = new moodle_url('/user/managetoken.php', array('sesskey'=>sesskey()));
2760             $usersetting->add(get_string('securitykeys', 'webservice'), $url, self::TYPE_SETTING);
2761         }
2763         // Repository
2764         if (!$currentuser) {
2765             require_once($CFG->dirroot . '/repository/lib.php');
2766             $editabletypes = repository::get_editable_types($usercontext);
2767             if ($usercontext->contextlevel == CONTEXT_USER && !empty($editabletypes)) {
2768                 $url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$usercontext->id));
2769                 $usersetting->add(get_string('repositories', 'repository'), $url, self::TYPE_SETTING);
2770             }
2771         }
2773         // Messaging
2774         if (has_capability('moodle/user:editownmessageprofile', $systemcontext)) {
2775             $url = new moodle_url('/message/edit.php', array('id'=>$user->id, 'course'=>$course->id));
2776             $usersetting->add(get_string('editmymessage', 'message'), $url, self::TYPE_SETTING);
2777         }
2779         return $usersetting;
2780     }
2782     /**
2783      * Loads block specific settings in the navigation
2784      *
2785      * @return navigation_node
2786      */
2787     protected function load_block_settings() {
2788         global $CFG;
2790         $blocknode = $this->add(print_context_name($this->context));
2791         $blocknode->force_open();
2793         // Assign local roles
2794         $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->context->id));
2795         $blocknode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING);
2797         // Override roles
2798         if (has_capability('moodle/role:review', $this->context) or  count(get_overridable_roles($this->context))>0) {
2799             $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->context->id));
2800             $blocknode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
2801         }
2802         // Check role permissions
2803         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->context)) {
2804             $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->context->id));
2805             $blocknode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
2806         }
2808         return $blocknode;
2809     }
2811     /**
2812      * Loads category specific settings in the navigation
2813      *
2814      * @return navigation_node
2815      */
2816     protected function load_category_settings() {
2817         global $CFG;
2819         $categorynode = $this->add(print_context_name($this->context));
2820         $categorynode->force_open();
2822         if ($this->page->user_is_editing() && has_capability('moodle/category:manage', $this->context)) {
2823             $categorynode->add(get_string('editcategorythis'), new moodle_url('/course/editcategory.php', array('id' => $this->context->instanceid)));
2824             $categorynode->add(get_string('addsubcategory'), new moodle_url('/course/editcategory.php', array('parent' => $this->context->instanceid)));
2825         }
2827         // Assign local roles
2828         $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->context->id));
2829         $categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING);
2831         // Override roles
2832         if (has_capability('moodle/role:review', $this->context) or count(get_overridable_roles($this->context))>0) {
2833             $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->context->id));
2834             $categorynode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
2835         }
2836         // Check role permissions
2837         if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->context)) {
2838             $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->context->id));
2839             $categorynode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
2840         }
2841         // Manage filters
2842         if (has_capability('moodle/filter:manage', $this->context) && count(filter_get_available_in_context($this->context))>0) {
2843             $url = new moodle_url('/filter/manage.php', array('contextid'=>$this->context->id));
2844             $categorynode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING);
2845         }
2847         return $categorynode;
2848     }
2850     /**
2851      * Determine whether the user is assuming another role
2852      *
2853      * This function checks to see if the user is assuming another role by means of
2854      * role switching. In doing this we compare each RSW key (context path) against
2855      * the current context path. This ensures that we can provide the switching
2856      * options against both the course and any page shown under the course.
2857      *
2858      * @return bool|int The role(int) if the user is in another role, false otherwise
2859      */
2860     protected function in_alternative_role() {
2861         global $USER;
2862         if (!empty($USER->access['rsw']) && is_array($USER->access['rsw'])) {
2863             if (!empty($this->page->context) && !empty($USER->access['rsw'][$this->page->context->path])) {
2864                 return $USER->access['rsw'][$this->page->context->path];
2865             }
2866             foreach ($USER->access['rsw'] as $key=>$role) {
2867                 if (strpos($this->context->path,$key)===0) {
2868                     return $role;
2869                 }
2870             }
2871         }
2872         return false;
2873     }
2875     /**
2876      * This function loads all of the front page settings into the settings navigation.
2877      * This function is called when the user is on the front page, or $COURSE==$SITE
2878      * @return navigation_node
2879      */
2880     protected function load_front_page_settings($forceopen = false) {
2881         global $SITE, $CFG;
2883         $course = clone($SITE);
2884         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);   // Course context
2886         $frontpage = $this->add(get_string('frontpagesettings'), null, self::TYPE_SETTING, null, 'frontpage');
2887         if ($forceopen) {
2888             $frontpage->force_open();
2889         }
2890         $frontpage->id = 'frontpagesettings';
2892         if (has_capability('moodle/course:update', $coursecontext)) {
2894             // Add the turn on/off settings
2895             $url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
2896             if ($this->page->user_is_editing()) {
2897                 $url->param('edit', 'off');
2898                 $editstring = get_string('turneditingoff');
2899             } else {
2900                 $url->param('edit', 'on');
2901                 $editstring = get_string('turneditingon');
2902             }
2903             $frontpage->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
2905             // Add the course settings link
2906             $url = new moodle_url('/admin/settings.php', array('section'=>'frontpagesettings'));
2907             $frontpage->add(get_string('settings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
2908         }
2910         //Participants
2911         if (has_capability('moodle/site:viewparticipants', $coursecontext)) {
2912             $url = new moodle_url('/user/index.php', array('contextid'=>$coursecontext->id));
2913             $frontpage->add(get_string('participants'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/users', ''));
2914         }
2916         // Roles
2917         if (has_capability('moodle/role:assign', $coursecontext)) {
2918             // Add assign or override roles if allowed
2919             $url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$coursecontext->id));
2920             $frontpage->add(get_string('assignroles', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2921             // Override roles
2922             if (has_capability('moodle/role:review', $coursecontext) or count(get_overridable_roles($coursecontext))>0) {
2923                 $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$coursecontext->id));
2924                 $frontpage->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2925             }
2926             // Check role permissions
2927             if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
2928                 $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$coursecontext->id));
2929                 $frontpage->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
2930             }
2931             // Manage filters
2932             if (has_capability('moodle/filter:manage', $coursecontext) && count(filter_get_available_in_context($coursecontext))>0) {
2933                 $url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id));
2934                 $frontpage->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/filter', ''));
2935             }
2936         }
2938         // Backup this course
2939         if (has_capability('moodle/backup:backupcourse', $coursecontext)) {
2940             $url = new moodle_url('/backup/backup.php', array('id'=>$course->id));
2941             $frontpage->add(get_string('backup'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/backup', ''));
2942         }
2944         // Restore to this course
2945         if (has_capability('moodle/restore:restorecourse', $coursecontext)) {
2946             $url = new moodle_url('/files/index.php', array('id'=>$course->id, 'wdir'=>'/backupdata'));
2947             $frontpage->add(get_string('restore'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
2948         }
2950         // Manage questions
2951         $questioncaps = array('moodle/question:add',
2952                               'moodle/question:editmine',
2953                               'moodle/question:editall',
2954                               'moodle/question:viewmine',
2955                               'moodle/question:viewall',
2956                               'moodle/question:movemine',
2957                               'moodle/question:moveall');
2958         if (has_any_capability($questioncaps, $this->context)) {
2959             $questionlink = $CFG->wwwroot.'/question/edit.php';
2960         } else if (has_capability('moodle/question:managecategory', $this->context)) {
2961             $questionlink = $CFG->wwwroot.'/question/category.php';
2962         }
2963         if (isset($questionlink)) {
2964             $url = new moodle_url($questionlink, array('courseid'=>$course->id));
2965             $frontpage->add(get_string('questions','quiz'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/questions', ''));
2966         }
2968         // Manage files
2969         if (has_capability('moodle/course:managefiles', $this->context)) {
2970             $url = new moodle_url('/files/index.php', array('id'=>$course->id));
2971             $frontpage->add(get_string('files'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/files', ''));
2972         }
2973         return $frontpage;
2974     }
2976     /**
2977      * This function marks the cache as volatile so it is cleared during shutdown
2978      */
2979     public function clear_cache() {
2980         $this->cache->volatile();
2981     }
2984 /**
2985  * Simple class used to output a navigation branch in XML
2986  *
2987  * @package moodlecore
2988  * @subpackage navigation
2989  * @copyright 2009 Sam Hemelryk
2990  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2991  */
2992 class navigation_json {
2993     /** @var array */
2994     protected $nodetype = array('node','branch');
2995     /** @var array */
2996     protected $expandable = array();
2997     /**
2998      * Turns a branch and all of its children into XML
2999      *
3000      * @param navigation_node $branch
3001      * @return string XML string
3002      */
3003     public function convert($branch) {
3004         $xml = $this->convert_child($branch);
3005         return $xml;
3006     }
3007     /**
3008      * Set the expandable items in the array so that we have enough information
3009      * to attach AJAX events
3010      * @param array $expandable
3011      */
3012     public function set_expandable($expandable) {
3013         foreach ($expandable as $node) {
3014             $this->expandable[(string)$node['branchid']] = $node;
3015         }
3016     }
3017     /**
3018      * Recusively converts a child node and its children to XML for output
3019      *
3020      * @param navigation_node $child The child to convert
3021      * @param int $depth Pointlessly used to track the depth of the XML structure
3022      * @return string JSON
3023      */
3024     protected function convert_child($child, $depth=1) {
3025         global $OUTPUT;
3027         if (!$child->display) {
3028             return '';
3029         }
3030         $attributes = array();
3031         $attributes['id'] = $child->id;
3032         $attributes['name'] = $child->text;
3033         $attributes['type'] = $child->type;
3034         $attributes['key'] = $child->key;
3035         $attributes['class'] = $child->get_css_type();
3037         if ($child->icon instanceof pix_icon) {
3038             $attributes['icon'] = array(
3039                 'component' => $child->icon->component,
3040                 'pix' => $child->icon->pix,
3041             );
3042             foreach ($child->icon->attributes as $key=>$value) {
3043                 if ($key == 'class') {
3044                     $attributes['icon']['classes'] = explode(' ', $value);
3045                 } else if (!array_key_exists($key, $attributes['icon'])) {
3046                     $attributes['icon'][$key] = $value;
3047                 }
3049             }
3050         } else if (!empty($child->icon)) {
3051             $attributes['icon'] = (string)$child->icon;
3052         }
3054         if ($child->forcetitle || $child->title !== $child->text) {
3055             $attributes['title'] = htmlentities($child->title);
3056         }
3057         if (array_key_exists((string)$child->key, $this->expandable)) {
3058             $attributes['expandable'] = $child->key;
3059             $child->add_class($this->expandable[$child->key]['id']);
3060         }
3062         if (count($child->classes)>0) {
3063             $attributes['class'] .= ' '.join(' ',$child->classes);
3064         }
3065         if (is_string($child->action)) {
3066             $attributes['link'] = $child->action;
3067         } else if ($child->action instanceof moodle_url) {
3068             $attributes['link'] = $child->action->out();
3069         }
3070         $attributes['hidden'] = ($child->hidden);
3071         $attributes['haschildren'] = ($child->children->count()>0 || $child->type == navigation_node::TYPE_CATEGORY);
3073         if (count($child->children)>0) {
3074             $attributes['children'] = array();
3075             foreach ($child->children as $subchild) {
3076                 $attributes['children'][] = $this->convert_child($subchild, $depth+1);
3077             }
3078         }
3080         if ($depth > 1) {
3081             return $attributes;
3082         } else {
3083             return json_encode($attributes);
3084         }
3085     }
3088 /**
3089  * The cache class used by global navigation and settings navigation to cache bits
3090  * and bobs that are used during their generation.
3091  *
3092  * It is basically an easy access point to session with a bit of smarts to make
3093  * sure that the information that is cached is valid still.
3094  *
3095  * Example use:
3096  * <code php>
3097  * if (!$cache->viewdiscussion()) {
3098  *     // Code to do stuff and produce cachable content
3099  *     $cache->viewdiscussion = has_capability('mod/forum:viewdiscussion', $coursecontext);
3100  * }
3101  * $content = $cache->viewdiscussion;
3102  * </code>
3103  *
3104  * @package moodlecore
3105  * @subpackage navigation
3106  * @copyright 2009 Sam Hemelryk
3107  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3108  */
3109 class navigation_cache {
3110     /** @var int */
3111     protected $creation;
3112     /** @var array */
3113     protected $session;
3114     /** @var string */
3115     protected $area;
3116     /** @var int */
3117     protected $timeout;
3118     /** @var stdClass */
3119     protected $currentcontext;
3120     /** @var int */
3121     const CACHETIME = 0;
3122     /** @var int */
3123     const CACHEUSERID = 1;
3124     /** @var int */
3125     const CACHEVALUE = 2;
3126     /** @var null|array An array of navigation cache areas to expire on shutdown */
3127     public static $volatilecaches;
3129     /**
3130      * Contructor for the cache. Requires two arguments
3131      *
3132      * @param string $area The string to use to segregate this particular cache
3133      *                it can either be unique to start a fresh cache or if you want
3134      *                to share a cache then make it the string used in the original
3135      *                cache
3136      * @param int $timeout The number of seconds to time the information out after
3137      */
3138     public function __construct($area, $timeout=60) {
3139         global $SESSION;
3140         $this->creation = time();
3141         $this->area = $area;
3143         if (!isset($SESSION->navcache)) {
3144             $SESSION->navcache = new stdClass;
3145         }
3147         if (!isset($SESSION->navcache->{$area})) {
3148             $SESSION->navcache->{$area} = array();
3149         }
3150         $this->session = &$SESSION->navcache->{$area};
3151         $this->timeout = time()-$timeout;
3152         if (rand(0,10)===0) {
3153             $this->garbage_collection();
3154         }
3155     }
3157     /**
3158      * Magic Method to retrieve something by simply calling using = cache->key
3159      *
3160      * @param mixed $key The identifier for the information you want out again
3161      * @return void|mixed Either void or what ever was put in
3162      */
3163     public function __get($key) {
3164         if (!$this->cached($key)) {
3165             return;
3166         }
3167         $information = $this->session[$key][self::CACHEVALUE];
3168         return unserialize($information);
3169     }
3171     /**
3172      * Magic method that simply uses {@link set();} to store something in the cache
3173      *
3174      * @param string|int $key
3175      * @param mixed $information
3176      */
3177     public function __set($key, $information) {
3178         $this->set($key, $information);
3179     }
3181     /**
3182      * Sets some information against the cache (session) for later retrieval
3183      *
3184      * @param string|int $key
3185      * @param mixed $information
3186      */
3187     public function set($key, $information) {
3188         global $USER;
3189         $information = serialize($information);
3190         $this->session[$key]= array(self::CACHETIME=>time(), self::CACHEUSERID=>$USER->id, self::CACHEVALUE=>$information);
3191     }
3192     /**
3193      * Check the existence of the identifier in the cache
3194      *
3195      * @param string|int $key
3196      * @return bool
3197      */
3198     public function cached($key) {
3199         global $USER;
3200         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) {
3201             return false;
3202         }
3203         return true;
3204     }
3205     /**
3206      * Compare something to it's equivilant in the cache
3207      *
3208      * @param string $key
3209      * @param mixed $value
3210      * @param bool $serialise Whether to serialise the value before comparison
3211      *              this should only be set to false if the value is already
3212      *              serialised
3213      * @return bool If the value is the same false if it is not set or doesn't match
3214      */
3215     public function compare($key, $value, $serialise=true) {
3216         if ($this->cached($key)) {
3217             if ($serialise) {
3218                 $value = serialize($value);
3219             }
3220             if ($this->session[$key][self::CACHEVALUE] === $value) {
3221                 return true;
3222             }
3223         }
3224         return false;
3225     }
3226     /**
3227      * Wipes the entire cache, good to force regeneration
3228      */
3229     public function clear() {
3230         $this->session = array();
3231     }
3232     /**
3233      * Checks all cache entries and removes any that have expired, good ole cleanup
3234      */
3235     protected function garbage_collection() {
3236         foreach ($this->session as $key=>$cachedinfo) {
3237             if (is_array($cachedinfo) && $cachedinfo[self::CACHETIME]<$this->timeout) {
3238                 unset($this->session[$key]);
3239             }
3240         }
3241     }
3243     /**
3244      * Marks the cache as being volatile (likely to change)
3245      *
3246      * Any caches marked as volatile will be destroyed at the on shutdown by
3247      * {@link navigation_node::destroy_volatile_caches()} which is registered
3248      * as a shutdown function if any caches are marked as volatile.
3249      *
3250      * @param bool $setting True to destroy the cache false not too
3251      */
3252     public function volatile($setting = true) {
3253         if (self::$volatilecaches===null) {
3254             self::$volatilecaches = array();
3255             register_shutdown_function(array('navigation_cache','destroy_volatile_caches'));
3256         }
3258         if ($setting) {
3259             self::$volatilecaches[$this->area] = $this->area;
3260         } else if (array_key_exists($this->area, self::$volatilecaches)) {
3261             unset(self::$volatilecaches[$this->area]);
3262         }
3263     }
3265     /**
3266      * Destroys all caches marked as volatile
3267      *
3268      * This function is static and works in conjunction with the static volatilecaches
3269      * property of navigation cache.
3270      * Because this function is static it manually resets the cached areas back to an
3271      * empty array.
3272      */
3273     public static function destroy_volatile_caches() {
3274         global $SESSION;
3275         if (is_array(self::$volatilecaches) && count(self::$volatilecaches)>0) {
3276             foreach (self::$volatilecaches as $area) {
3277                 $SESSION->navcache->{$area} = array();
3278             }
3279         }
3280     }