littlebizzy / autoloader

Loads folder WordPress MU plugins
https://bizzy.in/autoloader
GNU General Public License v3.0
16 stars 2 forks source link

Merge plugins and mu-plugins when get_plugins function is called #2

Open radeno opened 3 years ago

radeno commented 3 years ago

This is follow up ticket for #1

Motivation?

Lot of plugins interacts with each others and they checks if plugin is installed and what is more important when different plugin is active. For example plugins extensions or plugins which uses API of different plugins (dependencies).

By my experience they are using get_plugins function to check if specific plugin is installed and active.

In the current situation mu-plugins are loaded automatically, but WP does know nothing about them.

Implementation?

Curiously it could be very easy. get_plugins checks every sub-folder in the plugins folder. And this plugin is used heavily. This function doesn't have any filter what is sad, but happily it uses wp_get_cache, so there is a workaround.

Step 1

Copy code from get_plugins (https://developer.wordpress.org/reference/functions/get_plugins/) and change just wp_cache_get and wp_cache_set namespace to mu_plugins and $plugin_root to WPMU_PLUGIN_DIR. Name this function for example as get_mu_autoloader_plugins()

Step 2

Merge both lists together. Eg:

\add_action('init', function() {
    $plugins = \get_plugins();
    $muPlugins = get_mu_autoloader_plugins();

    \wp_cache_set( 'plugins', ['' => array_merge($plugins, $muPlugins)], 'plugins' );
});

This code is not optimized, so cache is rewrite on every request, which is not good.

Drawbacks?

Listed all mu-plugins as regular plugins. Which is not good.

Drawbacks fixes

Append specific array key which will say that that plugin is MU plugin. Then in the all_plugins filter, just skip this plugins list.

Proof of concept, workable code


// almost 1:1 with https://developer.wordpress.org/reference/functions/get_plugins/
function get_mu_autoloader_plugins( $plugin_folder = '' ) {

    $cache_plugins = wp_cache_get( 'mu_plugins', 'mu_plugins' );
    if ( ! $cache_plugins ) {
        $cache_plugins = array();
    }

    if ( isset( $cache_plugins[ $plugin_folder ] ) ) {
        return $cache_plugins[ $plugin_folder ];
    }

    $wp_plugins  = array();
    $plugin_root = WPMU_PLUGIN_DIR;
    if ( ! empty( $plugin_folder ) ) {
        $plugin_root .= $plugin_folder;
    }

    // Files in wp-content/plugins directory.
    $plugins_dir  = @ opendir( $plugin_root );
    $plugin_files = array();

    if ( $plugins_dir ) {
        while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
            if ( '.' === substr( $file, 0, 1 ) ) {
                continue;
            }

            if ( is_dir( $plugin_root . '/' . $file ) ) {
                $plugins_subdir = @ opendir( $plugin_root . '/' . $file );

                if ( $plugins_subdir ) {
                    while ( ( $subfile = readdir( $plugins_subdir ) ) !== false ) {
                        if ( '.' === substr( $subfile, 0, 1 ) ) {
                            continue;
                        }

                        if ( '.php' === substr( $subfile, -4 ) ) {
                            $plugin_files[] = "$file/$subfile";
                        }
                    }

                    closedir( $plugins_subdir );
                }
            } else {
                if ( '.php' === substr( $file, -4 ) ) {
                    $plugin_files[] = $file;
                }
            }
        }

        closedir( $plugins_dir );
    }

    if ( empty( $plugin_files ) ) {
        return $wp_plugins;
    }

    foreach ( $plugin_files as $plugin_file ) {
        if ( ! is_readable( "$plugin_root/$plugin_file" ) ) {
            continue;
        }

        // Do not apply markup/translate as it will be cached.
        $plugin_data = get_plugin_data( "$plugin_root/$plugin_file", false, false );

        if ( empty( $plugin_data['Name'] ) ) {
            continue;
        }

        $plugin_data['IsMuPlugin'] = true;

        $wp_plugins[ plugin_basename( $plugin_file ) ] = $plugin_data;
    }

    uasort( $wp_plugins, '_sort_uname_callback' );

    $cache_plugins[ $plugin_folder ] = $wp_plugins;
    wp_cache_set( 'mu_plugins', $cache_plugins, 'mu_plugins' );

    return $wp_plugins;
}

// this hook is used in the plugins list WP admin page
add_filter('all_plugins', function($get_plugins) {
    return array_filter($get_plugins, function($plugin) { return !isset($plugin['IsMuPlugin']); });
});

// this action merge both plugin list, unoptimized
\add_action('init', function() {
    $plugins = \get_plugins();
    $muPlugins = get_mu_autoloader_plugins();

    \wp_cache_set( 'plugins', ['' => array_merge($plugins, $muPlugins)], 'plugins' );
});
radeno commented 3 years ago

We can fix when plugins requesting assets with plugins_url function.

Within add_action init we can replace URLs

$muPluginList = array_filter(array_map(function($pluginKey) { return substr($pluginKey, 0, strrpos( $pluginKey, '/') ?: 0); }, array_keys(get_mu_autoloader_plugins())));
\add_filter('plugins_url', function($url, $path, $plugin) use($muPluginList) {
    if (strpos($url, WP_PLUGIN_URL . '/') === false) {
        return $url;
    }

    foreach ($muPluginList as $plugin) {
        $pluginUrl = WP_PLUGIN_URL . '/' . $plugin;
        $muPluginUrl = WPMU_PLUGIN_URL . '/' . $plugin;

        if (strpos($url, $pluginUrl) !== false) {
            return str_replace($pluginUrl, $muPluginUrl, $url);
        }
    }
}, 10, 3);