Site icon WP Smith

How to Automatically Add a Class (like 'first' or 'last') to a WordPress Menu

So recently, I needed a way to dynamically add a 'first' class to all the first child nav menu items of a particular parent nav menu item item. On a simple Google, one tutorial by @WPBeginner suggests that we use a filter.
[php]
//Filtering a Class in Navigation Menu Item
add_filter( 'nav_menu_css_class' , 'special_nav_class' , 10 , 2 );
function special_nav_class( $classes, $item ){
if( is_single() && $item->title == 'Blog' ){
$classes[] = 'current-menu-item';
}
return $classes;
}
[/php]
However great this filter is, this approach won't and cannot be very dynamic.

So I wrote a function (and generalized it) that would accomplish this easily and nicely. This uses a function that I wrote about previously called get_nav_menu_item_children() in a post called How to Get All the Children of a Specific Nav Menu Item that I have modified (12/6).

Given a menu id and a parent item id and the optional class name, the function will automatically add that class to the first child nav menu item. So here is the new function:
[php]
/*
* Adds a class to the first menu item.
*
* Uses update_post_meta() to update a nav menu item.
*
* @param int $menu_id Menu ID, defaults to 0.
* @param int $parent_id Menu item ID, defaults to 0.
* @param string $newclass Class to add, defaults to 'first'.
* @param boolean $remove Remove class from other children, defaults to true.
* @return boolean, false: if no $menu_id or $parent_id, else true.
*/
function wps_update_classes( $menu_id = 0 , $parent_id = 0 , $newclass = 'first' , $remove = true ) {

// Validate inputs
$menu_id = intval ( $menu_id );
$parent_id = intval ( $parent_id );

if ( ( $menu_id == 0 ) || ( $parent_id == 0 ) || ( !is_bool ( $remove ) ) || ( !is_string ( $newclass ) ) )
return false;

// Get & filter nav menu items
$nav_menu_items = wp_get_nav_menu_items( $menu_id , array( 'post_status' => 'publish,draft' ) );
$children = get_nav_menu_item_children( $parent_id , $nav_menu_items );
$first_item = true;

// Cycle through child nav menu items
foreach ( $children as $child ) {
if ( $first_item ) {
//found first item
$first_item = false;
$match = false;

// Cycle through to check for first
foreach ( $child->classes as $class ) {
// Match found
if ( $class == $newclass ) {
$match = true;
break;
}
}

// Update Post if match found
if ( ! $match ) {
if ( $child->classes[0] == '' )
$child->classes[0] = $newclass;
else
$child->classes[] = $newclass;

$menu_item_data = array(
'menu-item-classes' => $child->classes,
);
update_post_meta( $child->ID, '_menu_item_classes', $child->classes );
}
continue;
}

// Cycle through and remove $newclass from other children
if ( $remove ) {
foreach ( $child->classes as $key => $class ) {

// If match, remove $newclass from other children
if ( $class == $newclass ) {
$child->classes[$key] = '';
update_post_meta( $child->ID, '_menu_item_classes', $child->classes );
}
}
}
}

return true;
}
[/php]

So to call this function, you can use something like this:
[php]
add_action( 'init' , 'wps_update_menus' );
function wps_update_menus() {
wps_update_classes( 3 , 277 );
wps_update_classes( 3 , 7 , 'test-class' );
}
[/php]
This bit of code adds 'first' to the first child of menu item 277 on menu 3 and 'test-class' to the first child of menu item 7 on menu 3.

If you want to add the ability to add a 'last' class, you can use this:
[php]
/*
* Sorts a multidimensional array of nav menu items.
*
* @link http://php.net/manual/en/function.sort.php
* @param array $array Multidimensional array to be sorted.
* @param string $index Index to determine the sort.
* @param string $order Must be either asc or desc.
* @param boolean $natsort Sort an array using a "natural order" algorithm.
* @param boolean $case_sensitive Sort via case sensitivity.
* @return boolean, false: if no $menu_id or $parent_id, else true.
*/
function wps_sort_nav_menu_items ( $array, $index, $order, $natsort = FALSE, $case_sensitive = FALSE ) {
if( is_array ( $array ) && count( $array ) > 0 ) {
foreach ( array_keys ( $array ) as $key )
$temp[$key] = $array[$key]->$index;
if( ! $natsort ) {
if ( strtolower( $order ) == 'asc' )
asort( $temp );
else
arsort( $temp );
}
else
{
if ( $case_sensitive === true )
natsort( $temp );
else
natcasesort( $temp );
if( strtolower( $order ) != 'asc' )
$temp = array_reverse ( $temp , TRUE );
}
foreach( array_keys ( $temp ) as $key )
if ( is_numeric ( $key ) )
$sorted[] = $array[$key];
else
$sorted[$key] = $array[$key];
return $sorted;
}
return $sorted;
}

/*
* Adds a class to first/last menu item.
*
* Uses update_post_meta() to update a nav menu item.
*
* @param int $menu_id Menu ID, defaults to 0.
* @param int $parent_id Menu item ID, defaults to 0.
* @param string $newclass Class to add, defaults to 'first'.
* @param boolean $remove Remove class from other children, defaults to true.
* @return boolean, false: if no $menu_id or $parent_id, else true.
*/
function wps_update_classes( $menu_id = 0 , $parent_id = 0 , $newclass = 'first' , $firstlast = 'first' , $remove = true ) {

// Validate inputs
$menu_id = intval ( $menu_id );
$parent_id = intval ( $parent_id );

if ( ( $menu_id == 0 ) || ( $parent_id == 0 ) || ( !is_bool ( $remove ) ) || ( !is_string ( $newclass ) ) || ( !is_string ( $firstlast ) ) )
return false;

// Get & filter nav menu items
$nav_menu_items = wp_get_nav_menu_items( $menu_id , array( 'post_status' => 'publish,draft' ) );
$children = get_nav_menu_item_children( $parent_id , $nav_menu_items );
if ( strtolower( $firstlast ) == 'last' )
$children = wps_sort_nav_menu_items( $children , 'menu_order' , 'desc' );
$first_item = true;

// Cycle through child nav menu items
foreach ( $children as $child ) {
if ( $first_item ) {
//found first item
$first_item = false;
$match = false;

// Cycle through to check for first
foreach ( $child->classes as $class ) {
// Match found
if ( $class == $newclass ) {
$match = true;
break;
}
}

// Update Post if match found
if ( ! $match ) {
if ( $child->classes[0] == '' )
$child->classes[0] = $newclass;
else
$child->classes[] = $newclass;

$menu_item_data = array(
'menu-item-classes' => $child->classes,
);
update_post_meta( $child->ID, '_menu_item_classes', $child->classes );
}
continue;
}

// Cycle through and remove $newclass from other children
if ( $remove ) {
foreach ( $child->classes as $key => $class ) {

// If match, remove $newclass from other children
if ( $class == $newclass ) {
$child->classes[$key] = '';
update_post_meta( $child->ID, '_menu_item_classes', $child->classes );
}
}
}
}

return true;
}
[/php]

So to call this function, you can use something like this:
[php]
add_action( 'init' , 'wps_update_menus' );
function wps_update_menus() {
wps_update_classes( 3 , 277 );
wps_update_classes( 3 , 7 , 'last' , 'last' );
}
[/php]
This bit of code adds 'first' to the first child of menu item 277 on menu 3 and 'last' to the last child of menu item 7 on menu 3.