Getting users to do anything is hard. Be it filling out a form or just performing a few clicks – there’s an inherent resistance. Even if that action is for the users’ good, like changing a weak password, they won’t do it. Same applies for getting users to install a required plugin, or even worse, replacing an active plugin with a new one. It’s borderline mission impossible.

Thankfully, with a bit of code and user’s consent, we can download, install, activate, deactivate and even delete WordPress plugins. This automated process, when deployed responsibly, can drastically improve user experience and increase conversion rates for necessary user actions in WordPress admin.

Why download, install & activate plugins via code?

There are multiple scenarios but theory aside, let’s look at the situation WP Reset has at hand. I already wrote a post about rebranding reset-wp into WP Reset. Besides other things, the process involves getting people who already use reset-wp to remove it from their sites and then install WP Reset. Why would someone do that just because we asked? And even if they want to do it, they’ll be reluctant because it’s far from two clicks. However, if we explain everything and boil it down to one button “Yes, go ahead, replace reset-wp with Reset WP,” then things don’t look that bad.

Don’t mess with people’s sites without their permission! Period. Not even if it’s “a tiny little thing.” That’s not cool. It’s not your site. Notify users of any changes when they update the plugin, or when you add new features.

Due to wp.org rules that prevent us from tracking user’s actions (without their consent, and we didn’t want to bother them with asking) I, unfortunately, don’t have any numbers to share. But what I can tell you is that we had no complaints from reset-wp users who went through the replacement process, or just saw the notification in the plugin. And we’ve been running the process for over seven weeks.

Is it OK to do this? Will users mind?

Users will surely mind anything and everything you do behind their backs! Don’t do that! Don’t do anything without the users’ permission. Especially if the plugin is hosted on wp.org because that’s against the rules. If you do that, admins will promptly remove your plugin from the repository.

But, if you ask for permission – “Can we replace plugin A with plugin B? Please confirm.” Then it’s OK to do so. The process of deactivating and deleting one plugin, and downloading and activating another takes more than a few clicks. So, if you manage to automate that process, and reduce it to one click users will be pleased.

A similar process of activating required plugins for themes has been in play for years, and users are okay with it. Instead of following multiple links to install plugins they confirm they are OK with the process and several plugins get installed in one click.

As usual, very little code is needed

In pseudo code this is the general idea of what we want to do:

// pseudo code only!
// DO NOT copy & paste

var $old_plugin;
var $new_plugin;

if ( is_plugin_installed( $new_plugin ) ) {
  // new plugin is already installed
  // make sure we have the last version
  upgrade_plugin( $new_plugin );
} else {
  install_plugin( $new_plugin );
}

if ( !is_plugin_active( $new_plugin ) ) {
  // new plugin is not active - activate it
  activate_plugin( $new_plugin );
}

// deactivate old plugin
deactivate_plugin( $old_plugin );

// if needed delete old plugin
delete_plugin( $old_plugin );

// pseudo code only!
// DO NOT copy & paste

It’s quite simple and straightforward. As long as the user has the right file permissions set everything will go smoothly. The whole replacement doesn’t take more than a second or two to complete.

We need a few functions

Thankfully WordPress has most of the functions we need either done or semi-done, so it’s a matter of putting things together.

A crucial concept to understand is how WordPress identifies a plugin, how it knows which plugin is which. It does that by plugin slug – a combination of plugin folder name and the main PHP file. For instance: hello-dolly/hello-dolly.php. The main PHP file is the file with the plugin headers. To get the slug run plugin_basename( __FILE__ ). Whenever you have to check if a plugin is active or want to do something with it, you need to use the plugin slug. This slug is similar but not the same as the slug from wp.org. On the repository, the slug is just the folder name, without the filename. So, for Hello Dolly it’s “hello-dolly.” We don’t need that at the moment, but it needs to be clarified. For a more in-depth read on this somewhat confusing topic check out this thread on Stack Exchange.

WordPress identifies plugins by their slug, ie hello-dolly/hello-dolly.php. To get the slug run plugin_basename( __FILE__ ) in the plugin’s main file.

is_plugin_active( $plugin_slug ) comes built-in so there’s nothing for us to do. Codex page doesn’t reveal much extra, it’s a simple function.

is_plugin_installed( $plugin_slug ) doesn’t come built-in, but it’s only a few lines to code.

function is_plugin_installed( $slug ) {
  if ( ! function_exists( 'get_plugins' ) ) {
    require_once ABSPATH . 'wp-admin/includes/plugin.php';
  }
  $all_plugins = get_plugins();
  
  if ( !empty( $all_plugins[$slug] ) ) {
    return true;
  } else {
    return false;
  }
}

Those are all the conditional, testing functions we need. Now for the ones that do something. activate_plugin( ) is built-in and has four parameters so check out the Codex page about it.

deactivate_plugins( $plugin_slug ) is also available out-of-the-box but be sure to note the “s” (plural) in the functions’s name. It too has a few parameters so read the Codex page.

install_plugin( $plugin_zip ) is available as a part of the Plugin_Upgrader class. No need for any extra code, just a new class instance. Same goes for upgrade_plugin( $plugin_slug ).

function install_plugin( $plugin_zip ) {
  include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  wp_cache_flush();

  $upgrader = new Plugin_Upgrader();
  $installed = $upgrader->install( $plugin_zip );

  return $installed;
}

function upgrade_plugin( $plugin_slug ) {
  include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  wp_cache_flush();

  $upgrader = new Plugin_Upgrader();
  $upgraded = $upgrader->upgrade( $plugin_slug );

  return $upgraded;
}

wp_cache_flush() may not be needed but I added it just to be on the safe side. Besides things taking 10ms longer to run it can’t do any harm.

Putting it all together

If you’re reading this article not out of curiosity, but out of a real need to implement the code in a plugin, I highly suggest installing the reset-wp plugin. Reading about code is nice, but nothing compares to testing in the wild.

My preference is to use admin_action for almost everything that doesn’t need a full admin GUI. So, add this to the function where other actions and filters are added: add_action( 'admin_action_replace_plugin', 'replace_plugin' );. When the user opens admin.php?action=replace_plugin our code will run. Please don’t hardcode that URL. Use something like: $url = add_query_arg(array('action' => 'replace_plugin'), admin_url('admin.php'));. It speaks volumes about your code quality.

Here’s the code you can copy/paste. Obviously, modify the three variables on top with your values.

function replace_plugin() {
  // modify these variables with your new/old plugin values
  $plugin_slug = 'wp-reset/wp-reset.php';
  $plugin_zip = 'https://downloads.wordpress.org/plugin/wp-reset.latest-stable.zip';
  $old_plugin_slug = 'reset-wp/reset-wp.php';
  
  echo 'If things are not done in a minute <a href="plugins.php">click here to return to Plugins page</a><br><br>';
  echo 'Starting ...<br><br>';
  
  echo 'Check if new plugin is already installed - ';
  if ( is_plugin_installed( $plugin_slug ) ) {
    echo 'it\'s installed! Making sure it\'s the latest version.';
    upgrade_plugin( $plugin_slug );
    $installed = true;
  } else {
    echo 'it\'s not installed. Installing.';
    $installed = install_plugin( $plugin_zip );
  }
  
  if ( !is_wp_error( $installed ) && $installed ) {
    echo 'Activating new plugin.';
    $activate = activate_plugin( $plugin_slug );
    
    if ( is_null($activate) ) {
      echo '<br>Deactivating old plugin.<br>';
      deactivate_plugins( array( $old_plugin_slug ) );
      
      echo '<br>Done! Everything went smooth.';
    }
  } else {
    echo 'Could not install the new plugin.';
  }
}
  
function is_plugin_installed( $slug ) {
  if ( ! function_exists( 'get_plugins' ) ) {
    require_once ABSPATH . 'wp-admin/includes/plugin.php';
  }
  $all_plugins = get_plugins();
  
  if ( !empty( $all_plugins[$slug] ) ) {
    return true;
  } else {
    return false;
  }
}

function install_plugin( $plugin_zip ) {
  include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  wp_cache_flush();
  
  $upgrader = new Plugin_Upgrader();
  $installed = $upgrader->install( $plugin_zip );

  return $installed;
}

function upgrade_plugin( $plugin_slug ) {
  include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  wp_cache_flush();
  
  $upgrader = new Plugin_Upgrader();
  $upgraded = $upgrader->upgrade( $plugin_slug );

  return $upgraded;
}

The code uses everything we already discussed. GUI and messages are very basic so add a bit of CSS or load the URL in a lightbox as we did in reset-wp.

One click is always better than ten

Having users click once instead of ten times is always a positive thing! It’s a sign of good UX (user experience). So if your plugin or theme require other plugins, I’d recommend implementing a one-click installation procedure for all dependencies. You’ll undoubtedly get fewer support tickets from people in the early stages of plugin use.

As for replacing one plugin with another – try to stay out of those situations. Sometimes it’s unavoidable, but the problem is you’ll never get all users to switch to the new plugin which means you either have to support two plugins or abandon and disappoint a part of your user base that continues using the old plugin. We face the same decision with reset-wp and don’t like either one of those two solutions 🙂

    1. Sure, it’s more/less the same code. The only thing you need to do is add ob_start() so that nothing gets printed when the script is running.

    1. Sorry,
      My apologies, the email was shot out before I had finished it…
      Please disregard my ealier
      As I hasd started writing, I am building an app to manage WP websites & managing plugins (especially un-installing & updating ) is not easy from outside WP, wothout using an other plugin, which I want to avoid…
      I am wondering if WP will ‘allow me to include the WP included classes such as:
      ‘wp-admin/includes/class-wp-upgrader.php’, just by including them as you did :
      include_once ABSPATH . ‘wp-admin/includes/class-wp-upgrader.php’;
      If, so that would be great, and my worries are over ! With your help & a little tweeking of your scripts…<
      Many thanks,
      JM , from France !


Leave a Reply

Your email address will not be published. Required fields are marked *