{"id":1321,"date":"2009-09-11T19:31:17","date_gmt":"2009-09-11T17:31:17","guid":{"rendered":"http:\/\/planetozh.com\/blog\/?p=1321"},"modified":"2009-09-15T21:56:51","modified_gmt":"2009-09-15T19:56:51","slug":"top-10-most-common-coding-mistakes-in-wordpress-plugins","status":"publish","type":"post","link":"https:\/\/planetozh.com\/blog\/2009\/09\/top-10-most-common-coding-mistakes-in-wordpress-plugins\/","title":{"rendered":"Top 10 Most Common Coding Mistakes in WordPress Plugins"},"content":{"rendered":"<p><em>Short intro for readers who don&#39;t follow <a href=\"http:\/\/twitter.com\/ozh\">me<\/a> or this <a href=\"http:\/\/planetozh.com\/feed\/\">blog&#39;s feed<\/a>: I&#39;ve been a judge in the annual WordPress Plugin Competition, and as such I have reviewed a number of plugins. Read <a href=\"http:\/\/planetozh.com\/blog\/2009\/09\/being-a-judge-in-the-wordpress-plugin-competition\/\">more about this<\/a>.<\/em><\/p>\n<p>As promised, I&#39;m going to share a list of the most common mistakes, errors, misunderstandings, bad habits or wrong design decisions I&#39;ve encountered while reviewing all these 43 plugins. Some are highly critical stuff (I&#39;ve contacted 3 plugins authors after finding serious security holes in their plugin), some are more potential annoyances than real bugs, or are just causing a waste of server resources that could be avoided, but all have something in common: they&#39;re trivial to fix.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/planetozh.com\/blog\/wp-content\/uploads\/2009\/09\/wtf-code.jpg\" alt=\"wtf-code\" title=\"wtf-code\" width=\"450\" height=\"408\" class=\"aligncenter center size-full wp-image-1323\" srcset=\"https:\/\/planetozh.com\/blog\/wp-content\/uploads\/2009\/09\/wtf-code.jpg 450w, https:\/\/planetozh.com\/blog\/wp-content\/uploads\/2009\/09\/wtf-code-300x272.jpg 300w\" sizes=\"auto, (max-width: 450px) 100vw, 450px\" \/><br \/>\n(Image stolen from <a href=\"http:\/\/www.osnews.com\/story\/19266\/WTFs_m\">Thom Holwerda<\/a> without permission)<\/p>\n<p>I&#39;ve classified them in two parts: 10 bad code signs, plus a bonus with design decisions that suck. If you consider yourself a semi experienced coder or better, be sure to skip this article, you&#39;re not going to learn a thing :)<\/p>\n<p><!--more--><\/p>\n<h2>10 most frequent bad code bits<\/h2>\n<p>What I call bad code can be: code that might work but is ugly, code that works but will fail one day, and obviously code that doesn&#39;t work at all.<\/p>\n<h3>1. It&#39;s not a plugin, it&#39;s a mess<\/h3>\n<p>I&#39;ve been truly <strong>shocked<\/strong> by the number of coders who deliver stuff with no comment, no or random indentation in code (honestly!! just *no* indentation!! can you believe this??). As a judge I&#39;ve poured my wrath on their final grade, just like as a user I would simply never install a plugin that looks like a mess, because if it <em>looks<\/em> like a mess it <em>is<\/em> obviously one.<\/p>\n<p>A plugin with no comment where needed and no indentation to make code readable tells one thing: the plugin won&#39;t ever be updated or maintained, because in a couple of months the author will be simply lost and won&#39;t remember what, how and why they&#39;ve done this or that.<\/p>\n<p>I won&#39;t elaborate too much on this because it&#39;s just plain common sense, but if you&#39;re one of those messy guys or gals who don&#39;t really see the point here, please read the following article: <a href=\"http:\/\/planetozh.com\/blog\/articles\/make-clean-and-readable-sources-why-and-how\/\">Make clean and readable sources: why and how<\/a>. Hopefully you&#39;ll change your mind and adhere to <a href=\"http:\/\/codex.wordpress.org\/WordPress_Coding_Standards\">WordPress Coding Standards<\/a>.<\/p>\n<h3>2. Way too generic function names<\/h3>\n<p>Again basics here, but about <em>40% of the plugins<\/em> I&#39;ve reviewed use a waaaaay too generic function naming scheme. The point here is to make sure your plugin will never trigger a &quot;Fatal error: Cannot redeclare your poorly named function&quot; error.<\/p>\n<p>Function names must be two things: descriptive and unique. I&#39;ve found plugins with function named as simply as <tt>pretty_title()<\/tt>, <tt>pages()<\/tt> or <tt>update_options()<\/tt>. Ironically enough, a coder submitted several plugins that won&#39;t be able to run on the same blog because they all use the same function declarations.<\/p>\n<p>Better function names would have been for instance <tt>joeplugin_post_pretty_title()<\/tt>, <tt>joeplugin_list_pages()<\/tt> or <tt>joeplugin_update_options()<\/tt>. An alternative to keep function names short is to wrap them into a class (that also must have a unique and descriptive name).<\/p>\n<h3>3. What? 87 new rows in the option table?<\/h3>\n<p>If you&#39;ve been reading me for a while, you know it&#39;s my <a href=\"http:\/\/planetozh.com\/blog\/2008\/06\/wordpress-plugin-coding-tips\/\">favorite pet peeve<\/a>: don&#39;t save each setting into a separate DB entry. Store them in an array, and save it in <em>one<\/em> DB row. One of the plugins in the competition for instance creates <em>87 entries<\/em> in the option table.<\/p>\n<p>What I don&#39;t like about adding a bunch of entries in the option table is that it makes a unnecessarilly cluttered database once you deactivate the plugin, and that the plugin performs a bunch of SQL write queries instead of just one.<\/p>\n<p>There&#39;s an exception to this rule: if your plugin has a gazillion options and only a few of them are going to be used on every instance while the rest will only be used by an admin page, then it makes sense to store them into 2 or more entries <strong>and<\/strong> set the rather unknown <tt>autoload<\/tt> parameter to &#39;no&#39;.<\/p>\n<p>WordPress works like this: when initialized, it requires all its needed files, then does a <tt>\"SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'\"<\/tt>. This loads in memory *all* the options which have autoload set to (default value) &#39;yes&#39;.<\/p>\n<p>To specify that an item from the DB option must not be loaded on start, simply use:<\/p>\n<div id=\"ig-sh-1\" class=\"syntax_hilite\">\n\n\t\t<div class=\"toolbar\">\n\n\t\t<div class=\"view-different-container\">\n\t\t\t\t\t\t<a href=\"#\" class=\"view-different\">&lt; View <span>plain text<\/span> &gt;<\/a>\n\t\t\t\t\t<\/div>\n\n\t\t<div class=\"language-name\">php<\/div>\n\n\t\t\n\t\t<br clear=\"both\">\n\n\t<\/div>\n\t\n\t<div class=\"code\">\n\t\t<ol class=\"php\" style=\"font-family:monospace\"><li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">add_option<span style=\"color: #009900\">&#040;<\/span><span style=\"color: #0000ff\">'option_name'<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #0000ff\">'option_value'<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #0000ff\">''<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #0000ff\">'no'<\/span><span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span> <span style=\"color: #666666;font-style: italic\">\/\/ 'no' = not autoload<\/span><\/div><\/li>\n<\/ol>\t<\/div>\n\n<\/div>\n\n<h3>4. You create new tables for what?<\/h3>\n<p>WordPress comes with a <a href=\"http:\/\/codex.wordpress.org\/Database_Description\">several tables<\/a> for all the various tasks you might think of: users, meta informations about users, posts and their meta informations, etc..<\/p>\n<p>Now, of course, it&#39;s possible that your plugin needs to create one or more extra tables, but before doing so, think and be sure the existing tables just won&#39;t fit. Typically, consider using <tt>wp_options<\/tt> and the <tt>wp_*meta<\/tt> tables.<\/p>\n<p>If you *really* have to create tables, give them a name that makes sense. Always begin with <tt>$wpdb->prefix<\/tt>, use your plugin shortname, and append a word that will describe the table usage. Something like <tt>$wpdb->prefix.'myplugintitle_custom_logs'<\/tt>.<\/p>\n<h3>5. No uninstall function<\/h3>\n<p>WordPress since version 2.7 has implemented functions to uninstall plugins. There are two ways of doing so: using an uninstall hook, which some might find a bit complicated (well, it&#39;s not, really), and using an uninstall file, which is a no brainer dead easy solution, as <a href=\"http:\/\/jacobsantos.com\/2008\/general\/wordpress-27-plugin-uninstall-methods\/\">Jacob explains<\/a>.<\/p>\n<p>Now, having uninstall functions is not really something I consider a prerequisite or an absolute must have feature. It&#39;s just a nice touch to know that the plugin will leave no trails in the database once the user will want to get rid of it.<\/p>\n<p>This said, I find it totally unacceptable when a plugin creates a collection of option entries (see point #3) or extra tables (see point #4) and provide no automatic way of cleaning things up upon removal.<\/p>\n<h3>6. Custom javascript or CSS added on each and every admin pages<\/h3>\n<p>This one is an all time classic. When you create option pages for your plugin and will need custom javascript for them, please, please, pretty please, don&#39;t just hook your jQuery bits to <tt>admin_head<\/tt>. Inserting your javascript to all other pages *will* eventually break another plugin interface that also uses javascript, not to mention that it&#39;s useless.<\/p>\n<p>Inserting your script or CSS to your page <em>only<\/em> is <a href=\"http:\/\/planetozh.com\/blog\/2008\/04\/how-to-load-javascript-with-your-wordpress-plugin\/\">very easy<\/a>.<\/p>\n<h3>7. Plugin forms with no security, or nonces misunderstood<\/h3>\n<p>When an option form is submitted, a plugin should verify if the submitter has <strong>authority<\/strong> and <strong>intention<\/strong>. There&#39;s a <a href=\"http:\/\/markjaquith.wordpress.com\/2006\/06\/02\/wordpress-203-nonces\/\">very well explained article<\/a> from super star Mark Jaquith on this subject.<\/p>\n<p>The problem I&#39;ve seen too many times in the plugins I&#39;ve reviewed is that form data are handled and processed without even checking that <tt>is_admin()<\/tt> or that <tt>current_user_can()<\/tt>. Basically, this means that anyone (users with no authority) can POST data to your blog and play with the plugin.<\/p>\n<p>But even checking for authority can be insufficient. Imagine a plugin that would, say, <a href=\"http:\/\/wordpress.org\/extend\/plugins\/search.php?q=delete+posts\">delete posts<\/a>. It&#39;s trivial to make a webpage that sends POST data to someone else&#39;s blog plugin option page, and share with them a bitlyfied short URL of that webpage via Twitter. One click on it and you would be redirected to your own blog admin area where you do have authority, and delete your posts. You have authority, but no intention.<\/p>\n<p>This is the issue <strong>nonces<\/strong> address. Nonce functions generate unique and temporary timestamps that are impossible to guess, and check that data submitted come from a particular page from *your* admin area, not just from anywhere on the interwebs.<\/p>\n<p>A very common mistake I&#39;ve seen in a lot of plugin is simply adding a nonce field to the form. This won&#39;t work, it is not enough. You need nonce fields on the form, and nonce checks where the form is processed, otherwise it&#39;s just like telling &quot;everybody needs an ID card to get into my bar&quot; but never check them.<\/p>\n<p>Read more about <a href=\"http:\/\/codex.wordpress.org\/WordPress_Nonces\">nonces<\/a> and how to implement them. Or even better: read this article on <a href=\"http:\/\/planetozh.com\/blog\/2009\/05\/handling-plugins-options-in-wordpress-28-with-register_setting\/\">how to deal with options in WP 2.8<\/a>, and learn how to both correctly store all your options in a single DB entry and implement security in your forms, using just one function call.<\/p>\n<h3>8. Actions triggered from unchecked GET data<\/h3>\n<p>I&#39;ve seen similar constructs to the following block more than once, and that&#39;s a bit scary:<\/p>\n<div id=\"ig-sh-2\" class=\"syntax_hilite\">\n\n\t\t<div class=\"toolbar\">\n\n\t\t<div class=\"view-different-container\">\n\t\t\t\t\t\t<a href=\"#\" class=\"view-different\">&lt; View <span>plain text<\/span> &gt;<\/a>\n\t\t\t\t\t<\/div>\n\n\t\t<div class=\"language-name\">php<\/div>\n\n\t\t\n\t\t<br clear=\"both\">\n\n\t<\/div>\n\t\n\t<div class=\"code\">\n\t\t<ol class=\"php\" style=\"font-family:monospace\"><li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">add_action<span style=\"color: #009900\">&#040;<\/span><span style=\"color: #0000ff\">'init'<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #0000ff\">'myplugin_init'<\/span><span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #000000;font-weight: bold\">function<\/span> myplugin_init<span style=\"color: #009900\">&#040;<\/span><span style=\"color: #009900\">&#041;<\/span> <span style=\"color: #009900\">&#123;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp; &nbsp; <span style=\"color: #000000;font-weight: bold\">global<\/span> <span style=\"color: #000088\">$wpdb<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp; &nbsp; <span style=\"color: #990000\">call_user_func<\/span><span style=\"color: #009900\">&#040;<\/span><span style=\"color: #000088\">$_GET<\/span><span style=\"color: #009900\">&#091;<\/span><span style=\"color: #0000ff\">'action'<\/span><span style=\"color: #009900\">&#093;<\/span><span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp; &nbsp; <span style=\"color: #000088\">$wpdb<\/span><span style=\"color: #339933\">-&gt;<\/span><span style=\"color: #004000\">query<\/span><span style=\"color: #009900\">&#040;<\/span><span style=\"color: #0000ff\">'DELETE from sometable WHERE somefield = '<\/span><span style=\"color: #339933\">.<\/span> <span style=\"color: #000088\">$_GET<\/span><span style=\"color: #009900\">&#091;<\/span><span style=\"color: #0000ff\">'value'<\/span><span style=\"color: #009900\">&#093;<\/span><span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #009900\">&#125;<\/span><\/div><\/li>\n<\/ol>\t<\/div>\n\n<\/div>\n\n<p>This case is similar to the previous one: use an unchecked request (<tt>yoursite.com?action=bleh&value=wot<\/tt>) but this time to execute code or even perform SQL queries. Omit to implement security in this case is highly critical since you don&#39;t even have to trick a blog owner into clicking on a URL that sends them to their own admin: just anyone can do it.<\/p>\n<p>Once again, nonce functions are what you need. If you want to use <tt>$_GET['action']<\/tt> as the switch for your action, please do this:<\/p>\n<ul>\n<li>Instead of sending user to <tt>site.com\/?action=something<\/tt>, use the following code:\n<div id=\"ig-sh-3\" class=\"syntax_hilite\">\n\n\t\t<div class=\"toolbar\">\n\n\t\t<div class=\"view-different-container\">\n\t\t\t\t\t\t<a href=\"#\" class=\"view-different\">&lt; View <span>plain text<\/span> &gt;<\/a>\n\t\t\t\t\t<\/div>\n\n\t\t<div class=\"language-name\">php<\/div>\n\n\t\t\n\t\t<br clear=\"both\">\n\n\t<\/div>\n\t\n\t<div class=\"code\">\n\t\t<ol class=\"php\" style=\"font-family:monospace\"><li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #000088\">$url<\/span> <span style=\"color: #339933\">=<\/span> <span style=\"color: #0000ff\">&quot;site.com\/?action=something&quot;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #000088\">$action<\/span> <span style=\"color: #339933\">=<\/span> <span style=\"color: #0000ff\">&quot;myplugin-update&quot;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #000088\">$link<\/span> <span style=\"color: #339933\">=<\/span> wp_nonce_url<span style=\"color: #009900\">&#040;<\/span> <span style=\"color: #000088\">$url<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #000088\">$action<\/span> <span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #b1b100\">echo<\/span> <span style=\"color: #0000ff\">&quot;&lt;a href='<span style=\"color: #006699;font-weight: bold\">$link<\/span>'&gt;click here&lt;\/a&gt;&quot;<\/span><span style=\"color: #339933\">;<\/span> <span style=\"color: #666666;font-style: italic\">\/\/ whatever you're doing to echo the nonced link<\/span><\/div><\/li>\n<\/ol>\t<\/div>\n\n<\/div>\n\n<\/li>\n<li>In the function triggered by the <tt>$_GET<\/tt> parameter, do:\n<div id=\"ig-sh-4\" class=\"syntax_hilite\">\n\n\t\t<div class=\"toolbar\">\n\n\t\t<div class=\"view-different-container\">\n\t\t\t\t\t\t<a href=\"#\" class=\"view-different\">&lt; View <span>plain text<\/span> &gt;<\/a>\n\t\t\t\t\t<\/div>\n\n\t\t<div class=\"language-name\">php<\/div>\n\n\t\t\n\t\t<br clear=\"both\">\n\n\t<\/div>\n\t\n\t<div class=\"code\">\n\t\t<ol class=\"php\" style=\"font-family:monospace\"><li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #b1b100\">if<\/span> <span style=\"color: #009900\">&#040;<\/span> <span style=\"color: #990000\">isset<\/span><span style=\"color: #009900\">&#040;<\/span> <span style=\"color: #000088\">$_GET<\/span><span style=\"color: #009900\">&#091;<\/span><span style=\"color: #0000ff\">'action'<\/span><span style=\"color: #009900\">&#093;<\/span> <span style=\"color: #009900\">&#041;<\/span> <span style=\"color: #339933\">&amp;&amp;<\/span> <span style=\"color: #000088\">$_GET<\/span><span style=\"color: #009900\">&#091;<\/span><span style=\"color: #0000ff\">'action'<\/span><span style=\"color: #009900\">&#093;<\/span> <span style=\"color: #339933\">==<\/span> <span style=\"color: #0000ff\">'something'<\/span> <span style=\"color: #009900\">&#041;<\/span> <span style=\"color: #009900\">&#123;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp; &nbsp; check_admin_referer<span style=\"color: #009900\">&#040;<\/span> <span style=\"color: #0000ff\">'myplugin-update'<\/span> <span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span> <span style=\"color: #666666;font-style: italic\">\/\/ die if invalid or missing nonce<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp; &nbsp;<\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp; &nbsp; <span style=\"color: #666666;font-style: italic\">\/\/ rest of the code ...<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #009900\">&#125;<\/span><\/div><\/li>\n<\/ol>\t<\/div>\n\n<\/div>\n\n<\/li>\n<\/ul>\n<p>See? Nonces are simple. One function call in the form or the link, one function call in the processing part.<\/p>\n<p>There are also other functions that might be required or just sensible to use here: <tt>is_admin()<\/tt> to make sure we&#39;re in the admin area, <tt>current_user_can()<\/tt> to make sureprivileges are sufficient.<\/p>\n<h3>9. Trust user input and pass it to SQL<\/h3>\n<p>This one is the most critical security hole you can craft, especially when used in conjunction with point #8, as I&#39;ve seen in two plugins. Whenever you&#39;re performing SQL queries containing user input data, <strong>validate<\/strong> them. The risk here is <a href=\"http:\/\/en.wikipedia.org\/wiki\/SQL_injection\">SQL injection<\/a>.<\/p>\n<p>If you&#39;re passing a parameter that&#39;s supposed to be an integer, use <tt>intval()<\/tt> before storing it in the DB. If you&#39;re allowing HTML, <tt>esc_attr()<\/tt> it. If you&#39;re expecting a string, <tt>preg_replace('\/[^a-zA-Z]\/', '')<\/tt> it. And so on.<\/p>\n<p>Once the data for your SQL query is validated, send the query string through <tt>$wpdb->prepare()<\/tt> before running it. Method <tt>prepare()<\/tt> is similar in use to <tt>sprintf()<\/tt>, and handles for you all the escaping, quoting and integer casting you&#39;ll need. It&#39;s as simple as:<\/p>\n<div id=\"ig-sh-5\" class=\"syntax_hilite\">\n\n\t\t<div class=\"toolbar\">\n\n\t\t<div class=\"view-different-container\">\n\t\t\t\t\t\t<a href=\"#\" class=\"view-different\">&lt; View <span>plain text<\/span> &gt;<\/a>\n\t\t\t\t\t<\/div>\n\n\t\t<div class=\"language-name\">php<\/div>\n\n\t\t\n\t\t<br clear=\"both\">\n\n\t<\/div>\n\t\n\t<div class=\"code\">\n\t\t<ol class=\"php\" style=\"font-family:monospace\"><li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #000088\">$calvin<\/span> <span style=\"color: #339933\">=<\/span> <span style=\"color: #0000ff\">&quot;6 years&quot;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #000088\">$hobbes<\/span> <span style=\"color: #339933\">=<\/span> <span style=\"color: #0000ff\">&quot;stuffed&quot;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp;<\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #666666;font-style: italic\">\/\/ &quot;Prepare&quot; the query<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #000088\">$sql<\/span> <span style=\"color: #339933\">=<\/span> <span style=\"color: #000088\">$wpdb<\/span><span style=\"color: #339933\">-&gt;<\/span><span style=\"color: #004000\">prepare<\/span><span style=\"color: #009900\">&#040;<\/span> <span style=\"color: #0000ff\">&quot;INSERT INTO <span style=\"color: #006699;font-weight: bold\">$wpdb-&gt;joeplugin_table<\/span>( id, field1, field2 ) VALUES ( <span style=\"color: #009933;font-weight: bold\">%d<\/span>, <span style=\"color: #009933;font-weight: bold\">%s<\/span>, <span style=\"color: #009933;font-weight: bold\">%s<\/span> )&quot;<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #000088\">$_POST<\/span><span style=\"color: #009900\">&#091;<\/span><span style=\"color: #0000ff\">'id'<\/span><span style=\"color: #009900\">&#093;<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #000088\">$calvin<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #000088\">$hobbes<\/span> <span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp;<\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #666666;font-style: italic\">\/\/ Run it<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #000088\">$wpdb<\/span><span style=\"color: #339933\">-&gt;<\/span><span style=\"color: #004000\">query<\/span><span style=\"color: #009900\">&#040;<\/span> <span style=\"color: #000088\">$sql<\/span> <span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<\/ol>\t<\/div>\n\n<\/div>\n\n<p>If your plugin is going to play with MySQL, make sure you grok the whole <a href=\"http:\/\/codex.wordpress.org\/Function_Reference\/wpdb_Class\">$wpdb class<\/a>.<\/p>\n<h3>10. Localization done wrong<\/h3>\n<p>This last one has to be the most frequent code error I&#39;ve encountered: thinking <tt>__('some string')<\/tt> will be enough to make a plugin translatable.<\/p>\n<p>The correct syntax to use for a plugin to support localization is <tt>__('some string', 'myplugin')<\/tt> where &#39;myplugin&#39; is a unique identifier to the text domain, which has to be initialized with <tt>load_plugin_textdomain()<\/tt><\/p>\n<p>Complete example with a subdirectory &#39;<tt>translations\/<\/tt>&#39; in your plugin directory:<\/p>\n<div id=\"ig-sh-6\" class=\"syntax_hilite\">\n\n\t\t<div class=\"toolbar\">\n\n\t\t<div class=\"view-different-container\">\n\t\t\t\t\t\t<a href=\"#\" class=\"view-different\">&lt; View <span>plain text<\/span> &gt;<\/a>\n\t\t\t\t\t<\/div>\n\n\t\t<div class=\"language-name\">php<\/div>\n\n\t\t\n\t\t<br clear=\"both\">\n\n\t<\/div>\n\t\n\t<div class=\"code\">\n\t\t<ol class=\"php\" style=\"font-family:monospace\"><li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">add_action<span style=\"color: #009900\">&#040;<\/span><span style=\"color: #0000ff\">'init'<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #0000ff\">'myplugin_load_translation_file'<\/span><span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp;<\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #000000;font-weight: bold\">function<\/span> myplugin_load_translation_file<span style=\"color: #009900\">&#040;<\/span><span style=\"color: #009900\">&#041;<\/span> <span style=\"color: #009900\">&#123;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp; &nbsp; <span style=\"color: #666666;font-style: italic\">\/\/ relative path to WP_PLUGIN_DIR where the translation files will sit:<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp; &nbsp; <span style=\"color: #000088\">$plugin_path<\/span> <span style=\"color: #339933\">=<\/span> plugin_basename<span style=\"color: #009900\">&#040;<\/span> <span style=\"color: #990000\">dirname<\/span><span style=\"color: #009900\">&#040;<\/span> <span style=\"color: #009900;font-weight: bold\">__FILE__<\/span> <span style=\"color: #009900\">&#041;<\/span> <span style=\"color: #339933\">.<\/span><span style=\"color: #0000ff\">'\/translations'<\/span> <span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\">&nbsp; &nbsp; load_plugin_textdomain<span style=\"color: #009900\">&#040;<\/span> <span style=\"color: #0000ff\">'myplugin'<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #0000ff\">''<\/span><span style=\"color: #339933\">,<\/span> <span style=\"color: #000088\">$plugin_path<\/span> <span style=\"color: #009900\">&#041;<\/span><span style=\"color: #339933\">;<\/span><\/div><\/li>\n<li style=\"font-weight: normal;vertical-align:top\"><div style=\"font: normal normal 1em\/1.2em monospace;margin:0;padding:0;background:none;vertical-align:top\"><span style=\"color: #009900\">&#125;<\/span><\/div><\/li>\n<\/ol>\t<\/div>\n\n<\/div>\n\n<p>For a comprehensive tutorial on plugin translation, I suggest your <a href=\"http:\/\/urbangiraffe.com\/articles\/localizing-wordpress-themes-and-plugins\/\">read this one<\/a>. There&#39;s also a page on the Codex about <a href=\"http:\/\/codex.wordpress.org\/I18n_for_WordPress_Developers\">I18n for WordPress Developers<\/a> that you should read.<\/p>\n<h2>Design decisions that suck<\/h2>\n<p>During my reviewing of all the plugins, I&#39;ve found numerous bits of code that are not exactly <em>bad<\/em> (and can even be smart, actually) but are simply a bad idea because one day or another it will fail on someone&#39;s setup, or just because there are easier ways to go.<\/p>\n<h3>Unadvertised or unchecked PHP5+ functions<\/h3>\n<p>As of writing, WordPress has pretty loose requirements, namely PHP 4.3. I&#39;m not sure how many people are still running PHP 4.x, but there are. It&#39;s tempting to use PHP5 functions such as <tt>json_encode()<\/tt>, and that&#39;s OK, but in such a case, you need to either warn the user on the plugin&#39;s page (&quot;This plugin requires PHP5&quot;), or make your plugin check the environment and tell the user it&#39;s not going to run as expected.<\/p>\n<h3>Extension required. Is it available?<\/h3>\n<p>A lot of plugins I&#39;ve looked at require PHP&#39;s CURL extension, I&#39;ve also seen one needing <tt>mbstring<\/tt> functions, and yet they don&#39;t check if it&#39;s present. This is similar to the previous point: you have to tell the user, or make your code check that everything is OK prior to do anything.<\/p>\n<h3>Wheel reinvented<\/h3>\n<p>Speaking of cURL, why are you still using it? It&#39;s awesome and everything, but it totally might be unavailable. What&#39;s the point in writing a 10 line code block to fetch remote content while a single call to <tt>wp_remote_get()<\/tt> is enough, and will work even if there&#39;s no cURL?<\/p>\n<p>WordPress has a number of internal functions to make your code faster to write and more compatible with every setup. I&#39;ve written for instance about <a href=\"http:\/\/planetozh.com\/blog\/2009\/08\/how-to-make-http-requests-with-wordpress\/\">making HTTP requests<\/a> the easy way, <a href=\"http:\/\/planetozh.com\/blog\/2009\/05\/handling-plugins-options-in-wordpress-28-with-register_setting\/\">managing options<\/a> without mucking with <tt>$_POST<\/tt>, but there&#39;s also built-in Ajax functions and hooks, and more.<\/p>\n<p>Whenever you&#39;re going to write a code block that seems to be quite generic and common, check first  if there&#39;s a WP function that can do it for you.<\/p>\n<h3>Compatibility maintained with deprecated versions of WordPress<\/h3>\n<p>I&#39;ve seen code comments mentioning stuff like &quot;\/\/ we&#39;re doing this for people using WP 2.5&quot;. This one is more a personal choice, but I think maintaining compatibility with older versions is a <em>terrible idea<\/em>.<\/p>\n<p>Terrible for you: fixing bugs and implementing new features is quite a task already, don&#39;t add to the burden with more deprecated code.<\/p>\n<p>Terrible for the users: it&#39;s nice that your plugin is going to run fine on their obsolete, insecure and already hacked blog, but it really does not motivate them to upgrade, which is vital.<\/p>\n<p>I know I&#39;ve dropped backward compat with my plugins a long time ago, and always code for the latest release available. It makes life so much easier :)<\/p>\n<h3>Hardcoded paths<\/h3>\n<p>Since several point releases, things might not be where you think they are: <tt>wp-config.php<\/tt> gone up a directory, or the whole <tt>wp-content<\/tt> directory moved somewhere else. I&#39;ve also seen users <em>rename a plugin&#39;s directory<\/em>.<\/p>\n<p>In any of these edge cases, your plugin will break if you&#39;re relying on hardcoded path. Use <tt>WP_PLUGIN_DIR<\/tt>, <tt>WP_PLUGIN_URL<\/tt>, <tt>plugin_basename( __FILE__ )<\/tt><\/p>\n<h3>All files included, even if not needed<\/h3>\n<p>Make your life easier: split your plugin in several smaller chunks, put files in various directories instead of dropping everything in the same folder (<tt>includes\/<\/tt>, <tt>css\/<\/tt>, <tt>translations\/<\/tt>, etc&#8230;) and include only what you need. Maybe <tt>!is_admin()<\/tt>? Then don&#39;t <tt>require_once()<\/tt> all the stuffs that create the option forms.<\/p>\n<h3>User left alone in the dark if something goes wrong<\/h3>\n<p>Once you&#39;re done with your neat functions that send stuff to Twitter, ask yourself: what&#39;s going to happen if Twitter is down?<\/p>\n<p>Nothing is more frustrating than users coming to your site and asking for support because the plugin did not work and <tt>they just cannot tell what went wrong<\/tt>. Anywhere possible, try to add diagnosis functions and messages, check results of operations and warn the user if something looks like things failed.<\/p>\n<h3>No download link on the plugin&#39;s page<\/h3>\n<p>Yeah, this last one sounds like a joke, but it&#39;s not. I&#39;ve seen it <a href=\"http:\/\/planetozh.com\/blog\/2008\/08\/2008-plugin-competition-review-part-one\/\">last<\/a> <a href=\"http:\/\/planetozh.com\/blog\/2008\/08\/2008-plugin-competition-review-part-2-of-2\/\">year<\/a>, I&#39;ve seen it this year again: people make a plugin, write a nice page about it, and don&#39;t tell how to download it. Please make sure the download link is unmissable :)<\/p>\n<h2>And that&#39;s it<\/h2>\n<p>That was a way too long article for such basic tips :)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Short intro for readers who don&#39;t follow me or this blog&#39;s feed: I&#39;ve been a judge in the annual WordPress Plugin Competition, and as such I have reviewed a number of plugins. Read more about this. As promised, I&#39;m going to share a list of the most common mistakes, errors, misunderstandings, bad habits or wrong [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[2,259,363,85,63,245],"class_list":["post-1321","post","type-post","status-publish","format-standard","hentry","category-published","tag-code","tag-competition","tag-errors","tag-plugins","tag-review","tag-wordpress"],"_links":{"self":[{"href":"https:\/\/planetozh.com\/blog\/wp-json\/wp\/v2\/posts\/1321","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/planetozh.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/planetozh.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/planetozh.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/planetozh.com\/blog\/wp-json\/wp\/v2\/comments?post=1321"}],"version-history":[{"count":0,"href":"https:\/\/planetozh.com\/blog\/wp-json\/wp\/v2\/posts\/1321\/revisions"}],"wp:attachment":[{"href":"https:\/\/planetozh.com\/blog\/wp-json\/wp\/v2\/media?parent=1321"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/planetozh.com\/blog\/wp-json\/wp\/v2\/categories?post=1321"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/planetozh.com\/blog\/wp-json\/wp\/v2\/tags?post=1321"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}