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