WP’s Backbone-like Templating Language

In yesterday’s post, I talked about fixing up old PHP code to be safer.

There’s another anti-pattern common in old PHP code, and that’s mixing the display logic with the output logic. While some of this is inevitable, nowadays, the rule is to use a templating system like Twig to separate out even small bits of HTML code from the logic.

WordPress does this on the front end via Underscore templates, but configured to use Handlebars-like syntax.

This is a PHP class that does the same thing with PHP. I wrote it so I could use the same, or similar, templates on both the client and server side.
Continue reading WP’s Backbone-like Templating Language

WordPress Customizer, Selective Refresh and Partials for Multiple Settings

There are several good references about how to set up the Customizer to avoid refreshing the entire page with each change. There’s one here, and there’s some deeper explanation here. What’s not described much is how to map several settings to a single area of the page (called a Partial).

This tutorial will go into updating Partials that use several settings. I assume you have already done the other tutorials.
Continue reading WordPress Customizer, Selective Refresh and Partials for Multiple Settings

WordPress Plugin Update and Install Functions

On the page where they explain how to create tables for your plugin, there’s a link to the register_activation_hook function, which is run when the plugin is activated. However, right in the first section, it says:

Note: Don’t use activation hooks (especially for multisite). Do this instead:

It’s far better to use an upgrade routine fired on admin_init, and handle that per-site, basing it on a stored option.

That links to another page, which repeats the information, but doesn’t tell you how to do this. Here’s one way. Continue reading WordPress Plugin Update and Install Functions

filter_var? filter_input? No, Use Filter Input Array to Modernize Legacy Code

I’ve been a real nut for filter_var() for years, and have come up with concise ways to use it, but totally missed this other function, which, at first look, seemed a little too specialized.

filter_input_array()

Well, I was so wrong. This is a great way to filter inputs. I figured this out when I had to fix up some code because it was throwing a zillion “Notice, index foobar not defined.” in the error logs. Continue reading filter_var? filter_input? No, Use Filter Input Array to Modernize Legacy Code

Developing admin-ajax.php Handlers with PHPUnit and Curl (WordPress WP)

The typical way I’ve made AJAX handlers that hook into admin-ajax.php is with Firebug and little bits of Javascript code to exercise the REST API. The problem with this is that you lose all the development code. This note shows how to use PHPUnit
to write your code as tests, and develop the REST API using something like test driven development (TDD).

This isn’t formally “TDD” because these aren’t unit tests. Even if you wanted to include them in the test suite, they would be a problem because they 1) use the network, and take a long time to run, 2) are hard to run on the staging environment, 3) they are really integration tests, except they don’t integrate the browser and Javascript environments.

Not only that, I don’t write a complete test ahead of time; the testing and coding happen together.

Despite this, it’s just nice to be able to code, and then test, with a single command.

watch -n 5 ./run.sh

Code

The code is at https://github.com/johnkawakami/wp-admin-ajax-phpunit-toy.

Our WP AJAX Handler

WordPress has a new REST API system, but we’re not going
to use it here. Instead, we’re testing against the classic
admin-ajax.php call. If you don’t know how to do this, read
AJAX in Plugins

In our plugin or theme, we have this handler:

namespace JK;

function fe_test_callback($data) {
    $test = $_POST['test'];
    wp_send_json_success(array(
        'test' => $test
    ));
}

add_action('wp_ajax_fe-test', 'JK\fe_test_callback');

Note that I have my code namespaced under JK, so I needed to add the namespace to the handler. I forgot to do this, and went on a long excursion into the WP code 🙁

Also, note that I used a dash in the action name “fe-test”. This is just an old WP convention I’m staying with.

This handler simply finds the “test” parameter, and then returns it as the value of ‘test’ in the result.

The result looks like this:

{"success":true,"data":{"test":"test"}}

It’s JSON formatted. The wp_send_json_success() sends back the result as a JSON object with two properties, success and data. This is standard in WP.

Directory Structure

I’m keeping my tests in a subdirectory of tests.

tests
└── admin-ajax
    ├── tests
    │   └── 001-find.php
    ├── run.sh
    ├── library.php
    └── config.php

Command to create this:

mkdir -p tests/admin-ajax/tests
cd tests/admin-ajax
touch run.sh
chmod u+x run.sh

During development, I just keep running run.sh, which is a one liner:

run.sh

#! /bin/bash

clear
phpunit --bootstrap ~/.composer/vendor/autoload.php tests/001-find.php

I installed PHPUnit with Composer. It was installed globally to my home directory, with this command:

composer global require phpunit/phpunit

When it’s installed this way, you need to include the autoloader that Composer creates, so that your test script can find the PHPUnit classes.

If you want to install it locally, you can, with this:

composer require phpunit/phpunit

and then execute PHPUnit like this:

vendor/phpunit/phpunit/phpunit tests/001-find.php

Configuration

Our tests need to log in to WordPress. Since we don’t want to keep our passwords in the test scripts (or put them into the repo), we use a config file. The config file also has our site’s URL (which is running on a virtual machine).

config.php

<?php
return array(
"url" => "http://192.168.33.21/",
"username" => "admin",
"password" => "admin"
);

Returning a value from a script is an obscure PHP feature. It allows you to write a code that includes the script, and then assigns this return value to a variable:

$config = include('config.php');
$config['username'];

Utility Functions

Warning – there’s a bug in my library. I didn’t use cURL correctly, so session cookies are
lost between calls to *_url(), which is exactly the opposite of what I wanted. Also,
CURLOPT_FOLLOWLOCATION might also lose cookies, but I’m not certain of that. This note will
be deleted when it’s fixed.

library.php contains some utility functions that help shorten our code. I won’t get into it here, but here are the functions and constants defined within it:

define('COOKIEFILE', '/tmp/curl_cookies');
function get_url($url)
function post_url($url, $data)
function erase_cookies()
function url($path)
function parse_headers($text)
function response_body($text)

url() prepends the config’s URL to your path.
get_url() and post_url() get a url and return the response, both the headers and body. You then use parse_headers() to extract the headers as an associative array, and use response_body() to extract the body.

erase_cookies() deletes the COOKIEFILE. The COOKIEFILE is used to remember your login.

See the code for details.

The Test Samples

The tests are kept in the ajax-admin/tests directory.

tests/001-find.php:

<?php

use PHPUnit\Framework\TestCase;

include('library.php');
$config = include('config.php');

class APITest extends TestCase 
{
    public function setUp() 
    {
        global $config;
        erase_cookies();
        $login = post_url('/wp-login.php', array(
            'log'=>$config['username'],
            'pwd'=>$config['password'],
            'redirect_to'=>'/wp-admin',
            'testcookie'=>'1'
        ));
    }

    public function testFindAjax() 
    {
        $text = get_url('/wp-admin/admin-ajax.php');
        $headers = parse_headers($text);
        $this->assertEquals(200, $headers['HTTP']['status']);
    }

    public function testAjaxTest()
    {
        $text = post_url('/wp-admin/admin-ajax.php', array(
            'action'=>'fe-test',
            'test'=>'test'
        ));
        $body = response_body($text);
        /*
         *  Body looks like:
         *  {"success":true,"data":{"test":"test"}}
         */
        $json = json_decode($body);
        $this->assertEquals(true, $json->success);
        $this->assertEquals("test", $json->data->test);
    }
}

If you know PHPUnit, this will all look pretty easy. The rest of this article walks through the code.

The boilerplate at the top brings in the PHPUnit classes, our library, and our config.

use PHPUnit\Framework\TestCase;

include('library.php');
$config = include('config.php');

PHPUnit tests are subclasses for TestCase. TestCase has methods like assertEquals, which we used in our tests.

class APITest extends TestCase

The setUp method is run before each test. Our setUp clears out old cookies, then logs us into the wp-admin, and saves our login cookies to a file. (post_url() saves the cookies to a file.)

    public function setUp() 
    {
        global $config;
        erase_cookies();
        $login = post_url('/wp-login.php', array(
            'log'=>$config['username'],
            'pwd'=>$config['password'],
            'redirect_to'=>'/wp-admin',
            'testcookie'=>'1'
        ));
    }

The first test, testFindAjax(), just connects to the API endpoint. If this fails, it might mean the configuration is wrong, or some modification to the libraries broke code.

    public function testFindAjax() 
    {
        $text = get_url('/wp-admin/admin-ajax.php');
        $headers = parse_headers($text);
        $this->assertEquals(200, $headers['HTTP']['status']);
    }

get_url() and post_url() retrieve both the HTTP headers and the body. Here’s what that looks like:

HTTP/1.1 200 OK
Date: Tue, 22 Nov 2016 21:49:28 GMT
Server: Apache/2.4.7 (Ubuntu)
Content-Length: 1
Content-Type: text/html

0

parse_headers() takes the header part and turns it into an associative array that looks like this:

Array
(
    [HTTP] => Array
        (
            [header] => HTTP/1.1 200 OK
            [version] => 1.1
            [status] => 200
            [message] => OK
        )

    [date] => Tue, 22 Nov 2016 21:50:36 GMT
    [server] => Apache/2.4.7 (Ubuntu)
    [content-length] => 1
    [content-type] => text/html
)

Our test assertion checks that the status code is 200.

The second test is to our test function, which we defined above.

    public function testAjaxTest()
    {
        $text = post_url('/wp-admin/admin-ajax.php', array(
            'action'=>'fe-test',
            'test'=>'test'
        ));
        $body = response_body($text);
        /*
         *  Body looks like:
         *  {"success":true,"data":{"test":"test"}}
         */
        $json = json_decode($body);
        $this->assertEquals(true, $json->success);
        $this->assertEquals("test", $json->data->test);
    }

Setting up a call to our endpoint’s a little more elaborate. The second parameter contains the POST data, and the required ‘action’ parameter specifies which handler to call.

In this test, we use response_body() to get the body of the response. Responses can be anything from text to a JSON object, but the standard is to return a JSON object created with the wp_send_json_success() and wp_send_json_error() functions.

We decode the body with json_decode(), which returns an object, not an associative array.

We then test for two expected conditions.

If the endpoint doesn’t return JSON, then json_decode() returns nothing, and the tests will fail with error messages.

Comparison with JS Testing

Arguably, this is inferior to testing from the Javascript side, because it doesn’t involve the browser environment.

I created it because, for what I was doing, setting up a
JS IDE was a little too much. There’s also the mental
hopscotch you need to do when you’re shifting from
JS to PHP, and from Jasmine to PHPUnit. I’d rather write
tests for the plugin in PHPUnit, and then work on the
REST API, and continue to use PHPUnit, and not involve
the browser, or PhantomJS, or other tools.

(Well, this was a real rabbithole. I thought it’d take a few hours, but it took a day.)

Paginating Database Results

I describe how others page over results, and how I do it.

I find fascinating URIs that looks like this:

http://mysite.com/index.php?page=3&search=foo

Why do programmers use a “page”? It seems odd, because that number is going to be translated to a starting record number, and a number of records (per page). Effectively, the URL could be this:

http://mysite.com/index.php?offset=30&pagesize=10

Now, you can show any arbitrary number of rows, starting at any arbitrary point in the results. Of course, what you lose are the “pages”, because you can get at any single record in the result. (If they can type a url, that is.) To regain the page-y-ness of the interface, you need to write a loop that constructs the pager navigation. One way is this:

for( $i=0; $i < $resultcount; $i+=$pagesize )
{
    // start the link
    print '<a href="/drupal/script.php';
    print "?offset=".$i."&pagesize='.$pagesize;
    print '">'.title.'</a>';
    // and close the link
}

This way reduces the coupling between the creation of the page navigation from the display of the results. It also creates a cleaner mapping to the SQL OFFSET statement.

If you need to know what page you’re on, just divide the offset by the page size.