The (almost) ultimate members page in WordPress

One of the core feature of WordPress has always been to allow people to easily register on your blog. You can use this function to filter who can comment on your posts for example or to allow other people to submit articles to your blog.

But so far there is nothing that allow you to restrict access to a part of your blog to registered members, apart from the password you can add on a single post/page.

Some solutions have been suggested in the past and we are trying here to compile the best of those to make the ultimate members page.

The brief:

  • Content shouldn’t be accessible from non logged users.
  • Provide an easy way to login, without going to the default WordPress backend
  • Allow people to post articles to be reviewed
  • Protect Rss feed, archives, index, etc from displaying restricted posts
  • Allow admin to use subcategories on the Members posts

Limitations:

  • Users won’t be able to set the category of their post themselves, which in a way is a good thing. Each submitted post will go to the default ‘Members’ category. The admin can then add subcategories if needed.
  • You need a category.php file in your theme, might change in the future though

The code will pretty much all go in the function.php file as a php class. We are gonna use WordPress categories here, no page with custom template. That’s the way we started it but it seems more flexible using the default categorisation rather than a custom page template, especially for sub-categories.

This tutorial is for people with some knowledge in php. You might pull it off by raw copy-paste but it’s always better to understand what you are doing in case something goes wrong. And as usual do this at your how risks, it worked for us on test sites, but you never know…backup backup backup!

The files

For the lazy here is everything you need in two files: one to include in your function.php and the category.php template you need: members.zip

So should we get started?

Category exclusion

This tutorial is based on the fact that your members only category is called ‘Members’. If you want to change the name to something else you will have to make sure every reference to ‘members’ as the category is changed.

This is the function that will hide the members category from archives, general Rss feed, index. It also redirects someone not logged and trying to access the members rss to the Members category page with the log in form. We have a small issue here but we’ll talk about it later.

/*
* Exclude members and sub members categories from feed, index, archives, etc...
*/
function exclude_category($query) {

	if($query->is_feed && !is_user_logged_in() && ($query->in_category == 'members' || $this->post_is_in_descendant_category($this->members_id))){
		header("HTTP/1.0 403 forbidden"); 
		header('Location: '.get_category_link($this->members_id));
		exit;
	}

	if (!$query->is_admin && !$query->in_category == 'members' && !$this->post_is_in_descendant_category($this->members_id)){
		$query->set('cat', '-'.$this->members_id);
	}
	return $query;
}

This is a quick function that will find out if a post’s assigned categories are descendants of target categories.

/**
 * Tests if any of a post's assigned categories are descendants of target categories
 * From wordpress codex http://codex.wordpress.org/Function_Reference/in_category
 *
 * @param mixed $cats The target categories. Integer ID or array of integer IDs
 * @param mixed $_post The post
 * @return bool True if at least 1 of the post's categories is a descendant of any of the target categories
 * @see get_term_by() You can get a category by name or slug, then pass ID to this function
 * @uses get_term_children() Gets descendants of target category
 * @uses in_category() Tests against descendant categories
 * @version 2.7
 */
function post_is_in_descendant_category( $cats, $_post = null )
{
	foreach ( (array) $cats as $cat ) {
		// get_term_children() accepts integer ID only
		$descendants = get_term_children( (int) $cat, 'category');
		if ( $descendants && in_category( $descendants, $_post ) )
			return true;
	}
	return false;
}

Members category template

Next we need this special template for the members category. The template will go into an other function in our class and this function will be called in the category.php file.

/* 
 * Print out the template to use in members categories
 * Login form that redirect to same page
 * Post editor for members to post a new entry
 * Display of latest members posts
 */
function get_members_template(){ 
	//you don't want anyone to be able to send you some random post requests do you?
	if(is_user_logged_in()){
		//if a post is submitted, check it and create a new post in the databse if ok
		if($_POST['submit']){

			$submit = 1;

			if($_POST['title'] == ''){
				$error .= '<li>Please type in a title for your article</li>';
			}
			if($_POST['content'] == ''){
				$error .= '<li>Please type in the content of you article</li>';
			}

			if($error == ''){

				$your_post_title = $_POST['title'];
				$your_post_content = $_POST['content'];
				// Create post object
				$my_post = array();
				$my_post['post_title'] = $your_post_title;
				$my_post['post_content'] = $your_post_content;
				$my_post['post_status'] = 'pending';
				$my_post['post_author'] = $user_ID;
				$my_post['post_category'] = array(get_cat_id('members'));
				$my_post['post_type'] = 'post';

				// Insert the post into the database
				$postId = wp_insert_post($my_post);

				if(!empty($postId) && $postId != 0)
				{
					$message .= "<li>The article has been submited and will go live in the next 48hrs if approved</li>";
					$your_post_title = '';
					$your_post_content = '';
				}
				else $error .= "An error occured with your post";
			}
		}
	}
	?>

	<h2 class="pagetitle">Archive for the &#8216;<?php single_cat_title(); ?>&#8217; Category</h2>

	<?php 
	/*
	 * If user not logged in display login form
	*/
	if(!is_user_logged_in()): ?>

		<h3>Members access only</h3>
		<p>
			The content of this page is reserved to members of the website only.
		</p>
		<form action="<?php echo wp_login_url($_SERVER['REQUEST_URI']); ?>" method="post" id="memberslogin">

			<h3>Are you a member?</h3>

			<p>If you are a member, please use the form below to login</p>

			<ol>
				<li class="inline"><label for="log">Username</label><input type="text" name="log" id="log" value="<?php echo wp_specialchars(stripslashes($user_login), 1) ?>" size="22" /></li>
				<li class="inline"><label for="pwd">Password</label><input type="password" name="pwd" id="pwd" size="22" /></li>
				<li><input type="submit" name="submit" value="Send" class="button" /><label for="rememberme"><input name="rememberme" id="rememberme" type="checkbox" checked="checked" value="forever" /> Remember me</label></li>
			</ol>
		</form>
	<?php

	else:

	?>
		<div class="adminaccess">
			<a href="javascript:;" id="open-members-form">Submit an article &raquo;</a>
			<?php //first display the form to add a new post ?>
			<form id="members-post" action="" method="post" class="<?php if($submit) echo 'open'; ?>">
				<?php if($error != ''): ?>
					<ul class="error">
						<?php echo $error; ?>
					</ul>
				<?php endif; ?>
				<?php if($message != ''): ?>
					<ul class="success">
						<?php echo $message; ?>
					</ul>
				<?php endif; ?>
				<div id="form-section-author" class="form-section">
					<div class="form-label"><label for="title">Article title</label> <em>*</em></div>
					<div class="form-input"><input id="title" name="title" type="text" value="<?php echo $your_post_title ?>" size="40" tabindex="3" /></div>
				</div><!-- #form-section-author .form-section -->

				<div id="form-section-comment" class="form-section">
					<div class="form-label"><label for="Content">Article content</label> <em>*</em></div>
					<div class="form-textarea"><textarea id="content-area" name="content" cols="65" rows="12" tabindex="6"><?php echo $your_post_content ?></textarea></div>
				</div><!-- #form-section-comment .form-section -->
				<p><em>*</em> required fields</p>
				<div class="form-submit"><input id="submit" name="submit" type="submit" value="<?php _e('Submit your article for review', 'thematic') ?>" /></div>
			</form>
			<a href="<?php echo wp_logout_url(get_bloginfo('url')) ?>">Logout</a>
		</div>
		
		<div class="navigation">
			<div class="alignleft"><?php next_posts_link('&laquo; Older Entries') ?></div>
			<div class="alignright"><?php previous_posts_link('Newer Entries &raquo;') ?></div>
		</div>
		
		<?php 

		//Then display the latest post for the current category
		while ( have_posts() ) : the_post() ?>

			<div class="hentry">
				<h2><?php the_title() ?></h2>
				<div class="entry-content">
					<?php the_content('read more'); ?>
					<span class="meta-prep meta-prep-author">By</span>
					<span class="author vcard">
						<a class="url fn n" href="'<?php get_author_link(false, $authordata->ID, $authordata->user_nicename)?>" title="View all posts by <?php echo get_the_author() ?> ">
							<?php echo get_the_author(); ?>
						</a>
					</span>
				</div>
			</div><!-- .post -->

		<?php endwhile; ?>
		
		<div class="navigation">
			<div class="alignleft"><?php next_posts_link('&laquo; Older Entries') ?></div>
			<div class="alignright"><?php previous_posts_link('Newer Entries &raquo;') ?></div>
		</div>
		
		

	<?php endif;
}

The comments should hopefully be enough to let you understand what is happening. Basically if someone not logged in he will see the login form, if he is he will see a post submission form, a logout button and the latest post in the category.

Category.php file

Now we need to change the category.php file to call this function if we are in the right category:

<?php get_header(); ?>

<div id="content">
	<?php //if in the members category, call the template ?>
	<?php if(in_category('members') || $members_object->post_is_in_descendant_category($members_object->members_id)): ?>
		
		<?php $members_object->get_members_template(); ?>
	<?php //else do normal stuff ?>
	<?php else: ?>
		
		<h2 class="pagetitle">Archive for the &#8216;<?php single_cat_title(); ?>&#8217; Category</h2>
		
		<?php if (have_posts()) : ?>
		
			<div class="navigation">
				<div class="alignleft"><?php next_posts_link('&laquo; Older Entries') ?></div>
				<div class="alignright"><?php previous_posts_link('Newer Entries &raquo;') ?></div>
			</div>

			<?php while (have_posts()) : the_post(); ?>
				<div <?php post_class() ?>>
					<h3 id="post-<?php the_ID(); ?>"><a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title_attribute(); ?>"><?php the_title(); ?></a></h3>
					<small><?php the_time('l, F jS, Y') ?></small>

					<div class="entry">
						<?php the_content() ?>
					</div>

					<p class="postmetadata"><?php the_tags('Tags: ', ', ', '<br />'); ?> Posted in <?php the_category(', ') ?> | <?php edit_post_link('Edit', '', ' | '); ?>  <?php comments_popup_link('No Comments &#187;', '1 Comment &#187;', '% Comments &#187;'); ?></p>

				</div>

			<?php endwhile; ?>

			<div class="navigation">
				<div class="alignleft"><?php next_posts_link('&laquo; Older Entries') ?></div>
				<div class="alignright"><?php previous_posts_link('Newer Entries &raquo;') ?></div>
			</div>
			
		<?php else :
			printf("<h2 class='center'>Sorry, but there aren't any posts in the %s category yet.</h2>", single_cat_title('',false));
		endif; ?>
		
	<?php endif; ?>
</div>
<?php get_sidebar(); ?>

<?php get_footer(); ?>

Add tinyMCE

Just a quick cosmetic fix on the textarea in the form. Adding some style capabilities will make it easier for users to structure their posts.



//add tinymce to members page to build a more friendly post editor
function addtinymce() {

	if(in_category('members')){ ?>
		<script language="javascript" type="text/javascript" src="<?php echo get_bloginfo('url') ?>/wp-includes/js/tinymce/tiny_mce.js"></script>
		<script language="javascript" type="text/javascript">
			tinyMCE.init({
							mode : "none",
							theme : "advanced",
							theme_advanced_buttons1 : "bold,italic,strikethrough|,|,bullist,numlist,outdent,indent,|,link,unlink,|,formatselect",
							theme_advanced_buttons2 : "",
							theme_advanced_buttons3 : "",
							language : "en",
							theme_advanced_toolbar_location : "top",
							remove_linebreaks:"1",
							paste_auto_cleanup_on_paste : true,
							theme_advanced_blockformats : "Text=, Title 1=h3, Title 2=h4, Title 3=h5, Title 4=h6,Quote=blockquote",
							theme_advanced_toolbar_align : "left"});
		tinyMCE.execCommand("mceAddControl", true, "content-area");
		</script>
		
	<?php
	}
}

All in all

Now we have all our function we need to wrap them up in an object and hook them in the different places. So the full code, without the category.php modification of course, looks like this:


class Members_Section{

	var $members_id;

	function Members_Section(){
		add_filter('wp_footer', array($this,'addtinymce')); //hook the tinymce functions to the footer
		add_filter('pre_get_posts', array($this, 'exclude_category')); //hook the exclude category function
		$this->members_id = get_cat_id('members'); //get the id of the members page once and for all
	}

	//add tinymce to members page to build a more friendly post editor
	function addtinymce() {

		if(in_category('members')){ ?>
			<script language="javascript" type="text/javascript" src="<?php echo get_bloginfo('url') ?>/wp-includes/js/tinymce/tiny_mce.js"></script>
			<script language="javascript" type="text/javascript">
				tinyMCE.init({
								mode : "none",
								theme : "advanced",
								theme_advanced_buttons1 : "bold,italic,strikethrough|,|,bullist,numlist,outdent,indent,|,link,unlink,|,formatselect",
								theme_advanced_buttons2 : "",
								theme_advanced_buttons3 : "",
								language : "en",
								theme_advanced_toolbar_location : "top",
								remove_linebreaks:"1",
								paste_auto_cleanup_on_paste : true,
								theme_advanced_blockformats : "Text=, Title 1=h3, Title 2=h4, Title 3=h5, Title 4=h6,Quote=blockquote",
								theme_advanced_toolbar_align : "left"});
			tinyMCE.execCommand("mceAddControl", true, "content-area");
			</script>
			
		<?php
		}
	}

	/*
	 * Exclude members and sub members categories from feed, index, archives, etc...
	*/
	function exclude_category($query) {

		if($query->is_feed && !is_user_logged_in() && ($query->in_category == 'members' || $this->post_is_in_descendant_category($this->members_id))){
			header("HTTP/1.0 403 forbidden"); 
			header('Location: '.get_category_link($this->members_id));
			exit;
		}
	
		if (!$query->is_admin && !$query->in_category == 'members' && !$this->post_is_in_descendant_category($this->members_id)){
			$query->set('cat', '-'.$this->members_id);
		}
		return $query;
	}

	/**
	 * Tests if any of a post's assigned categories are descendants of target categories
	 * From wordpress codex http://codex.wordpress.org/Function_Reference/in_category
	 *
	 * @param mixed $cats The target categories. Integer ID or array of integer IDs
	 * @param mixed $_post The post
	 * @return bool True if at least 1 of the post's categories is a descendant of any of the target categories
	 * @see get_term_by() You can get a category by name or slug, then pass ID to this function
	 * @uses get_term_children() Gets descendants of target category
	 * @uses in_category() Tests against descendant categories
	 * @version 2.7
	 */
	function post_is_in_descendant_category( $cats, $_post = null )
	{
		foreach ( (array) $cats as $cat ) {
			// get_term_children() accepts integer ID only
			$descendants = get_term_children( (int) $cat, 'category');
			if ( $descendants && in_category( $descendants, $_post ) )
				return true;
		}
		return false;
	}
	
	
	/* 
	 * Print out the template to use in members categories
	 * Login form that redirect to same page
	 * Post editor for members to post a new entry
	 * Display of latest members posts
	 */
	function get_members_template(){ 
		//you don't want anyone to be able to send you some random post requests do you?
		if(is_user_logged_in()){

			if($_POST['submit']){

				$submit = 1;

				if($_POST['title'] == ''){
					$error .= '<li>Please type in a title for your article</li>';
				}
				if($_POST['content'] == ''){
					$error .= '<li>Please type in the content of you article</li>';
				}

				if($error == ''){

					$your_post_title = $_POST['title'];
					$your_post_content = $_POST['content'];
					// Create post object
					$my_post = array();
					$my_post['post_title'] = $your_post_title;
					$my_post['post_content'] = $your_post_content;
					$my_post['post_status'] = 'pending';
					$my_post['post_author'] = $user_ID;
					$my_post['post_category'] = array(get_cat_id('members'));
					$my_post['post_type'] = 'post';
					// Insert the post into the database
					$postId = wp_insert_post($my_post);

					if(!empty($postId) && $postId != 0)
					{
						$message .= "<li>The article has been submited and will go live in the next 48hrs if approved</li>";
						$your_post_title = '';
						$your_post_content = '';
					}
					else $error .= "An error occured with your post";
				}
			}
		}
		?>

		<h2 class="pagetitle">Archive for the &#8216;<?php single_cat_title(); ?>&#8217; Category</h2>

		<?php 
		/*
		 * If user not logged in display login form
		*/
		if(!is_user_logged_in()): ?>

			<h3>Members access only</h3>
			<p>
				The content of this page is reserved to members of the website only.
			</p>
			<form action="<?php echo wp_login_url($_SERVER['REQUEST_URI']); ?>" method="post" id="memberslogin">

				<h3>Are you a member?</h3>

				<p>If you are a member, please use the form below to login</p>

				<ol>
					<li class="inline"><label for="log">Username</label><input type="text" name="log" id="log" value="<?php echo wp_specialchars(stripslashes($user_login), 1) ?>" size="22" /></li>
					<li class="inline"><label for="pwd">Password</label><input type="password" name="pwd" id="pwd" size="22" /></li>
					<li><input type="submit" name="submit" value="Send" class="button" /><label for="rememberme"><input name="rememberme" id="rememberme" type="checkbox" checked="checked" value="forever" /> Remember me</label></li>
				</ol>
			</form>
		<?php

		else:

		?>
			<div class="adminaccess">
				<a href="javascript:;" id="open-members-form">Submit an article &raquo;</a>
				<form id="members-post" action="" method="post" class="<?php if($submit) echo 'open'; ?>">
					<?php if($error != ''): ?>
						<ul class="error">
							<?php echo $error; ?>
						</ul>
					<?php endif; ?>
					<?php if($message != ''): ?>
						<ul class="success">
							<?php echo $message; ?>
						</ul>
					<?php endif; ?>
					<div id="form-section-author" class="form-section">
						<div class="form-label"><label for="title">Article title</label> <em>*</em></div>
						<div class="form-input"><input id="title" name="title" type="text" value="<?php echo $your_post_title ?>" size="40" tabindex="3" /></div>
					</div><!-- #form-section-author .form-section -->

					<div id="form-section-comment" class="form-section">
						<div class="form-label"><label for="Content">Article content</label> <em>*</em></div>
						<div class="form-textarea"><textarea id="content-area" name="content" cols="65" rows="12" tabindex="6"><?php echo $your_post_content ?></textarea></div>
					</div><!-- #form-section-comment .form-section -->
					<p><em>*</em> required fields</p>
					<div class="form-submit"><input id="submit" name="submit" type="submit" value="<?php _e('Submit your article for review', 'thematic') ?>" /></div>
				</form>
				<a href="<?php echo wp_logout_url(get_bloginfo('url')) ?>">Logout</a>
			</div>
			
			<div class="navigation">
				<div class="alignleft"><?php next_posts_link('&laquo; Older Entries') ?></div>
				<div class="alignright"><?php previous_posts_link('Newer Entries &raquo;') ?></div>
			</div>
			
			<?php while ( have_posts() ) : the_post() ?>

			<div class="hentry">
				<h2><?php the_title() ?></h2>
				<div class="entry-content">
					<?php the_content('read more'); ?>
					<span class="meta-prep meta-prep-author">By</span>
					<span class="author vcard">
						<a class="url fn n" href="'<?php get_author_link(false, $authordata->ID, $authordata->user_nicename)?>" title="View all posts by <?php echo get_the_author() ?> ">
							<?php echo get_the_author(); ?>
						</a>
					</span>
				</div>
			</div><!-- .post -->

			<?php endwhile; ?>
			
			<div class="navigation">
				<div class="alignleft"><?php next_posts_link('&laquo; Older Entries') ?></div>
				<div class="alignright"><?php previous_posts_link('Newer Entries &raquo;') ?></div>
			</div>
			
			

		<?php endif;
	}
}
// Create the members object
$members_object = new Members_Section();

The hooks are in the constructor of the class and get called when we create the object (last line). This class goes in a separate file that then gets included in your function.php file or straight in the function.php, that’s up to you.

Some issues

There are some issues we couldn’t resolve. The main one is about the rss of the members category. The way Firefox caches rss feeds make the solution not perfect. If you are not logged in you will be redirected, no problem, but then if you login and go back to the rss feed, the browser cache will kick in and you will get redirected again.

The other small issue is in TinyMCE, the style dropdown we’ve added has got a strange default value. Not a big deal but still could be better.

If anyone has an idea about those to problem feel free to discuss in the comment section. And of course the discussion is open for comment, questions and anything related to the above.

One Comment

  1. Posted August 25, 2009 at 7:15 pm | Permalink

    This article has been shared on favSHARE.net. Go and vote it!

One Trackback

  1. [...] The (almost) Ultimate Members Page In WordPress [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>