diff --git a/classes/output/navigation/primary.php b/classes/output/navigation/primary.php index ded227a4396..b49e948bc75 100644 --- a/classes/output/navigation/primary.php +++ b/classes/output/navigation/primary.php @@ -108,7 +108,8 @@ public function export_for_template(?renderer_base $output = null): array { $locationbottom = smartmenu::get_menus_forlocation(smartmenu::LOCATION_BOTTOM, $smartmenus); // Merge the smart menu nodes which contain the main menu location with the primary and custom menu nodes. - $menudata = array_merge($this->get_primary_nav(), $this->get_custom_menu($output), $mainmenu); + $mainsmartmenumergedcustom = array_merge($this->get_custom_menu($output), $mainmenu); + $menudata = (object) $this->merge_primary_and_custom($this->get_primary_nav(), $mainsmartmenumergedcustom); $moremenu = new \core\navigation\output\more_menu((object) $menudata, 'navbar-nav', false); // Menubar. @@ -120,9 +121,10 @@ public function export_for_template(?renderer_base $output = null): array { // Bottom bar. // Include the menu navigation menus to the mobile menu when the bottom bar doesn't have any menus. + $mergecustombottommenus = array_merge($this->get_custom_menu($output), $locationbottom); $mobileprimarynav = (!empty($locationbottom)) - ? array_merge($this->get_primary_nav(), $this->get_custom_menu($output), $locationbottom) - : $mobileprimarynav = $menudata; + ? $this->merge_primary_and_custom($this->get_primary_nav(), $mergecustombottommenus, true) + : $this->merge_primary_and_custom($this->get_primary_nav(), $mainsmartmenumergedcustom, true); if (!empty($mobileprimarynav)) { $bottombar = new \core\navigation\output\more_menu((object) $mobileprimarynav, 'navbar-nav-bottom-bar', false); @@ -307,4 +309,86 @@ public function build_usermenus(&$usermenu, $menus) { array_push($usermenu['items'], $logout); } } + + /** + * Recursive checks if any of the children is active. If that's the case this node (the parent) is active as + * well. If the node has no children, check if the node itself is active. Use pass by reference for the node + * object because we actively change/set the "isactive" flag inside the method and this needs to be kept at the + * callers side. + * Set $expandedmenu to true, if the mobile menu is done, in this case the active flag gets the node that is + * actually active, while the parent hierarchy of the active node gets the flag isopen. + * + * Modifications compared to the original function: + * * Updated the children node type to object + * + * @param object $node + * @param bool $expandedmenu + * @return bool + */ + protected function flag_active_nodes(object $node, bool $expandedmenu = false): bool { + global $FULLME; + $active = false; + foreach (array_keys($node->children ?? []) as $c) { + + // Update the type of child nodes (smart menu). + // To prevent issues with already configured menus, + // The type of children is not updated during the smart menu build process. + $child = (object) $node->children[$c]; + + if ($this->flag_active_nodes($child, $expandedmenu)) { + $active = true; + } + } + // One of the children is active, so this node (the parent) is active as well. + if ($active) { + if ($expandedmenu) { + $node->isopen = true; + } else { + $node->isactive = true; + } + return true; + } + + // By default, the menu item node to check is not active. + $node->isactive = false; + + // Check if the node url matches the called url. The node url may omit the trailing index.php, therefore check + // this as well. + if (empty($node->url)) { + // Current menu node has no url set, so it can't be active. + return false; + } + $nodeurl = parse_url($node->url); + $current = parse_url($FULLME ?? ''); + + $pathmatches = false; + + // Exact match of the path of node and current url. + $nodepath = $nodeurl['path'] ?? '/'; + $currentpath = $current['path'] ?? '/'; + if ($nodepath === $currentpath) { + $pathmatches = true; + } + // The current url may be trailed by a index.php, otherwise it's the same as the node path. + if (!$pathmatches && $nodepath . 'index.php' === $currentpath) { + $pathmatches = true; + } + // No path did match, so the node can't be active. + if (!$pathmatches) { + return false; + } + // We are here because the path matches, so now look at the query string. + $nodequery = $nodeurl['query'] ?? ''; + $currentquery = $current['query'] ?? ''; + // If the node has no query string defined, then the patch match is sufficient. + if (empty($nodeurl['query'])) { + $node->isactive = true; + return true; + } + // If the node contains a query string then also the current url must match this query. + if ($nodequery === $currentquery) { + $node->isactive = true; + } + return $node->isactive; + } } diff --git a/version.php b/version.php index b395416e939..2872aa17ff3 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'theme_boost_union'; -$plugin->version = 2023102035; +$plugin->version = 2023102036; $plugin->release = 'v4.3-r12'; $plugin->requires = 2023100900; $plugin->supported = [403, 403];