PageRenderTime 58ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/navigationlib.php

https://github.com/dongsheng/moodle
PHP | 5939 lines | 3612 code | 515 blank | 1812 comment | 1036 complexity | bd34b8f33e68d6f71ede4bd5592183e3 MD5 | raw file
Possible License(s): BSD-3-Clause, MIT, GPL-3.0, Apache-2.0, LGPL-2.1

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

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