Is it possible to programmatically install plugins from wordpress theme

My dream is to include a php file in a theme which checks if a set of plugins are installed, and installs the ones which are not. Kind of like a set of dependencies for the theme, but also just a good way to package theme development to include a set of good plugins.

My questions…

  1. Is there something like this in existence?
  2. Is it possible to achieve from a single php file in a theme folder?
  3. Are there any obvious pit-falls or problems with this approach?
  4. How would I go about achieving this?
    • is it possible to enumerate installed plugins from within theme folder?
    • is it possible to download and place plugin files in the plugins folder?
    • is it possible to activate plugins from within theme directory?

Related posts

Leave a Reply

2 comments

  1. 07/06/2018 EDIT: If you are coming across this answer, the code highlighted below is extremely outdated and insecure and should not be used in any capacity outside of experimentation on a local server. If you are looking for a more modern solution for plugin management, consider installing WordPress via Composer and Bedrock


    I would advise NOT programatically checking for the existence of certain plugins, downloading, installing, and activating them from within any theme file. You have to consider that the check will be run every time the given page is loaded, and can result in a lot of superfluous code and unnecessary activity.

    Instead, my advice would be to package any plugins on which your theme depends as a part of the theme itself, and NOT as a plugin. Plugins should be installed at the discretion of the user. If a theme depends on a plugin to function properly or efficiently, then it really should be packaged and downloaded with the theme itself.

    But to answer your questions directly:

    1. Probably. It is certainly possible to do.
    2. Yes.
    3. See the above. You potentially run into more issues by constantly checking for plugins and running a series of actions based on those conditions rather than just including everything needed.
    4. Plenty of research

      • Probably
      • Yes
      • Yes

    I cannot stress enough, however, that the purpose of a PLUGIN is to give the user the option to extend a given theme’s capabilities. If your theme’s capabilities DEPEND on existing plugins, then you really REALLY should include all the files when somebody downloads your theme.

    Though if you feel that your approach benefits your theme in ways that I might be missing, feel free to write it however you like.

    COMPLETE ANSWER: I decided to help create a proof of concept for you, because I got bored and curious. Much of this should be self explanatory. Add these functions:

    function mm_get_plugins($plugins)
    {
        $args = array(
                'path' => ABSPATH.'wp-content/plugins/',
                'preserve_zip' => false
        );
    
        foreach($plugins as $plugin)
        {
                mm_plugin_download($plugin['path'], $args['path'].$plugin['name'].'.zip');
                mm_plugin_unpack($args, $args['path'].$plugin['name'].'.zip');
                mm_plugin_activate($plugin['install']);
        }
    }
    function mm_plugin_download($url, $path) 
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $data = curl_exec($ch);
        curl_close($ch);
        if(file_put_contents($path, $data))
                return true;
        else
                return false;
    }
    function mm_plugin_unpack($args, $target)
    {
        if($zip = zip_open($target))
        {
                while($entry = zip_read($zip))
                {
                        $is_file = substr(zip_entry_name($entry), -1) == '/' ? false : true;
                        $file_path = $args['path'].zip_entry_name($entry);
                        if($is_file)
                        {
                                if(zip_entry_open($zip,$entry,"r")) 
                                {
                                        $fstream = zip_entry_read($entry, zip_entry_filesize($entry));
                                        file_put_contents($file_path, $fstream );
                                        chmod($file_path, 0777);
                                        //echo "save: ".$file_path."<br />";
                                }
                                zip_entry_close($entry);
                        }
                        else
                        {
                                if(zip_entry_name($entry))
                                {
                                        mkdir($file_path);
                                        chmod($file_path, 0777);
                                        //echo "create: ".$file_path."<br />";
                                }
                        }
                }
                zip_close($zip);
        }
        if($args['preserve_zip'] === false)
        {
                unlink($target);
        }
    }
    function mm_plugin_activate($installer)
    {
        $current = get_option('active_plugins');
        $plugin = plugin_basename(trim($installer));
    
        if(!in_array($plugin, $current))
        {
                $current[] = $plugin;
                sort($current);
                do_action('activate_plugin', trim($plugin));
                update_option('active_plugins', $current);
                do_action('activate_'.trim($plugin));
                do_action('activated_plugin', trim($plugin));
                return true;
        }
        else
                return false;
    }
    

    … and then execute like so:

    $plugins = array(
        array('name' => 'jetpack', 'path' => 'http://downloads.wordpress.org/plugin/jetpack.1.3.zip', 'install' => 'jetpack/jetpack.php'),
        array('name' => 'buddypress', 'path' => 'http://downloads.wordpress.org/plugin/buddypress.1.5.5.zip', 'install' => 'buddypress/bp-loader.php'),
        array('name' => 'tumblr-importer', 'path' => 'http://downloads.wordpress.org/plugin/tumblr-importer.0.5.zip', 'install' => 'tumblr-importer/tumblr-importer.php')
    );
    mm_get_plugins($plugins);
    

    ‘name’ can be anything, as it serves to be more of a temporary value. ‘path’ is exactly what it looks like, and is the direct URL to the zip file on the WordPress server. The ‘install’ value is simply the path to the main PHP script that has all of the plugin information. You will have to know the layout of that particular plugin directory in order to fill out this information, as this is also required for the activation hack to work.

    Activation function was found here (credit to sorich87): https://wordpress.stackexchange.com/questions/4041/how-to-activate-plugins-via-code

    WARNING: This is by no means a very safe way to do things. I actually think that this can be abused quite easily, so our best bet is to use this as our baseline and try and improve from there.

    If you should decide to use this approach, all I ask is that I’m credited with the initial overall script, along with sorich87 for his activation process may God have mercy on your soul.

    07/06/2018 EDIT: Seriously, don’t do this. By today’s standards, this code is hot garbage. Plugin management should be done through Composer and Bedrock.

  2. Inspired by the comment from Jamie Dixon I inspected how WordPress works.

    The process can be seen in /wp-admin/update.php from line 93. A short version could be made like this:

    include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); //for plugins_api..
    
    $plugin = 'plugin-name';
    
    $api = plugins_api( 'plugin_information', array(
        'slug' => $plugin,
        'fields' => array(
            'short_description' => false,
            'sections' => false,
            'requires' => false,
            'rating' => false,
            'ratings' => false,
            'downloaded' => false,
            'last_updated' => false,
            'added' => false,
            'tags' => false,
            'compatibility' => false,
            'homepage' => false,
            'donate_link' => false,
        ),
    ));
    
    //includes necessary for Plugin_Upgrader and Plugin_Installer_Skin
    include_once( ABSPATH . 'wp-admin/includes/file.php' );
    include_once( ABSPATH . 'wp-admin/includes/misc.php' );
    include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
    
    $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact('title', 'url', 'nonce', 'plugin', 'api') ) );
    $upgrader->install($api->download_link);
    

    If you don’t want the feedback displayed you should create a custom Skin class. For example:

    $upgrader = new Plugin_Upgrader( new Quiet_Skin() );
    
    class Quiet_Skin extends WP_Upgrader_Skin {
        public function feedback($string)
        {
            // just keep it quiet
        }
    }