PageRenderTime 111ms CodeModel.GetById 42ms app.highlight 48ms RepoModel.GetById 0ms app.codeStats 2ms

/lib/navigationlib.php

http://github.com/moodle/moodle
PHP | 5875 lines | 3577 code | 506 blank | 1792 comment | 1031 complexity | 76903fa81e3138bfc01fd296c65665c0 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   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
  26defined('MOODLE_INTERNAL') || die();
  27
  28/**
  29 * The name that will be used to separate the navigation cache within SESSION
  30 */
  31define('NAVIGATION_CACHE_NAME', 'navigation');
  32define('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 */
  48class 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    /** var string The course index page navigation node */
  88    const COURSE_INDEX_PAGE = 'courseindexpage';
  89
  90    /** @var int Parameter to aid the coder in tracking [optional] */
  91    public $id = null;
  92    /** @var string|int The identifier for the node, used to retrieve the node */
  93    public $key = null;
  94    /** @var string The text to use for the node */
  95    public $text = null;
  96    /** @var string Short text to use if requested [optional] */
  97    public $shorttext = null;
  98    /** @var string The title attribute for an action if one is defined */
  99    public $title = null;
 100    /** @var string A string that can be used to build a help button */
 101    public $helpbutton = null;
 102    /** @var moodle_url|action_link|null An action for the node (link) */
 103    public $action = null;
 104    /** @var pix_icon The path to an icon to use for this node */
 105    public $icon = null;
 106    /** @var int See TYPE_* constants defined for this class */
 107    public $type = self::TYPE_UNKNOWN;
 108    /** @var int See NODETYPE_* constants defined for this class */
 109    public $nodetype = self::NODETYPE_LEAF;
 110    /** @var bool If set to true the node will be collapsed by default */
 111    public $collapse = false;
 112    /** @var bool If set to true the node will be expanded by default */
 113    public $forceopen = false;
 114    /** @var array An array of CSS classes for the node */
 115    public $classes = array();
 116    /** @var navigation_node_collection An array of child nodes */
 117    public $children = array();
 118    /** @var bool If set to true the node will be recognised as active */
 119    public $isactive = false;
 120    /** @var bool If set to true the node will be dimmed */
 121    public $hidden = false;
 122    /** @var bool If set to false the node will not be displayed */
 123    public $display = true;
 124    /** @var bool If set to true then an HR will be printed before the node */
 125    public $preceedwithhr = false;
 126    /** @var bool If set to true the the navigation bar should ignore this node */
 127    public $mainnavonly = false;
 128    /** @var bool If set to true a title will be added to the action no matter what */
 129    public $forcetitle = false;
 130    /** @var navigation_node A reference to the node parent, you should never set this directly you should always call set_parent */
 131    public $parent = null;
 132    /** @var bool Override to not display the icon even if one is provided **/
 133    public $hideicon = false;
 134    /** @var bool Set to true if we KNOW that this node can be expanded.  */
 135    public $isexpandable = false;
 136    /** @var array */
 137    protected $namedtypes = array(0 => 'system', 10 => 'category', 20 => 'course', 30 => 'structure', 40 => 'activity',
 138                                  50 => 'resource', 60 => 'custom', 70 => 'setting', 71 => 'siteadmin', 80 => 'user',
 139                                  90 => 'container');
 140    /** @var moodle_url */
 141    protected static $fullmeurl = null;
 142    /** @var bool toogles auto matching of active node */
 143    public static $autofindactive = true;
 144    /** @var bool should we load full admin tree or rely on AJAX for performance reasons */
 145    protected static $loadadmintree = false;
 146    /** @var mixed If set to an int, that section will be included even if it has no activities */
 147    public $includesectionnum = false;
 148    /** @var bool does the node need to be loaded via ajax */
 149    public $requiresajaxloading = false;
 150    /** @var bool If set to true this node will be added to the "flat" navigation */
 151    public $showinflatnavigation = false;
 152
 153    /**
 154     * Constructs a new navigation_node
 155     *
 156     * @param array|string $properties Either an array of properties or a string to use
 157     *                     as the text for the node
 158     */
 159    public function __construct($properties) {
 160        if (is_array($properties)) {
 161            // Check the array for each property that we allow to set at construction.
 162            // text         - The main content for the node
 163            // shorttext    - A short text if required for the node
 164            // icon         - The icon to display for the node
 165            // type         - The type of the node
 166            // key          - The key to use to identify the node
 167            // parent       - A reference to the nodes parent
 168            // action       - The action to attribute to this node, usually a URL to link to
 169            if (array_key_exists('text', $properties)) {
 170                $this->text = $properties['text'];
 171            }
 172            if (array_key_exists('shorttext', $properties)) {
 173                $this->shorttext = $properties['shorttext'];
 174            }
 175            if (!array_key_exists('icon', $properties)) {
 176                $properties['icon'] = new pix_icon('i/navigationitem', '');
 177            }
 178            $this->icon = $properties['icon'];
 179            if ($this->icon instanceof pix_icon) {
 180                if (empty($this->icon->attributes['class'])) {
 181                    $this->icon->attributes['class'] = 'navicon';
 182                } else {
 183                    $this->icon->attributes['class'] .= ' navicon';
 184                }
 185            }
 186            if (array_key_exists('type', $properties)) {
 187                $this->type = $properties['type'];
 188            } else {
 189                $this->type = self::TYPE_CUSTOM;
 190            }
 191            if (array_key_exists('key', $properties)) {
 192                $this->key = $properties['key'];
 193            }
 194            // This needs to happen last because of the check_if_active call that occurs
 195            if (array_key_exists('action', $properties)) {
 196                $this->action = $properties['action'];
 197                if (is_string($this->action)) {
 198                    $this->action = new moodle_url($this->action);
 199                }
 200                if (self::$autofindactive) {
 201                    $this->check_if_active();
 202                }
 203            }
 204            if (array_key_exists('parent', $properties)) {
 205                $this->set_parent($properties['parent']);
 206            }
 207        } else if (is_string($properties)) {
 208            $this->text = $properties;
 209        }
 210        if ($this->text === null) {
 211            throw new coding_exception('You must set the text for the node when you create it.');
 212        }
 213        // Instantiate a new navigation node collection for this nodes children
 214        $this->children = new navigation_node_collection();
 215    }
 216
 217    /**
 218     * Checks if this node is the active node.
 219     *
 220     * This is determined by comparing the action for the node against the
 221     * defined URL for the page. A match will see this node marked as active.
 222     *
 223     * @param int $strength One of URL_MATCH_EXACT, URL_MATCH_PARAMS, or URL_MATCH_BASE
 224     * @return bool
 225     */
 226    public function check_if_active($strength=URL_MATCH_EXACT) {
 227        global $FULLME, $PAGE;
 228        // Set fullmeurl if it hasn't already been set
 229        if (self::$fullmeurl == null) {
 230            if ($PAGE->has_set_url()) {
 231                self::override_active_url(new moodle_url($PAGE->url));
 232            } else {
 233                self::override_active_url(new moodle_url($FULLME));
 234            }
 235        }
 236
 237        // Compare the action of this node against the fullmeurl
 238        if ($this->action instanceof moodle_url && $this->action->compare(self::$fullmeurl, $strength)) {
 239            $this->make_active();
 240            return true;
 241        }
 242        return false;
 243    }
 244
 245    /**
 246     * True if this nav node has siblings in the tree.
 247     *
 248     * @return bool
 249     */
 250    public function has_siblings() {
 251        if (empty($this->parent) || empty($this->parent->children)) {
 252            return false;
 253        }
 254        if ($this->parent->children instanceof navigation_node_collection) {
 255            $count = $this->parent->children->count();
 256        } else {
 257            $count = count($this->parent->children);
 258        }
 259        return ($count > 1);
 260    }
 261
 262    /**
 263     * Get a list of sibling navigation nodes at the same level as this one.
 264     *
 265     * @return bool|array of navigation_node
 266     */
 267    public function get_siblings() {
 268        // Returns a list of the siblings of the current node for display in a flat navigation element. Either
 269        // the in-page links or the breadcrumb links.
 270        $siblings = false;
 271
 272        if ($this->has_siblings()) {
 273            $siblings = [];
 274            foreach ($this->parent->children as $child) {
 275                if ($child->display) {
 276                    $siblings[] = $child;
 277                }
 278            }
 279        }
 280        return $siblings;
 281    }
 282
 283    /**
 284     * This sets the URL that the URL of new nodes get compared to when locating
 285     * the active node.
 286     *
 287     * The active node is the node that matches the URL set here. By default this
 288     * is either $PAGE->url or if that hasn't been set $FULLME.
 289     *
 290     * @param moodle_url $url The url to use for the fullmeurl.
 291     * @param bool $loadadmintree use true if the URL point to administration tree
 292     */
 293    public static function override_active_url(moodle_url $url, $loadadmintree = false) {
 294        // Clone the URL, in case the calling script changes their URL later.
 295        self::$fullmeurl = new moodle_url($url);
 296        // True means we do not want AJAX loaded admin tree, required for all admin pages.
 297        if ($loadadmintree) {
 298            // Do not change back to false if already set.
 299            self::$loadadmintree = true;
 300        }
 301    }
 302
 303    /**
 304     * Use when page is linked from the admin tree,
 305     * if not used navigation could not find the page using current URL
 306     * because the tree is not fully loaded.
 307     */
 308    public static function require_admin_tree() {
 309        self::$loadadmintree = true;
 310    }
 311
 312    /**
 313     * Creates a navigation node, ready to add it as a child using add_node
 314     * function. (The created node needs to be added before you can use it.)
 315     * @param string $text
 316     * @param moodle_url|action_link $action
 317     * @param int $type
 318     * @param string $shorttext
 319     * @param string|int $key
 320     * @param pix_icon $icon
 321     * @return navigation_node
 322     */
 323    public static function create($text, $action=null, $type=self::TYPE_CUSTOM,
 324            $shorttext=null, $key=null, pix_icon $icon=null) {
 325        // Properties array used when creating the new navigation node
 326        $itemarray = array(
 327            'text' => $text,
 328            'type' => $type
 329        );
 330        // Set the action if one was provided
 331        if ($action!==null) {
 332            $itemarray['action'] = $action;
 333        }
 334        // Set the shorttext if one was provided
 335        if ($shorttext!==null) {
 336            $itemarray['shorttext'] = $shorttext;
 337        }
 338        // Set the icon if one was provided
 339        if ($icon!==null) {
 340            $itemarray['icon'] = $icon;
 341        }
 342        // Set the key
 343        $itemarray['key'] = $key;
 344        // Construct and return
 345        return new navigation_node($itemarray);
 346    }
 347
 348    /**
 349     * Adds a navigation node as a child of this node.
 350     *
 351     * @param string $text
 352     * @param moodle_url|action_link $action
 353     * @param int $type
 354     * @param string $shorttext
 355     * @param string|int $key
 356     * @param pix_icon $icon
 357     * @return navigation_node
 358     */
 359    public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) {
 360        // Create child node
 361        $childnode = self::create($text, $action, $type, $shorttext, $key, $icon);
 362
 363        // Add the child to end and return
 364        return $this->add_node($childnode);
 365    }
 366
 367    /**
 368     * Adds a navigation node as a child of this one, given a $node object
 369     * created using the create function.
 370     * @param navigation_node $childnode Node to add
 371     * @param string $beforekey
 372     * @return navigation_node The added node
 373     */
 374    public function add_node(navigation_node $childnode, $beforekey=null) {
 375        // First convert the nodetype for this node to a branch as it will now have children
 376        if ($this->nodetype !== self::NODETYPE_BRANCH) {
 377            $this->nodetype = self::NODETYPE_BRANCH;
 378        }
 379        // Set the parent to this node
 380        $childnode->set_parent($this);
 381
 382        // Default the key to the number of children if not provided
 383        if ($childnode->key === null) {
 384            $childnode->key = $this->children->count();
 385        }
 386
 387        // Add the child using the navigation_node_collections add method
 388        $node = $this->children->add($childnode, $beforekey);
 389
 390        // If added node is a category node or the user is logged in and it's a course
 391        // then mark added node as a branch (makes it expandable by AJAX)
 392        $type = $childnode->type;
 393        if (($type == self::TYPE_CATEGORY) || (isloggedin() && ($type == self::TYPE_COURSE)) || ($type == self::TYPE_MY_CATEGORY) ||
 394                ($type === self::TYPE_SITE_ADMIN)) {
 395            $node->nodetype = self::NODETYPE_BRANCH;
 396        }
 397        // If this node is hidden mark it's children as hidden also
 398        if ($this->hidden) {
 399            $node->hidden = true;
 400        }
 401        // Return added node (reference returned by $this->children->add()
 402        return $node;
 403    }
 404
 405    /**
 406     * Return a list of all the keys of all the child nodes.
 407     * @return array the keys.
 408     */
 409    public function get_children_key_list() {
 410        return $this->children->get_key_list();
 411    }
 412
 413    /**
 414     * Searches for a node of the given type with the given key.
 415     *
 416     * This searches this node plus all of its children, and their children....
 417     * If you know the node you are looking for is a child of this node then please
 418     * use the get method instead.
 419     *
 420     * @param int|string $key The key of the node we are looking for
 421     * @param int $type One of navigation_node::TYPE_*
 422     * @return navigation_node|false
 423     */
 424    public function find($key, $type) {
 425        return $this->children->find($key, $type);
 426    }
 427
 428    /**
 429     * Walk the tree building up a list of all the flat navigation nodes.
 430     *
 431     * @param flat_navigation $nodes List of the found flat navigation nodes.
 432     * @param boolean $showdivider Show a divider before the first node.
 433     * @param string $label A label for the collection of navigation links.
 434     */
 435    public function build_flat_navigation_list(flat_navigation $nodes, $showdivider = false, $label = '') {
 436        if ($this->showinflatnavigation) {
 437            $indent = 0;
 438            if ($this->type == self::TYPE_COURSE || $this->key === self::COURSE_INDEX_PAGE) {
 439                $indent = 1;
 440            }
 441            $flat = new flat_navigation_node($this, $indent);
 442            $flat->set_showdivider($showdivider, $label);
 443            $nodes->add($flat);
 444        }
 445        foreach ($this->children as $child) {
 446            $child->build_flat_navigation_list($nodes, false);
 447        }
 448    }
 449
 450    /**
 451     * Get the child of this node that has the given key + (optional) type.
 452     *
 453     * If you are looking for a node and want to search all children + their children
 454     * then please use the find method instead.
 455     *
 456     * @param int|string $key The key of the node we are looking for
 457     * @param int $type One of navigation_node::TYPE_*
 458     * @return navigation_node|false
 459     */
 460    public function get($key, $type=null) {
 461        return $this->children->get($key, $type);
 462    }
 463
 464    /**
 465     * Removes this node.
 466     *
 467     * @return bool
 468     */
 469    public function remove() {
 470        return $this->parent->children->remove($this->key, $this->type);
 471    }
 472
 473    /**
 474     * Checks if this node has or could have any children
 475     *
 476     * @return bool Returns true if it has children or could have (by AJAX expansion)
 477     */
 478    public function has_children() {
 479        return ($this->nodetype === navigation_node::NODETYPE_BRANCH || $this->children->count()>0 || $this->isexpandable);
 480    }
 481
 482    /**
 483     * Marks this node as active and forces it open.
 484     *
 485     * Important: If you are here because you need to mark a node active to get
 486     * the navigation to do what you want have you looked at {@link navigation_node::override_active_url()}?
 487     * You can use it to specify a different URL to match the active navigation node on
 488     * rather than having to locate and manually mark a node active.
 489     */
 490    public function make_active() {
 491        $this->isactive = true;
 492        $this->add_class('active_tree_node');
 493        $this->force_open();
 494        if ($this->parent !== null) {
 495            $this->parent->make_inactive();
 496        }
 497    }
 498
 499    /**
 500     * Marks a node as inactive and recusised back to the base of the tree
 501     * doing the same to all parents.
 502     */
 503    public function make_inactive() {
 504        $this->isactive = false;
 505        $this->remove_class('active_tree_node');
 506        if ($this->parent !== null) {
 507            $this->parent->make_inactive();
 508        }
 509    }
 510
 511    /**
 512     * Forces this node to be open and at the same time forces open all
 513     * parents until the root node.
 514     *
 515     * Recursive.
 516     */
 517    public function force_open() {
 518        $this->forceopen = true;
 519        if ($this->parent !== null) {
 520            $this->parent->force_open();
 521        }
 522    }
 523
 524    /**
 525     * Adds a CSS class to this node.
 526     *
 527     * @param string $class
 528     * @return bool
 529     */
 530    public function add_class($class) {
 531        if (!in_array($class, $this->classes)) {
 532            $this->classes[] = $class;
 533        }
 534        return true;
 535    }
 536
 537    /**
 538     * Removes a CSS class from this node.
 539     *
 540     * @param string $class
 541     * @return bool True if the class was successfully removed.
 542     */
 543    public function remove_class($class) {
 544        if (in_array($class, $this->classes)) {
 545            $key = array_search($class,$this->classes);
 546            if ($key!==false) {
 547                // Remove the class' array element.
 548                unset($this->classes[$key]);
 549                // Reindex the array to avoid failures when the classes array is iterated later in mustache templates.
 550                $this->classes = array_values($this->classes);
 551
 552                return true;
 553            }
 554        }
 555        return false;
 556    }
 557
 558    /**
 559     * Sets the title for this node and forces Moodle to utilise it.
 560     * @param string $title
 561     */
 562    public function title($title) {
 563        $this->title = $title;
 564        $this->forcetitle = true;
 565    }
 566
 567    /**
 568     * Resets the page specific information on this node if it is being unserialised.
 569     */
 570    public function __wakeup(){
 571        $this->forceopen = false;
 572        $this->isactive = false;
 573        $this->remove_class('active_tree_node');
 574    }
 575
 576    /**
 577     * Checks if this node or any of its children contain the active node.
 578     *
 579     * Recursive.
 580     *
 581     * @return bool
 582     */
 583    public function contains_active_node() {
 584        if ($this->isactive) {
 585            return true;
 586        } else {
 587            foreach ($this->children as $child) {
 588                if ($child->isactive || $child->contains_active_node()) {
 589                    return true;
 590                }
 591            }
 592        }
 593        return false;
 594    }
 595
 596    /**
 597     * To better balance the admin tree, we want to group all the short top branches together.
 598     *
 599     * This means < 8 nodes and no subtrees.
 600     *
 601     * @return bool
 602     */
 603    public function is_short_branch() {
 604        $limit = 8;
 605        if ($this->children->count() >= $limit) {
 606            return false;
 607        }
 608        foreach ($this->children as $child) {
 609            if ($child->has_children()) {
 610                return false;
 611            }
 612        }
 613        return true;
 614    }
 615
 616    /**
 617     * Finds the active node.
 618     *
 619     * Searches this nodes children plus all of the children for the active node
 620     * and returns it if found.
 621     *
 622     * Recursive.
 623     *
 624     * @return navigation_node|false
 625     */
 626    public function find_active_node() {
 627        if ($this->isactive) {
 628            return $this;
 629        } else {
 630            foreach ($this->children as &$child) {
 631                $outcome = $child->find_active_node();
 632                if ($outcome !== false) {
 633                    return $outcome;
 634                }
 635            }
 636        }
 637        return false;
 638    }
 639
 640    /**
 641     * Searches all children for the best matching active node
 642     * @return navigation_node|false
 643     */
 644    public function search_for_active_node() {
 645        if ($this->check_if_active(URL_MATCH_BASE)) {
 646            return $this;
 647        } else {
 648            foreach ($this->children as &$child) {
 649                $outcome = $child->search_for_active_node();
 650                if ($outcome !== false) {
 651                    return $outcome;
 652                }
 653            }
 654        }
 655        return false;
 656    }
 657
 658    /**
 659     * Gets the content for this node.
 660     *
 661     * @param bool $shorttext If true shorttext is used rather than the normal text
 662     * @return string
 663     */
 664    public function get_content($shorttext=false) {
 665        if ($shorttext && $this->shorttext!==null) {
 666            return format_string($this->shorttext);
 667        } else {
 668            return format_string($this->text);
 669        }
 670    }
 671
 672    /**
 673     * Gets the title to use for this node.
 674     *
 675     * @return string
 676     */
 677    public function get_title() {
 678        if ($this->forcetitle || $this->action != null){
 679            return $this->title;
 680        } else {
 681            return '';
 682        }
 683    }
 684
 685    /**
 686     * Used to easily determine if this link in the breadcrumbs has a valid action/url.
 687     *
 688     * @return boolean
 689     */
 690    public function has_action() {
 691        return !empty($this->action);
 692    }
 693
 694    /**
 695     * Used to easily determine if this link in the breadcrumbs is hidden.
 696     *
 697     * @return boolean
 698     */
 699    public function is_hidden() {
 700        return $this->hidden;
 701    }
 702
 703    /**
 704     * Gets the CSS class to add to this node to describe its type
 705     *
 706     * @return string
 707     */
 708    public function get_css_type() {
 709        if (array_key_exists($this->type, $this->namedtypes)) {
 710            return 'type_'.$this->namedtypes[$this->type];
 711        }
 712        return 'type_unknown';
 713    }
 714
 715    /**
 716     * Finds all nodes that are expandable by AJAX
 717     *
 718     * @param array $expandable An array by reference to populate with expandable nodes.
 719     */
 720    public function find_expandable(array &$expandable) {
 721        foreach ($this->children as &$child) {
 722            if ($child->display && $child->has_children() && $child->children->count() == 0) {
 723                $child->id = 'expandable_branch_'.$child->type.'_'.clean_param($child->key, PARAM_ALPHANUMEXT);
 724                $this->add_class('canexpand');
 725                $child->requiresajaxloading = true;
 726                $expandable[] = array('id' => $child->id, 'key' => $child->key, 'type' => $child->type);
 727            }
 728            $child->find_expandable($expandable);
 729        }
 730    }
 731
 732    /**
 733     * Finds all nodes of a given type (recursive)
 734     *
 735     * @param int $type One of navigation_node::TYPE_*
 736     * @return array
 737     */
 738    public function find_all_of_type($type) {
 739        $nodes = $this->children->type($type);
 740        foreach ($this->children as &$node) {
 741            $childnodes = $node->find_all_of_type($type);
 742            $nodes = array_merge($nodes, $childnodes);
 743        }
 744        return $nodes;
 745    }
 746
 747    /**
 748     * Removes this node if it is empty
 749     */
 750    public function trim_if_empty() {
 751        if ($this->children->count() == 0) {
 752            $this->remove();
 753        }
 754    }
 755
 756    /**
 757     * Creates a tab representation of this nodes children that can be used
 758     * with print_tabs to produce the tabs on a page.
 759     *
 760     * call_user_func_array('print_tabs', $node->get_tabs_array());
 761     *
 762     * @param array $inactive
 763     * @param bool $return
 764     * @return array Array (tabs, selected, inactive, activated, return)
 765     */
 766    public function get_tabs_array(array $inactive=array(), $return=false) {
 767        $tabs = array();
 768        $rows = array();
 769        $selected = null;
 770        $activated = array();
 771        foreach ($this->children as $node) {
 772            $tabs[] = new tabobject($node->key, $node->action, $node->get_content(), $node->get_title());
 773            if ($node->contains_active_node()) {
 774                if ($node->children->count() > 0) {
 775                    $activated[] = $node->key;
 776                    foreach ($node->children as $child) {
 777                        if ($child->contains_active_node()) {
 778                            $selected = $child->key;
 779                        }
 780                        $rows[] = new tabobject($child->key, $child->action, $child->get_content(), $child->get_title());
 781                    }
 782                } else {
 783                    $selected = $node->key;
 784                }
 785            }
 786        }
 787        return array(array($tabs, $rows), $selected, $inactive, $activated, $return);
 788    }
 789
 790    /**
 791     * Sets the parent for this node and if this node is active ensures that the tree is properly
 792     * adjusted as well.
 793     *
 794     * @param navigation_node $parent
 795     */
 796    public function set_parent(navigation_node $parent) {
 797        // Set the parent (thats the easy part)
 798        $this->parent = $parent;
 799        // Check if this node is active (this is checked during construction)
 800        if ($this->isactive) {
 801            // Force all of the parent nodes open so you can see this node
 802            $this->parent->force_open();
 803            // Make all parents inactive so that its clear where we are.
 804            $this->parent->make_inactive();
 805        }
 806    }
 807
 808    /**
 809     * Hides the node and any children it has.
 810     *
 811     * @since Moodle 2.5
 812     * @param array $typestohide Optional. An array of node types that should be hidden.
 813     *      If null all nodes will be hidden.
 814     *      If an array is given then nodes will only be hidden if their type mtatches an element in the array.
 815     *          e.g. array(navigation_node::TYPE_COURSE) would hide only course nodes.
 816     */
 817    public function hide(array $typestohide = null) {
 818        if ($typestohide === null || in_array($this->type, $typestohide)) {
 819            $this->display = false;
 820            if ($this->has_children()) {
 821                foreach ($this->children as $child) {
 822                    $child->hide($typestohide);
 823                }
 824            }
 825        }
 826    }
 827
 828    /**
 829     * Get the action url for this navigation node.
 830     * Called from templates.
 831     *
 832     * @since Moodle 3.2
 833     */
 834    public function action() {
 835        if ($this->action instanceof moodle_url) {
 836            return $this->action;
 837        } else if ($this->action instanceof action_link) {
 838            return $this->action->url;
 839        }
 840        return $this->action;
 841    }
 842
 843    /**
 844     * Add the menu item to handle locking and unlocking of a conext.
 845     *
 846     * @param \navigation_node $node Node to add
 847     * @param \context $context The context to be locked
 848     */
 849    protected function add_context_locking_node(\navigation_node $node, \context $context) {
 850        global $CFG;
 851        // Manage context locking.
 852        if (!empty($CFG->contextlocking) && has_capability('moodle/site:managecontextlocks', $context)) {
 853            $parentcontext = $context->get_parent_context();
 854            if (empty($parentcontext) || !$parentcontext->locked) {
 855                if ($context->locked) {
 856                    $lockicon = 'i/unlock';
 857                    $lockstring = get_string('managecontextunlock', 'admin');
 858                } else {
 859                    $lockicon = 'i/lock';
 860                    $lockstring = get_string('managecontextlock', 'admin');
 861                }
 862                $node->add(
 863                    $lockstring,
 864                    new moodle_url(
 865                        '/admin/lock.php',
 866                        [
 867                            'id' => $context->id,
 868                        ]
 869                    ),
 870                    self::TYPE_SETTING,
 871                    null,
 872                    'contextlocking',
 873                     new pix_icon($lockicon, '')
 874                );
 875            }
 876        }
 877
 878    }
 879}
 880
 881/**
 882 * Navigation node collection
 883 *
 884 * This class is responsible for managing a collection of navigation nodes.
 885 * It is required because a node's unique identifier is a combination of both its
 886 * key and its type.
 887 *
 888 * Originally an array was used with a string key that was a combination of the two
 889 * however it was decided that a better solution would be to use a class that
 890 * implements the standard IteratorAggregate interface.
 891 *
 892 * @package   core
 893 * @category  navigation
 894 * @copyright 2010 Sam Hemelryk
 895 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 896 */
 897class navigation_node_collection implements IteratorAggregate, Countable {
 898    /**
 899     * A multidimensional array to where the first key is the type and the second
 900     * key is the nodes key.
 901     * @var array
 902     */
 903    protected $collection = array();
 904    /**
 905     * An array that contains references to nodes in the same order they were added.
 906     * This is maintained as a progressive array.
 907     * @var array
 908     */
 909    protected $orderedcollection = array();
 910    /**
 911     * A reference to the last node that was added to the collection
 912     * @var navigation_node
 913     */
 914    protected $last = null;
 915    /**
 916     * The total number of items added to this array.
 917     * @var int
 918     */
 919    protected $count = 0;
 920
 921    /**
 922     * Label for collection of nodes.
 923     * @var string
 924     */
 925    protected $collectionlabel = '';
 926
 927    /**
 928     * Adds a navigation node to the collection
 929     *
 930     * @param navigation_node $node Node to add
 931     * @param string $beforekey If specified, adds before a node with this key,
 932     *   otherwise adds at end
 933     * @return navigation_node Added node
 934     */
 935    public function add(navigation_node $node, $beforekey=null) {
 936        global $CFG;
 937        $key = $node->key;
 938        $type = $node->type;
 939
 940        // First check we have a 2nd dimension for this type
 941        if (!array_key_exists($type, $this->orderedcollection)) {
 942            $this->orderedcollection[$type] = array();
 943        }
 944        // Check for a collision and report if debugging is turned on
 945        if ($CFG->debug && array_key_exists($key, $this->orderedcollection[$type])) {
 946            debugging('Navigation node intersect: Adding a node that already exists '.$key, DEBUG_DEVELOPER);
 947        }
 948
 949        // Find the key to add before
 950        $newindex = $this->count;
 951        $last = true;
 952        if ($beforekey !== null) {
 953            foreach ($this->collection as $index => $othernode) {
 954                if ($othernode->key === $beforekey) {
 955                    $newindex = $index;
 956                    $last = false;
 957                    break;
 958                }
 959            }
 960            if ($newindex === $this->count) {
 961                debugging('Navigation node add_before: Reference node not found ' . $beforekey .
 962                        ', options: ' . implode(' ', $this->get_key_list()), DEBUG_DEVELOPER);
 963            }
 964        }
 965
 966        // Add the node to the appropriate place in the by-type structure (which
 967        // is not ordered, despite the variable name)
 968        $this->orderedcollection[$type][$key] = $node;
 969        if (!$last) {
 970            // Update existing references in the ordered collection (which is the
 971            // one that isn't called 'ordered') to shuffle them along if required
 972            for ($oldindex = $this->count; $oldindex > $newindex; $oldindex--) {
 973                $this->collection[$oldindex] = $this->collection[$oldindex - 1];
 974            }
 975        }
 976        // Add a reference to the node to the progressive collection.
 977        $this->collection[$newindex] = $this->orderedcollection[$type][$key];
 978        // Update the last property to a reference to this new node.
 979        $this->last = $this->orderedcollection[$type][$key];
 980
 981        // Reorder the array by index if needed
 982        if (!$last) {
 983            ksort($this->collection);
 984        }
 985        $this->count++;
 986        // Return the reference to the now added node
 987        return $node;
 988    }
 989
 990    /**
 991     * Return a list of all the keys of all the nodes.
 992     * @return array the keys.
 993     */
 994    public function get_key_list() {
 995        $keys = array();
 996        foreach ($this->collection as $node) {
 997            $keys[] = $node->key;
 998        }
 999        return $keys;
1000    }
1001
1002    /**
1003     * Set a label for this collection.
1004     *
1005     * @param string $label
1006     */
1007    public function set_collectionlabel($label) {
1008        $this->collectionlabel = $label;
1009    }
1010
1011    /**
1012     * Return a label for this collection.
1013     *
1014     * @return string
1015     */
1016    public function get_collectionlabel() {
1017        return $this->collectionlabel;
1018    }
1019
1020    /**
1021     * Fetches a node from this collection.
1022     *
1023     * @param string|int $key The key of the node we want to find.
1024     * @param int $type One of navigation_node::TYPE_*.
1025     * @return navigation_node|null
1026     */
1027    public function get($key, $type=null) {
1028        if ($type !== null) {
1029            // If the type is known then we can simply check and fetch
1030            if (!empty($this->orderedcollection[$type][$key])) {
1031                return $this->orderedcollection[$type][$key];
1032            }
1033        } else {
1034            // Because we don't know the type we look in the progressive array
1035            foreach ($this->collection as $node) {
1036                if ($node->key === $key) {
1037                    return $node;
1038                }
1039            }
1040        }
1041        return false;
1042    }
1043
1044    /**
1045     * Searches for a node with matching key and type.
1046     *
1047     * This function searches both the nodes in this collection and all of
1048     * the nodes in each collection belonging to the nodes in this collection.
1049     *
1050     * Recursive.
1051     *
1052     * @param string|int $key  The key of the node we want to find.
1053     * @param int $type  One of navigation_node::TYPE_*.
1054     * @return navigation_node|null
1055     */
1056    public function find($key, $type=null) {
1057        if ($type !== null && array_key_exists($type, $this->orderedcollection) && array_key_exists($key, $this->orderedcollection[$type])) {
1058            return $this->orderedcollection[$type][$key];
1059        } else {
1060            $nodes = $this->getIterator();
1061            // Search immediate children first
1062            foreach ($nodes as &$node) {
1063                if ($node->key === $key && ($type === null || $type === $node->type)) {
1064                    return $node;
1065                }
1066            }
1067            // Now search each childs children
1068            foreach ($nodes as &$node) {
1069                $result = $node->children->find($key, $type);
1070                if ($result !== false) {
1071                    return $result;
1072                }
1073            }
1074        }
1075        return false;
1076    }
1077
1078    /**
1079     * Fetches the last node that was added to this collection
1080     *
1081     * @return navigation_node
1082     */
1083    public function last() {
1084        return $this->last;
1085    }
1086
1087    /**
1088     * Fetches all nodes of a given type from this collection
1089     *
1090     * @param string|int $type  node type being searched for.
1091     * @return array ordered collection
1092     */
1093    public function type($type) {
1094        if (!array_key_exists($type, $this->orderedcollection)) {
1095            $this->orderedcollection[$type] = array();
1096        }
1097        return $this->orderedcollection[$type];
1098    }
1099    /**
1100     * Removes the node with the given key and type from the collection
1101     *
1102     * @param string|int $key The key of the node we want to find.
1103     * @param int $type
1104     * @return bool
1105     */
1106    public function remove($key, $type=null) {
1107        $child = $this->get($key, $type);
1108        if ($child !== false) {
1109            foreach ($this->collection as $colkey => $node) {
1110                if ($node->key === $key && (is_null($type) || $node->type == $type)) {
1111                    unset($this->collection[$colkey]);
1112                    $this->collection = array_values($this->collection);
1113                    break;
1114                }
1115            }
1116            unset($this->orderedcollection[$child->type][$child->key]);
1117            $this->count--;
1118            return true;
1119        }
1120        return false;
1121    }
1122
1123    /**
1124     * Gets the number of nodes in this collection
1125     *
1126     * This option uses an internal count rather than counting the actual options to avoid
1127     * a performance hit through the count function.
1128     *
1129     * @return int
1130     */
1131    public function count() {
1132        return $this->count;
1133    }
1134    /**
1135     * Gets an array iterator for the collection.
1136     *
1137     * This is required by the IteratorAggregator interface and is used by routines
1138     * such as the foreach loop.
1139     *
1140     * @return ArrayIterator
1141     */
1142    public function getIterator() {
1143        return new ArrayIterator($this->collection);
1144    }
1145}
1146
1147/**
1148 * The global navigation class used for... the global navigation
1149 *
1150 * This class is used by PAGE to store the global navigation for the site
1151 * and is then used by the settings nav and navbar to save on processing and DB calls
1152 *
1153 * See
1154 * {@link lib/pagelib.php} {@link moodle_page::initialise_theme_and_output()}
1155 * {@link lib/ajax/getnavbranch.php} Called by ajax
1156 *
1157 * @package   core
1158 * @category  navigation
1159 * @copyright 2009 Sam Hemelryk
1160 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1161 */
1162class global_navigation extends navigation_node {
1163    /** @var moodle_page The Moodle page this navigation object belongs to. */
1164    protected $page;
1165    /** @var bool switch to let us know if the navigation object is initialised*/
1166    protected $initialised = false;
1167    /** @var array An array of course information */
1168    protected $mycourses = array();
1169    /** @var navigation_node[] An array for containing  root navigation nodes */
1170    protected $rootnodes = array();
1171    /** @var bool A switch for whether to show empty sections in the navigation */
1172    protected $showemptysections = true;
1173    /** @var bool A switch for whether courses should be shown within categories on the navigation. */
1174    protected $showcategories = null;
1175    /** @var null@var bool A switch for whether or not to show categories in the my courses branch. */
1176    protected $showmycategories = null;
1177    /** @var array An array of stdClasses for users that the navigation is extended for */
1178    protected $extendforuser = array();
1179    /** @var navigation_cache */
1180    protected $cache;
1181    /** @var array An array of course ids that are present in the navigation */
1182    protected $addedcourses = array();
1183    /** @var bool */
1184    protected $allcategoriesloaded = false;
1185    /** @var array An array of category ids that are included in the navigation */
1186    protected $addedcategories = array();
1187    /** @var int expansion limit */
1188    protected $expansionlimit = 0;
1189    /** @var int userid to allow parent to see child's profile page navigation */
1190    protected $useridtouseforparentchecks = 0;
1191    /** @var cache_session A cache that stores information on expanded courses */
1192    protected $cacheexpandcourse = null;
1193
1194    /** Used when loading categories to load all top level categories [parent = 0] **/
1195    const LOAD_ROOT_CATEGORIES = 0;
1196    /** Used when loading categories to load all categories **/
1197    const LOAD_ALL_CATEGORIES = -1;
1198
1199    /**
1200     * Constructs a new global navigation
1201     *
1202     * @param moodle_page $page The page this navigation object belongs to
1203     */
1204    public function __construct(moodle_page $page) {
1205        global $CFG, $SITE, $USER;
1206
1207        if (during_initial_install()) {
1208            return;
1209        }
1210
1211        if (get_home_page() == HOMEPAGE_SITE) {
1212            // We are using the site home for the root element
1213            $properties = array(
1214                'key' => 'home',
1215                'type' => navigation_node::TYPE_SYSTEM,
1216                'text' => get_string('home'),
1217                'action' => new moodle_url('/'),
1218                'icon' => new pix_icon('i/home', '')
1219            );
1220        } else {
1221            // We are using the users my moodle for the root element
1222            $properties = array(
1223                'key' => 'myhome',
1224                'type' => navigation_node::TYPE_SYSTEM,
1225                'text' => get_string('myhome'),
1226                'action' => new moodle_url('/my/'),
1227                'icon' => new pix_icon('i/dashboard', '')
1228            );
1229        }
1230
1231        // Use the parents constructor.... good good reuse
1232        parent::__construct($properties);
1233        $this->showinflatnavigation = true;
1234
1235        // Initalise and set defaults
1236        $this->page = $page;
1237        $this->forceopen = true;
1238        $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
1239    }
1240
1241    /**
1242     * Mutator to set userid to allow parent to see child's profile
1243     * page navigation. See MDL-25805 for initial issue. Linked to it
1244     * is an issue explaining why this is a REALLY UGLY HACK thats not
1245     * for you to use!
1246     *
1247     * @param int $userid userid of profile page that parent wants to navigate around.
1248     */
1249    public function set_userid_for_parent_checks($userid) {
1250        $this->useridtouseforparentchecks = $userid;
1251    }
1252
1253
1254    /**
1255     * Initialises the navigation object.
1256     *
1257     * This causes the navigation object to look at the current state of the page
1258     * that it is associated with and then load the appropriate content.
1259     *
1260     * This should only occur the first time that the navigation structure is utilised
1261     * which will normally be either when the navbar is called to be displayed or
1262     * when a block makes use of it.
1263     *
1264     * @return bool
1265     */
1266    public function initialise() {
1267        global $CFG, $SITE, $USER;
1268        // Check if it has already been initialised
1269        if ($this->initialised || during_initial_install()) {
1270            return true;
1271        }
1272        $this->initialised = true;
1273
1274        // Set up the five base root nodes. These are nodes where we will put our
1275        // content and are as follows:
1276        // site: Navigation for the front page.
1277        // myprofile: User profile information goes here.
1278        // currentcourse: The course being currently viewed.
1279        // mycourses: The users courses get added here.
1280        // courses: Additional courses are added here.
1281        // users: Other users information loaded here.
1282        $this->rootnodes = array();
1283        if (get_home_page() == HOMEPAGE_SITE) {
1284            // The home element should be my moodle because the root element is the site
1285            if (isloggedin() && !isguestuser()) {  // Makes no sense if you aren't logged in
1286                $this->rootnodes['home'] = $this->add(get_string('myhome'), new moodle_url('/my/'),
1287                    self::TYPE_SETTING, null, 'myhome', new pix_icon('i/dashboard', ''));
1288                $this->rootnodes['home']->showinflatnavigation = true;
1289            }
1290        } else {
1291            // The home element should be the site because the root node is my moodle
1292            $this->rootnodes['home'] = $this->add(get_string('sitehome'), new moodle_url('/'),
1293                self::TYPE_SETTING, null, 'home', new pix_icon('i/home', ''));
1294            $this->rootnodes['home']->showinflatnavigation = true;
1295            if (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_MY)) {
1296                // We need to stop automatic redirection
1297                $this->rootnodes['home']->action->param('redirect', '0');
1298            }
1299        }
1300        $this->rootnodes['site'] = $this->add_course($SITE);
1301        $this->rootnodes['myprofile'] = $this->add(get_string('profile'), null, self::TYPE_USER, null, 'myprofile');
1302        $this->rootnodes['currentcourse'] = $this->add(get_string('currentcourse'), null, self::TYPE_ROOTNODE, null, 'currentcourse');
1303        $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), null, self::TYPE_ROOTNODE, null, 'mycourses', new pix_icon('i/course', ''));
1304        $this->rootnodes['courses'] = $this->add(get_string('courses'), new moodle_url('/course/index.php'), self::TYPE_ROOTNODE, null, 'courses');
1305        if (!core_course_category::user_top()) {
1306            $this->rootnodes['courses']->hide();
1307        }
1308        $this->rootnodes['users'] = $this->add(get_string('users'), null, self::TYPE_ROOTNODE, null, 'users');
1309
1310        // We always load the frontpage course to ensure it is available without
1311        // JavaScript enabled.
1312        $this->add_front_page_course_essentials($this->rootnodes['site'], $SITE);
1313        $this->load_course_sections($SITE, $this->rootnodes['site']);
1314
1315        $course = $this->page->course;
1316        $this->load_courses_enrolled();
1317
1318        // $issite gets set to true if the current pages course is the sites frontpage course
1319        $issite = ($this->page->course->id == $SITE->id);
1320
1321        // Determine if the user is enrolled in any course.
1322        $enrolledinanycourse = enrol_user_sees_own_courses();
1323
1324        $this->rootnodes['currentcourse']->mainnavonly = true;
1325        if ($enrolledinanycourse) {
1326            $this->rootnodes['mycourses']->isexpandable = true;
1327            $this->rootnodes['mycourses']->showinflatnavigation = true;
1328            if ($CFG->navshowallcourses) {
1329                // When we show all courses we need to show both the my courses and the regular courses branch.
1330                $this->rootnodes['courses']->isexpandable = true;
1331            }
1332        } else {
1333            $this->rootnodes['courses']->isexpandable = true;
1334        }
1335        $this->rootnodes['mycourses']->forceopen = true;
1336
1337        $canviewcourseprofile = true;
1338
1339        // Next load context specific content into the navigation
1340        switch ($this->page->context->contextlevel) {
1341            case CONTEXT_SYSTEM :
1342                // Nothing left to do here I feel.
1343                break;
1344            case CONTEXT_COURSECAT :
1345                // This is essential, we must load categories.
1346                $this->load_all_categories($this->page->context->instanceid, true);
1347                break;
1348            case CONTEXT_BLOCK :
1349            case CONTEXT_COURSE :
1350                if ($issite) {
1351                    // Nothing left to do here.
1352                    break;
1353                }
1354
1355                // Load the course assoc…

Large files files are truncated, but you can click here to view the full file