I went through with the "walk all blogs" plan. My code is crude but it works.
First, I took the plugin enumeration function from the Plugin Activation Status plugin. Since that plugin does nothing but list which plugin is active where it was a convenient place to get the functionality from.
My version of the function was cut down in scope and merely returns an associative array with the blog ID as the key and an array of plugin basenames as the value. Network-wide plugins are included under the "network" key.
// Note: Since the source plugin is licensed under GPLv2 or later, so is this snippet.
function parse_plugins() {
global $wpdb;
$blogs = $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs} ORDER BY blog_id" );
$network_plugins = $wpdb->get_results( $wpdb->prepare( "SELECT site_id, meta_value FROM {$wpdb->sitemeta} WHERE meta_key=%s", 'active_sitewide_plugins' ) );
$active = array('network' => array());
foreach ( $network_plugins as $k => $val ) {
$v = maybe_unserialize( $val->meta_value );
if ( ! is_array( $v ) )
continue;
if ( count( v ) <= 0 )
continue;
$tmp = array_values( $v );
/**
* Some records are stored with the plugin name as the key & the timestamp
* of activation as the value; others are stored with just the plugin
* name as the value, with numeric keys
*/
$v = is_numeric( $tmp[0] ) ? array_keys( $v ) : array_values( $v );
$active['network'] = array_merge( $active['network'], $v );
}
/**
* Retrieve all of the plugins active on individual sites
*/
foreach ( $blogs as $b ) {
$wpdb->set_blog_id( $b );
$plugins = maybe_unserialize( $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name=%s", 'active_plugins' ) ) );
if ( ! is_array( $plugins ) )
continue;
$tmp = array_values( $plugins );
if ( count( $tmp ) <= 0 )
continue;
/**
* Some records are stored with the plugin name as the key & the timestamp
* of activate as the value; others are stored with just the plugin
* name as the value, with numeric keys
*/
$plugins = is_numeric( $tmp[0] ) ? array_keys( $plugins ) : array_values( $plugins );
$active[$b] = $plugins;
}
return $active;
}
Now that we know which plugin is active where we can walk the network and deactivate them.
function deactivate_all_the_plugins()
{
$active_plugins = parse_plugins();
// Add the basenames of all unwanted plugins here
$target_plugins = array(
'hello.php',
'akismet/akismet.php',
'some-other-plugin/other_plugin.php'
);
foreach ($active_plugins as $blog_id => $blog_plugins) {
$plugins = array_intersect($target_plugins, $blog_plugins);
if (!empty($plugins)) {
if ($blog_id == 'network') {
deactivate_plugins($plugins, false, true);
}
else {
switch_to_blog($blog_id);
deactivate_plugins($plugins, false, false);
restore_current_blog();
}
}
echo '<h1>'.$blog_id.'</h1>'; var_dump($plugins);
}
}
I don't trigger uninstallation hooks here; the assumption is that the plugin will clean up sufficiently on deactivation. Also, I'm not certain on how to trigger them without actually running the "delete plugin" code and WordPress doesn't have the FTP credentials. Still, it's better than nothing.