admin管理员组

文章数量:1291454

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;
}

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;
}
Share Improve this question edited Jun 1, 2020 at 16:07 fuxia 107k38 gold badges255 silver badges459 bronze badges asked Jun 1, 2020 at 7:16 pixelngrainpixelngrain 1,3901 gold badge23 silver badges50 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 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 )
        );
    } );
} );

Here you may find a more in depth approach on dealing with the limitations of the wp_get_nav_menu_items

It uses SQL to determine the children but provides far more flexibility in order to achieve full control of what you are actually want to do:

   //this query produces all you need to have for the children at any depth. It is explained on the readme and will spare you the add on walkers.   
                            $query = "SELECT e.id, e.post_title, CASE WHEN c.meta_value='custom' THEN ( d.meta_value) ELSE concat ('/', e.post_name ,'/') END as linkurl, b.post_id 
                                                FROM wp_postmeta a FORCE INDEX (post_id)
                                                JOIN wp_postmeta b FORCE INDEX (post_id) ON a.post_id=b.post_id
                                                JOIN wp_postmeta c FORCE INDEX (post_id) ON c.post_id=b.post_id
                                                JOIN wp_postmeta d FORCE INDEX (post_id) ON d.post_id=b.post_id
                                                JOIN wp_posts e ON e.id=b.meta_value
                                                WHERE d.meta_key='_menu_item_url' AND c.meta_key='_menu_item_object' AND b.meta_key='_menu_item_object_id' 
                                                AND a.meta_key='_menu_item_menu_item_parent' AND a.meta_value='" . $submenu->ID . "'";

                            $resultsData = $wpdb->get_results($query);

                            foreach ($resultsData as $childrenData) {

                                $menu_html_s .=   '<!-- Lets print each of the children  -->';
                                $menu_html_s .=   '<div class="menu-sub menu-sub-accordion menu-active-bg">';
                                $menu_html_s .=   '<div class="menu-item">';
                                $menu_html_s .=   '<a class="menu-link" href="' . $childrenData->linkurl . '">';
                                $menu_html_s .=   '<span class="menu-bullet">';
                                $menu_html_s .=   '<span class="bullet bullet-dot"></span>';
                                $menu_html_s .=   '</span>';
                                $menu_html_s .=   '<span class="menu-title">' . $childrenData->post_title . '</span>';
                                $menu_html_s .=   '</a>';
                                $menu_html_s .=   '</div>';
                                $menu_html_s .=   '</div>';

More details here:

https://github/exonianp/wp-bootstrap5-accordeon-vertical-menu

本文标签: Search if menu item has child in wpgetnavmenuitems hook