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