Instantly share code, notes, and snippets.
-
Star
4
(4)
You must be signed in to star a gist -
Fork
4
(4)
You must be signed in to fork a gist
-
Save coxmi/191ce08d22fed74da05a to your computer and use it in GitHub Desktop.
Custom wordpress menu: Get menu as hierarchical array with children
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@MichaelAnd you should add a backlink and author tag as well, as there are projects simply including the file as is (like wuxt for example, you get attributed in the readme, but when just looking at the file it is not clear)