Advertisement

Developing Plugins With WordPress Boilerplates: Building a Plugin

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

This post is part of a series called Developing Plugins With WordPress Boilerplates.
Developing Plugins With WordPress Boilerplates: Why Boilerplates Matter

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:

  1. Download a copy of the WordPress Plugin Boilerplate
  2. Fill out the TODO's with the specific information for your own project
  3. Implement the code necessary to display and save information in a post meta box
  4. Check for the existence of post meta and then render it in the content
  5. 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.

WordPress 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@domain.com)

  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:

WordPress Plugin Boilerplate 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: tom@tommcfarlin.com
License:

  Copyright 2013 Tom McFarlin (om@tommcfarlin.com)

  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:

  1. We'll display a post meta box directly under the content editor
  2. It will include a text area that allows users to provide their own content
  3. 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:

Plugin Message Example

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.

Localizing The Plugin

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.

Advertisement