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