A WordPress ajax handler for custom themes
Something I have been noodling on is a better way to handle ajax requests in my custom themes. Seems to me that a relatively complex theme ends up with a lot of add_action calls for custom ajax handlers, and this could be simplified/reduced. Every time a new endpoint is required we have to add two new add_action calls to our theme. Maybe a better approach is to write a single ajax endpoint that will route requests to the proper classes/methods?
The goal would be that all ajax requests are run through our custom ajax handler and routed to the appropriate controller & method for execution. This method allows us to encapsulate functionality into separate classes/modules instead of cluttering functions.php with ajax functions. Potentially this makes the code more reusable.
With some sane configuration I think that this could be a good way to build a flexible ajax interface into your theme. To protect against malicious calls classes are namespaced (\Ecs\Modules\), and the methods are prefixed (ajax*). This should stop most attempts to execute arbitrary theme code. There are issues though. There would be a fatal error if the class does not exist which could expose information about the environment. We have to make sure that the $_REQUEST params are well sanitized. This would need to be scrutinized and tested for security issues. We don't want someone to be able to craft a request to execute code we don't explicitly want executed.
Here is an example structure in a hypothetical Theme class.
class Theme
{
public function __construct()
{
... snip ...
add_action('wp_ajax_ecs_ajax', array(&$this, 'executeAjax'));
add_action('wp_ajax_nopriv_ecs_ajax', array(&$this, 'executeAjax'));
}
... snip ...
/**
* Simple interface for executing ajax requests
*
* Usage: /wp-admin/admin-ajax.php?action=ecs_ajax&c=CLASS&m=METHOD&_wpnonce=NONCE
*
* Params for ajax request:
* c = class to instantiate
* m = method to run
* _wpnonce = WordPress Nonce
* display = json,html
*
* Naming Conventions
* Method names will be prefixed with "ajax_" and then run through the Inflector to camelize it
* - eg: "doThing" would become "ajaxDoThing", so you need a method in your class called "ajaxDoThing"
*
* Classes can be whatever you want. They are expected to be namespaces to \Ecs\Modules
*
* Output can be rendered as JSON, or HTML
*
* Generate a nonce: wp_create_nonce('execute_ajax_nonce');
*/
public function executeAjax()
{
try {
// We expect a valid wp nonce
if (!isset($_REQUEST['_wpnonce']) || !wp_verify_nonce($_REQUEST['_wpnonce'], 'execute_ajax_nonce')) {
throw new \Exception('Invalid ajax request');
}
// Make sure we have a class and a method to execute
if (!isset($_REQUEST['c']) && !isset($_REQUEST['m'])) {
throw new \Exception('Invalid params in ajax request');
}
// Make sure that the requested class exists and instantiate it
$c = filter_var($_REQUEST['c'], FILTER_SANITIZE_STRING);
$class = "\Ecs\\Modules\\$c";
if (!class_exists($class)) {
throw new \Exception('Class does not exist');
}
$Obj = new $class();
// Add our prefix and camelize the requested method
// eg: "method" becomes "ajaxMethod"
// eg: "do_thing" becomes "ajaxDoThing", or "doThing" becomes "ajaxDoThing"
$Inflector = new \Ecs\Core\Inflector();
$m = $Inflector->camelize('ajax_' . filter_var($_REQUEST['m'], FILTER_SANITIZE_STRING));
// Make sure that the requested method exists in our object
if (!method_exists($Obj, $m)) {
throw new \Exception('Ajax method does not exist');
}
// Execute
$result = $Obj->$m();
// Render the response
\Ecs\Helpers\json_response($result);
} catch (\Exception $e) {
\Ecs\Helpers\json_response(array('error' => $e->getMessage()));
}
// Make sure this thing dies so it never echoes back that damn zero.
die();
}
}