I can't find if WordPress stores the number of items in a menu. Basically I need to find out how many items are in a menu to work out the percentage each item should take up in the header bar. Is there a function? Or is the best way to achieve this doing something with WP_Query
?
2 Answers
I was curious and decided to check it out, regardless if it's relevant for a CSS problem ;-)
I first peeked into the database tables to find more about the menu structure:
Menu - building blocks
Each navigational menu is registered as a term in the nav_menu
taxonomy.
Then when we add items to that menu, we are creating new post objects of the type nav_menu_item
.
Now the tricky part, where's the tree structure for that menu stored?
It's not the stored in the post_parent
field of the nav_menu_item
posts, as expected.
We actually find it in the post meta table where it's stored under the _menu_item_menu_item_parent
meta key, for each nav_menu_item
post.
Now there are various ways to get the count. Here are few examples:
Example - get_term_by()
/ wp_get_nav_menu_object()
First the easy one: If we need to get the number of items in a particular menu we can actually get it from a term query, because it's stored in the count
column of the wp_term_taxonomy
table.
First we get the menu term with:
$menuterm = get_term_by( 'slug', 'some-menu-slug', 'nav_menu' );
There's exists a wrapper for this, namely:
$menuterm = wp_get_nav_menu_object( 'some-menu-slug' );
To get the total count of items in the menu, we can then use:
$total_count = ( $menuterm instanceof \WP_Term ) ? $menuterm->count : 0;
Example - WP_Query()
/ get_posts()
If we only want to count menu items without parents, then we can collect all the post objects within our menu with:
if( $menuterm instanceof \WP_Term )
$pids = get_objects_in_term( $menuterm->term_id, 'nav_menu' );
where $pids
is an array of post IDs.
Then we can make the following meta query:
$args = [
'post__in' => $pids,
'post_type' => 'nav_menu_item',
'fields' => 'ids',
'ignore_sticky_posts' => true,
'nopaging' => true,
'meta_query' => [
[
'key' => '_menu_item_menu_item_parent',
'value' => 0,
'compare' => '=',
]
]
];
$count = count( get_posts( $args ) );
There's also a way with:
$q = new WP_Query();
$count = count( $q->query( $args ) );
or just:
$count = $q->found_posts;
with the common trick, posts_per_page
as 1, to reduce fetched data.
Example - wp_get_nav_menu_items()
There exists handy wrappers in core, like wp_get_nav_menu_items( $menu, $args )
where $args
are get_posts()
arguments.
Here's one way:
$count = count(
wp_list_filter(
wp_get_nav_menu_items( 'some-menu-slug' ),
[ 'menu_item_parent' => 0 ]
)
);
We can also use:
$args = [
'meta_query' => [
[
'key' => '_menu_item_menu_item_parent',
'value' => 0,
'compare' => '=',
]
]
];
$count = count( wp_get_nav_menu_items( 'some-menu-slug', $args ) );
Example - filter/hooks
There are many filters/actions that we could hook into to do the counting, e.g. the wp_get_nav_menu_items
filter. Even hooks within the nav menu walker.
We could even hook into the wp_update_nav_menu
that fires when the menu is updated. Then we could count the items with no parents and store it e.g. as term meta data.
Then there are also various ways with javascript.
Hopefully this will give you some ideas to take this further.
-
Thanks, Marked this as the most useful answer because you gave me a lot to go on and work from there. Posted the solution I came up with to solve the problem below in case you want to check it out. Jul 5, 2016 at 15:17
-
You're welcome, good to hear it helped you work out a solution that suited you best @LewisSelf– birgireJul 5, 2016 at 15:22
Thanks for the help birgire. That gave me a lot to think about. Eventually I have came up with a solution using a filter I can count the number of parents the menu has. The only problem with this approach is that it will run every time a menu is displayed.
function my_nav_menu_objects($sorted_menu_items, $args)
{
if($args->menu == 'header_menu')
{
$counter = 0;
foreach($sorted_menu_items as $sorted_menu_item)
{
if($sorted_menu_item->menu_item_parent == 0)
{
$counter++;
}
}
echo 'Menu Count: ' . $counter;
}
return $sorted_menu_items;
}
add_filter('wp_nav_menu_objects', 'my_nav_menu_objects', 10, 2);
Change the "header_menu" to what ever the slug of your navigation you want to check is. You could then store this into an option and call it when needed.