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