PSEC: Connecting Dots, Part V

SUMMARY: Decided to use built-in WordPress AJAX support. Adapted tutorials to work with my solution by tracing through WordPress code line-by-line. Successfully sent jQuery POST to WordPress AJAX handler and dispatched through my handler. Successfully returned a WordPress object back to calling javascript. Round-trip communication is now achieved.

Well, here we are on Part V of “connecting the dots”, the laborious process of figuring out how to everything to connect together. Once that happens, I can start developing the entire system at once.

Before going to bed last night, I had gotten stuck on how to make an authenticated data request between the web browser and the web server. This is important because ultimately I want to support multiple users, so the web server needs to somehow know which user is requesting what data. And even more importantly, I want to make sure that the data is reasonably secure.

This morning, I think I’ve come to some clarity on how to approach the problem. First, I should certainly try the WordPress AJAX technique, even though it seems to be overly heavy-weight. All I really want this for, right now, is to handle on-the-fly saving. Here’s my thoughts on that:

  • Saving data can be relatively infrequent. Instead of “update as field is edited”, as I imagined, I could do “autosave every 5 minutes” instead, with an explicit save button. This is falling out of fashion, I know, but it’s not a bad place to start.

  • If I need a lighter-weight AJAX response for saving, I could actually have that webservice use files instead of the DB for server-side storage, using some kind of hashed version of the nonce + user authentication. This becomes a lighter-weight “data buffer” for interactive save-on-the-fly. Explicit saving could then replay the databuffer back into the database.

  • Local storage is a possibility too, though closing the browser window would lose all changes (this happens too frequently to not protect against). Since I’m targeting HTML 5, though, the new persistent storage APIs might be usable.

The upshot: I should just try the WordPress AJAX thing now to get it working, and then consider optimizations later. So without further ado, let’s follow this tutorial. (many hours pass) After fiddling with the tutorials and then finally tracing the WordPress code, I got it working. There are a few caveats to implementing this technique. The basic idea is simple:

  • In your javascript, use jQuery to send a request to WordPress through the admin-ajax.php file, with an “action” payload that matches what that WordPress is expecting.
  • In your plugin or theme initialization, set up add_action handlers that match the name of the action payload’s “action” attribute. The admin-ajax.php file will fire the action for you based on whether you are logged-in or not.
The caveats: The admin-ajax.php reloads the WordPress core so you can access anything you normally can outside the Loop. This includes running functions.php in theme. However:
  • It defines WP_ADMIN flag, so is_admin() evaluates to true all the time.
  • It also defines DOING_AJAX, so you know that an AJAX call is going on
I had to rewrite my functions.php to take this into account, because originally I assumed that is_admin() wouldn’t be set. My handler functions were never getting called, and yet I was constantly getting a returned response of “-1”. I had to insert traces in admin-ajax.php file to discover exactly where this was coming from, which was when I discovered that none of my code was getting called. The other caveat is that your javascript needs to know the URL of admin-ajax.php in the first place. This is a separate issue from handling the AJAX call, and needs to be set up so these vars are available at page render time.
  • Many of the online tutorials (such as this one) recommending using wp_localize() to set Javascript variables used a script loaded through wp_enqueue_script(). That makes sense for a plugin.

  • However, since I’m using page templates, there’s no way I can use wp_enqueue_script() because its hook fires in wp_head(). So I do it the “bad” way, using PHP echoes that are set to fire by the wp_footer action. The advantage is that I can choose to load a particular javascript by modifying the page template source itself at the very bottom, where the script executes.

<

p>I’ve had to reformat my code a bit. Here’s my theme’s functions.php:

<?php // functions.php

/*  A theme's functions.php executes late in the wp boostrap, after plugins
 *  have loaded but before wp->init() sets up the current user. Set up
 *  action hooks to run later, or be called from page templates when
 *  additional global context is available.
 */

/** INITIALIZE DEBUGGING *********************************************/
define ('CHROME_DEBUGGING',true);
include_once 'code/libs/SeahDebug.php';

/** VERSION CHECKS ***************************************************/
include ('version.inc');

/** EXECUTION CONTEXT ************************************************/
if (!defined('DOING_AJAX')) 
    SDBG::out('*** P'SEC BUILD '. SECOND_APP_VERSION . ' ***');
else 
    SDBG::out('*** P'SEC AJAX HANDLING: '.$_REQUEST['action']);

/** GLOBAL STATE DETECTION and CONSTANTS *****************************/
define ( PARENT_PATH, get_template_directory() );
define ( CHILD_PATH, get_stylesheet_directory() );
define ( CHILD_URI, get_stylesheet_directory_uri() );

// global dseah app class instance
global $dsApp;  

/** LOADER ************************************************************/
// warning: during AJAX call, WP_ADMIN flag is set!
// so we check for DOING_AJAX first...
if ( defined('DOING_AJAX')) {
    require_once ( 'code/_ajax.php' );
    dseah_hook_ajax();
    // action processing terminates in die() of ajax action hooks
} else if (is_admin()) {
    require_once ( 'code/admin/admin.php' );
    add_action('admin_init','dseah_admin_main');
    // execution resumes in dseah_admin_main
} else {
    require_once ( 'code/_main.php' );
    add_action('init','dseah_main');
    // execution resumes in dseah_main
}

/** END OF EXECUTION **************************************************/

The key change is to detect DOING_AJAX, and including the right code modules at the right time.

I’ve separated AJAX handling to it’s own file in _ajax.php. Since during a WordPress AJAX call the entire WordPress core is loaded and the user is authenticated, I can do practically anything I want here.

Here’s what’s in _ajax.php, which handles an AJAX packet with the ‘action’ parameter set arbitrarily to ‘ajax_action’:

// called by function.php if DOING_AJAX is defined...
function dseah_hook_ajax() {

    // define actions to handle
    $actions = array (
        'ajax_action'
    );

    // automatically define handlers
    foreach ($actions as $action) {
        $fn = 'ajax_'.$action;
        if (function_exists($fn)) {
            $a = 'wp_ajax_'.$action;
            $an = 'wp_ajax_nopriv_'.$action;
            add_action($a, $fn);
            add_action($an, $fn);
        } else {
            SDBG::out("ERROR: $action handler function $fn() does not exist");
        }
    }
}

// this is a test 'ajax_action' handler
function ajax_ajax_action() {
    // doing ajax stuff
    $wpuser = wp_get_current_user();
    $response = json_encode( array( 'success' => true, 'data' => $wpuser ) );
    // response output
    header( "Content-Type: application/json" );
    die ($response);
}

At the top, I’ve defined an array where I can easily drop-in new ajax actions as I define them in Javascript.

NOTE: A good reference for “AJAX handling the WordPress Way” is to look through the source of wp-admin/admin-ajax.php. It’s a giant dispatching statement for handling all the administrative panel functions via AJAX.

The code that actually sends the AJAX request is in second-main.js, which is loaded by the page template with a call to dseah_set_script("second-main.js") just after the footer closes in HTML. The pertinent code looks like this:

$('.controls').click(function () {
    $.post( DS_AjaxURL, {
        action: 'ajax_action'
    }, function(response) {
        console.log(response); // alerts 'ajax submitted'
    });
});

The DS_AjaxURL variable is handled by the call to dseah_set_script(), and contains the URL pointing to wp-admin/admin-ajax.php.

The second parameter of the $.post() is an associative array; the action parameter is required by admin-ajax.php so it can dispatch the request to the proper handler. I’ve defined those handlers in my own _ajax.php file.

The final parameter is a function that handles whatever is spit back out from the AJAX call. In my example, I’m just dumping the raw response to the console so I can see what’s inside it, but you could have this do anything.

And THAT completes the round-trip! The dots are connected!

Adding Nonces

The last thing I want to address is adding Nonces, to add a bit more security and protection from replays. For nonces to work, though, I need to know what needs a nonce. Since I haven’t actually gotten the front-end to work yet, this is a bit premature.

Next Steps

Now that the page can talk to the server and get data back, I can now think about creating the UI! Tomorrow, I’m going to add the code that:

  • Loads an object list from server into Model
  • Create browser elements from Model
  • Render Model to View

And that will truly be an advancement. After that, it can be pure UI coding for a while in Javascript. The step after that is saving the data back to the server.