"We’ve been talking a lot about “Best Practices” here on Wptuts lately. Today, we’ll cover some important best practices for creating a WordPress plugin. From security tips to namespacing tricks, follow these rules and you’ll do no wrong. Whether you’re a budding new plugin developer or a time-tested veteran, these simple rules and suggestions will make you a better developer (and the community will thank you for it!)
Rule 01: Have a Strategy
Yes, a strategy. Try this checklist:- Is my plugin just for fun/demonstration purposes or for everyday use in the real world?
- Am I writing it to contribute to the community, to promote myself or to earn income?
- Can I afford the time to offer support to users?
- Have similar plugins already been written? Check the WordPress Plugin Repository.
Rule 02: Use Consistent and Clear Coding Standards
Which do you prefer, this?function parsefile($imagedata,$file){ if(!in_array($imagedata['mime'], array('image/jpeg','image/png','image/gif'))){ $result['error']='Your image must be a jpeg, png or gif!'; }elseif(($file['size']>200000)){ $result['error']='Your image was '.$file['size'].' bytes! It must not exceed '.MAX_UPLOAD_SIZE.' bytes.'; } return $result; }Or this?
/* processes the uploaded image, checking against file type and file size */ function parse_file($image_data, $file){ if(!in_array($image_data['mime'], unserialize(TYPE_WHITELIST))){ $result['error'] = 'Your image must be a jpeg, png or gif!'; }elseif(($file['size'] > MAX_UPLOAD_SIZE)){ $result['error'] = 'Your image was ' . $file['size'] . ' bytes! It must not exceed ' . MAX_UPLOAD_SIZE . ' bytes.'; } return $result; }Simple things like consistent spacing, indenting, informative variable naming and succinct comments are a good place to start. That’s the only difference in the examples above. WordPress has an excellent guide to coding standards. If you code to a standard your code will be easier to understand, edit and debug.
Use Namespacing
There are two ways to construct a WordPress plugin: as a bunch of functions or as a class. Keep in mind that the functions in your plugin get thrown into the global namespace with all the other non-namespaced functions, which means that if you have a function named plugin_init then it’s probably going to conflict with the same function written by someone else at some point. The obvious way to fix this issue is to prefix the function name with something unique. I like to use the first letters of my plugin name, so if I had a plugin called My Great Plugin, I would call the above function mgp_plugin_init, and the same for the rest of my functions.But a better way is to create a class for your plugin, that way all functions will be namespaced under the name of the class and even better, you can now use short, common method (function) names.
class my_class{ function hello_world(){ return 'Hello World!'; } function goodbye_world(){ return 'Goodbye World!'; } }Then you could access your class methods under the its own namespace:
$my_new_plugin = new my_class(); echo $my_new_plugin->hello_world(); echo $my_new_plugin->goodbye_world();
Rule 03: Take Security Seriously
Making your plugin secure is the single most important step to take, but many developers discount security or relegate it to the status of an afterthought. Don’t make that mistake.Sanitize inputs, escape outputs
You should especially make yourself familiar with:If you use a function such as wp_insert_post, WordPress will sanitize that data for you. Similarly, if you use the database methods $wpdb->insert or $wpdb->update, WordPress will sanitize the data. But if you choose to access the database more directly with something like $wpdb->get_result, then you should use wpdb->prepare to prevent sql injections of malicious code.
Instead of:
$admin_posts = $wpdb->get_results( "SELECT ID, post_title FROM $wpdb->posts WHERE post_status = 'publish' AND post_author = 1" );You would use:
$admin_posts = $wpdb->get_results($wpdb->prepare( "SELECT ID, post_title FROM $wpdb->posts WHERE post_status = %s AND post_author = %d", 'publish', 1 ));In the above example, %s is a placeholder for string input (publish) and %d is a placeholder for numeric input (1). You can use as many placeholders as you need.
Use Nonces for Form and URL Verification
It’s important to know that when a form or url is posted back to WordPress, that it was your WordPress site that actually generated it and not some third party or malicious agent. To take care of this aspect of security, we use nonces. A nonce is a number used once.For a form, we generate a nonce field using wp_nonce_field that will be included in our form as a hidden field:
wp_nonce_field('my_nonce', 'my_nonce_submit');Because it’s now a hidden field in our form, it’ll come back to us when the form is submitted. We can then check that the nonce is valid using wp_verify_nonce:
wp_verify_nonce($_POST['my_nonce_submit'], 'my_nonce') )That will return true if the nonce verifies.
For a url (links in emails, browser urls), you can create a nonced url with wp_nonce_url:
$nonced_url = wp_nonce_url('http://my_site.com?action=register&id=123456', 'register_nonce');That would create a url like this…
http://my_site.com?action=register&id=123456&_wpnonce=250d696dc6
…which you could send in a registration confirmation email. You would then check the incoming url for a nonce with the same id using the check_admin_referer function and proceed with verification if true.
if($_GET['action'] == 'register'){ if(check_admin_referer('register_nonce')){ if(verify_id($_GET['id'])){ echo 'Registration verified!'; } }else{ echo 'Registration verification FAILED!'; } }
Rule 04: Access Web Services Intelligently
The internet is full of web services providing us with everything from weather forecasts to stock quotes to the latest tweets from Twitter. The most efficient way for a plugin to access remote data is to use the HTTP API which will handle your request in the background, using the most efficient of the five possible remote methods that PHP exposes. It’s like a one-stop shop for webservice access.This uses the wp_remote_get method (a wrapper function of the HTTP class) to get a page body:
$page_data = wp_remote_get('http://site.com/page'); if($page_data['response']['code'] == 200){ $body = $page_data['body']; }Same with wp_remote_post to login:
$args = array( 'login' => 'login_name', 'password' => 'my_password' ); $response = wp_remote_post('http://site.com/login', $args);
Rule 05: Internationalization
This doesn’t have to be a big deal but is essential if you want to reach the widest audience possible. At a minimum all you need do is prepare your plugin with the _e() and __() functions and worry about the texdomain afterwards.It’s all explained very well in this wp.tutsplus article by Tom McFarlin and here at the WordPress codex.
Rule 06: Load Only What You Need
When you go away overnight, you don’t take as much with you as you would if you were going on vacation for a month. So treat your plugin the same way: only load the stylesheets, javascript and other scripts that you need for that page or section of the site. At the very least, don’t load admin scripts on the frontend and vice-versa.if(is_admin()){ //load my plugin stuff for the admin pages }else{ //load my plugin stuff for the frontend }
Rule 07: Tidy Up After Yourself
When you activate a plugin, you will see a deactivate link appear in the plugins admin page. But deactivating only stops the plugin from functioning. After deactivation, you then get a delete link. The delete link is the key to tidying up after yourself. It should really be called uninstall.If you create a file named uninstall.php in the same directory as you plugin, the code in this file will be run when the user clicks delete.
if(defined('WP_UNINSTALL_PLUGIN') ){ //delete options, tables or anything else }The above code checks for the WP_UNINSTALL_PLUGIN constant which WordPress sets when you hit delete. Then you can proceed to delete any options or custom tables that your plugin created when it was activated.
Bonus Tips
Turn on debug
The WordPress debugger will find errors in your code that while they may not crash you plugin should still be corrected to make sure that you’re doing things correctly. So, turn on WordPress debug by placing this in your /wp-config.php:define('WP_DEBUG', TRUE);