The time has come for me to finally publish a post about my recent foray into Wordpress plugin development. I am by no means an expert on the subject and maybe that is why this guide can be helpful for the newbie. Prior to this I have done extensive plugin and development work on Joomla! so I figured it shouldn’t be to hard to do something for Wordpress. I have been working with WP websites for only about a year or so and have grown to love the platform. I had written my cSprites core API and was looking for a platform to implement a plugin for and decided it would be a good chance to learn all about Wordpress.
It has been about two weeks from when I first decided to write my plugin, to today. Luckily for the time being I don’t have much other work so I have been able to dedicate quite a bit of my time to development of this plugin. I am really happy how far its come in the past two weeks, with about 800 downloads so far and a fairly stable version now.
There really isn’t too much organization to this post, really just a hodgepodge of various tools, tips, ideas and links that have been helpful to me. This isn’t meant to be a comprehensive “My first WP plugin tutorial”, there are already plenty of those. This is meant to be what comes after that, when you are scratching your head trying to figure something out (which I did plenty of!) or when you want to make sure you haven’t forgot. Also I don’t want to claim to be any kind of expert on the subject, considering this is my one and only plugin.
One lesson I have learned though, is learn from the best. I spent a lot of time looking through the source code of really popular and well establish plugins like NextGen Gallery. Studying this code can provide a lot of insight into how problems are solved, security, internationalization and other things you need to know in order to write a good plugin.
Anyways without further ado, here is a list of some stuff to think about:
The Wordpress SVN integration is a definite must to know when starting your projects. I use Coda for development, which has integrated SVN but I am not sure its ready for primetime as I still have some problems using it. So I ended up using SCplugin which integrates nicely into the finder and works like a charm. Once I can afford it, I am going to buy Versions which looks even better and has much more functionality. I also tend to use the command line when performing certain functions.
To start my project (after signing up with Wordpress and getting my account information) all I did was use SCplugin to checkout the base folder structure from the Wordpress repository and started working on it. It was really easy. Also the base folder strucutre includes trunk, branches and tags so should have most everything you need. Here are some of the commands I use:
svn co http://svn.wp-plugins.org/csprites-for-wordpress/ myfolder
Use this to check out the entire tree.
svn co http://svn.wp-plugins.org/csprites-for-wordpress/trunk myworkingdir
I use this to just check out the trunk when I am doing development, so I can test it directly in my WP install
svn cp trunk tags/0.495
This will create a new tagged version, which is important when using WP’s plugin repository and packaging service. The version in your readme and base plugin file need to match one of these tags.
svn merge -r 91101:HEAD branches/benchmark/ trunk
I created a branch for benchmarking purposes and did some work on it. Eventually I used the above code to merge my changes from the benchmark branch back into the trunk
svn log
You can call this in one of your repository directories to get a log of all the changes. This can be useful when you need to find a revision when doing a merge.
Wordpress Readme Standard | Wordpress Readme Validator
The readme.txt file is arguably one of the more important file in your whole plugin. It is parsed by the WP plugins server and displays a lot of relevant information on your plugin. It is important to keep this information up to date.
=== cSprites - Speed Up Page Load Time with Dynamic Image Sprites ===
Contributors: amummey
Tags: css, sprites, cSprites, images, content
Stable tag: 0.5
Requires at least: 2.7
Tested up to: 2.7.1
Donate link: http://mummey.org/csprites/
Automatically compress you content images into a big sprite, decreasing web page load time.
The top of my readme.txt file, this should be in your root directory. You can also add screenshots of you project by putting files in your root plugin directory and naming them screenshot-1.jpg, screenshot-2.jpg…
/*
Plugin Name: cSprites for Wordpress
Plugin URI: http://wordpress.org/extend/plugins/csprites-for-wordpress/
Description: Automatically compress you content images into a big sprite, decreasing web page load time.
Version: 0.5
Author: Adrian Mummey
Author URI: http://mummey.org/
Copyright 2008-2009 Adrian Mummey
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
The header of my main wp_csprites.php. Here I have included some more formatted information about the plugin. One thing to remember is that the Version number here should match the version in your readme.txt file for the Wordpress plugin server to recognize your latest version. I have forgotten to make these the same and wondered why my plugins page wasn’t updating. I have also included the license in my header.
Another thing is that when you commit a new version, it may not be recognized right away, there is some delay so wait 15 minutes or so and come back and refresh the page on Wordpress before you start panicking. Also I have noticed there is quite some delay in the automatic update feature on the plugin page in WP admin. It can take a few hours sometimes before my Admin shows me there is a new version of my plugin.
WP_PLUGIN_DIR
This constant will give you the Absolute directory path of the plugins directory. This is helpful when you need to do some file reads or writes.
WP_PLUGIN_URL
This constant will give you the full url path to the plugin directory.
define('CSPRITE_PLUGIN_NAME', basename(dirname(__FILE__)));
define('CSPRITE_PLUGIN_DIR', WP_PLUGIN_DIR.'/'.CSPRITE_PLUGIN_NAME);
define('CSPRITE_PLUGIN_URL', WP_PLUGIN_URL.'/'.CSPRITE_PLUGIN_NAME);
This is a quick way to define some constants that point to your plugins directory.
Before you put your plugin out, you need to be sure that all the requirements for its proper execution are met. If they aren’t you should degrade gracefully and not show any errors. In my plugin I wrote some preflight checks to make sure all the requirements are met before I hooked into wordpress. In addition, if the preconditions are not met, I will display a nice message for the user to tell them why the plugin isn’t working.
function cSpriteCheckPhpVersion(){
return version_compare(PHP_VERSION, '5.0.0', '>');
}
This function will make sure that PHP version is higher than 5.0.0 (a requirement of my plugin).
function cSpriteCheckWordPressVersion(){
global $wp_version;
$minimum_wp = '2.7';
return version_compare($wp_version, $minimum_wp, '>=');
}
Wordpress has a global version variable that you can use to check the current running version of Wordpress.
function cSpriteCheckCacheWritable(){
$file = CSPRITE_CACHE_DIR.'/permission.txt';
return (@file_put_contents($file, 'sanity check') !== false);
}
This check tries to write a small file to my cache directory. It is essential for the proper execution of my plugin for the cache directory to be writable.
There is a lot of documentation already written about most of the hooks and action, but I thought I would just cover a few here that don’t have as much written about them, but can be useful.
add_action('plugins_loaded', 'startcSprites');
I wrap all the plugin initialization code in a function called startcSprites which is handled by the plugins_loaded hook.
add_action('loop_start', 'cSpriteLoopStart');
Since I need to parse all of the content before its displayed I use this action hook to call a function to parse all the posts at the beginning of the loop.
function cSpriteLoopStart(){
global $wp_query;
if(in_the_loop() && !is_admin()){
$the_posts = $wp_query->posts;
foreach($the_posts as $post){
$content = $post->post_content;
$content = apply_filters('the_content', $content);
$content = str_replace(']]>', ']]>', $content);
$content = do_shortcode($content);
cSpriteFindImg($content);
}
if(SpritePreprocessorCache::preprocessorCacheNeedsCreation()){
SpritePreprocessorCache::registerImageSources();
Sprite::process();
}
add_filter('the_content', 'cSpriteImgReplace',50);
}
}
And then here is the code to process the posts one at a time inside the loop_start hook.
When writing a plugin, you should make sure you clean up what you left behind after the plugin is removed. Luckily Wordpress gives us a handy hook when dealing with this. I have a file called uninstall.php:
if ( function_exists('register_uninstall_hook') )
register_uninstall_hook(__FILE__, 'cSpriteUninstall');
This registers my uninstall function, which is located in the same file. The only changes I make to Wordpress are to add some options, so I just removed all the options I previously set.
function cSpriteUninstall() {
delete_option('cSpriteJpgQuality');
delete_option('cSpritePngCompression');
delete_option('cSpriteCacheTime');
delete_option('cSpriteForceNoPadding');
delete_option('cSpriteClearCacheNow');
delete_option('cSpriteProcessPng');
delete_option('cSpriteProcessJpg');
delete_option('cSpriteProcessGif');
}
Wordpress has a really nice way to handle your plugin options, it provides some special hooks and takes care of all the database stuff for you which is really nice. There are a couple ways to hook into it, the route I used is to first check if the user is acutally in an admin page, I have this function, which is a modified one from here:
if (!function_exists('is_admin_page')) {
function is_admin_page() {
if (function_exists('is_admin')) {
return is_admin();
}
if (function_exists('check_admin_referer')) {
return true;
}
else {
return false;
}
}
}
I do this check before adding the hooks. In addition I do a security check to make sure the current user has access to modify options.
if (current_user_can('manage_options')) {
add_action('admin_menu', 'cSpriteAdminMenu');
add_action('update_option_cSpriteClearCacheNow', 'cSpriteClearCache');
}
Only after these two checks will I add the hooks. The first hook is to create the options page link on the left nav bar. And the function it calls will actually provide another callback to display the options form.
function cSpriteAdminMenu() {
add_options_page(__('cSprites Options', CSPRITE_PLUGIN_NAME), __('cSprites', CSPRITE_PLUGIN_NAME), 8, __FILE__, 'cSpriteOptionsPage');
}
This will call a function named cSpriteOptionsPage that will output all the HTML needed for the function. The second hook from above update_option_cSpriteClearCacheNow is used because when the cSpriteClearCacheNow option is modified I need to do some specialized work that the default WP options handlers do not take care of. This is the function it calls, essentially deleting all the files in my cache:
function cSpriteClearCache(){
remove_action('update_option_cSpriteClearCacheNow', 'cSpriteClearCache');
if(isset($_REQUEST['cSpriteClearCacheNow'])){
Sprite::clearCache();
}
update_option('cSpriteClearCacheNow', 0);
return 0;
}
The function needs to return the new value of the option being modified. Since this isn’t a toggle switch I will always return zero to reset that option. You will notice that I first had to remove the action, this is because if I dont't then the following call to update_option will keep propagating and get stuck in an endless loop.
I wanted to spruce up my options page with some custom CSS and nice jQuery UI elements. Wordpress once again provides some more hooks to do this.
//Only load scripts and styles if we are on the csprites option page
if(strstr($_REQUEST['page'], CSPRITE_PLUGIN_NAME)!== false){
if (function_exists('wp_enqueue_style')) {
wp_enqueue_style('cSpritesAdminStyles', trailingslashit(CSPRITE_CSS_URL_DIR).'csprites.min.css', array(), '', 'screen');
}
if (function_exists('wp_enqueue_script')) {
wp_enqueue_script('cSpritesCheckbox', trailingslashit(CSPRITE_JS_URL_DIR).'jquery.checkbox.js', array('jquery'), '1.0');
wp_enqueue_script('jquery-slider', trailingslashit(CSPRITE_JS_URL_DIR).'ui.slider.js', array('jquery', 'jquery-ui-core'), '1.0');
wp_enqueue_script('cSpriteInit', trailingslashit(CSPRITE_JS_URL_DIR).'csprites.js', array('jquery-slider'), '1.0'); }
add_action('admin_head', 'wp_print_styles', '100');
}
Before loading the scripts and CSS I do a check to make sure that the admin is currently on the cSprites option page. The next function call is to wp_enqueue_script. This will put my custom CSS file in the style queue and will be outputted when the wp_print_styles function is called. Next I add my scripts to the queue with wp_enqueue_script. The cool part of this function is that you can specify dependencies for your scripts. In my case I wanted to use the jQuery Slider, so my script dependecies are jquery and jquery-ui-core. Wordpress will automatically put the code for these scripts before your custom JS files are added. The list of bundled scripts are located here in the Wordpress Codex.
Lastly I added an action which will call the wp_print_styles function when the header is called.
One thing to be aware of is that most of these hooks that wordpress provides will always be called at some point, in administrator and the frontend. It may be unnecessary for you to include CSS and JS in the header if your plugin isn’t being used on the page being viewed. It is important to make sure that your plugin is only being run when you want it to. This will avoid potential conflicts and wasted bandwith.
In addition, when using custom javascript, don't re-include libraries like jquery and prototype, because WP already has them and it does a pretty good job of handling them. So when appropriate you should always use wp_enqueue_script with the proper dependencies.
This will be an ongoing article once I get around to adding more stuff
Related posts:
nice blog, i come again
This is an excellent post, thanks!
I’m currently putting together a plug-in for my new web site too, and this info is very helpful as an addition to the WP Codex.
Nicely written article. I have now added php and wordpress version checking to my plugin