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