When I was putting together the site for my Justified Image Grid gallery plugin, I ran into a severe problem. It began accumulating a lot of plugins, merely to showcase their compatibility on dedicated demo pages. Things like WooCommerce or Lightbox plugins were cluttering not only my admin area, but they were active on the front-end too, on all pages. This chaos did hurt performance, I could tell even without measuring it. There needed to be a way to manipulate the list of active plugins, most specifically to disable a WordPress plugin on specific pages. Or better yet, only enable them wherever needed. The following method allows you to sneak in many plugins to your WordPress installation, without a significant performance hit.
Must Use Plugins to the rescue
The approach uses the WordPress concept called Must Use Plugins. When you look at the WordPress loading sequence
(of actions fired, not files loaded), you’ll understand why. It’s the first action that runs, therefore Must Use Plugins load so early you can direct the rest of the loading flow in any way you like, for example, to disable a WordPress plugin. In a nutshell, Must Use Plugins are single files living in the wp-content/mu-plugins folder. They cannot be controlled (just seen) in the WordPress Dashboard. If you need more information on how and why they work, check out Must Use Plugins
in the WordPress Codex.
I’ll call this creation Plugin Control from now on. I’ll show you how it works, function by function. You can download the complete plugin for free at the end of the article. However, I strongly suggest you learn or at least understand what happens here, as this is not an autopilot solution. It has the power to lock any plugin, either in an enabled or disabled state. Unless you know what’s going on, it might lead to frustration.
The code to disable a WordPress plugin on specific pages
So, Plugin Control starts with a minimal plugin header, that’s all it takes to make it a WordPress plugin! (As pointed out when I recommended creating plugins to add snippets.)
The first function is where you edit the rules, namely the $enabling_rules
and $disabling_rules
arrays. The array keys should be the relative path to each plugin’s file in the plugins folder. Most commonly this comprises the folder name and the plugin file name. You’ll write the rules to enable or disable a WordPress plugin on specific pages as array values for each plugin. The page rules can be relative URLs on your site or a fuzzy regex patterns. See more on this later.
You’ll notice WP Object Cache
spread all over the code. Caching is necessary because other scripts can request the list of active plugins at any time, multiple times. And they do. It wouldn’t make sense to process this list dozens of times over. Therefore once Plugin Control manipulates the active plugins list, it caches the results. This is in line with how WordPress caches the original list (which is an option from the database, using the option_{$option}
hook).
<?php
/*
Plugin Name: Let's WP Plugin Control
Description: Enable or Disable a WordPress Plugin on Specific Pages
*/
/**
* Controls the rules by which plugins are manipulated
*
* @param array $plugins List of active plugins
* @return array Modified list of active plugins
*/
function lwp_plugin_control($plugins)
{
$lwp_controlled_plugins = wp_cache_get('lwp_controlled_plugins', 'plugins');
if ($lwp_controlled_plugins !== false) {
return $lwp_controlled_plugins;
}
wp_cache_set('lwp_original_plugins', $plugins, 'plugins');
// Enable plugins on certain URLs
$enabling_rules = array(
'some-gallery/some-gallery.php' => array(
'/gallery/'
)
);
// Disable plugins on certain URLs
$disabling_rules = array(
'some-lightbox/some-lightbox.php' => array(
'/contact/$',
'/wp-admin/post.php'
)
);
// Run enables
$plugins = array_unique(
array_merge($plugins, lwp_plugins_affected_by($enabling_rules))
);
// Run disables
$plugins = array_diff($plugins, lwp_plugins_affected_by($disabling_rules));
wp_cache_set('lwp_controlled_plugins', $plugins, 'plugins');
return $plugins;
}
add_filter('option_active_plugins', 'lwp_plugin_control');
Matching the current URL to rules
Continuing with the heart of this plugin, it’s where all the magic happens. This function is universal in a way that it doesn’t care about enables or disables. It just matches the current page against your rules and returns the affected plugins. First, it tries to do an exact match on the current path. Then it resorts to regex matching to find partial matches. You can lock this behavior by using regex anchors such as $
to mark the end of the URL. In the code, passing an empty array to the add_query_arg()
is useful to get the current path the WordPress way.
/**
* Scan for affected plugins to manipulate
*
* @param array $rules Plugin files paired with URL rules
* @return array Affected plugins by their dir + file name
*/
function lwp_plugins_affected_by($rules)
{
$affected = array();
$current_path = add_query_arg(array());
foreach ($rules as $plugin => $paths) {
// If any of the paths match the current path
$matches = array_filter(
$paths,
function ($path) use ($current_path) {
return (
$path === $current_path ||
preg_match('%'.$path.'%', $current_path)
);
}
);
if (empty($matches)) {
continue;
}
$affected[] = $plugin;
add_filter('plugin_action_links_'.$plugin, 'lwp_add_action_links');
}
return $affected;
}
Marking affected plugins in the list
The curious ones already noticed me using the plugin_action_links_{$…}
hook. That’s optional, and you’ll only see its result when you manipulate plugins in the admin or dashboard. It’s generally safe to disable a WordPress plugin that is resource-heavy on some parts of the admin. When I go as far as force-disabling or force-enabling plugins even on the plugins list, I prefer to see a hint about them. The indication appears as a red message in place of the activate or deactivate button. They wouldn’t work anyway, so I replace them with a message from Plugin Control.
Please note that if you make changes the plugins list by hand, the enabled or disabled state of your controlled plugins will get baked in by the system as soon as you save. Even if you toggle unaffected plugins. This means you can’t keep “originally enabled” or “originally disabled” states for too long. Thankfully, Plugin Control doesn’t care about the original state of your plugins, as it can still enable or disable a WordPress plugin.
/**
* Show a red message if a plugin is affected by the MU plugin
* It explains why it's not possible to change it by hand
*
* @param array $links Links in the plugin row, like "Activate"
* @return array Changed links to show in the plugin row
*/
function lwp_add_action_links($links)
{
unset($links['activate'], $links['deactivate']);
array_unshift($links, '<span style="color:red;">Controlled by MU!</span>');
return $links;
}
Interference by 3rd party plugins
The following is entirely optional. Even on a small experimental installation, I ran into a plugin that doesn’t use best practices and causes conflicts. For some weird reason, that plugin (which shall not be named), saved the active plugins list on the frontend. This auto-saved the manipulated plugin list that Plugin Control conceived. The following part of the code detects unsubstantial changes such as re-ordering to the active plugins list and prevents them from being saved using the pre_update_option_{$option_name}
filter. This doesn’t affect manual plugin toggling on the plugins screen, though.
/**
* Prevent saving controlled plugin states by 3rd party plugins
*
* @param array $new_value New list of active plugins
* @param array $old_value Old list of active plugins
* @return array New list of active plugins
*/
function lwp_prevent_saving_plugins($new_value, $old_value)
{
$lwp_controlled_plugins = wp_cache_get('lwp_controlled_plugins', 'plugins');
$lwp_original_plugins = wp_cache_get('lwp_original_plugins', 'plugins');
sort($old_value);
sort($new_value);
sort($lwp_controlled_plugins);
if (($new_value === $old_value) &&
($old_value === $lwp_controlled_plugins)) {
return $lwp_original_plugins;
}
return $new_value;
}
add_filter(
'pre_update_option_active_plugins',
'lwp_prevent_saving_plugins',
10,
2
);
Download the Plugin Control Must Use Plugin
Even if you decided to skip most of this article, you still need to make changes to the rules. It’s necessary for Plugin Control to know in order to enable or disable a WordPress plugin. Your plugins and your pages will be different, meaning that this will not work out of the box. It’s a powerful tool, and since you are reading this article, the need to use something like this already came up. However, it’s up to you how you use it. You could performance test your results. For me, it’s often enough to know that unnecessary plugins are at rest. It gives me peace of mind and frees up some otherwise wasted CPU cycles. It’s all about the snappy feel of an empty WordPress installation vs. a bloated one.
Why do I extensively use this even on admin pages? Because no matter how many caching plugins and CDNs I throw at a site, they only benefit visitors that are not logged in. For me, managing the site can still be a crawl. This plugin will help you too, but you need to teach it the rules!
Update! (December 23, 2018): Added the plugins
group to the caching function calls, it helps when using the W3TC plugin.
Comments are closed.