Instantly share code, notes, and snippets.
Custom wordpress menu: Get menu as hierarchical array with children
<?php | |
/* | |
* Helper class to display menu items in a hierarchical array | |
* Usage: $mainMenu = new Menu('primary'); | |
* $menuItems = $mainMenu->getTree(); | |
* $menuItems = $mainMenu->getMenuItems(); | |
*/ | |
class Menu { | |
protected $menu = []; | |
protected $tree = []; | |
/** | |
* Initialises $this->menu | |
*/ | |
public function __construct($menuName = '', $args = array(), $filter = null) { | |
$filter = is_callable($filter) ? $filter : null; | |
if (empty($menuName)) { | |
throw new Exception('No menu location name provided.'); | |
return; | |
} | |
$menuLocations = get_nav_menu_locations(); | |
if (empty($menuLocations[$menuName])) return; | |
$this->menu = $this->retrieveMenu(wp_get_nav_menu_object($menuLocations[$menuName]), $args, $filter); | |
} | |
/** | |
* Retrieves menu from wordpress | |
* @uses private core function _wp_menu_item_classes_by_context() | |
* @return Array|null $this->menu | |
*/ | |
protected function retrieveMenu($navMenuObject = null, $args = array(), $filter = null) { | |
global $wp_query; | |
global $post; | |
$queriedPostType = get_post_type(); | |
$this->queriedPostType = $queriedPostType; | |
$isTax = (is_tax() || is_tag()); | |
if (!$navMenuObject) return null; | |
$menu_items = wp_get_nav_menu_items( $navMenuObject->term_id , $args ); | |
/* the following was taken from wp_nav_menu core function | |
* line 154–169: https://developer.wordpress.org/reference/functions/wp_nav_menu/ | |
*/ | |
// set up menu item classes | |
_wp_menu_item_classes_by_context($menu_items); | |
$sorted_menu_items = $menu_items_with_children = array(); | |
foreach ( (array) $menu_items as $menu_item ) { | |
$sorted_menu_items[ $menu_item->menu_order ] = $menu_item; | |
if ( $menu_item->menu_item_parent ) | |
$menu_items_with_children[ $menu_item->menu_item_parent ] = true; | |
} | |
// Add the menu-item-has-children class where applicable | |
if ( $menu_items_with_children ) { | |
foreach ( $sorted_menu_items as &$menu_item ) { | |
if ( isset( $menu_items_with_children[ $menu_item->ID ] ) ) | |
$menu_item->classes[] = 'menu-item-has-children'; | |
} | |
} | |
/* | |
* end taken wp_nav_menu from core function | |
*/ | |
foreach($menu_items as $key => &$menu_item) { | |
// Add isCurrent class to parents or ancestors | |
$menu_item->isCurrent = false; | |
if ($menu_item->current_item_ancestor) $menu_item->isCurrent = 'ancestor'; | |
if ($menu_item->current_item_parent && ($menu_item->type !== 'taxonomy')) { | |
$menu_item->isCurrent = 'parent ancestor'; | |
} | |
if ($menu_item->current) $menu_item->isCurrent = 'current'; | |
// Add isCurrent class, and ancestor classes to custom post type archive menu items | |
$isCurrentCustomPostType = static::menuItemIsCustomPostTypeArchive($menu_item, $queriedPostType); | |
// if is current | |
if ($isCurrentCustomPostType) { | |
$menu_item->classes[] = "current-{$queriedPostType}-ancestor"; | |
$menu_item->isCurrent = 'ancestor'; | |
} | |
// if is current and !hierarchical | |
if ($isCurrentCustomPostType && !is_post_type_hierarchical($queriedPostType)) { | |
$menu_item->classes[] = "current-{$queriedPostType}-parent"; | |
$menu_item->isCurrent = 'parent ancestor'; | |
} | |
// if is current and hierarchical and top level | |
if ($post | |
&& $isCurrentCustomPostType | |
&& is_post_type_hierarchical($queriedPostType) | |
&& (count(get_post_ancestors($post->ID)) === 0) | |
) { | |
$menu_item->classes[] = "current-{$queriedPostType}-parent"; | |
$menu_item->isCurrent = 'parent ancestor'; | |
} | |
// if a filter exists, run it | |
if ($filter && is_callable($filter)) { | |
$filtered = $filter($menu_item); | |
$menu_item = $filtered ? $filtered : null; | |
if (!$menu_item) unset($menu_items[$key]); | |
} | |
} | |
return $this->menu = $menu_items; | |
} | |
/** | |
* Returns $this->menu | |
*/ | |
public function getMenuItems() { | |
return $this->menu; | |
} | |
/** | |
* Returns $this->tree | |
* | |
* @return Array|null $tree | |
*/ | |
public function getTree() { | |
if ($this->tree) return $this->tree; | |
return static::buildTree($this->menu, 0, 1); | |
} | |
/** | |
* Transform a navigational menu to a tree structure | |
* | |
* @return Array $branch | |
*/ | |
public static function buildTree(array &$elements, $parentId = 0, $level = 1) { | |
$branch = array(); | |
foreach ( $elements as &$element ) { | |
if ( $element->menu_item_parent == $parentId ) { | |
$subLevel = $level + 1; | |
$element->level = $level; | |
$children = static::buildTree( $elements, $element->ID, $subLevel ); | |
if ($children) { | |
$element->children = $children; | |
} else { | |
$element->children = array(); | |
} | |
$branch[$element->ID] = $element; | |
unset($element); | |
} | |
} | |
return $branch; | |
} | |
/** | |
* Checks if menu item is a custom post type archive | |
* | |
* @return Boolean | |
*/ | |
protected static function menuItemIsCustomPostTypeArchive($menuItem, $type = null) { | |
$isCustomPostType = ( | |
isset($menuItem->type) | |
&& ($menuItem->type === 'post_type_archive') | |
&& (is_post_type_archive(get_post_type()) || is_singular(get_post_type())) | |
); | |
if (!$type) { | |
return $isCustomPostType; | |
} | |
$isOfType = ( | |
$isCustomPostType | |
&& isset($menuItem->object) | |
&& ($menuItem->object === $type) | |
); | |
return $isOfType; | |
} | |
/** | |
* Returns maximum depth of menu tree | |
* | |
* @return Integer | |
*/ | |
public static function menuItemDepth($menuItem = null) { | |
$maxDepth = 0; | |
foreach ($menuItem->children as $child) { | |
if (is_array($child->children)) { | |
$depth = static::menuItemDepth($child) + 1; | |
if ($depth > $maxDepth) $maxDepth = $depth; | |
} | |
} | |
return $maxDepth; | |
} | |
} |
This comment has been minimized.
This comment has been minimized.
@thomasfrobieter You're welcome! I made a couple of tweaks recently after I received a few errors, so I've just updated the gist for you too. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
Hell yes. Finally, better menu arrays, thank you so much!