Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 401 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          // Search and set the active node.
  84          $this->set_active_node();
  85          $this->initialised = true;
  86      }
  87  
  88      /**
  89       * Get the site admin node if available.
  90       *
  91       * @return navigation_node|null
  92       */
  93      private function get_site_admin_node(): ?navigation_node {
  94          // Add the site admin node. We are using the settingsnav so as to avoid rechecking permissions again.
  95          $settingsnav = $this->page->settingsnav;
  96          $node = $settingsnav->find('siteadministration', self::TYPE_SITE_ADMIN);
  97          if (!$node) {
  98              // Try again. This node can exist with 2 different keys.
  99              $node = $settingsnav->find('root', self::TYPE_SITE_ADMIN);
 100          }
 101  
 102          return $node ?: null;
 103      }
 104  
 105      /**
 106       * Find and set the active node. Initially searches based on URL/explicitly set active node.
 107       * If nothing is found, it checks the following:
 108       *      - If the node is a site page, set 'Home' as active
 109       *      - If within a course context, set 'My courses' as active
 110       *      - If within a course category context, set 'Site Admin' (if available) else set 'Home'
 111       *      - Else if available set site admin as active
 112       *      - Fallback, set 'Home' as active
 113       */
 114      private function set_active_node(): void {
 115          global $SITE;
 116          $activenode = $this->search_and_set_active_node($this);
 117          // If we haven't found an active node based on the standard search. Follow the criteria above.
 118          if (!$activenode) {
 119              $children = $this->get_children_key_list();
 120              $navactivenode = $this->page->navigation->find_active_node();
 121              $activekey = 'home';
 122              if (isset($navactivenode->parent) && $navactivenode->parent->text == get_string('sitepages')) {
 123                  $activekey = 'home';
 124              } else if (in_array($this->context->contextlevel, [CONTEXT_COURSE, CONTEXT_MODULE])) {
 125                  if ($this->page->course->id != $SITE->id) {
 126                      $activekey = 'courses';
 127                  }
 128              } else if (in_array('siteadminnode', $children) && $node = $this->get_site_admin_node()) {
 129                  if ($this->context->contextlevel == CONTEXT_COURSECAT || $node->search_for_active_node(URL_MATCH_EXACT)) {
 130                      $activekey = 'siteadminnode';
 131                  }
 132              }
 133  
 134              if ($activekey && $activenode = $this->find($activekey, null)) {
 135                  $activenode->make_active();
 136              }
 137          }
 138      }
 139  
 140      /**
 141       * Searches all children for the matching active node
 142       *
 143       * This method recursively traverse through the node tree to
 144       * find the node to activate/highlight:
 145       * 1. If the user had set primary node key to highlight, it
 146       *    tries to match this key with the node(s). Hence it would
 147       *    travel all the nodes.
 148       * 2. If no primary key is provided by the dev, then it would
 149       *    check for the active node set in the tree.
 150       *
 151       * @param navigation_node $node
 152       * @param array $actionnodes navigation nodes array to set active and inactive.
 153       * @return navigation_node|null
 154       */
 155      private function search_and_set_active_node(navigation_node $node,
 156          array &$actionnodes = []): ?navigation_node {
 157          global $PAGE;
 158  
 159          $activekey = $PAGE->get_primary_activate_tab();
 160          if ($activekey) {
 161              if ($node->key && ($activekey === $node->key)) {
 162                  return $node;
 163              }
 164          } else if ($node->check_if_active()) {
 165              return $node;
 166          }
 167  
 168          foreach ($node->children as $child) {
 169              $outcome = $this->search_and_set_active_node($child, $actionnodes);
 170              if ($outcome !== null) {
 171                  $outcome->make_active();
 172                  $actionnodes['active'] = $outcome;
 173                  if ($activekey === null) {
 174                      return $actionnodes['active'];
 175                  }
 176              } else {
 177                  // If the child is active then make it inactive.
 178                  if ($child->isactive) {
 179                      $actionnodes['set_inactive'][] = $child;
 180                  }
 181              }
 182          }
 183  
 184          // If we have successfully found an active node then reset any other nodes to inactive.
 185          if (isset($actionnodes['set_inactive']) && isset($actionnodes['active'])) {
 186              foreach ($actionnodes['set_inactive'] as $inactivenode) {
 187                  $inactivenode->make_inactive();
 188              }
 189              $actionnodes['set_inactive'] = [];
 190          }
 191          return ($actionnodes['active'] ?? null);
 192      }
 193  }