1

My WP menu structure looks like this (simplified)

<ul>
<li class="dashboard menu-item ... menu-item-56"><a href=#link">WP link</a></li>
<li class="messages menu-item ... menu-item-57"><a href=#custom-link">Messages</a></li>
<li class="profile menu-item ... menu-item-58"><a href=#link">WP link 2</a></li>
</ul>

Now it'd like to add a label/badge with a counter in it but only when the css class "messages" is used.

The counter will be a variable, so this...

<li class="messages menu-item ... menu-item-57"><a href=#link">Messages</a></li>

...should become...

<li class="messages menu-item ... menu-item-57">
     <a href=#link">Messages</a><span class="counter">'. $counter .'</span>
</li>

[![enter image description here][1]][1]

My starter's code...

function add_counter_comments($items, $args) {

  if ($args->theme_location == 'my_custom_menu' ) {

   //what code should I add?

  }

  return $items;
}
add_filter('wp_nav_menu_items', 'add_counter_comments', 10, 2); 

Update:

My counter looks like this, but I don't see why it doesn't get added.

$current_user = wp_get_current_user();

$args = array(
    'post_author'   => $current_user->ID,
    'status'        => 'approve',
    //...
);

$comments_query = new WP_Comment_Query;
$comments       = $comments_query->query( $args );

$comment_count  = 0;

foreach ($comments as $comment) { 
    $comment_count++;       
} 

$counter = $comment_count;

And then change this line

$item_output .= sprintf( ' <span class="counter">%d</span>', $counter );

I even tested it with this but that didn't worked either.

$item_output .= $counter;
4
  • Regarding your update: What do you mean by "didn't work" ? Are you appending the number of approved comments by the current user? You wan't that number for all your menu items? Isn't that then always the same number for all menu items?
    – birgire
    Jun 15, 2016 at 13:33
  • There's a message system I'm using. These are basically comments with an extra meta_query/meta_key attached to it. I'd like to show my counter only next to the menu link with the class messages if there are new unread messages (comments) and not to any other menu links. The code you shared adds the counter to all menu links and I'm having a hard time to tweak this a bit.
    – kiarashi
    Jun 15, 2016 at 17:38
  • So you want in fact only display this once, in the menu? I updated the answer with a simplification for your comment counting.
    – birgire
    Jun 16, 2016 at 19:26
  • glad to hear it worked, you're welcome.
    – birgire
    Jun 17, 2016 at 6:54

1 Answer 1

3

Counter - Inside Menu Links

You can e.g. use the nav_menu_item_titleCodex filter to adjust the menu titles:

add_filter( 'nav_menu_item_title', function( $title, $item, $args, $depth )
{
    static $instance = 0;

    // Append an increasing counter to the menu title for menu items
    // within a given menu location and menu class

    if(    'my-custom-location' === $args->theme_location 
        && in_array( 'my-custom-class', (array) $item->classes ) 
    )
        $title .= sprintf( ' <span class="counter">%d</span>', ++$instance );

    return $title;

}, 10, 4 );

where you should adjust it to your theme location and menu class.

Counter - Outside Menu Links

The above approach will add the counter within the link title. What if we want to add it right after the menu link? Then we can use the walker_nav_menu_start_elCodex filter:

add_filter( 'walker_nav_menu_start_el', function( $item_output, $item, $depth, $args )
{
    static $instance = 0;

    // Append an increasing counter to the menu link for menu items
    // within a given menu location and menu class

    if(    'my-custom-location' === $args->theme_location 
        && in_array( 'my-custom-class', (array) $item->classes ) 
    )
        $item_output .= sprintf( ' <span class="counter">%d</span>', ++$instance );

    return $item_output;

}, 10, 4 );

Note on the inline documentation

I noticed that the inline documentation for the nav_menu_item_title says $args is an array but it seems to be a stdClass object. So this is somewhat confusing!

If we check out Walker::display_element() then we see that the $args input argument is in fact an array, that becomes merged to the array of the output, element and depth with:

$cb_args = array_merge( array(&$output, $element, $depth), $args);
call_user_func_array(array($this, 'start_el'), $cb_args);

But here $cb_args is an array where the fourth element is a stdClass object (not an array) and this is the fourth input argument of the Walker_Nav_Menu::start_el() method.

The inline documentation for Walker_Nav_Menu::start_el() also says that it's third input argument is an array. when it's an object.

I will consider creating a trac ticket for this.

Update

If your counter is the number of comments, you could simplify your code by using the count attribute. Here's an example:

$counter = get_comments( 
    [ 
        'count'         => true,
        'post_author'   => $current_user->ID,
        'status'        => 'approve',
        // ... etc
    ]
);

You might also want to check if the user is logged in, to avoid running this query for other visitors.

7
  • When I use your function like that, I get an error: Warning: in_array() expects parameter 2 to be array, string given in
    – kiarashi
    Jun 15, 2016 at 9:10
  • Are you using the latest version of the code snippet? @kiarashi
    – birgire
    Jun 15, 2016 at 9:34
  • The array message is gone. Thank you. Also updated my post with one more issue.
    – kiarashi
    Jun 15, 2016 at 9:55
  • It works! I made a small mistake with the class name (used message, while it should've been messages.
    – kiarashi
    Jun 15, 2016 at 10:01
  • I have some difficulties with replacing static $instance = 0; with my own counter function. Will update my post after I tried to solve it myself and failed in doing it. ^^;;
    – kiarashi
    Jun 15, 2016 at 10:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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