2

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?

5

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. – Chess Byte Jul 5 '16 at 15:17
  • You're welcome, good to hear it helped you work out a solution that suited you best @LewisSelf – birgire Jul 5 '16 at 15:22
1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.