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