5

I have one "branch" of my main site's navigation tree that should only be accessible to a set of registered, logged-in users. I understand how to query a user's role and capabilities. This question is specifically about what is the best way to leverage the build-in nav menu, but just hide an item conditionally.

Do I need to overload the built-in default navigation and write a custom query and build the navigation structure manually? I'd love to avoid this if possible.

I don't need full code samples, just your ideas and general framework / approach.

Appreciate the advice!

T

1
  • I wrote a blog post that is related to this issue. For the specific use case of hiding menu item(s) from users that don't have access, see this in-depth post.
    – Tom Auger
    Commented Dec 9, 2011 at 17:47

6 Answers 6

5

Use your own walker and check the capability before you create an item.

4
  • Absolutely brilliant. Thanks for this - though I'm surprised it's quite as complex as all that. Like you said, possibly an oversight?
    – Tom Auger
    Commented Apr 21, 2011 at 19:43
  • So the walker is good for making sure that a menu item that is flagged as "protected" doesn't appear. But how do you create a menu item that is flagged as "protected", or, better yet, as belonging to a specific role or capability? Sounds like we're extending menu items and adding some more user-configurable parameters to the menu item form...?
    – Tom Auger
    Commented May 17, 2011 at 15:54
  • @TomAuger Add appropriate meta data to the linked post object, for example a custom taxonomy or a post meta field. Check the field’s value in the walker.
    – fuxia
    Commented May 17, 2011 at 15:56
  • Thanks for the clarification. Absolutely a custom taxonomy or custom field would be the proper way to go. Another option that I've come up with is based on the template. I don't like my solution as much because you shouldn't couple a template to built-in functionality like that, but there's a logical connection between the template (which ought to check whether the user has access to see the content) and the menu. I'm going to post my code in an answer, below, for other users with the same question.
    – Tom Auger
    Commented May 20, 2011 at 12:42
4

Someone has made a brilliant plugin to do this with no coding. Even has checkboxes in the menu editor interface for selecting the approved roles per menu item.

http://wordpress.org/extend/plugins/nav-menu-roles/

3

The answer toscho posted is correct but for the few people who know what they are doing and how to do it. :) I'm adding this for the rest of the world as a simpler solution for the less advanced users. The idea would be to have different menus and just displaying them based on the user role.

So say you have 3 menus named editor, author and default:

if (current_user_can('Editor')){
    //menu for editor role
    wp_nav_menu( array('menu' => 'editor' ));

}elseif(current_user_can('Author')){
    //menu for author role
    wp_nav_menu( array('menu' => 'author' ));

}else{
    //default menu
    wp_nav_menu( array('menu' => 'default' ));
}
3
  • Now you have to manage one menu for each role. I’d rather add a checkbox to the menu items in the editor. Unfortunately there is no do_action() in class Walker_Nav_Menu_Edit – no API to do that. Looks like an oversight.
    – fuxia
    Commented Apr 7, 2011 at 16:53
  • I agree and i myself would rather add a checkbox or a simple input field to hold the wanted role name, but again i posted this answer as an alternative to yours. There is an option to use the description box for each item and based on that you can check the role, but then you lose the description ability. its a new API, give it some time and open and many trac tickets as you can to get it going :)
    – Bainternet
    Commented Apr 7, 2011 at 17:21
  • Thanks for this more straightforward approach. I personally shudder at having to define my menus through code - to me it completely undermines one of the basic features of wp_admin but perhaps there's no easier alternative.
    – Tom Auger
    Commented Apr 21, 2011 at 19:45
2

The trouble with overriding start_el and end_el is that doing so only controls the display of the menu item in question - it doesn't affect the display of the children. I think you need to override display_element to hide the children as well.

Also, it's possible to use the description field of the menu item to hold information for each menu item about whether to display it or not.

This code looks in the description of each menu item for a comma delimited list of capabilities such as [capability: this_one, next_one] and if the current user has none of the capabilities won't display the item (or any of its children). It's fairly easy to remove the strings from the description if you actually want to use the description for its intended purpose.

/*
 * hide or display menus based on user capabilities
 *
 * Use the description field of the custom menu item. If it contains a string like [capability: xx, xx] 
 * display the menu item only if the user has at least one of the capabilities.
 * If a menu item is not displayed, nor are any of its sub items.
 */
/* Custom Walker */
class NASS_Nav_Walker extends Walker_Nav_Menu {

        // override parent method
        function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
            // we know the $element is a menu item  
            // we want to check its description field to see if it's OK to display it
            // in which case we just retreat to the parent method
            $desc = $element->description;
            if ( should_display_menu_item($desc) ) {
                parent::display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output );
            } else {
                return;
            }
        }    
}
/* should we display the menu item, if this is its description? */
function should_display_menu_item( $desc ) {
    // first see if the description contains a capability specification of the form
    // [capability: comma, delimited, list]
    // we assume all capability names consist only of lower case with underbars
    $prefix = "\[capability:";
    $postfix = "\]";
    $pattern = '@' . $prefix . '([a-z_, ]+)' . $postfix . '@';
    $answer = true;
    if ( preg_match($pattern, $desc, $matches) ) { // if we've got a match
        $found = $matches[1];   // the parenthesized bit of the regex
        $caps = array_map('trim', explode(",", $found));
        if ( count ($caps) > 0 ) { // there is at least one
            $answer = false;
            // now see if the user has any of them
            foreach ($caps as $cap) {
                if ( current_user_can ($cap) ) $answer = true;
            }
        }
    }
    return $answer;

}
2
  • 1
    I've added this answer even though it's a while since this question was active because I thought it might be useful to others.
    – lpryor
    Commented Aug 23, 2011 at 21:46
  • +1 Thanks for taking the time to add your solution! It's always appreciated by others that stumble upon the question in a Google or SE search. Cheers!
    – Tom Auger
    Commented Aug 24, 2011 at 12:25
1

I have tried my hand at using the description field to use as to which roles can access which menu items, and based my modifications on code I got from here - Pimp my WP Menu

My modified version:

<?php

/***
* Menu WALKER - for restricting the menu items visibility
* Code modified by - Trupti Bhatt (http://3sided.co.in)
* using original code posted here - http://www.tisseur-de-toile.fr/wordpress-tricks/pimp-my-wordpress-menu-part-2-access-granted-to-authorized-personnel-only.html
***/
class description_walker extends Walker_Nav_Menu
{
        /*
                 *      Custom var to store current role
                 */
                private $current_user_role = "";

                /*
                 *      Get te current user role
                 */
                private function getCurrentUserRole()
                {
                                global $current_user;
                                if ( is_user_logged_in() )
                                {
                                        if ( $this->current_user_role == "" )
                                        {
                                                $this->current_user_role = $current_user->roles[0];
                                        }

                                        return $this->current_user_role;
                                }
                                else
                                {
                                        $this->current_user_role='visitor';
                                        return $this->current_user_role;
                                }
                }

                /*
                 *      Check if the user is an administrator
                 */
                private function isAdmin()
                {
                                $current_role = $this->getCurrentUserRole();

                if ( $current_role == "administrator" )
                                {
                                                return true;
                                }
                                else
                                {
                                                return false;
                                }
                }

                /*
                 *      Get all restrictions
                 */
                private function getAllRestrictions()
                {
                        global $menu_restricted_access_array;


                                $all_restrictions_array = array();

                                foreach ( $menu_restricted_access_array as $one_restriction )
                                {
                                        $all_restrictions_array = array_merge($all_restrictions_array, $one_restriction);
                                }
                                $all_restrictions_array = array_unique($all_restrictions_array);

                                return $all_restrictions_array;
                }

                /*
                 *      Check the access
                 */
                private function isAccessGranted( $id_menu_item )
                {
                                global $menu_restricted_access_array;

                if ( $this->isAdmin() )
                                {
                                                return true;
                                }
                else if ( isset($menu_restricted_access_array[$this->current_user_role]) )
                {
                    $restricted_access = $menu_restricted_access_array[$this->current_user_role];

                    if ( in_array($id_menu_item, $restricted_access) )
                                        {
                        return true;
                                        }
                                        else
                                        {
                        return false;
                                        }
                }
                else {
                        return true;
                                        }

                }

     /*
                 *      Element render
                 */
                function start_el(&$output, $item, $depth, $args)
        {

            global $wp_query, $menu_restricted_access_array;
            global $g_role,$g_pageid;
            $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
                        $g_role=strtolower((trim($item->description)));

                        $str = explode(',',$g_role);
                        for( $i=0; $i< count($str); $i++)
                                {                      

                                        if (strtolower(trim($str[$i]))==$this->current_user_role)
                                        {                      
                                                $restriction =$item->object_id;        
                                                $menu_restricted_access_array[$this->current_user_role] =array( $restriction);
                                        }


                                }


            $class_names = $value = '';

            $classes = empty( $item->classes ) ? array() : (array) $item->classes;
            $classes[] = 'menu-item-' . $item->ID;


            /*
             *  First test, add custom class to each menu item
             */
                $classes[] = 'my-custom-menu-class';

            /*
             *  Detect the menu item matching the unpublished page
             *  Detect the menu item matching the unpublished page
             */
                // -> FLag to display the output
                $item_to_display = true;
                $is_item_published = true;

                // -> Gather data of linked object
                $item_data = get_post($item->object_id);

                // --> If it's a page, act on the flag

                if ( !empty($item_data) && ($item->object == "page") )
                {
                    $is_item_published = ( $item_data->post_status == "publish" ) ? true : false;
                    $item_output = "";
                }

            /*
             *  Detect and display by user Role
             **/
                if ( _USE_RESTRICTED_ACCESS )
                {
                    $restrictions_array = $this->getAllRestrictions();
                    $this->isAccessGranted($item->object_id);

                }
                else
                {
                    $item_to_display = $is_item_published;
                }

            $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) );
            $class_names = ' class="' . esc_attr( $class_names ) . '"';



            $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
            $id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';

            $output .= $indent . '<li id="menu-item-'. $item->ID . '"' . $value . $class_names .'>';

            $attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
            $attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
            $attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
            $attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';



                        if($depth != 0)
                        {
                    $description = $append = $prepend = "";
                        }

            // --> If the flag is true, when display the output

            if ( $item_to_display )
            {
                $item_output = $args->before;
                $item_output .= '<a'. $attributes .'>';
                $item_output .= $args->link_before .apply_filters( 'the_title', $item->title, $item->ID ).$append; // this is where the strong tags are prepend and append to the description

                                $item_output .= '</a>';
                $item_output .= $args->after;
            }

            $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
        }
}
/*
 *      Restrictions configuration
* 2 is page id of Homepage
 **/
define("_USE_RESTRICTED_ACCESS", true);
$menu_restricted_access_array['subscriber'] = array('2');

?>

It is not the cleanest version yet, but it works. I hope someone else can make a good use of it as well.

0
0

I'm going to post my solution for others who may happen upon this thread. I'm not 100% happy with it because you shouldn't couple a template with menu functionality (Toscho's approach of using metadata or a custom taxonomy is probably more correct). However, it's a quick and dirty one. I've tried to mitigate the tight coupling by providing some constants near the top of functions.php to help future developers maintain the code:

// define the custom password-protected template that is used to determine whether this item is protected or not in the menus
define ('ZG_PROTECTED_PAGE_TEMPLATE', 'page-membersonly.php');
// define the custom capability name for protected pages
define ('ZG_PROTECTED_PAGE_CAPABILITY', 'view_member_pages');

So the protected page template is just a variant of single.php, but it will check to see whether current_user_can(ZG_PROTECTED_PAGE_CAPABILITY) before displaying any content, for security reasons.

Next, I implement a custom walker, as per Toscho's suggestion. The walker in this case is extremely simple - we override the Walker_Nav_Menu's public start_el and end_el methods, just intercepting them long enough to ask the question: do we have access to see the menu item?

The rule is simple: if the page is not a "private" page (which in this case is determined by that page using a particular page template) it's visible. If it IS a "private" page, and the user is authenticated into a role that has the custom capability we're looking for, then it is visible. Otherwise, it's not visible.

If we do have access, then we just have to use the Walker_Nav_Menu's built-in methods without modification, so we call the parent:: method of the same name.

/* Custom Walker to prevent password-protected pages from appearing in the list */
    class HALCO_Nav_Walker extends Walker_Nav_Menu {

        protected $is_private = false;
        protected $page_is_visible = false;

        // override parent method
        function start_el(&$output, $item, $depth, $args) {
            // does this menu item refer to a page that is using our protected template?
            $is_private = get_post_meta($item->object_id, '_wp_page_template', true) == ZG_PROTECTED_PAGE_TEMPLATE;
            $page_is_visible = !$is_private || ($is_private && current_user_can(ZG_PROTECTED_PAGE_CAPABILITY));

            if ($page_is_visible){
                parent::start_el(&$output, $item, $depth, $args);
            }
        }

        // override parent method
        function end_el(&$output, $item, $depth) {
            if ($page_is_visible){
                parent::end_el(&$output, $item, $depth);
            }
        }
    }

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.