Skip to content

Instantly share code, notes, and snippets.

@coxmi
Last active June 27, 2023 03:37
Show Gist options
  • Save coxmi/191ce08d22fed74da05a to your computer and use it in GitHub Desktop.
Save coxmi/191ce08d22fed74da05a to your computer and use it in GitHub Desktop.
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;
}
}
@coxmi
Copy link
Author

coxmi commented Jan 27, 2021

@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.

@Larzans
Copy link

Larzans commented Feb 8, 2021

@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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment