Changing Behaviour

We have seen how to create a very simple child theme — so simple in fact that all it changed is styling. Not that that’s unimportant. An astonishing amount can be accomplished just using CSS. But (apart, perhaps, from choosing whether to hide or display particular chunks of content, and where to display them) CSS isn’t intended to be used for controlling what content should be displayed or in what order.

For this sort of thing we must resort to scripting of some kind — in PHP if we can do it on the server, before the content is sent to the client, or in Javascript if it is better handled on the client. Sometimes both kinds of scripting can be used to good effect in concert, with a conversation being held behind the scenes between client and server, using a mixture of AJAX, JSON and curl. (I mention this for the sake of completeness only as it’s well beyond the scope of this post!)

Suppose, for example, that we wanted to display the posts for a particular category in chronological order, rather than WordPress’s default (reverse chronological order). CSS will be of no help to us here. (It might theoretically be possible to completely reverse the order using the CSS ‘position’ attribute but it’s hardly a practical proposition!)

Far simpler, surely, if we can just modify the WordPress database query that selects the posts, so that instead of selecting them in descending date order it selects them in ascending order. If we decide to take this approach we have two options. Either we can replace the WordPress PHP file that handles the retrieval of the posts from the database, or we can intervene at the crucial point in WordPress’s operations (known, appropriately, as a ‘hook’), and just fix up the database query so that it behaves as we want it to.

There are times when the first approach is necessary. It is much more powerful and flexible, because it gives us complete control over the process. On the other hand it is also more complicated. In general it’s better to go for the solution that requires the least intervention, and here that is the second approach. In fact, there’s a very convenient hook that makes things pretty straightforward.

WordPress hooks

A hook is a mechanism that allows you to inject PHP code at any one of a large number of key points in the process of generating a web-page. Each of these points has its own unique identifier and can be set to call an arbitrary function of our choosing. The hook we are interested in has the identifier pre_get_posts. It’s triggered, or ‘fired’, after a query to retrieve posts has been prepared, immediately before the query is executed. If a function is attached to the hook it will be called at this point, with the query itself as a ‘call by reference’ argument, allowing the query to be modified before it is executed.

To associate a function with a hook we use the WP function

add_action( hook_id, function_name );

This tells WordPress to call the function called function_name when it reaches the point in its processing represented by the hook with the given hook_id, so if we write

add_action( 'pre_get_posts', 'modify_query_order' );

and supply a function called modify_query_order that function will be called immediately before the the query is executed. We’ll look at the means available for the function to change the query in a moment, but first there are a couple of deployment issues we need to clear up.

Function prefixing

Actually it’s not a very good idea to call a function modify_query_order. For one thing, we don’t know (unless we check) whether WordPress already uses a function with that name, but more importantly, even if it doesn’t, we don’t know if it (or another plugin) might do so at some point in the future.

The approved PHP way of preventing name collisions is by using ‘namespaces’, but for some reason this method is not the one conventionally used for actions (or ‘filters’, which are similar) in WordPress. Instead the convention is to prefix any new non-WordPress functions with a string of characters that is unlikely to be used either by WordPress or by another plugin. Not a perfect solution, but much better than nothing. If I were implementing this on the Obson website I could for example use something like

add_action( 'pre_get_posts', 'obson_modify_query_order' );

It’s up to you how you deal with this. I’ll stick to the established convention, but if you feel inclined to set an example by using namespaces that’s even better!

Where should I put this function?

Now that is a very good question. Most of the documentation you’re going to find, whether in the Codex or elsewhere, assumes you already know the answer. This can be very frustrating!

I suppose the reason why people tend to be so vague about it is that there are a number of answers that are potentially correct, and which one you’re going to use will depend on your setup. WordPress is designed to be flexible and to cope with quite widely differing configurations. Often, if it needs to load a file it will run through a set of preferences to see which ones exist and select the most appropriate. But while this is something we need to be aware of, in the present context we have a fairly well defined arrangement.

Assuming you have been working through my posts in order, you will have created a child theme, which to date contains nothing more that a style.css file. You now need to create a functions.php file in the same directory, and that is where your new function, together with the add_action call, will go. When WordPress is loading it will load both the functions.php file (if there is one) in the child theme directory, and the file with the same name in the parent theme directory. If there are name collisions the function in the child theme will take precedence, and this enables you, so to speak, to ‘override’ the WordPress function with your own*. This is a very useful feature as long as you don’t use it accidentally!

Modifying the query

With the preliminaries out of the way lets have a look at the code to modify the query. Here’s an outline of what we need to put in our functions.php file:

function obson_modify_query_order( $query ) 
{
    if ( /* Check type of query */ ) 
    {
        $query->set( 'order', 'ASC' );
    }
}
add_action( 'pre_get_posts', 'obson_modify_query_order' );

Notice that the query that has already been prepared is supplied to the function as an argument. It’s a PHP object and one of its methods is set. In fact it’s a WP_Query instance, and you can find its documentation at https://codex.wordpress.org/Class_Reference/WP_Query. The set method allows us to change the order while leaving everything else unchanged. Just what we want!

The final version

However, there is a complication. The pre_get_posts hook is triggered for all kinds of posts (in this context pages are regarded as posts, as are images, and lots of other things). We therefore need to ensure that we only apply our query modification to posts of the kind we are interested in. Here’s how I used it:

function obson_modify_query_order( $query ) {
    if ( $query->is_category('Child themes') 
        && $query->is_main_query() ) 
    {
        $query->set( 'order', 'ASC' );
    }
}
add_action( 'pre_get_posts', 'obson_modify_query_order' );

In fact, if you select ‘Child themes’ in the list of topics you will see it in operation!

*Note: This doesn’t work in general, but a few core functions are written as ‘pluggable’. This means that they are not loaded until after you (as the child theme) have had a chance to load your version. They will then only be loaded if one with the same name (i.e. yours) doesn’t already exist. Strictly, what happpens is that the core function is replaced, not overridden.