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