PageRenderTime 68ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/navigationlib.php

http://github.com/moodle/moodle
PHP | 5875 lines | 3577 code | 506 blank | 1792 comment | 1031 complexity | 76903fa81e3138bfc01fd296c65665c0 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause

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

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