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