Deactivating a plugin at activation time

While developing the Route Pages plugin I’ve ran into the need to make sure that WP Router plugin is installed and activated in the WordPress installation and got a little confused while trying to sort out a way to

  1. avoid plugin activation
  2. give the user information about what’s happening

A first, non working, version

My first attempt was code like this

/**
 * Activate the plugin
 */
public static function activate()
{
    // check for WP Router plugin to be activated
    $isWPRouterInactive = is_plugin_inactive('WP-Router/wp-router.php');

    if ($isWPRouterInactive) {

        deactivate_plugins(plugin_basename(__FILE__));

        add_action('admin_notices', function()
            {
                $wpRouterUrl = "http://wordpress.org/plugins/wp-router/";
                echo sprintf("Route Pages requires <a href=\"%s\">WP Router plugin</a> to be installed and activated.<br>Install and/or activate it and try again.", $wpRouterUrl));
    }

    // if all ok then...
    $this->goOnWithActivation();
}

Sadly that’s not working at all. I’ve searched the web for a while and could not find much information and have set for an informative wp_die generated screen like the one below

Die with style
Die with style

The entry point

The below function, defined in my plugin main file, will take care of checking for the required WP Router plugin and prompt the user with possible actions

/**
 * Activate the plugin
 */
public static function activate()
{
    // check for WP Router plugin to be installed and activated
    $isWPRouterActive = is_plugin_active('WP-Router/wp-router.php');

    if (!$isWPRouterActive) {

        $installOrActivateLinkFormat = '<a style="float:right;" href="%s">%s WP Router now &#8594;</a>';
        $installOrActivateLink = '';
        $wpRouterPath = self::checkWPRouterInstalled();

        if ($wpRouterPath) {

            // generate an activation URL
            $installOrActivateLink = self::generateWPRouterActivationLink($installOrActivateLinkFormat, $wpRouterPath);

        } else {

            // generate an installation URL
            $installOrActivateLink = self::generateWPRouterInstallationLink($installOrActivateLinkFormat);

        }

        // do not activate the plugin
        deactivate_plugins(plugin_basename(__FILE__));

        // display the die message
        $dieMessage = self::generateWpDieMessage($installOrActivateLink);

        wp_die($dieMessage);
    }

    // requirements are ok, go on
    ...
}

thanks to PHPStorm magical refactoring abilities I’ve moved the code into separate functions and will walk them here for my future memory.

Checking if a plugin is installed

WordPress does not pack a is_plugin_installed function so I’ve created one to check if WP Router is installed.

protected static function checkWPRouterInstalled()
{
    // get all the plugins
    $installedPlugins = get_plugins();
    foreach ($installedPlugins as $installedPlugin => $data) {

        // check for the WP Router title
        if ($data['Title'] == 'WP Router') {

            // return the plugin folder/file
            return $installedPlugin;
        }
    }

    return false;
}

That’s probably the trickiest method among all as it requires spoofing the plugin request to pass the nonce check

/**
 * @param $installOrActivateLinkFormat
 * @return string
 */
public static function generateWPRouterActivationLink($installOrActivateLinkFormat, $plugin)
{
    $activateUrl = sprintf(admin_url('plugins.php?action=activate&plugin=%s&plugin_status=all&paged=1&s'), str_replace('/', '%2F', $plugin));

    // change the plugin request to WP Router to pass the nonce check
    $_REQUEST['plugin'] = $plugin;
    $activateUrl = wp_nonce_url($activateUrl, 'activate-plugin_' . $plugin);
    $installOrActivateLink = sprintf($installOrActivateLinkFormat, $activateUrl, 'Activate');

    return $installOrActivateLink;
}

Generating an installation link is easier and does not require any convolute hack

/**
 * @param $installOrActivateLinkFormat
 * @return string
 */
public static function generateWPRouterInstallationLink($installOrActivateLinkFormat)
{
    $slug = 'wp-router';
    $installUrl = admin_url('update.php?action=install-plugin&plugin=' . $slug);
    $installUrl = wp_nonce_url($installUrl, 'install-plugin_' . $slug);
    $installOrActivateLink = sprintf($installOrActivateLinkFormat, $installUrl, 'Install');

    return $installOrActivateLink;
}

Generating an informative die message

Finally I’ve taken some care to generate a wp_die_message that did not left a plugin administrator out of options forcing him/her to make assumptions and hit the dreaded Back button.

/**
 * @param $installOrActivateLink
 * @return string
 */
public static function generateWpDieMessage($installOrActivateLink)
{
    $wpRouterUrl = "http://wordpress.org/plugins/wp-router/";
    $notice = sprintf('<span style="display:block;text-align:center;">Route Pages requires <a href="%s" target="_blank">WP Router plugin</a> to be installed and activated.</span>', $wpRouterUrl);
    $pluginsUrl = admin_url('plugins.php');
    $backToPluginsLink = sprintf('<a href="%s">&#8592; Back to plugins.</a>', $pluginsUrl);
    $dieMessage = sprintf("%s<br><br>%s%s", $notice, $backToPluginsLink, $installOrActivateLink);
    return $dieMessage;
}

And the final result is the one from the image above.

I appreciate your input