Composer packages and WPMU Plugins

I am by no means a composer expert. In fact it was something that Ale at Briteweb introduced me to a little over 9 months ago. So whilst the method that I’m describing below most definitely works – and is stable and solid – it is by no means the only, or indeed possibly best way to accomplish this task.

Composer packages, by default, are placed into their own directory, within the ‘install path’ that you specify. If you use the wpackagist repository then you have access to the wordpress-muplugin type. Unfortunately, WordPress Must Use (MU) plugins – by default – must be in the root of the mu-plugins directory (which by default is mu-plugins in your wp-content directory, but can be defined using WPMU_PLUGIN_DIR and WPMU_PLUGIN_URL ). This means that, without any modification, any composer package that is installed into the mu-plugins directory simply won’t work. Bummer.

Composer and WordPress MU Plugins

However, yay for filters. With a little minor hackery and a bit of poking around WP source, we’re able to come up with a half-decent solution that isn’t too inefficient and fits within the composer methodology (to a point).

The full code for this plugin can be found on github – it’s relatively well commented, so should be fairly self-explanatory. But, the beef of it is hook into muplugins_loaded and look for files in subdirectories, too. Also, set a transient so it’s not loaded on every page load, and automatically clear that transient whenever we’re on a dashboard’s plugins page. We also modify the output of the Must Use tab in the network admin’s plugin page to show which plugins are loaded from subdirectories.

You can place this file into the MU plugins directory (we have it in our shared folder which then symlinks this file on a deploy, more about that some other time).

Because I like owning my own data and just in case github goes nuts and gets rid of gists, here’s a fully copy of the source code;


    /**
     * Plugin Name: MU plugins subdirectory loader
     * Plugin URI: http://code.ctlt.ubc.ca
     * Description: Enables the loading of plugins sitting in mu-plugins (as folders)
     * Version: 0.1
     * Author: iamfriendly, CTLT
     * Author URI: http://ubc.ca/
     *
     */

    class CTLT_Load_MU_Plugins_In_SubDir
    {

        // The transient name
        static $transientName = 'mu_plugins_in_sub_dir';

        /**
         * Set up our actions and filters
         *
         * @author Richard Tape <@richardtape-->
         * @package CTLT_Load_MU_Plugins_In_SubDir
         * @since 1.0
         * @param null
         * @return null
         */

        public function __construct()
        {

            // Load the plugins
            add_action( 'muplugins_loaded', array( $this, 'muplugins_loaded__requirePlugins' ) );

            // Adjust the MU plugins list table to show which plugins are MU
            add_action( 'after_plugin_row_subdir-loader.php', array( $this, 'after_plugin_row__addRows' ) );

        }/* __construct() */


        /**
         * Will clear cache when visiting the plugin page in /wp-admin/.
         * Will also clear cache if a previously detected mu-plugin was deleted.
         *
         * @author Richard Tape <@richardtape>
         * @package CTLT_Load_MU_Plugins_In_SubDir
         * @since 1.0
         * @param null
         * @return (array) $plugins - an array of plugins in sub directories in the WPMU plugins dir
         */
        
        public static function WPMUPluginFilesInSubDirs()
        {

            // Do we have a pre-existing cache of the plugins? This checks in %prefix%_sitemeta
            $plugins = get_site_transient( static::$transientName );

            // If we do have a cache, let's check the plugin still exists
            if( $plugins !== false )
            {

                foreach( $plugins as $pluginFile )
                {

                    if( !is_readable( WPMU_PLUGIN_DIR . '/' . $pluginFile ) )
                    {

                        $plugins = false;
                        break;

                    }

                }

            }

            if( $plugins !== false ){
                return $plugins;
            }


            // Check we have access to get_plugins()
            if( !function_exists( 'get_plugins' ) ) {
                require ABSPATH . 'wp-admin/includes/plugin.php';
            }

            // Start fresh
            $plugins = array();

            foreach( get_plugins( '/../mu-plugins' ) as $pluginFile => $pluginData )
            {

                // skip files directly at root (WP already handles these)
                if( dirname( $pluginFile ) != '.' ){
                    $plugins[] = $pluginFile; 
                }

            }

            // OK, set the transient and...
            set_site_transient( static::$transientName, $plugins );

            // ...ship
            return $plugins;

        }/* WPMUPluginFilesInSubDirs() */

        

        /**
         * Delete the transient if we're on an individual site's plugins page
         * Require each of the MU plugins
         *
         * @author Richard Tape <@richardtape>
         * @package CTLT_Load_MU_Plugins_In_SubDir
         * @since 1.0
         * @param null
         * @return null
         */
        
        public function muplugins_loaded__requirePlugins()
        {

            // delete cache when viewing the plugins page in the dashboard
            if( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], '/wp-admin/plugins.php' ) !== false ){
                delete_site_transient( static::$transientName );
            }

            // Now load each plugin in a subdir
            foreach( static::WPMUPluginFilesInSubDirs() as $pluginFile ){
                require WPMU_PLUGIN_DIR . '/' . $pluginFile;
            }

        }/* muplugins_loaded__requirePlugins() */


        /**
         * Quick and dirty way to display which plugins are MU and slightly adjust their layout 
         * to show which ones are subdir or not
         *
         * @author Richard Tape <@richardtape>
         * @package CTLT_Load_MU_Plugins_In_SubDir
         * @since 1.0
         * @param null
         * @return null
         */
        
        public function after_plugin_row__addRows()
        {

            foreach( static::WPMUPluginFilesInSubDirs() as $pluginFile )
            {

                // Super stripped down version of WP_Plugins_List_Table
                $data   = get_plugin_data( WPMU_PLUGIN_DIR . '/' . $pluginFile );
                $name   = empty( $data['Plugin Name'] ) ? $pluginFile : $data['Plugin Name'];
                $desc   = empty( $data['Description'] ) ? ' ' : $data['Description'];
                $id     = sanitize_title( $name );

                echo static::getPluginRowMarkup( $id, $name, $desc );

            }

        }/* after_plugin_row__addRows() */


        /**
         * Helper function to output a table row in the MU plugins list
         *
         * @author Richard Tape <@richardtape>
         * @package CTLT_Load_MU_Plugins_In_SubDir
         * @since 1.0
         * @param (string) $id - plugin ID (slug of the $name)
         * @param (string) $name - Name of the plugin
         * @param (string) $desc - The plugin's description
         * @return (string) the markup for this plugin
         */

        public static function getPluginRowMarkup( $id, $name, $desc )
        {

            $output = ' +  ' . $name . '

‘ . $desc . ‘


                    
                
            ';

            return $output;

        }/* getPluginRowMarkup() */
        

    }/* class CTLT_Load_MU_Plugins_In_SubDir */

    global $CTLT_Load_MU_Plugins_In_SubDir;
    $CTLT_Load_MU_Plugins_In_SubDir = new CTLT_Load_MU_Plugins_In_SubDir();
        

2 comments

  1. this is working great except with single WP install (not multisite) on subdomain. The plugins in mu-plugins are not loaded, activated or listed in dashboard.
    I’ve set up a subdomain (dev) for staging purpose on my production server (Apache). Do you know what should I edit to get the autoloader to work in my case? The autoloader himself or some config on the server?
    Thanks in advance,
    olivier

    1. Hi Oliver,

      This very site runs with a WordPress Mulstisite subdomain install and it works just great. Chances are it’ll be a web server configuration issue. I use nginx and not Apacha so I’m not sure I can help too much. Over the next few weeks I’m going to be doing a ‘from scratch’ tutorial which will include all changes I make, so maybe that will help.

Leave a comment

Your email address will not be published. Required fields are marked *