WordPress Plugin Pet Peeve #2: Direct Calls to Plugin Files
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 making an HTTP request directly to one of the files in the plugin… something like:
echo '<script type="text/javascript">
jQuery.get("' . plugins_url('my-plugin/ajax-handler.php') . '");
// do something with AJAX data ...
</script>';
This is not a problem in and of itself, in fact it’s great that the plugin developer is actually using the plugins_url
function! The problem arises in the my-plugin/ajax-handler.php
file itself.
If that plugin needs to make use of any WordPress data or functions, then the developer is certain to do something very ugly. You’ll know it when you see it:
require_once('../../../wp-load.php');
// or sometimes you'll see...
require_once('../../../wp-config.php');
So what is this, and why is it so bad? Well, the wp-load.php
is a file provided by WordPress core that bootstraps the
WordPress environment. It loads in the wp-config.php
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
require_once
call above, the plugin developer is assuming that the wp-load.php
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 as we’ve already seen, WordPress allows deployers to
move where their wp-content
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 wp-admin/admin.php
. But these are safe
for WordPress core files because they do know where certain files will be. It is never safe to make this
assumption from a plugin. I think it’s pretty safe to say that if you have a require
or include
call that goes up
more than a couple of directories ("../../
"), you’re almost certainly doing it wrong.
A couple of points to emphasize before moving on: if your plugin files are not accessing any built-in WordPress functions, classes, or data, calling them directly should be just fine. Also, if you’re doing AJAX calls on admin pages, use the built-in functionality WordPress core provides.
The Right Way
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.
All standard WordPress requests eventually get broken down to a request to /index.php
with a bunch of URL parameters.
For example, when someone goes to your blog post at
http://example.com/2009/01/hello-world
WordPress uses the configured permalink structure to break this down to
http://example.com/index.php?year=2008&monthnum=01&name=hello-world
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
http://example.com/index.php?my-plugin=ajax-handler
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 process_request
. Functions that hook into this
action are passed one parameter, an instance of the WP
class, which encapsulates most of the specific parameters for
the current request. If we want to process only the requests which include my-plugin=ajax-handler
, we would add
something like this to the plugin:
function my_plugin_parse_request($wp) {
// only process requests with "my-plugin=ajax-handler"
if (array_key_exists('my-plugin', $wp->query_vars)
&& $wp->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');
If you’re following along at home, try accessing http://example.com/index.php?my-plugin=ajax-handler
. What happens?
Most likely, nothing at all… 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’t accidentally process something it
wasn’t meant to. Fortunately, registering a new query variable is very simple using the query_vars
filter hook:
function my_plugin_query_vars($vars) {
$vars[] = 'my-plugin';
return $vars;
}
add_filter('query_vars', 'my_plugin_query_vars');
This simply registers ‘my-plugin’ 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
“my-plugin ajax-handler!”. Now you just need to modify the my_plugin_parse_request
function to actually do your
custom request logic, and you’re good to go.
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 before WordPress has processed the query itself.
WordPress’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 is_*
functions work: is_page
, is_front_page
,
is_feed
, 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 parse_request
action, you should use the wp
action. You are still
passed an instance of the WP
class, so all you need to modify is the add_action
call:
add_action('wp', 'my_plugin_parse_request');
I would recommend that you not make this change unless you know that you need to. Otherwise, you’re having WordPress perform a lot of logic that isn’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’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.
Additional Exercise for the reader
If you don’t like the look of URL containing index.php?my-plugin=ajax-handler
, and instead want something pretty like
http://example.com/my-plugin/ajax-handler
that is certainly possible with just a little bit more work (see WP_Rewrite). The WordPress OpenID Plugin 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 common.php file. There’s a lot of other stuff in there, and the rewrite code is a little confusing at parts, but it’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.
Further codex reading:
Comments and responses
Thank you so much Will! I´ve been googleing up and down but this was the exact answer I needed. Very well written and understandable. It´s a gold nugget & saved me heaps of time.
I used the process while writing a shop-plugin fpr wordpress that implements onpage price calculation for the variations of a product (via ajax). Since it all has to come within one plugin, including the ajax-handler that needs to do requests to the normal wp-functions, your post described exactly what I had to do.
Wouldn´t have known otherwise. Thx again!
Okay, I got to admit I've been doing this dirty trick on my plugin for a while now. After reading this, I've converted to using HOOK to "parse_request".
But there comes errors, My plugin uses wp_mail() and wp_verify_nonce()! Both failed as Undefined functions? so I try changing the parse_request HOOK to 'wp'. Same result, I guess this method have its limit!
wp_mail
and wp_verify_nonce
are defined in pluggable.php
which is loaded before either of those hooks. If you're seeing an error about the functions being undefined, it must be caused by something else.
Hello,
Great post however I am not really clear on how to deal with the : require_once('../../../wp-config.php');
I need to use the get_option function to get the site url which I then use in an ajax call : jQuery.post("/wp-admin/admin-ajax.php"...
I have been through the methods you suggest but I don't really understand. The codex describes the method to perform Ajax calls on the admin and frontend sides. You need to include the wp-config.php to get the site url... I fully agree on the fact that this may cause some problems if the installation uses non standard paths but I don't get from your note the way to deal with that... I should have missed something...
Olivier: the only way you would need something like require_once('../../../wp-config.php')
in your code is if you are calling your plugin files directly, and they need access to WP functions. This is really bad, you never want to do this.
So instead of calling your plugin file directly, and having it try to bootstrap the WP environment, what you want to do is actually send the request through WordPress (so the environment gets setup properly) and then have the requested passed off to your plugin to process it. You accomplish the same thing, but this is the Right Way to do it.
The AJAX in Plugins Codex page suggests that you can still use /wp-admin/admin-ajax.php
to process user facing AJAX calls. I was thinking that wasn't possibly, but I certainly could be wrong... might be worth looking into.
Thanks for this great series, Will - I've updated WPBook to comply with them (I think!).
When constructing in code the full url that will be used (example.com/index.php in your example), is it better to use get_bloginfo('wpurl') or get_bloginfo('home')?
That is, do I need to point at index.php in the directory where WordPress is installed or the index.php where they set their home url to be, in cases where they've moved WordPress into a subdirectory?