Permanent Tourist

A personal website by Mark Howells-Mead

Handling file requests with the WordPress Rewrite API

I occasionally have a need to divert file requests on a website to a PHP script. By doing so, I can determine whether the visitor may access it, see whether an image should have a watermark automatically applied, or notify an administrator that a PDF has been viewed or downloaded.

The easiest way to do so on an Apache web server is to enter a line in the .htaccess file which passes the request on to PHP. But if I want to avoid having a standalone PHP script and instead parse the request using WordPress, then I have to jump through a hoop. The hoop is called the Rewrite API.

I needed to get the Rewrite API working on a client site, so that we can avoid a manual entry in the .htaccess file. Manually adding stuff to this file isn’t future-proof and the system can easily break if you forget to edit or track the file, or if someone invalidates the rules by accident.

After a little digging and a fair bit of experience in all things WordPress, I got a plugin working earlier today. Because the online guides are a little convoluted, I thought I’d write this blog post. Mainly for my own future reference, but also for anyone else who is searching for it. The example code uses the PHP class logic I write about in September, so read that if the syntax is unfamiliar.

Because of the complexities of WordPress multisite installations, use of the Rewrite API is a bit complex. So this post only goes into the use of the Rewrite API in a single-site WordPress installation.

Using the WordPress Rewrite API to receive static file requests involves a few simple steps. (Simple if you look at them in order.)

  1. Register a regex rule to catch the request, and use it to pass required variables to WordPress.
  2. Tell WordPress that it should use these variables.
  3. Write a function to do something with the variables. (Write a log; send an email; add a watermark; whatever.)

First, create your plugin using this PHP class and namespace logic and add the following hook calls to the constructor function.

add_action('init', array($this, 'rewriteRules'));
add_filter('query_vars', array($this, 'customQueryVariables'));
add_action('parse_request', array($this, 'handleDownload'), 10, 1);
1. Catch the request using regex

The class function rewriteRules steps in and adds the regex rule which recognizes the file request. This is identical to the syntax you’d be using in .htaccess.

The following example says to grab any file request which starts with either download or view, which has a path of any length, and which ends with one of the indicated file suffixes. It also say to pass a matched request to index.php, as WordPress processes rules passed to this file before it starts working on any others. Finally, the rule should be applied as soon as possible – before any other rules – by using the third parameter top.

public function rewriteRules()
  add_rewrite_rule('^(download|view)/((.*?).(jpe?g|gif|png|rar|zip|pdf|tar|gz))$', 'index.php?mhm_handle_filerequest[mode]=$matches[1]&mhm_handle_filerequest[file]=$matches[2]', 'top');
2. Register allowed variables

In the example above, I’ve told WordPress to put the matches from my regex into an array called mhm_handle_filerequest, which I can then access in my function. This naming convention avoids conflicts with other variables; mhm_handle_filerequest is the unique key I’m using for my plugin.

So that WordPress allows you to use this array, you’ll have to register (“allow”) it via the query_vars hook. This extends the existing array of query variables which is always created during a WordPress request.

public function customQueryVariables($query_variables)
  $query_variables[] = 'mhm_handle_filerequest';
  return $query_variables;
3. Process the request

The final stage is to write the function which handles the request itself. In this function, called handleDownload in this example, you have access to the query variables, which you can use to achieve what you need to.

public function handleDownload($query)
    case 'download':
      // Do the download process
    case 'view':
      // Do the view process
      wp_die('Invalid request!');
4. Reload the Permalink structure when installing the plugin

The final step, once the plugin contains the necessary code, is to ensure that the rewrite rules are seen by WordPress. This only happens after the Permalinks rules have been flushed and re-generated. The best way to do this is by adding an activation hook in your plugin, which also fires if you switch themes.

Add the following lines to the constructor function…

register_deactivation_hook(__FILE__, array($this, 'flushRewriteRules'));
register_activation_hook(__FILE__, array($this, 'flushRewriteRules'));
add_action('after_switch_theme', array($this, 'flushRewriteRules'));

…and then add the referenced function to the class. Activate your plugin, and you’re done.

public function flushRewriteRules()
  global $wp_rewrite;
Complete example code

(If you’d like to download a fully-formed piece of example code, then you can do so from my Helpers repository on Github.)