In the first article of this series, we looked at how a boilerplate can improve your development efforts by providing a foundation off of which your project can be built.
Ideally, boilerplates should provide just enough of a framework to get started while letting you focus on the specific business logic, core need, or domain-specific code that you need to write.
Specifically, we took a look at the WordPress Widget Boilerplates and the WordPress Plugin Boilerplate. In this post, we're going to take advantage of the Plugin Boilerplate to write our own plugin in order to see how Boilerplates both lay the foundation for writing good code, and how we can use it as a starting place for our future work.
A Post Message Plugin
In this post, we're going to be building a post notification widget that allows the author to add a new post message before the content on their page. This will actually be based on a plugin that's already in the wild so you can have a point of reference once the project has been completed.
As with the rest of my tutorials, I like to plan out the project in advance so let's lay out everything that we're going to be doing:
- Download a copy of the WordPress Plugin Boilerplate
- Fill out the TODO's with the specific information for your own project
- Implement the code necessary to display and save information in a post meta box
- Check for the existence of post meta and then render it in the content
- Finish up the README and Localization
Overall, it's a relatively simple plugin but it should provide a solid example of how Boilerplates allow you to focus on your specific functionality all the while working within the context of WordPress best practices.
Building the Widget
1. Download the WordPress Plugin Boilerplate
In order to get started, you need to download a copy of the WordPress Plugin Boilerplate. You can do this by navigating to its GitHub page, then clicking on the 'zip' button that you see near the top of the page or by clicking here.
Next, extract the contents of the download into your plugins directory. It should initially write out a directory called plugin-boilerplate.
Rename this to post-message. At this point, we're ready to begin working on the source code for the boilerplate.
2. Fill Out the TODO's in the Boilerplate
Next, open the post-message directory in your favorite IDE. The first thing that we want to do is open plugin.php and then locate all of the TODO's that exist in the code.
Out of the box, the code will look something like this:
<?php /* Plugin Name: TODO Plugin URI: TODO Description: TODO Version: 1.0 Author: TODO Author URI: TODO Author Email: TODO License: Copyright 2013 TODO ([email protected]) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ // TODO: rename this class to a proper name for your plugin class PluginName { /*--------------------------------------------* * Constructor *--------------------------------------------*/ /** * Initializes the plugin by setting localization, filters, and administration functions. */ function __construct() { // Load plugin text domain add_action( 'init', array( $this, 'plugin_textdomain' ) ); // Register admin styles and scripts add_action( 'admin_print_styles', array( $this, 'register_admin_styles' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'register_admin_scripts' ) ); // Register site styles and scripts add_action( 'wp_enqueue_scripts', array( $this, 'register_plugin_styles' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'register_plugin_scripts' ) ); // Register hooks that are fired when the plugin is activated, deactivated, and uninstalled, respectively. register_activation_hook( __FILE__, array( $this, 'activate' ) ); register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) ); register_uninstall_hook( __FILE__, array( $this, 'uninstall' ) ); /* * TODO: * Define the custom functionality for your plugin. The first parameter of the * add_action/add_filter calls are the hooks into which your code should fire. * * The second parameter is the function name located within this class. See the stubs * later in the file. * * For more information: * http://codex.wordpress.org/Plugin_API#Hooks.2C_Actions_and_Filters */ add_action( 'TODO', array( $this, 'action_method_name' ) ); add_filter( 'TODO', array( $this, 'filter_method_name' ) ); } // end constructor /** * Fired when the plugin is activated. * * @param boolean $network_wide True if WPMU superadmin uses "Network Activate" action, false if WPMU is disabled or plugin is activated on an individual blog */ public function activate( $network_wide ) { // TODO: Define activation functionality here } // end activate /** * Fired when the plugin is deactivated. * * @param boolean $network_wide True if WPMU superadmin uses "Network Activate" action, false if WPMU is disabled or plugin is activated on an individual blog */ public function deactivate( $network_wide ) { // TODO: Define deactivation functionality here } // end deactivate /** * Fired when the plugin is uninstalled. * * @param boolean $network_wide True if WPMU superadmin uses "Network Activate" action, false if WPMU is disabled or plugin is activated on an individual blog */ public function uninstall( $network_wide ) { // TODO: Define uninstall functionality here } // end uninstall /** * Loads the plugin text domain for translation */ public function plugin_textdomain() { // TODO: replace "plugin-name-locale" with a unique value for your plugin load_plugin_textdomain( 'plugin-name-locale', false, dirname( plugin_basename( __FILE__ ) ) . '/lang' ); } // end plugin_textdomain /** * Registers and enqueues admin-specific styles. */ public function register_admin_styles() { // TODO: Change 'plugin-name' to the name of your plugin wp_enqueue_style( 'plugin-name-admin-styles', plugins_url( 'plugin-name/css/admin.css' ) ); } // end register_admin_styles /** * Registers and enqueues admin-specific JavaScript. */ public function register_admin_scripts() { // TODO: Change 'plugin-name' to the name of your plugin wp_enqueue_script( 'plugin-name-admin-script', plugins_url( 'plugin-name/js/admin.js' ) ); } // end register_admin_scripts /** * Registers and enqueues plugin-specific styles. */ public function register_plugin_styles() { // TODO: Change 'plugin-name' to the name of your plugin wp_enqueue_style( 'plugin-name-plugin-styles', plugins_url( 'plugin-name/css/display.css' ) ); } // end register_plugin_styles /** * Registers and enqueues plugin-specific scripts. */ public function register_plugin_scripts() { // TODO: Change 'plugin-name' to the name of your plugin wp_enqueue_script( 'plugin-name-plugin-script', plugins_url( 'plugin-name/js/display.js' ) ); } // end register_plugin_scripts /*--------------------------------------------* * Core Functions *---------------------------------------------*/ /** * NOTE: Actions are points in the execution of a page or process * lifecycle that WordPress fires. * * WordPress Actions: http://codex.wordpress.org/Plugin_API#Actions * Action Reference: http://codex.wordpress.org/Plugin_API/Action_Reference * */ function action_method_name() { // TODO: Define your action method here } // end action_method_name /** * NOTE: Filters are points of execution in which WordPress modifies data * before saving it or sending it to the browser. * * WordPress Filters: http://codex.wordpress.org/Plugin_API#Filters * Filter Reference: http://codex.wordpress.org/Plugin_API/Filter_Reference * */ function filter_method_name() { // TODO: Define your filter method here } // end filter_method_name } // end class // TODO: Update the instantiation call of your plugin to the name given at the class definition $plugin_name = new PluginName();
Next, we need to make each TODO plugin-specific so we'll place all instances where needed with a variation of the name of the plugin.
For example:
- Replace the name of the plugin with Post Message
- Replace the URL of the plugin with your URL of choice
- Populate the name, email address, and all personal information with what works
- Name the class
Post_Message
- Name any locale-related strings, classnames, and ID's
post-message
Next, we can also remove all of the JavaScript and stylesheet calls except the admin.css styles. We'll be using this file later. Also be sure to remove the following files from the Boilerplate:
- views/display.php
- css/plugin.css
- js/admin.js
- js/plugin.js
This should leave you with the following directory structure:
Note that this is one thing about a Boilerplate: if anything, you should want to remove something from it. If you have to add something to it that helps you get off the ground - or that's more foundational - then it may be an opportunity to improve it.
Anyway, though we're not done yet, and though we will be adding to the plugin, the file should look something like this at this point:
<?php /* Plugin Name: Tom McFarlin Plugin URI: http://tommcfarlin.com/single-post-message/ Description: A simple way to add a message at the top of each of your posts. Version: 1.0 Author: Tom McFarlin Author URI: http://tommcfarlin.com/ Author Email: [email protected] License: Copyright 2013 Tom McFarlin ([email protected]) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ class Post_Message { /*--------------------------------------------* * Constructor *--------------------------------------------*/ /** * Initializes the plugin by setting localization, filters, and administration functions. */ function __construct() { // Load plugin text domain add_action( 'init', array( $this, 'plugin_textdomain' ) ); // Register admin styles and scripts add_action( 'admin_print_styles', array( $this, 'register_admin_styles' ) ); } // end constructor /** * Loads the plugin text domain for translation */ public function plugin_textdomain() { load_plugin_textdomain( 'post-message', false, dirname( plugin_basename( __FILE__ ) ) . '/lang' ); } // end plugin_textdomain /** * Registers and enqueues admin-specific styles. */ public function register_admin_styles() { wp_enqueue_style( 'post-message-admin', plugins_url( 'post-message/css/admin.css' ) ); } // end register_admin_styles } // end class new Post_Message();
Next, we need to actually begin working on our core business logic.
3. Implement the Code Necessary to Display and Save Information in a Post Meta Box
First, we need to identify exactly how this is going to work:
- We'll display a post meta box directly under the content editor
- It will include a text area that allows users to provide their own content
- When the post is updated, it needs to save the content of the text area
So the first thing we need to do is introduce a hook and a function in order to capture the data in the post meta box. In the constructor, let's add the following line:
add_action( 'add_meta_boxes', array( $this, 'add_notice_metabox' ) );
Next, we need to define the function add_notice_metabox
so that we can actually render the meta box, so let's provide that function now:
function add_notice_metabox() { add_meta_box( 'post_message', __( 'Post Message', 'post-message' ), array( $this, 'post_message_display' ), 'post', 'normal', 'high' ); } // end add_notice_metabox
After that, we need to write a function that's responsible for actually rendering the post content:
function post_message_display( $post ) { wp_nonce_field( plugin_basename( __FILE__ ), 'post_message_nonce' ); // The textfield and preview area echo '<textarea id="post-message" name="post_message" placeholder="' . __( 'Enter your post message here. HTML accepted.', 'post-message' ) . '">' . esc_textarea( get_post_meta( $post->ID, 'post_message', true ) ) . '</textarea>'; } // end post_message_display
Above, notice that we've introduced a nonce field for security purposes. Notice also that we've given this textarea
the ID of post-message
so that we can easily style it using CSS, and we've given it the name post_message
which will come in handy as we save the contents of the textarea
to the specific post's meta data.
Finally, we've taken advantage of the esc_textarea
function to make sure that we're properly encoding our data for rendering it in the textarea.
At this point, let's add some light styling using the admin.css file to give the post message a slightly different look and feel from the content:
#post-message { width: 100%; }
This should result in something like the following:
Of course, we still aren't done. We need to actually save and retrieve the data when the user clicks on the "Publish" or the "Update" button. To do this, we need to setup a call back for saving the post data.
So the last thing that we need to do is to introduce a save_notice
function. First, we'll register this in the constructor with the following code:
add_action( 'save_post', array( $this, 'save_notice' ) );
Then, we'll define the following function:
function save_notice( $post_id ) { if ( isset( $_POST['post_message_nonce'] ) && isset( $_POST['post_type'] ) ) { // Don't save if the user hasn't submitted the changes if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } // end if // Verify that the input is coming from the proper form if ( ! wp_verify_nonce( $_POST['post_message_nonce'], plugin_basename( __FILE__ ) ) ) { return; } // end if // Make sure the user has permissions to post if ( 'post' == $_POST['post_type'] ) { if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } // end if } // end if/else // Read the post message $post_message = isset( $_POST['post_message'] ) ? $_POST['post_message'] : ''; // If the value for the post message exists, delete it first. Don't want to write extra rows into the table. if ( 0 == count( get_post_meta( $post_id, 'post_message' ) ) ) { delete_post_meta( $post_id, 'post_message' ); } // end if // Update it for this post. update_post_meta( $post_id, 'post_message', $post_message ); } // end if } // end save_notice
We've discussed saving the contents of post meta data at length in previous articles, so I don't want to belabor the point here, but suffice it to say that the function does the following:
- Verifies that the user has permission to save this information
- Deletes any existing post meta data
- Saves the data to the associated post meta data
At this point, we're ready to test the plugin so fill out the textarea
, save the data, and when the page refreshes, make sure that the data also shows up in the textarea
.
If so, we're ready to move on; otherwise, make sure your code looks like the code above.
4. Render the Post Notice
Next up, we're ready to render the post message in the content. The process for doing this will be as follows:
- Register a function with the
the_content
filter - Check for the existence of post meta data
- Render it above the content if it's present
So let's do just that. First, let's register a function with the_content
filter:
add_filter( 'the_content', array( $this, 'prepend_post_message' ) );
After that, let's setup the actual function:
function prepend_post_message( $content ) { // If there is a notice, prepend it to the content if ( '' != get_post_meta( get_the_ID(), 'post_message', true ) ) { $post_message = '<p class="post-message">'; $post_message .= get_post_meta( get_the_ID(), 'post_message', true ); $post_message .= '</p><!-- /.post-message -->'; $content = $post_message . $content; } // end if return $content; } // end prepend_post_message
Notice that the post message is contained within its own p
element with its own class name so that you can easily style it however you'd like, should you want to do so.
At this point, we should have everything that we need in order to see the post message, so let's test it out. Create a post message, publish it, then view the post in your browser.
Next, remove the post content and then verify that nothing appears.
Easy enough, isn't it?
5. Finish Up the README and Localization
Finally, we need to make sure that we clean up the README and properly localize the plugin.
First, the README should contain the usual information. This is largely subjective; however, I've provided an example README below:
=== Post Message === Contributors: tommcfarlin Donate link: http://tommcfarlin.com/single-post-message/ Tags: post Requires at least: 3.4.1 Tested up to: 3.5 Stable tag: 1.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Easily add short messages and announcements above posts. Displays in the RSS feed and on the blog. == Description == Post Message is a plugin that allows you to add custom messages, announcements, and notices to individual posts. It's styled to grab the reader's attention and will render in both the browser *and* in RSS readers. Post Message... * Supports the use of HTML tags in the message content * Is available directly under the post content editor * Is fully localized and ready for translation == Installation == = Using The WordPress Dashboard = 1. Navigate to the 'Add New' Plugin Dashboard 1. Select `post-message.zip` from your computer 1. Upload 1. Activate the plugin on the WordPress Plugin Dashboard = Using FTP = 1. Extract `post-message.zip` to your computer 1. Upload the `post-messsage` directory to your `wp-content/plugins` directory 1. Activate the plugin on the WordPress Plugins dashboard == Changelog == = 1.0 = * Initial release
And finally, localization should be a cinch: Simply open the `plugin.po` file in the `lang` directory in your IDE, make sure that you change up the name of the plugin and the author information, then open it in POEdit to register the localization files.
Save your work and you're ready to go!
Conclusion
In this series, we've examined exactly what's needed to take advantage of WordPress Boilerplates, how they can help impact our workflow, and help us to focus more on the core of our project rather than re-inventing the wheel and getting up and going by repeating a lot of what's already needed.
Additionally, we built a plugin that can be downloaded for further review.
Hopefully this series has provided a case for why you should be using boilerplate code when applicable, and has helped to show how they can ease the pain of re-writing a lot of the same code in each of your projects.
Comments