0

I am conditionally showing menu items. All is fine but want to remove parent item if returning no child items.

For example if I have a parent menu (it will always be a custom link with #) called More and it has multiple child items (mostly one level). Now if I am hiding child items based on user roles and for some roles no child items available, in that case, I want to remove More menu items also since it has no child.

I am trying with the following code but since $item is an object, array_search won't work. So how can I check for child-parent and remove the menu item if it has no child?

array_search( $item[ 'ID' ], array_column( $item, 'menu_item_parent' ) )

Below is the working code that hides menu item based on user roles.

public static function exclude_menu_items( $items, $menu, $args ) {

    if ( current_user_can( 'administrator' ) ) {
        return $items;
    }

    foreach ( $items as $key => $item ) {

        if ( $page = get_post( $item->object_id ) ) {
            if ( $page->post_type == 'page' ) {
                $template  = get_post_meta( $page->ID, '_wp_page_template', TRUE );
                $post_type = self::get_cpt_for_template( $template );

                if ( $post_type && ( ! current_user_can( 'cp_access_' . $post_type ) || ! ( new self() )->is_current_user_granted_for_module( $post_type ) ) ) {
                    unset( $items[ $key ] );
                }
            }
        }
    }

    return $items;
}
1

If you want to use the wp_get_nav_menu_items hook, then try the function below which should be hooked after the exclude_menu_items function:

// Example when NOT using a class: (you already defined the exclude_menu_items function)
add_filter( 'wp_get_nav_menu_items', 'exclude_menu_items', 10, 3 );
add_filter( 'wp_get_nav_menu_items', 'exclude_menu_items2' );

function exclude_menu_items2( $items ) {
    if ( current_user_can( 'administrator' ) ) {
        return $items;
    }

    $mores   = [];
    $parents = [];
    foreach ( $items as $key => $item ) {
        if ( '#' === $item->url && 'More' === $item->title ) {
            $mores[] = $item->ID;
        }

        if ( ! in_array( $item->menu_item_parent, $parents ) ) {
            $parents[] = $item->menu_item_parent;
        }
    }

    $mores = array_diff( $mores, $parents );
    foreach ( $items as $i => $item ) {
        if ( in_array( $item->ID, $mores ) ) {
            unset( $items[ $i ] );
        }
    }

    return $items;
}

Just make sure to run the appropriate validation so that the menu items don't get messed, e.g. on the Menus admin screen.

And the two functions can also be combined:

function exclude_menu_items( $items, $menu, $args ) {
    if ( current_user_can( 'administrator' ) ) {
        return $items;
    }

    $mores   = [];
    $parents = [];
    foreach ( $items as $key => $item ) {
        if ( $page = get_post( $item->object_id ) ) {
            // ... your code here.
        }

        if ( ! empty( $items[ $key ] ) ) {
            if ( '#' === $item->url && 'More' === $item->title ) {
                $mores[] = $item->ID;
            }

            if ( ! in_array( $item->menu_item_parent, $parents ) ) {
                $parents[] = $item->menu_item_parent;
            }
        }
    }

    $mores = array_diff( $mores, $parents );
    foreach ( $items as $i => $item ) {
        if ( in_array( $item->ID, $mores ) ) {
            unset( $items[ $i ] );
        }
    }

    return $items;
}

And actually — when wp_nav_menu() is called — with the wp_nav_menu_objects hook, it's simpler to remove the "More" items which have no children:

add_filter( 'wp_nav_menu_objects', function ( $items ) {
    if ( is_admin() ) { // sample validation..
        return $items;
    }

    return array_filter( $items, function ( $item ) {
        return ! ( '#' === $item->url && 'More' === $item->title &&
            ! in_array( 'menu-item-has-children', $item->classes )
        );
    } );
} );
| improve this answer | |
  • This looks great to me. Let me try and get back to you. Thanks a lot and appreciated. – pixelngrain Jun 3 at 4:46
  • Sure, take your time. :) – Sally CJ Jun 3 at 5:55

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.