Members Only Menu Plugin

Visit my up-to-date page for this plugin

I released my my walker class as a super easy to use plugin (This doesn’t even require you to change your wp_nav_menu commands as it uses a filter to add it). I’ve submitted it to WordPress.org so I’ve linked to it below.

Just install and activate it, and then you can go to a page and mark it as Members Only.

Now featuring full support for wp_page_menu AND wp_nav_menu. Tested on WordPress 2.9+. Also fully compatible with PHP 4+!

Download my super awesome plugin now!

WordPress Walker Classes

Walkers are used in WordPress to recursively handle data such as menus. As defined in the WordPress codex

The walker class encapsulates the basic functionality necessary to output HTML representing WordPress objects with a tree structure. For instance pages and categories are the two types of objects that the WordPress 2.2 code uses the walker class to enumerate. While one could always display categories as you wished by using right func and looping through them using it takes a great deal of work to arrange subcategories below their parents with proper formatting. The walker class takes care of most of this work for you.

Now, unfortunately there is very little good documentation about Walker classes, even though they give you more customization and flexibility then the default code, all without modifying core WordPress files which is perhaps the most important part. I set out this last week to allow my client to create a members only page which he could add to the site navigation, and have it only show up if the user was logged in. This turned out to be more difficult than I initially expected as private pages cannot be added to the menu.

After searching the Internet and finding nothing, I did the next best thing: Searched through the WordPress code. I eventually came up with this, which adds a checkbox saying “Is Members Only?” to the page editor. Then I added some code to my theme’s functions.php, which could easily be put in a plugin instead which checks to make sure the user is logged in. Now to make it selectively appear in the menus, I wrote my only walker class, extending the default one.

Here is my code:

add_action( 'wp' , 'check_for_members_only' );

function check_for_members_only() {
	global $post;

	if ( get_post_meta( $post->ID , '_members_only' , true ) && !is_user_logged_in() ) {
		header( 'Location: /wp-login.php' );
		exit();
	}
}

The above snippet is fairly simple. It executes right after the post data is set, and checks if some post meta is set. If it is, we redirect to the login page.

function add_members_only_meta_box() {
    add_meta_box( 'add_members_only_meta' , 'Members Only' , 'add_members_only_meta' , 'page' , 'side' , 'high' );
}
add_action( 'add_meta_boxes' , 'add_members_only_meta_box' );

function add_members_only_meta() {
    global $post;

    $meta_box_value = get_post_meta( $post->ID , '_members_only' , true ); 

    if ( $meta_box_value == 'true' ) {
        $meta_box_value = 'checked="checked"';
    }

    echo '<input type="hidden" name="members_only_nonce" id="members_only_nonce" value="' . wp_create_nonce( 'members_only' ) . '" />';
    echo '<input type="checkbox" name="members_only" id="members_only" value="true" '.$meta_box_value .' /> Restrict To Members?';
}

/* When the post is saved, saves our custom data */
function members_only_save_postdata( $post_id )
{
    // verify this came from the our screen and with proper authorization,
    // because save_post can be triggered at other times
    if ( !wp_verify_nonce( $_POST['members_only_nonce'], 'members_only' ) )
    {
        return $post_id;
    }

    // verify if this is an auto save routine. If it is our form has not been submitted, so we dont want
    // to do anything
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
        return $post_id;

    // Save
    update_post_meta( $post_id , '_members_only' , $_POST['members_only'] );

    return $post_id;
}
add_action( 'save_post' , 'members_only_save_postdata' );

This code is also fairly straight forward. We add a meta box to the page editor (Look at the add_meta_box, I specify page). We also add some code to save our post meta, prefixing it with an underscore so it is hidden from the meta box interview.

Now here is the walker class.

class Walker_Nav_Menu_CMS extends Walker_Nav_Menu {
	function start_el(&$output, $item, $depth, $args) {
		global $wp_query;

		$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

		$class_names = $value = '';

		$classes = empty( $item->classes ) ? array() : (array) $item->classes;

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

		if ( !get_post_meta( $item->object_id , '_members_only' , true ) || is_user_logged_in() ) {
			$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        ) .'"' : '';

		$item_output = $args->before;
		$item_output .= '<a'. $attributes .'>';
		$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
		$item_output .= '</a>';
		$item_output .= $args->after;

		if ( !get_post_meta( $item->object_id, '_members_only' , true ) || is_user_logged_in() ) {
			$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
		}
	}

	function end_el(&$output, $item, $depth) {
		if ( !get_post_meta( $item->object_id, '_members_only' , true ) || is_user_logged_in() ) {
			$output .= "</li>\n";
		}
	}
}

This is almost exactly the same as the default nav function, except I added the line “if ( !get_post_meta( $item->object_id, ‘_members_only’ , true ) || is_user_logged_in() ) {“. Now, in this case I wanted to modify only the Nav Walker, without editing core files. I didn’t need to change much, so all I did was extend the default Nav Walker, instead of the base Walker class.

If you look through the code, it’s not really hard to understand. The walker class has several functions, start_el, end_el, walk, start_lvl, and end_lvl. You can extend the base classes and implement your own versions of these functions if you want. I’ll explain what these do.

start_lvl – Creates the <ul>
end_lvl – Creates the </ul>
start_el – Creates the <li right to the </li> but excluding the </li>
end_el – Creates the </li>
walk – This is a fairly complex function that calls the above functions depending on if a submenu needs to be created, or closed, etc.

I would recommend looking at the Walker class in wp-includes/classes.php and the other classes that extend the Walker class from wp-includes/nav-menu-templates.php.

Now the most important part is getting your code to use the new walker class. This is super simple:

wp_nav_menu( array( 'walker' => new Walker_Nav_Menu_CMS() ) );