<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule"
>

<channel>
	<title>Will Norris &#187; pet-peeve</title>
	<atom:link href="http://willnorris.com/tag/pet-peeve/feed" rel="self" type="application/rss+xml" />
	<link>http://willnorris.com</link>
	<description>there&#039;s more to life than this</description>
	<lastBuildDate>Sun, 05 Feb 2012 17:41:44 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.4-alpha-19719</generator>
<creativeCommons:license>http://creativecommons.org/licenses/by-nc-sa/3.0/</creativeCommons:license>
		<item>
		<title>WordPress Plugin Pet Peeve #3: Not being extensible</title>
		<link>http://willnorris.com/2009/06/wordpress-plugin-pet-peeve-3-not-being-extensible</link>
		<comments>http://willnorris.com/2009/06/wordpress-plugin-pet-peeve-3-not-being-extensible#comments</comments>
		<pubDate>Thu, 04 Jun 2009 15:53:25 +0000</pubDate>
		<dc:creator>Will Norris</dc:creator>
				<category><![CDATA[technology]]></category>
		<category><![CDATA[pet-peeve]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[wordpress]]></category>

		<guid isPermaLink="false">http://willnorris.com/?p=671</guid>
		<description><![CDATA[So this is one that is incredibly easy to implement, and yet goes a really long way in keeping people happy with your plugin. The very reason that WordPress has a plugin API is because they know that different people want different things from their blog. Some people are satisfied with just the core functionality [...]]]></description>
			<content:encoded><![CDATA[<p>So this is one that is incredibly easy to implement, and yet goes a <strong>really</strong> long way in keeping people happy with your plugin.  The very reason that WordPress has a <a href="http://codex.wordpress.org/Plugin_API">plugin API</a> is because they know that different people want different things from their blog.  Some people are satisfied with just the core functionality that WordPress provides, but most people want a little more.  Perhaps they are using WordPress as a photo blog, and need better support for handling that kind of data.  Perhaps they need WordPress to integrate with some other system, or pull in data from elsewhere.  Whatever their needs, there is no possible way WordPress could address them all in the core package.  And even if they were to try, people have different ideas on how the same feature should be implemented, so there would be no way to please everyone.  And unless your plugin is very simple, I can pretty well assure you that some of your users probably want it to do something more than it does today, or do it in a slightly different way.</p>

<p>The very same mechanisms that makes WordPress extensible can be used to make your plugin extensible as well.  Most of the time this will mean action and filter hooks.  As a plugin author, you are no doubt familiar with how these hooks work, since your plugin is using them to hook into WordPress.  But what some newer developers may not realize is just how easy it is to provide your own hooks to extend the functionality of your plugin.</p>

<p><span id="more-671"></span></p>

<h2>Where to add hooks</h2>

<p>When you&#8217;re getting started with your plugin it may not be obvious where to add hooks, and that&#8217;s okay.  If you already have a number of people using your plugin, then perhaps you may have already received requests from people wanting to add some kind of functionality to the plugin or change how something works.  Oftentimes these changes are appropriate to add directly into your plugin, and you are most certainly encouraged to do so when it makes sense.  But sometimes the request represents an edge-case&#8230; something that one individual is wanting to do, but is very likely <em>not</em> going to be useful to most people using your plugin.  Even worse, making such a change may be confusing or even undesirable by other users.  In these cases, see if you can provide the appropriate hooks to allow people to extend your plugin to suit their needs.</p>

<p><a href="http://wordpress.org/extend/plugins/extended-profile/">Extended Profile</a> is a good example which provides quite a few hooks for people to extend.  One of the core features of the plugin is that it allows you to output an hCard Profile on a page, like I have on the front page of <a href="http://willnorris.com/">willnorris.com</a>.  The plugin provides most of the common hCard attributes that we thought an individual might be interested in like name, website, address, telephone number, etc. To collect this data from the user, the plugin extends the standard WordPress Profile administration page.  But we knew that users might already have a different plugin capable of providing this data, or that they may want to pull the data form some other source. Additionally, the <a href="http://microformats.org/wiki/hCard">hCard microformat</a> supports a number of other attributes that we didn&#8217;t include in the plugin.  In order to support these use-cases, we used a combination of action and filter hooks (mostly filters, in our case) to allow the hCard profile to be supplemented, or outright replaced. This is actually how I added the &#8220;Last Seen&#8221; geo data to my hCard, which pulls data from <a href="http://brightkite.com/">Brightkite</a>.</p>

<p>As a general rule of thumb, action hooks are used to alert other plugins that you are at a particular point within a process.  Perhaps that you&#8217;re about to begin doing something, or that you&#8217;ve just finished doing something which other code may care about.  If you&#8217;re printing things to the screen, an action hook may be a good place to allow other plugins to print things as well at a very particular point in your plugin&#8217;s processing.</p>

<p>Filters are intended to allow other pieces of code to actually modify something that your plugin has done.  As mentioned above, the Extended Profile plugin makes heavy use of filters to allow other plugins to modify various aspects of the generated hCard before it is printed to the screen.  In fact, the plugin hooks into its own filters in order to provide most of the functionality, just like any other plugin would.</p>

<p>That last point is worth underscoring: <strong>hooks are not just for other plugins to use, you should be using them yourself</strong>.  WordPress makes extensive use of its own actions and filters, just take a look at <a href="http://core.trac.wordpress.org/browser/trunk/wp-includes/default-filters.php">default-filters.php</a>.  This has a number of benefits &#8212; it allows you to keep your own code fairly clean and modular, but perhaps more importantly it allows other plugins to actually <em>remove</em> functionality they don&#8217;t want using the <code>remove_action</code> and <code>remove_filter</code> methods. When it comes to making your plugin extensible, being able to remove functionality is just as important as the ability to add it.</p>

<h2>How to add hooks</h2>

<p>Adding new action or filter hooks is deceptively simple &#8212; there is no registration of hooks, you simply call <code>do_action</code> or <code>apply_filters</code> as needed.  A simplified version of what happens in the Extended Profile plugin might look like this:</p>

<pre><code>function get_extended_profile($user_id) {
    // start output buffering
    ob_start();

    // call the action hook which actually builds the profile
    do_action('extended_profile', $user_id);

    // get buffered content
    $profile = '&lt;div id="profile"&gt;' . ob_get_contents() . '&lt;/div&gt;';
    ob_end_clean();

    // filter everything before returning
    return apply_filters('post_extended_profile', $profile, $user_id);
}
</code></pre>

<p>Here you can see both a custom action and filter being used, first to build the profile and then to filter it.  When adding hooks to your plugin, its important to think about what data those hook functions may need in order to do something useful.  For example, functions which hook into the &#8216;extended&#95;profile&#8217; action need to know the ID of the user the profile is being built for so that they can fetch the appropriate data.  The same user ID is also passed to the &#8216;post&#95;extended&#95;profile&#8217; filter for the same reason.  With filters it&#8217;s important to remember that only the first parameter passed into the hooking functions gets filtered.  You can certainly pass other data as well to provide context, but only the first one is filtered (which is actually the second parameter passed to <code>apply_filters</code>, since the first one is the filter name).</p>

<p>The real beauty of WordPress action and filter hooks is in how lightweight the entire system is.  Calling a hook that has no registered functions returns almost immediately.  And registering a function to a non-existent hook has basically no overhead, and absolutely no consequence&#8230; the function simply never gets executed.  While you should certainly think through your plugin and add hooks at the logical points, don&#8217;t worry too much about adding too many.  Consider this: on a standard, out of the box WordPress installation with no plugins activated, a page load can easily cause over 1,000 calls to <code>do_action</code> and <code>apply_filter</code>.  Having your plugin add in a handful more is not going to have any kind of adverse effect on WordPress performance, and you will potentially make your plugin users much happier.</p>

<h2>Custom Constants</h2>

<p>Another way to expose additional functionality within your plugin is through the use of custom constants.  This is generally most appropriate to enable advanced functionality that you don&#8217;t want to expose through the normal WordPress option pages.  For example, suppose you have a plugin which makes API calls to some service.  The URL for that API endpoint is constant, but you might want to allow it to be overridden for testing purposes.  Use a PHP constant for your API URL, but check to see if it is already defined:</p>

<pre><code>// near the beginning of your plugin
if ( !defined('MY_PLUGIN_API_URL') ) {
    define('MY_PLUGIN_API_URL', 'http://api.example.com/service'); // default value
}

// throughout the rest of your plugin, always use the constant
file_get_contents( MY_PLUGIN_API_URL . '?user=joe' );
</code></pre>

<p>In this way, the API URL can be overridden by defining the constant in <code>wp-config.php</code>:</p>

<pre><code>define('MY_PLUGIN_API_URL', 'http://testing.example.net/test-service');
</code></pre>

<p>This is not as common as using action and filter hooks, but is still useful especially when you want to ensure that other plugins are not able to manipulate the value.  Unless you do some additional checking, you never know what functions may be hooking into your filter or what they&#8217;re going to do with the value&#8230; in fact that&#8217;s kind of the point, you&#8217;re not supposed to know or care.  But in cases where you want to allow the user to define a custom value, but also ensure that other plugins don&#8217;t manipulate the data in any way, constants are the way to go.</p>

<p>A final note if you do end up adding hooks or constants to your plugin.  Because so many plugins <strong>aren&#8217;t</strong> able to be extended in these ways, it is well worth drawing attention to the fact in your plugin&#8217;s readme.txt.  You can even go so far as to document all of the extension points as I did <a href="http://wiki.diso-project.org/wordpress-openid-api">with the OpenID plugin</a>, but you don&#8217;t necessarily have to.  Just pointing out that hooks exist is often enough, developers can quickly scan your code for them.</p>
]]></content:encoded>
			<wfw:commentRss>http://willnorris.com/2009/06/wordpress-plugin-pet-peeve-3-not-being-extensible/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>WordPress Plugin Pet Peeve #2: Direct Calls to Plugin Files</title>
		<link>http://willnorris.com/2009/06/wordpress-plugin-pet-peeve-2-direct-calls-to-plugin-files</link>
		<comments>http://willnorris.com/2009/06/wordpress-plugin-pet-peeve-2-direct-calls-to-plugin-files#comments</comments>
		<pubDate>Tue, 02 Jun 2009 20:05:45 +0000</pubDate>
		<dc:creator>Will Norris</dc:creator>
				<category><![CDATA[technology]]></category>
		<category><![CDATA[pet-peeve]]></category>
		<category><![CDATA[plugins]]></category>
		<category><![CDATA[wordpress]]></category>

		<guid isPermaLink="false">http://willnorris.com/?p=637</guid>
		<description><![CDATA[This is actually very similar to my first pet peeve of hardcoding the path to wp-content, in that it makes assumptions about where files are placed on the filesystem. Oftentimes, plugins need to handle certain kinds of requests, maybe for some specific protocol, or to handle an AJAX request. Some plugins will do this by [...]]]></description>
			<content:encoded><![CDATA[<p>This is actually very similar to my first pet peeve of <a href="http://willnorris.com/2009/05/wordpress-plugin-pet-peeve-hardcoding-wp-content">hardcoding the path to wp-content</a>, in that it makes assumptions about where files are placed on the filesystem.  Oftentimes, plugins need to handle certain kinds of requests, maybe for some specific protocol, or to handle an AJAX request.  Some plugins will do this by making an HTTP request directly to one of the files in the plugin&#8230; something like:</p>

<pre><code>echo '&lt;script type="text/javascript"&gt;
    jQuery.get("' . plugins_url('my-plugin/ajax-handler.php') . '");
    // do something with AJAX data ...
&lt;/script&gt;';
</code></pre>

<p>This is not a problem in and of itself, in fact it&#8217;s great that the plugin developer is actually using the <code>plugins_url</code> function!  The problem arises in the <code>my-plugin/ajax-handler.php</code> file itself.  <span id="more-637"></span> If that plugin needs to make use of any WordPress data or functions, then the developer is certain to do something very ugly.  You&#8217;ll know it when you see it:</p>

<pre><code>require_once('../../../wp-load.php');

// or sometimes you'll see...
require_once('../../../wp-config.php');
</code></pre>

<p>So what is this, and why is it so bad?  Well, the <code>wp-load.php</code> is a file provided by WordPress core that bootstraps the WordPress environment.  It loads in the <code>wp-config.php</code> file, loads all the common WordPress functions and classes, initializes the database connection, and gets everything in place to process the request.  These are all important things, and provides a lot of functionality that the plugin developer may need.  The problem is that in the <code>require_once</code> call above, the plugin developer is assuming that the <code>wp-load.php</code> file is in a directory exactly three levels up in the filesystem from their plugin directory.  In a standard WordPress deployment, this will be true and everything will work fine (for the most part).  But <a href="http://willnorris.com/2009/05/wordpress-plugin-pet-peeve-hardcoding-wp-content">as we&#8217;ve already seen</a>, WordPress allows deployers to move where their <code>wp-content</code> or their plugins directory lives, so the above code will break completely.  Yes, there are files in WordPress core that do something similar to the above, for example <code>wp-admin/admin.php</code>.  But these are safe for WordPress core files because they <strong>do</strong> know where certain files will be.  It is <strong>never</strong> safe to make this assumption from a plugin.  I think it&#8217;s pretty safe to say that if you have a <code>require</code> or <code>include</code> call that goes up more than a couple of directories (&#8220;<code>../../</code>&#8221;), you&#8217;re almost certainly doing it wrong.</p>

<p>A couple of points to emphasize before moving on: if your plugin files are <strong>not</strong> accessing any built-in WordPress functions, classes, or data, calling them directly should be just fine.  Also, if you&#8217;re doing <a href="http://codex.wordpress.org/AJAX_in_Plugins#Ajax_on_the_Administration_Side">AJAX calls on admin pages</a>, use the built-in functionality WordPress core provides.</p>

<h3>The Right Way</h3>

<p>So the right way of doing this is actually pretty straightforward conceptually, but in practice there are a couple of ways to do it depending on your needs.  The short answer is that instead of calling your plugin file directly, you must send the request through the standard WordPress request handling mechanisms.  This requires two things: constructing your request properly, and then hooking into the WordPress request handling code at the appropriate time to take over the request handling yourself.</p>

<p>All standard WordPress requests eventually get broken down to a request to <code>/index.php</code> with a bunch of URL parameters.  For example, when someone goes to your blog post at</p>

<pre><code>http://example.com/2009/01/hello-world
</code></pre>

<p>WordPress uses the configured permalink structure to break this down to</p>

<pre><code>http://example.com/index.php?year=2008&amp;monthnum=01&amp;name=hello-world
</code></pre>

<p>And from that, WordPress can determine which post the request is for, and serve that up accordingly.  So what we want to do is be able to construct a request along the lines of</p>

<pre><code>http://example.com/index.php?my-plugin=ajax-handler
</code></pre>

<p>and then hook into the WordPress request handling logic so that we can process this request ourselves.  Fortunately, WordPress provides an action hook for exactly that purpose, called <code>process_request</code>.  Functions that hook into this action are passed one parameter, an instance of the <code>WP</code> class, which encapsulates most of the specific parameters for the current request.  If we want to process only the requests which include <code>my-plugin=ajax-handler</code>, we would add something like this to the plugin:</p>

<pre><code>function my_plugin_parse_request($wp) {
    // only process requests with "my-plugin=ajax-handler"
    if (array_key_exists('my-plugin', $wp-&gt;query_vars) 
            &amp;&amp; $wp-&gt;query_vars['my-plugin'] == 'ajax-handler') {

        // process the request.
        // For now, we'll just call wp_die, so we know it got processed
        wp_die('my-plugin ajax-handler!');
    }
}
add_action('parse_request', 'my_plugin_parse_request');
</code></pre>

<p>If you&#8217;re following along at home, try accessing <code>http://example.com/index.php?my-plugin=ajax-handler</code>.  What happens?  Most likely, nothing at all&#8230; it still loads your normal blog index page.  So what went wrong?  In order to have the WordPress request handling code process custom URL parameters, we have to register them.  This is necessary for a number of reasons including security, performance, and also to ensure that WordPress doesn&#8217;t accidentally process something it wasn&#8217;t meant to.  Fortunately, registering a new query variable is very simple using the <code>query_vars</code> filter hook:</p>

<pre><code>function my_plugin_query_vars($vars) {
    $vars[] = 'my-plugin';
    return $vars;
}
add_filter('query_vars', 'my_plugin_query_vars');
</code></pre>

<p>This simply registers &#8216;my-plugin&#8217; as a valid query variable to be processed by WordPress, but says nothing about valid values for that variable.  So now try reloading the page, and you should be greeted with a simple styled page reading &#8220;my-plugin ajax-handler!&#8221;.  Now you just need to modify the <code>my_plugin_parse_request</code> function to actually do your custom request logic, and you&#8217;re good to go.</p>

<p>I mentioned earlier that there are a couple of ways to do this.  The one caveat to be aware of with the above approach is that you are hooking into the WordPress request processing logic <strong>before</strong> WordPress has processed the query itself.  WordPress&#8217;s query handling logic is responsible for examining the request and figuring out what page or post within WordPress the request maps to.  This is also what makes the <code>is_*</code> functions work: <code>is_page</code>, <code>is_front_page</code>, <code>is_feed</code>, etc.  If your plugin is actually doing things on normal WordPress page requests and needs access to these functions, then instead of hooking into the <code>parse_request</code> action, you should use the <code>wp</code> action.  You are still passed an instance of the <code>WP</code> class, so all you need to modify is the <code>add_action</code> call:</p>

<pre><code>add_action('wp', 'my_plugin_parse_request');
</code></pre>

<p>I would recommend that you <strong>not</strong> make this change unless you know that you need to.  Otherwise, you&#8217;re having WordPress perform a lot of logic that isn&#8217;t necessary (including hits against the database), potentially making the request take longer than it needs to be.  Though the difference is likely to be imperceptible, it&#8217;s just good practice to keep things as fast as possible.  And when dealing with custom request handling, you can keep things fast by hooking into as soon in the flow as possible.</p>

<h3>Additional Exercise for the reader</h3>

<p>If you don&#8217;t like the look of URL containing <code>index.php?my-plugin=ajax-handler</code>, and instead want something pretty like</p>

<pre><code>http://example.com/my-plugin/ajax-handler
</code></pre>

<p>that is certainly possible with just a little bit more work (see <a href="http://codex.wordpress.org/Function_Reference/WP_Rewrite">WP_Rewrite</a>).  The <a href="http://wordpress.org/extend/plugins/openid/">WordPress OpenID Plugin</a> does this to achieve nice endpoint URLs for the OpenID authentication protocol.  A word of caution with this however: if you want to make sure that your plugin works on the widest number of deployments, where you have no idea what their permalink structure is going to be, there is a bit more work that is necessary.  You can see the rewrite rules I setup for the OpenID plugin in the <a href="http://code.google.com/p/diso/source/browse/wordpress/openid/trunk/common.php">common.php</a> file.  There&#8217;s a lot of other stuff in there, and the rewrite code is a little confusing at parts, but it&#8217;s all there.  I will actually be changing this in the future to simplify things a bit, so avoid complicating matters unless there is a real reason to.</p>

<p>Further codex reading:</p>

<ul>
<li><a href="http://codex.wordpress.org/Query_Overview">http://codex.wordpress.org/Query_Overview</a></li>
<li><a href="http://codex.wordpress.org/Custom_Queries">http://codex.wordpress.org/Custom_Queries</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://willnorris.com/2009/06/wordpress-plugin-pet-peeve-2-direct-calls-to-plugin-files/feed</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>WordPress Plugin Pet Peeve #1: Hardcoding wp-content</title>
		<link>http://willnorris.com/2009/05/wordpress-plugin-pet-peeve-hardcoding-wp-content</link>
		<comments>http://willnorris.com/2009/05/wordpress-plugin-pet-peeve-hardcoding-wp-content#comments</comments>
		<pubDate>Sat, 23 May 2009 22:50:31 +0000</pubDate>
		<dc:creator>Will Norris</dc:creator>
				<category><![CDATA[technology]]></category>
		<category><![CDATA[pet-peeve]]></category>
		<category><![CDATA[plugins]]></category>
		<category><![CDATA[wordpress]]></category>

		<guid isPermaLink="false">http://willnorris.com/?p=618</guid>
		<description><![CDATA[Perhaps my biggest pet peeves I run across with WordPress plugins is when developers hardcode the URL or path to the WordPress content folder. By default this folder is named &#8216;wp-content&#8217;, and resides at the root of the primary WordPress folder. However, since WordPress 2.6 (released July 2008), this location can be moved by simply [...]]]></description>
			<content:encoded><![CDATA[<p>Perhaps my biggest pet peeves I run across with WordPress plugins is when developers hardcode the URL or path to the WordPress content folder.  By default this folder is named &#8216;wp-content&#8217;, and resides at the root of the primary WordPress folder.  However, since WordPress 2.6 (released July 2008), this location <a href="http://codex.wordpress.org/Editing_wp-config.php#Moving_wp-content">can be moved</a> by simply defining a constant in <code>wp-config.php</code>.  That&#8217;s precisely what I do on my website: my WordPress installation lives at <a href="http://willnorris.com/wordpress/">/wordpress</a>, while my content folder is at <a href="http://willnorris.com/wordpress-content/">/wordpress-content</a>.  I like having this separation of core WordPress files from the themes, plugins, and uploads I&#8217;ve added myself.  It also makes it easier for me to upgrade WordPress, since I don&#8217;t use the built-in upgrade system added in 2.7.  Any plugins that still hardcode the path of the <code>wp-content</code> folder break in often spectacular ways on my site.</p>

<p>So what should plugins do instead?  In order to make moving your content folder possible, WordPress 2.6 added a number of constants and functions which refer to the correct location of several often used folders.  <span id="more-618"></span> So instead of including an image using something like:</p>

<pre><code>&lt;img src="&lt;?php bloginfo('wpurl') ?&gt;/wp-content/plugins/my-plugin/images/logo.png" /&gt;
</code></pre>

<p>you would have:</p>

<pre><code>&lt;img src="&lt;?php echo WP_PLUGIN_URL ?&gt;/my-plugin/images/logo.png" /&gt;
</code></pre>

<p>or even better:</p>

<pre><code>&lt;img src="&lt;?php echo plugins_url('my-plugin/images/logo.png') ?&gt;" /&gt;
</code></pre>

<p>Since these constants were added in WordPress 2.6, they obviously won&#8217;t work in earlier versions.  No problem, you can define them yourself in your plugin.  The WordPress Codex page <a href="http://codex.wordpress.org/Determining_Plugin_and_Content_Directories">Determining Plugin and Content Directories</a> includes 8 lines of code (plus 1 comment) that you can add to your plugin to ensure these constants are set, even in older versions of WordPress:</p>

<pre><code>// Pre-2.6 compatibility
if ( ! defined( 'WP_CONTENT_URL' ) )
    define( 'WP_CONTENT_URL', get_option( 'siteurl' ) . '/wp-content' );
if ( ! defined( 'WP_CONTENT_DIR' ) )
    define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
if ( ! defined( 'WP_PLUGIN_URL' ) )
    define( 'WP_PLUGIN_URL', WP_CONTENT_URL. '/plugins' );
if ( ! defined( 'WP_PLUGIN_DIR' ) )
    define( 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins' );
</code></pre>

<p>Everywhere else in your plugin, make sure that you use the constants, not the hardcoded path.  If the string &#8216;wp-content&#8217; appears anywhere in your plugin that isn&#8217;t defining one of the constants above, you&#8217;re doing it wrong.  If you also want to make sure the functions like <code>plugins_url</code> are available in older versions of WordPress, see the <a href="http://code.google.com/p/diso/source/browse/wordpress/openid/trunk/compatibility.php">compatibility.php</a> file the ships with the <a href="http://wordpress.org/extend/plugins/openid/">WordPress OpenID Plugin</a>.</p>

<p>So plugin authors, please go and fix this in your plugins.  Please?  Otherwise I can&#8217;t use your plugin at all on my site.</p>
]]></content:encoded>
			<wfw:commentRss>http://willnorris.com/2009/05/wordpress-plugin-pet-peeve-hardcoding-wp-content/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
	</channel>
</rss>

