Fixing TGM Plugin Activation for WordPress Updates

TGM Plugin Activation (TGMPA) is a discontinued PHP library that some WordPress authors use to handle distributing theme-required plugins. TGMPA was last updated in March of 2016, and with that age comes a bit of baggage. The main issue comes with management. They have a separate page to manage and run updates. This is super annoying when managing many WordPress websites as it requires manual effort to run TGM plugin updates per each site.

TGMPA admin dashboard notice
TGMPA’s “Install Plugins” page found under the Appearance menu item
Regular WordPress updates screen showing there is an update but no direct way to run the update.

Injecting TGMPA into WordPress updates

Their approach is overly complex. Instead of a separate page to manage updates TGMPA should inject its customized updates into WordPress through two filter hooks plugins_api and site_transient_update_plugins. That would allow these plugins to be updated from /wp-admin/plugins.php and also from WP-CLI. So after a bit of trial and error, I came up with the following code to do just that.

<?php

class CaptainCoreInjectTGMPAUpdater {

    public function __construct() {

        if ( class_exists("TGM_Plugin_Activation") && has_action("tgmpa_register") ) {
            add_filter( 'plugins_api', [ $this, 'info' ], 20, 3 );
            add_filter( 'site_transient_update_plugins', [ $this, 'update' ] );
        }

    }

    public function plugins() {

        // fetch tgmpa plugins with external downloads
        do_action("tgmpa_register");
        $tgmpa    = \TGM_Plugin_Activation::get_instance();
        $tgmpa->populate_file_path();

        if ( empty( $tgmpa->plugins ) ) {
            return [];
        }
        foreach( $tgmpa->plugins as $key => $plugin ) {
            if ( $plugin["source_type"] != "external" ) {
                unset( $tgmpa->plugins[ $key ] );
            }
        }

        return $tgmpa->plugins;

    }

    function info( $response, $action, $args ) {

        // do nothing if you're not getting plugin information right now
        if ( 'plugin_information' !== $action || empty( $response ) ) {
            return $response;
        }

        // get updates
        $plugins = $this->plugins();

        if ( empty( $plugins ) ) {
            return $response;
        }

        // do nothing if plugin not in TGM Plugin Activation
        if ( empty( $args->slug ) || ! in_array( $args->slug, array_keys( $plugins ) ) ) {
            return $response;
        }

        $response->version        = $plugins[ $args->slug ]["version"];
        $response->download_link  = $plugins[ $args->slug ]["source"];
        $response->trunk          = $plugins[ $args->slug ]["source"];

        return $response;

    }

    public function update( $transient ) {

        if ( empty( $transient->checked ) ) {
            return $transient;
        }

        $plugins                      = $this->plugins();
        $installed_plugins            = get_plugins();
        $installed_plugins_file_paths = array_keys( $installed_plugins );

        foreach( $plugins as $plugin ) {

            $plugin                = (object) $plugin;

            // Skip if TGMPA plugin not installed
            if ( ! in_array( $plugin->file_path, $installed_plugins_file_paths ) ) {
                continue;
            }

            $response              = new \stdClass();
            $response->slug        = $plugin->slug;
            $response->plugin      = $plugin->file_path;
            $response->new_version = $plugin->version;
            $response->package     = $plugin->source;

            if ( version_compare( $installed_plugins[ $plugin->file_path ]["Version"], $plugin->version, '<' ) ) {
                $transient->response[ $plugin->file_path ] = $response;
            }
        }

        return $transient;

    }

}

$tgmpa_updater = new CaptainCoreInjectTGMPAUpdater();
add_action( 'wp_loaded', [ $tgmpa_updater, '__construct' ] );

My initial attempts had issues running after the TGM_Plugin_Activation class was loaded. While hooking into the wp_loaded might not be the most efficient hook, it was the first hook I found that seemed to work consistently. With the above code loaded into a must-use plugin, both WP-CLI and WordPress updates on /wp-admin/ now work with any TGM plugins.

Theme required plugins js_composer_theme and jupiter_core showing updates are available from WP-CLI.