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