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