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