Switch out plugins easily

Tech Articles | November 4, 2025 | Automation, Blog, Coding, GridPane, Hosting, Plugins

I’ve been using GridPane for years and one of the default plugins installed is a customised version of nginx-helper. I have no issues with it except it was forked before the load-textdomain fix was applied.

I recently forked that gridpane version of the plugin applied the fix and submitted a pull request because it bothers me it hasn’t been picked up yet but its there and ready for when they do, in the interim i’ve been manually swapping it over.

I had to do four this afternoon for some video recording and testing and had enough of that so thought about how i could do this automatically and fell on using my tasks after install plugin to do this and added an addtional set of functions to do just this

Our New Code

I wont explain how its linked into the main plugin its just on an admin init and runs through our code in turn and this takes a reusable filterable list of plugins to find and replace with new versions and in my case this is nginx-helper.


 * Install/replace a single plugin from a ZIP URL.
 *
 * @param string $target_slug  Plugin main file path, e.g. 'nginx-helper/nginx-helper.php'.
 * @param string $zip_url      Direct URL to a plugin .zip.
 * @param bool   $activate     Activate after install (default: true).
 * @param bool   $force        Ignore per-site "already done" guard (default: false).
 * @return array               ['ok'=>bool, 'status'=>string, 'error'=>string|null]
 */
function oaf_wptai_install_or_replace_plugin( $target_slug, $zip_url, $activate = true, $force = false ) {
    if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) {
        return array('ok'=>false,'status'=>'not_allowed','error'=>null);
    }

    // Per-plugin once-only guard - If wanted
    /*
    $done = get_option( 'oaf_wptai_replaced_plugins', array() );
    if ( ! is_array( $done ) ) $done = array();

    if ( ! $force && ! empty( $done[ $target_slug ] ) ) {
        return array('ok'=>true,'status'=>'skipped_already_done','error'=>null);
    } 
    */

    // Make sure core APIs are available
    require_once ABSPATH . 'wp-admin/includes/plugin.php';
    require_once ABSPATH . 'wp-admin/includes/file.php';
    require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';

    // Deactivate if active
    if ( is_plugin_active( $target_slug ) ) {
        deactivate_plugins( $target_slug, true );
    }

    // Delete existing plugin folder if present
    // Using core delete helper is safest
    if ( file_exists( WP_PLUGIN_DIR . '/' . dirname( $target_slug ) ) ) {
        $del_result = delete_plugins( array( $target_slug ) );
        if ( is_wp_error( $del_result ) ) {
            return array('ok'=>false,'status'=>'delete_failed','error'=>$del_result->get_error_message());
        }
    }

    // Ensure FS is ready
    if ( ! WP_Filesystem() ) {
        return array('ok'=>false,'status'=>'fs_not_ready','error'=>'Filesystem credentials required');
    }

    // Install from ZIP
    $skin     = new Automatic_Upgrader_Skin();
    $upgrader = new Plugin_Upgrader( $skin );
    $result   = $upgrader->install( $zip_url );

    if ( is_wp_error( $result ) ) {
        return array('ok'=>false,'status'=>'install_failed','error'=>$result->get_error_message());
    }
    if ( ! $result ) {
        return array('ok'=>false,'status'=>'install_failed','error'=>'Unknown install failure');
    }

    // Try to detect the installed plugin file to activate.
    // Prefer the requested target path; if it doesn't exist, fall back to upgrader->plugin_info()
    $activate_file = $target_slug;
    if ( ! file_exists( WP_PLUGIN_DIR . '/' . $activate_file ) ) {
        if ( ! empty( $upgrader->plugin_info() ) ) {
            $activate_file = $upgrader->plugin_info();
        }
    }

    // Activate
    if ( $activate && $activate_file ) {
        $act = activate_plugin( $activate_file );
        if ( is_wp_error( $act ) ) {
            // Mark installed but not activated so we don't loop forever
            $done[ $target_slug ] = array(
                'ts'     => time(),
                'status' => 'installed_not_activated',
                'zip'    => $zip_url,
                'error'  => $act->get_error_message(),
            );
            //update_option( 'oaf_wptai_replaced_plugins', $done, false );
            return array('ok'=>false,'status'=>'activated_failed','error'=>$act->get_error_message());
        }
    }

    // Success
    $done[ $target_slug ] = array(
        'ts'     => time(),
        'status' => $activate ? 'installed_and_activated' : 'installed',
        'zip'    => $zip_url,
    );
    //update_option( 'oaf_wptai_replaced_plugins', $done, false );

    return array('ok'=>true,'status'=>'done','error'=>null);
}

/**
 * Batch runner. Feeds a list of replacement specs through the single-item helper.
 * Specs: [
 *   [
 *     'target'   => 'plugin-dir/plugin-main.php',
 *     'zip'      => 'https://example.com/plugin.zip',
 *     'activate' => true,        // optional, default true
 *     'force'    => false,       // optional, default false
 *   ],
 *   ...
 * ]
 * You can extend/override via the 'oaf_wptai_plugin_replacements' filter.
 */
/**
 * Batch replace/install plugins from filtered specs.
 * Specs via filter 'oaf_wptai_plugin_replacements':
 * [
 *   [ 'target' => 'dir/main.php', 'zip' => 'https://...', 'activate' => true, 'force' => false ],
 *   ...
 * ]
 */
function oaf_wptai_replace_plugins_batch() {
    if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) return;

    // Start empty; everything comes from the filter
    $replacements = apply_filters( 'oaf_wptai_plugin_replacements', array() );

     /**
     * Filter to add/modify plugin replacements.
     * Example to add more:
     * add_filter('oaf_wptai_plugin_replacements', function($items){
     *     $items[] = [
     *         'target' => 'some-plugin/some-plugin.php',
     *         'zip'    => 'https://example.com/some-plugin.zip',
     *     ];
     *     return $items;
     * });
     */

    if ( empty( $replacements ) || ! is_array( $replacements ) ) return;

    foreach ( $replacements as $item ) {
        $target   = isset( $item['target'] )   ? trim( (string) $item['target'] )   : '';
        $zip      = isset( $item['zip'] )      ? trim( (string) $item['zip'] )      : '';
        $activate = isset( $item['activate'] ) ? (bool) $item['activate']           : true;
        $force    = isset( $item['force'] )    ? (bool) $item['force']              : false;

        if ( $target && $zip ) {
            oaf_wptai_install_or_replace_plugin( $target, $zip, $activate, $force );
        }
    }
}
PHP

Now i’ve allowed for only run once flagging which i don’t think is necessary buts in there and commented out and its now as easy as adding a filter for the plugins we want to replace such as


add_filter( 'oaf_wptai_plugin_replacements', function( $items ) {
    $items[] = array(
        'target'   => 'nginx-helper/nginx-helper.php',
        'zip'      => 'https://github.com/stingray82/nginx-helper/releases/latest/download/nginx-helper.zip',
        'activate' => true,
        'force'    => false,
    );
    return $items;
});
PHP

Once I had this working i’ve patched this into my customised version and away we go we now automatically replace this as of part of our normal installation modification its fast, and automatic.

Support the Author

buy me a coffee
Really Useful Plugin Logo
Wpvideobank temp
Appoligies for any spelling and grammer issue. As a dyslexic i need to rely on tools for this they like me are not perfect but I do try my best