Custom plugin update (WordPress)

Uit De Vliegende Brigade
Naar navigatie springen Naar zoeken springen

I would like to be able to update plugins from an own repository: Most (if not all) plugins are open source, licensed under GPLv2 or GPLv3 and free as in free beer. I would prefer to buy just one license of a given plugin, and distribute it myself to other sites that I manage, than buying a separate plugin for each site.

Solution (2024.10

  • Use wp install --force in combination with a local path to update a site
  • Use a site like plugins.example.com as a local repository for these plugins, on the same webserver as the sites to be updated (as I'll use the path, not the urls).

Overview - Custom plugin update

Various methods for custom plugin updates:

Custom update script

Backups of a theme accumulating at a site. Each backup is a complete functional instance of the theme, and could get activated. This makes maintenance messy and consumes storage space

Around 2020/2021, I developed a script for updating plugins and themes, as part of a larger server-sided update script:

  1. Rename the existing plugin/theme folder, to e.g.: pluginname-bk-yyyy.mm.dd
  2. Create a new folder with the original name of the plugin or theme, e.g. pluginname
  3. Upload the updated plugin or theme to this new folder.

This script had some issues

  • It didn't check if an update was actually needed, but updated it anyway whenever invoked
  • Backups weren't deleted afterwards - I'm reluctant to automatically delete stuff
  • Site wasn't put in maintenance mode during update - Asking for trouble
  • All kinds of checks that the Plugin update procedure (WordPress) follows, are missing
  • This is asking for trouble.

Recommendations

Don't do this. Use existing procedures instead, e.g., wp plugin install --force or using the WordPress Update API.

Redirect the plugin update location

Plugins add code to instruct WordPress from where to update. This can be changed. An example that I haven't checked:

add_filter( 'pre_set_site_transient_update_plugins', 'check_for_custom_plugin_update' );
function check_for_custom_plugin_update( $transient ) {
    if ( empty( $transient->checked ) ) {
        return $transient;
    }

    // Replace this with your plugin slug and the URL where the plugin updates can be fetched
    $plugin_slug = 'my-custom-plugin';
    $api_url = 'https://custom-plugin-server.com/api/plugin-update.json';

    // Fetch the update information from the custom server
    $response = wp_remote_get( $api_url );
    if ( is_wp_error( $response ) ) {
        return $transient;
    }

    $data = json_decode( wp_remote_retrieve_body( $response ) );
    if ( version_compare( $data->new_version, $transient->checked[$plugin_slug], '>' ) ) {
        $transient->response[$plugin_slug] = (object) array(
            'slug'        => $plugin_slug,
            'new_version' => $data->new_version,
            'package'     => $data->download_url,
            'url'         => $data->changelog_url,
        );
    }

    return $transient;
}

wp install --force - Custom URL

WP-CLI command wp plugin update doesn't support custom source locations. However, using wp plugin install with the flag --force executed on a plugin that is already installed, effectively turns it into an update:

  • Download the plugin zip from the custom URL
  • Replace the existing plugin files with the new ones from the ZIP file
  • Keep the plugin's existing settings and data (like with a typical update).

From a custom URL:

wp plugin install https://custom-server.com/path-to-plugin/plugin-name.zip --force

Somehow, I seem to be unable to get this to work. See wp plugin install for details.

wp install --force - Custom path

As before, but from a custom path, assuming the plugin is on the same server as the site:

wp plugin install /path/to/local/plugin-name.zip --force

BTW: The package has to be a zip file. See Wp plugin install (WP-CLI) for details.

Custom plugin update services

Some plugin developers use services like GitHub or private repositories to host their plugins and manage updates. In these cases, plugins can be configured to pull updates from sources like GitHub by integrating update mechanisms directly into the plugin code.

For example, you can use plugins like GitHub Updater to enable plugin updates from a GitHub repository:

  • Install the GitHub Updater plugin (https://github.com/afragen/github-updater)
  • Configure the plugin to point to the GitHub repository where the plugin is hosted
  • The plugin will then check GitHub for updates and fetch new versions from there instead of the WordPress.org Plugin Repository

This can also work with other repositories like Bitbucket or custom servers, depending on how the update mechanism is implemented.

Custom plugins update APIs

If you're hosting your plugin updates on a custom server, you can build a custom API that responds to requests for plugin version information. Your plugin can check this API periodically (or when wp plugin update is run) to determine if an update is available and download the new version.

The steps involved include:

  • Create a custom API on your server that responds with the latest plugin version, download link, and other relevant data
  • Modify the plugin to check the custom API using wp_remote_get() and compare the version
  • If an update is available, the API provides a ZIP file that can be downloaded and installed.

Package location

Where to store the source package?

GitHub

  • Maybe the easiest approach: Store in the usual gitHub account?
  • Conclusion: It turns out, that I have no 'manual interaction' with these packages at all: They are 100% handled by scripts, so it doesn't matter where it is located - That makes this option not so usefull

Pro

  • Easy: I can prepare source packages at my workstation and push it to the various servers
  • Flexible: No problem when using wp plugin install with a local path: Just enable this repository (or only parts of it) on each webserver
  • Universal: I already use GitHub for patching Filterer.php, so this fits in current procedures.

Con

  • GitHub pushes are likely to get slower when such relative large packages are stored in it

Server directory

Place these packages at standard locations at webservers. E.g., /opt

Pro

  • Works without GitHub

Con

  • I don't like interacting with server storage through Nemo or rsync or so
  • Higher trashhold for doing it - Increasing the change that I won't do it at all
  • I don't find such a location intuitive.

Plugins.example.com

  • Store at an own URL - I didn't get this to work so far. Details: wp plugin install
  • On the other hand: This 'repository site' is for me at the same server as the sites that need to be updated, so effectively it can still use a path, like /var/www/plugins.example.com/dolly.zip
  • Additional advantage: It's available from outside the server, if ever needed
  • It's an intuitive location: I would remember to look here when I have to review all this six months or two years from now.

Dropbox

  • It would be possible to generate these packages at a Dropbox account at a webserver, but it isn't very intuitive to me and possible technically challenging
  • The repository actually doesn't have to be distributed: Currently (2024.10), all WordPress sites are at one giant server
  • Not intuitive: Where in the Dropbox account to place these?
  • Conclusion: Don't: Too complicated and contrived.

Packaging procedure

Now the interesting part: How to create these source packages? How to automate stuff as much as possible, while keeping procedures robust? The nice thing: I can start doing this manually and progressively update the script:

  • Licenses will be used on one site: example.nl - This site will be the source for these custom packages
  • Custom packages can be created by simply zipping up the content of wp-content/plugins/<plugin>/
  • Names of these packages are straightforward: Just use the regular name/title of these packages. No need to include versioning codes
  • Newly created custom packages, can overwrite the older ones in the GitHub account
  • Have a script for this in the GitHub account, maybe called wp_update_custom_plugin_packages. In this script, enable/disable the functions that are needed to make specific packages - Ideally, this enabling/disabling wouldn't be needed, but that seems too much work to automate

Updating procedure

  • In the script mentioned before, also include code for updating the other sites that use this plugin - Quite straightforward

Test: woocommerce-eu-vat-number

Context

  • I have two licenses and have this plugin installed on nl_nl and fr_fr
  • However, this plugin is probably installed on all similar websites, as these are basically clones of nl_nl
  • Let's see if I can do custom plugin update on be_nl
  • At nl_nl, version 2.9.3 is installed
  • At be_nl, version 2.4.2 is installed, which is from 2022.02.22 - I can well imagine that nl_nl has been cloned to be_nl around that time
  • At fr_fr, version 2.4.2 is installed. I'm surprised, as this is actually licensed.

Create woocommerce-eu-vat-number.zip

cd /var/www/nl_nl/wp-content/plugins
zip_file_path_name="/home/jeroen/woocommerce-eu-vat-number.zip"
source_directory="woocommerce-eu-vat-number"

zip -r  "${zip_file_path_name}" "${source_directory}"

Update fr_fr

Juhu! Updated woocommerce-eu-vat-number from a custom repository. The plugin was actually licensed for this site, so it's not such a big deal, but a nice first step for sure!

This is the easy step, as fr_fr is actually licensed:

zip_file_path_name="/home/jeroen/woocommerce-eu-vat-number.zip"
cd /var/www/fr_fr

wp plugin list --name="woocommerce-eu-vat-number"
wp plugin install --force "${zip_file_path_name}"
wp plugin list --name="woocommerce-eu-vat-number"

Update be_nl

Now the real test: Does this procedure also work at a site for which there is no license for this plugin?

zip_file_path_name="/home/jeroen/woocommerce-eu-vat-number.zip"
cd /var/www/be_nl

wp plugin list --name="woocommerce-eu-vat-number"
wp plugin install --force "${zip_file_path_name}"
wp plugin list --name="woocommerce-eu-vat-number"
wp-admin/plugins: Old situation: Version 2.4.2
Juhu! I succeeded in updating woocommerce-eu-vat-number from an own repository on a site for which I don't have a license
wp-admin/plugins: New situation: Version 2.9.3
Consistency-check: Somehow, I didn't have the very newest version at my repository. Trying to update through the regular channels doesn't work, because of the absence of a license - As expected

Update be_nl - Repeat?

What happens if you try to do this multiple times with the same package? Does --force really forces a "meaningless" update? Or is it just skipped?

When installing a plugin with option --force, it does re-install the plugin, regardless if the update is newer than the existing plugin or not. This actually makes sense, as it is a way to repair a damaged plugin. If this is problematic, I could incorporate a version check in the script, but I think it's fine like this

See also