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