WordPress: Network-wide plugin settings

When developing a plugin, which will be used on single site, as well as multi-site, there are two ways of supporting multi-site deployments:

  1. The plugin is activated per site and provides per-site settings only.
  2. The plugin can be activated for the whole network and provides network-wide settings

I’m currently working on a plugin which needs to support both scenarios. The first scenario is pretty straight forward. But the second one is a little bit trickier.

  • Proper support of the network activation
  • Activation for new sites
  • Network-wide settings
  • Proper support of the network deactivation

Proper support of the network activation

To activate the plugin in a multisite environment, you’ll need go through all the blogs and activate them individually e.g.:

register_activation_hook( __FILE__, 'my_plugin_activate' );

function my_plugin_activate($network_wide)
{
	global $wpdb;

	if (function_exists('is_multisite') && is_multisite() && $network_wide) {
		$current_blog = $wpdb->blogid;
		$blogs = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
		foreach ($blogs as $blog) {
			switch_to_blog($blog);
			my_plugin_activate();
		}
		switch_to_blog($current_blog);
	} else {
		my_plugin_activate();
	}
}

If we are in a multisite environment but the plugin is only activated to for a blog, a normal activation is performed. Otherwise we fetch the list of all blogs and activate them one by one before reverting to the current blog.

Note that you should not use restore current blog() to get back to the original blog since it will revert to the blog active before the last switch_to_blog(). So if switch_to_blog() is called twice it will not revert to the blog which was active before this all started.

Activation for new sites

When a new blog is added, you’ll need to specifically activate the plugin for this blog since this blog was not present, when the activation occurred.

add_action( 'wpmu_new_blog', 'activate_new_blog' );
 
function activate_new_blog($blog_id) {
    global $wpdb;
 
    if (is_plugin_active_for_network('plugin_name/plugin_main_file.php')) {
	switch_to_blog($blog_id);
	my_plugin_activate();
	restore_current_blog();
    }
}

So first we check whether the plugin was activated for the whole network. If it is the case, we switch to the new blog, activate the plugin for this blog and switch back to the previous blog. Here it is fine to use restore_current_blog() since we only use one switch_to_blog().

Network-wide settings

In single site environment, you would use the functions get_option() and update_option() to respectively read and write your plugin settings. In a multi-site environment, you should rather use the get_site_option() and update_site_option() functions instead.

So instead of:

get_option('my_settings', array())
...
update_option( 'my_settings', $settings );

Use:

get_site_option('my_settings', array())
...
update_site_option( 'my_settings', $settings );

When registering your settings page, instead of using options-general.php as parent slug, when calling add_submenu_page, if your plugin has been network activated, you’ll need to add the subm

enu page using settings.php as parent slug.

Also when linking to your settings page (i.e. from the plugins list), in a single site environment, you’d do it this way:

<a href="options-general.php?page=my_settings">Settings</a>

But to link to the network settings page, you’ll need to use the following

<a href="settings.php?page=my_settings">Settings</a>

The hook for adding the link is also different. Instead of adding a filter for ‘plugin_action_links_pluginname_pluginfile’, you’ll have to add a filter for ‘network_admin_plugin_action_links_pluginname_pluginfile’.

Also the hook to use to call your registering function is different, instead of adding an action to the ‘admin_menu’ hook, you should add an action to the ‘network_admin_menu’ hook in a network activation scenario.

In your settings page, when in a single site environment, you can use the settings API and do not need to update your settings manually, by using options.php as form action:

<form method="post" action="options.php">

For network-wide settings it’s not so easy. There is no direct equivalent to options.php for such settings. You will need to direct to edit.php and provide an action name which will be linked to one of your functions where you will update the settings. Here the registration and definition of this function:

add_action('network_admin_edit_my_settings', __FILE__, 'update_network_setting');

function update_network_setting() {
	update_site_option( 'my_settings', $_POST[ 'my_settings' ] );
	wp_redirect( add_query_arg( array( 'page' => my_settings, 'updated' => 'true' ), network_admin_url( 'settings.php' ) ) );
	exit;
}

Of course instead of my_settings, you should use the name of your settings. The name of the action hook is network_admin_edit_ followed by an action name which will be used as parameter to edit.php in the form definition e.g.:

<form method="post" action="edit.php?action=my_settings">

Of course, since you want to support both the normal and the network activation scenarios, you’ll have to use is_plugin_active_for_network() to figure out in which scenario you are and trigger the appropriate logic.

Proper support of the network deactivation

For the deactivation, you need to do exactly the same you’ve already done for the activation i.e.:

register_deactivation_hook( __FILE__, 'my_plugin_deactivate' );

function my_plugin_deactivate($network_wide)
{
	global $wpdb;

	if (function_exists('is_multisite') && is_multisite() && $network_wide) {
		$current_blog = $wpdb->blogid;
		$blogs = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
		foreach ($blogs as $blog) {
			switch_to_blog($blog);
			my_plugin_deactivate();
		}
		switch_to_blog($current_blog);
	} else {
		my_plugin_deactivate();
	}
}

function my_plugin_deactivate()
{
	//deactivate the plugin for the current blog
}

Of course in a network-wide deactivation scenario, you may want to add some additional cleanup after the plugin has been deactivated for all blogs.

Conclusion

So supporting all three scenarios (single site, multisite with per blog activation and multisite with network activation) is not so difficult. The only tricky part is handling network-wide settings. Unfortunately, the settings API does not help you much here. But once you’ve done it for a plugin, it’s just a matter of copy&paste.

Note that if you do not perform anything special as part of your activation, you do not need to handle the multisite activation since WordPress will activate the plugins appropriately. It is only required if your plugin does some additional magic since WordPress will not call you for each blog.

Leave a Reply

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