[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This file contains classes used to manage the navigation structures within Moodle. 19 * 20 * @since Moodle 2.0 21 * @package core 22 * @copyright 2009 Sam Hemelryk 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 /** 29 * The name that will be used to separate the navigation cache within SESSION 30 */ 31 define('NAVIGATION_CACHE_NAME', 'navigation'); 32 define('NAVIGATION_SITE_ADMIN_CACHE_NAME', 'navigationsiteadmin'); 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 core 44 * @category navigation 45 * @copyright 2009 Sam Hemelryk 46 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 47 */ 48 class navigation_node implements renderable { 49 /** @var int Used to identify this node a leaf (default) 0 */ 50 const NODETYPE_LEAF = 0; 51 /** @var int Used to identify this node a branch, happens with children 1 */ 52 const NODETYPE_BRANCH = 1; 53 /** @var null Unknown node type null */ 54 const TYPE_UNKNOWN = null; 55 /** @var int System node type 0 */ 56 const TYPE_ROOTNODE = 0; 57 /** @var int System node type 1 */ 58 const TYPE_SYSTEM = 1; 59 /** @var int Category node type 10 */ 60 const TYPE_CATEGORY = 10; 61 /** var int Category displayed in MyHome navigation node */ 62 const TYPE_MY_CATEGORY = 11; 63 /** @var int Course node type 20 */ 64 const TYPE_COURSE = 20; 65 /** @var int Course Structure node type 30 */ 66 const TYPE_SECTION = 30; 67 /** @var int Activity node type, e.g. Forum, Quiz 40 */ 68 const TYPE_ACTIVITY = 40; 69 /** @var int Resource node type, e.g. Link to a file, or label 50 */ 70 const TYPE_RESOURCE = 50; 71 /** @var int A custom node type, default when adding without specifing type 60 */ 72 const TYPE_CUSTOM = 60; 73 /** @var int Setting node type, used only within settings nav 70 */ 74 const TYPE_SETTING = 70; 75 /** @var int site admin branch node type, used only within settings nav 71 */ 76 const TYPE_SITE_ADMIN = 71; 77 /** @var int Setting node type, used only within settings nav 80 */ 78 const TYPE_USER = 80; 79 /** @var int Setting node type, used for containers of no importance 90 */ 80 const TYPE_CONTAINER = 90; 81 /** var int Course the current user is not enrolled in */ 82 const COURSE_OTHER = 0; 83 /** var int Course the current user is enrolled in but not viewing */ 84 const COURSE_MY = 1; 85 /** var int Course the current user is currently viewing */ 86 const COURSE_CURRENT = 2; 87 88 /** @var int Parameter to aid the coder in tracking [optional] */ 89 public $id = null; 90 /** @var string|int The identifier for the node, used to retrieve the node */ 91 public $key = null; 92 /** @var string The text to use for the node */ 93 public $text = null; 94 /** @var string Short text to use if requested [optional] */ 95 public $shorttext = null; 96 /** @var string The title attribute for an action if one is defined */ 97 public $title = null; 98 /** @var string A string that can be used to build a help button */ 99 public $helpbutton = null; 100 /** @var moodle_url|action_link|null An action for the node (link) */ 101 public $action = null; 102 /** @var pix_icon The path to an icon to use for this node */ 103 public $icon = null; 104 /** @var int See TYPE_* constants defined for this class */ 105 public $type = self::TYPE_UNKNOWN; 106 /** @var int See NODETYPE_* constants defined for this class */ 107 public $nodetype = self::NODETYPE_LEAF; 108 /** @var bool If set to true the node will be collapsed by default */ 109 public $collapse = false; 110 /** @var bool If set to true the node will be expanded by default */ 111 public $forceopen = false; 112 /** @var array An array of CSS classes for the node */ 113 public $classes = array(); 114 /** @var navigation_node_collection An array of child nodes */ 115 public $children = array(); 116 /** @var bool If set to true the node will be recognised as active */ 117 public $isactive = false; 118 /** @var bool If set to true the node will be dimmed */ 119 public $hidden = false; 120 /** @var bool If set to false the node will not be displayed */ 121 public $display = true; 122 /** @var bool If set to true then an HR will be printed before the node */ 123 public $preceedwithhr = false; 124 /** @var bool If set to true the the navigation bar should ignore this node */ 125 public $mainnavonly = false; 126 /** @var bool If set to true a title will be added to the action no matter what */ 127 public $forcetitle = false; 128 /** @var navigation_node A reference to the node parent, you should never set this directly you should always call set_parent */ 129 public $parent = null; 130 /** @var bool Override to not display the icon even if one is provided **/ 131 public $hideicon = false; 132 /** @var bool Set to true if we KNOW that this node can be expanded. */ 133 public $isexpandable = false; 134 /** @var array */ 135 protected $namedtypes = array(0=>'system',10=>'category',20=>'course',30=>'structure',40=>'activity',50=>'resource',60=>'custom',70=>'setting',71=>'siteadmin', 80=>'user'); 136 /** @var moodle_url */ 137 protected static $fullmeurl = null; 138 /** @var bool toogles auto matching of active node */ 139 public static $autofindactive = true; 140 /** @var bool should we load full admin tree or rely on AJAX for performance reasons */ 141 protected static $loadadmintree = false; 142 /** @var mixed If set to an int, that section will be included even if it has no activities */ 143 public $includesectionnum = false; 144 /** @var bool does the node need to be loaded via ajax */ 145 public $requiresajaxloading = false; 146 147 /** 148 * Constructs a new navigation_node 149 * 150 * @param array|string $properties Either an array of properties or a string to use 151 * as the text for the node 152 */ 153 public function __construct($properties) { 154 if (is_array($properties)) { 155 // Check the array for each property that we allow to set at construction. 156 // text - The main content for the node 157 // shorttext - A short text if required for the node 158 // icon - The icon to display for the node 159 // type - The type of the node 160 // key - The key to use to identify the node 161 // parent - A reference to the nodes parent 162 // action - The action to attribute to this node, usually a URL to link to 163 if (array_key_exists('text', $properties)) { 164 $this->text = $properties['text']; 165 } 166 if (array_key_exists('shorttext', $properties)) { 167 $this->shorttext = $properties['shorttext']; 168 } 169 if (!array_key_exists('icon', $properties)) { 170 $properties['icon'] = new pix_icon('i/navigationitem', ''); 171 } 172 $this->icon = $properties['icon']; 173 if ($this->icon instanceof pix_icon) { 174 if (empty($this->icon->attributes['class'])) { 175 $this->icon->attributes['class'] = 'navicon'; 176 } else { 177 $this->icon->attributes['class'] .= ' navicon'; 178 } 179 } 180 if (array_key_exists('type', $properties)) { 181 $this->type = $properties['type']; 182 } else { 183 $this->type = self::TYPE_CUSTOM; 184 } 185 if (array_key_exists('key', $properties)) { 186 $this->key = $properties['key']; 187 } 188 // This needs to happen last because of the check_if_active call that occurs 189 if (array_key_exists('action', $properties)) { 190 $this->action = $properties['action']; 191 if (is_string($this->action)) { 192 $this->action = new moodle_url($this->action); 193 } 194 if (self::$autofindactive) { 195 $this->check_if_active(); 196 } 197 } 198 if (array_key_exists('parent', $properties)) { 199 $this->set_parent($properties['parent']); 200 } 201 } else if (is_string($properties)) { 202 $this->text = $properties; 203 } 204 if ($this->text === null) { 205 throw new coding_exception('You must set the text for the node when you create it.'); 206 } 207 // Instantiate a new navigation node collection for this nodes children 208 $this->children = new navigation_node_collection(); 209 } 210 211 /** 212 * Checks if this node is the active node. 213 * 214 * This is determined by comparing the action for the node against the 215 * defined URL for the page. A match will see this node marked as active. 216 * 217 * @param int $strength One of URL_MATCH_EXACT, URL_MATCH_PARAMS, or URL_MATCH_BASE 218 * @return bool 219 */ 220 public function check_if_active($strength=URL_MATCH_EXACT) { 221 global $FULLME, $PAGE; 222 // Set fullmeurl if it hasn't already been set 223 if (self::$fullmeurl == null) { 224 if ($PAGE->has_set_url()) { 225 self::override_active_url(new moodle_url($PAGE->url)); 226 } else { 227 self::override_active_url(new moodle_url($FULLME)); 228 } 229 } 230 231 // Compare the action of this node against the fullmeurl 232 if ($this->action instanceof moodle_url && $this->action->compare(self::$fullmeurl, $strength)) { 233 $this->make_active(); 234 return true; 235 } 236 return false; 237 } 238 239 /** 240 * This sets the URL that the URL of new nodes get compared to when locating 241 * the active node. 242 * 243 * The active node is the node that matches the URL set here. By default this 244 * is either $PAGE->url or if that hasn't been set $FULLME. 245 * 246 * @param moodle_url $url The url to use for the fullmeurl. 247 * @param bool $loadadmintree use true if the URL point to administration tree 248 */ 249 public static function override_active_url(moodle_url $url, $loadadmintree = false) { 250 // Clone the URL, in case the calling script changes their URL later. 251 self::$fullmeurl = new moodle_url($url); 252 // True means we do not want AJAX loaded admin tree, required for all admin pages. 253 if ($loadadmintree) { 254 // Do not change back to false if already set. 255 self::$loadadmintree = true; 256 } 257 } 258 259 /** 260 * Use when page is linked from the admin tree, 261 * if not used navigation could not find the page using current URL 262 * because the tree is not fully loaded. 263 */ 264 public static function require_admin_tree() { 265 self::$loadadmintree = true; 266 } 267 268 /** 269 * Creates a navigation node, ready to add it as a child using add_node 270 * function. (The created node needs to be added before you can use it.) 271 * @param string $text 272 * @param moodle_url|action_link $action 273 * @param int $type 274 * @param string $shorttext 275 * @param string|int $key 276 * @param pix_icon $icon 277 * @return navigation_node 278 */ 279 public static function create($text, $action=null, $type=self::TYPE_CUSTOM, 280 $shorttext=null, $key=null, pix_icon $icon=null) { 281 // Properties array used when creating the new navigation node 282 $itemarray = array( 283 'text' => $text, 284 'type' => $type 285 ); 286 // Set the action if one was provided 287 if ($action!==null) { 288 $itemarray['action'] = $action; 289 } 290 // Set the shorttext if one was provided 291 if ($shorttext!==null) { 292 $itemarray['shorttext'] = $shorttext; 293 } 294 // Set the icon if one was provided 295 if ($icon!==null) { 296 $itemarray['icon'] = $icon; 297 } 298 // Set the key 299 $itemarray['key'] = $key; 300 // Construct and return 301 return new navigation_node($itemarray); 302 } 303 304 /** 305 * Adds a navigation node as a child of this node. 306 * 307 * @param string $text 308 * @param moodle_url|action_link $action 309 * @param int $type 310 * @param string $shorttext 311 * @param string|int $key 312 * @param pix_icon $icon 313 * @return navigation_node 314 */ 315 public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) { 316 // Create child node 317 $childnode = self::create($text, $action, $type, $shorttext, $key, $icon); 318 319 // Add the child to end and return 320 return $this->add_node($childnode); 321 } 322 323 /** 324 * Adds a navigation node as a child of this one, given a $node object 325 * created using the create function. 326 * @param navigation_node $childnode Node to add 327 * @param string $beforekey 328 * @return navigation_node The added node 329 */ 330 public function add_node(navigation_node $childnode, $beforekey=null) { 331 // First convert the nodetype for this node to a branch as it will now have children 332 if ($this->nodetype !== self::NODETYPE_BRANCH) { 333 $this->nodetype = self::NODETYPE_BRANCH; 334 } 335 // Set the parent to this node 336 $childnode->set_parent($this); 337 338 // Default the key to the number of children if not provided 339 if ($childnode->key === null) { 340 $childnode->key = $this->children->count(); 341 } 342 343 // Add the child using the navigation_node_collections add method 344 $node = $this->children->add($childnode, $beforekey); 345 346 // If added node is a category node or the user is logged in and it's a course 347 // then mark added node as a branch (makes it expandable by AJAX) 348 $type = $childnode->type; 349 if (($type == self::TYPE_CATEGORY) || (isloggedin() && ($type == self::TYPE_COURSE)) || ($type == self::TYPE_MY_CATEGORY) || 350 ($type === self::TYPE_SITE_ADMIN)) { 351 $node->nodetype = self::NODETYPE_BRANCH; 352 } 353 // If this node is hidden mark it's children as hidden also 354 if ($this->hidden) { 355 $node->hidden = true; 356 } 357 // Return added node (reference returned by $this->children->add() 358 return $node; 359 } 360 361 /** 362 * Return a list of all the keys of all the child nodes. 363 * @return array the keys. 364 */ 365 public function get_children_key_list() { 366 return $this->children->get_key_list(); 367 } 368 369 /** 370 * Searches for a node of the given type with the given key. 371 * 372 * This searches this node plus all of its children, and their children.... 373 * If you know the node you are looking for is a child of this node then please 374 * use the get method instead. 375 * 376 * @param int|string $key The key of the node we are looking for 377 * @param int $type One of navigation_node::TYPE_* 378 * @return navigation_node|false 379 */ 380 public function find($key, $type) { 381 return $this->children->find($key, $type); 382 } 383 384 /** 385 * Get the child of this node that has the given key + (optional) type. 386 * 387 * If you are looking for a node and want to search all children + their children 388 * then please use the find method instead. 389 * 390 * @param int|string $key The key of the node we are looking for 391 * @param int $type One of navigation_node::TYPE_* 392 * @return navigation_node|false 393 */ 394 public function get($key, $type=null) { 395 return $this->children->get($key, $type); 396 } 397 398 /** 399 * Removes this node. 400 * 401 * @return bool 402 */ 403 public function remove() { 404 return $this->parent->children->remove($this->key, $this->type); 405 } 406 407 /** 408 * Checks if this node has or could have any children 409 * 410 * @return bool Returns true if it has children or could have (by AJAX expansion) 411 */ 412 public function has_children() { 413 return ($this->nodetype === navigation_node::NODETYPE_BRANCH || $this->children->count()>0 || $this->isexpandable); 414 } 415 416 /** 417 * Marks this node as active and forces it open. 418 * 419 * Important: If you are here because you need to mark a node active to get 420 * the navigation to do what you want have you looked at {@link navigation_node::override_active_url()}? 421 * You can use it to specify a different URL to match the active navigation node on 422 * rather than having to locate and manually mark a node active. 423 */ 424 public function make_active() { 425 $this->isactive = true; 426 $this->add_class('active_tree_node'); 427 $this->force_open(); 428 if ($this->parent !== null) { 429 $this->parent->make_inactive(); 430 } 431 } 432 433 /** 434 * Marks a node as inactive and recusised back to the base of the tree 435 * doing the same to all parents. 436 */ 437 public function make_inactive() { 438 $this->isactive = false; 439 $this->remove_class('active_tree_node'); 440 if ($this->parent !== null) { 441 $this->parent->make_inactive(); 442 } 443 } 444 445 /** 446 * Forces this node to be open and at the same time forces open all 447 * parents until the root node. 448 * 449 * Recursive. 450 */ 451 public function force_open() { 452 $this->forceopen = true; 453 if ($this->parent !== null) { 454 $this->parent->force_open(); 455 } 456 } 457 458 /** 459 * Adds a CSS class to this node. 460 * 461 * @param string $class 462 * @return bool 463 */ 464 public function add_class($class) { 465 if (!in_array($class, $this->classes)) { 466 $this->classes[] = $class; 467 } 468 return true; 469 } 470 471 /** 472 * Removes a CSS class from this node. 473 * 474 * @param string $class 475 * @return bool True if the class was successfully removed. 476 */ 477 public function remove_class($class) { 478 if (in_array($class, $this->classes)) { 479 $key = array_search($class,$this->classes); 480 if ($key!==false) { 481 unset($this->classes[$key]); 482 return true; 483 } 484 } 485 return false; 486 } 487 488 /** 489 * Sets the title for this node and forces Moodle to utilise it. 490 * @param string $title 491 */ 492 public function title($title) { 493 $this->title = $title; 494 $this->forcetitle = true; 495 } 496 497 /** 498 * Resets the page specific information on this node if it is being unserialised. 499 */ 500 public function __wakeup(){ 501 $this->forceopen = false; 502 $this->isactive = false; 503 $this->remove_class('active_tree_node'); 504 } 505 506 /** 507 * Checks if this node or any of its children contain the active node. 508 * 509 * Recursive. 510 * 511 * @return bool 512 */ 513 public function contains_active_node() { 514 if ($this->isactive) { 515 return true; 516 } else { 517 foreach ($this->children as $child) { 518 if ($child->isactive || $child->contains_active_node()) { 519 return true; 520 } 521 } 522 } 523 return false; 524 } 525 526 /** 527 * Finds the active node. 528 * 529 * Searches this nodes children plus all of the children for the active node 530 * and returns it if found. 531 * 532 * Recursive. 533 * 534 * @return navigation_node|false 535 */ 536 public function find_active_node() { 537 if ($this->isactive) { 538 return $this; 539 } else { 540 foreach ($this->children as &$child) { 541 $outcome = $child->find_active_node(); 542 if ($outcome !== false) { 543 return $outcome; 544 } 545 } 546 } 547 return false; 548 } 549 550 /** 551 * Searches all children for the best matching active node 552 * @return navigation_node|false 553 */ 554 public function search_for_active_node() { 555 if ($this->check_if_active(URL_MATCH_BASE)) { 556 return $this; 557 } else { 558 foreach ($this->children as &$child) { 559 $outcome = $child->search_for_active_node(); 560 if ($outcome !== false) { 561 return $outcome; 562 } 563 } 564 } 565 return false; 566 } 567 568 /** 569 * Gets the content for this node. 570 * 571 * @param bool $shorttext If true shorttext is used rather than the normal text 572 * @return string 573 */ 574 public function get_content($shorttext=false) { 575 if ($shorttext && $this->shorttext!==null) { 576 return format_string($this->shorttext); 577 } else { 578 return format_string($this->text); 579 } 580 } 581 582 /** 583 * Gets the title to use for this node. 584 * 585 * @return string 586 */ 587 public function get_title() { 588 if ($this->forcetitle || $this->action != null){ 589 return $this->title; 590 } else { 591 return ''; 592 } 593 } 594 595 /** 596 * Gets the CSS class to add to this node to describe its type 597 * 598 * @return string 599 */ 600 public function get_css_type() { 601 if (array_key_exists($this->type, $this->namedtypes)) { 602 return 'type_'.$this->namedtypes[$this->type]; 603 } 604 return 'type_unknown'; 605 } 606 607 /** 608 * Finds all nodes that are expandable by AJAX 609 * 610 * @param array $expandable An array by reference to populate with expandable nodes. 611 */ 612 public function find_expandable(array &$expandable) { 613 foreach ($this->children as &$child) { 614 if ($child->display && $child->has_children() && $child->children->count() == 0) { 615 $child->id = 'expandable_branch_'.$child->type.'_'.clean_param($child->key, PARAM_ALPHANUMEXT); 616 $this->add_class('canexpand'); 617 $child->requiresajaxloading = true; 618 $expandable[] = array('id' => $child->id, 'key' => $child->key, 'type' => $child->type); 619 } 620 $child->find_expandable($expandable); 621 } 622 } 623 624 /** 625 * Finds all nodes of a given type (recursive) 626 * 627 * @param int $type One of navigation_node::TYPE_* 628 * @return array 629 */ 630 public function find_all_of_type($type) { 631 $nodes = $this->children->type($type); 632 foreach ($this->children as &$node) { 633 $childnodes = $node->find_all_of_type($type); 634 $nodes = array_merge($nodes, $childnodes); 635 } 636 return $nodes; 637 } 638 639 /** 640 * Removes this node if it is empty 641 */ 642 public function trim_if_empty() { 643 if ($this->children->count() == 0) { 644 $this->remove(); 645 } 646 } 647 648 /** 649 * Creates a tab representation of this nodes children that can be used 650 * with print_tabs to produce the tabs on a page. 651 * 652 * call_user_func_array('print_tabs', $node->get_tabs_array()); 653 * 654 * @param array $inactive 655 * @param bool $return 656 * @return array Array (tabs, selected, inactive, activated, return) 657 */ 658 public function get_tabs_array(array $inactive=array(), $return=false) { 659 $tabs = array(); 660 $rows = array(); 661 $selected = null; 662 $activated = array(); 663 foreach ($this->children as $node) { 664 $tabs[] = new tabobject($node->key, $node->action, $node->get_content(), $node->get_title()); 665 if ($node->contains_active_node()) { 666 if ($node->children->count() > 0) { 667 $activated[] = $node->key; 668 foreach ($node->children as $child) { 669 if ($child->contains_active_node()) { 670 $selected = $child->key; 671 } 672 $rows[] = new tabobject($child->key, $child->action, $child->get_content(), $child->get_title()); 673 } 674 } else { 675 $selected = $node->key; 676 } 677 } 678 } 679 return array(array($tabs, $rows), $selected, $inactive, $activated, $return); 680 } 681 682 /** 683 * Sets the parent for this node and if this node is active ensures that the tree is properly 684 * adjusted as well. 685 * 686 * @param navigation_node $parent 687 */ 688 public function set_parent(navigation_node $parent) { 689 // Set the parent (thats the easy part) 690 $this->parent = $parent; 691 // Check if this node is active (this is checked during construction) 692 if ($this->isactive) { 693 // Force all of the parent nodes open so you can see this node 694 $this->parent->force_open(); 695 // Make all parents inactive so that its clear where we are. 696 $this->parent->make_inactive(); 697 } 698 } 699 700 /** 701 * Hides the node and any children it has. 702 * 703 * @since Moodle 2.5 704 * @param array $typestohide Optional. An array of node types that should be hidden. 705 * If null all nodes will be hidden. 706 * If an array is given then nodes will only be hidden if their type mtatches an element in the array. 707 * e.g. array(navigation_node::TYPE_COURSE) would hide only course nodes. 708 */ 709 public function hide(array $typestohide = null) { 710 if ($typestohide === null || in_array($this->type, $typestohide)) { 711 $this->display = false; 712 if ($this->has_children()) { 713 foreach ($this->children as $child) { 714 $child->hide($typestohide); 715 } 716 } 717 } 718 } 719 } 720 721 /** 722 * Navigation node collection 723 * 724 * This class is responsible for managing a collection of navigation nodes. 725 * It is required because a node's unique identifier is a combination of both its 726 * key and its type. 727 * 728 * Originally an array was used with a string key that was a combination of the two 729 * however it was decided that a better solution would be to use a class that 730 * implements the standard IteratorAggregate interface. 731 * 732 * @package core 733 * @category navigation 734 * @copyright 2010 Sam Hemelryk 735 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 736 */ 737 class navigation_node_collection implements IteratorAggregate { 738 /** 739 * A multidimensional array to where the first key is the type and the second 740 * key is the nodes key. 741 * @var array 742 */ 743 protected $collection = array(); 744 /** 745 * An array that contains references to nodes in the same order they were added. 746 * This is maintained as a progressive array. 747 * @var array 748 */ 749 protected $orderedcollection = array(); 750 /** 751 * A reference to the last node that was added to the collection 752 * @var navigation_node 753 */ 754 protected $last = null; 755 /** 756 * The total number of items added to this array. 757 * @var int 758 */ 759 protected $count = 0; 760 761 /** 762 * Adds a navigation node to the collection 763 * 764 * @param navigation_node $node Node to add 765 * @param string $beforekey If specified, adds before a node with this key, 766 * otherwise adds at end 767 * @return navigation_node Added node 768 */ 769 public function add(navigation_node $node, $beforekey=null) { 770 global $CFG; 771 $key = $node->key; 772 $type = $node->type; 773 774 // First check we have a 2nd dimension for this type 775 if (!array_key_exists($type, $this->orderedcollection)) { 776 $this->orderedcollection[$type] = array(); 777 } 778 // Check for a collision and report if debugging is turned on 779 if ($CFG->debug && array_key_exists($key, $this->orderedcollection[$type])) { 780 debugging('Navigation node intersect: Adding a node that already exists '.$key, DEBUG_DEVELOPER); 781 } 782 783 // Find the key to add before 784 $newindex = $this->count; 785 $last = true; 786 if ($beforekey !== null) { 787 foreach ($this->collection as $index => $othernode) { 788 if ($othernode->key === $beforekey) { 789 $newindex = $index; 790 $last = false; 791 break; 792 } 793 } 794 if ($newindex === $this->count) { 795 debugging('Navigation node add_before: Reference node not found ' . $beforekey . 796 ', options: ' . implode(' ', $this->get_key_list()), DEBUG_DEVELOPER); 797 } 798 } 799 800 // Add the node to the appropriate place in the by-type structure (which 801 // is not ordered, despite the variable name) 802 $this->orderedcollection[$type][$key] = $node; 803 if (!$last) { 804 // Update existing references in the ordered collection (which is the 805 // one that isn't called 'ordered') to shuffle them along if required 806 for ($oldindex = $this->count; $oldindex > $newindex; $oldindex--) { 807 $this->collection[$oldindex] = $this->collection[$oldindex - 1]; 808 } 809 } 810 // Add a reference to the node to the progressive collection. 811 $this->collection[$newindex] = $this->orderedcollection[$type][$key]; 812 // Update the last property to a reference to this new node. 813 $this->last = $this->orderedcollection[$type][$key]; 814 815 // Reorder the array by index if needed 816 if (!$last) { 817 ksort($this->collection); 818 } 819 $this->count++; 820 // Return the reference to the now added node 821 return $node; 822 } 823 824 /** 825 * Return a list of all the keys of all the nodes. 826 * @return array the keys. 827 */ 828 public function get_key_list() { 829 $keys = array(); 830 foreach ($this->collection as $node) { 831 $keys[] = $node->key; 832 } 833 return $keys; 834 } 835 836 /** 837 * Fetches a node from this collection. 838 * 839 * @param string|int $key The key of the node we want to find. 840 * @param int $type One of navigation_node::TYPE_*. 841 * @return navigation_node|null 842 */ 843 public function get($key, $type=null) { 844 if ($type !== null) { 845 // If the type is known then we can simply check and fetch 846 if (!empty($this->orderedcollection[$type][$key])) { 847 return $this->orderedcollection[$type][$key]; 848 } 849 } else { 850 // Because we don't know the type we look in the progressive array 851 foreach ($this->collection as $node) { 852 if ($node->key === $key) { 853 return $node; 854 } 855 } 856 } 857 return false; 858 } 859 860 /** 861 * Searches for a node with matching key and type. 862 * 863 * This function searches both the nodes in this collection and all of 864 * the nodes in each collection belonging to the nodes in this collection. 865 * 866 * Recursive. 867 * 868 * @param string|int $key The key of the node we want to find. 869 * @param int $type One of navigation_node::TYPE_*. 870 * @return navigation_node|null 871 */ 872 public function find($key, $type=null) { 873 if ($type !== null && array_key_exists($type, $this->orderedcollection) && array_key_exists($key, $this->orderedcollection[$type])) { 874 return $this->orderedcollection[$type][$key]; 875 } else { 876 $nodes = $this->getIterator(); 877 // Search immediate children first 878 foreach ($nodes as &$node) { 879 if ($node->key === $key && ($type === null || $type === $node->type)) { 880 return $node; 881 } 882 } 883 // Now search each childs children 884 foreach ($nodes as &$node) { 885 $result = $node->children->find($key, $type); 886 if ($result !== false) { 887 return $result; 888 } 889 } 890 } 891 return false; 892 } 893 894 /** 895 * Fetches the last node that was added to this collection 896 * 897 * @return navigation_node 898 */ 899 public function last() { 900 return $this->last; 901 } 902 903 /** 904 * Fetches all nodes of a given type from this collection 905 * 906 * @param string|int $type node type being searched for. 907 * @return array ordered collection 908 */ 909 public function type($type) { 910 if (!array_key_exists($type, $this->orderedcollection)) { 911 $this->orderedcollection[$type] = array(); 912 } 913 return $this->orderedcollection[$type]; 914 } 915 /** 916 * Removes the node with the given key and type from the collection 917 * 918 * @param string|int $key The key of the node we want to find. 919 * @param int $type 920 * @return bool 921 */ 922 public function remove($key, $type=null) { 923 $child = $this->get($key, $type); 924 if ($child !== false) { 925 foreach ($this->collection as $colkey => $node) { 926 if ($node->key === $key && (is_null($type) || $node->type == $type)) { 927 unset($this->collection[$colkey]); 928 $this->collection = array_values($this->collection); 929 break; 930 } 931 } 932 unset($this->orderedcollection[$child->type][$child->key]); 933 $this->count--; 934 return true; 935 } 936 return false; 937 } 938 939 /** 940 * Gets the number of nodes in this collection 941 * 942 * This option uses an internal count rather than counting the actual options to avoid 943 * a performance hit through the count function. 944 * 945 * @return int 946 */ 947 public function count() { 948 return $this->count; 949 } 950 /** 951 * Gets an array iterator for the collection. 952 * 953 * This is required by the IteratorAggregator interface and is used by routines 954 * such as the foreach loop. 955 * 956 * @return ArrayIterator 957 */ 958 public function getIterator() { 959 return new ArrayIterator($this->collection); 960 } 961 } 962 963 /** 964 * The global navigation class used for... the global navigation 965 * 966 * This class is used by PAGE to store the global navigation for the site 967 * and is then used by the settings nav and navbar to save on processing and DB calls 968 * 969 * See 970 * {@link lib/pagelib.php} {@link moodle_page::initialise_theme_and_output()} 971 * {@link lib/ajax/getnavbranch.php} Called by ajax 972 * 973 * @package core 974 * @category navigation 975 * @copyright 2009 Sam Hemelryk 976 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 977 */ 978 class global_navigation extends navigation_node { 979 /** @var moodle_page The Moodle page this navigation object belongs to. */ 980 protected $page; 981 /** @var bool switch to let us know if the navigation object is initialised*/ 982 protected $initialised = false; 983 /** @var array An array of course information */ 984 protected $mycourses = array(); 985 /** @var navigation_node[] An array for containing root navigation nodes */ 986 protected $rootnodes = array(); 987 /** @var bool A switch for whether to show empty sections in the navigation */ 988 protected $showemptysections = true; 989 /** @var bool A switch for whether courses should be shown within categories on the navigation. */ 990 protected $showcategories = null; 991 /** @var null@var bool A switch for whether or not to show categories in the my courses branch. */ 992 protected $showmycategories = null; 993 /** @var array An array of stdClasses for users that the navigation is extended for */ 994 protected $extendforuser = array(); 995 /** @var navigation_cache */ 996 protected $cache; 997 /** @var array An array of course ids that are present in the navigation */ 998 protected $addedcourses = array(); 999 /** @var bool */ 1000 protected $allcategoriesloaded = false; 1001 /** @var array An array of category ids that are included in the navigation */ 1002 protected $addedcategories = array(); 1003 /** @var int expansion limit */ 1004 protected $expansionlimit = 0; 1005 /** @var int userid to allow parent to see child's profile page navigation */ 1006 protected $useridtouseforparentchecks = 0; 1007 /** @var cache_session A cache that stores information on expanded courses */ 1008 protected $cacheexpandcourse = null; 1009 1010 /** Used when loading categories to load all top level categories [parent = 0] **/ 1011 const LOAD_ROOT_CATEGORIES = 0; 1012 /** Used when loading categories to load all categories **/ 1013 const LOAD_ALL_CATEGORIES = -1; 1014 1015 /** 1016 * Constructs a new global navigation 1017 * 1018 * @param moodle_page $page The page this navigation object belongs to 1019 */ 1020 public function __construct(moodle_page $page) { 1021 global $CFG, $SITE, $USER; 1022 1023 if (during_initial_install()) { 1024 return; 1025 } 1026 1027 if (get_home_page() == HOMEPAGE_SITE) { 1028 // We are using the site home for the root element 1029 $properties = array( 1030 'key' => 'home', 1031 'type' => navigation_node::TYPE_SYSTEM, 1032 'text' => get_string('home'), 1033 'action' => new moodle_url('/') 1034 ); 1035 } else { 1036 // We are using the users my moodle for the root element 1037 $properties = array( 1038 'key' => 'myhome', 1039 'type' => navigation_node::TYPE_SYSTEM, 1040 'text' => get_string('myhome'), 1041 'action' => new moodle_url('/my/') 1042 ); 1043 } 1044 1045 // Use the parents constructor.... good good reuse 1046 parent::__construct($properties); 1047 1048 // Initalise and set defaults 1049 $this->page = $page; 1050 $this->forceopen = true; 1051 $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME); 1052 } 1053 1054 /** 1055 * Mutator to set userid to allow parent to see child's profile 1056 * page navigation. See MDL-25805 for initial issue. Linked to it 1057 * is an issue explaining why this is a REALLY UGLY HACK thats not 1058 * for you to use! 1059 * 1060 * @param int $userid userid of profile page that parent wants to navigate around. 1061 */ 1062 public function set_userid_for_parent_checks($userid) { 1063 $this->useridtouseforparentchecks = $userid; 1064 } 1065 1066 1067 /** 1068 * Initialises the navigation object. 1069 * 1070 * This causes the navigation object to look at the current state of the page 1071 * that it is associated with and then load the appropriate content. 1072 * 1073 * This should only occur the first time that the navigation structure is utilised 1074 * which will normally be either when the navbar is called to be displayed or 1075 * when a block makes use of it. 1076 * 1077 * @return bool 1078 */ 1079 public function initialise() { 1080 global $CFG, $SITE, $USER; 1081 // Check if it has already been initialised 1082 if ($this->initialised || during_initial_install()) { 1083 return true; 1084 } 1085 $this->initialised = true; 1086 1087 // Set up the five base root nodes. These are nodes where we will put our 1088 // content and are as follows: 1089 // site: Navigation for the front page. 1090 // myprofile: User profile information goes here. 1091 // currentcourse: The course being currently viewed. 1092 // mycourses: The users courses get added here. 1093 // courses: Additional courses are added here. 1094 // users: Other users information loaded here. 1095 $this->rootnodes = array(); 1096 if (get_home_page() == HOMEPAGE_SITE) { 1097 // The home element should be my moodle because the root element is the site 1098 if (isloggedin() && !isguestuser()) { // Makes no sense if you aren't logged in 1099 $this->rootnodes['home'] = $this->add(get_string('myhome'), new moodle_url('/my/'), self::TYPE_SETTING, null, 'home'); 1100 } 1101 } else { 1102 // The home element should be the site because the root node is my moodle 1103 $this->rootnodes['home'] = $this->add(get_string('sitehome'), new moodle_url('/'), self::TYPE_SETTING, null, 'home'); 1104 if (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_MY)) { 1105 // We need to stop automatic redirection 1106 $this->rootnodes['home']->action->param('redirect', '0'); 1107 } 1108 } 1109 $this->rootnodes['site'] = $this->add_course($SITE); 1110 $this->rootnodes['myprofile'] = $this->add(get_string('profile'), null, self::TYPE_USER, null, 'myprofile'); 1111 $this->rootnodes['currentcourse'] = $this->add(get_string('currentcourse'), null, self::TYPE_ROOTNODE, null, 'currentcourse'); 1112 $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), null, self::TYPE_ROOTNODE, null, 'mycourses'); 1113 $this->rootnodes['courses'] = $this->add(get_string('courses'), new moodle_url('/course/index.php'), self::TYPE_ROOTNODE, null, 'courses'); 1114 $this->rootnodes['users'] = $this->add(get_string('users'), null, self::TYPE_ROOTNODE, null, 'users'); 1115 1116 // We always load the frontpage course to ensure it is available without 1117 // JavaScript enabled. 1118 $this->add_front_page_course_essentials($this->rootnodes['site'], $SITE); 1119 $this->load_course_sections($SITE, $this->rootnodes['site']); 1120 1121 $course = $this->page->course; 1122 1123 // $issite gets set to true if the current pages course is the sites frontpage course 1124 $issite = ($this->page->course->id == $SITE->id); 1125 // Determine if the user is enrolled in any course. 1126 $enrolledinanycourse = enrol_user_sees_own_courses(); 1127 1128 $this->rootnodes['currentcourse']->mainnavonly = true; 1129 if ($enrolledinanycourse) { 1130 $this->rootnodes['mycourses']->isexpandable = true; 1131 if ($CFG->navshowallcourses) { 1132 // When we show all courses we need to show both the my courses and the regular courses branch. 1133 $this->rootnodes['courses']->isexpandable = true; 1134 } 1135 } else { 1136 $this->rootnodes['courses']->isexpandable = true; 1137 } 1138 1139 // Load the users enrolled courses if they are viewing the My Moodle page AND the admin has not 1140 // set that they wish to keep the My Courses branch collapsed by default. 1141 if (!empty($CFG->navexpandmycourses) && $this->page->pagelayout === 'mydashboard'){ 1142 $this->rootnodes['mycourses']->forceopen = true; 1143 $this->load_courses_enrolled(); 1144 } else { 1145 $this->rootnodes['mycourses']->collapse = true; 1146 $this->rootnodes['mycourses']->make_inactive(); 1147 } 1148 1149 $canviewcourseprofile = true; 1150 1151 // Next load context specific content into the navigation 1152 switch ($this->page->context->contextlevel) { 1153 case CONTEXT_SYSTEM : 1154 // Nothing left to do here I feel. 1155 break; 1156 case CONTEXT_COURSECAT : 1157 // This is essential, we must load categories. 1158 $this->load_all_categories($this->page->context->instanceid, true); 1159 break; 1160 case CONTEXT_BLOCK : 1161 case CONTEXT_COURSE : 1162 if ($issite) { 1163 // Nothing left to do here. 1164 break; 1165 } 1166 1167 // Load the course associated with the current page into the navigation. 1168 $coursenode = $this->add_course($course, false, self::COURSE_CURRENT); 1169 // If the course wasn't added then don't try going any further. 1170 if (!$coursenode) { 1171 $canviewcourseprofile = false; 1172 break; 1173 } 1174 1175 // If the user is not enrolled then we only want to show the 1176 // course node and not populate it. 1177 1178 // Not enrolled, can't view, and hasn't switched roles 1179 if (!can_access_course($course, null, '', true)) { 1180 if ($coursenode->isexpandable === true) { 1181 // Obviously the situation has changed, update the cache and adjust the node. 1182 // This occurs if the user access to a course has been revoked (one way or another) after 1183 // initially logging in for this session. 1184 $this->get_expand_course_cache()->set($course->id, 1); 1185 $coursenode->isexpandable = true; 1186 $coursenode->nodetype = self::NODETYPE_BRANCH; 1187 } 1188 // Very ugly hack - do not force "parents" to enrol into course their child is enrolled in, 1189 // this hack has been propagated from user/view.php to display the navigation node. (MDL-25805) 1190 if (!$this->current_user_is_parent_role()) { 1191 $coursenode->make_active(); 1192 $canviewcourseprofile = false; 1193 break; 1194 } 1195 } else if ($coursenode->isexpandable === false) { 1196 // Obviously the situation has changed, update the cache and adjust the node. 1197 // This occurs if the user has been granted access to a course (one way or another) after initially 1198 // logging in for this session. 1199 $this->get_expand_course_cache()->set($course->id, 1); 1200 $coursenode->isexpandable = true; 1201 $coursenode->nodetype = self::NODETYPE_BRANCH; 1202 } 1203 1204 // Add the essentials such as reports etc... 1205 $this->add_course_essentials($coursenode, $course); 1206 // Extend course navigation with it's sections/activities 1207 $this->load_course_sections($course, $coursenode); 1208 if (!$coursenode->contains_active_node() && !$coursenode->search_for_active_node()) { 1209 $coursenode->make_active(); 1210 } 1211 1212 break; 1213 case CONTEXT_MODULE : 1214 if ($issite) { 1215 // If this is the site course then most information will have 1216 // already been loaded. 1217 // However we need to check if there is more content that can 1218 // yet be loaded for the specific module instance. 1219 $activitynode = $this->rootnodes['site']->find($this->page->cm->id, navigation_node::TYPE_ACTIVITY); 1220 if ($activitynode) { 1221 $this->load_activity($this->page->cm, $this->page->course, $activitynode); 1222 } 1223 break; 1224 } 1225 1226 $course = $this->page->course; 1227 $cm = $this->page->cm; 1228 1229 // Load the course associated with the page into the navigation 1230 $coursenode = $this->add_course($course, false, self::COURSE_CURRENT); 1231 1232 // If the course wasn't added then don't try going any further. 1233 if (!$coursenode) { 1234 $canviewcourseprofile = false; 1235 break; 1236 } 1237 1238 // If the user is not enrolled then we only want to show the 1239 // course node and not populate it. 1240 if (!can_access_course($course, null, '', true)) { 1241 $coursenode->make_active(); 1242 $canviewcourseprofile = false; 1243 break; 1244 } 1245 1246 $this->add_course_essentials($coursenode, $course); 1247 1248 // Load the course sections into the page 1249 $this->load_course_sections($course, $coursenode, null, $cm); 1250 $activity = $coursenode->find($cm->id, navigation_node::TYPE_ACTIVITY); 1251 if (!empty($activity)) { 1252 // Finally load the cm specific navigaton information 1253 $this->load_activity($cm, $course, $activity); 1254 // Check if we have an active ndoe 1255 if (!$activity->contains_active_node() && !$activity->search_for_active_node()) { 1256 // And make the activity node active. 1257 $activity->make_active(); 1258 } 1259 } 1260 break; 1261 case CONTEXT_USER : 1262 if ($issite) { 1263 // The users profile information etc is already loaded 1264 // for the front page. 1265 break; 1266 } 1267 $course = $this->page->course; 1268 // Load the course associated with the user into the navigation 1269 $coursenode = $this->add_course($course, false, self::COURSE_CURRENT); 1270 1271 // If the course wasn't added then don't try going any further. 1272 if (!$coursenode) { 1273 $canviewcourseprofile = false; 1274 break; 1275 } 1276 1277 // If the user is not enrolled then we only want to show the 1278 // course node and not populate it. 1279 if (!can_access_course($course, null, '', true)) { 1280 $coursenode->make_active(); 1281 $canviewcourseprofile = false; 1282 break; 1283 } 1284 $this->add_course_essentials($coursenode, $course); 1285 $this->load_course_sections($course, $coursenode); 1286 break; 1287 } 1288 1289 // Load for the current user 1290 $this->load_for_user(); 1291 if ($this->page->context->contextlevel >= CONTEXT_COURSE && $this->page->context->instanceid != $SITE->id && $canviewcourseprofile) { 1292 $this->load_for_user(null, true); 1293 } 1294 // Load each extending user into the navigation. 1295 foreach ($this->extendforuser as $user) { 1296 if ($user->id != $USER->id) { 1297 $this->load_for_user($user); 1298 } 1299 } 1300 1301 // Give the local plugins a chance to include some navigation if they want. 1302 foreach (get_plugin_list_with_function('local', 'extend_navigation') as $function) { 1303 $function($this); 1304 } 1305 1306 // Remove any empty root nodes 1307 foreach ($this->rootnodes as $node) { 1308 // Dont remove the home node 1309 /** @var navigation_node $node */ 1310 if ($node->key !== 'home' && !$node->has_children() && !$node->isactive) { 1311 $node->remove(); 1312 } 1313 } 1314 1315 if (!$this->contains_active_node()) { 1316 $this->search_for_active_node(); 1317 } 1318 1319 // If the user is not logged in modify the navigation structure as detailed 1320 // in {@link http://docs.moodle.org/dev/Navigation_2.0_structure} 1321 if (!isloggedin()) { 1322 $activities = clone($this->rootnodes['site']->children); 1323 $this->rootnodes['site']->remove(); 1324 $children = clone($this->children); 1325 $this->children = new navigation_node_collection(); 1326 foreach ($activities as $child) { 1327 $this->children->add($child); 1328 } 1329 foreach ($children as $child) { 1330 $this->children->add($child); 1331 } 1332 } 1333 return true; 1334 } 1335 1336 /** 1337 * Returns true if the current user is a parent of the user being currently viewed. 1338 * 1339 * If the current user is not viewing another user, or if the current user does not hold any parent roles over the 1340 * other user being viewed this function returns false. 1341 * In order to set the user for whom we are checking against you must call {@link set_userid_for_parent_checks()} 1342 * 1343 * @since Moodle 2.4 1344 * @return bool 1345 */ 1346 protected function current_user_is_parent_role() { 1347 global $USER, $DB; 1348 if ($this->useridtouseforparentchecks && $this->useridtouseforparentchecks != $USER->id) { 1349 $usercontext = context_user::instance($this->useridtouseforparentchecks, MUST_EXIST); 1350 if (!has_capability('moodle/user:viewdetails', $usercontext)) { 1351 return false; 1352 } 1353 if ($DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id))) { 1354 return true; 1355 } 1356 } 1357 return false; 1358 } 1359 1360 /** 1361 * Returns true if courses should be shown within categories on the navigation. 1362 * 1363 * @param bool $ismycourse Set to true if you are calculating this for a course. 1364 * @return bool 1365 */ 1366 protected function show_categories($ismycourse = false) { 1367 global $CFG, $DB; 1368 if ($ismycourse) { 1369 return $this->show_my_categories(); 1370 } 1371 if ($this->showcategories === null) { 1372 $show = false; 1373 if ($this->page->context->contextlevel == CONTEXT_COURSECAT) { 1374 $show = true; 1375 } else if (!empty($CFG->navshowcategories) && $DB->count_records('course_categories') > 1) { 1376 $show = true; 1377 } 1378 $this->showcategories = $show; 1379 } 1380 return $this->showcategories; 1381 } 1382 1383 /** 1384 * Returns true if we should show categories in the My Courses branch. 1385 * @return bool 1386 */ 1387 protected function show_my_categories() { 1388 global $CFG, $DB; 1389 if ($this->showmycategories === null) { 1390 $this->showmycategories = !empty($CFG->navshowmycoursecategories) && $DB->count_records('course_categories') > 1; 1391 } 1392 return $this->showmycategories; 1393 } 1394 1395 /** 1396 * Loads the courses in Moodle into the navigation. 1397 * 1398 * @global moodle_database $DB 1399 * @param string|array $categoryids An array containing categories to load courses 1400 * for, OR null to load courses for all categories. 1401 * @return array An array of navigation_nodes one for each course 1402 */ 1403 protected function load_all_courses($categoryids = null) { 1404 global $CFG, $DB, $SITE; 1405 1406 // Work out the limit of courses. 1407 $limit = 20; 1408 if (!empty($CFG->navcourselimit)) { 1409 $limit = $CFG->navcourselimit; 1410 } 1411 1412 $toload = (empty($CFG->navshowallcourses))?self::LOAD_ROOT_CATEGORIES:self::LOAD_ALL_CATEGORIES; 1413 1414 // If we are going to show all courses AND we are showing categories then 1415 // to save us repeated DB calls load all of the categories now 1416 if ($this->show_categories()) { 1417 $this->load_all_categories($toload); 1418 } 1419 1420 // Will be the return of our efforts 1421 $coursenodes = array(); 1422 1423 // Check if we need to show categories. 1424 if ($this->show_categories()) { 1425 // Hmmm we need to show categories... this is going to be painful. 1426 // We now need to fetch up to $limit courses for each category to 1427 // be displayed. 1428 if ($categoryids !== null) { 1429 if (!is_array($categoryids)) { 1430 $categoryids = array($categoryids); 1431 } 1432 list($categorywhere, $categoryparams) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'cc'); 1433 $categorywhere = 'WHERE cc.id '.$categorywhere; 1434 } else if ($toload == self::LOAD_ROOT_CATEGORIES) { 1435 $categorywhere = 'WHERE cc.depth = 1 OR cc.depth = 2'; 1436 $categoryparams = array(); 1437 } else { 1438 $categorywhere = ''; 1439 $categoryparams = array(); 1440 } 1441 1442 // First up we are going to get the categories that we are going to 1443 // need so that we can determine how best to load the courses from them. 1444 $sql = "SELECT cc.id, COUNT(c.id) AS coursecount 1445 FROM {course_categories} cc 1446 LEFT JOIN {course} c ON c.category = cc.id 1447 {$categorywhere} 1448 GROUP BY cc.id"; 1449 $categories = $DB->get_recordset_sql($sql, $categoryparams); 1450 $fullfetch = array(); 1451 $partfetch = array(); 1452 foreach ($categories as $category) { 1453 if (!$this->can_add_more_courses_to_category($category->id)) { 1454 continue; 1455 } 1456 if ($category->coursecount > $limit * 5) { 1457 $partfetch[] = $category->id; 1458 } else if ($category->coursecount > 0) { 1459 $fullfetch[] = $category->id; 1460 } 1461 } 1462 $categories->close(); 1463 1464 if (count($fullfetch)) { 1465 // First up fetch all of the courses in categories where we know that we are going to 1466 // need the majority of courses. 1467 list($categoryids, $categoryparams) = $DB->get_in_or_equal($fullfetch, SQL_PARAMS_NAMED, 'lcategory'); 1468 $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx'); 1469 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)"; 1470 $categoryparams['contextlevel'] = CONTEXT_COURSE; 1471 $sql = "SELECT c.id, c.sortorder, c.visible, c.fullname, c.shortname, c.category $ccselect 1472 FROM {course} c 1473 $ccjoin 1474 WHERE c.category {$categoryids} 1475 ORDER BY c.sortorder ASC"; 1476 $coursesrs = $DB->get_recordset_sql($sql, $categoryparams); 1477 foreach ($coursesrs as $course) { 1478 if ($course->id == $SITE->id) { 1479 // This should not be necessary, frontpage is not in any category. 1480 continue; 1481 } 1482 if (array_key_exists($course->id, $this->addedcourses)) { 1483 // It is probably better to not include the already loaded courses 1484 // directly in SQL because inequalities may confuse query optimisers 1485 // and may interfere with query caching. 1486 continue; 1487 } 1488 if (!$this->can_add_more_courses_to_category($course->category)) { 1489 continue; 1490 } 1491 context_helper::preload_from_record($course); 1492 if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) { 1493 continue; 1494 } 1495 $coursenodes[$course->id] = $this->add_course($course); 1496 } 1497 $coursesrs->close(); 1498 } 1499 1500 if (count($partfetch)) { 1501 // Next we will work our way through the categories where we will likely only need a small 1502 // proportion of the courses. 1503 foreach ($partfetch as $categoryid) { 1504 $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx'); 1505 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)"; 1506 $sql = "SELECT c.id, c.sortorder, c.visible, c.fullname, c.shortname, c.category $ccselect 1507 FROM {course} c 1508 $ccjoin 1509 WHERE c.category = :categoryid 1510 ORDER BY c.sortorder ASC"; 1511 $courseparams = array('categoryid' => $categoryid, 'contextlevel' => CONTEXT_COURSE); 1512 $coursesrs = $DB->get_recordset_sql($sql, $courseparams, 0, $limit * 5); 1513 foreach ($coursesrs as $course) { 1514 if ($course->id == $SITE->id) { 1515 // This should not be necessary, frontpage is not in any category. 1516 continue; 1517 } 1518 if (array_key_exists($course->id, $this->addedcourses)) { 1519 // It is probably better to not include the already loaded courses 1520 // directly in SQL because inequalities may confuse query optimisers 1521 // and may interfere with query caching. 1522 // This also helps to respect expected $limit on repeated executions. 1523 continue; 1524 } 1525 if (!$this->can_add_more_courses_to_category($course->category)) { 1526 break; 1527 } 1528 context_helper::preload_from_record($course); 1529 if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) { 1530 continue; 1531 } 1532 $coursenodes[$course->id] = $this->add_course($course); 1533 } 1534 $coursesrs->close(); 1535 } 1536 } 1537 } else { 1538 // Prepare the SQL to load the courses and their contexts 1539 list($courseids, $courseparams) = $DB->get_in_or_equal(array_keys($this->addedcourses), SQL_PARAMS_NAMED, 'lc', false); 1540 $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx'); 1541 $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)"; 1542 $courseparams['contextlevel'] = CONTEXT_COURSE; 1543 $sql = "SELECT c.id, c.sortorder, c.visible, c.fullname, c.shortname, c.category $ccselect 1544 FROM {course} c 1545 $ccjoin 1546 WHERE c.id {$courseids} 1547 ORDER BY c.sortorder ASC"; 1548 $coursesrs = $DB->get_recordset_sql($sql, $courseparams); 1549 foreach ($coursesrs as $course) { 1550 if ($course->id == $SITE->id) { 1551 // frotpage is not wanted here 1552 continue; 1553 } 1554 if ($this->page->course && ($this->page->course->id == $course->id)) { 1555 // Don't include the currentcourse in this nodelist - it's displayed in the Current course node 1556 continue; 1557 } 1558 context_helper::preload_from_record($course); 1559 if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) { 1560 continue; 1561 } 1562 $coursenodes[$course->id] = $this->add_course($course); 1563 if (count($coursenodes) >= $limit) { 1564 break; 1565 } 1566 } 1567 $coursesrs->close(); 1568 } 1569 1570 return $coursenodes; 1571 } 1572 1573 /** 1574 * Returns true if more courses can be added to the provided category. 1575 * 1576 * @param int|navigation_node|stdClass $category 1577 * @return bool 1578 */ 1579 protected function can_add_more_courses_to_category($category) { 1580 global $CFG; 1581 $limit = 20; 1582 if (!empty($CFG->navcourselimit)) { 1583 $limit = (int)$CFG->navcourselimit; 1584 } 1585 if (is_numeric($category)) { 1586 if (!array_key_exists($category, $this->addedcategories)) { 1587 return true; 1588 } 1589 $coursecount = count($this->addedcategories[$category]->children->type(self::TYPE_COURSE)); 1590 } else if ($category instanceof navigation_node) { 1591 if (($category->type != self::TYPE_CATEGORY) || ($category->type != self::TYPE_MY_CATEGORY)) { 1592 return false; 1593 } 1594 $coursecount = count($category->children->type(self::TYPE_COURSE)); 1595 } else if (is_object($category) && property_exists($category,'id')) { 1596 $coursecount = count($this->addedcategories[$category->id]->children->type(self::TYPE_COURSE)); 1597 } 1598 return ($coursecount <= $limit); 1599 } 1600 1601 /** 1602 * Loads all categories (top level or if an id is specified for that category) 1603 * 1604 * @param int $categoryid The category id to load or null/0 to load all base level categories 1605 * @param bool $showbasecategories If set to true all base level categories will be loaded as well 1606 * as the requested category and any parent categories. 1607 * @return navigation_node|void returns a navigation node if a category has been loaded. 1608 */ 1609 protected function load_all_categories($categoryid = self::LOAD_ROOT_CATEGORIES, $showbasecategories = false) { 1610 global $CFG, $DB; 1611 1612 // Check if this category has already been loaded 1613 if ($this->allcategoriesloaded || ($categoryid < 1 && $this->is_category_fully_loaded($categoryid))) { 1614 return true; 1615 } 1616 1617 $catcontextsql = context_helper::get_preload_record_columns_sql('ctx'); 1618 $sqlselect = "SELECT cc.*, $catcontextsql 1619 FROM {course_categories} cc 1620 JOIN {context} ctx ON cc.id = ctx.instanceid"; 1621 $sqlwhere = "WHERE ctx.contextlevel = ".CONTEXT_COURSECAT; 1622 $sqlorder = "ORDER BY cc.depth ASC, cc.sortorder ASC, cc.id ASC"; 1623 $params = array(); 1624 1625 $categoriestoload = array(); 1626 if ($categoryid == self::LOAD_ALL_CATEGORIES) { 1627 // We are going to load all categories regardless... prepare to fire 1628 // on the database server! 1629 } else if ($categoryid == self::LOAD_ROOT_CATEGORIES) { // can be 0 1630 // We are going to load all of the first level categories (categories without parents) 1631 $sqlwhere .= " AND cc.parent = 0"; 1632 } else if (array_key_exists($categoryid, $this->addedcategories)) { 1633 // The category itself has been loaded already so we just need to ensure its subcategories 1634 // have been loaded 1635 $addedcategories = $this->addedcategories; 1636 unset($addedcategories[$categoryid]); 1637 if (count($addedcategories) > 0) { 1638 list($sql, $params) = $DB->get_in_or_equal(array_keys($addedcategories), SQL_PARAMS_NAMED, 'parent', false); 1639 if ($showbasecategories) { 1640 // We need to include categories with parent = 0 as well 1641 $sqlwhere .= " AND (cc.parent = :categoryid OR cc.parent = 0) AND cc.parent {$sql}"; 1642 } else { 1643 // All we need is categories that match the parent 1644 $sqlwhere .= " AND cc.parent = :categoryid AND cc.parent {$sql}"; 1645 } 1646 } 1647 $params['categoryid'] = $categoryid; 1648 } else { 1649 // This category hasn't been loaded yet so we need to fetch it, work out its category path 1650 // and load this category plus all its parents and subcategories 1651 $category = $DB->get_record('course_categories', array('id' => $categoryid), 'path', MUST_EXIST); 1652 $categoriestoload = explode('/', trim($category->path, '/')); 1653 list($select, $params) = $DB->get_in_or_equal($categoriestoload); 1654 // We are going to use select twice so double the params 1655 $params = array_merge($params, $params); 1656 $basecategorysql = ($showbasecategories)?' OR cc.depth = 1':''; 1657 $sqlwhere .= " AND (cc.id {$select} OR cc.parent {$select}{$basecategorysql})"; 1658 } 1659 1660 $categoriesrs = $DB->get_recordset_sql("$sqlselect $sqlwhere $sqlorder", $params); 1661 $categories = array(); 1662 foreach ($categoriesrs as $category) { 1663 // Preload the context.. we'll need it when adding the category in order 1664 // to format the category name. 1665 context_helper::preload_from_record($category); 1666 if (array_key_exists($category->id, $this->addedcategories)) { 1667 // Do nothing, its already been added. 1668 } else if ($category->parent == '0') { 1669 // This is a root category lets add it immediately 1670 $this->add_category($category, $this->rootnodes['courses']); 1671 } else if (array_key_exists($category->parent, $this->addedcategories)) { 1672 // This categories parent has already been added we can add this immediately 1673 $this->add_category($category, $this->addedcategories[$category->parent]); 1674 } else { 1675 $categories[] = $category; 1676 } 1677 } 1678 $categoriesrs->close(); 1679 1680 // Now we have an array of categories we need to add them to the navigation. 1681 while (!empty($categories)) { 1682 $category = reset($categories); 1683 if (array_key_exists($category->id, $this->addedcategories)) { 1684 // Do nothing 1685 } else if ($category->parent == '0') { 1686 $this->add_category($category, $this->rootnodes['courses']); 1687 } else if (array_key_exists($category->parent, $this->addedcategories)) { 1688 $this->add_category($category, $this->addedcategories[$category->parent]); 1689 } else { 1690 // This category isn't in the navigation and niether is it's parent (yet). 1691 // We need to go through the category path and add all of its components in order. 1692 $path = explode('/', trim($category->path, '/')); 1693 foreach ($path as $catid) { 1694 if (!array_key_exists($catid, $this->addedcategories)) { 1695 // This category isn't in the navigation yet so add it. 1696 $subcategory = $categories[$catid]; 1697 if ($subcategory->parent == '0') { 1698 // Yay we have a root category - this likely means we will now be able 1699 // to add categories without problems. 1700 $this->add_category($subcategory, $this->rootnodes['courses']); 1701 } else if (array_key_exists($subcategory->parent, $this->addedcategories)) { 1702 // The parent is in the category (as we'd expect) so add it now. 1703 $this->add_category($subcategory, $this->addedcategories[$subcategory->parent]); 1704 // Remove the category from the categories array. 1705 unset($categories[$catid]); 1706 } else { 1707 // We should never ever arrive here - if we have then there is a bigger 1708 // problem at hand. 1709 throw new coding_exception('Category path order is incorrect and/or there are missing categories'); 1710 } 1711 } 1712 } 1713 } 1714 // Remove the category from the categories array now that we know it has been added. 1715 unset($categories[$category->id]); 1716 } 1717 if ($categoryid === self::LOAD_ALL_CATEGORIES) { 1718 $this->allcategoriesloaded = true; 1719 } 1720 // Check if there are any categories to load. 1721 if (count($categoriestoload) > 0) { 1722 $readytoloadcourses = array(); 1723 foreach ($categoriestoload as $category) { 1724 if ($this->can_add_more_courses_to_category($category)) { 1725 $readytoloadcourses[] = $category; 1726 } 1727 } 1728 if (count($readytoloadcourses)) { 1729 $this->load_all_courses($readytoloadcourses); 1730 } 1731 } 1732 1733 // Look for all categories which have been loaded 1734 if (!empty($this->addedcategories)) { 1735 $categoryids = array(); 1736 foreach ($this->addedcategories as $category) { 1737 if ($this->can_add_more_courses_to_category($category)) { 1738 $categoryids[] = $category->key; 1739 } 1740 } 1741 if ($categoryids) { 1742 list($categoriessql, $params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED); 1743 $params['limit'] = (!empty($CFG->navcourselimit))?$CFG->navcourselimit:20; 1744 $sql = "SELECT cc.id, COUNT(c.id) AS coursecount 1745 FROM {course_categories} cc 1746 JOIN {course} c ON c.category = cc.id 1747 WHERE cc.id {$categoriessql} 1748 GROUP BY cc.id 1749 HAVING COUNT(c.id) > :limit"; 1750 $excessivecategories = $DB->get_records_sql($sql, $params); 1751 foreach ($categories as &$category) { 1752 if (array_key_exists($category->key, $excessivecategories) && !$this->can_add_more_courses_to_category($category)) { 1753 $url = new moodle_url('/course/index.php', array('categoryid' => $category->key)); 1754 $category->add(get_string('viewallcourses'), $url, self::TYPE_SETTING); 1755 } 1756 } 1757 } 1758 } 1759 } 1760 1761 /** 1762 * Adds a structured category to the navigation in the correct order/place 1763 * 1764 * @param stdClass $category category to be added in navigation. 1765 * @param navigation_node $parent parent navigation node 1766 * @param int $nodetype type of node, if category is under MyHome then it's TYPE_MY_CATEGORY 1767 * @return void. 1768 */ 1769 protected function add_category(stdClass $category, navigation_node $parent, $nodetype = self::TYPE_CATEGORY) { 1770 if (array_key_exists($category->id, $this->addedcategories)) { 1771 return; 1772 } 1773 $url = new moodle_url('/course/index.php', array('categoryid' => $category->id)); 1774 $context = context_coursecat::instance($category->id); 1775 $categoryname = format_string($category->name, true, array('context' => $context)); 1776 $categorynode = $parent->add($categoryname, $url, $nodetype, $categoryname, $category->id); 1777 if (empty($category->visible)) { 1778 if (has_capability('moodle/category:viewhiddencategories', context_system::instance())) { 1779 $categorynode->hidden = true; 1780 } else { 1781 $categorynode->display = false; 1782 } 1783 } 1784 $this->addedcategories[$category->id] = $categorynode; 1785 } 1786 1787 /** 1788 * Loads the given course into the navigation 1789 * 1790 * @param stdClass $course 1791 * @return navigation_node 1792 */ 1793 protected function load_course(stdClass $course) { 1794 global $SITE; 1795 if ($course->id == $SITE->id) { 1796 // This is always loaded during initialisation 1797 return $this->rootnodes['site']; 1798 } else if (array_key_exists($course->id, $this->addedcourses)) { 1799 // The course has already been loaded so return a reference 1800 return $this->addedcourses[$course->id]; 1801 } else { 1802 // Add the course 1803 return $this->add_course($course); 1804 } 1805 } 1806 1807 /** 1808 * Loads all of the courses section into the navigation. 1809 * 1810 * This function calls method from current course format, see 1811 * {@link format_base::extend_course_navigation()} 1812 * If course module ($cm) is specified but course format failed to create the node, 1813 * the activity node is created anyway. 1814 * 1815 * By default course formats call the method {@link global_navigation::load_generic_course_sections()} 1816 * 1817 * @param stdClass $course Database record for the course 1818 * @param navigation_node $coursenode The course node within the navigation 1819 * @param null|int $sectionnum If specified load the contents of section with this relative number 1820 * @param null|cm_info $cm If specified make sure that activity node is created (either 1821 * in containg section or by calling load_stealth_activity() ) 1822 */ 1823 protected function load_course_sections(stdClass $course, navigation_node $coursenode, $sectionnum = null, $cm = null) { 1824 global $CFG, $SITE; 1825 require_once($CFG->dirroot.'/course/lib.php'); 1826 if (isset($cm->sectionnum)) { 1827 $sectionnum = $cm->sectionnum; 1828 } 1829 if ($sectionnum !== null) { 1830 $this->includesectionnum = $sectionnum; 1831 } 1832 course_get_format($course)->extend_course_navigation($this, $coursenode, $sectionnum, $cm); 1833 if (isset($cm->id)) { 1834 $activity = $coursenode->find($cm->id, self::TYPE_ACTIVITY); 1835 if (empty($activity)) { 1836 $activity = $this->load_stealth_activity($coursenode, get_fast_modinfo($course)); 1837 } 1838 } 1839 } 1840 1841 /** 1842 * Generates an array of sections and an array of activities for the given course. 1843 * 1844 * This method uses the cache to improve performance and avoid the get_fast_modinfo call 1845 * 1846 * @param stdClass $course 1847 * @return array Array($sections, $activities) 1848 */ 1849 protected function generate_sections_and_activities(stdClass $course) { 1850 global $CFG; 1851 require_once($CFG->dirroot.'/course/lib.php'); 1852 1853 $modinfo = get_fast_modinfo($course); 1854 $sections = $modinfo->get_section_info_all(); 1855 1856 // For course formats using 'numsections' trim the sections list 1857 $courseformatoptions = course_get_format($course)->get_format_options(); 1858 if (isset($courseformatoptions['numsections'])) { 1859 $sections = array_slice($sections, 0, $courseformatoptions['numsections']+1, true); 1860 } 1861 1862 $activities = array(); 1863 1864 foreach ($sections as $key => $section) { 1865 // Clone and unset summary to prevent $SESSION bloat (MDL-31802). 1866 $sections[$key] = clone($section); 1867 unset($sections[$key]->summary); 1868 $sections[$key]->hasactivites = false; 1869 if (!array_key_exists($section->section, $modinfo->sections)) { 1870 continue; 1871 } 1872 foreach ($modinfo->sections[$section->section] as $cmid) { 1873 $cm = $modinfo->cms[$cmid]; 1874 $activity = new stdClass; 1875 $activity->id = $cm->id; 1876 $activity->course = $course->id; 1877 $activity->section = $section->section; 1878 $activity->name = $cm->name; 1879 $activity->icon = $cm->icon; 1880 $activity->iconcomponent = $cm->iconcomponent; 1881 $activity->hidden = (!$cm->visible); 1882 $activity->modname = $cm->modname; 1883 $activity->nodetype = navigation_node::NODETYPE_LEAF; 1884 $activity->onclick = $cm->onclick; 1885 $url = $cm->url; 1886 if (!$url) { 1887 $activity->url = null; 1888 $activity->display = false; 1889 } else { 1890 $activity->url = $url->out(); 1891 $activity->display = $cm->uservisible ? true : false; 1892 if (self::module_extends_navigation($cm->modname)) { 1893 $activity->nodetype = navigation_node::NODETYPE_BRANCH; 1894 } 1895 } 1896 $activities[$cmid] = $activity; 1897 if ($activity->display) { 1898 $sections[$key]->hasactivites = true; 1899 } 1900 } 1901 } 1902 1903 return array($sections, $activities); 1904 } 1905 1906 /** 1907 * Generically loads the course sections into the course's navigation. 1908 * 1909 * @param stdClass $course 1910 * @param navigation_node $coursenode 1911 * @return array An array of course section nodes 1912 */ 1913 public function load_generic_course_sections(stdClass $course, navigation_node $coursenode) { 1914 global $CFG, $DB, $USER, $SITE; 1915 require_once($CFG->dirroot.'/course/lib.php'); 1916 1917 list($sections, $activities) = $this->generate_sections_and_activities($course); 1918 1919 $navigationsections = array(); 1920 foreach ($sections as $sectionid => $section) { 1921 $section = clone($section); 1922 if ($course->id == $SITE->id) { 1923 $this->load_section_activities($coursenode, $section->section, $activities); 1924 } else { 1925 if (!$section->uservisible || (!$this->showemptysections && 1926 !$section->hasactivites && $this->includesectionnum !== $section->section)) { 1927 continue; 1928 } 1929 1930 $sectionname = get_section_name($course, $section); 1931 $url = course_get_url($course, $section->section, array('navigation' => true)); 1932 1933 $sectionnode = $coursenode->add($sectionname, $url, navigation_node::TYPE_SECTION, null, $section->id); 1934 $sectionnode->nodetype = navigation_node::NODETYPE_BRANCH; 1935 $sectionnode->hidden = (!$section->visible || !$section->available); 1936 if ($this->includesectionnum !== false && $this->includesectionnum == $section->section) { 1937 $this->load_section_activities($sectionnode, $section->section, $activities); 1938 } 1939 $section->sectionnode = $sectionnode; 1940 $navigationsections[$sectionid] = $section; 1941 } 1942 } 1943 return $navigationsections; 1944 } 1945 1946 /** 1947 * Loads all of the activities for a section into the navigation structure. 1948 * 1949 * @param navigation_node $sectionnode 1950 * @param int $sectionnumber 1951 * @param array $activities An array of activites as returned by {@link global_navigation::generate_sections_and_activities()} 1952 * @param stdClass $course The course object the section and activities relate to. 1953 * @return array Array of activity nodes 1954 */ 1955 protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, array $activities, $course = null) { 1956 global $CFG, $SITE; 1957 // A static counter for JS function naming 1958 static $legacyonclickcounter = 0; 1959 1960 $activitynodes = array(); 1961 if (empty($activities)) { 1962 return $activitynodes; 1963 } 1964 1965 if (!is_object($course)) { 1966 $activity = reset($activities); 1967 $courseid = $activity->course; 1968 } else { 1969 $courseid = $course->id; 1970 } 1971 $showactivities = ($courseid != $SITE->id || !empty($CFG->navshowfrontpagemods)); 1972 1973 foreach ($activities as $activity) { 1974 if ($activity->section != $sectionnumber) { 1975 continue; 1976 } 1977 if ($activity->icon) { 1978 $icon = new pix_icon($activity->icon, get_string('modulename', $activity->modname), $activity->iconcomponent); 1979 } else { 1980 $icon = new pix_icon('icon', get_string('modulename', $activity->modname), $activity->modname); 1981 } 1982 1983 // Prepare the default name and url for the node 1984 $activityname = format_string($activity->name, true, array('context' => context_module::instance($activity->id))); 1985 $action = new moodle_url($activity->url); 1986 1987 // Check if the onclick property is set (puke!) 1988 if (!empty($activity->onclick)) { 1989 // Increment the counter so that we have a unique number. 1990 $legacyonclickcounter++; 1991 // Generate the function name we will use 1992 $functionname = 'legacy_activity_onclick_handler_'.$legacyonclickcounter; 1993 $propogrationhandler = ''; 1994 // Check if we need to cancel propogation. Remember inline onclick 1995 // events would return false if they wanted to prevent propogation and the 1996 // default action. 1997 if (strpos($activity->onclick, 'return false')) { 1998 $propogrationhandler = 'e.halt();'; 1999 } 2000 // Decode the onclick - it has already been encoded for display (puke) 2001 $onclick = htmlspecialchars_decode($activity->onclick, ENT_QUOTES); 2002 // Build the JS function the click event will call 2003 $jscode = "function {$functionname}(e) { $propogrationhandler $onclick }"; 2004 $this->page->requires->js_init_code($jscode); 2005 // Override the default url with the new action link 2006 $action = new action_link($action, $activityname, new component_action('click', $functionname)); 2007 } 2008 2009 $activitynode = $sectionnode->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon); 2010 $activitynode->title(get_string('modulename', $activity->modname)); 2011 $activitynode->hidden = $activity->hidden; 2012 $activitynode->display = $showactivities && $activity->display; 2013 $activitynode->nodetype = $activity->nodetype; 2014 $activitynodes[$activity->id] = $activitynode; 2015 } 2016 2017 return $activitynodes; 2018 } 2019 /** 2020 * Loads a stealth module from unavailable section 2021 * @param navigation_node $coursenode 2022 * @param stdClass $modinfo 2023 * @return navigation_node or null if not accessible 2024 */ 2025 protected function load_stealth_activity(navigation_node $coursenode, $modinfo) { 2026 if (empty($modinfo->cms[$this->page->cm->id])) { 2027 return null; 2028 } 2029 $cm = $modinfo->cms[$this->page->cm->id]; 2030 if ($cm->icon) { 2031 $icon = new pix_icon($cm->icon, get_string('modulename', $cm->modname), $cm->iconcomponent); 2032 } else { 2033 $icon = new pix_icon('icon', get_string('modulename', $cm->modname), $cm->modname); 2034 } 2035 $url = $cm->url; 2036 $activitynode = $coursenode->add(format_string($cm->name), $url, navigation_node::TYPE_ACTIVITY, null, $cm->id, $icon); 2037 $activitynode->title(get_string('modulename', $cm->modname)); 2038 $activitynode->hidden = (!$cm->visible); 2039 if (!$cm->uservisible) { 2040 // Do not show any error here, let the page handle exception that activity is not visible for the current user. 2041 // Also there may be no exception at all in case when teacher is logged in as student. 2042 $activitynode->display = false; 2043 } else if (!$url) { 2044 // Don't show activities that don't have links! 2045 $activitynode->display = false; 2046 } else if (self::module_extends_navigation($cm->modname)) { 2047 $activitynode->nodetype = navigation_node::NODETYPE_BRANCH; 2048 } 2049 return $activitynode; 2050 } 2051 /** 2052 * Loads the navigation structure for the given activity into the activities node. 2053 * 2054 * This method utilises a callback within the modules lib.php file to load the 2055 * content specific to activity given. 2056 * 2057 * The callback is a method: {modulename}_extend_navigation() 2058 * Examples: 2059 * * {@link forum_extend_navigation()} 2060 * * {@link workshop_extend_navigation()} 2061 * 2062 * @param cm_info|stdClass $cm 2063 * @param stdClass $course 2064 * @param navigation_node $activity 2065 * @return bool 2066 */ 2067 protected function load_activity($cm, stdClass $course, navigation_node $activity) { 2068 global $CFG, $DB; 2069 2070 // make sure we have a $cm from get_fast_modinfo as this contains activity access details 2071 if (!($cm instanceof cm_info)) { 2072 $modinfo = get_fast_modinfo($course); 2073 $cm = $modinfo->get_cm($cm->id); 2074 } 2075 $activity->nodetype = navigation_node::NODETYPE_LEAF; 2076 $activity->make_active(); 2077 $file = $CFG->dirroot.'/mod/'.$cm->modname.'/lib.php'; 2078 $function = $cm->modname.'_extend_navigation'; 2079 2080 if (file_exists($file)) { 2081 require_once($file); 2082 if (function_exists($function)) { 2083 $activtyrecord = $DB->get_record($cm->modname, array('id' => $cm->instance), '*', MUST_EXIST); 2084 $function($activity, $course, $activtyrecord, $cm); 2085 } 2086 } 2087 2088 // Allow the active advanced grading method plugin to append module navigation 2089 $featuresfunc = $cm->modname.'_supports'; 2090 if (function_exists($featuresfunc) && $featuresfunc(FEATURE_ADVANCED_GRADING)) { 2091 require_once($CFG->dirroot.'/grade/grading/lib.php'); 2092 $gradingman = get_grading_manager($cm->context, 'mod_'.$cm->modname); 2093 $gradingman->extend_navigation($this, $activity); 2094 } 2095 2096 return $activity->has_children(); 2097 } 2098 /** 2099 * Loads user specific information into the navigation in the appropriate place. 2100 * 2101 * If no user is provided the current user is assumed. 2102 * 2103 * @param stdClass $user 2104 * @param bool $forceforcontext probably force something to be loaded somewhere (ask SamH if not sure what this means) 2105 * @return bool 2106 */ 2107 protected function load_for_user($user=null, $forceforcontext=false) { 2108 global $DB, $CFG, $USER, $SITE; 2109 2110 if ($user === null) { 2111 // We can't require login here but if the user isn't logged in we don't 2112 // want to show anything 2113 if (!isloggedin() || isguestuser()) { 2114 return false; 2115 } 2116 $user = $USER; 2117 } else if (!is_object($user)) { 2118 // If the user is not an object then get them from the database 2119 $select = context_helper::get_preload_record_columns_sql('ctx'); 2120 $sql = "SELECT u.*, $select 2121 FROM {user} u 2122 JOIN {context} ctx ON u.id = ctx.instanceid 2123 WHERE u.id = :userid AND 2124 ctx.contextlevel = :contextlevel"; 2125 $user = $DB->get_record_sql($sql, array('userid' => (int)$user, 'contextlevel' => CONTEXT_USER), MUST_EXIST); 2126 context_helper::preload_from_record($user); 2127 } 2128 2129 $iscurrentuser = ($user->id == $USER->id); 2130 2131 $usercontext = context_user::instance($user->id); 2132 2133 // Get the course set against the page, by default this will be the site 2134 $course = $this->page->course; 2135 $baseargs = array('id'=>$user->id); 2136 if ($course->id != $SITE->id && (!$iscurrentuser || $forceforcontext)) { 2137 $coursenode = $this->add_course($course, false, self::COURSE_CURRENT); 2138 $baseargs['course'] = $course->id; 2139 $coursecontext = context_course::instance($course->id); 2140 $issitecourse = false; 2141 } else { 2142 // Load all categories and get the context for the system 2143 $coursecontext = context_system::instance(); 2144 $issitecourse = true; 2145 } 2146 2147 // Create a node to add user information under. 2148 $usersnode = null; 2149 if (!$issitecourse) { 2150 // Not the current user so add it to the participants node for the current course. 2151 $usersnode = $coursenode->get('participants', navigation_node::TYPE_CONTAINER); 2152 $userviewurl = new moodle_url('/user/view.php', $baseargs); 2153 } else if ($USER->id != $user->id) { 2154 // This is the site so add a users node to the root branch. 2155 $usersnode = $this->rootnodes['users']; 2156 if (has_capability('moodle/course:viewparticipants', $coursecontext)) { 2157 $usersnode->action = new moodle_url('/user/index.php', array('id' => $course->id)); 2158 } 2159 $userviewurl = new moodle_url('/user/profile.php', $baseargs); 2160 } 2161 if (!$usersnode) { 2162 // We should NEVER get here, if the course hasn't been populated 2163 // with a participants node then the navigaiton either wasn't generated 2164 // for it (you are missing a require_login or set_context call) or 2165 // you don't have access.... in the interests of no leaking informatin 2166 // we simply quit... 2167 return false; 2168 } 2169 // Add a branch for the current user. 2170 $canseefullname = has_capability('moodle/site:viewfullnames', $coursecontext); 2171 $usernode = $usersnode->add(fullname($user, $canseefullname), $userviewurl, self::TYPE_USER, null, 'user' . $user->id); 2172 if ($this->page->context->contextlevel == CONTEXT_USER && $user->id == $this->page->context->instanceid) { 2173 $usernode->make_active(); 2174 } 2175 2176 // Add user information to the participants or user node. 2177 if ($issitecourse) { 2178 2179 // If the user is the current user or has permission to view the details of the requested 2180 // user than add a view profile link. 2181 if ($iscurrentuser || has_capability('moodle/user:viewdetails', $coursecontext) || 2182 has_capability('moodle/user:viewdetails', $usercontext)) { 2183 if ($issitecourse || ($iscurrentuser && !$forceforcontext)) { 2184 $usernode->add(get_string('viewprofile'), new moodle_url('/user/profile.php', $baseargs)); 2185 } else { 2186 $usernode->add(get_string('viewprofile'), new moodle_url('/user/view.php', $baseargs)); 2187 } 2188 } 2189 2190 if (!empty($CFG->navadduserpostslinks)) { 2191 // Add nodes for forum posts and discussions if the user can view either or both 2192 // There are no capability checks here as the content of the page is based 2193 // purely on the forums the current user has access too. 2194 $forumtab = $usernode->add(get_string('forumposts', 'forum')); 2195 $forumtab->add(get_string('posts', 'forum'), new moodle_url('/mod/forum/user.php', $baseargs)); 2196 $forumtab->add(get_string('discussions', 'forum'), new moodle_url('/mod/forum/user.php', 2197 array_merge($baseargs, array('mode' => 'discussions')))); 2198 } 2199 2200 // Add blog nodes. 2201 if (!empty($CFG->enableblogs)) { 2202 if (!$this->cache->cached('userblogoptions'.$user->id)) { 2203 require_once($CFG->dirroot.'/blog/lib.php'); 2204 // Get all options for the user. 2205 $options = blog_get_options_for_user($user); 2206 $this->cache->set('userblogoptions'.$user->id, $options); 2207 } else { 2208 $options = $this->cache->{'userblogoptions'.$user->id}; 2209 } 2210 2211 if (count($options) > 0) { 2212 $blogs = $usernode->add(get_string('blogs', 'blog'), null, navigation_node::TYPE_CONTAINER); 2213 foreach ($options as $type => $option) { 2214 if ($type == "rss") { 2215 $blogs->add($option['string'], $option['link'], settings_navigation::TYPE_SETTING, null, null, 2216 new pix_icon('i/rss', '')); 2217 } else { 2218 $blogs->add($option['string'], $option['link']); 2219 } 2220 } 2221 } 2222 } 2223 2224 // Add the messages link. 2225 // It is context based so can appear in the user's profile and in course participants information. 2226 if (!empty($CFG->messaging)) { 2227 $messageargs = array('user1' => $USER->id); 2228 if ($USER->id != $user->id) { 2229 $messageargs['user2'] = $user->id; 2230 } 2231 if ($course->id != $SITE->id) { 2232 $messageargs['viewing'] = MESSAGE_VIEW_COURSE. $course->id; 2233 } 2234 $url = new moodle_url('/message/index.php', $messageargs); 2235 $usernode->add(get_string('messages', 'message'), $url, self::TYPE_SETTING, null, 'messages'); 2236 } 2237 2238 // Add the "My private files" link. 2239 // This link doesn't have a unique display for course context so only display it under the user's profile. 2240 if ($issitecourse && $iscurrentuser && has_capability('moodle/user:manageownfiles', $usercontext)) { 2241 $url = new moodle_url('/user/files.php'); 2242 $usernode->add(get_string('privatefiles'), $url, self::TYPE_SETTING); 2243 } 2244 2245 // Add a node to view the users notes if permitted. 2246 if (!empty($CFG->enablenotes) && 2247 has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $coursecontext)) { 2248 $url = new moodle_url('/notes/index.php', array('user' => $user->id)); 2249 if ($coursecontext->instanceid != SITEID) { 2250 $url->param('course', $coursecontext->instanceid); 2251 } 2252 $usernode->add(get_string('notes', 'notes'), $url); 2253 } 2254 2255 // Show the grades node. 2256 if (($issitecourse && $iscurrentuser) || has_capability('moodle/user:viewdetails', $usercontext)) { 2257 require_once($CFG->dirroot . '/user/lib.php'); 2258 // Set the grades node to link to the "Grades" page. 2259 if ($course->id == SITEID) { 2260 $url = user_mygrades_url($user->id, $course->id); 2261 } else { // Otherwise we are in a course and should redirect to the user grade report (Activity report version). 2262 $url = new moodle_url('/course/user.php', array('mode' => 'grade', 'id' => $course->id, 'user' => $user->id)); 2263 } 2264 if ($USER->id != $user->id) { 2265 $usernode->add(get_string('grades', 'grades'), $url, self::TYPE_SETTING, null, 'usergrades'); 2266 } else { 2267 $usernode->add(get_string('grades', 'grades'), $url); 2268 } 2269 } 2270 2271 // If the user is the current user add the repositories for the current user. 2272 $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields)); 2273 if (!$iscurrentuser && 2274 $course->id == $SITE->id && 2275 has_capability('moodle/user:viewdetails', $usercontext) && 2276 (!in_array('mycourses', $hiddenfields) || has_capability('moodle/user:viewhiddendetails', $coursecontext))) { 2277 2278 // Add view grade report is permitted. 2279 $reports = core_component::get_plugin_list('gradereport'); 2280 arsort($reports); // User is last, we want to test it first. 2281 2282 $userscourses = enrol_get_users_courses($user->id); 2283 $userscoursesnode = $usernode->add(get_string('courses')); 2284 2285 $count = 0; 2286 foreach ($userscourses as $usercourse) { 2287 if ($count === (int)$CFG->navcourselimit) { 2288 $url = new moodle_url('/user/profile.php', array('id' => $user->id, 'showallcourses' => 1)); 2289 $userscoursesnode->add(get_string('showallcourses'), $url); 2290 break; 2291 } 2292 $count++; 2293 $usercoursecontext = context_course::instance($usercourse->id); 2294 $usercourseshortname = format_string($usercourse->shortname, true, array('context' => $usercoursecontext)); 2295 $usercoursenode = $userscoursesnode->add($usercourseshortname, new moodle_url('/user/view.php', 2296 array('id' => $user->id, 'course' => $usercourse->id)), self::TYPE_CONTAINER); 2297 2298 $gradeavailable = has_capability('moodle/grade:viewall', $usercoursecontext); 2299 if (!$gradeavailable && !empty($usercourse->showgrades) && is_array($reports) && !empty($reports)) { 2300 foreach ($reports as $plugin => $plugindir) { 2301 if (has_capability('gradereport/'.$plugin.':view', $usercoursecontext)) { 2302 // Stop when the first visible plugin is found. 2303 $gradeavailable = true; 2304 break; 2305 } 2306 } 2307 } 2308 2309 if ($gradeavailable) { 2310 $url = new moodle_url('/grade/report/index.php', array('id' => $usercourse->id)); 2311 $usercoursenode->add(get_string('grades'), $url, self::TYPE_SETTING, null, null, 2312 new pix_icon('i/grades', '')); 2313 } 2314 2315 // Add a node to view the users notes if permitted. 2316 if (!empty($CFG->enablenotes) && 2317 has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $usercoursecontext)) { 2318 $url = new moodle_url('/notes/index.php', array('user' => $user->id, 'course' => $usercourse->id)); 2319 $usercoursenode->add(get_string('notes', 'notes'), $url, self::TYPE_SETTING); 2320 } 2321 2322 if (can_access_course($usercourse, $user->id, '', true)) { 2323 $usercoursenode->add(get_string('entercourse'), new moodle_url('/course/view.php', 2324 array('id' => $usercourse->id)), self::TYPE_SETTING, null, null, new pix_icon('i/course', '')); 2325 } 2326 2327 $reporttab = $usercoursenode->add(get_string('activityreports')); 2328 2329 $reports = get_plugin_list_with_function('report', 'extend_navigation_user', 'lib.php'); 2330 foreach ($reports as $reportfunction) { 2331 $reportfunction($reporttab, $user, $usercourse); 2332 } 2333 2334 $reporttab->trim_if_empty(); 2335 } 2336 } 2337 2338 // Let plugins hook into user navigation. 2339 $pluginsfunction = get_plugins_with_function('extend_navigation_user', 'lib.php'); 2340 foreach ($pluginsfunction as $plugintype => $plugins) { 2341 if ($plugintype != 'report') { 2342 foreach ($plugins as $pluginfunction) { 2343 $pluginfunction($usernode, $user, $usercontext, $course, $coursecontext); 2344 } 2345 } 2346 } 2347 } 2348 return true; 2349 } 2350 2351 /** 2352 * This method simply checks to see if a given module can extend the navigation. 2353 * 2354 * @todo (MDL-25290) A shared caching solution should be used to save details on what extends navigation. 2355 * 2356 * @param string $modname 2357 * @return bool 2358 */ 2359 public static function module_extends_navigation($modname) { 2360 global $CFG; 2361 static $extendingmodules = array(); 2362 if (!array_key_exists($modname, $extendingmodules)) { 2363 $extendingmodules[$modname] = false; 2364 $file = $CFG->dirroot.'/mod/'.$modname.'/lib.php'; 2365 if (file_exists($file)) { 2366 $function = $modname.'_extend_navigation'; 2367 require_once($file); 2368 $extendingmodules[$modname] = (function_exists($function)); 2369 } 2370 } 2371 return $extendingmodules[$modname]; 2372 } 2373 /** 2374 * Extends the navigation for the given user. 2375 * 2376 * @param stdClass $user A user from the database 2377 */ 2378 public function extend_for_user($user) { 2379 $this->extendforuser[] = $user; 2380 } 2381 2382 /** 2383 * Returns all of the users the navigation is being extended for 2384 * 2385 * @return array An array of extending users. 2386 */ 2387 public function get_extending_users() { 2388 return $this->extendforuser; 2389 } 2390 /** 2391 * Adds the given course to the navigation structure. 2392 * 2393 * @param stdClass $course 2394 * @param bool $forcegeneric 2395 * @param bool $ismycourse 2396 * @return navigation_node 2397 */ 2398 public function add_course(stdClass $course, $forcegeneric = false, $coursetype = self::COURSE_OTHER) { 2399 global $CFG, $SITE; 2400 2401 // We found the course... we can return it now :) 2402 if (!$forcegeneric && array_key_exists($course->id, $this->addedcourses)) { 2403 return $this->addedcourses[$course->id]; 2404 } 2405 2406 $coursecontext = context_course::instance($course->id); 2407 2408 if ($course->id != $SITE->id && !$course->visible) { 2409 if (is_role_switched($course->id)) { 2410 // user has to be able to access course in order to switch, let's skip the visibility test here 2411 } else if (!has_capability('moodle/course:viewhiddencourses', $coursecontext)) { 2412 return false; 2413 } 2414 } 2415 2416 $issite = ($course->id == $SITE->id); 2417 $shortname = format_string($course->shortname, true, array('context' => $coursecontext)); 2418 $fullname = format_string($course->fullname, true, array('context' => $coursecontext)); 2419 // This is the name that will be shown for the course. 2420 $coursename = empty($CFG->navshowfullcoursenames) ? $shortname : $fullname; 2421 2422 // Can the user expand the course to see its content. 2423 $canexpandcourse = true; 2424 if ($issite) { 2425 $parent = $this; 2426 $url = null; 2427 if (empty($CFG->usesitenameforsitepages)) { 2428 $coursename = get_string('sitepages'); 2429 } 2430 } else if ($coursetype == self::COURSE_CURRENT) { 2431 $parent = $this->rootnodes['currentcourse']; 2432 $url = new moodle_url('/course/view.php', array('id'=>$course->id)); 2433 $canexpandcourse = $this->can_expand_course($course); 2434 } else if ($coursetype == self::COURSE_MY && !$forcegeneric) { 2435 if (!empty($CFG->navshowmycoursecategories) && ($parent = $this->rootnodes['mycourses']->find($course->category, self::TYPE_MY_CATEGORY))) { 2436 // Nothing to do here the above statement set $parent to the category within mycourses. 2437 } else { 2438 $parent = $this->rootnodes['mycourses']; 2439 } 2440 $url = new moodle_url('/course/view.php', array('id'=>$course->id)); 2441 } else { 2442 $parent = $this->rootnodes['courses']; 2443 $url = new moodle_url('/course/view.php', array('id'=>$course->id)); 2444 // They can only expand the course if they can access it. 2445 $canexpandcourse = $this->can_expand_course($course); 2446 if (!empty($course->category) && $this->show_categories($coursetype == self::COURSE_MY)) { 2447 if (!$this->is_category_fully_loaded($course->category)) { 2448 // We need to load the category structure for this course 2449 $this->load_all_categories($course->category, false); 2450 } 2451 if (array_key_exists($course->category, $this->addedcategories)) { 2452 $parent = $this->addedcategories[$course->category]; 2453 // This could lead to the course being created so we should check whether it is the case again 2454 if (!$forcegeneric && array_key_exists($course->id, $this->addedcourses)) { 2455 return $this->addedcourses[$course->id]; 2456 } 2457 } 2458 } 2459 } 2460 2461 $coursenode = $parent->add($coursename, $url, self::TYPE_COURSE, $shortname, $course->id); 2462 $coursenode->hidden = (!$course->visible); 2463 $coursenode->title(format_string($course->fullname, true, array('context' => $coursecontext, 'escape' => false))); 2464 if ($canexpandcourse) { 2465 // This course can be expanded by the user, make it a branch to make the system aware that its expandable by ajax. 2466 $coursenode->nodetype = self::NODETYPE_BRANCH; 2467 $coursenode->isexpandable = true; 2468 } else { 2469 $coursenode->nodetype = self::NODETYPE_LEAF; 2470 $coursenode->isexpandable = false; 2471 } 2472 if (!$forcegeneric) { 2473 $this->addedcourses[$course->id] = $coursenode; 2474 } 2475 2476 return $coursenode; 2477 } 2478 2479 /** 2480 * Returns a cache instance to use for the expand course cache. 2481 * @return cache_session 2482 */ 2483 protected function get_expand_course_cache() { 2484 if ($this->cacheexpandcourse === null) { 2485 $this->cacheexpandcourse = cache::make('core', 'navigation_expandcourse'); 2486 } 2487 return $this->cacheexpandcourse; 2488 } 2489 2490 /** 2491 * Checks if a user can expand a course in the navigation. 2492 * 2493 * We use a cache here because in order to be accurate we need to call can_access_course which is a costly function. 2494 * Because this functionality is basic + non-essential and because we lack good event triggering this cache 2495 * permits stale data. 2496 * In the situation the user is granted access to a course after we've initialised this session cache the cache 2497 * will be stale. 2498 * It is brought up to date in only one of two ways. 2499 * 1. The user logs out and in again. 2500 * 2. The user browses to the course they've just being given access to. 2501 * 2502 * Really all this controls is whether the node is shown as expandable or not. It is uber un-important. 2503 * 2504 * @param stdClass $course 2505 * @return bool 2506 */ 2507 protected function can_expand_course($course) { 2508 $cache = $this->get_expand_course_cache(); 2509 $canexpand = $cache->get($course->id); 2510 if ($canexpand === false) { 2511 $canexpand = isloggedin() && can_access_course($course, null, '', true); 2512 $canexpand = (int)$canexpand; 2513 $cache->set($course->id, $canexpand); 2514 } 2515 return ($canexpand === 1); 2516 } 2517 2518 /** 2519 * Returns true if the category has already been loaded as have any child categories 2520 * 2521 * @param int $categoryid 2522 * @return bool 2523 */ 2524 protected function is_category_fully_loaded($categoryid) { 2525 return (array_key_exists($categoryid, $this->addedcategories) && ($this->allcategoriesloaded || $this->addedcategories[$categoryid]->children->count() > 0)); 2526 } 2527 2528 /** 2529 * Adds essential course nodes to the navigation for the given course. 2530 * 2531 * This method adds nodes such as reports, blogs and participants 2532 * 2533 * @param navigation_node $coursenode 2534 * @param stdClass $course 2535 * @return bool returns true on successful addition of a node. 2536 */ 2537 public function add_course_essentials($coursenode, stdClass $course) { 2538 global $CFG, $SITE; 2539 2540 if ($course->id == $SITE->id) { 2541 return $this->add_front_page_course_essentials($coursenode, $course); 2542 } 2543 2544 if ($coursenode == false || !($coursenode instanceof navigation_node) || $coursenode->get('participants', navigation_node::TYPE_CONTAINER)) { 2545 return true; 2546 } 2547 2548 //Participants 2549 if (has_capability('moodle/course:viewparticipants', $this->page->context)) { 2550 $participants = $coursenode->add(get_string('participants'), new moodle_url('/user/index.php?id='.$course->id), self::TYPE_CONTAINER, get_string('participants'), 'participants'); 2551 if (!empty($CFG->enableblogs)) { 2552 if (($CFG->bloglevel == BLOG_GLOBAL_LEVEL or ($CFG->bloglevel == BLOG_SITE_LEVEL and (isloggedin() and !isguestuser()))) 2553 and has_capability('moodle/blog:view', context_system::instance())) { 2554 $blogsurls = new moodle_url('/blog/index.php'); 2555 if ($currentgroup = groups_get_course_group($course, true)) { 2556 $blogsurls->param('groupid', $currentgroup); 2557 } else { 2558 $blogsurls->param('courseid', $course->id); 2559 } 2560 $participants->add(get_string('blogscourse', 'blog'), $blogsurls->out(), self::TYPE_SETTING, null, 'courseblogs'); 2561 } 2562 } 2563 if (!empty($CFG->enablenotes) && (has_capability('moodle/notes:manage', $this->page->context) || has_capability('moodle/notes:view', $this->page->context))) { 2564 $participants->add(get_string('notes', 'notes'), new moodle_url('/notes/index.php', array('filtertype' => 'course', 'filterselect' => $course->id)), self::TYPE_SETTING, null, 'currentcoursenotes'); 2565 } 2566 } else if (count($this->extendforuser) > 0 || $this->page->course->id == $course->id) { 2567 $participants = $coursenode->add(get_string('participants'), null, self::TYPE_CONTAINER, get_string('participants'), 'participants'); 2568 } 2569 2570 // Badges. 2571 if (!empty($CFG->enablebadges) && !empty($CFG->badges_allowcoursebadges) && 2572 has_capability('moodle/badges:viewbadges', $this->page->context)) { 2573 $url = new moodle_url('/badges/view.php', array('type' => 2, 'id' => $course->id)); 2574 2575 $coursenode->add(get_string('coursebadges', 'badges'), null, 2576 navigation_node::TYPE_CONTAINER, null, 'coursebadges'); 2577 $coursenode->get('coursebadges')->add(get_string('badgesview', 'badges'), $url, 2578 navigation_node::TYPE_SETTING, null, 'badgesview', 2579 new pix_icon('i/badge', get_string('badgesview', 'badges'))); 2580 } 2581 2582 return true; 2583 } 2584 /** 2585 * This generates the structure of the course that won't be generated when 2586 * the modules and sections are added. 2587 * 2588 * Things such as the reports branch, the participants branch, blogs... get 2589 * added to the course node by this method. 2590 * 2591 * @param navigation_node $coursenode 2592 * @param stdClass $course 2593 * @return bool True for successfull generation 2594 */ 2595 public function add_front_page_course_essentials(navigation_node $coursenode, stdClass $course) { 2596 global $CFG; 2597 2598 if ($coursenode == false || $coursenode->get('frontpageloaded', navigation_node::TYPE_CUSTOM)) { 2599 return true; 2600 } 2601 2602 $sitecontext = context_system::instance(); 2603 2604 // Hidden node that we use to determine if the front page navigation is loaded. 2605 // This required as there are not other guaranteed nodes that may be loaded. 2606 $coursenode->add('frontpageloaded', null, self::TYPE_CUSTOM, null, 'frontpageloaded')->display = false; 2607 2608 // Participants. 2609 // If this is the site course, they need to have moodle/site:viewparticipants at the site level. 2610 // If no, then they need to have moodle/course:viewparticipants at the course level. 2611 if ((($course->id == SITEID) && has_capability('moodle/site:viewparticipants', $sitecontext)) || 2612 has_capability('moodle/course:viewparticipants', context_course::instance($course->id))) { 2613 $coursenode->add(get_string('participants'), new moodle_url('/user/index.php?id='.$course->id), self::TYPE_CUSTOM, get_string('participants'), 'participants'); 2614 } 2615 2616 // Blogs. 2617 if (!empty($CFG->enableblogs) 2618 and ($CFG->bloglevel == BLOG_GLOBAL_LEVEL or ($CFG->bloglevel == BLOG_SITE_LEVEL and (isloggedin() and !isguestuser()))) 2619 and has_capability('moodle/blog:view', $sitecontext)) { 2620 $blogsurls = new moodle_url('/blog/index.php'); 2621 $coursenode->add(get_string('blogssite', 'blog'), $blogsurls->out(), self::TYPE_SYSTEM, null, 'siteblog'); 2622 } 2623 2624 $filterselect = 0; 2625 2626 // Badges. 2627 if (!empty($CFG->enablebadges) && has_capability('moodle/badges:viewbadges', $sitecontext)) { 2628 $url = new moodle_url($CFG->wwwroot . '/badges/view.php', array('type' => 1)); 2629 $coursenode->add(get_string('sitebadges', 'badges'), $url, navigation_node::TYPE_CUSTOM); 2630 } 2631 2632 // Notes. 2633 if (!empty($CFG->enablenotes) && has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $sitecontext)) { 2634 $coursenode->add(get_string('notes', 'notes'), new moodle_url('/notes/index.php', 2635 array('filtertype' => 'course', 'filterselect' => $filterselect)), self::TYPE_SETTING, null, 'notes'); 2636 } 2637 2638 // Tags 2639 if (!empty($CFG->usetags) && isloggedin()) { 2640 $node = $coursenode->add(get_string('tags', 'tag'), new moodle_url('/tag/search.php'), 2641 self::TYPE_SETTING, null, 'tags'); 2642 } 2643 2644 // Search. 2645 if (!empty($CFG->enableglobalsearch) && has_capability('moodle/search:query', $sitecontext)) { 2646 $node = $coursenode->add(get_string('search', 'search'), new moodle_url('/search/index.php'), 2647 self::TYPE_SETTING, null, 'search'); 2648 } 2649 2650 if (isloggedin()) { 2651 // Calendar 2652 $calendarurl = new moodle_url('/calendar/view.php', array('view' => 'month')); 2653 $coursenode->add(get_string('calendar', 'calendar'), $calendarurl, self::TYPE_CUSTOM, null, 'calendar'); 2654 } 2655 2656 return true; 2657 } 2658 2659 /** 2660 * Clears the navigation cache 2661 */ 2662 public function clear_cache() { 2663 $this->cache->clear(); 2664 } 2665 2666 /** 2667 * Sets an expansion limit for the navigation 2668 * 2669 * The expansion limit is used to prevent the display of content that has a type 2670 * greater than the provided $type. 2671 * 2672 * Can be used to ensure things such as activities or activity content don't get 2673 * shown on the navigation. 2674 * They are still generated in order to ensure the navbar still makes sense. 2675 * 2676 * @param int $type One of navigation_node::TYPE_* 2677 * @return bool true when complete. 2678 */ 2679 public function set_expansion_limit($type) { 2680 global $SITE; 2681 $nodes = $this->find_all_of_type($type); 2682 2683 // We only want to hide specific types of nodes. 2684 // Only nodes that represent "structure" in the navigation tree should be hidden. 2685 // If we hide all nodes then we risk hiding vital information. 2686 $typestohide = array( 2687 self::TYPE_CATEGORY, 2688 self::TYPE_COURSE, 2689 self::TYPE_SECTION, 2690 self::TYPE_ACTIVITY 2691 ); 2692 2693 foreach ($nodes as $node) { 2694 // We need to generate the full site node 2695 if ($type == self::TYPE_COURSE && $node->key == $SITE->id) { 2696 continue; 2697 } 2698 foreach ($node->children as $child) { 2699 $child->hide($typestohide); 2700 } 2701 } 2702 return true; 2703 } 2704 /** 2705 * Attempts to get the navigation with the given key from this nodes children. 2706 * 2707 * This function only looks at this nodes children, it does NOT look recursivily. 2708 * If the node can't be found then false is returned. 2709 * 2710 * If you need to search recursivily then use the {@link global_navigation::find()} method. 2711 * 2712 * Note: If you are trying to set the active node {@link navigation_node::override_active_url()} 2713 * may be of more use to you. 2714 * 2715 * @param string|int $key The key of the node you wish to receive. 2716 * @param int $type One of navigation_node::TYPE_* 2717 * @return navigation_node|false 2718 */ 2719 public function get($key, $type = null) { 2720 if (!$this->initialised) { 2721 $this->initialise(); 2722 } 2723 return parent::get($key, $type); 2724 } 2725 2726 /** 2727 * Searches this nodes children and their children to find a navigation node 2728 * with the matching key and type. 2729 * 2730 * This method is recursive and searches children so until either a node is 2731 * found or there are no more nodes to search. 2732 * 2733 * If you know that the node being searched for is a child of this node 2734 * then use the {@link global_navigation::get()} method instead. 2735 * 2736 * Note: If you are trying to set the active node {@link navigation_node::override_active_url()} 2737 * may be of more use to you. 2738 * 2739 * @param string|int $key The key of the node you wish to receive. 2740 * @param int $type One of navigation_node::TYPE_* 2741 * @return navigation_node|false 2742 */ 2743 public function find($key, $type) { 2744 if (!$this->initialised) { 2745 $this->initialise(); 2746 } 2747 if ($type == self::TYPE_ROOTNODE && array_key_exists($key, $this->rootnodes)) { 2748 return $this->rootnodes[$key]; 2749 } 2750 return parent::find($key, $type); 2751 } 2752 2753 /** 2754 * They've expanded the 'my courses' branch. 2755 */ 2756 protected function load_courses_enrolled() { 2757 global $CFG, $DB; 2758 $sortorder = 'visible DESC'; 2759 // Prevent undefined $CFG->navsortmycoursessort errors. 2760 if (empty($CFG->navsortmycoursessort)) { 2761 $CFG->navsortmycoursessort = 'sortorder'; 2762 } 2763 // Append the chosen sortorder. 2764 $sortorder = $sortorder . ',' . $CFG->navsortmycoursessort . ' ASC'; 2765 $courses = enrol_get_my_courses(null, $sortorder); 2766 if (count($courses) && $this->show_my_categories()) { 2767 // OK Actually we are loading categories. We only want to load categories that have a parent of 0. 2768 // In order to make sure we load everything required we must first find the categories that are not 2769 // base categories and work out the bottom category in thier path. 2770 $categoryids = array(); 2771 foreach ($courses as $course) { 2772 $categoryids[] = $course->category; 2773 } 2774 $categoryids = array_unique($categoryids); 2775 list($sql, $params) = $DB->get_in_or_equal($categoryids); 2776 $categories = $DB->get_recordset_select('course_categories', 'id '.$sql.' AND parent <> 0', $params, 'sortorder, id', 'id, path'); 2777 foreach ($categories as $category) { 2778 $bits = explode('/', trim($category->path,'/')); 2779 $categoryids[] = array_shift($bits); 2780 } 2781 $categoryids = array_unique($categoryids); 2782 $categories->close(); 2783 2784 // Now we load the base categories. 2785 list($sql, $params) = $DB->get_in_or_equal($categoryids); 2786 $categories = $DB->get_recordset_select('course_categories', 'id '.$sql.' AND parent = 0', $params, 'sortorder, id'); 2787 foreach ($categories as $category) { 2788 $this->add_category($category, $this->rootnodes['mycourses'], self::TYPE_MY_CATEGORY); 2789 } 2790 $categories->close(); 2791 } else { 2792 foreach ($courses as $course) { 2793 $this->add_course($course, false, self::COURSE_MY); 2794 } 2795 } 2796 } 2797 } 2798 2799 /** 2800 * The global navigation class used especially for AJAX requests. 2801 * 2802 * The primary methods that are used in the global navigation class have been overriden 2803 * to ensure that only the relevant branch is generated at the root of the tree. 2804 * This can be done because AJAX is only used when the backwards structure for the 2805 * requested branch exists. 2806 * This has been done only because it shortens the amounts of information that is generated 2807 * which of course will speed up the response time.. because no one likes laggy AJAX. 2808 * 2809 * @package core 2810 * @category navigation 2811 * @copyright 2009 Sam Hemelryk 2812 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2813 */ 2814 class global_navigation_for_ajax extends global_navigation { 2815 2816 /** @var int used for determining what type of navigation_node::TYPE_* is being used */ 2817 protected $branchtype; 2818 2819 /** @var int the instance id */ 2820 protected $instanceid; 2821 2822 /** @var array Holds an array of expandable nodes */ 2823 protected $expandable = array(); 2824 2825 /** 2826 * Constructs the navigation for use in an AJAX request 2827 * 2828 * @param moodle_page $page moodle_page object 2829 * @param int $branchtype 2830 * @param int $id 2831 */ 2832 public function __construct($page, $branchtype, $id) { 2833 $this->page = $page; 2834 $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME); 2835 $this->children = new navigation_node_collection(); 2836 $this->branchtype = $branchtype; 2837 $this->instanceid = $id; 2838 $this->initialise(); 2839 } 2840 /** 2841 * Initialise the navigation given the type and id for the branch to expand. 2842 * 2843 * @return array An array of the expandable nodes 2844 */ 2845 public function initialise() { 2846 global $DB, $SITE; 2847 2848 if ($this->initialised || during_initial_install()) { 2849 return $this->expandable; 2850 } 2851 $this->initialised = true; 2852 2853 $this->rootnodes = array(); 2854 $this->rootnodes['site'] = $this->add_course($SITE); 2855 $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), new moodle_url('/my'), self::TYPE_ROOTNODE, null, 'mycourses'); 2856 $this->rootnodes['courses'] = $this->add(get_string('courses'), null, self::TYPE_ROOTNODE, null, 'courses'); 2857 // The courses branch is always displayed, and is always expandable (although may be empty). 2858 // This mimicks what is done during {@link global_navigation::initialise()}. 2859 $this->rootnodes['courses']->isexpandable = true; 2860 2861 // Branchtype will be one of navigation_node::TYPE_* 2862 switch ($this->branchtype) { 2863 case 0: 2864 if ($this->instanceid === 'mycourses') { 2865 $this->load_courses_enrolled(); 2866 } else if ($this->instanceid === 'courses') { 2867 $this->load_courses_other(); 2868 } 2869 break; 2870 case self::TYPE_CATEGORY : 2871 $this->load_category($this->instanceid); 2872 break; 2873 case self::TYPE_MY_CATEGORY : 2874 $this->load_category($this->instanceid, self::TYPE_MY_CATEGORY); 2875 break; 2876 case self::TYPE_COURSE : 2877 $course = $DB->get_record('course', array('id' => $this->instanceid), '*', MUST_EXIST); 2878 if (!can_access_course($course, null, '', true)) { 2879 // Thats OK all courses are expandable by default. We don't need to actually expand it we can just 2880 // add the course node and break. This leads to an empty node. 2881 $this->add_course($course); 2882 break; 2883 } 2884 require_course_login($course, true, null, false, true); 2885 $this->page->set_context(context_course::instance($course->id)); 2886 $coursenode = $this->add_course($course); 2887 $this->add_course_essentials($coursenode, $course); 2888 $this->load_course_sections($course, $coursenode); 2889 break; 2890 case self::TYPE_SECTION : 2891 $sql = 'SELECT c.*, cs.section AS sectionnumber 2892 FROM {course} c 2893 LEFT JOIN {course_sections} cs ON cs.course = c.id 2894 WHERE cs.id = ?'; 2895 $course = $DB->get_record_sql($sql, array($this->instanceid), MUST_EXIST); 2896 require_course_login($course, true, null, false, true); 2897 $this->page->set_context(context_course::instance($course->id)); 2898 $coursenode = $this->add_course($course); 2899 $this->add_course_essentials($coursenode, $course); 2900 $this->load_course_sections($course, $coursenode, $course->sectionnumber); 2901 break; 2902 case self::TYPE_ACTIVITY : 2903 $sql = "SELECT c.* 2904 FROM {course} c 2905 JOIN {course_modules} cm ON cm.course = c.id 2906 WHERE cm.id = :cmid"; 2907 $params = array('cmid' => $this->instanceid); 2908 $course = $DB->get_record_sql($sql, $params, MUST_EXIST); 2909 $modinfo = get_fast_modinfo($course); 2910 $cm = $modinfo->get_cm($this->instanceid); 2911 require_course_login($course, true, $cm, false, true); 2912 $this->page->set_context(context_module::instance($cm->id)); 2913 $coursenode = $this->load_course($course); 2914 $this->load_course_sections($course, $coursenode, null, $cm); 2915 $activitynode = $coursenode->find($cm->id, self::TYPE_ACTIVITY); 2916 if ($activitynode) { 2917 $modulenode = $this->load_activity($cm, $course, $activitynode); 2918 } 2919 break; 2920 default: 2921 throw new Exception('Unknown type'); 2922 return $this->expandable; 2923 } 2924 2925 if ($this->page->context->contextlevel == CONTEXT_COURSE && $this->page->context->instanceid != $SITE->id) { 2926 $this->load_for_user(null, true); 2927 } 2928 2929 $this->find_expandable($this->expandable); 2930 return $this->expandable; 2931 } 2932 2933 /** 2934 * They've expanded the general 'courses' branch. 2935 */ 2936 protected function load_courses_other() { 2937 $this->load_all_courses(); 2938 } 2939 2940 /** 2941 * Loads a single category into the AJAX navigation. 2942 * 2943 * This function is special in that it doesn't concern itself with the parent of 2944 * the requested category or its siblings. 2945 * This is because with the AJAX navigation we know exactly what is wanted and only need to 2946 * request that. 2947 * 2948 * @global moodle_database $DB 2949 * @param int $categoryid id of category to load in navigation. 2950 * @param int $nodetype type of node, if category is under MyHome then it's TYPE_MY_CATEGORY 2951 * @return void. 2952 */ 2953 protected function load_category($categoryid, $nodetype = self::TYPE_CATEGORY) { 2954 global $CFG, $DB; 2955 2956 $limit = 20; 2957 if (!empty($CFG->navcourselimit)) { 2958 $limit = (int)$CFG->navcourselimit; 2959 } 2960 2961 $catcontextsql = context_helper::get_preload_record_columns_sql('ctx'); 2962 $sql = "SELECT cc.*, $catcontextsql 2963 FROM {course_categories} cc 2964 JOIN {context} ctx ON cc.id = ctx.instanceid 2965 WHERE ctx.contextlevel = ".CONTEXT_COURSECAT." AND 2966 (cc.id = :categoryid1 OR cc.parent = :categoryid2) 2967 ORDER BY cc.depth ASC, cc.sortorder ASC, cc.id ASC"; 2968 $params = array('categoryid1' => $categoryid, 'categoryid2' => $categoryid); 2969 $categories = $DB->get_recordset_sql($sql, $params, 0, $limit); 2970 $categorylist = array(); 2971 $subcategories = array(); 2972 $basecategory = null; 2973 foreach ($categories as $category) { 2974 $categorylist[] = $category->id; 2975 context_helper::preload_from_record($category); 2976 if ($category->id == $categoryid) { 2977 $this->add_category($category, $this, $nodetype); 2978 $basecategory = $this->addedcategories[$category->id]; 2979 } else { 2980 $subcategories[$category->id] = $category; 2981 } 2982 } 2983 $categories->close(); 2984 2985 2986 // If category is shown in MyHome then only show enrolled courses and hide empty subcategories, 2987 // else show all courses. 2988 if ($nodetype === self::TYPE_MY_CATEGORY) { 2989 $courses = enrol_get_my_courses(); 2990 $categoryids = array(); 2991 2992 // Only search for categories if basecategory was found. 2993 if (!is_null($basecategory)) { 2994 // Get course parent category ids. 2995 foreach ($courses as $course) { 2996 $categoryids[] = $course->category; 2997 } 2998 2999 // Get a unique list of category ids which a part of the path 3000 // to user's courses. 3001 $coursesubcategories = array(); 3002 $addedsubcategories = array(); 3003 3004 list($sql, $params) = $DB->get_in_or_equal($categoryids); 3005 $categories = $DB->get_recordset_select('course_categories', 'id '.$sql, $params, 'sortorder, id', 'id, path'); 3006 3007 foreach ($categories as $category){ 3008 $coursesubcategories = array_merge($coursesubcategories, explode('/', trim($category->path, "/"))); 3009 } 3010 $coursesubcategories = array_unique($coursesubcategories); 3011 3012 // Only add a subcategory if it is part of the path to user's course and 3013 // wasn't already added. 3014 foreach ($subcategories as $subid => $subcategory) { 3015 if (in_array($subid, $coursesubcategories) && 3016 !in_array($subid, $addedsubcategories)) { 3017 $this->add_category($subcategory, $basecategory, $nodetype); 3018 $addedsubcategories[] = $subid; 3019 } 3020 } 3021 } 3022 3023 foreach ($courses as $course) { 3024 // Add course if it's in category. 3025 if (in_array($course->category, $categorylist)) { 3026 $this->add_course($course, true, self::COURSE_MY); 3027 } 3028 } 3029 } else { 3030 if (!is_null($basecategory)) { 3031 foreach ($subcategories as $key=>$category) { 3032 $this->add_category($category, $basecategory, $nodetype); 3033 } 3034 } 3035 $courses = $DB->get_recordset('course', array('category' => $categoryid), 'sortorder', '*' , 0, $limit); 3036 foreach ($courses as $course) { 3037 $this->add_course($course); 3038 } 3039 $courses->close(); 3040 } 3041 } 3042 3043 /** 3044 * Returns an array of expandable nodes 3045 * @return array 3046 */ 3047 public function get_expandable() { 3048 return $this->expandable; 3049 } 3050 } 3051 3052 /** 3053 * Navbar class 3054 * 3055 * This class is used to manage the navbar, which is initialised from the navigation 3056 * object held by PAGE 3057 * 3058 * @package core 3059 * @category navigation 3060 * @copyright 2009 Sam Hemelryk 3061 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3062 */ 3063 class navbar extends navigation_node { 3064 /** @var bool A switch for whether the navbar is initialised or not */ 3065 protected $initialised = false; 3066 /** @var mixed keys used to reference the nodes on the navbar */ 3067 protected $keys = array(); 3068 /** @var null|string content of the navbar */ 3069 protected $content = null; 3070 /** @var moodle_page object the moodle page that this navbar belongs to */ 3071 protected $page; 3072 /** @var bool A switch for whether to ignore the active navigation information */ 3073 protected $ignoreactive = false; 3074 /** @var bool A switch to let us know if we are in the middle of an install */ 3075 protected $duringinstall = false; 3076 /** @var bool A switch for whether the navbar has items */ 3077 protected $hasitems = false; 3078 /** @var array An array of navigation nodes for the navbar */ 3079 protected $items; 3080 /** @var array An array of child node objects */ 3081 public $children = array(); 3082 /** @var bool A switch for whether we want to include the root node in the navbar */ 3083 public $includesettingsbase = false; 3084 /** @var breadcrumb_navigation_node[] $prependchildren */ 3085 protected $prependchildren = array(); 3086 /** 3087 * The almighty constructor 3088 * 3089 * @param moodle_page $page 3090 */ 3091 3092 3093 public function __construct(moodle_page $page) { 3094 global $CFG; 3095 if (during_initial_install()) { 3096 $this->duringinstall = true; 3097 return false; 3098 } 3099 $this->page = $page; 3100 $this->text = get_string('home'); 3101 $this->shorttext = get_string('home'); 3102 $this->action = new moodle_url($CFG->wwwroot); 3103 $this->nodetype = self::NODETYPE_BRANCH; 3104 $this->type = self::TYPE_SYSTEM; 3105 } 3106 3107 /** 3108 * Quick check to see if the navbar will have items in. 3109 * 3110 * @return bool Returns true if the navbar will have items, false otherwise 3111 */ 3112 public function has_items() { 3113 if ($this->duringinstall) { 3114 return false; 3115 } else if ($this->hasitems !== false) { 3116 return true; 3117 } 3118 if (count($this->children) > 0 || count($this->prependchildren) > 0) { 3119 // There have been manually added items - there are definitely items. 3120 $outcome = true; 3121 } else if (!$this->ignoreactive) { 3122 // We will need to initialise the navigation structure to check if there are active items. 3123 $this->page->navigation->initialise($this->page); 3124 $outcome = ($this->page->navigation->contains_active_node() || $this->page->settingsnav->contains_active_node()); 3125 } 3126 $this->hasitems = $outcome; 3127 return $outcome; 3128 } 3129 3130 /** 3131 * Turn on/off ignore active 3132 * 3133 * @param bool $setting 3134 */ 3135 public function ignore_active($setting=true) { 3136 $this->ignoreactive = ($setting); 3137 } 3138 3139 /** 3140 * Gets a navigation node 3141 * 3142 * @param string|int $key for referencing the navbar nodes 3143 * @param int $type breadcrumb_navigation_node::TYPE_* 3144 * @return breadcrumb_navigation_node|bool 3145 */ 3146 public function get($key, $type = null) { 3147 foreach ($this->children as &$child) { 3148 if ($child->key === $key && ($type == null || $type == $child->type)) { 3149 return $child; 3150 } 3151 } 3152 foreach ($this->prependchildren as &$child) { 3153 if ($child->key === $key && ($type == null || $type == $child->type)) { 3154 return $child; 3155 } 3156 } 3157 return false; 3158 } 3159 /** 3160 * Returns an array of breadcrumb_navigation_nodes that make up the navbar. 3161 * 3162 * @return array 3163 */ 3164 public function get_items() { 3165 global $CFG; 3166 $items = array(); 3167 // Make sure that navigation is initialised 3168 if (!$this->has_items()) { 3169 return $items; 3170 } 3171 if ($this->items !== null) { 3172 return $this->items; 3173 } 3174 3175 if (count($this->children) > 0) { 3176 // Add the custom children. 3177 $items = array_reverse($this->children); 3178 } 3179 3180 // Check if navigation contains the active node 3181 if (!$this->ignoreactive) { 3182 // We will need to ensure the navigation has been initialised. 3183 $this->page->navigation->initialise($this->page); 3184 // Now find the active nodes on both the navigation and settings. 3185 $navigationactivenode = $this->page->navigation->find_active_node(); 3186 $settingsactivenode = $this->page->settingsnav->find_active_node(); 3187 3188 if ($navigationactivenode && $settingsactivenode) { 3189 // Parse a combined navigation tree 3190 while ($settingsactivenode && $settingsactivenode->parent !== null) { 3191 if (!$settingsactivenode->mainnavonly) { 3192 $items[] = new breadcrumb_navigation_node($settingsactivenode); 3193 } 3194 $settingsactivenode = $settingsactivenode->parent; 3195 } 3196 if (!$this->includesettingsbase) { 3197 // Removes the first node from the settings (root node) from the list 3198 array_pop($items); 3199 } 3200 while ($navigationactivenode && $navigationactivenode->parent !== null) { 3201 if (!$navigationactivenode->mainnavonly) { 3202 $items[] = new breadcrumb_navigation_node($navigationactivenode); 3203 } 3204 if (!empty($CFG->navshowcategories) && 3205 $navigationactivenode->type === self::TYPE_COURSE && 3206 $navigationactivenode->parent->key === 'currentcourse') { 3207 foreach ($this->get_course_categories() as $item) { 3208 $items[] = new breadcrumb_navigation_node($item); 3209 } 3210 } 3211 $navigationactivenode = $navigationactivenode->parent; 3212 } 3213 } else if ($navigationactivenode) { 3214 // Parse the navigation tree to get the active node 3215 while ($navigationactivenode && $navigationactivenode->parent !== null) { 3216 if (!$navigationactivenode->mainnavonly) { 3217 $items[] = new breadcrumb_navigation_node($navigationactivenode); 3218 } 3219 if (!empty($CFG->navshowcategories) && 3220 $navigationactivenode->type === self::TYPE_COURSE && 3221 $navigationactivenode->parent->key === 'currentcourse') { 3222 foreach ($this->get_course_categories() as $item) { 3223 $items[] = new breadcrumb_navigation_node($item); 3224 } 3225 } 3226 $navigationactivenode = $navigationactivenode->parent; 3227 } 3228 } else if ($settingsactivenode) { 3229 // Parse the settings navigation to get the active node 3230 while ($settingsactivenode && $settingsactivenode->parent !== null) { 3231 if (!$settingsactivenode->mainnavonly) { 3232 $items[] = new breadcrumb_navigation_node($settingsactivenode); 3233 } 3234 $settingsactivenode = $settingsactivenode->parent; 3235 } 3236 } 3237 } 3238 3239 $items[] = new breadcrumb_navigation_node(array( 3240 'text' => $this->page->navigation->text, 3241 'shorttext' => $this->page->navigation->shorttext, 3242 'key' => $this->page->navigation->key, 3243 'action' => $this->page->navigation->action 3244 )); 3245 3246 if (count($this->prependchildren) > 0) { 3247 // Add the custom children 3248 $items = array_merge($items, array_reverse($this->prependchildren)); 3249 } 3250 3251 $this->items = array_reverse($items); 3252 return $this->items; 3253 } 3254 3255 /** 3256 * Get the list of categories leading to this course. 3257 * 3258 * This function is used by {@link navbar::get_items()} to add back the "courses" 3259 * node and category chain leading to the current course. Note that this is only ever 3260 * called for the current course, so we don't need to bother taking in any parameters. 3261 * 3262 * @return array 3263 */ 3264 private function get_course_categories() { 3265 global $CFG; 3266 require_once($CFG->dirroot.'/course/lib.php'); 3267 require_once($CFG->libdir.'/coursecatlib.php'); 3268 3269 $categories = array(); 3270 $cap = 'moodle/category:viewhiddencategories'; 3271 $showcategories = coursecat::count_all() > 1; 3272 3273 if ($showcategories) { 3274 foreach ($this->page->categories as $category) { 3275 if (!$category->visible && !has_capability($cap, get_category_or_system_context($category->parent))) { 3276 continue; 3277 } 3278 $url = new moodle_url('/course/index.php', array('categoryid' => $category->id)); 3279 $name = format_string($category->name, true, array('context' => context_coursecat::instance($category->id))); 3280 $categorynode = breadcrumb_navigation_node::create($name, $url, self::TYPE_CATEGORY, null, $category->id); 3281 if (!$category->visible) { 3282 $categorynode->hidden = true; 3283 } 3284 $categories[] = $categorynode; 3285 } 3286 } 3287 3288 // Don't show the 'course' node if enrolled in this course. 3289 if (!is_enrolled(context_course::instance($this->page->course->id, null, '', true))) { 3290 $courses = $this->page->navigation->get('courses'); 3291 if (!$courses) { 3292 // Courses node may not be present. 3293 $courses = breadcrumb_navigation_node::create( 3294 get_string('courses'), 3295 new moodle_url('/course/index.php'), 3296 self::TYPE_CONTAINER 3297 ); 3298 } 3299 $categories[] = $courses; 3300 } 3301 3302 return $categories; 3303 } 3304 3305 /** 3306 * Add a new breadcrumb_navigation_node to the navbar, overrides parent::add 3307 * 3308 * This function overrides {@link breadcrumb_navigation_node::add()} so that we can change 3309 * the way nodes get added to allow us to simply call add and have the node added to the 3310 * end of the navbar 3311 * 3312 * @param string $text 3313 * @param string|moodle_url|action_link $action An action to associate with this node. 3314 * @param int $type One of navigation_node::TYPE_* 3315 * @param string $shorttext 3316 * @param string|int $key A key to identify this node with. Key + type is unique to a parent. 3317 * @param pix_icon $icon An optional icon to use for this node. 3318 * @return navigation_node 3319 */ 3320 public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) { 3321 if ($this->content !== null) { 3322 debugging('Nav bar items must be printed before $OUTPUT->header() has been called', DEBUG_DEVELOPER); 3323 } 3324 3325 // Properties array used when creating the new navigation node 3326 $itemarray = array( 3327 'text' => $text, 3328 'type' => $type 3329 ); 3330 // Set the action if one was provided 3331 if ($action!==null) { 3332 $itemarray['action'] = $action; 3333 } 3334 // Set the shorttext if one was provided 3335 if ($shorttext!==null) { 3336 $itemarray['shorttext'] = $shorttext; 3337 } 3338 // Set the icon if one was provided 3339 if ($icon!==null) { 3340 $itemarray['icon'] = $icon; 3341 } 3342 // Default the key to the number of children if not provided 3343 if ($key === null) { 3344 $key = count($this->children); 3345 } 3346 // Set the key 3347 $itemarray['key'] = $key; 3348 // Set the parent to this node 3349 $itemarray['parent'] = $this; 3350 // Add the child using the navigation_node_collections add method 3351 $this->children[] = new breadcrumb_navigation_node($itemarray); 3352 return $this; 3353 } 3354 3355 /** 3356 * Prepends a new navigation_node to the start of the navbar 3357 * 3358 * @param string $text 3359 * @param string|moodle_url|action_link $action An action to associate with this node. 3360 * @param int $type One of navigation_node::TYPE_* 3361 * @param string $shorttext 3362 * @param string|int $key A key to identify this node with. Key + type is unique to a parent. 3363 * @param pix_icon $icon An optional icon to use for this node. 3364 * @return navigation_node 3365 */ 3366 public function prepend($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) { 3367 if ($this->content !== null) { 3368 debugging('Nav bar items must be printed before $OUTPUT->header() has been called', DEBUG_DEVELOPER); 3369 } 3370 // Properties array used when creating the new navigation node. 3371 $itemarray = array( 3372 'text' => $text, 3373 'type' => $type 3374 ); 3375 // Set the action if one was provided. 3376 if ($action!==null) { 3377 $itemarray['action'] = $action; 3378 } 3379 // Set the shorttext if one was provided. 3380 if ($shorttext!==null) { 3381 $itemarray['shorttext'] = $shorttext; 3382 } 3383 // Set the icon if one was provided. 3384 if ($icon!==null) { 3385 $itemarray['icon'] = $icon; 3386 } 3387 // Default the key to the number of children if not provided. 3388 if ($key === null) { 3389 $key = count($this->children); 3390 } 3391 // Set the key. 3392 $itemarray['key'] = $key; 3393 // Set the parent to this node. 3394 $itemarray['parent'] = $this; 3395 // Add the child node to the prepend list. 3396 $this->prependchildren[] = new breadcrumb_navigation_node($itemarray); 3397 return $this; 3398 } 3399 } 3400 3401 /** 3402 * Subclass of navigation_node allowing different rendering for the breadcrumbs 3403 * in particular adding extra metadata for search engine robots to leverage. 3404 * 3405 * @package core 3406 * @category navigation 3407 * @copyright 2015 Brendan Heywood 3408 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3409 */ 3410 class breadcrumb_navigation_node extends navigation_node { 3411 3412 /** 3413 * A proxy constructor 3414 * 3415 * @param mixed $navnode A navigation_node or an array 3416 */ 3417 public function __construct($navnode) { 3418 if (is_array($navnode)) { 3419 parent::__construct($navnode); 3420 } else if ($navnode instanceof navigation_node) { 3421 3422 // Just clone everything. 3423 $objvalues = get_object_vars($navnode); 3424 foreach ($objvalues as $key => $value) { 3425 $this->$key = $value; 3426 } 3427 } else { 3428 throw coding_exception('Not a valid breadcrumb_navigation_node'); 3429 } 3430 } 3431 3432 } 3433 3434 /** 3435 * Class used to manage the settings option for the current page 3436 * 3437 * This class is used to manage the settings options in a tree format (recursively) 3438 * and was created initially for use with the settings blocks. 3439 * 3440 * @package core 3441 * @category navigation 3442 * @copyright 2009 Sam Hemelryk 3443 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3444 */ 3445 class settings_navigation extends navigation_node { 3446 /** @var stdClass the current context */ 3447 protected $context; 3448 /** @var moodle_page the moodle page that the navigation belongs to */ 3449 protected $page; 3450 /** @var string contains administration section navigation_nodes */ 3451 protected $adminsection; 3452 /** @var bool A switch to see if the navigation node is initialised */ 3453 protected $initialised = false; 3454 /** @var array An array of users that the nodes can extend for. */ 3455 protected $userstoextendfor = array(); 3456 /** @var navigation_cache **/ 3457 protected $cache; 3458 3459 /** 3460 * Sets up the object with basic settings and preparse it for use 3461 * 3462 * @param moodle_page $page 3463 */ 3464 public function __construct(moodle_page &$page) { 3465 if (during_initial_install()) { 3466 return false; 3467 } 3468 $this->page = $page; 3469 // Initialise the main navigation. It is most important that this is done 3470 // before we try anything 3471 $this->page->navigation->initialise(); 3472 // Initialise the navigation cache 3473 $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME); 3474 $this->children = new navigation_node_collection(); 3475 } 3476 /** 3477 * Initialise the settings navigation based on the current context 3478 * 3479 * This function initialises the settings navigation tree for a given context 3480 * by calling supporting functions to generate major parts of the tree. 3481 * 3482 */ 3483 public function initialise() { 3484 global $DB, $SESSION, $SITE; 3485 3486 if (during_initial_install()) { 3487 return false; 3488 } else if ($this->initialised) { 3489 return true; 3490 } 3491 $this->id = 'settingsnav'; 3492 $this->context = $this->page->context; 3493 3494 $context = $this->context; 3495 if ($context->contextlevel == CONTEXT_BLOCK) { 3496 $this->load_block_settings(); 3497 $context = $context->get_parent_context(); 3498 } 3499 switch ($context->contextlevel) { 3500 case CONTEXT_SYSTEM: 3501 if ($this->page->url->compare(new moodle_url('/admin/settings.php', array('section'=>'frontpagesettings')))) { 3502 $this->load_front_page_settings(($context->id == $this->context->id)); 3503 } 3504 break; 3505 case CONTEXT_COURSECAT: 3506 $this->load_category_settings(); 3507 break; 3508 case CONTEXT_COURSE: 3509 if ($this->page->course->id != $SITE->id) { 3510 $this->load_course_settings(($context->id == $this->context->id)); 3511 } else { 3512 $this->load_front_page_settings(($context->id == $this->context->id)); 3513 } 3514 break; 3515 case CONTEXT_MODULE: 3516 $this->load_module_settings(); 3517 $this->load_course_settings(); 3518 break; 3519 case CONTEXT_USER: 3520 if ($this->page->course->id != $SITE->id) { 3521 $this->load_course_settings(); 3522 } 3523 break; 3524 } 3525 3526 $usersettings = $this->load_user_settings($this->page->course->id); 3527 3528 $adminsettings = false; 3529 if (isloggedin() && !isguestuser() && (!isset($SESSION->load_navigation_admin) || $SESSION->load_navigation_admin)) { 3530 $isadminpage = $this->is_admin_tree_needed(); 3531 3532 if (has_capability('moodle/site:config', context_system::instance())) { 3533 // Make sure this works even if config capability changes on the fly 3534 // and also make it fast for admin right after login. 3535 $SESSION->load_navigation_admin = 1; 3536 if ($isadminpage) { 3537 $adminsettings = $this->load_administration_settings(); 3538 } 3539 3540 } else if (!isset($SESSION->load_navigation_admin)) { 3541 $adminsettings = $this->load_administration_settings(); 3542 $SESSION->load_navigation_admin = (int)($adminsettings->children->count() > 0); 3543 3544 } else if ($SESSION->load_navigation_admin) { 3545 if ($isadminpage) { 3546 $adminsettings = $this->load_administration_settings(); 3547 } 3548 } 3549 3550 // Print empty navigation node, if needed. 3551 if ($SESSION->load_navigation_admin && !$isadminpage) { 3552 if ($adminsettings) { 3553 // Do not print settings tree on pages that do not need it, this helps with performance. 3554 $adminsettings->remove(); 3555 $adminsettings = false; 3556 } 3557 $siteadminnode = $this->add(get_string('administrationsite'), new moodle_url('/admin'), self::TYPE_SITE_ADMIN, null, 'siteadministration'); 3558 $siteadminnode->id = 'expandable_branch_'.$siteadminnode->type.'_'.clean_param($siteadminnode->key, PARAM_ALPHANUMEXT); 3559 $siteadminnode->requiresajaxloading = 'true'; 3560 } 3561 } 3562 3563 if ($context->contextlevel == CONTEXT_SYSTEM && $adminsettings) { 3564 $adminsettings->force_open(); 3565 } else if ($context->contextlevel == CONTEXT_USER && $usersettings) { 3566 $usersettings->force_open(); 3567 } 3568 3569 // At this point we give any local plugins the ability to extend/tinker with the navigation settings. 3570 $this->load_local_plugin_settings(); 3571 3572 foreach ($this->children as $key=>$node) { 3573 if ($node->nodetype == self::NODETYPE_BRANCH && $node->children->count() == 0) { 3574 // Site administration is shown as link. 3575 if (!empty($SESSION->load_navigation_admin) && ($node->type === self::TYPE_SITE_ADMIN)) { 3576 continue; 3577 } 3578 $node->remove(); 3579 } 3580 } 3581 $this->initialised = true; 3582 } 3583 /** 3584 * Override the parent function so that we can add preceeding hr's and set a 3585 * root node class against all first level element 3586 * 3587 * It does this by first calling the parent's add method {@link navigation_node::add()} 3588 * and then proceeds to use the key to set class and hr 3589 * 3590 * @param string $text text to be used for the link. 3591 * @param string|moodle_url $url url for the new node 3592 * @param int $type the type of node navigation_node::TYPE_* 3593 * @param string $shorttext 3594 * @param string|int $key a key to access the node by. 3595 * @param pix_icon $icon An icon that appears next to the node. 3596 * @return navigation_node with the new node added to it. 3597 */ 3598 public function add($text, $url=null, $type=null, $shorttext=null, $key=null, pix_icon $icon=null) { 3599 $node = parent::add($text, $url, $type, $shorttext, $key, $icon); 3600 $node->add_class('root_node'); 3601 return $node; 3602 } 3603 3604 /** 3605 * This function allows the user to add something to the start of the settings 3606 * navigation, which means it will be at the top of the settings navigation block 3607 * 3608 * @param string $text text to be used for the link. 3609 * @param string|moodle_url $url url for the new node 3610 * @param int $type the type of node navigation_node::TYPE_* 3611 * @param string $shorttext 3612 * @param string|int $key a key to access the node by. 3613 * @param pix_icon $icon An icon that appears next to the node. 3614 * @return navigation_node $node with the new node added to it. 3615 */ 3616 public function prepend($text, $url=null, $type=null, $shorttext=null, $key=null, pix_icon $icon=null) { 3617 $children = $this->children; 3618 $childrenclass = get_class($children); 3619 $this->children = new $childrenclass; 3620 $node = $this->add($text, $url, $type, $shorttext, $key, $icon); 3621 foreach ($children as $child) { 3622 $this->children->add($child); 3623 } 3624 return $node; 3625 } 3626 3627 /** 3628 * Does this page require loading of full admin tree or is 3629 * it enough rely on AJAX? 3630 * 3631 * @return bool 3632 */ 3633 protected function is_admin_tree_needed() { 3634 if (self::$loadadmintree) { 3635 // Usually external admin page or settings page. 3636 return true; 3637 } 3638 3639 if ($this->page->pagelayout === 'admin' or strpos($this->page->pagetype, 'admin-') === 0) { 3640 // Admin settings tree is intended for system level settings and management only, use navigation for the rest! 3641 if ($this->page->context->contextlevel != CONTEXT_SYSTEM) { 3642 return false; 3643 } 3644 return true; 3645 } 3646 3647 return false; 3648 } 3649 3650 /** 3651 * Load the site administration tree 3652 * 3653 * This function loads the site administration tree by using the lib/adminlib library functions 3654 * 3655 * @param navigation_node $referencebranch A reference to a branch in the settings 3656 * navigation tree 3657 * @param part_of_admin_tree $adminbranch The branch to add, if null generate the admin 3658 * tree and start at the beginning 3659 * @return mixed A key to access the admin tree by 3660 */ 3661 protected function load_administration_settings(navigation_node $referencebranch=null, part_of_admin_tree $adminbranch=null) { 3662 global $CFG; 3663 3664 // Check if we are just starting to generate this navigation. 3665 if ($referencebranch === null) { 3666 3667 // Require the admin lib then get an admin structure 3668 if (!function_exists('admin_get_root')) { 3669 require_once($CFG->dirroot.'/lib/adminlib.php'); 3670 } 3671 $adminroot = admin_get_root(false, false); 3672 // This is the active section identifier 3673 $this->adminsection = $this->page->url->param('section'); 3674 3675 // Disable the navigation from automatically finding the active node 3676 navigation_node::$autofindactive = false; 3677 $referencebranch = $this->add(get_string('administrationsite'), null, self::TYPE_SITE_ADMIN, null, 'root'); 3678 foreach ($adminroot->children as $adminbranch) { 3679 $this->load_administration_settings($referencebranch, $adminbranch); 3680 } 3681 navigation_node::$autofindactive = true; 3682 3683 // Use the admin structure to locate the active page 3684 if (!$this->contains_active_node() && $current = $adminroot->locate($this->adminsection, true)) { 3685 $currentnode = $this; 3686 while (($pathkey = array_pop($current->path))!==null && $currentnode) { 3687 $currentnode = $currentnode->get($pathkey); 3688 } 3689 if ($currentnode) { 3690 $currentnode->make_active(); 3691 } 3692 } else { 3693 $this->scan_for_active_node($referencebranch); 3694 } 3695 return $referencebranch; 3696 } else if ($adminbranch->check_access()) { 3697 // We have a reference branch that we can access and is not hidden `hurrah` 3698 // Now we need to display it and any children it may have 3699 $url = null; 3700 $icon = null; 3701 if ($adminbranch instanceof admin_settingpage) { 3702 $url = new moodle_url('/'.$CFG->admin.'/settings.php', array('section'=>$adminbranch->name)); 3703 } else if ($adminbranch instanceof admin_externalpage) { 3704 $url = $adminbranch->url; 3705 } else if (!empty($CFG->linkadmincategories) && $adminbranch instanceof admin_category) { 3706 $url = new moodle_url('/'.$CFG->admin.'/category.php', array('category' => $adminbranch->name)); 3707 } 3708 3709 // Add the branch 3710 $reference = $referencebranch->add($adminbranch->visiblename, $url, self::TYPE_SETTING, null, $adminbranch->name, $icon); 3711 3712 if ($adminbranch->is_hidden()) { 3713 if (($adminbranch instanceof admin_externalpage || $adminbranch instanceof admin_settingpage) && $adminbranch->name == $this->adminsection) { 3714 $reference->add_class('hidden'); 3715 } else { 3716 $reference->display = false; 3717 } 3718 } 3719 3720 // Check if we are generating the admin notifications and whether notificiations exist 3721 if ($adminbranch->name === 'adminnotifications' && admin_critical_warnings_present()) { 3722 $reference->add_class('criticalnotification'); 3723 } 3724 // Check if this branch has children 3725 if ($reference && isset($adminbranch->children) && is_array($adminbranch->children) && count($adminbranch->children)>0) { 3726 foreach ($adminbranch->children as $branch) { 3727 // Generate the child branches as well now using this branch as the reference 3728 $this->load_administration_settings($reference, $branch); 3729 } 3730 } else { 3731 $reference->icon = new pix_icon('i/settings', ''); 3732 } 3733 } 3734 } 3735 3736 /** 3737 * This function recursivily scans nodes until it finds the active node or there 3738 * are no more nodes. 3739 * @param navigation_node $node 3740 */ 3741 protected function scan_for_active_node(navigation_node $node) { 3742 if (!$node->check_if_active() && $node->children->count()>0) { 3743 foreach ($node->children as &$child) { 3744 $this->scan_for_active_node($child); 3745 } 3746 } 3747 } 3748 3749 /** 3750 * Gets a navigation node given an array of keys that represent the path to 3751 * the desired node. 3752 * 3753 * @param array $path 3754 * @return navigation_node|false 3755 */ 3756 protected function get_by_path(array $path) { 3757 $node = $this->get(array_shift($path)); 3758 foreach ($path as $key) { 3759 $node->get($key); 3760 } 3761 return $node; 3762 } 3763 3764 /** 3765 * This function loads the course settings that are available for the user 3766 * 3767 * @param bool $forceopen If set to true the course node will be forced open 3768 * @return navigation_node|false 3769 */ 3770 protected function load_course_settings($forceopen = false) { 3771 global $CFG; 3772 3773 $course = $this->page->course; 3774 $coursecontext = context_course::instance($course->id); 3775 3776 // note: do not test if enrolled or viewing here because we need the enrol link in Course administration section 3777 3778 $coursenode = $this->add(get_string('courseadministration'), null, self::TYPE_COURSE, null, 'courseadmin'); 3779 if ($forceopen) { 3780 $coursenode->force_open(); 3781 } 3782 3783 if ($this->page->user_allowed_editing()) { 3784 // Add the turn on/off settings 3785 3786 if ($this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) { 3787 // We are on the course page, retain the current page params e.g. section. 3788 $baseurl = clone($this->page->url); 3789 $baseurl->param('sesskey', sesskey()); 3790 } else { 3791 // Edit on the main course page. 3792 $baseurl = new moodle_url('/course/view.php', array('id'=>$course->id, 'return'=>$this->page->url->out_as_local_url(false), 'sesskey'=>sesskey())); 3793 } 3794 3795 $editurl = clone($baseurl); 3796 if ($this->page->user_is_editing()) { 3797 $editurl->param('edit', 'off'); 3798 $editstring = get_string('turneditingoff'); 3799 } else { 3800 $editurl->param('edit', 'on'); 3801 $editstring = get_string('turneditingon'); 3802 } 3803 $coursenode->add($editstring, $editurl, self::TYPE_SETTING, null, 'turneditingonoff', new pix_icon('i/edit', '')); 3804 } 3805 3806 if (has_capability('moodle/course:update', $coursecontext)) { 3807 // Add the course settings link 3808 $url = new moodle_url('/course/edit.php', array('id'=>$course->id)); 3809 $coursenode->add(get_string('editsettings'), $url, self::TYPE_SETTING, null, 'editsettings', new pix_icon('i/settings', '')); 3810 3811 // Add the course completion settings link 3812 if ($CFG->enablecompletion && $course->enablecompletion) { 3813 $url = new moodle_url('/course/completion.php', array('id'=>$course->id)); 3814 $coursenode->add(get_string('coursecompletion', 'completion'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', '')); 3815 } 3816 } else if (has_capability('moodle/course:tag', $coursecontext)) { 3817 $url = new moodle_url('/course/tags.php', array('id' => $course->id)); 3818 $coursenode->add(get_string('coursetags', 'tag'), $url, self::TYPE_SETTING, null, 'coursetags', new pix_icon('i/settings', '')); 3819 } 3820 3821 // add enrol nodes 3822 enrol_add_course_navigation($coursenode, $course); 3823 3824 // Manage filters 3825 if (has_capability('moodle/filter:manage', $coursecontext) && count(filter_get_available_in_context($coursecontext))>0) { 3826 $url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id)); 3827 $coursenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/filter', '')); 3828 } 3829 3830 // View course reports. 3831 if (has_capability('moodle/site:viewreports', $coursecontext)) { // Basic capability for listing of reports. 3832 $reportnav = $coursenode->add(get_string('reports'), null, self::TYPE_CONTAINER, null, 'coursereports', 3833 new pix_icon('i/stats', '')); 3834 $coursereports = core_component::get_plugin_list('coursereport'); 3835 foreach ($coursereports as $report => $dir) { 3836 $libfile = $CFG->dirroot.'/course/report/'.$report.'/lib.php'; 3837 if (file_exists($libfile)) { 3838 require_once($libfile); 3839 $reportfunction = $report.'_report_extend_navigation'; 3840 if (function_exists($report.'_report_extend_navigation')) { 3841 $reportfunction($reportnav, $course, $coursecontext); 3842 } 3843 } 3844 } 3845 3846 $reports = get_plugin_list_with_function('report', 'extend_navigation_course', 'lib.php'); 3847 foreach ($reports as $reportfunction) { 3848 $reportfunction($reportnav, $course, $coursecontext); 3849 } 3850 } 3851 3852 // Add view grade report is permitted 3853 $reportavailable = false; 3854 if (has_capability('moodle/grade:viewall', $coursecontext)) { 3855 $reportavailable = true; 3856 } else if (!empty($course->showgrades)) { 3857 $reports = core_component::get_plugin_list('gradereport'); 3858 if (is_array($reports) && count($reports)>0) { // Get all installed reports 3859 arsort($reports); // user is last, we want to test it first 3860 foreach ($reports as $plugin => $plugindir) { 3861 if (has_capability('gradereport/'.$plugin.':view', $coursecontext)) { 3862 //stop when the first visible plugin is found 3863 $reportavailable = true; 3864 break; 3865 } 3866 } 3867 } 3868 } 3869 if ($reportavailable) { 3870 $url = new moodle_url('/grade/report/index.php', array('id'=>$course->id)); 3871 $gradenode = $coursenode->add(get_string('grades'), $url, self::TYPE_SETTING, null, 'grades', new pix_icon('i/grades', '')); 3872 } 3873 3874 // Check if we can view the gradebook's setup page. 3875 if (has_capability('moodle/grade:manage', $coursecontext)) { 3876 $url = new moodle_url('/grade/edit/tree/index.php', array('id' => $course->id)); 3877 $coursenode->add(get_string('gradebooksetup', 'grades'), $url, self::TYPE_SETTING, 3878 null, 'gradebooksetup', new pix_icon('i/settings', '')); 3879 } 3880 3881 // Add outcome if permitted 3882 if (!empty($CFG->enableoutcomes) && has_capability('moodle/course:update', $coursecontext)) { 3883 $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$course->id)); 3884 $coursenode->add(get_string('outcomes', 'grades'), $url, self::TYPE_SETTING, null, 'outcomes', new pix_icon('i/outcomes', '')); 3885 } 3886 3887 //Add badges navigation 3888 if (!empty($CFG->enablebadges)) { 3889 require_once($CFG->libdir .'/badgeslib.php'); 3890 badges_add_course_navigation($coursenode, $course); 3891 } 3892 3893 // Backup this course 3894 if (has_capability('moodle/backup:backupcourse', $coursecontext)) { 3895 $url = new moodle_url('/backup/backup.php', array('id'=>$course->id)); 3896 $coursenode->add(get_string('backup'), $url, self::TYPE_SETTING, null, 'backup', new pix_icon('i/backup', '')); 3897 } 3898 3899 // Restore to this course 3900 if (has_capability('moodle/restore:restorecourse', $coursecontext)) { 3901 $url = new moodle_url('/backup/restorefile.php', array('contextid'=>$coursecontext->id)); 3902 $coursenode->add(get_string('restore'), $url, self::TYPE_SETTING, null, 'restore', new pix_icon('i/restore', '')); 3903 } 3904 3905 // Import data from other courses 3906 if (has_capability('moodle/restore:restoretargetimport', $coursecontext)) { 3907 $url = new moodle_url('/backup/import.php', array('id'=>$course->id)); 3908 $coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, 'import', new pix_icon('i/import', '')); 3909 } 3910 3911 // Publish course on a hub 3912 if (has_capability('moodle/course:publish', $coursecontext)) { 3913 $url = new moodle_url('/course/publish/index.php', array('id'=>$course->id)); 3914 $coursenode->add(get_string('publish'), $url, self::TYPE_SETTING, null, 'publish', new pix_icon('i/publish', '')); 3915 } 3916 3917 // Reset this course 3918 if (has_capability('moodle/course:reset', $coursecontext)) { 3919 $url = new moodle_url('/course/reset.php', array('id'=>$course->id)); 3920 $coursenode->add(get_string('reset'), $url, self::TYPE_SETTING, null, 'reset', new pix_icon('i/return', '')); 3921 } 3922 3923 // Questions 3924 require_once($CFG->libdir . '/questionlib.php'); 3925 question_extend_settings_navigation($coursenode, $coursecontext)->trim_if_empty(); 3926 3927 if (has_capability('moodle/course:update', $coursecontext)) { 3928 // Repository Instances 3929 if (!$this->cache->cached('contexthasrepos'.$coursecontext->id)) { 3930 require_once($CFG->dirroot . '/repository/lib.php'); 3931 $editabletypes = repository::get_editable_types($coursecontext); 3932 $haseditabletypes = !empty($editabletypes); 3933 unset($editabletypes); 3934 $this->cache->set('contexthasrepos'.$coursecontext->id, $haseditabletypes); 3935 } else { 3936 $haseditabletypes = $this->cache->{'contexthasrepos'.$coursecontext->id}; 3937 } 3938 if ($haseditabletypes) { 3939 $url = new moodle_url('/repository/manage_instances.php', array('contextid' => $coursecontext->id)); 3940 $coursenode->add(get_string('repositories'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/repository', '')); 3941 } 3942 } 3943 3944 // Manage files 3945 if ($course->legacyfiles == 2 and has_capability('moodle/course:managefiles', $coursecontext)) { 3946 // hidden in new courses and courses where legacy files were turned off 3947 $url = new moodle_url('/files/index.php', array('contextid'=>$coursecontext->id)); 3948 $coursenode->add(get_string('courselegacyfiles'), $url, self::TYPE_SETTING, null, 'coursefiles', new pix_icon('i/folder', '')); 3949 3950 } 3951 3952 // Switch roles 3953 $roles = array(); 3954 $assumedrole = $this->in_alternative_role(); 3955 if ($assumedrole !== false) { 3956 $roles[0] = get_string('switchrolereturn'); 3957 } 3958 if (has_capability('moodle/role:switchroles', $coursecontext)) { 3959 $availableroles = get_switchable_roles($coursecontext); 3960 if (is_array($availableroles)) { 3961 foreach ($availableroles as $key=>$role) { 3962 if ($assumedrole == (int)$key) { 3963 continue; 3964 } 3965 $roles[$key] = $role; 3966 } 3967 } 3968 } 3969 if (is_array($roles) && count($roles)>0) { 3970 $switchroles = $this->add(get_string('switchroleto'), null, self::TYPE_CONTAINER, null, 'switchroleto'); 3971 if ((count($roles)==1 && array_key_exists(0, $roles))|| $assumedrole!==false) { 3972 $switchroles->force_open(); 3973 } 3974 foreach ($roles as $key => $name) { 3975 $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id, 'sesskey'=>sesskey(), 'switchrole'=>$key, 'returnurl'=>$this->page->url->out_as_local_url(false))); 3976 $switchroles->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/switchrole', '')); 3977 } 3978 } 3979 3980 // Let plugins hook into course navigation. 3981 $pluginsfunction = get_plugins_with_function('extend_navigation_course', 'lib.php'); 3982 foreach ($pluginsfunction as $plugintype => $plugins) { 3983 // Ignore the report plugin as it was already loaded above. 3984 if ($plugintype == 'report') { 3985 continue; 3986 } 3987 foreach ($plugins as $pluginfunction) { 3988 $pluginfunction($coursenode, $course, $coursecontext); 3989 } 3990 } 3991 3992 // Return we are done 3993 return $coursenode; 3994 } 3995 3996 /** 3997 * This function calls the module function to inject module settings into the 3998 * settings navigation tree. 3999 * 4000 * This only gets called if there is a corrosponding function in the modules 4001 * lib file. 4002 * 4003 * For examples mod/forum/lib.php {@link forum_extend_settings_navigation()} 4004 * 4005 * @return navigation_node|false 4006 */ 4007 protected function load_module_settings() { 4008 global $CFG; 4009 4010 if (!$this->page->cm && $this->context->contextlevel == CONTEXT_MODULE && $this->context->instanceid) { 4011 $cm = get_coursemodule_from_id(false, $this->context->instanceid, 0, false, MUST_EXIST); 4012 $this->page->set_cm($cm, $this->page->course); 4013 } 4014 4015 $file = $CFG->dirroot.'/mod/'.$this->page->activityname.'/lib.php'; 4016 if (file_exists($file)) { 4017 require_once($file); 4018 } 4019 4020 $modulenode = $this->add(get_string('pluginadministration', $this->page->activityname), null, self::TYPE_SETTING, null, 'modulesettings'); 4021 $modulenode->nodetype = navigation_node::NODETYPE_BRANCH; 4022 $modulenode->force_open(); 4023 4024 // Settings for the module 4025 if (has_capability('moodle/course:manageactivities', $this->page->cm->context)) { 4026 $url = new moodle_url('/course/modedit.php', array('update' => $this->page->cm->id, 'return' => 1)); 4027 $modulenode->add(get_string('editsettings'), $url, navigation_node::TYPE_SETTING, null, 'modedit'); 4028 } 4029 // Assign local roles 4030 if (count(get_assignable_roles($this->page->cm->context))>0) { 4031 $url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->page->cm->context->id)); 4032 $modulenode->add(get_string('localroles', 'role'), $url, self::TYPE_SETTING, null, 'roleassign'); 4033 } 4034 // Override roles 4035 if (has_capability('moodle/role:review', $this->page->cm->context) or count(get_overridable_roles($this->page->cm->context))>0) { 4036 $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->page->cm->context->id)); 4037 $modulenode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, 'roleoverride'); 4038 } 4039 // Check role permissions 4040 if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->page->cm->context)) { 4041 $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->page->cm->context->id)); 4042 $modulenode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, 'rolecheck'); 4043 } 4044 // Manage filters 4045 if (has_capability('moodle/filter:manage', $this->page->cm->context) && count(filter_get_available_in_context($this->page->cm->context))>0) { 4046 $url = new moodle_url('/filter/manage.php', array('contextid'=>$this->page->cm->context->id)); 4047 $modulenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, 'filtermanage'); 4048 } 4049 // Add reports 4050 $reports = get_plugin_list_with_function('report', 'extend_navigation_module', 'lib.php'); 4051 foreach ($reports as $reportfunction) { 4052 $reportfunction($modulenode, $this->page->cm); 4053 } 4054 // Add a backup link 4055 $featuresfunc = $this->page->activityname.'_supports'; 4056 if (function_exists($featuresfunc) && $featuresfunc(FEATURE_BACKUP_MOODLE2) && has_capability('moodle/backup:backupactivity', $this->page->cm->context)) { 4057 $url = new moodle_url('/backup/backup.php', array('id'=>$this->page->cm->course, 'cm'=>$this->page->cm->id)); 4058 $modulenode->add(get_string('backup'), $url, self::TYPE_SETTING, null, 'backup'); 4059 } 4060 4061 // Restore this activity 4062 $featuresfunc = $this->page->activityname.'_supports'; 4063 if (function_exists($featuresfunc) && $featuresfunc(FEATURE_BACKUP_MOODLE2) && has_capability('moodle/restore:restoreactivity', $this->page->cm->context)) { 4064 $url = new moodle_url('/backup/restorefile.php', array('contextid'=>$this->page->cm->context->id)); 4065 $modulenode->add(get_string('restore'), $url, self::TYPE_SETTING, null, 'restore'); 4066 } 4067 4068 // Allow the active advanced grading method plugin to append its settings 4069 $featuresfunc = $this->page->activityname.'_supports'; 4070 if (function_exists($featuresfunc) && $featuresfunc(FEATURE_ADVANCED_GRADING) && has_capability('moodle/grade:managegradingforms', $this->page->cm->context)) { 4071 require_once($CFG->dirroot.'/grade/grading/lib.php'); 4072 $gradingman = get_grading_manager($this->page->cm->context, 'mod_'.$this->page->activityname); 4073 $gradingman->extend_settings_navigation($this, $modulenode); 4074 } 4075 4076 $function = $this->page->activityname.'_extend_settings_navigation'; 4077 if (function_exists($function)) { 4078 $function($this, $modulenode); 4079 } 4080 4081 // Remove the module node if there are no children. 4082 if ($modulenode->children->count() <= 0) { 4083 $modulenode->remove(); 4084 } 4085 4086 return $modulenode; 4087 } 4088 4089 /** 4090 * Loads the user settings block of the settings nav 4091 * 4092 * This function is simply works out the userid and whether we need to load 4093 * just the current users profile settings, or the current user and the user the 4094 * current user is viewing. 4095 * 4096 * This function has some very ugly code to work out the user, if anyone has 4097 * any bright ideas please feel free to intervene. 4098 * 4099 * @param int $courseid The course id of the current course 4100 * @return navigation_node|false 4101 */ 4102 protected function load_user_settings($courseid = SITEID) { 4103 global $USER, $CFG; 4104 4105 if (isguestuser() || !isloggedin()) { 4106 return false; 4107 } 4108 4109 $navusers = $this->page->navigation->get_extending_users(); 4110 4111 if (count($this->userstoextendfor) > 0 || count($navusers) > 0) { 4112 $usernode = null; 4113 foreach ($this->userstoextendfor as $userid) { 4114 if ($userid == $USER->id) { 4115 continue; 4116 } 4117 $node = $this->generate_user_settings($courseid, $userid, 'userviewingsettings'); 4118 if (is_null($usernode)) { 4119 $usernode = $node; 4120 } 4121 } 4122 foreach ($navusers as $user) { 4123 if ($user->id == $USER->id) { 4124 continue; 4125 } 4126 $node = $this->generate_user_settings($courseid, $user->id, 'userviewingsettings'); 4127 if (is_null($usernode)) { 4128 $usernode = $node; 4129 } 4130 } 4131 $this->generate_user_settings($courseid, $USER->id); 4132 } else { 4133 $usernode = $this->generate_user_settings($courseid, $USER->id); 4134 } 4135 return $usernode; 4136 } 4137 4138 /** 4139 * Extends the settings navigation for the given user. 4140 * 4141 * Note: This method gets called automatically if you call 4142 * $PAGE->navigation->extend_for_user($userid) 4143 * 4144 * @param int $userid 4145 */ 4146 public function extend_for_user($userid) { 4147 global $CFG; 4148 4149 if (!in_array($userid, $this->userstoextendfor)) { 4150 $this->userstoextendfor[] = $userid; 4151 if ($this->initialised) { 4152 $this->generate_user_settings($this->page->course->id, $userid, 'userviewingsettings'); 4153 $children = array(); 4154 foreach ($this->children as $child) { 4155 $children[] = $child; 4156 } 4157 array_unshift($children, array_pop($children)); 4158 $this->children = new navigation_node_collection(); 4159 foreach ($children as $child) { 4160 $this->children->add($child); 4161 } 4162 } 4163 } 4164 } 4165 4166 /** 4167 * This function gets called by {@link settings_navigation::load_user_settings()} and actually works out 4168 * what can be shown/done 4169 * 4170 * @param int $courseid The current course' id 4171 * @param int $userid The user id to load for 4172 * @param string $gstitle The string to pass to get_string for the branch title 4173 * @return navigation_node|false 4174 */ 4175 protected function generate_user_settings($courseid, $userid, $gstitle='usercurrentsettings') { 4176 global $DB, $CFG, $USER, $SITE; 4177 4178 if ($courseid != $SITE->id) { 4179 if (!empty($this->page->course->id) && $this->page->course->id == $courseid) { 4180 $course = $this->page->course; 4181 } else { 4182 $select = context_helper::get_preload_record_columns_sql('ctx'); 4183 $sql = "SELECT c.*, $select 4184 FROM {course} c 4185 JOIN {context} ctx ON c.id = ctx.instanceid 4186 WHERE c.id = :courseid AND ctx.contextlevel = :contextlevel"; 4187 $params = array('courseid' => $courseid, 'contextlevel' => CONTEXT_COURSE); 4188 $course = $DB->get_record_sql($sql, $params, MUST_EXIST); 4189 context_helper::preload_from_record($course); 4190 } 4191 } else { 4192 $course = $SITE; 4193 } 4194 4195 $coursecontext = context_course::instance($course->id); // Course context 4196 $systemcontext = context_system::instance(); 4197 $currentuser = ($USER->id == $userid); 4198 4199 if ($currentuser) { 4200 $user = $USER; 4201 $usercontext = context_user::instance($user->id); // User context 4202 } else { 4203 $select = context_helper::get_preload_record_columns_sql('ctx'); 4204 $sql = "SELECT u.*, $select 4205 FROM {user} u 4206 JOIN {context} ctx ON u.id = ctx.instanceid 4207 WHERE u.id = :userid AND ctx.contextlevel = :contextlevel"; 4208 $params = array('userid' => $userid, 'contextlevel' => CONTEXT_USER); 4209 $user = $DB->get_record_sql($sql, $params, IGNORE_MISSING); 4210 if (!$user) { 4211 return false; 4212 } 4213 context_helper::preload_from_record($user); 4214 4215 // Check that the user can view the profile 4216 $usercontext = context_user::instance($user->id); // User context 4217 $canviewuser = has_capability('moodle/user:viewdetails', $usercontext); 4218 4219 if ($course->id == $SITE->id) { 4220 if ($CFG->forceloginforprofiles && !has_coursecontact_role($user->id) && !$canviewuser) { // Reduce possibility of "browsing" userbase at site level 4221 // Teachers can browse and be browsed at site level. If not forceloginforprofiles, allow access (bug #4366) 4222 return false; 4223 } 4224 } else { 4225 $canviewusercourse = has_capability('moodle/user:viewdetails', $coursecontext); 4226 $userisenrolled = is_enrolled($coursecontext, $user->id, '', true); 4227 if ((!$canviewusercourse && !$canviewuser) || !$userisenrolled) { 4228 return false; 4229 } 4230 $canaccessallgroups = has_capability('moodle/site:accessallgroups', $coursecontext); 4231 if (!$canaccessallgroups && groups_get_course_groupmode($course) == SEPARATEGROUPS && !$canviewuser) { 4232 // If groups are in use, make sure we can see that group (MDL-45874). That does not apply to parents. 4233 if ($courseid == $this->page->course->id) { 4234 $mygroups = get_fast_modinfo($this->page->course)->groups; 4235 } else { 4236 $mygroups = groups_get_user_groups($courseid); 4237 } 4238 $usergroups = groups_get_user_groups($courseid, $userid); 4239 if (!array_intersect_key($mygroups[0], $usergroups[0])) { 4240 return false; 4241 } 4242 } 4243 } 4244 } 4245 4246 $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $this->page->context)); 4247 4248 $key = $gstitle; 4249 $prefurl = new moodle_url('/user/preferences.php'); 4250 if ($gstitle != 'usercurrentsettings') { 4251 $key .= $userid; 4252 $prefurl->param('userid', $userid); 4253 } 4254 4255 // Add a user setting branch. 4256 if ($gstitle == 'usercurrentsettings') { 4257 $dashboard = $this->add(get_string('myhome'), new moodle_url('/my/'), self::TYPE_CONTAINER, null, 'dashboard'); 4258 // This should be set to false as we don't want to show this to the user. It's only for generating the correct 4259 // breadcrumb. 4260 $dashboard->display = false; 4261 if (get_home_page() == HOMEPAGE_MY) { 4262 $dashboard->mainnavonly = true; 4263 } 4264 4265 $iscurrentuser = ($user->id == $USER->id); 4266 4267 $baseargs = array('id' => $user->id); 4268 if ($course->id != $SITE->id && !$iscurrentuser) { 4269 $baseargs['course'] = $course->id; 4270 $issitecourse = false; 4271 } else { 4272 // Load all categories and get the context for the system. 4273 $issitecourse = true; 4274 } 4275 4276 // Add the user profile to the dashboard. 4277 $profilenode = $dashboard->add(get_string('profile'), new moodle_url('/user/profile.php', 4278 array('id' => $user->id)), self::TYPE_SETTING, null, 'myprofile'); 4279 4280 if (!empty($CFG->navadduserpostslinks)) { 4281 // Add nodes for forum posts and discussions if the user can view either or both 4282 // There are no capability checks here as the content of the page is based 4283 // purely on the forums the current user has access too. 4284 $forumtab = $profilenode->add(get_string('forumposts', 'forum')); 4285 $forumtab->add(get_string('posts', 'forum'), new moodle_url('/mod/forum/user.php', $baseargs), null, 'myposts'); 4286 $forumtab->add(get_string('discussions', 'forum'), new moodle_url('/mod/forum/user.php', 4287 array_merge($baseargs, array('mode' => 'discussions'))), null, 'mydiscussions'); 4288 } 4289 4290 // Add blog nodes. 4291 if (!empty($CFG->enableblogs)) { 4292 if (!$this->cache->cached('userblogoptions'.$user->id)) { 4293 require_once($CFG->dirroot.'/blog/lib.php'); 4294 // Get all options for the user. 4295 $options = blog_get_options_for_user($user); 4296 $this->cache->set('userblogoptions'.$user->id, $options); 4297 } else { 4298 $options = $this->cache->{'userblogoptions'.$user->id}; 4299 } 4300 4301 if (count($options) > 0) { 4302 $blogs = $profilenode->add(get_string('blogs', 'blog'), null, navigation_node::TYPE_CONTAINER); 4303 foreach ($options as $type => $option) { 4304 if ($type == "rss") { 4305 $blogs->add($option['string'], $option['link'], self::TYPE_SETTING, null, null, 4306 new pix_icon('i/rss', '')); 4307 } else { 4308 $blogs->add($option['string'], $option['link'], self::TYPE_SETTING, null, 'blog' . $type); 4309 } 4310 } 4311 } 4312 } 4313 4314 // Add the messages link. 4315 // It is context based so can appear in the user's profile and in course participants information. 4316 if (!empty($CFG->messaging)) { 4317 $messageargs = array('user1' => $USER->id); 4318 if ($USER->id != $user->id) { 4319 $messageargs['user2'] = $user->id; 4320 } 4321 if ($course->id != $SITE->id) { 4322 $messageargs['viewing'] = MESSAGE_VIEW_COURSE. $course->id; 4323 } 4324 $url = new moodle_url('/message/index.php', $messageargs); 4325 $dashboard->add(get_string('messages', 'message'), $url, self::TYPE_SETTING, null, 'messages'); 4326 } 4327 4328 // Add the "My private files" link. 4329 // This link doesn't have a unique display for course context so only display it under the user's profile. 4330 if ($issitecourse && $iscurrentuser && has_capability('moodle/user:manageownfiles', $usercontext)) { 4331 $url = new moodle_url('/user/files.php'); 4332 $dashboard->add(get_string('privatefiles'), $url, self::TYPE_SETTING); 4333 } 4334 4335 // Add a node to view the users notes if permitted. 4336 if (!empty($CFG->enablenotes) && 4337 has_any_capability(array('moodle/notes:manage', 'moodle/notes:view'), $coursecontext)) { 4338 $url = new moodle_url('/notes/index.php', array('user' => $user->id)); 4339 if ($coursecontext->instanceid != SITEID) { 4340 $url->param('course', $coursecontext->instanceid); 4341 } 4342 $profilenode->add(get_string('notes', 'notes'), $url); 4343 } 4344 4345 // Show the grades node. 4346 if (($issitecourse && $iscurrentuser) || has_capability('moodle/user:viewdetails', $usercontext)) { 4347 require_once($CFG->dirroot . '/user/lib.php'); 4348 // Set the grades node to link to the "Grades" page. 4349 if ($course->id == SITEID) { 4350 $url = user_mygrades_url($user->id, $course->id); 4351 } else { // Otherwise we are in a course and should redirect to the user grade report (Activity report version). 4352 $url = new moodle_url('/course/user.php', array('mode' => 'grade', 'id' => $course->id, 'user' => $user->id)); 4353 } 4354 $dashboard->add(get_string('grades', 'grades'), $url, self::TYPE_SETTING, null, 'mygrades'); 4355 } 4356 4357 // Let plugins hook into user navigation. 4358 $pluginsfunction = get_plugins_with_function('extend_navigation_user', 'lib.php'); 4359 foreach ($pluginsfunction as $plugintype => $plugins) { 4360 if ($plugintype != 'report') { 4361 foreach ($plugins as $pluginfunction) { 4362 $pluginfunction($profilenode, $user, $usercontext, $course, $coursecontext); 4363 } 4364 } 4365 } 4366 4367 $usersetting = navigation_node::create(get_string('preferences', 'moodle'), $prefurl, self::TYPE_CONTAINER, null, $key); 4368 $dashboard->add_node($usersetting); 4369 } else { 4370 $usersetting = $this->add(get_string('preferences', 'moodle'), $prefurl, self::TYPE_CONTAINER, null, $key); 4371 $usersetting->display = false; 4372 } 4373 $usersetting->id = 'usersettings'; 4374 4375 // Check if the user has been deleted. 4376 if ($user->deleted) { 4377 if (!has_capability('moodle/user:update', $coursecontext)) { 4378 // We can't edit the user so just show the user deleted message. 4379 $usersetting->add(get_string('userdeleted'), null, self::TYPE_SETTING); 4380 } else { 4381 // We can edit the user so show the user deleted message and link it to the profile. 4382 if ($course->id == $SITE->id) { 4383 $profileurl = new moodle_url('/user/profile.php', array('id'=>$user->id)); 4384 } else { 4385 $profileurl = new moodle_url('/user/view.php', array('id'=>$user->id, 'course'=>$course->id)); 4386 } 4387 $usersetting->add(get_string('userdeleted'), $profileurl, self::TYPE_SETTING); 4388 } 4389 return true; 4390 } 4391 4392 $userauthplugin = false; 4393 if (!empty($user->auth)) { 4394 $userauthplugin = get_auth_plugin($user->auth); 4395 } 4396 4397 $useraccount = $usersetting->add(get_string('useraccount'), null, self::TYPE_CONTAINER, null, 'useraccount'); 4398 4399 // Add the profile edit link. 4400 if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) { 4401 if (($currentuser || is_siteadmin($USER) || !is_siteadmin($user)) && 4402 has_capability('moodle/user:update', $systemcontext)) { 4403 $url = new moodle_url('/user/editadvanced.php', array('id'=>$user->id, 'course'=>$course->id)); 4404 $useraccount->add(get_string('editmyprofile'), $url, self::TYPE_SETTING); 4405 } else if ((has_capability('moodle/user:editprofile', $usercontext) && !is_siteadmin($user)) || 4406 ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext))) { 4407 if ($userauthplugin && $userauthplugin->can_edit_profile()) { 4408 $url = $userauthplugin->edit_profile_url(); 4409 if (empty($url)) { 4410 $url = new moodle_url('/user/edit.php', array('id'=>$user->id, 'course'=>$course->id)); 4411 } 4412 $useraccount->add(get_string('editmyprofile'), $url, self::TYPE_SETTING); 4413 } 4414 } 4415 } 4416 4417 // Change password link. 4418 if ($userauthplugin && $currentuser && !\core\session\manager::is_loggedinas() && !isguestuser() && 4419 has_capability('moodle/user:changeownpassword', $systemcontext) && $userauthplugin->can_change_password()) { 4420 $passwordchangeurl = $userauthplugin->change_password_url(); 4421 if (empty($passwordchangeurl)) { 4422 $passwordchangeurl = new moodle_url('/login/change_password.php', array('id'=>$course->id)); 4423 } 4424 $useraccount->add(get_string("changepassword"), $passwordchangeurl, self::TYPE_SETTING, null, 'changepassword'); 4425 } 4426 4427 if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) { 4428 if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) || 4429 has_capability('moodle/user:editprofile', $usercontext)) { 4430 $url = new moodle_url('/user/language.php', array('id' => $user->id, 'course' => $course->id)); 4431 $useraccount->add(get_string('preferredlanguage'), $url, self::TYPE_SETTING, null, 'preferredlanguage'); 4432 } 4433 } 4434 $pluginmanager = core_plugin_manager::instance(); 4435 $enabled = $pluginmanager->get_enabled_plugins('mod'); 4436 if (isset($enabled['forum']) && isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) { 4437 if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) || 4438 has_capability('moodle/user:editprofile', $usercontext)) { 4439 $url = new moodle_url('/user/forum.php', array('id' => $user->id, 'course' => $course->id)); 4440 $useraccount->add(get_string('forumpreferences'), $url, self::TYPE_SETTING); 4441 } 4442 } 4443 $editors = editors_get_enabled(); 4444 if (count($editors) > 1) { 4445 if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) { 4446 if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) || 4447 has_capability('moodle/user:editprofile', $usercontext)) { 4448 $url = new moodle_url('/user/editor.php', array('id' => $user->id, 'course' => $course->id)); 4449 $useraccount->add(get_string('editorpreferences'), $url, self::TYPE_SETTING); 4450 } 4451 } 4452 } 4453 4454 // Add "Course preferences" link. 4455 if (isloggedin() && !isguestuser($user)) { 4456 if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) || 4457 has_capability('moodle/user:editprofile', $usercontext)) { 4458 $url = new moodle_url('/user/course.php', array('id' => $user->id, 'course' => $course->id)); 4459 $useraccount->add(get_string('coursepreferences'), $url, self::TYPE_SETTING, null, 'coursepreferences'); 4460 } 4461 } 4462 4463 // View the roles settings. 4464 if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override', 4465 'moodle/role:manage'), $usercontext)) { 4466 $roles = $usersetting->add(get_string('roles'), null, self::TYPE_SETTING); 4467 4468 $url = new moodle_url('/admin/roles/usersroles.php', array('userid'=>$user->id, 'courseid'=>$course->id)); 4469 $roles->add(get_string('thisusersroles', 'role'), $url, self::TYPE_SETTING); 4470 4471 $assignableroles = get_assignable_roles($usercontext, ROLENAME_BOTH); 4472 4473 if (!empty($assignableroles)) { 4474 $url = new moodle_url('/admin/roles/assign.php', 4475 array('contextid' => $usercontext->id, 'userid' => $user->id, 'courseid' => $course->id)); 4476 $roles->add(get_string('assignrolesrelativetothisuser', 'role'), $url, self::TYPE_SETTING); 4477 } 4478 4479 if (has_capability('moodle/role:review', $usercontext) || count(get_overridable_roles($usercontext, ROLENAME_BOTH))>0) { 4480 $url = new moodle_url('/admin/roles/permissions.php', 4481 array('contextid' => $usercontext->id, 'userid' => $user->id, 'courseid' => $course->id)); 4482 $roles->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING); 4483 } 4484 4485 $url = new moodle_url('/admin/roles/check.php', 4486 array('contextid' => $usercontext->id, 'userid' => $user->id, 'courseid' => $course->id)); 4487 $roles->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING); 4488 } 4489 4490 // Repositories. 4491 if (!$this->cache->cached('contexthasrepos'.$usercontext->id)) { 4492 require_once($CFG->dirroot . '/repository/lib.php'); 4493 $editabletypes = repository::get_editable_types($usercontext); 4494 $haseditabletypes = !empty($editabletypes); 4495 unset($editabletypes); 4496 $this->cache->set('contexthasrepos'.$usercontext->id, $haseditabletypes); 4497 } else { 4498 $haseditabletypes = $this->cache->{'contexthasrepos'.$usercontext->id}; 4499 } 4500 if ($haseditabletypes) { 4501 $repositories = $usersetting->add(get_string('repositories', 'repository'), null, self::TYPE_SETTING); 4502 $repositories->add(get_string('manageinstances', 'repository'), new moodle_url('/repository/manage_instances.php', 4503 array('contextid' => $usercontext->id))); 4504 } 4505 4506 // Portfolio. 4507 if ($currentuser && !empty($CFG->enableportfolios) && has_capability('moodle/portfolio:export', $systemcontext)) { 4508 require_once($CFG->libdir . '/portfoliolib.php'); 4509 if (portfolio_has_visible_instances()) { 4510 $portfolio = $usersetting->add(get_string('portfolios', 'portfolio'), null, self::TYPE_SETTING); 4511 4512 $url = new moodle_url('/user/portfolio.php', array('courseid'=>$course->id)); 4513 $portfolio->add(get_string('configure', 'portfolio'), $url, self::TYPE_SETTING); 4514 4515 $url = new moodle_url('/user/portfoliologs.php', array('courseid'=>$course->id)); 4516 $portfolio->add(get_string('logs', 'portfolio'), $url, self::TYPE_SETTING); 4517 } 4518 } 4519 4520 $enablemanagetokens = false; 4521 if (!empty($CFG->enablerssfeeds)) { 4522 $enablemanagetokens = true; 4523 } else if (!is_siteadmin($USER->id) 4524 && !empty($CFG->enablewebservices) 4525 && has_capability('moodle/webservice:createtoken', context_system::instance()) ) { 4526 $enablemanagetokens = true; 4527 } 4528 // Security keys. 4529 if ($currentuser && $enablemanagetokens) { 4530 $url = new moodle_url('/user/managetoken.php', array('sesskey'=>sesskey())); 4531 $useraccount->add(get_string('securitykeys', 'webservice'), $url, self::TYPE_SETTING); 4532 } 4533 4534 // Messaging. 4535 if (($currentuser && has_capability('moodle/user:editownmessageprofile', $systemcontext)) || (!isguestuser($user) && 4536 has_capability('moodle/user:editmessageprofile', $usercontext) && !is_primary_admin($user->id))) { 4537 $url = new moodle_url('/message/edit.php', array('id'=>$user->id)); 4538 $useraccount->add(get_string('messaging', 'message'), $url, self::TYPE_SETTING); 4539 } 4540 4541 // Blogs. 4542 if ($currentuser && !empty($CFG->enableblogs)) { 4543 $blog = $usersetting->add(get_string('blogs', 'blog'), null, navigation_node::TYPE_CONTAINER, null, 'blogs'); 4544 if (has_capability('moodle/blog:view', $systemcontext)) { 4545 $blog->add(get_string('preferences', 'blog'), new moodle_url('/blog/preferences.php'), 4546 navigation_node::TYPE_SETTING); 4547 } 4548 if (!empty($CFG->useexternalblogs) && $CFG->maxexternalblogsperuser > 0 && 4549 has_capability('moodle/blog:manageexternal', $systemcontext)) { 4550 $blog->add(get_string('externalblogs', 'blog'), new moodle_url('/blog/external_blogs.php'), 4551 navigation_node::TYPE_SETTING); 4552 $blog->add(get_string('addnewexternalblog', 'blog'), new moodle_url('/blog/external_blog_edit.php'), 4553 navigation_node::TYPE_SETTING); 4554 } 4555 // Remove the blog node if empty. 4556 $blog->trim_if_empty(); 4557 } 4558 4559 // Badges. 4560 if ($currentuser && !empty($CFG->enablebadges)) { 4561 $badges = $usersetting->add(get_string('badges'), null, navigation_node::TYPE_CONTAINER, null, 'badges'); 4562 if (has_capability('moodle/badges:manageownbadges', $usercontext)) { 4563 $url = new moodle_url('/badges/mybadges.php'); 4564 $badges->add(get_string('managebadges', 'badges'), $url, self::TYPE_SETTING); 4565 } 4566 $badges->add(get_string('preferences', 'badges'), new moodle_url('/badges/preferences.php'), 4567 navigation_node::TYPE_SETTING); 4568 if (!empty($CFG->badges_allowexternalbackpack)) { 4569 $badges->add(get_string('backpackdetails', 'badges'), new moodle_url('/badges/mybackpack.php'), 4570 navigation_node::TYPE_SETTING); 4571 } 4572 } 4573 4574 // Let plugins hook into user settings navigation. 4575 $pluginsfunction = get_plugins_with_function('extend_navigation_user_settings', 'lib.php'); 4576 foreach ($pluginsfunction as $plugintype => $plugins) { 4577 foreach ($plugins as $pluginfunction) { 4578 $pluginfunction($usersetting, $user, $usercontext, $course, $coursecontext); 4579 } 4580 } 4581 4582 return $usersetting; 4583 } 4584 4585 /** 4586 * Loads block specific settings in the navigation 4587 * 4588 * @return navigation_node 4589 */ 4590 protected function load_block_settings() { 4591 global $CFG; 4592 4593 $blocknode = $this->add($this->context->get_context_name(), null, self::TYPE_SETTING, null, 'blocksettings'); 4594 $blocknode->force_open(); 4595 4596 // Assign local roles 4597 if (get_assignable_roles($this->context, ROLENAME_ORIGINAL)) { 4598 $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $this->context->id)); 4599 $blocknode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING, null, 4600 'roles', new pix_icon('i/assignroles', '')); 4601 } 4602 4603 // Override roles 4604 if (has_capability('moodle/role:review', $this->context) or count(get_overridable_roles($this->context))>0) { 4605 $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->context->id)); 4606 $blocknode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, 4607 'permissions', new pix_icon('i/permissions', '')); 4608 } 4609 // Check role permissions 4610 if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->context)) { 4611 $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->context->id)); 4612 $blocknode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, 4613 'checkpermissions', new pix_icon('i/checkpermissions', '')); 4614 } 4615 4616 return $blocknode; 4617 } 4618 4619 /** 4620 * Loads category specific settings in the navigation 4621 * 4622 * @return navigation_node 4623 */ 4624 protected function load_category_settings() { 4625 global $CFG; 4626 4627 // We can land here while being in the context of a block, in which case we 4628 // should get the parent context which should be the category one. See self::initialise(). 4629 if ($this->context->contextlevel == CONTEXT_BLOCK) { 4630 $catcontext = $this->context->get_parent_context(); 4631 } else { 4632 $catcontext = $this->context; 4633 } 4634 4635 // Let's make sure that we always have the right context when getting here. 4636 if ($catcontext->contextlevel != CONTEXT_COURSECAT) { 4637 throw new coding_exception('Unexpected context while loading category settings.'); 4638 } 4639 4640 $categorynode = $this->add($catcontext->get_context_name(), null, null, null, 'categorysettings'); 4641 $categorynode->nodetype = navigation_node::NODETYPE_BRANCH; 4642 $categorynode->force_open(); 4643 4644 if (can_edit_in_category($catcontext->instanceid)) { 4645 $url = new moodle_url('/course/management.php', array('categoryid' => $catcontext->instanceid)); 4646 $editstring = get_string('managecategorythis'); 4647 $categorynode->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', '')); 4648 } 4649 4650 if (has_capability('moodle/category:manage', $catcontext)) { 4651 $editurl = new moodle_url('/course/editcategory.php', array('id' => $catcontext->instanceid)); 4652 $categorynode->add(get_string('editcategorythis'), $editurl, self::TYPE_SETTING, null, 'edit', new pix_icon('i/edit', '')); 4653 4654 $addsubcaturl = new moodle_url('/course/editcategory.php', array('parent' => $catcontext->instanceid)); 4655 $categorynode->add(get_string('addsubcategory'), $addsubcaturl, self::TYPE_SETTING, null, 'addsubcat', new pix_icon('i/withsubcat', '')); 4656 } 4657 4658 // Assign local roles 4659 $assignableroles = get_assignable_roles($catcontext); 4660 if (!empty($assignableroles)) { 4661 $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $catcontext->id)); 4662 $categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING, null, 'roles', new pix_icon('i/assignroles', '')); 4663 } 4664 4665 // Override roles 4666 if (has_capability('moodle/role:review', $catcontext) or count(get_overridable_roles($catcontext)) > 0) { 4667 $url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid' => $catcontext->id)); 4668 $categorynode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, 'permissions', new pix_icon('i/permissions', '')); 4669 } 4670 // Check role permissions 4671 if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride', 4672 'moodle/role:override', 'moodle/role:assign'), $catcontext)) { 4673 $url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid' => $catcontext->id)); 4674 $categorynode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, 'checkpermissions', new pix_icon('i/checkpermissions', '')); 4675 } 4676 4677 // Cohorts 4678 if (has_any_capability(array('moodle/cohort:view', 'moodle/cohort:manage'), $catcontext)) { 4679 $categorynode->add(get_string('cohorts', 'cohort'), new moodle_url('/cohort/index.php', 4680 array('contextid' => $catcontext->id)), self::TYPE_SETTING, null, 'cohort', new pix_icon('i/cohort', '')); 4681 } 4682 4683 // Manage filters 4684 if (has_capability('moodle/filter:manage', $catcontext) && count(filter_get_available_in_context($catcontext)) > 0) { 4685 $url = new moodle_url('/filter/manage.php', array('contextid' => $catcontext->id)); 4686 $categorynode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, 'filters', new pix_icon('i/filter', '')); 4687 } 4688 4689 // Restore. 4690 if (has_capability('moodle/restore:restorecourse', $catcontext)) { 4691 $url = new moodle_url('/backup/restorefile.php', array('contextid' => $catcontext->id)); 4692 $categorynode->add(get_string('restorecourse', 'admin'), $url, self::TYPE_SETTING, null, 'restorecourse', new pix_icon('i/restore', '')); 4693 } 4694 4695 // Let plugins hook into category settings navigation. 4696 $pluginsfunction = get_plugins_with_function('extend_navigation_category_settings', 'lib.php'); 4697 foreach ($pluginsfunction as $plugintype => $plugins) { 4698 foreach ($plugins as $pluginfunction) { 4699 $pluginfunction($categorynode, $catcontext); 4700 } 4701 } 4702 4703 return $categorynode; 4704 } 4705 4706 /** 4707 * Determine whether the user is assuming another role 4708 * 4709 * This function checks to see if the user is assuming another role by means of 4710 * role switching. In doing this we compare each RSW key (context path) against 4711 * the current context path. This ensures that we can provide the switching 4712 * options against both the course and any page shown under the course. 4713 * 4714 * @return bool|int The role(int) if the user is in another role, false otherwise 4715 */ 4716 protected function in_alternative_role() { 4717 global $USER; 4718 if (!empty($USER->access['rsw']) && is_array($USER->access['rsw'])) { 4719 if (!empty($this->page->context) && !empty($USER->access['rsw'][$this->page->context->path])) { 4720 return $USER->access['rsw'][$this->page->context->path]; 4721 } 4722 foreach ($USER->access['rsw'] as $key=>$role) { 4723 if (strpos($this->context->path,$key)===0) { 4724 return $role; 4725 } 4726 } 4727 } 4728 return false; 4729 } 4730 4731 /** 4732 * This function loads all of the front page settings into the settings navigation. 4733 * This function is called when the user is on the front page, or $COURSE==$SITE 4734 * @param bool $forceopen (optional) 4735 * @return navigation_node 4736 */ 4737 protected function load_front_page_settings($forceopen = false) { 4738 global $SITE, $CFG; 4739 4740 $course = clone($SITE); 4741 $coursecontext = context_course::instance($course->id); // Course context 4742 4743 $frontpage = $this->add(get_string('frontpagesettings'), null, self::TYPE_SETTING, null, 'frontpage'); 4744 if ($forceopen) { 4745 $frontpage->force_open(); 4746 } 4747 $frontpage->id = 'frontpagesettings'; 4748 4749 if ($this->page->user_allowed_editing()) { 4750 4751 // Add the turn on/off settings 4752 $url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey())); 4753 if ($this->page->user_is_editing()) { 4754 $url->param('edit', 'off'); 4755 $editstring = get_string('turneditingoff'); 4756 } else { 4757 $url->param('edit', 'on'); 4758 $editstring = get_string('turneditingon'); 4759 } 4760 $frontpage->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', '')); 4761 } 4762 4763 if (has_capability('moodle/course:update', $coursecontext)) { 4764 // Add the course settings link 4765 $url = new moodle_url('/admin/settings.php', array('section'=>'frontpagesettings')); 4766 $frontpage->add(get_string('editsettings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', '')); 4767 } 4768 4769 // add enrol nodes 4770 enrol_add_course_navigation($frontpage, $course); 4771 4772 // Manage filters 4773 if (has_capability('moodle/filter:manage', $coursecontext) && count(filter_get_available_in_context($coursecontext))>0) { 4774 $url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id)); 4775 $frontpage->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/filter', '')); 4776 } 4777 4778 // View course reports. 4779 if (has_capability('moodle/site:viewreports', $coursecontext)) { // Basic capability for listing of reports. 4780 $frontpagenav = $frontpage->add(get_string('reports'), null, self::TYPE_CONTAINER, null, 'frontpagereports', 4781 new pix_icon('i/stats', '')); 4782 $coursereports = core_component::get_plugin_list('coursereport'); 4783 foreach ($coursereports as $report=>$dir) { 4784 $libfile = $CFG->dirroot.'/course/report/'.$report.'/lib.php'; 4785 if (file_exists($libfile)) { 4786 require_once($libfile); 4787 $reportfunction = $report.'_report_extend_navigation'; 4788 if (function_exists($report.'_report_extend_navigation')) { 4789 $reportfunction($frontpagenav, $course, $coursecontext); 4790 } 4791 } 4792 } 4793 4794 $reports = get_plugin_list_with_function('report', 'extend_navigation_course', 'lib.php'); 4795 foreach ($reports as $reportfunction) { 4796 $reportfunction($frontpagenav, $course, $coursecontext); 4797 } 4798 } 4799 4800 // Backup this course 4801 if (has_capability('moodle/backup:backupcourse', $coursecontext)) { 4802 $url = new moodle_url('/backup/backup.php', array('id'=>$course->id)); 4803 $frontpage->add(get_string('backup'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/backup', '')); 4804 } 4805 4806 // Restore to this course 4807 if (has_capability('moodle/restore:restorecourse', $coursecontext)) { 4808 $url = new moodle_url('/backup/restorefile.php', array('contextid'=>$coursecontext->id)); 4809 $frontpage->add(get_string('restore'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', '')); 4810 } 4811 4812 // Questions 4813 require_once($CFG->libdir . '/questionlib.php'); 4814 question_extend_settings_navigation($frontpage, $coursecontext)->trim_if_empty(); 4815 4816 // Manage files 4817 if ($course->legacyfiles == 2 and has_capability('moodle/course:managefiles', $this->context)) { 4818 //hiden in new installs 4819 $url = new moodle_url('/files/index.php', array('contextid'=>$coursecontext->id)); 4820 $frontpage->add(get_string('sitelegacyfiles'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/folder', '')); 4821 } 4822 4823 // Let plugins hook into frontpage navigation. 4824 $pluginsfunction = get_plugins_with_function('extend_navigation_frontpage', 'lib.php'); 4825 foreach ($pluginsfunction as $plugintype => $plugins) { 4826 foreach ($plugins as $pluginfunction) { 4827 $pluginfunction($frontpage, $course, $coursecontext); 4828 } 4829 } 4830 4831 return $frontpage; 4832 } 4833 4834 /** 4835 * This function gives local plugins an opportunity to modify the settings navigation. 4836 */ 4837 protected function load_local_plugin_settings() { 4838 4839 foreach (get_plugin_list_with_function('local', 'extend_settings_navigation') as $function) { 4840 $function($this, $this->context); 4841 } 4842 } 4843 4844 /** 4845 * This function marks the cache as volatile so it is cleared during shutdown 4846 */ 4847 public function clear_cache() { 4848 $this->cache->volatile(); 4849 } 4850 4851 /** 4852 * Checks to see if there are child nodes available in the specific user's preference node. 4853 * If so, then they have the appropriate permissions view this user's preferences. 4854 * 4855 * @since Moodle 2.9.3 4856 * @param int $userid The user's ID. 4857 * @return bool True if child nodes exist to view, otherwise false. 4858 */ 4859 public function can_view_user_preferences($userid) { 4860 if (is_siteadmin()) { 4861 return true; 4862 } 4863 // See if any nodes are present in the preferences section for this user. 4864 $preferencenode = $this->find('userviewingsettings' . $userid, null); 4865 if ($preferencenode && $preferencenode->has_children()) { 4866 // Run through each child node. 4867 foreach ($preferencenode->children as $childnode) { 4868 // If the child node has children then this user has access to a link in the preferences page. 4869 if ($childnode->has_children()) { 4870 return true; 4871 } 4872 } 4873 } 4874 // No links found for the user to access on the preferences page. 4875 return false; 4876 } 4877 } 4878 4879 /** 4880 * Class used to populate site admin navigation for ajax. 4881 * 4882 * @package core 4883 * @category navigation 4884 * @copyright 2013 Rajesh Taneja <rajesh@moodle.com> 4885 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4886 */ 4887 class settings_navigation_ajax extends settings_navigation { 4888 /** 4889 * Constructs the navigation for use in an AJAX request 4890 * 4891 * @param moodle_page $page 4892 */ 4893 public function __construct(moodle_page &$page) { 4894 $this->page = $page; 4895 $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME); 4896 $this->children = new navigation_node_collection(); 4897 $this->initialise(); 4898 } 4899 4900 /** 4901 * Initialise the site admin navigation. 4902 * 4903 * @return array An array of the expandable nodes 4904 */ 4905 public function initialise() { 4906 if ($this->initialised || during_initial_install()) { 4907 return false; 4908 } 4909 $this->context = $this->page->context; 4910 $this->load_administration_settings(); 4911 4912 // Check if local plugins is adding node to site admin. 4913 $this->load_local_plugin_settings(); 4914 4915 $this->initialised = true; 4916 } 4917 } 4918 4919 /** 4920 * Simple class used to output a navigation branch in XML 4921 * 4922 * @package core 4923 * @category navigation 4924 * @copyright 2009 Sam Hemelryk 4925 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4926 */ 4927 class navigation_json { 4928 /** @var array An array of different node types */ 4929 protected $nodetype = array('node','branch'); 4930 /** @var array An array of node keys and types */ 4931 protected $expandable = array(); 4932 /** 4933 * Turns a branch and all of its children into XML 4934 * 4935 * @param navigation_node $branch 4936 * @return string XML string 4937 */ 4938 public function convert($branch) { 4939 $xml = $this->convert_child($branch); 4940 return $xml; 4941 } 4942 /** 4943 * Set the expandable items in the array so that we have enough information 4944 * to attach AJAX events 4945 * @param array $expandable 4946 */ 4947 public function set_expandable($expandable) { 4948 foreach ($expandable as $node) { 4949 $this->expandable[$node['key'].':'.$node['type']] = $node; 4950 } 4951 } 4952 /** 4953 * Recusively converts a child node and its children to XML for output 4954 * 4955 * @param navigation_node $child The child to convert 4956 * @param int $depth Pointlessly used to track the depth of the XML structure 4957 * @return string JSON 4958 */ 4959 protected function convert_child($child, $depth=1) { 4960 if (!$child->display) { 4961 return ''; 4962 } 4963 $attributes = array(); 4964 $attributes['id'] = $child->id; 4965 $attributes['name'] = (string)$child->text; // This can be lang_string object so typecast it. 4966 $attributes['type'] = $child->type; 4967 $attributes['key'] = $child->key; 4968 $attributes['class'] = $child->get_css_type(); 4969 $attributes['requiresajaxloading'] = $child->requiresajaxloading; 4970 4971 if ($child->icon instanceof pix_icon) { 4972 $attributes['icon'] = array( 4973 'component' => $child->icon->component, 4974 'pix' => $child->icon->pix, 4975 ); 4976 foreach ($child->icon->attributes as $key=>$value) { 4977 if ($key == 'class') { 4978 $attributes['icon']['classes'] = explode(' ', $value); 4979 } else if (!array_key_exists($key, $attributes['icon'])) { 4980 $attributes['icon'][$key] = $value; 4981 } 4982 4983 } 4984 } else if (!empty($child->icon)) { 4985 $attributes['icon'] = (string)$child->icon; 4986 } 4987 4988 if ($child->forcetitle || $child->title !== $child->text) { 4989 $attributes['title'] = htmlentities($child->title, ENT_QUOTES, 'UTF-8'); 4990 } 4991 if (array_key_exists($child->key.':'.$child->type, $this->expandable)) { 4992 $attributes['expandable'] = $child->key; 4993 $child->add_class($this->expandable[$child->key.':'.$child->type]['id']); 4994 } 4995 4996 if (count($child->classes)>0) { 4997 $attributes['class'] .= ' '.join(' ',$child->classes); 4998 } 4999 if (is_string($child->action)) { 5000 $attributes['link'] = $child->action; 5001 } else if ($child->action instanceof moodle_url) { 5002 $attributes['link'] = $child->action->out(); 5003 } else if ($child->action instanceof action_link) { 5004 $attributes['link'] = $child->action->url->out(); 5005 } 5006 $attributes['hidden'] = ($child->hidden); 5007 $attributes['haschildren'] = ($child->children->count()>0 || $child->type == navigation_node::TYPE_CATEGORY); 5008 $attributes['haschildren'] = $attributes['haschildren'] || $child->type == navigation_node::TYPE_MY_CATEGORY; 5009 5010 if ($child->children->count() > 0) { 5011 $attributes['children'] = array(); 5012 foreach ($child->children as $subchild) { 5013 $attributes['children'][] = $this->convert_child($subchild, $depth+1); 5014 } 5015 } 5016 5017 if ($depth > 1) { 5018 return $attributes; 5019 } else { 5020 return json_encode($attributes); 5021 } 5022 } 5023 } 5024 5025 /** 5026 * The cache class used by global navigation and settings navigation. 5027 * 5028 * It is basically an easy access point to session with a bit of smarts to make 5029 * sure that the information that is cached is valid still. 5030 * 5031 * Example use: 5032 * <code php> 5033 * if (!$cache->viewdiscussion()) { 5034 * // Code to do stuff and produce cachable content 5035 * $cache->viewdiscussion = has_capability('mod/forum:viewdiscussion', $coursecontext); 5036 * } 5037 * $content = $cache->viewdiscussion; 5038 * </code> 5039 * 5040 * @package core 5041 * @category navigation 5042 * @copyright 2009 Sam Hemelryk 5043 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 5044 */ 5045 class navigation_cache { 5046 /** @var int represents the time created */ 5047 protected $creation; 5048 /** @var array An array of session keys */ 5049 protected $session; 5050 /** 5051 * The string to use to segregate this particular cache. It can either be 5052 * unique to start a fresh cache or if you want to share a cache then make 5053 * it the string used in the original cache. 5054 * @var string 5055 */ 5056 protected $area; 5057 /** @var int a time that the information will time out */ 5058 protected $timeout; 5059 /** @var stdClass The current context */ 5060 protected $currentcontext; 5061 /** @var int cache time information */ 5062 const CACHETIME = 0; 5063 /** @var int cache user id */ 5064 const CACHEUSERID = 1; 5065 /** @var int cache value */ 5066 const CACHEVALUE = 2; 5067 /** @var null|array An array of navigation cache areas to expire on shutdown */ 5068 public static $volatilecaches; 5069 5070 /** 5071 * Contructor for the cache. Requires two arguments 5072 * 5073 * @param string $area The string to use to segregate this particular cache 5074 * it can either be unique to start a fresh cache or if you want 5075 * to share a cache then make it the string used in the original 5076 * cache 5077 * @param int $timeout The number of seconds to time the information out after 5078 */ 5079 public function __construct($area, $timeout=1800) { 5080 $this->creation = time(); 5081 $this->area = $area; 5082 $this->timeout = time() - $timeout; 5083 if (rand(0,100) === 0) { 5084 $this->garbage_collection(); 5085 } 5086 } 5087 5088 /** 5089 * Used to set up the cache within the SESSION. 5090 * 5091 * This is called for each access and ensure that we don't put anything into the session before 5092 * it is required. 5093 */ 5094 protected function ensure_session_cache_initialised() { 5095 global $SESSION; 5096 if (empty($this->session)) { 5097 if (!isset($SESSION->navcache)) { 5098 $SESSION->navcache = new stdClass; 5099 } 5100 if (!isset($SESSION->navcache->{$this->area})) { 5101 $SESSION->navcache->{$this->area} = array(); 5102 } 5103 $this->session = &$SESSION->navcache->{$this->area}; // pointer to array, =& is correct here 5104 } 5105 } 5106 5107 /** 5108 * Magic Method to retrieve something by simply calling using = cache->key 5109 * 5110 * @param mixed $key The identifier for the information you want out again 5111 * @return void|mixed Either void or what ever was put in 5112 */ 5113 public function __get($key) { 5114 if (!$this->cached($key)) { 5115 return; 5116 } 5117 $information = $this->session[$key][self::CACHEVALUE]; 5118 return unserialize($information); 5119 } 5120 5121 /** 5122 * Magic method that simply uses {@link set();} to store something in the cache 5123 * 5124 * @param string|int $key 5125 * @param mixed $information 5126 */ 5127 public function __set($key, $information) { 5128 $this->set($key, $information); 5129 } 5130 5131 /** 5132 * Sets some information against the cache (session) for later retrieval 5133 * 5134 * @param string|int $key 5135 * @param mixed $information 5136 */ 5137 public function set($key, $information) { 5138 global $USER; 5139 $this->ensure_session_cache_initialised(); 5140 $information = serialize($information); 5141 $this->session[$key]= array(self::CACHETIME=>time(), self::CACHEUSERID=>$USER->id, self::CACHEVALUE=>$information); 5142 } 5143 /** 5144 * Check the existence of the identifier in the cache 5145 * 5146 * @param string|int $key 5147 * @return bool 5148 */ 5149 public function cached($key) { 5150 global $USER; 5151 $this->ensure_session_cache_initialised(); 5152 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) { 5153 return false; 5154 } 5155 return true; 5156 } 5157 /** 5158 * Compare something to it's equivilant in the cache 5159 * 5160 * @param string $key 5161 * @param mixed $value 5162 * @param bool $serialise Whether to serialise the value before comparison 5163 * this should only be set to false if the value is already 5164 * serialised 5165 * @return bool If the value is the same false if it is not set or doesn't match 5166 */ 5167 public function compare($key, $value, $serialise = true) { 5168 if ($this->cached($key)) { 5169 if ($serialise) { 5170 $value = serialize($value); 5171 } 5172 if ($this->session[$key][self::CACHEVALUE] === $value) { 5173 return true; 5174 } 5175 } 5176 return false; 5177 } 5178 /** 5179 * Wipes the entire cache, good to force regeneration 5180 */ 5181 public function clear() { 5182 global $SESSION; 5183 unset($SESSION->navcache); 5184 $this->session = null; 5185 } 5186 /** 5187 * Checks all cache entries and removes any that have expired, good ole cleanup 5188 */ 5189 protected function garbage_collection() { 5190 if (empty($this->session)) { 5191 return true; 5192 } 5193 foreach ($this->session as $key=>$cachedinfo) { 5194 if (is_array($cachedinfo) && $cachedinfo[self::CACHETIME]<$this->timeout) { 5195 unset($this->session[$key]); 5196 } 5197 } 5198 } 5199 5200 /** 5201 * Marks the cache as being volatile (likely to change) 5202 * 5203 * Any caches marked as volatile will be destroyed at the on shutdown by 5204 * {@link navigation_node::destroy_volatile_caches()} which is registered 5205 * as a shutdown function if any caches are marked as volatile. 5206 * 5207 * @param bool $setting True to destroy the cache false not too 5208 */ 5209 public function volatile($setting = true) { 5210 if (self::$volatilecaches===null) { 5211 self::$volatilecaches = array(); 5212 core_shutdown_manager::register_function(array('navigation_cache','destroy_volatile_caches')); 5213 } 5214 5215 if ($setting) { 5216 self::$volatilecaches[$this->area] = $this->area; 5217 } else if (array_key_exists($this->area, self::$volatilecaches)) { 5218 unset(self::$volatilecaches[$this->area]); 5219 } 5220 } 5221 5222 /** 5223 * Destroys all caches marked as volatile 5224 * 5225 * This function is static and works in conjunction with the static volatilecaches 5226 * property of navigation cache. 5227 * Because this function is static it manually resets the cached areas back to an 5228 * empty array. 5229 */ 5230 public static function destroy_volatile_caches() { 5231 global $SESSION; 5232 if (is_array(self::$volatilecaches) && count(self::$volatilecaches)>0) { 5233 foreach (self::$volatilecaches as $area) { 5234 $SESSION->navcache->{$area} = array(); 5235 } 5236 } else { 5237 $SESSION->navcache = new stdClass; 5238 } 5239 } 5240 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |