Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

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

share|improve this question

7 Answers 7

up vote 2 down vote accepted

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

share|improve this answer
    
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 Apr 21 '11 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 May 17 '11 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. –  toscho May 17 '11 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 May 20 '11 at 12:42

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' ));
}
share|improve this answer
    
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. –  toscho Apr 7 '11 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 Apr 7 '11 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 Apr 21 '11 at 19:45

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 is pasted at http://pastebin.com/hQnShaxA

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

share|improve this answer
    
Thanks for this! –  Tom Auger May 17 '11 at 12:52

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);
            }
        }
    }
share|improve this answer

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;

}
share|improve this answer
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 Aug 23 '11 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 Aug 24 '11 at 12:25

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.

share|improve this answer

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/

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.