U bent hier

WordPress Plugin Development – Relate Posts as a Series – Part 1/2

afbeelding van globecom

Today our task is to create a simple WordPress plugin to relate posts as a series. With this, when you  create a new post it is automatically listed in your series, so if a user is reading any post of the series he can jump to others, even posts created after that one he is reading.

It may seem a bit too simple, but with this we can learn a lot about:

  • Custom post types
  • Custom fields
  • Metaboxes
  • Actions
  • Shortcodes
  • Basic plugin structure for WordPress
  • Little bit of jQuery

So, let’s rock!

Step 1 – Planning

Before any coding, we need to know what we want to create, so we don’t get lost in the process. There are a lot of tools and ways to use them to help us. The most important thing is to ensure that you at least know the minimum requirements for the project you’re working on, what you can add or cut down, and how things must work.

Our plugin functions list is:

  1. Create a new series, with title, description and posts assigned to it
  2. Add / remove current post to series (from post edit screen)
  3. Theme – Show other posts from series via shortcodes
  4. Theme – Show other posts from series via function (so you can use in your theme)
  5. Create fake series items, for future posts
  6. Edit title of the post when listed in series (it doesn’t have to be post’s WordPress title)

As long as we are using a CMS with a lot of built-in functions we won’t need to worry about external tables, SQL and many other functions that could take a little more time to create.

We can use custom post types to store data, by creating a simple custom post type structure and assign meta fields to it, so we don’t need any additional tables, or any complex changes, we will just use WordPress’ built-in functions. Just write a few lines and you are ready for it.

(Actually I’ve stolen Gilbert Pelegron’s idea, who has explained it much better than me. So, to dig a little deeper, check his post )

With this in mind, we can start to think more specifically about which data we will store and process. At this point we create a simple representation of how data should be stored, so we have all the data we need, without redundancy (so we just get the data from one point). There are a lot of ways to do it, but my idea is to save most data in our series entity, so when we get the series all needed data comes together (without additional calls). In this model, our post entity will just have to store which series it belongs to, and the rest is with the series.

As a simple way to represent it, I’ve made an entity-relationship diagram, with a little help from gliffy.

As you can see, our post type series will store almost all data, for a better performance. And its attributes is:

  • ID - Given by WordPress, when we create a series post.
  • Name - Title, saved as common WordPress’ post title.
  • Description - Little text about series. We will user WordPress’ description for this.
  • Open? - It is useful to know if a series is still open, so is much easier to assign a post to series when you have 1.000 series but just 3 open (you have posts to write for it in future).
  • Posts[], PostsScreenName[], SeriesSize – As we will use custom fields, I think the better way to store posts is create “fake arrays”. Let’s say we have one series with 3 posts, so we will have Post_1 = 10, Post_2 = 15, Post_3 = 32 and PostScreenName_1 = “Hello World”, PostScreenName_2 = “Hello World – Again”, PostScreenName_3 = “Hello, Hello World”. As we have 3 posts, our SeriesSize must to be 3.

Now, we can get started on coding.

Step 2 – Create our plugin and custom post type

Let’s create a folder inside our WordPress install (I recommend you do a blank 3.1.2 install, this is the version that I’m working with). Our folder name will be 1WDSeries and our plugin file will be 1WDSeries.php.

Plugin Basic Structure

Just open this blank file and write something like this:

php

/*
Plugin Name: 1WD Series
Plugin URI: http://www.1stwebdesigner.com/
Description: Creates "Series" custom post type, and makes possible relate normal posts inside one series
Author: Rochester Oliveira
Version: 1.0
Author URI: http://www.1stwebdesigner.com/author/rochester/
*/
?>

Those lines are required WordPress data for plugins. Just put a name, URI, Description and it will de shown in wp-admin, when users manage plugins.

Create post type

Before we start creating anything you must have in mind that any function we create here can be called in WordPress after (if this plugin is activated, for sure) AND any WordPress function can be called from this plugin. So we have to put a prefix in all our functions, to ensure that we will not have any conflict with other plugins or WordPress functions.

Our magic function to create custom post types is register_post_type($name,$args). Let’s see how it works:

php

function wd_registerPostType() {
//for better understanding when create / edit series we change some default labels
$labels = array(
'name' => _x('Series', 'wd_series'),
'singular_name' => _x('Series', 'wd_series'),
'add_new' => _x('New Series', 'wd_series'),
'add_new_item' => __('Add New Series'),
'edit_item' => __('Edit Series'),
'new_item' => __('New Series'),
'view_item' => __('View Series'),
'search_items' => __('Search Series')
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'series', 'with_front' => false ),
'capability_type' => 'post',
'menu_position' => null,
'supports' => array('title','editor', 'custom-fields')
);
register_post_type('wd_series',$args);
}
add_action("init", "wd_registerPostType");
?>

The first block set’s up our labels, for different cases for this plugin. That $args part is pretty important because it defines how this custom post type will behave. Important things:

  • public – whether display or not this post in front-end, admin menu
  • rewrite – overwrite permalinks structure for this post
  • capability_type -If it is more like a post (hierarchical and “fixed”), or more like a page (categorized and time-oriented)
  • menu_position – Set its order in admin menu.
  • supports – Which common fields this post will have

That last line is what calls the function, every time we call WordPress.

Step 3 – Post functionality

Creating our metabox

Now we need to add a metabox in the post edit screen. A metabox is a panel in our edit mode, in this case with fixed custom inputs. Thus the user doesn’t need to know the field name to add it (via default custom fields insert), moreover we can do some custom actions with this data (create a post series when we have a field “new series”, for example).

The function we need to call is add_meta_box, and it has some simple parameters. We will call it inside a function, so when we create our series metabox (surely it will need one too) we put it all together.

php

function wd_call_meta() {
add_meta_box(
'series-data', // id of the metabox
'Series setting', // Title
'wd_post_options', //callback function that will "echo" the contents
'post', // which post types will have it (page, post, link, wd_series)
'side', //positioning: side, advanced
'low' // positioning: low, high
);
}

if (is_admin()) {
//register metaboxes
add_action('admin_menu', 'wd_call_meta');
}
?>

The first block is our magic function, that says to WordPress “hey, when you edit a post, add this box, with these parameters!” The second function, says “hey, when you are in wp-admin don’t forget to call this function”.

We will now create our wd_post_options function, that defines what we have in our metabox. It is a simple HTML,  that we will just add some CSS to make it prettier. But we have to pay attention to get default values so if we are editing a post (not creating) we show the correct current data to the user (if our post is part of series_id 11, for example, we have to show this to the user).

php

//posts meta
function wd_post_options() {

global $post;
$custom = get_post_custom($post->ID);
$exist_series = $custom["series_id"][0]; //it comes as array, we have to get the index 0 value
$order_series = $custom["series_order"][0];
$closing = $name = "";
if(!empty($exist_series)) {
$open = get_post_meta($exist_series, "open", true);
if ($open == "no") {
$size = get_post_meta($exist_series, "size", true);
if ($size == $order_series) { //so, we have the last post in a closed series, this post is the "closing" series post
$closing = "checked = 'checked'"; //html value that we add to checkbox, to 'check' it, by default
}
$key = "name_".$order_series;
$name = get_post_meta($exist_series, $key, true);
}
}
?>

php echo $name; ?>" />

php echo $order_series; ?>" />
Close series?

php
}
?>

Creating the inputs itself, doesn’t save any data. It is just an input that WordPress ignores when saving default post data, so we need to change it. This is a job for our fantastic add_action function.

Save all this data with custom function

We need to run a function every time we have a post saved or edited. At this point, we have just $_POST data so we need to add our custom fields, and create our custom post type when this post is the first of its series. At this point we will deal with some series custom fields (because it stores almost all data), so it may seem quite strange now, but in the end it’s gonna be all right  :D

php

//saves our post custom data
function wd_meta($post_id, $post = null) {
/*
Custom Fields in posts: series_id
*/
if ($post->post_type == "post") {
$title = get_the_title($post_id);

$old_series = get_post_meta($post_id, "series_id", true);
//compare old series data, and see if it needs to be rewritten
$exist_series = (int) @$_POST["wd_exist_series"];
$new_series = @$_POST["wd_new_series"];
$order = @$_POST["wd_order"];
$name = @$_POST["wd_name"];
$closing = @$_POST["wd_end"];

if(!empty($new_series) && $new_series != "New Series Name") {
//our series have changed, let's delete this post from old series and "resave" it!
$key = "order_".$post_id;
$old_order = get_post_meta($old_series, $key, true);
delete_post_meta($old_series, $key);

$key = "post_".$old_order;
delete_post_meta($old_series, $key);

$key = "name_".$old_order;
delete_post_meta($old_series, $key);

//update old series size
$old_size = get_post_meta($old_series, "size", true);
$old_size--;
update_post_meta($old_series, "size", $old_size);

//we have a new series, so let's create it, dude!
$args = array(
'post_title' => $new_series,
'post_status' => 'publish',
'post_type' => 'wd_series'
);
$series_id = wp_insert_post( $args );

if (empty($name)) {
//we don't have any name, so let's use post name
$name = $title;
}
//add this post to series_id and its wd_order and wd_name
update_post_meta($series_id, "size", 1);
update_post_meta($series_id, "post_1", $post_id);
update_post_meta($series_id, "name_1", $name);
$key = "order_".$post_id;
update_post_meta($series_id, $key, 1);

update_post_meta( $post_id, "series_id", $series_id );
}
elseif (!empty($exist_series)) {
// we have a series, let's see if it is still the same
if ( $exist_series != $old_series) {
//our series have changed, let's delete this post from old series and "resave" it!
$key = "order_".$post_id;
$old_order = get_post_meta($old_series, $key, true);
delete_post_meta($old_series, $key);

$key = "post_".$old_order;
delete_post_meta($old_series, $key);

$key = "name_".$old_order;
delete_post_meta($old_series, $key);

//update old series size
$old_size = get_post_meta($old_series, "size", true);
$old_size--;
update_post_meta($old_series, "size", $old_size);

//update series size
$size = get_post_meta($exist_series, "size", true);
$size++;
update_post_meta($exist_series, "size", $size);

if (!empty($new_order)) {
//we do have a new order to set, so let's follow it
$order = $new_order;
} else {
//we don't have any order to set, so let's check it as last item
$order = $size;
}
$key = "post_".$order;
update_post_meta( $exist_series, $key, $post_id );

if (empty($name)) {
//we don't have any name, so let's use post name
$name = $title;
}
$key = "name_".$order;
update_post_meta( $exist_series, $key, $name );

$key = "order_".$post_id;
update_post_meta( $exist_series, $key, $order );

//save new series on this post
update_post_meta( $post_id, "series_id", $exist_series );
}
}
//closing if needed
if (!empty($closing)) {
update_post_meta( $series_id, "open", "no" );
} else {
update_post_meta( $series_id, "open", "yes" );
}
}
}
//add custom meta when we save posts
add_action("wp_insert_post", 'wd_meta', 10, 2);
?>

Are you hungry yet?

I think we’ve had enough fun for today. In our next post, we add our series meta box, deal with its data and show all this stuff in our theme (via functions and shortcodes). It will be amazing :D

As I know you are a good padawan, and want to know more about all this stuff we worked with here, so here are some good links for reading:

Onze klanten

From the blog

afbeelding van globecom
afbeelding van globecom

Changing next number in a Drupal serial field

After searching for 2 days on Drupal.org without finding a good way to change the starting point of a serial field I

Read more...
afbeelding van globecom

Automatisch PDF maken, mailen en toevoegen als bijlage

Voor een klant had ik de uitdaging het volgende te maken.

Read more...

Neem contact op

  • Globecom
          Schoolstraat 245
          7606EM
          Almelo
  • +31 (0)634924795
  • info@globecom.nl

Laatste Tweets

Latest Shots