Remove class in nav_menu_link_attibutes filter

Let us first look at this part:

I don’t exactly understand what object $item is and this is probably
the problem. When I call get_class() on $item I get WP_Post, but
WP_Post does not have a classes property. So how is $item->classes
returning an array of classes?

Few notes regarding the structure of menus in WordPress:

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

  • The tree structure for each menu is 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.

  • The $item menu item data object is a decorated WP_Post object, i.e. with extra attributes added with the wp_setup_nav_menu_item() function:

    /**
     * Decorates a menu item object with the shared navigation menu item properties.
     *
     * Properties:
     * - ID:               The term_id if the menu item represents a taxonomy term. 
     * - attr_title:       The title attribute of the link element for this menu item.
     * - classes:          The array of class attribute values for the link element of this menu item.
     * - db_id:            The DB ID of this item as a nav_menu_item object, if it exists (0 if it doesn't exist).
     * - description:      The description of this menu item.
     * - menu_item_parent: The DB ID of the nav_menu_item that is this item's menu parent, if any. 0 otherwise.
     * - object:           The type of object originally represented, such as "category," "post", or "attachment."
     * - object_id:        The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories.
     * - post_parent:      The DB ID of the original object's parent object, if any (0 otherwise).
     * - post_title:       A "no title" label if menu item represents a post that lacks a title.
     * - target:           The target attribute of the link element for this menu item.
     * - title:            The title of this menu item.
     * - type:             The family of objects originally represented, such as "post_type" or "taxonomy."
     * - type_label:       The singular label used to describe this type of menu item.
     * - url:              The URL to which this menu item points.
     * - xfn:              The XFN relationship expressed in the link of this menu item.
     * - _invalid:         Whether the menu item represents an object that no longer exists.
    

Then let’s look at this part:

After using the class search-class to locate the item, I would like to
remove it from the item. However, my call to
unset($item->classes[$class_key]); doesn’t work.

It’s too late to remove classes from the $item object, within the nav_menu_link_attributes filter, in order to remove classes from the menu items (li tags).

We can e.g. use the nav_menu_css_class filter to modify these classes.

It’s also probably a better approach in general to use such a filter rather than modifying the data object directly, because other plugins might depend on the raw data object.

Here’s an example how to remove the search-class class:

add_filter( 'nav_menu_css_class', function( $classes, $item, $args, $depth )
{
    return array_filter(
        (array) $classes, 
        function( $val ) { return 'search-class' !== $val; } 
    ); 

}, 10, 4 );

Then we can use the nav_menu_link_attributesfilter to add a data attribute and append a class to the anchor tag:

add_filter( 'nav_menu_link_attributes', function( $atts, $item, $args )
{
    // Nothing to do
    if( 
           ! isset( $item->classes ) 
        || ! in_array( 'klasi1', $item->classes, true ) 
        || ! isset( $atts['class'] ) 
    )
        return $atts;

    // Add data attribute to anchor tag
    $atts['data-something'] = 'something';

    // Append an anchor class
    $classes = explode( ' ', $atts['class'] );
    $classes[] = 'something-class';
    $atts['class'] = join( ' ', array_filter( $classes ) );

    return $atts;
}, 10, 3 );

We might also want to restrict the above filters to a given menu, by checking if $args->theme_location is 'primary' or $args->menu->slug is 'somemenuslug'.