In the first post in this series, we discussed the reasons that WordPress Plugins should be treated more like larger software projects (but often aren't) and made a case for using well-organized code in an attempt to make our plugin projects more maintainable. All of this was done within the context of developing a WordPress widget.
The thing is, widgets are not the only type of plugins that can be developed for WordPress. The application also supports plugins that extend its functionality through the use of hooks located throughout the application. As such, widgets are not the only type of plugin that could benefit from a boilerplate.
In this tutorial, we'll define what exactly WordPress hooks are, how they work, and why they are beneficial. Next, we'll take a look at my WordPress Widget Boilerplate and how to leverage it in new projects through the context of an example application.
Understanding Hooks, Actions, and Filters
Before developing plugins, it's important to understand WordPress' model for hooking into the application and the different between actions and filters.
- Hooks are areas in the core WordPress application that allow you to register your code for execution. Simply put, your plugin registers itself with WordPress at a specific point of execution. When WordPress reaches that point of execution (either in saving information, rendering information or some other point), it fires your code.
- Actions are a type of hook that WordPress makes available for you to take advantage of whenever a specific event occurs during the application's life cycle. Some of these points include whenever a theme is activated, whenever a post is saved, or whenever style sheets are being rendered. Generally speaking, if you're looking to insert some type of custom functionality at any point in the WordPress life cycle, there is likely an action hook available.
- Filters are a type of hook that WordPress makes available for you to manipulate data before sending it to the database or sending it to render in the browser. Simply put, WordPress will pass the content to your function and then continue with its process with whatever you return. If you're looking to manipulate data before saving it or seeing it, your best option is to use filter.
Easy enough, right? On top of that, WordPress has solid documentation for adding actions[1] and adding filters[2]. You can reach much more about plugins in the Plugin API[3] in the WordPress Codex[4].
A Plugin Boilerplate
If you read the previous article, then you are already familiar with the Widget API. It's precise in that it requires a constructor and no less than three functions to get something working. Because plugins have the flexibility of hooking into a variety of points in the WordPress process, the Plugin API is a bit less structured. Fortunately, this doesn't mean that we can't craft some form of boilerplate off of which to create our plugins.
After all, they still consist of some common functionality:
- Core plugin code
- Style sheets
- JavaScript
- Localization files
- Markup
- Images
Similar to our WordPress Widget Boilerplate, we can setup our template directory to look like this:
Looks familiar, doesn't it? The most important aspect of the Plugin Boilerplate isn't the details of the directory structure (though we will examine a bit in the future) but the organization of the core plugin code itself.
The Plugin Skeleton
Plugin's can be developed either by using a handful of functions or by wrapping each function in a class in an object-oriented approach. I'm a fan of the latter and my boilerplate represents that. Here's the plugin definition:
<?php /* Plugin Name: TODO Plugin URI: TODO Description: TODO Version: 1.0 Author: TODO Author URI: TODO License: Copyright 2011 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 TODO { /*--------------------------------------------* * Constructor *--------------------------------------------*/ /** * Initializes the plugin by setting localization, filters, and administration functions. */ function __construct() { // Define constnats used throughout the plugin $this->init_plugin_constants(); load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename(__FILE__)) . '/lang'); /* * 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 if } // end constructor /*--------------------------------------------* * Core Functions *---------------------------------------------*/ /** * Note: Actions are points in the execution of a page or process * lifecycle that WordPress fires. */ 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. */ function filter_method_name() { // TODO define your filter method here } // end filter_method_name /*--------------------------------------------* * Private Functions *---------------------------------------------*/ /** * Initializes constants used for convenience throughout * the plugin. */ private function init_plugin_constants() { /* TODO * * This provides the unique identifier for your plugin used in * localizing the strings used throughout. * * For example: wordpress-widget-boilerplate-locale. */ if(!defined('PLUGIN_LOCALE')) { define('PLUGIN_LOCALE', 'plugin-name-locale'); } // end if /* TODO * * Define this as the name of your plugin. This is what shows * in the Widgets area of WordPress. * * For example: WordPress Widget Boilerplate. */ if(!defined('PLUGIN_NAME')) { define('PLUGIN_NAME', 'Plugin Name'); } // end if /* TODO * * this is the slug of your plugin used in initializing it with * the WordPress API. * This should also be the * directory in which your plugin resides. Use hyphens. * * For example: wordpress-widget-boilerplate */ if(!defined('PLUGIN_SLUG')) { define('PLUGIN_SLUG', 'plugin-name-slug'); } // end if } // end init_plugin_constants /** * Helper function for registering and loading scripts and styles. * * @name The ID to register with WordPress * @file_path The path to the actual file * @is_script Optional argument for if the incoming file_path is a JavaScript source file. */ private function load_file($name, $file_path, $is_script = false) { $url = WP_PLUGIN_URL . $file_path; $file = WP_PLUGIN_DIR . $file_path; if(file_exists($file)) { if($is_script) { wp_register_script($name, $url); wp_enqueue_script($name); } else { wp_register_style($name, $url); wp_enqueue_style($name); } // end if } // end if } // end _load_file } // end class // TODO: update the instantiation call of your plugin to the name given at the class definition new TODO(); ?>
Most IDE's have a function that lists all outstanding TODO's, so I place them throughout the code to easily locate what needs to be done during development. Note that there are three primary areas of code:
- Constructor. This function is responsible for defining any constants used throughout the code, specifying any localization files, and registering all actions and filters with WordPress.
- Core Functions are the actual function definitions registered in the constructor that are fired after during WordPress' execution.
- Helper Functions refer to functions that I use that help in execution by abstracting away common functionality (such as registering JavaScripts and stylesheets).
Note here that plugin development deviates from widget development in that there aren't multiple functions that are expected. In fact, you really only need a constructor. From there, any functions defined within the add_action or add_filter calls and your responsibility to implement.
Make sense? Let's take a look at using this boiler plate in a simple example.
A Working Example With Your Blog's RSS Feed
WordPress offers hooks for almost point of execution that you can imagine. In this example, we'll hook into the post rendering process in order to introduce a custom message. The thing is, we only want to display said message within the context of an RSS reader.
First, the requirements:
- Display a message at the footer of each post
- The message should only appear in RSS readers
- The message should contain a link back to the website of the post
Based on this, it doesn't look like we'll need to use any JavaScript, CSS, or markup create our plugin so we can reduce our plugin boilerplate to the core plugin code and the localization files:
At this point, we can begin stubbing out the boilerplate with a name and the plugin constants:
/** * Initializes constants used for convenience throughout * the plugin. */ private function init_plugin_constants() { if(!defined('PLUGIN_LOCALE')) { define('PLUGIN_LOCALE', 'rss-note-locale'); } // end if if(!defined('PLUGIN_NAME')) { define('PLUGIN_NAME', 'RSS Note'); } // end if if(!defined('PLUGIN_SLUG')) { define('PLUGIN_SLUG', 'rss-note-slug'); } // end if } // end init_plugin_constants
Next, we need to consider what type of hook is required for manipulating the content. Recall tha tsince we're trying to add something to the content proir to rendering it in the browser, we'll need a filter. From here, we can stub out the constructor:
/** * Initializes the plugin by setting localization, filters, and administration functions. */ function __construct() { // Define constnats used throughout the plugin $this->init_plugin_constants(); load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename(__FILE__)) . '/lang'); // add the note to both the excerpt and the main feed add_filter('the_content', array($this, 'display_rss_note')); add_filter('the_excerpt_rss', array($this, 'display_rss_note')); } // end constructor
Note that the function is using two filter - one for the_content[5] and one for the_excerpt_rss[6]. We're doing this because some users opt to only publish an excerpt of their blog rather than all of the content and we want to make sure that we're capturing both cases.
Next, let's actually implement the function definition that will append the message to the rest of the post:
/** * Appends a short message at the footer of each post viewed in an RSS reader * reminding users to visit your site. */ public function display_rss_note($content) { if(is_feed()) { $content .= '<div class="rss-note">'; $content .= '<p>'; $content .= __('Thanks for reading! Be sure to catch up on the rest of my posts at ', PLUGIN_LOCALE); $content .= '<a href="' . get_bloginfo('url') . '">'; $content .= get_bloginfo('name'); $content .= '</a>!'; $content .= '</p>'; $content .= '</div>'; } // end if return $content; } // end display_rss_note
Note here that the function accepts a parameter referred to by the $content variable. WordPress itself is passing this data into the function. For these particular filters, we're dealing with the content of a blog post so whatever we add to it must be concatenated so that it's added to the end of it.
This message we're adding simply says "Thanks for reading! Be sure to catch up on the rest of my posts at [Blog Name]!" through use of the get_bloginfo()[7] function.. Of course, you can update it to read whatever you like. Finally, note that we've wrapped this in an conditional that checks the is_feed()[8] function. This is important as we only want this code to fire if the content is being sent via an RSS feed.
That's it - not too bad, right? You can download the full version of the working source code (including the associated README) for this plugin on GitHub or right here from Wptuts. The boilerplate is also available on GitHub, too.
The point of this series was not only to help provide an introductory guide to the WordPress Plugin API, but also to provide a case and boilerplates for making it much easier to treat and maintain WordPress Plugins like any other software project.
- http://codex.wordpress.org/Function_Reference/add_action
- http://codex.wordpress.org/Function_Reference/add_filter
- http://codex.wordpress.org/Plugin_API
- http://codex.wordpress.org/Main_Page
- http://codex.wordpress.org/Function_Reference/the_content
- http://codex.wordpress.org/Template_Tags/the_excerpt_rss
- http://codex.wordpress.org/Function_Reference/is_feed
- http://codex.wordpress.org/Function_Reference/get_bloginfo
Comments