Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   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  namespace core\navigation\views;
  18  
  19  use navigation_node;
  20  
  21  /**
  22   * Class primary.
  23   *
  24   * The primary navigation view is a combination of few components - navigation, output->navbar,
  25   *
  26   * @package     core
  27   * @category    navigation
  28   * @copyright   2021 onwards Peter Dias
  29   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class primary extends view {
  32      /**
  33       * Initialise the primary navigation node
  34       */
  35      public function initialise(): void {
  36          global $CFG;
  37  
  38          if (during_initial_install() || $this->initialised) {
  39              return;
  40          }
  41          $this->id = 'primary_navigation';
  42  
  43          $showhomenode = empty($this->page->theme->removedprimarynavitems) ||
  44              !in_array('home', $this->page->theme->removedprimarynavitems);
  45          // We do not need to change the text for the home/dashboard depending on the set homepage.
  46          if ($showhomenode) {
  47              $sitehome = $this->add(get_string('home'), new \moodle_url('/'), self::TYPE_SYSTEM,
  48                  null, 'home', new \pix_icon('i/home', ''));
  49          }
  50          if (isloggedin() && !isguestuser()) {
  51              $homepage = get_home_page();
  52              if ($homepage == HOMEPAGE_MY || $homepage == HOMEPAGE_MYCOURSES) {
  53                  // We need to stop automatic redirection.
  54                  if ($showhomenode) {
  55                      $sitehome->action->param('redirect', '0');
  56                  }
  57              }
  58  
  59              // Add the dashboard link.
  60              $showmyhomenode = !empty($CFG->enabledashboard) && (empty($this->page->theme->removedprimarynavitems) ||
  61                  !in_array('myhome', $this->page->theme->removedprimarynavitems));
  62              if ($showmyhomenode) {
  63                  $this->add(get_string('myhome'), new \moodle_url('/my/'),
  64                      self::TYPE_SETTING, null, 'myhome', new \pix_icon('i/dashboard', ''));
  65              }
  66  
  67              // Add the mycourses link.
  68              $showcoursesnode = empty($this->page->theme->removedprimarynavitems) ||
  69                  !in_array('courses', $this->page->theme->removedprimarynavitems);
  70              if ($showcoursesnode) {
  71                  $this->add(get_string('mycourses'), new \moodle_url('/my/courses.php'), self::TYPE_ROOTNODE, null, 'mycourses');
  72              }
  73          }
  74  
  75          $showsiteadminnode = empty($this->page->theme->removedprimarynavitems) ||
  76              !in_array('siteadminnode', $this->page->theme->removedprimarynavitems);
  77  
  78          if ($showsiteadminnode && $node = $this->get_site_admin_node()) {
  79              // We don't need everything from the node just the initial link.
  80              $this->add($node->text, $node->action(), self::TYPE_SITE_ADMIN, null, 'siteadminnode', $node->icon);
  81          }
  82  
  83          // Allow plugins to add nodes to the primary navigation.
  84          $hook = new \core\hook\navigation\primary_extend($this);
  85          \core\hook\manager::get_instance()->dispatch($hook);
  86  
  87          // Search and set the active node.
  88          $this->set_active_node();
  89          $this->initialised = true;
  90      }
  91  
  92      /**
  93       * Get the site admin node if available.
  94       *
  95       * @return navigation_node|null
  96       */
  97      private function get_site_admin_node(): ?navigation_node {
  98          // Add the site admin node. We are using the settingsnav so as to avoid rechecking permissions again.
  99          $settingsnav = $this->page->settingsnav;
 100          $node = $settingsnav->find('siteadministration', self::TYPE_SITE_ADMIN);
 101          if (!$node) {
 102              // Try again. This node can exist with 2 different keys.
 103              $node = $settingsnav->find('root', self::TYPE_SITE_ADMIN);
 104          }
 105  
 106          return $node ?: null;
 107      }
 108  
 109      /**
 110       * Find and set the active node. Initially searches based on URL/explicitly set active node.
 111       * If nothing is found, it checks the following:
 112       *      - If the node is a site page, set 'Home' as active
 113       *      - If within a course context, set 'My courses' as active
 114       *      - If within a course category context, set 'Site Admin' (if available) else set 'Home'
 115       *      - Else if available set site admin as active
 116       *      - Fallback, set 'Home' as active
 117       */
 118      private function set_active_node(): void {
 119          global $SITE;
 120          $activenode = $this->search_and_set_active_node($this);
 121          // If we haven't found an active node based on the standard search. Follow the criteria above.
 122          if (!$activenode) {
 123              $children = $this->get_children_key_list();
 124              $navactivenode = $this->page->navigation->find_active_node();
 125              $activekey = 'home';
 126              if (isset($navactivenode->parent) && $navactivenode->parent->text == get_string('sitepages')) {
 127                  $activekey = 'home';
 128              } else if (in_array($this->context->contextlevel, [CONTEXT_COURSE, CONTEXT_MODULE])) {
 129                  if ($this->page->course->id != $SITE->id) {
 130                      $activekey = 'courses';
 131                  }
 132              } else if (in_array('siteadminnode', $children) && $node = $this->get_site_admin_node()) {
 133                  if ($this->context->contextlevel == CONTEXT_COURSECAT || $node->search_for_active_node(URL_MATCH_EXACT)) {
 134                      $activekey = 'siteadminnode';
 135                  }
 136              }
 137  
 138              if ($activekey && $activenode = $this->find($activekey, null)) {
 139                  $activenode->make_active();
 140              }
 141          }
 142      }
 143  
 144      /**
 145       * Searches all children for the matching active node
 146       *
 147       * This method recursively traverse through the node tree to
 148       * find the node to activate/highlight:
 149       * 1. If the user had set primary node key to highlight, it
 150       *    tries to match this key with the node(s). Hence it would
 151       *    travel all the nodes.
 152       * 2. If no primary key is provided by the dev, then it would
 153       *    check for the active node set in the tree.
 154       *
 155       * @param navigation_node $node
 156       * @param array $actionnodes navigation nodes array to set active and inactive.
 157       * @return navigation_node|null
 158       */
 159      private function search_and_set_active_node(navigation_node $node,
 160          array &$actionnodes = []): ?navigation_node {
 161          global $PAGE;
 162  
 163          $activekey = $PAGE->get_primary_activate_tab();
 164          if ($activekey) {
 165              if ($node->key && ($activekey === $node->key)) {
 166                  return $node;
 167              }
 168          } else if ($node->check_if_active()) {
 169              return $node;
 170          }
 171  
 172          foreach ($node->children as $child) {
 173              $outcome = $this->search_and_set_active_node($child, $actionnodes);
 174              if ($outcome !== null) {
 175                  $outcome->make_active();
 176                  $actionnodes['active'] = $outcome;
 177                  if ($activekey === null) {
 178                      return $actionnodes['active'];
 179                  }
 180              } else {
 181                  // If the child is active then make it inactive.
 182                  if ($child->isactive) {
 183                      $actionnodes['set_inactive'][] = $child;
 184                  }
 185              }
 186          }
 187  
 188          // If we have successfully found an active node then reset any other nodes to inactive.
 189          if (isset($actionnodes['set_inactive']) && isset($actionnodes['active'])) {
 190              foreach ($actionnodes['set_inactive'] as $inactivenode) {
 191                  $inactivenode->make_inactive();
 192              }
 193              $actionnodes['set_inactive'] = [];
 194          }
 195          return ($actionnodes['active'] ?? null);
 196      }
 197  }