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