WordPress Plugin Development – Relate Posts as a Series Part 2

Posted in Coding, PHP, Plugins, Tutorials, WordPress3 years ago • Written by 16 Comments

So, we started our dive into WordPress plugin development with the first part of this tutorial where we talked a little about planning, basic plugin structure, custom post types, metaboxes and how to add custom functions to WordPress’s defaults actions.

Today we will talk a little more about metaboxes and jQuery, shortcodes, and front-end functionality.

All these things with a pretty practical example of how to make a plugin that will relate posts as a series.

So, let’s rock!

A little recap… and download!

Last time we completed the first three steps:

  1. Planning – We’ve made our entity-relationship diagram to know which data we would have to store
  2. Create our plugin and custom post type – We’ve created the basic file for our plugin, and registered our series custom post type
  3. Post functionality – We’ve made some metaboxes to store custom data, and added them to WordPress’s default post saving function via actions.

If you didn’t read the first part of this tutorial, I highly recommend you to do this now, but if you don’t want to, you can still understand the functions and logic we are using here and make use of it.

Well, for people who are in a hurry, you can download the fully working plugin and make use of it without having to copy/paste all this code ;).

Step 4 – Series functionality

Registering our metabox

After we have control over all our posts related to series, we need to be able to manually edit the series itself.

In order to do this, we will need some metaboxes on them also. So, let’s edit our plugin with this code (here is just the additional code to avoid any confusion with the code that I explained last time, and won’t explain now):

<?php<br /-->//this function will register our metabox for wd_series custom post type
	function wd_call_meta() {
		add_meta_box(
			'series-data', // id of the <div> we'll add
			'Series Custom Data', //title
			'wd_series_options', // callback function that will echo the box content
			'wd_series', // where to add the box: on "post", "page", or "link" page
			'side', //positioning
			'low' //positioning
		);
	}
//with this we call it!
add_action('admin_menu', 'wd_call_meta');
?>

Our HTML metabox and jQuery enhancements

So, as you can see this function doesn’t have the metabox itself, it just says “Hey, WordPress, you should add function wd_series_options as a metabox for this guy!”. So we now need the metabox itself.

This is a tricky part since this box will give the user the ability to add “fake” posts to the series, so our readers could be interested in this series content. So what do we have now is:

  • Standard posts for a series, added via post editing screen
  • Fake posts for a series, added via series editing screen
  • Delete option, to remove a post from a series

We have also the opening / closing option for a series, and a really important attribute, the size of the series, so we don’t get lost in all our counters.

To get this working we will need some jQuery. We will have a “model” row, and when the user wants to add more lines we will get this model and duplicate it inside our form. We have to pay attention in regards to recovering data. We have to dynamically add rows as they are needed by one series. And we have, of course, some CSS for that.

Let’s do it this way:

<?php

function wd_series_options() {
		global $post;
		//get saved data
		$custom    = get_post_custom($post->ID);
		$open      = $custom["open"][0];
		$numFields = $custom["size"][0];

		$openNo = $openYes = "";
		if ($open == "yes") {
			$openYes = "checked = 'checked'";
		} else {
			$openNo = "checked = 'checked'";
		}

		$series_items = array();
		$i = 0;
		while($i < $numFields) {
			$i++;
			$key = "name_".$i;
			$series_items[$i] = $custom[$key][0];
		}
		$numFields++;
?>

<table>
<tbody>
<tr class="space">
<td><label>Open?</label></td>
<td>
				<label><input id="wd_open" name="wd_open" type="radio" value="yes" /> /> Yes, it is open.</label>

				<label><input id="wd_open" name="wd_open" type="radio" value="no" /> /> No, it is closed</label></td>
</tr>
</tbody>
</table>
	<table class="default-line">
		<tr>
			<td><input id="order_K" name="order_K" type="text" value="" size="5" /></td>
			<td><input id="name_K" name="name_K" type="text" value="" /></td>
			<td class="check"><input id="remove_K" name="remove_K" type="checkbox" value="remove" /></td>
		</tr>
	</table>
	<input type="hidden" id="NumFields" name="NumFields" value="<?php echo $numFields; ?>" />
	<table>
		<tr>
			<td>Order</td>
			<td>PostName</td>
			<td class="checkboxTd">Remove?</td>
		</tr>
	</table>
	<div class="ins">
	<?php
		foreach($series_items as $key => $name) {
			echo "<table>";
				echo "<tr>";
					echo "<td><input id='order_$key' name='order_$key' type='text' value='$key' size='5' /></td>";
					echo "<td><input id='name_$key' name='name_$key' type='text' value='$name' /></td>";
					echo "<td class='check'><input id='remove_$key' name='remove_$key' type='checkbox' value='remove' /></td>";
				echo "</tr>";
			echo "</table>";
		}
	?>
	</div>
	<table>
		<tr id="addItem">
<td colspan="3"><a id="clickLink" class="link" onclick="addLine();">Add new line</a></td>
</tr>
</tbody>
</table>
<style type="text/css">
	.default-line {
		display: none;
	}
	#series-data table {
		width: 100%
	}
		#series-data tr.space td {
			padding-bottom: 10px;
		}
			#series-data .space label {
				padding-right: 20px
			}
		#series-data .check {
			text-align: center;
		}
		#addItem td {
			padding-top: 10px;
			text-align: right;
		}
	.checkboxTd {
		width: 20px;
	}
	.link { cursor: pointer; }
</style>
<script type="text/javascript">
	function addLine() {
		jQuery(".default-line").clone().appendTo(".ins");
		var numElem = jQuery("#NumFields").attr('value');
		jQuery(".ins .default-line").fadeIn().removeClass('default-line');
		correctsLine(numElem);
	}
	function correctsLine(nID) {
		//goes switching K to correct number of line
		fieldName  = "order_" + nID;
		jQuery(".ins input#order_K").attr('value', ( parseInt(nID)+1) ).attr('id', fieldName).attr('name', fieldName);

		fieldName  = "name_" + nID;
		jQuery(".ins input#name_K").attr('id', fieldName).attr('name', fieldName);

		fieldName  = "remove_" + nID;
		jQuery(".ins input#remove_K").attr('id', fieldName).attr('name', fieldName);

		//corrects number of fields
		nID++;
		jQuery("#NumFields").attr('value', nID);
	}
	jQuery(window).load(function(){
		addLine(); //adds first (blank) row
	});
</script>

With this code you should see something like this as your series metabox:

Edit our default post saving function

And when you click on “Add new line”, believe me, it should create a fresh and brilliant new line.

Now we have this pretty box, but when you click “Update” nothing happens. This is why we haven’t prepared our WordPress insert post function to treat this data. What we have to do now it to say to WordPress “Hey, when you see this field in wd_series post type, delete all old data and save this new one for me, ok?”.

One important thing to note here is that we must delete all previous data and use some logic to reorder the series when needed.

Our magic here relies on ksort php function, so we save a temporary array and save all the items in the correct order after run this function.

Well, let’s do it:

<?php //we need a function that recieves post_id and $_POST data
function wd_meta($post_id, $post = null) {
//gets our POST custom data and saves it as meta keys, when needed
/* we have to save this metafields: open = Yes / No for open / closed series
size = how many items do we have in this series, so we can adjust our order counter
name_ORDER = the name of the ORDER'th item
post_ORDER = ID of the ORDER'th item
order_POST = Order of the ID (post) */
if( $post->post_type == "wd_series"  ) {
			//update series state (open / closed)
			$open = @$_POST["wd_open"];
			update_post_meta( $post_id, "open", $open );

			$size = @$_POST["NumFields"];
			$i = 0;

			$organize = array();
			while ($i < $size) {
				//let's pre-organize all posts
				$survive = $key = $remove = $order = $name = $post = null;

				$key = "remove_".$i;
				$remove = @$_POST[$key];

				if (empty($remove)) {
					//we won't delete this guy, and we'll put he in his right order
					$key = "order_".$i;
					$order = @$_POST[$key];

					$key = "name_".$i;
					$name = @$_POST[$key];

					$key = "post_".$i;
					$post = get_post_meta($post_id, $key, true);

					if (!empty($name)) {
						$organize[$order] = array( 'name' => $name, 'post' => $post);
						$survive = true;
					}
				}
				//we will pre delete everybody, to prevent trash in here
				$key = "name_".$i;
				delete_post_meta($post_id, $key);

				$key = "post_".$i;
				$post = get_post_meta($post_id, $key, true);
				delete_post_meta($post_id, $key);

				$key = "order_".$post;
				delete_post_meta($post_id, $key);

				//if it won't survive, we delete series_id from post_id
				if(!$survive) {
					$key = "series_id";
					delete_post_meta($post, $key);
				}
				$i++;
			}
			//let's correctly order this
			ksort($organize);
			$i = 0;
			foreach($organize as $item) {
				$i++;

				$key = "name";
				$nam = $item[$key];
				$key = "name_".$i;
				update_post_meta( $post_id, $key, $nam );

				$key  = "post";
				$post = $item[$key];
				if(!empty($post)) {
					$key = "post_".$i;
					update_post_meta( $post_id, $key, $post );
					$key = "order_".$post;
					update_post_meta( $post_id, $key, $i );
				}
			}

			$size = count($organize);
			update_post_meta( $post_id, "size", $size );
		}
	}
	//it's me saying to wordpress "Hey guy, don't forget to save this data when you insert or update posts!"
	add_action("wp_insert_post", 'wd_meta', 10, 2);
?>

Now we have our plugin working, let’s improve it.

Step 5 – Shortcodes and theming functions

Before we can output our posts we have to prepare two kind functions:

  • Common theming functions – Something like wd_series($args), so we can use for theming and widgets
  • Shortcodes – Something like [wd-series] so we can insert it directly from content edit mode.

Common functions

We will need to run a get_post loop, because probably our series will be shown inside another WordPress loop, so we can’t use WordPress’ default loop. Think about it this way: we will show a series when we are INSIDE a post, right? Thus the best way is via get_post.

Then we will just prepare a simple output function based on current post’s ID so it will show all items for the series related to it.

As we store the series related to this post inside “series_id” custom field, we just need to run a get_post for this series_id and output all metadata about series items.

Since for every item the output function is potentially the same, we will create two functions this time, one for items output and other for complete series output, as follow:

<?php //display item with or without link
function wd_item( $name, $postItem ) {
	if ( ! empty ( $postItem ) ) {
		$link = get_permalink($postItem);
		echo "<a title="$name" href="$link">$name</a>";
	} else {
		echo $name;
	}
}
//output!
function wd_series ($series) {
	$title = get_the_title($series);
	$meta  = get_post_custom($series);

	echo "<h3>$title</h3>";
	$size = $meta["size"][0];
	$i = 1;
	echo "<ol>";
	while ($i <= $size) {
		$key = "name_".$i;
		$name = $meta[$key][0];

		$key = "post_".$i;
		$post_item = $meta[$key][0];

		echo "<li>";
			wd_item( $name, $post_item);
		echo "</li>";

		$i++;

		$name = $post_item = null;
	}
	echo "</ol>";
}
?>

Shortcodes

So with the function above we can output our series in our template, and it is pretty customizable as you can see. But what if you want to give your writers the ability to decide where the series content should appear? Well, to do this you will need a shortcode.

Long story short, they give the ability to call functions via post content. So while I’m writing this post I could write [wd-series] and BAM! our series content would have to appear just above this text.

It is pretty easy to register a shortcode, and as long as we have our output function defined it will be even easier. With no more than six lines you can do it:

<?php // [wd-series]
function wd_series_shortcode() {
              global $post;
              $series = get_post_meta($post--->ID, "series_id", true);

              wd_series($series);
}
add_shortcode( 'wd-series', 'wd_series_shortcode' );
?>

After all this code, you will see something similar to this when you write [wd-series] in your content box:

Are you hungry yet?

This code surely could be improved and I know that some of our brilliant readers could point out some things to make it better. So why not leave a comment and share your thoughts?

And finally, I recommend you dig a little deeper into the  Shortcodes API, since it is a great tool when well used!

43 Written ArticlesWebsiteGoogle+

I'm a web designer and entrepreneur from Itajubá (MG), Brasil. I love writing about obscure topics and doing some cool stuff. And also I do some FREE stuff, check it out: http://www.roch.com.br/

16 Comments Best Comments First
  • Rochester Oliveira

    Tuesday, July 5th, 2011 21:57

    1

    Thank you! Hope it helps! :)

    0
  • Jason

    Thursday, September 15th, 2011 02:10

    7

    Great tutorial and details. thanks.

    0
    • Rochester Oliveira

      Thursday, September 22nd, 2011 05:32

      8

      Hi Jason,

      I’m glad you find it useful!

      Thanks!

      0
  • Mylène

    Tuesday, August 9th, 2011 20:33

    5

    Hi,

    Thanks a lot for this, this is just what I was looking for ! Just a question, though : when I install the plugin, should I add some code to single.php ? Or le series list will display automatically ?

    Thanks in advance !

    0
    • Rochester Oliveira

      Tuesday, August 30th, 2011 18:21

      6

      Hi Mylène,

      If you use shortcodes you don’t need anything at your single.php.. But if you want to control exactly where it will be shown, just put wd_series($series) if your post have one :)

      Thank you!
      []‘s

      0
  • Rochester Oliveira

    Tuesday, July 5th, 2011 21:58

    2

    Thank you Siddhartha, I’m glad you liked it!

    0
  • Antown

    Friday, July 15th, 2011 10:17

    3

    Very interesting article. I’m not very good at coding. It would be just great if you have collected all of the code in one file and can be downloaded.

    0
  • Giuseppina

    Saturday, October 1st, 2011 23:54

    9

    Wonderful job! Thank you!
    Is it possible to use this plugin with a custom type?

    0
    • Rochester Oliveira

      Sunday, October 2nd, 2011 06:22

      10

      Hey Giuseppina!

      Yes, it is. You’ll just have to call your metabox inside your custom post type, and readjust you post saving function to work for your custom post type same way it works for posts!

      []‘s

      0
  • Rochester Oliveira

    Monday, January 2nd, 2012 19:23

    16

    Hey WordPress Development,

    Thanks a lot! I like it too ;)

    []‘s

    0
  • Stanley

    Tuesday, October 25th, 2011 05:58

    14

    Great! I’m actually doing this for a client right now. Do you however know of a way to do this just with taxonomies alone? Or something a little lighter weight?

    0
    • Rochester Oliveira

      Tuesday, October 25th, 2011 13:30

      15

      Hi Web Design Edmonton,

      Yes, you could do it via custom taxonomies, but it would be a little harder to maintain.. You could create a taxonomy called series, and then add each post inside one series a “tag”. It will be like categorizing, so as time goes it will be a little hard to find proper category for each series.

      []‘s

      0
  • Rochester Oliveira

    Thursday, October 13th, 2011 16:47

    13

    Yo adi,

    I’m glad you liked it :) Hope you get this working soon!

    []‘s

    0
  • Tomb

    Thursday, October 6th, 2011 03:29

    11

    Would love to use this plugin! I just don’t have the time to code things. :/

    0
    • Rochester Oliveira

      Thursday, October 6th, 2011 14:45

      12

      Hey Tomb, just download it, then! :)

      []‘s

      0
  • Rochester Oliveira

    Monday, January 2nd, 2012 19:23

    16

    Hey WordPress Development,

    Thanks a lot! I like it too ;)

    []‘s

    0
  • Stanley

    Tuesday, October 25th, 2011 05:58

    14

    Great! I’m actually doing this for a client right now. Do you however know of a way to do this just with taxonomies alone? Or something a little lighter weight?

    0
    • Rochester Oliveira

      Tuesday, October 25th, 2011 13:30

      15

      Hi Web Design Edmonton,

      Yes, you could do it via custom taxonomies, but it would be a little harder to maintain.. You could create a taxonomy called series, and then add each post inside one series a “tag”. It will be like categorizing, so as time goes it will be a little hard to find proper category for each series.

      []‘s

      0
  • Rochester Oliveira

    Thursday, October 13th, 2011 16:47

    13

    Yo adi,

    I’m glad you liked it :) Hope you get this working soon!

    []‘s

    0
  • Tomb

    Thursday, October 6th, 2011 03:29

    11

    Would love to use this plugin! I just don’t have the time to code things. :/

    0
    • Rochester Oliveira

      Thursday, October 6th, 2011 14:45

      12

      Hey Tomb, just download it, then! :)

      []‘s

      0
  • Giuseppina

    Saturday, October 1st, 2011 23:54

    9

    Wonderful job! Thank you!
    Is it possible to use this plugin with a custom type?

    0
    • Rochester Oliveira

      Sunday, October 2nd, 2011 06:22

      10

      Hey Giuseppina!

      Yes, it is. You’ll just have to call your metabox inside your custom post type, and readjust you post saving function to work for your custom post type same way it works for posts!

      []‘s

      0
  • Jason

    Thursday, September 15th, 2011 02:10

    7

    Great tutorial and details. thanks.

    0
    • Rochester Oliveira

      Thursday, September 22nd, 2011 05:32

      8

      Hi Jason,

      I’m glad you find it useful!

      Thanks!

      0
  • Mylène

    Tuesday, August 9th, 2011 20:33

    5

    Hi,

    Thanks a lot for this, this is just what I was looking for ! Just a question, though : when I install the plugin, should I add some code to single.php ? Or le series list will display automatically ?

    Thanks in advance !

    0
    • Rochester Oliveira

      Tuesday, August 30th, 2011 18:21

      6

      Hi Mylène,

      If you use shortcodes you don’t need anything at your single.php.. But if you want to control exactly where it will be shown, just put wd_series($series) if your post have one :)

      Thank you!
      []‘s

      0
  • Antown

    Friday, July 15th, 2011 10:17

    3

    Very interesting article. I’m not very good at coding. It would be just great if you have collected all of the code in one file and can be downloaded.

    0
  • Rochester Oliveira

    Tuesday, July 5th, 2011 21:58

    2

    Thank you Siddhartha, I’m glad you liked it!

    0
  • Rochester Oliveira

    Tuesday, July 5th, 2011 21:57

    1

    Thank you! Hope it helps! :)

    0

Comments are closed.

54.242.18.190 - unknown - unknown - US