Wordpress - How to make a plugin require another plugin?

Thanks for the answers guys. Though both answers set me on the right path, none worked out of the box. So I'm sharing my solutions below.

Method 1 - Using register_activation_hook:

Create the Parent Plugin in plugins/parent-plugin/parent-plugin.php:

<?php
/*
Plugin Name: Parent Plugin
Description: Demo plugin with a dependent child plugin.
Version: 1.0.0
*/

Create the Child Plugin in plugins/child-plugin/child-plugin.php:

<?php
/*
Plugin Name: Child Plugin
Description: Parent Plugin should be installed and active to use this plugin.
Version: 1.0.0
*/
register_activation_hook( __FILE__, 'child_plugin_activate' );
function child_plugin_activate(){

    // Require parent plugin
    if ( ! is_plugin_active( 'parent-plugin/parent-plugin.php' ) and current_user_can( 'activate_plugins' ) ) {
        // Stop activation redirect and show error
        wp_die('Sorry, but this plugin requires the Parent Plugin to be installed and active. <br><a href="' . admin_url( 'plugins.php' ) . '">&laquo; Return to Plugins</a>');
    }
}

Notice that I'm not using deactivate_plugins( $plugin ); as for some reason it does not work. So I used wp_die to cancel the activation redirection and inform the user.

Advantage:

  • Simple solution and does not incur additional db hits compared to method 2

Disadvantages:

  • wp_die screen is ugly
  • wp_die screen will STILL show up if you activated the Parent Plugin and Child Plugin at the same time using the checkboxes in the plugins admin screen.

Method 2 - Using admin_init and admin_notices

Create the Parent Plugin in plugins/parent-plugin/parent-plugin.php:

<?php
/*
Plugin Name: Parent Plugin
Description: Demo plugin with a dependent child plugin.
Version: 1.0.0
*/

Create the Child Plugin in plugins/child-plugin/child-plugin.php:

<?php
/*
Plugin Name: Child Plugin
Description: Parent Plugin should be installed and active to use this plugin.
Version: 1.0.0
*/
add_action( 'admin_init', 'child_plugin_has_parent_plugin' );
function child_plugin_has_parent_plugin() {
    if ( is_admin() && current_user_can( 'activate_plugins' ) &&  !is_plugin_active( 'parent-plugin/parent-plugin.php' ) ) {
        add_action( 'admin_notices', 'child_plugin_notice' );

        deactivate_plugins( plugin_basename( __FILE__ ) ); 

        if ( isset( $_GET['activate'] ) ) {
            unset( $_GET['activate'] );
        }
    }
}

function child_plugin_notice(){
    ?><div class="error"><p>Sorry, but Child Plugin requires the Parent plugin to be installed and active.</p></div><?php
}

Advantage:

  • Works when you activate the Parent and Child plugin at the same time using checkboxes

Disadvantage:

  • Incur additional db hits as the plugin is actually activated at first and deactivated once admin_init runs.

As for my question regarding disabling the activate link, I could use:

add_filter( 'plugin_action_links', 'disable_child_link', 10, 2 );
function disable_child_link( $links, $file ) {

    if ( 'child-plugin/child-plugin.php' == $file and isset($links['activate']) )
        $links['activate'] = '<span>Activate</span>';

    return $links;
}

However, it turned out to be highly impractical as there is NO place to put this code. I could not put it on the parent plugin as the parent plugin should be active for this code to run. Certainly does not belong to child plugin or functions.php. So I'm scrapping this idea.


Both of the suggested solutions have flaws.

Method 1: As mentioned, wp_die() screen will STILL show up when the Parent Plugin and Child Plugin activated at the same time using the checkboxes in the plugins admin screen.

Method 2: In some use-cases it's not good since 'admin_init' is executed way after 'plugins_loaded' (https://codex.wordpress.org/Plugin_API/Action_Reference), and after the uninstallation hook (https://codex.wordpress.org/Function_Reference/register_uninstall_hook). So for example, if we do want the add-on to run some code on uninstallation whether the parent plugin is active or not, this approach will NOT work.

Solution:

First of all, we need to append the following code to the end of the parent plugin's main PHP file:

do_action( 'my_plugin_loaded' );

This will dispatch an event/signal to all subscribers, telling that the core plugin was loaded.

Then, the add-on's class should look like the following:

class My_Addon
{
    static function init ()
    {
        register_activation_hook( __FILE__, array( __CLASS__, '_install' ) );

        if ( ! self::_is_parent_active_and_loaded() ) {
            return;
        }
    }

    #region Parent Plugin Check

    /**
     * Check if parent plugin is activated (not necessarly loaded).
     *
     * @author Vova Feldman (@svovaf)
     *
     * @return bool
     */
    static function _is_parent_activated()
    {
        $active_plugins_basenames = get_option( 'active_plugins' );
        foreach ( $active_plugins_basenames as $plugin_basename ) {
            if ( false !== strpos( $plugin_basename, '/my-plugin-main-file.php' ) ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if parent plugin is active and loaded.
     *
     * @author Vova Feldman (@svovaf)
     *
     * @return bool
     */
    static function _is_parent_active_and_loaded()
    {
        return class_exists( 'My_Plugin' );
    }

    /**
     *
     * @author Vova Feldman (@svovaf)
     */
    static function _install()
    {
        if ( ! self::_is_parent_active_and_loaded() ) {
            deactivate_plugins( basename( __FILE__ ) );

            // Message error + allow back link.
            wp_die( __( 'My Add-on requires My Plugin to be installed and activated.' ), __( 'Error' ), array( 'back_link' => true ) );
        }
    }

    #endregion Parent Plugin Check
}

if (My_Addon::_is_parent_active_and_loaded())
{
    // If parent plugin already included, init add-on.
    My_Addon::init();
}
else if (My_Addon::_is_parent_activated())
{
    // Init add-on only after the parent plugins is loaded.
    add_action( 'my_plugin_loaded', array( __CLASS__, 'init' ) );
}
else
{
    // Even though the parent plugin is not activated, execute add-on for activation / uninstall hooks.
    My_Addon::init();
}

Hope it helps :)


Try this out, it's commented, so that should help you understand it.

<?php
register_activation_hook( __FILE__, 'myplugin_activate' ); // Register myplugin_activate on
function myplugin_activate() {
    $plugin = plugin_basename( __FILE__ ); // 'myplugin'
    if ( is_plugin_active( 'plugin-directory/first-plugin.php' ) ) {
        // Plugin was active, do hook for 'myplugin'
    } else {
        // Plugin was not-active, uh oh, do not allow this plugin to activate
        deactivate_plugins( $plugin ); // Deactivate 'myplugin'
    }
}
?> 

If this throws out an error, you could also check the 'option' of 'myplugin' and set it to false or not activated.