Video icon 64
Learning to code? Skill up faster with our practical video courses. Start your free trial today.
Advertisement

Adding Post Series Functionality to WordPress With Taxonomies

by

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!

Advertisement