Creating Ajax backend for Specialised Page Template? Should admin-ajax.php be used?

We’re scoping an upcoming project that will require the development of a large php web application within an existing wordpress site – this is for our use only and we’ve no intention of trying to package it for other users; so interoperability isn’t really a concern as long as we can continue to apply WordPress updates as they’re released without breaking our application.

Our initial thought is to code the application as a single php file located within the theme folder as a ‘Specialised Page Template‘.

Read More

The main reason for this is to make use of the Role, Capability & Authentication features already available within WordPress.

This will be a relatively large web application consisting of bespoke objects, classes & 1000’s of lines of bespoke php, JQuery and TSQL code.

Part of this web application will be a large back-end of ajax functions (120 approx.) – my question is related to this ajax backend file.

We’d prefer to handle logged in vs logged out behaviour at the ajax level ourself as we’ll be adding extra functionality & authentication on ajax requests based on role and capability restrictions.

Our Question:

If we write our own ajax.php file, which wp files do we need to require_once() to ensure access to wordpress core functions & objects (such as is_user_logged_in(), current_user_can() & $current_user etc)

Whats our problem with admin-ajax.php I hear you ask?

A typical example of an ajax request we’ll be making is to update a single value in the database, that’s going to consist of 3 simple steps:

  • Check user is authenticated, if not, return a 403 header and exit();
  • Initialise our applications database class & execute tsql statement
  • return 200 header

Very simple as you can see & could be done with a little clientside jquery and 3 lines of server side php, to do the same with admin-ajax we need to dig through wp_localize_script, add_action, wp_enqueue_script before we even get to writing the client-side JQuery (or have we misunderstood?) – it just seems a little “overkill”?

Related posts

Leave a Reply

2 comments

  1. Custom implementation vs. Standard API usage

    Using the WP AJAX API is the way to go. First off, you get access to the complete set of WP APIs, you can leverage standard jQuery $.ajax() and similar API calls and you are running standard conform, gaining access to all knowledge spread around the web with articles or for e.g. in answers on this site. In the short term, you might think you are faster, but in the long term you are just cutting yourself off of all help sources.

    Example: Debugging

    You will have to implement custom debugging routes, while WordPress AJAX has lots of sources that will help you getting around it.

    External Database

    The best thing to do is to use the $wpdb class.

    class CustomDatabaseHandler extends wpdb
    {
        // Overwrite what you need here
    }
    

    More info in this answer.

    Overhead?

    The main “problem”/performance drawback when using WP AJAX is that every request to admin-ajax.php actually reloads the whole WordPress core. This file can be replaced, as shown by @Rarst in this answer with a custom file that reduces the impact and only loads what you need.

    Custom Application

    As I am doing it on a day to day basis: It’s no problem developing your application outside of WordPress and then simply pulling it in via Composer (or whatever package manager you are using) and loading it from a simple single file in your plugins folder that has nothing than a call to require our autoloader, a controller and a header comment that declares it as a plugin.

    <?php
    /** Plugin Name: Custom Application as Plugin */
    
    # Composer autoloader
    include_once __DIR__.'/vendor/autoload.php';
    
    add_action( 'plugins_loaded', function()
    {
        // Initialize your plugin here
    } );
    

    That’s all you need.

    Web Applications

    If you are running an Angular.js, Ember or Backbone WeApp it’s no problem. WP AJAX will work with that without a problem.

    Security

    WordPress reuses its nonces, which is a well known fact. This does not really expose anything or open a security whole, but you can go one step further and generate a new nonce [for every request as well], making your calls … ubersecure.

    Basics

    As it doesn’t make sense to repeat other answers, simply read through , look at my example GitHub Gist or my other GitHub Gist that shows different implementations to show that callbacks can be attached in any possible way.

    Overall, your plugin won’t do much than the following:

    // Public or private?
    // 'wp_enqueue_scripts'/'admin_enqueue_scripts'
    // or only on login?
    // 'login_enqueue_scripts'
    add_action( 'wp_enqueue_scripts', function()
    {
        $name = 'handle';
        wp_register_script(
            $name,
            plugins_url( 'assets/ajax.js', __FILE__ ),
            [ 'jquery' ],
            filemtime( plugins_dir_path( __FILE__ ).'assets/ajax.js' ),
            true
        );
        wp_enqueue_script( $name );
        wp_localize_script(
            $name,
            "{$name}Obj", // This string is what gives you access to below array
            [
                'ajaxurl'     => admin_url( 'admin-ajax.php' ),
                '_ajax_nonce' => wp_create_nonce( "{$name}_action" ),
                'action'      => "{$name}_action",
                'data'        => [ /* additional data as array */ ],
            ]
        );
    } );
    

    You just register that and your AJAX callback and you are done:

    // Public or private?
    add_action( "wp_ajax_{$name}_action", 'ajaxCb' );
    add_action( "wp_ajax_nopriv_{$name}_action", 'ajaxCb' );
    
    public function ajaxCb( $data )
    {
        check_ajax_referer( $data['action'] );
    
        # @TODO sanitize data here:
        // filter_var() filter_var_array() and filter_input()
        # @TODO Custom logic here
    
        // Error?
        if ( is_wp_error( $thing ) )
            wp_send_json_error( $data );
    
        // Success!
        wp_send_json_success( $data );
    }
    

    Note: Comprehensive example Gist on how to sanitize using filter_var_array().

    And you AJAX.js file will look close to the following:

    /*global jQuery, $, handleObj */
    ( function( $, plugin ) {
        "use strict";
            var pass = $( '#password__field' ).val(),
                name = $( '#name__field' ).val();
    
            // @TODO other setup tasks for static vars in here
    
            $.ajax( {
                url  : plugin.ajaxurl,
                data : {
                    action      : plugin.action,
                    _ajax_nonce : plugin._ajax_nonce,
                    userName    : name,
                    passWord    : pass
                },
                beforeSend : function( d ) {
                    // @TODO Add loading animation
                }
            } )
                .done( function( response, textStatus, jqXHR ) {
                    // @TODO Clear loading animation
                    if ( ! response.success ) {
                        // @TODO Custom error
                    }
                    else {
                        // @TODO success
                    }
                } )
                .fail( function( jqXHR, textStatus, errorThrown ) {
                    console.log( errorThrown );
                } );
    } ( jQuery, handleObj || {} ) );
    

    There’s not really much more to do aside from filling in the gaps/your application logic into what is shown above in code.

  2. This is an add on to @kaiser answer, read that before.

    To be honest wp_localize_script, add_action are the best part of Ajax API, and really, they are an help, not a problem.

    Let’s imagine you have 120 ajax functions. Even if you don’t use admin-ajax.php see again your workflow:

    • Check user is authenticated, if not, return a 403 header and exit();
    • Initialise our applications database class & execute sql statement
    • return 200 header

    the “core” part is “execute sql statement” that is an action, one among 120.

    How do you choose the action to perform? Do you want to create 200 ajax entry points? I hope no. I think you’ll create a single entry point file, and using a request argument, $_POST['action'] or similar, you’ll decide which action to perform.

    Using 'wp_ajax_*' and 'wp_ajax_nopriv_*' you already have a built-in code that dispatch a request to the related action performer (an object method, a callback, etc..).

    Moreover, if you use only 'wp_ajax_*' without 'wp_ajax_nopriv_*' you already have the authentication routine included, no need to do anything else (or maybe you can use 'wp_ajax_nopriv_*' to send the 403 header).

    Let’s talk about wp_localize_script.

    First o later you’ll need to pass variables form PHP to javascript. How do you will handle this task? Putting javascript inside the php files and use echo inside the js code? I hope no.
    Actually wp_localize_script gives you a convenient way to do the trick, without having to write custom code and concentrate your efforts on write your business logic.

    First place where you’ll need to pass data from PHP to js is when you need to implement CSRF protection.
    Your 3 points workflow above in fact is not complete nor secure: authentication is done via cookie and so vulnerable to Cross-site request forgery.
    The way WordPress prevents this sort of attacks are nonces that are short-lived random strings that can’t be predicted by attackers. These nonces are generated in PHP how do you want to pass that variables to js?

    Now the bad part: yes, WordPress ajax is slow. As @kaiser said in his answer you can create your own entry point avoiding to load things that you do not need, like widgets, themes, and other things. But in that file I suggests to trigger all the WordPress hooks and, in general, try to keep WordPress compatibility. Why?

    It’s simple: even if you don’t want to distribuite your code can arrive a day that you want to use a WordPress plugin: there are thousand over there… is not fine having the possibility to use some plugins to do things instead of reinvent the wheel everytime you need something?