Sep
9
0

EDD plugin updater for WordPress multi-sites

Security Ids

This plugin is a replacement for the Easy Digital Downloads plugin updater for WordPress single site blogs.

Easy Digital Downloads and the Software Licensing plugin works just fine when used in single site configuration. However the update features available through the EDD SL plugin updater probably will not work in a multi-site configuration and EDD plugin updater for WordPress multi-sites aims to address this.

Download

If you don’t use or support EDD plugins in a WordPress multi-site installation then this post is unlikely to be relevant to you.

What's the problem being addressed?

I’ll start by stating that EDD and the Software Licensing plugin works just fine when used in multi-site configuration. However the update features available through the Easy Digital Downloads plugin updater (EDD updater) probably will not work in a multi-site configuration. This not a problem with EDD or the SL plugin but due to a limitation in the way the multi-site extensions work (a deficiency in WordPress in my view).

This post describes the issue and provides links to download containing a pair of plugins that together resolve the issue. Because its a long post, here’s a review of the headings:

Note: the problem described in this post does not affect plugins hosted in the WordPress plugin repository. It only affects plugins hosted on a third party site such as EDD.com. I explain what’s special about plugins hosted by WordPress.org later.

What's the problem with multi-sites?

To understand the issue it’s first necessary to understand a little about how the multi-site WordPress works and the impact this has on plugins, reporting of their update status and upgrading.

‘Site’ in this context is synonymous with ‘blog’ except that instead of each of several different blogs being their own WordPress installation, each of the posts, comments options and taxonomies are held in blog unique tables but that share common tables, such as users, and one WordPress installation. Technically each blog can be its own site (myblog1.mydomain.com, myblog2.mydomain.com, myblog3.myotherdomain.com) which is probably why its called multi-site not multi-blog. By the way, if a blog is not a unique site then it has a unique path (/blogs/blog1, /blogs/blog2, etc).

The administrator of individual blogs is able to activate and deactivate plugins but cannot install them. Plugin installation is left to a ‘network’ administrator. The network administrator is able install plugins and is able to activate a plugin for all blogs, a process called ‘network activation’. If a plugin is network activated then its not visible to and, so, cannot be managed by the blog/site administrator except through any admin pages provided by the plug-in. The plug-in data will be saved the blog’s tables such as the options table.

One more piece of information is needed. That is, the ‘network’ is a blog but has no blog functionality. The network blog presents no UI except to manage plug-ins and select/manage sites/blogs. By implication, then, the plug-in UIs do not appear, there are no posts and no comments.

If the multi-site functionality is installed over an existing blog, the existing blog become blog number 1 and it’s tables also host the ‘network’. This has an implication: if the EDD plugin is active on blog number 1 then there is no problem. I’ll cover this in a moment.

Before that it's time to explain the issue and why it occurs.

A plugin that is network activated does not present a UI to the network administrator so the network administrator cannot activate the plugin (in the sense of EDD Software Licensing). Instead the plugin must be activated on each blog in which it is to be used. This means the plugin’s license is activated using the site address of the blog.

This has a consequence because of the way the software licensing plugin works on the remote server (the one which will check the plugin license and provide version information). When a plug-in is activated (in the SL sense) part of the information used by the EDD software licensing is the site address.

However, in a multi-site configuration, update checking can only occur at the network level. As mentioned above, the network is associated with blog number 1. Because the address of the blog is used when the plugin’s license is activated, version checking will fail unless the network is able to use the same blog address. So this is why there’s no problem if the plugin is activated by blog number 1: because the site address of the network (remember it’s the network that is checking the license and version) is the site address of blog number 1.

However if the address of the blog is not the same as blog number 1 the version checking will fail. There will be no indication of the failure except that no updates will ever appear for the plugin.

Why doesn't this affect plugins hosted by WordPress.org?

For the EDD SL updater to work, the plugin must be loaded. But as described, only those plugins active on blog 1 are loaded. So unless the EDD SL licensed plugin is active in blog 1 the plugin is not loaded and the SL Updater cannot run.

When checking plugins downloaded from the WordPress.org site, WordPress does not need to load a plugin to find the address of a site to contact to obtain version information. For WordPress plugins this information is hard-coded (in wp-includes/update.php). Instead, WordPress obtains a list of all plugins files, determines the slug based only on the file/path information and sends the list to wordpress.org. WordPress returns a set of ‘offers’ containing information about the plugins it hosts.

So what's the solution?

There are two potential solutions. One is to change the SL updater (come to this in a moment). But this only works if the plugin has been network activated or activated (in a WordPress sense) in blog number 1.

If the if the plugin has been activated (again in a WordPress sense) in another blog then the plugin will not be loaded when in the network blog which is the only place update status shown so the updater will never be called. This requires another, more general solution.

Changing the updater

The main task is to retrieve and use two pieces of information needed by the remote EDD SL server: the license key and the correct site url. This can be done by iterating over the blogs to retrieve options from each blog to find one with a license key and corresponding site address. With this information the call to retrieve version information can succeed.

Here is an alternative implementation of the SL updater which implements this behavior.

A more general solution

Again, the reason why a change to the SL updater is not a complete solution is that it relies on the plugin being active. And not just active, active at the network level or in the number 1 blog. It is not desirable to have all plugins active on all blogs because an active plugin consumes resources even though it will not be used: it must be loaded and any filters/actions it establishes will be run whether relevant to a blog or not.

The ‘plugins’ menu option at the network lists all of the installed plugins and identifies those that are network activated or active in the number 1 blog. Although all the installed plugins are listed, WordPress only loads those plugins that have been network activated or are active in blog number one.

So a more general solution needs to make at least one of instance of the plugin that is active in at least one blog ‘known’ to the network. In my view this should be a task implemented by default by WordPress but it’s not so it’s something to be dealt with.

WordPress plugin loading

Plugin loading takes place in ./wp-settings.php. This file calls ‘wp_get_active_and_valid_plugins()’ which is in ./wp=includes/load.php. This in turn calls ‘get_option( ‘active_plugins’, array() )’. It’s this function that is responsible for returning the active plugins of the number 1 blog.

Interestingly, the ‘get_option’ function calls a filter to give a 3rd party the chance to return some custom set of plugins. This filter can be exploited to return not just those plugins active for the number 1 blog but active plugins across all blogs that among the plugin’s classes includes, say, an SL updater.

The challenge with exploiting the filter called by the ‘get-option’ function is that plugins are loaded very early in the WordPress start-up process. By definition, the active plugins filter must be called before plugins are loaded or the filter will not be available to load the plugins! This means the get_options ‘active_plugins’ filter cannot be exploited by a regular plugin.

Must-use plugins

The solution is to create and use a ‘must-use’ plugin. Before regular plugins are loaded, a different set of plugins called ‘must use plugins’ are loaded. Must-use plugins are those .php files found in a sub-folder of ./wp-content called ‘mu-plugins’. A filter implemented in a must-use plugin will be able to change the list of plugins loaded during the execution of the ./wp-settings.php file. If that filter only returns a different set of plugins to load if the the WordPress installation uses a multi-site configuration and the current user is a network administrator then there is no unwanted side-effect.

‘Must-use’ replacement for the SL updater

As well as returning an extended list of plugins WordPress should load for a network administrator, when the must-use filter is active, any plugins that would normally use the standard SL Updater class will in fact use the SL Updater that is also implemented in the must-use plugin.

The SL updater implementation in the must-use plugin is almost the same as the separate SL Updater presented in this post. There are a few differences. These are to support the use of properties that are provided by the plugin. If these properties are used, there is no need to include a separate SL updater or explicitly instantiate an SL Updater.

The must-use plugin will be used automatically if the plugin includes an sl_updater filter (see below) or includes a property at the top of the plugin file called ‘Updateable’.

You will need to implement a filter hooking ‘sl_updater_‘ which implements the logic required to retrieve the license key of the plugin. The filter should not rely on any global variables. The filter will be called as the current blog changes and any global variables will probably not hold values that are correct for a specific blog. For example, the EDD Software Licensing updater is initialized by referring to the global variable ‘$edd_options’. This is populated by EDD based on the blog active at the time, in this case the number 1 blog. The values in the ‘$edd_options’ global variable are unlikely to be correct for any subsequent blog.

Installing a must-use plugin

A disadvantage of must-use plugins is that there is no user interface to install must-use plugins. It is expected that any files containing must-use plugins will be copies to the mu-plugins file by hand by a person with appropriate credentials.

This must-use plugin then is self installing. All a plugin creator need do is ‘require_once’ the copy of the must-use plugin in their own plugin file set. When the host plugin is activated, a ‘register_activation’ hook will attempt to copy the must-use plugin to the mu-plugins folder, creating the mu-plugins folder if necessary. Any problems installing the must-use plugin it will be presented as admin notification messages.

Using the must-use updater plugin

There are two three to use the must-use updater plugin:

  1. Copy the plugin file (edd_mu_updater.php) to your plugin’s root folder
  2. Add a reference to the require the plugin and initialize the activation hook
  3. Add a filter to return the information required for version checking

1) Copy the plugin file (edd_mu_updater.php) to your plugin’s root folder

The file name is hardcoded and you should not change it. The plugin file should be copied into the root folder of your plugin not, for example, an sub-folder called ‘includes’.

2) Add a reference to the require the plugin and initialize the activation hook

Add the following lines to your plugin’s main file:

require_once 'edd_mu_updater.php';
$this->updater = init_lsl_mu_updater(plugin_basename(__FILE__));

These lines should be placed such that they execute as the plugin is load not, for example, in the ‘plugins_loaded’ action.

3) Add a filter to return the information required for version checking

The filter will look something like this:

function sl_updater_edd_software_licenses($data, $required_fields)
{
	$edd_sl_license_key = <Code to retrieve a licence key>;

	$data['license']	= $edd_sl_license_key;
	$data['item_name']	= <The name of the licences product *>;
	$data['api_url']	= <The address of the store **>;
	$data['version']	= <The current version number ***>;

	return $data;
}
add_filter('sl_updater_edd-software-licenses', 'sl_updater_edd_software_licenses', 10, 2);
*The name of the product as recognized by the license server
**The address of the licensing service that will validate the license and return the current version number
***A string holding the version number of the installed plugin in the format x.y.z

$required_field is an array of strings that document the list of the elements of the array that should be returned.

Please leave a comment

Supportive

We're here to help you every step of the way. Contact Us by sending a private message or ask a question in the support forums.

Innovative

We're always looking for ways to improve what we offer and so help you do business. Need something, not exactly what you are looking for … let us know, we might be able to help.

Reliable

Used by a growing number of websites throughout the world - we help smooth your WordPress site use.

Translate »
Copyright Lyquidity Solutions © 2021 · All Rights Reserved · EDD & WooCommerce plugins · WordPress plug-ins store