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