WordPress Hook Order

November 11th, 2022

I’ve made a site that shows actions and filters executed during different WordPress requests.

Screenshot of hookorder.com

Why?

When I’m writing a plugin, I sometimes need to know not just what a hook does, but also when it runs in relation to other hooks. The official WordPress documentation has a list of common actions, but it’s out of date and far from complete. There have been other attempts to document the hook execution order, but those are also a few years old now and not comprehensive.

This is not entirely surprising. WordPress comes with hundreds of unique actions and filters, and some of them are called dozens of times per page. Manually collating and maintaining an ordered list of all hooks would require a huge amount of work. Fortunately, it’s possible to automate some of that work, which is what I’ve done for this project.

What is it?

HookOrder.com is a documentation/reference site that shows the execution order of all unique WordPress hooks that are fired on common pages. Notable features include:

  • Up to date with WordPress 6.1 (as of this writing).
  • Shows both actions and filters. You can hide filters if you’re only interested in actions.
  • Hook documentation in the sidebar.
  • Shows active callbacks, if any.
  • Shows hook arguments. The actual values will be different on each site, of course, but it can be handy to see some practical examples.

How does it work?

The hook lists are generated by setting up a basic WordPress site and logging every do_action(), apply_filters(), etc call that happens during a request.  A bunch of post-processing is necessary to normalize and format the results and make the database fast enough to be usable.

For anyone curious, here are some of the tools I used:

  • The all hook makes it possible to catch every action and filter. The hook itself appears to be undocumented, but see _wp_call_all_hook.
  • Puppeteer – to control headless Chrome.
  • WP-CLI – to install and configure WordPress, activate plugins, create or import sample data, and so on.
  • Theme Test Data – to fill out the site and hopefully make the results more realistic.
  • SQLite – for temporary log storage.
  • Hook documentation parser – to extract hook documentation from WordPress source code.
  • Miscellaneous Symfony Components.

 


Plugin Updates: Securing Download Links

March 19th, 2013

Ever since the release of the Plugin Update Checker library, one of the most common questions asked has been this: “How can I secure my download links so that only users who have purchased my plugin can download an update?”

In this post I’ll try to answer that question in the context of using WP Update Server to serve plugin updates. I will also provide a couple of practical examples from one of my commercial plugins.

Lets get started. There are many ways to secure update downloads, but most of them boil down to this:

  1. Give some kind of security token to each user. This could be a login + password to a membership site, a license key, or something more esoteric.
  2. Whenever someone installs your plugin on their site, ask them to enter the key/log in/whatever.
  3. Modify the plugin to append the token to each update request.
  4. Write a server script that verifies the token before it allows download.

Choose a Security Token

How you implement the first two steps will vary greatly depending on your plugin UI and what online store, cart software, or membership plugin you use. You might already have some kind of customer authentication mechanism in place that just needs a bit of tweaking to be used for updates, or you might need to build your own from scratch. There’s no “one size fits all” solution here.

Personally, I prefer to use license keys. Whenever someone buys my Admin Menu Editor Pro plugin, the order processing script generates a random key, stores it in the database, and sends the key and a download link to the customer’s email. Then, when they install the plugin, there’s a link to enter the license key.

I won’t include the license management code here because it’s beyond the scope of this post and built for that specific plugin, but the user interface looks something like this (click to enlarge):

Admin Menu Editor Pro entry on the "Plugins" page

Add the Token To Update Requests

Now, how do we add our security token to each update request? You can do that by using the addQueryArgFilter($callback) method of the update checker. The callback function will receive an associative array of query arguments. Just add the token to the list and return the modified array.

Here’s an example:

/* ... Code that initializes the update checker ... */

//Add the license key to query arguments.
$updateChecker->addQueryArgFilter('wsh_filter_update_checks');
function wsh_filter_update_checks($queryArgs) {
	$settings = get_option('my_plugin_settings');
	if ( !empty($settings['license_key']) ) {
		$queryArgs['license_key'] = $settings['license_key'];
	}
	return $queryArgs;
}

Use the Token To Authorize Downloads

Finally, let’s make the update server verify the security token before it lets the user download an update. To do that, you’ll need to create a custom server class (see Extending the server) and override at least the Wpup_UpdateServer::checkAuthorization($request) method. Here’s what you should do in this method:

  1. Retrieve the query argument(s) that contains the token by using $request->param('arg_name').
  2. Verify the token. Again, this part is up to you. You could look it up in a database, use a checksum to validate it, or something else.
  3. If the token is good, you don’t need to do anything special.
  4. If the token is invalid, call $this->exitWithError('Error message') to output an error and stop script execution.

Below is a simplified version of the script I use to implement secure updates for  Admin Menu Editor Pro. It is slightly more advanced than the above outline, but the general idea is the same.

(Again, license management is beyond the scope of this post, so I’ve omitted most of the code that deals with loading and validating licenses. Just treat verifyLicenseExists() and other licensing functions as pseudo-code.)

class SecureUpdateServer extends Wpup_UpdateServer {
	protected $licenseServer;

	public function __construct($serverUrl, $licenseServer) {
		parent::__construct($serverUrl);
		$this->licenseServer = $licenseServer;
	}

	protected function initRequest($query = null, $headers = null) {
		$request = parent::initRequest($query, $headers);

		//Load the license, if any.
		$license = null;
		if ( $request->param('license_key') ) {
			$result = $this->licenseServer->verifyLicenseExists(
				$request->slug,
				$request->param('license_key')
			);
			if ( is_wp_error($result) ) {
				//If the license doesn't exist, we'll output an invalid dummy license.
				$license = new Wslm_ProductLicense(array(
					'status' => $result->get_error_code(),
					'error' => array(
						'code' => $result->get_error_code(),
						'message' => $result->get_error_message(),
					),
				));
			} else {
				$license = $result;
			}
		}

		$request->license = $license;
		return $request;
	}

	protected function filterMetadata($meta, $request) {
		$meta = parent::filterMetadata($meta, $request);

		//Include license information in the update metadata. This saves an HTTP request
		//or two since the plugin doesn't need to explicitly fetch license details.
		$license = $request->license;
		if ( $license !== null ) {
			$meta['license'] = $this->licenseServer->prepareLicenseForOutput($license);
		}

		//Only include the download URL if the license is valid.
		if ( $license && $license->isValid() ) {
			//Append the license key or to the download URL.
			$args = array( 'license_key' => $request->param('license_key') );
			$meta['download_url'] = self::addQueryArg($args, $meta['download_url']);
		} else {
			//No license = no download link.
			unset($meta['download_url']);
		}

		return $meta;
	}

	protected function checkAuthorization($request) {
		parent::checkAuthorization($request);

		//Prevent download if the user doesn't have a valid license.
		$license = $request->license;
		if ( $request->action === 'download' && ! ($license && $license->isValid()) ) {
			if ( !isset($license) ) {
				$message = 'You must provide a license key to download this plugin.';
			} else {
				$error = $license->get('error');
				$message = isset($error) ? $error : 'Sorry, your license is not valid.';
			}
			$this->exitWithError($message, 403);
		}
	}
}

If you have any questions, feel free to leave a comment.


WordPress Update Server

March 12th, 2013

It’s been a long time coming, but I’ve finally released an external update API server for WordPress plugins and themes. Check it out on GitHub. This is the server component to my plugin update checker and theme update checker client libraries.

Features

  • Provide updates for private or commercial plugins and themes.
    From the users’ perspective, the updates work just like they do with plugins and themes listed in the official WordPress.org directory.
  • Easy to set up.
    Just upload the script to your site and drop a plugin or theme Zip in the “packages” subdirectory.
  • Easy to integrate with existing plugins and themes.
    All it takes is about 5 lines of code.
  • Minimal server requirements.
    The server component requires PHP 5.3+ and the Zip extension. The update checker component only requires PHP 5.2 – same as the current version of WordPress.
  • Extensible.
    Want to secure your upgrade download links? Or use a custom logger or cache? Maybe you ‘d like to load the change log and other update meta from the database instead of parsing it from a Zip file? Create your own, customized update server by extending the Wpup_UpdateServer class.

What’s Next?

See the GitHub repository for download links and usage instructions. The documentation still needs work, but it should be good enough to get you up and running.


Plugin Compatibility Reporter

December 4th, 2012

Plugin Compatibility Reporter is a WordPress plugin that enables you report if the plugins that you’re using are working properly. It integrates with the compatibility reporting features of the official WordPress.org plugin directory, and lets you mark plugins as “working” or “broken” from within your WordPress dashboard.

It also tracks […] Continue Reading…


How to Pre-Select a Category for a New Post

November 20th, 2012

Lets say you have a category called “X”, and you want to create a link which when clicked will take the user to the “Add New Post” screen with the “X” category already selected. This could be used to simplify posting for non-technical users, or even as a way […] Continue Reading…