How to make your plugin have universal settings on Multisite WordPress?

While updating my Check Amazon Links plugin to work with WordPress Multisite, I struggled with how I could make my plugin settings universal across all network installs.  More specifically, I wanted it to be an option that could be turned on and off.  I tried to find a way to implement a hook or filter for when the plugin options were being updated via the settings page, but I had so much trouble with that, that I finally decided to use some Javascript.  Yay for javascript!  Sometimes it’s the easier option.

First, I added the option to my settings page, but only on multisite installs.  Here’s what it looks like:

Multisite Option on my WordPress Plugin (Check Amazon Links)

Next, I wrote the Javascript (jQuery) code to respond to submit events:


jQuery(document).ready(function () {
    var option_page_name = jQuery('input[name=option_page]').val();
    if (typeof(option_page_name) != "undefined") {
        if(option_page_name === 'amazon_link_plugin_options') {
            jQuery('form').submit(function(e) {
                var update_all = jQuery('select[name="azlc_multisite_same[toggle]"]').val();
                if(update_all === '1') {
                    jQuery.post(ajaxurl, 'action=azlc_ms', function(){});
                }

            });
        }
    }

});

As you see, my code checks the page name to insure that it only sends the AJAX request if it’s MY plugin’s page.   I add that javascript code to a javascript file I already enqueue.

Here’s the PHP WordPress Backend code for handling the Ajax request:


if(is_multisite()) {
	add_action('wp_ajax_azlc_ms', 'azlc_ms');
}

function azlc_ms() {
	add_site_option( 'azlc_update_from', get_current_blog_id() );
}

 

And here’s the PHP WordPress code the updates the options:



if(is_multisite()) {
	add_action('plugins_loaded', 'azlc_multisite');
}


function azlc_multisite() {
	global $wpdb;

	// check for flag 
        // I call this a flag because its existence means that this code needs to run.
        // You could write this to make it more human readable
	$blog_id = get_site_option('azlc_update_from');
	if($blog_id) {
		// switch to the blog to copy the actions from
		switch_to_blog($blog_id);
		$options = get_option('azlc_plugin_options');
		// copy settings to all sites
		AmazonLinkCheckerCore::copy_settings_from($blog_id, 'azlc_plugin_options', $options );
		// remove flag
		delete_site_option('azlc_update_from');
		// switch back
		restore_current_blog();
	}
}

 

And here’s the code that actually does the copying:


	public static function copy_settings_from($source_id, $option_name, $option_value) {
				global $wpdb;
				$sql = "SELECT blog_id FROM $wpdb->blogs";
				$blog_ids = $wpdb->get_col($sql);
				foreach($blog_ids as $blog_id) {
					// update option on other blogs
					if($blog_id!=$source_id) {
						switch_to_blog($blog_id);
						update_option($option_name, $option_value);
					}
				}
				restore_current_blog();
	}

 

Related Tutorial

Don’t miss this related article: How to Enable WordPress Multisite for your Plugin

Your feedback is important

I write this blog with sincere hope that I can help other WordPress developers.  Please let me know if this helped you, or if I made a bad error.  Thanks!

How to make your plugin compatible with WordPress Multisite?

How to Enable WordPress Multisite for your Plugin

So you have written a WordPress plugin, and need to enable it to work with WordPress Multisite.  Multisite allows the Network admins to install plugins on all their network blogs at once.

When it comes to plugin development, adding this feature is really not too difficult, but there are not many tutorials available to follow.  And while working on this feature, I came across one prominent tutorial with code that just didn’t  work.  So here’s the code that actually works!  This is written for version 4.3 and should work for WordPress versions as early as 3.0.0.

The code shown below is an excerpt from my Check Amazon Links WordPress Plugin.  Of course, you will have to change the function and class names to the names used in your plugin.

Code that actually works!

First, the Hook.  My hook looks like this because my activate function is inside of the class AmazonLinkCheckerCore:


register_activation_hook( __FILE__, array( 'AmazonLinkCheckerCore', 'activate' ) );

This hook will run on both single sites and multisite WordPress installations.  There is no need to specify a different activation hook for multisite, if you write the function as follows.

The function that’s called:


     public static function activate($network_wide) {
		if(is_multisite() && $network_wide) { 
                // running on multi site with network install
		        global $wpdb;
			$activated = array();
			$sql = "SELECT blog_id FROM $wpdb->blogs";
			$blog_ids = $wpdb->get_col($sql);
			foreach($blog_ids as $blog_id) {
				switch_to_blog($blog_id);
				AmazonLinkCheckerCore::implement_activation();
				$activated[] = $blog_id;
			}
			restore_current_blog();
			update_site_option('azlc_multisite_activated', $activated);

		} else { // running on a single blog
			AmazonLinkCheckerCore::implement_activation();
		}

		// this sets a transient and should only be done once 	
                // put any code that should only happen once, network-wide, here:
		    self::activate_about_page();

	}

Note that my activation code uses a transient to display the settings page after activation. Setting this transient on each blog caused a big bug, so I moved that code outside of the foreach loop.  If you don’t use a transient, just delete that line, but I left it here for demonstration purposes.  If you do use a transient, then change the code to point to the function that you wrote that generates the transient.

The function that’s called by the above function:


      public static function implement_activation() {
            // put your activation code here
            // for example, install databases
            // initialize options
            // whatever your plugin already does on activation, move here
      }

 

You will also need to change your deactivation code so that it works in a similar manner to the above activation code.  Depending on your plugin, you may need to loop through all of the blogs and perform your deactivate code on each one.

Add this to your deactivation code:


delete_site_option('azlc_multisite_activated');

Also, add code to check if any new blogs were installed on the network

This code will check if there are any new blogs installed and it will activate the plugin on those too.   This ensures that your plugin always runs on every single blog on the network.


	add_action('admin_init', array('AmazonLinkCheckerCore', 'multisite_check_activated'));

	public static function multisite_check_activated() {
		global $wpdb;
		$activated = get_site_option('azlc_multisite_activated');
		if($activated == 'false') {
			return false;
		} else {
			$sql = "SELECT blog_id FROM $wpdb->blogs";
			$blog_ids = $wpdb->get_col($sql);
			foreach($blog_ids as $blog_id) {
				if(!in_array($blog_id, $activated)) {
					switch_to_blog($blog_id);
					AmazonLinkCheckerCore::implement_activation();
					$activated[] = $blog_id;
				}
			}
			restore_current_blog();
			update_site_option('azlc_multisite_activated', $activated);
		}
	}

 

 

Other Considerations

  • If you have a settings page, it will appear on each blog.  If you want to make the settings universal across all network blogs, you will have to implement a way to copy the settings from one blog to all.  Read this tutorial: How to make your plugin have universal settings on Multisite WordPress.
  • You may find settings that should always be the same on all blogs.  In that case, you want the setting to be a SITE OPTION instead of a regular option.  Instead of “update_option” use “update_site_option.”

Your feedback is important

I write this blog with sincere hope that I can help other WordPress developers.  Please let me know if this helped you, or if I made a bad error.  Thanks!

 

How to Debug WordPress WP-Cron Jobs?

Debugging php code that’s run by WordPress Cron is difficult because:

1) If you don’t know how to force the wp-cron job to run, you have to wait for it to be called on schedule. WP-Cron jobs are often run on an interval like hourly, daily, etc.

2) You can’t see any error messages on the website, even if you have WP-DEBUG set to TRUE.

Today I’ve had a lot of success in debugging wp-cron jobs. This is what I now know how to do, and I’m saving it here for my own future reference, and perhaps it will help you too!

Force WP-Cron Job to Run

This worked for me in WordPress 4.2.2
1. Install the plugin Core Control.
2. In WordPress Admin, go to Tools->Core Control.
3. Check off “Cron Module” and click save.
4. Click the link “Cron Tasks”
5. Click on “Run Now” next to the cron job you are debugging.
6. If an error occurs, it will say “Error occurred” at the top of the page.

Wordpress Core Control Plugin Cron Tasks Module
WordPress Core Control Plugin Cron Tasks Module

View PHP Error Messages

There are two ways I was able to view the error message today.  There should be a better way, and perhaps there is, but this is what I figured out.

View PHP Error Log

It turns out that my development server, which is a WAMP setup, is already recording all php errors in this file:

E:\wamp\logs\php_error.log

If you are running a local server, you may discover that error messages are already being recorded.  If not, here are instructions on how to set up the PHP error log for WordPress.

Because the log file had been recording errors (and notices) for what seems like a century, the file was huge.  To simplify debugging, I deleted the log file contents and saved it as a blank file.  Then I ran my cron job and checked the file again.  Ta-da!  My error was prominently displayed.

View PHP Error Message in PHP Storm with XDebug

 

If you can’t log errors or view the PHP error log, you can view the error while debugging… Keep reading to find out how…

Fix Problems that Don’t Cause Error Messages

Sometimes programming errors are errors in logic, or errors where you use the wrong variable (maybe a typo) and it doesn’t throw an exception but rather the program just doesn’t work correctly.

That’s when a debugger is really helpful!  If you don’t understand debugging, I suggest this Lynda.com course:  Debugging the Web: Javascript.  Yes, it’s about javascript, but it explains concepts that you can use in any programming language with any debugger. I watched it recently and the knowledge gained definitely applies to debugging WordPress.

These are the 3 pieces of software I use to debug WordPress and PHP:

  1. Xdebug
  2. PHP Storm
  3. XDebug Helper Chrome Extension

These are the tutorials I used to set up debugging:

How I debugged the WordPress Cron Job

  1. Set a breakpoint on the first line of the function called by wp-cron.
  2. Click “Play”
  3. When it stops at my breakpoint, click “Step Into” repeatedly until I see my error.

In my case, the error message was displayed in the debugger, next to the error_handler function (see the line above the blue line).  The text is gray, but it shows the contents of the $message variable:

Debugging WordPress with PHP Storm

 

And there you go, one more way to view your PHP error.  Hope this has helped you.  If you have any questions, I will try to answer them.

More About WP-Cron

WP-Cron can cause WordPress problems and might make your site run slow.  But, cron jobs often do very important tasks and disabling them will cause problems.  Here is how to fix this issue:

Properly Setting Up WordPress Cron Jobs