1stwebdesigner |
WordPress Plugin Development – Relate Posts as a Series – Part 1/2 Posted: 11 May 2011 03:00 AM PDT 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:
Step 1 – PlanningBefore 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:
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:
Now, we can get started on coding. Step 2 – Create our plugin and custom post typeLet’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 StructureJust open this blank file and write something like this: <pre><!--?<span class="hiddenSpellError" pre=""-->php</pre> /* 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 typeBefore 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: <pre><!--?<span class="hiddenSpellError" pre=""-->php</pre> 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"); ?> <pre> 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:
That last line is what calls the function, every time we call WordPress. Step 3 – Post functionalityCreating our metaboxNow 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. <pre><!--?<span class="hiddenSpellError" pre=""-->php</pre> 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). <pre><!--?<span class="hiddenSpellError" pre=""-->php</pre> //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); } } ?> <table> <tr> <td><label>Add to Series (leave blank to remove)</label></td> <td> <label> <select name="wd_exist_series" id="wd_exist_series"> <option>--Select existing series</option> <!--?<span class="hiddenSpellError" pre=""-->php $series_print = 0; //when this post is assigned to a closed series, we have to show it :D $args = array( 'post_type' => 'wd_series', 'posts_per_page' => -1, 'meta_key' => 'open', 'meta_value' => 'yes' ) ; $loop = get_posts( $args ); //we get all open series, to show them as options in our select foreach( $loop as $post ) : setup_postdata($post); if ($post->ID == $exist_series) { $selected = "selected = 'selected'"; $series_print = 1; } else { $selected = ""; } echo "<option value='".$post->ID."' $selected>".get_the_title()."</option>"; endforeach; if (empty($series_print)) { $data = get_post($exist_series); echo "<option value='".$data->ID."' selected = 'selected'>".get_the_title($exist_series)."</option>"; } ?> </select> </label><br /> <label><input id="wd_new_series" name="wd_new_series" type="text" value="New Series Name" /></label> </td> </tr> <tr> <td><label for="wd_order">Order in series (leave blank to be last)</label></td> <td> <input id="wd_order" name="wd_order" type="text" value="<?<span class=" />php echo $order_series; ?>" /> </td> </tr> <tr> <td>Close series?</td> <td> <label><input id="wd_end" class="hiddenSpellError" name="wd_end" type="checkbox" value="Yes" />php echo $closing; ?> /> Yes</label> </td> </tr> <tr> <td colspan="2"><label for="wd_name">Display name in series</label></td> </tr> <tr> <input id="wd_name" name="wd_name" type="text" value="<?<span class=" />php echo $name; ?>" /> </tr> </table> <style type="text/css"> #series-data table { width: 100% } #series-data tr td { padding-bottom: 10px; } #series-data .nospace td { padding-bottom: 0 } </style> <!--?<span class="hiddenSpellError" pre=""-->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 functionWe 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 <pre><!--?<span class="hiddenSpellError" pre=""-->php</pre> //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:
|
You are subscribed to email updates from 1stwebdesigner - Graphic and Web Design Blog To stop receiving these emails, you may unsubscribe now. | Email delivery powered by Google |
Google Inc., 20 West Kinzie, Chicago IL USA 60610 |
Comments (0)
Post a Comment