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