Adding Post Series Functionality to WordPress With Taxonomies

Adding Post Series Functionality to WordPress With Taxonomies

Tutorial Details
  • Program: WordPress
  • Version: 3.2+
  • Difficulty: Medium
  • Estimated Completion Time: 45 minutes

Ever wrote a “post series” on your blog? If you did, you probably needed to add the links of the other parts of the series into the latest post you wrote. Each time you finished a new part, you had to update the link list of the other parts. There has to be an easier way, right?

There is. In this article, we’re going to learn how to create the “post series” functionality by using the the taxonomies and shortcodes.

The article contains 3 parts:

  • Creating the taxonomy
  • Creating the shortcode
  • Adding the taxonomies and using the shortcode

I’ll explain almost every line of code, so you can develop it any way you like.


Step 1 Creating the Plugin File

This barely counts as a step: We’ll just create the plugin file and fill in the details of the plugin:

<?php
/*
Plugin Name: Post Series
Plugin URI: http://wp.tutsplus.com/tutorials/plugins/adding-post-series-functionality-to-wordpress-with-taxonomies/
Description: Adds the "post series" functionality to WordPress with the help of a taxonomy and a shortcode.
Version: 1.0
Author: Barış Ünver
Author URI: http://beyn.org/
*/
?>

Save this little chunk of code in the file post-series.php or any name you like, into the folder /wp-content/plugins/post-series/.


Step 2 Setting Up Our New Taxonomy: “Series”

WordPress has a not-so-new functionality to create “taxonomies” so we can create more kinds of “classification groups” just like tags or categories (which are the built-in taxonomies of WordPress). We’re going to set up a new taxonomy called “Series” and we’ll be using this new taxonomy in our post series to help us.

First, we need to create a function to register the taxonomy. Let’s call it series_tax:

function series_tax() {
	/* Creating an empty function LIKE A BOSS */
}
add_action('init', 'series_tax', 0);

Take note that we also added an action to make the function run when WordPress is ready.

Next, we’ll be using the native register_taxonomy() function to create our taxonomy. But before that, we should set up the “labels” of the taxonomy:

function series_tax() {
	$labels = array(
		'name' => _x('Series', 'taxonomy general name'),
		'singular_name' => _x('Series', 'taxonomy singular name'),
		'all_items' => __('All Series'),
		'edit_item' => __('Edit Series'),
		'update_item' => __('Update Series'),
		'add_new_item' => __('Add New Series'),
		'new_item_name' => __('New Series Name'),
		'menu_name' => __('Series')
	);
}
add_action('init', 'series_tax', 0);

We didn’t use all the labels – You can find a full reference of taxonomy labels in our article Taking WordPress Custom Taxonomies to the Next Level.

We can register the taxonomy now:

// create the "Series" taxonomy for posts only
function series_tax() {
	$labels = array(
		'name' => _x('Series', 'taxonomy general name'),
		'singular_name' => _x('Series', 'taxonomy singular name'),
		'all_items' => __('All Series'),
		'edit_item' => __('Edit Series'),
		'update_item' => __('Update Series'),
		'add_new_item' => __('Add New Series'),
		'new_item_name' => __('New Series Name'),
		'menu_name' => __('Series')
	);

	register_taxonomy(
		'series',
		array('post'), /* if you want to use pages or custom post types, simply extend the array like array('post','page','custom-post-type') */
		array(
			'hierarchical' => true, /* if set to "true", you can use Series as categories; if set to "false", you can use them as tags! */
			'labels' => $labels,
			'show_ui' => true,
			'query_var' => true,
			'rewrite' => array('slug' => 'series'), /* you may need to flush the rewrite rules at Options -> Permalinks (just update the existing preferences without any change) */
		)
	);
}
add_action('init', 'series_tax', 0);

Congratulations, you just created the first half of your plugin! Now, let’s get to the second half…


Step 3 Creating the Shortcode: [series]

In this step, we’re going to build the [series] shortcode. With this shortcode, we’ll be able to insert the list of the series’ posts. We’ll also be able to customize the shortcode with some attributes like “title“, “title_wrap“, “slug“, “id“, “list“, “limit” and “future” which will all be optional.

Let’s start by creating our function:

function series_sc($atts) {
	extract(
		shortcode_atts(
			array(
				"slug" => '',
				"id" => '',
				"title" => '',
				"title_wrap" => 'h3',
				"list" => 'ul',
				"limit" => -1,
				"future" => 'on'
			),
			$atts
		)
	);
}
add_shortcode('series','series_sc');

We now have an empty shortcode with 7 optional attributes! We’ll get to know them while writing the rest of the code but I should explain them now:

  • slug – The slug of the series, defaults to nothing
  • id – The ID of the series, defaults to nothing
  • title – The title of the output, defaults to nothing
  • title_wrap – The HTML tag to wrap the title with, defaults to “h3
  • list – The HTML tag to wrap the post list with, defaults to “ul
  • limit – The maximum number of posts, defaults to -1 (all posts)
  • future – The option to include the future posts or not, defaults to “on

Next, we’ll code the “find the proper taxonomy” part:

if ($id) {
	// Use the "id" attribute if it exists
	$tax_query = array(array('taxonomy' => 'series', 'field' => 'id', 'terms' => $id));
}
elseif ($slug) {
	// Use the "slug" attribute if "id" does not exist
	$tax_query = array(array('taxonomy' => 'series', 'field' => 'slug', 'terms' => $slug));
}
else {
	// Use posts own Series tax if neither "id" nor "slug" exist
	$terms = get_the_terms($post->ID,'series');
	if ($terms && !is_wp_error($terms)) {
		$tax_query = array(array('taxonomy' => 'series', 'field' => 'slug', 'terms' => $term[0]->slug));
	}
	else {
		$error = true;
	}
}

The “slug” and the “id” work in cooperation but they’re both optional: The shortcode will first look for the “slug“, then it’ll look for the “id“. If there’s no slug and no ID either, the shortcode will try to get the “series” taxonomy of the post that contains the shortcode.

Now, let’s code the “create the title (if it’s specified)” part:

if ($title) {
	// Create the title if the "title" attribute exists
	$title_output = '<'.$title_wrap.' class="post-series-title">'.$title.'</'.$title_wrap.'>';
}

There will be no title if the “title” attribute is not specified. There will be a title with the <h3> tag if the “title_wrap” attribute is not specified. You should change the default of the attribute to something else (like <h4>) if you use something else as subheadings.

Let’s get to the “display the future posts or not” part now:

if ($future == 'on') {
	// Include the future posts if the "future" attribute is set to "on"
	$post_status = array('publish','future');
}
else {
	// Exclude the future posts if the "future" attribute is set to "off"
	$post_status = 'publish';
}

I strongly recommend that you leave the “future” attribute “on” but if you don’t want to list the titles of your future posts (not drafts), you can set it to “off“.

All right, we finished coding the attribute parts (except “limit“). We can now get to the part where we get the posts:

if ($error == false) { /* We are going to close this later */
	$args = array(
		'tax_query' => $tax_query,
		'posts_per_page' => $limit,
		'orderby' => 'date',
		'order' => 'ASC',
		'post_status' => $post_status
	);
	$the_posts = get_posts($args);
  • The ‘tax_query‘ argument is defined by the $tax_query variable which we created just a minute ago in the “find the proper taxonomy” part.
  • The ‘posts_per_page‘ argument is defined by the “limit” attribute of the shortcode.
  • The ‘orderby‘ and ‘order‘ arguments are defined to get the posts listed chronologically – the newest post will be at the bottom of the list.
  • The ‘post_status‘ argument is defined by the $post_status variable which we also created above, in the “display the future posts or not” part.

Now we’re done with fetching the posts, we can put everything together and create the post list!

	/* if there is more than one post with the specified "series" taxonomy, display the list. if there is just one post with the specified taxonomy, there is no need to list the only post! */
	if (count($the_posts) > 1) {
		// display the title first
		$output = $title_output;
		// create the list tag - notice the "post-series-list" class
		$output .= '<'.$list.' class="post-series-list">';
		// the loop to list the posts
		foreach ($the_posts as $post) {
			setup_postdata($post);
			if ($post->post_status == 'publish') {
				$output .= '<li><a href="'.get_permalink($post->ID).'">'.get_the_title($post->ID).'</a></li>';
			}
			else {
				/* we can not link the post if the post is not published yet! */
				$output .= '<li>Future post: '.get_the_title($post->ID).'</li>';
			}
		}
		wp_reset_query();
		// close the list tag...
		$output .= '</'.$list.'>';
		// ...and return the whole output!
		return $output;
	}
} /* Remember the "if" we did not close? :) */

And the final code of our shortcode’s function will be like this:

// The shortcode function of Post Series
function series_sc($atts) {
	extract(
		shortcode_atts(
			array(
				"slug" => '',
				"id" => '',
				"title" => '',
				"title_wrap" => 'h3',
				"list" => 'ul',
				"limit" => -1,
				"future" => 'on'
			),
			$atts
		)
	);
	if ($id) {
		// Use the "id" attribute if it exists
		$tax_query = array(array('taxonomy' => 'series', 'field' => 'id', 'terms' => $id));
	}
	elseif ($slug) {
		// Use the "slug" attribute if "id" does not exist
		$tax_query = array(array('taxonomy' => 'series', 'field' => 'slug', 'terms' => $slug));
	}
	else {
		// Use posts own Series tax if neither "id" nor "slug" exist
		$terms = get_the_terms($post->ID,'series');
		if ($terms && !is_wp_error($terms)) {
			$tax_query = array(array('taxonomy' => 'series', 'field' => 'slug', 'terms' => $term[0]->slug));
		}
		else {
			$error = true;
		}
	}
	if ($title) {
		// Create the title if the "title" attribute exists
		$title_output = '<'.$title_wrap.' class="post-series-title">'.$title.'</'.$title_wrap.'>';
	}
	if ($future == 'on') {
		// Include the future posts if the "future" attribute is set to "on"
		$post_status = array('publish','future');
	}
	else {
		// Exclude the future posts if the "future" attribute is set to "off"
		$post_status = 'publish';
	}
	if ($error == false) {
		$args = array(
			'tax_query' => $tax_query,
			'posts_per_page' => $limit,
			'orderby' => 'date',
			'order' => 'ASC',
			'post_status' => $post_status
		);
		$the_posts = get_posts($args);
		/* if there is more than one post with the specified "series" taxonomy, display the list. if there is just one post with the specified taxonomy, there is no need to list the only post! */
		if (count($the_posts) > 1) {
			// display the title first
			$output = $title_output;
			// create the list tag - notice the "post-series-list" class
			$output .= '<'.$list.' class="post-series-list">';
			// the loop to list the posts
			foreach ($the_posts as $post) {
				setup_postdata($post);
				if ($post->post_status == 'publish') {
					$output .= '<li><a href="'.get_permalink($post->ID).'">'.get_the_title($post->ID).'</a></li>';
				}
				else {
					/* we can not link the post if the post is not published yet! */
					$output .= '<li>Future post: '.get_the_title($post->ID).'</li>';
				}
			}
			wp_reset_query();
			// close the list tag...
			$output .= '</'.$list.'>';
			// ...and return the whole output!
			return $output;
		}
	}
}
add_shortcode('series','series_sc');

Conclusion: Usage Examples

Usage is pretty simple:

  1. Create your “series” from the Posts » Series page (as if you’re creating new categories),
  2. Assign your posts to those “series” while writing (as if you’re choosing categories),
  3. Use the [series] shortcode wherever you want!

You probably noticed this: You can use the shortcode without any attributes – if you’re using it inside your series’ posts:

[series]

But if you want to include series in pages or in a post which is not assigned to the series you’re including, you can use the “slug” or the “id” attributes:

[series slug="wordpress-themes"]
[series id="146"]

Finally, you can limit the number of posts shown, you can set a title and its heading tag, you can change the list to an ordered (numbered) list and you can prevent the future posts to be shown:

[series title="More WordPress Theme Lists" title_wrap="h4" limit="5" list="ol" future="off"]

I don’t want to paste the 100+ lines of the full source code here (the tutorial is already long) but you can download the plugin we created from here. Hope you like it!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://beyn.org/ Barış Ünver
    Author

    Shoot, I forgot to thank Stephen Harris in the article: He gave me the ideas for about the half of the function! Thanks, Steph! :)

  • http://chipmint.com Ritam Das

    Woha! That is the article on taxonomy I was looking for so log.. actually I want to create a tutorial series on my blog.. I think this article is going to help me!

    • http://beyn.org/ Barış Ünver
      Author

      I hope it does! Glad you like it :)

  • http://www.siamcomm.com Eric Buckley

    This was a very well written and succinct post … I really enjoyed the way you just get to the point in this one. Well done!

  • http://ylivalot.us Antti

    Whoa this came in handy :) Thanks Barış for this article!

  • http://thomasmaxson.com Thomas Maxson

    This is really well done and definitely gets the mental gears turning in the right direction!

    //thumbs up

  • http://LeWordpress.fr Soufiane

    Very good tutorial, the idea of using a shortcode is great but I think posts of the series should be display automatically inside every item of the series.

    Also some kind of demo website or screenshots of final results would be great :)

    • http://twitter.com/twittem Edward McIntyre
      • http://valendesigns.com Derek Herman

        @Edward McIntyre This plugin does not use the same code nor is it very close to the same code I wrote to create a series for the Tuts+ sites. The series on the Tuts+ sites use a custom post type to attach posts/pages to, not taxonomies. It’s a much more complex code base than what’s offered above.

        If you want the code Tuts+ uses you can purchase it on CodeCanyon http://codecanyon.net/item/post-series/239344 however, this solution will work in a simliar fashion but without adding an actual series. It all just depends on your needs.

    • jd

      This is very good article. But I would like to know how could you use the same code except the SHORTCODE and display the series automatically? That will be really helpfull and we will have options: Shortcode and automatic display. Thanks.

    • jd

      Also I would like to know what code do you use so that ‘series page’ would look like this: http://psd.tutsplus.com/sessions/introduction-to-photoshop-cs6/

  • Pingback: Adding Post Series Functionality to WordPress With Taxonomies | Qtiva

  • http://www.hairstylee.tk hairstyles

    Thank you Baris This is really.Very good tutorial.I want to create a tutorial series on my blog

  • Pingback: Adding Post Series Functionality to WordPress With Taxonomies | Shadowtek Hosting and Design Solutions

  • http://www.mentalaritmetik.org.tr Erkut E.

    This article is very clear to understand. Thanks to the tutsplus for it.

  • Pingback: How to Adding Post Series functionality to WordPress | WPAOT

  • http://twitter.com/GemmaWeirs Gemma W.

    Nice tut, but we already have plugins for this. I was hoping to learn how to hard-code this into my theme, if that’s even possible.

    • http://beyn.org/ Barış Ünver
      Author

      @Gemma; you can add the whole code to your functions.php file (except the plugin definition parts) so you can use it with your theme. But I think installing this as a plugin might be more helpful since it’s “portable” that way and it doesn’t have any options which queries with the database.

  • http://www.customicondesign.com CUSTOM ICON DESIGN

    very good tutorial. I will bookmark this article, and will read and test it very carefully tonight.

  • MP

    Wow, that’s way easier than I would have guessed. Could also double as a “similar articles” short code.

  • http://www.uxde.net/ Toan Nguyen Minh

    This is really a good example of functions taxonomies but I think use related posts functions will be easier.
    Thanks!

  • Pingback: WordPress Community Roundup for the Week Ending May 19 - Charleston WordPress User Group

  • Pingback: Best of Tuts+ in May 2012 - Milk-Break

  • Pingback: My Stream | Best of Tuts+ in May 2012 | My Stream

  • Shawn

    Cool plugin. I’m wondering how to add a “current” class to the appropriate list item. This would seem to be an important feature, IMHO.
    Also, is there a way for the default title to use the name of the taxonomy, so that every instance of the shortcode wouldn’t need the “title” attribute manually added? But could be overridden if specified?
    Thanks for the post!

  • Pingback: Best of Tuts+ in May 2012 | http://www.piczap.com

  • Pingback: Best of Tuts+ in May 2012 | clickshots

  • Pingback: Best of Tuts+ in May 2012 | GMancer

  • Pingback: Tweet Heat – The hottest Tweets of the Month [May 2012] | Web Dezining

  • Pingback: Wordpress Leaks » Best of Tuts+ in May 2012

  • http://blog.sklambert.com/ Steven Lambert

    Great tutorial.
    @Shawn: To get the “current” post and say, not link it in the list, modify your code to like this:

    global $post; // The current post data
    if (count($the_posts) > 1) {
    $output = $title_output;
    $output .= ”;
    foreach ($the_posts as $the_post) { // Change the $post to $the_post as to not interfere with the global variable
    setup_postdata($the_post);
    if ($the_post->post_status == ‘publish’) {
    $output .= ”;

    /* look for “current” post */
    if ($the_post->ID != $post->ID) {
    $output .= ‘ID).’”>’.get_the_title($the_post->ID).’‘;
    }
    else {
    $output .= get_the_title($the_post->ID);
    }
    $output .= ”;
    }
    else {
    $output .= ‘Future post: ‘.get_the_title($the_post->ID).”;
    }
    }
    wp_reset_query();
    $output .= ”;
    return $output;
    }

    Also, for some reason, this line is giving me problems ($term[0]-slug should be $terms[0]->slug in the tutorial):

    29. $tax_query = array(array(‘taxonomy’ => ‘series’, ‘field’ => ‘slug’, ‘terms’ => $term[0]->slug));

    the $terms = get_the_terms($post->ID,’series’); returns the stdClass Object as the 4th index and not as the 0th index. I’m not sure why it isn’t adding it to the first element of the array, but it caused me some problems until I found out why. If anyone knows why it does this, please let me know.

    • http://blog.sklambert.com Steven Lambert

      Hmm.. The pre tag didn’t work as I had hoped…

      In answer to my own question, I have found that the code $terms = get_the_terms($post->ID,’series’); will add the term to the array at the index of the term_id of the term in the database. This means that if I add a new term to the series taxonomy and it gets inserted into the database with a term_id of 4, the get_the_terms() function will return to the array at $terms[4]. Thus, hardcoding an index to find the term will not work since each term will get inserted into its own unique index.

      I think the best way to solve this is to use a foreach loop, grab the first term, and then break the loop. This will ensure that no matter where the term was inserted into the array, it can be found.

      $terms = get_the_terms($post->ID,’series’);
      if ($terms && !is_wp_error($terms)) {
      foreach($terms as $term) {
      $tax_query = array(array(‘taxonomy’ => ‘series’, ‘field’ => ‘slug’, ‘terms’ => $term->slug));
      break;
      }
      }

  • http://www.myleneb.net Mylène

    Hi and thanks a lot for this useful tutorial !

    I have a question, though : I created a template to list all existing series on a page, but I don’t manage to find the right query to do this. Could you give me a hint, please ?

    Thanks in advance

  • http://www.facebook.com/tacki.d Tacki D Ewan

    I tried using this plugin, but I’m having a problem with the shortcodes.

    [series] works, but if I start adding attributes, it won’t display anything. Any help?

  • Braunson Y

    Super handy! Thanks.

  • Pingback: How to manage WordPress Plugins by WordPress Tutorials