src/web/app/mu-plugins/bedrock-autoloader.php
changeset 1 79bc439970cd
child 15 1921d064475c
equal deleted inserted replaced
0:800e4a6ab128 1:79bc439970cd
       
     1 <?php
       
     2 /**
       
     3  * Plugin Name: Bedrock Autoloader
       
     4  * Plugin URI: https://github.com/roots/bedrock/
       
     5  * Description: An autoloader that enables standard plugins to be required just like must-use plugins. The autoloaded plugins are included during mu-plugin loading. An asterisk (*) next to the name of the plugin designates the plugins that have been autoloaded.
       
     6  * Version: 1.0.0
       
     7  * Author: Roots
       
     8  * Author URI: https://roots.io/
       
     9  * License: MIT License
       
    10  */
       
    11 
       
    12 namespace Roots\Bedrock;
       
    13 
       
    14 if (!is_blog_installed()) {
       
    15     return;
       
    16 }
       
    17 
       
    18 /**
       
    19  * Class Autoloader
       
    20  * @package Roots\Bedrock
       
    21  * @author Roots
       
    22  * @link https://roots.io/
       
    23  */
       
    24 class Autoloader
       
    25 {
       
    26     /** @var array Store Autoloader cache and site option */
       
    27     private static $cache;
       
    28 
       
    29     /** @var array Autoloaded plugins */
       
    30     private static $auto_plugins;
       
    31 
       
    32     /** @var array Autoloaded mu-plugins */
       
    33     private static $mu_plugins;
       
    34 
       
    35     /** @var int Number of plugins */
       
    36     private static $count;
       
    37 
       
    38     /** @var array Newly activated plugins */
       
    39     private static $activated;
       
    40 
       
    41     /** @var string Relative path to the mu-plugins dir */
       
    42     private static $relative_path;
       
    43 
       
    44     /** @var static Singleton instance */
       
    45     private static $_single;
       
    46 
       
    47     /**
       
    48      * Create singleton, populate vars, and set WordPress hooks
       
    49      */
       
    50     public function __construct()
       
    51     {
       
    52         if (isset(self::$_single)) {
       
    53             return;
       
    54         }
       
    55 
       
    56         self::$_single = $this;
       
    57         self::$relative_path = '/../' . basename(__DIR__);
       
    58 
       
    59         if (is_admin()) {
       
    60             add_filter('show_advanced_plugins', [$this, 'showInAdmin'], 0, 2);
       
    61         }
       
    62 
       
    63         $this->loadPlugins();
       
    64     }
       
    65 
       
    66    /**
       
    67     * Run some checks then autoload our plugins.
       
    68     */
       
    69     public function loadPlugins()
       
    70     {
       
    71         $this->checkCache();
       
    72         $this->validatePlugins();
       
    73         $this->countPlugins();
       
    74 
       
    75         array_map(static function () {
       
    76             include_once WPMU_PLUGIN_DIR . '/' . func_get_args()[0];
       
    77         }, array_keys(self::$cache['plugins']));
       
    78 
       
    79         $this->pluginHooks();
       
    80     }
       
    81 
       
    82     /**
       
    83      * Filter show_advanced_plugins to display the autoloaded plugins.
       
    84      * @param $show bool Whether to show the advanced plugins for the specified plugin type.
       
    85      * @param $type string The plugin type, i.e., `mustuse` or `dropins`
       
    86      * @return bool We return `false` to prevent WordPress from overriding our work
       
    87      * {@internal We add the plugin details ourselves, so we return false to disable the filter.}
       
    88      */
       
    89     public function showInAdmin($show, $type)
       
    90     {
       
    91         $screen = get_current_screen();
       
    92         $current = is_multisite() ? 'plugins-network' : 'plugins';
       
    93 
       
    94         if ($screen->base !== $current || $type !== 'mustuse' || !current_user_can('activate_plugins')) {
       
    95             return $show;
       
    96         }
       
    97 
       
    98         $this->updateCache();
       
    99 
       
   100         self::$auto_plugins = array_map(function ($auto_plugin) {
       
   101             $auto_plugin['Name'] .= ' *';
       
   102             return $auto_plugin;
       
   103         }, self::$auto_plugins);
       
   104 
       
   105         $GLOBALS['plugins']['mustuse'] = array_unique(array_merge(self::$auto_plugins, self::$mu_plugins), SORT_REGULAR);
       
   106 
       
   107         return false;
       
   108     }
       
   109 
       
   110     /**
       
   111      * This sets the cache or calls for an update
       
   112      */
       
   113     private function checkCache()
       
   114     {
       
   115         $cache = get_site_option('bedrock_autoloader');
       
   116 
       
   117         if ($cache === false) {
       
   118             $this->updateCache();
       
   119             return;
       
   120         }
       
   121 
       
   122         self::$cache = $cache;
       
   123     }
       
   124 
       
   125     /**
       
   126      * Get the plugins and mu-plugins from the mu-plugin path and remove duplicates.
       
   127      * Check cache against current plugins for newly activated plugins.
       
   128      * After that, we can update the cache.
       
   129      */
       
   130     private function updateCache()
       
   131     {
       
   132         require_once ABSPATH . 'wp-admin/includes/plugin.php';
       
   133 
       
   134         self::$auto_plugins = get_plugins(self::$relative_path);
       
   135         self::$mu_plugins   = get_mu_plugins();
       
   136         $plugins            = array_diff_key(self::$auto_plugins, self::$mu_plugins);
       
   137         $rebuild            = !is_array(self::$cache['plugins']);
       
   138         self::$activated    = $rebuild ? $plugins : array_diff_key($plugins, self::$cache['plugins']);
       
   139         self::$cache        = ['plugins' => $plugins, 'count' => $this->countPlugins()];
       
   140 
       
   141         update_site_option('bedrock_autoloader', self::$cache);
       
   142     }
       
   143 
       
   144     /**
       
   145      * This accounts for the plugin hooks that would run if the plugins were
       
   146      * loaded as usual. Plugins are removed by deletion, so there's no way
       
   147      * to deactivate or uninstall.
       
   148      */
       
   149     private function pluginHooks()
       
   150     {
       
   151         if (!is_array(self::$activated)) {
       
   152             return;
       
   153         }
       
   154 
       
   155         foreach (self::$activated as $plugin_file => $plugin_info) {
       
   156             do_action('activate_' . $plugin_file);
       
   157         }
       
   158     }
       
   159 
       
   160     /**
       
   161      * Check that the plugin file exists, if it doesn't update the cache.
       
   162      */
       
   163     private function validatePlugins()
       
   164     {
       
   165         foreach (self::$cache['plugins'] as $plugin_file => $plugin_info) {
       
   166             if (!file_exists(WPMU_PLUGIN_DIR . '/' . $plugin_file)) {
       
   167                 $this->updateCache();
       
   168                 break;
       
   169             }
       
   170         }
       
   171     }
       
   172 
       
   173     /**
       
   174      * Count the number of autoloaded plugins.
       
   175      *
       
   176      * Count our plugins (but only once) by counting the top level folders in the
       
   177      * mu-plugins dir. If it's more or less than last time, update the cache.
       
   178      *
       
   179      * @return int Number of autoloaded plugins.
       
   180      */
       
   181     private function countPlugins()
       
   182     {
       
   183         if (isset(self::$count)) {
       
   184             return self::$count;
       
   185         }
       
   186 
       
   187         $count = count(glob(WPMU_PLUGIN_DIR . '/*/', GLOB_ONLYDIR | GLOB_NOSORT));
       
   188 
       
   189         if (!isset(self::$cache['count']) || $count !== self::$cache['count']) {
       
   190             self::$count = $count;
       
   191             $this->updateCache();
       
   192         }
       
   193 
       
   194         return self::$count;
       
   195     }
       
   196 }
       
   197 
       
   198 new Autoloader();