Get it All
Together

This plugin is deprecated and no longer maintained by the developer.

The new 2.7 version of WordPress MU is pretty awesome and I’m glad to have it running on both of my sites right now. There’s a lot 2.7 has to offer in terms of usability boosts, and while I balked at the redesign when it first started, I have to confess that there is much to love about the new layout.

However, one thing that’s been nagging me is the new Admin Bar functionality. It suffers from two things, in my opinion:

  1. It’s a blog by blog setting, not sitewide. This is fine for sites where you want savvy people to start blogging on your network. It’s less so for community based sites like mine, where the idea is a sort of news magazine site where consistency is key.
  2. Because plugins can add their own top-level menus, the Admin Bar can get a bit crowded and I’d like the option to add or subtract items from the list as I see fit. Again, it would be nice to have these settings happen globally rather than by site.

While I have not worked out the second problem just yet, I thought I’d go ahead and post my modified code to this site which allows site-wide settings rather than by-site. It also tucks the Admin Bar settings under the Admin section rather than Options.

There’s nothing magical here: all I did was change every occurrence of “get_option” and “update_option” to “get_site_option” and “update_site_option.” Then, I just modified the menu statement to look like the following:
// Register the settings page
function AddAdminMenu() {
add_submenu_page('wpmu-admin.php', __('WordPress Admin Bar', 'wordpress-admin-bar'), __('Admin Bar', 'wordpress-admin-bar'), 'read', 'wordpress-admin-bar', array(&$this, 'SettingsPage') );
}

Download the file here and replace the one currently sitting in /wp-includes/wordpress-admin-bar/

Meanwhile, I’m going to work on tweaking the plugin to make it more flexible for MU as I go.

I’ve been struggling to understand exactly how the cron functions of WordPress work.

I’ve been working on a plugin for some friends of mine that need a mailing list manager, this plugin in turn requires the ability to schedule tasks. So, I spent a long time puzzling over the various bits of documentation and posts on other blogs about WP-Cron, pouring over code and experimenting with my own, until I finally got a working plugin.

And while I’m sure that the information that is out there now about Cron functions is adequate for some, it’s clear to me that others may need a different set of explanations, which is where this blog post comes in. I’m going to attempt to lay out the WP-Cron world in a different way in hopes of improving slightly on what’s out there.

Let me start by saying that the best article I’ve read on the subject of crons is definitely “Timing is everything: scheduling in WordPress,” by Glenn Slaven. I highly recommend that, even before reading this article, you take a gander at that one. There are really just a few things on which I would like to expand and revise slightly, because they threw me off. I’m also slightly reorganizing the order of discussion into something more akin to a step-by-step set of instructions.

Editor Update: Ack! Looks like this other article is no longer available. Sorry, everybody!

The Scenario

The example we’re going to work with is the mailing list manager plugin I’m developing. We’re not going to get into too much of the actual code of that plugin, but the scenario is doubtless a common need. We’ll be assuming that we’re working with a blog which has many, many users. (real world example: 400+) We want to keep our users up to date with periodic email blasts. Rather than those blasts being separated from the regular content, we want to specify that certain posts will become both posts on the site and email messages.

But of course, sending emails out to 400+ recipients at once is going to make the SMTP server very, very suspicious. In order to avoid problems with the mail server, we need to throttle the messages by sending blasts every twenty minutes ((or at least, approximately twenty minutes, remember that WordPress cannot be exact about timing)) until the last user group has been emailed. To do this, we will be using the wp_schedule_single_event() function within the WP-Cron suite of tools.

Scheduling: What You’ll Need

In order to schedule an event of any kind, you’re going to need a few basic components. We’ll get into more detail further down the line, but it’s important to be able to identify the landmarks as we proceed:

  • Your Cron Function ~ This is the actual function that does the stuff you want when the scheduled event is fired
  • A Custom Action Hook ~ Here’s where we get into the nitty-gritty of how WordPress operates. It’s very simple, actually, but understanding how to create custom action hooks is key to understanding WordPress cron functions. And a great deal more about WordPress!!
  • Your Scheduling Function ~ This is the function that initially assigns the scheduled task.

Your Cron Function

my_scheduled_function([$arg1, [$arg2, [$arg3. . . ]]])

This function lies at the heart of your scheduling world! It is here that you put all the stuff you want to get done at a specified time. In our example, this is where you actually send the email to your latest batch of users. Note that this function can include an arbitrary number of arguments. We’ll get into how you pass those arguments on to the function later. For now, just notice that this is a standard function like pretty much any other. In fact, in the case of the mailing list manager I created, it’s as simple a function as you could ask for:

function bhd_cron_send($sender_email, $subject, $mail_text, $send_headers) {
	wp_mail($sender_email, $subject, $mail_text, $send_headers);
}

It’s probably advisable to create your function and make sure it works without adding in the cron functionality first. This is because a) there’s no need adding extra complexity to the system until you’re sure the function works and b) you’ll need to know how many arguments will need to be passed to the function and in what order.

Your Custom Action Hook

This is what really threw me. I’ve never created my own custom action hooks and never really understood them, but you’ll need to in order to get things done with cron, so this is important. Creating a custom hook is as simple as typing the following:

add_action('my_custom_hook', 'some_function' [, $priority, [$num_args]]);

WordPress assumes that anything added as the first argument exists without any additional registration of that new hook. In other words, ‘my_custom_hook’ actually is the declaration of the action hook in this example. The second argument should be a valid function name and will be the function associated with ‘my_custom_hook’ whenever ‘my_custom_hook’ is invoked anywhere in the system.

Obviously, since you’re writing your custom action, you’ll also need to invoke that custom action somewhere. That’s the last piece of the puzzle, which is explained down the page a bit.

But before we leave this topic, it’s worth pointing out that, just like any other action, it’s entirely possible to hook more than one function into the custom action you’ve created. It’s also possible to invoke the hook somewhere else in your code, separate from your Cron function. This bit of WordPress theory may prove helpful to you down the line, so it’s just an FYI.

The last two arguments of the add_action function are important. Recall the number of arguments you needed for your scheduling function. That’s the number you’re going to want to put in the $num_args add_action parameter. Otherwise, add_action doesn’t look for and doesn’t accept any arguments provided, which will doubtless render your function either inoperable or ineffective.

Late Update: Per jeremyclark in the comments, this is not really the reason to mess with priority! Thanks Jeremy! If you’re function is particularly computationally expensive (if it’s complex and relies on a lot of processing), it might be worth it to back the priority down a bit. You can do this by setting the $priority argument. The default is 10. In our case, the actual function is trivial, so this is what the add_action looks like for our mailing list:

add_action( 'bhd_cron_send_hook', 'bhd_cron_send', 10, 4 );

Your Scheduling Function

Finally, there is the function which will do the scheduling. Depending on the complexity of your plugin, this may not be very complex at all. But the one thing that is required of your function is that it bundle up all the arguments to your cron function into an associative array in the proper order. In our example, the parameters for our sending function are $sender_email, $subject, $mail_text, $send_headers. Thus, we create an array like so:

$mail_bundle = array(
     'sender_email' => $sender_email,
     'subject' => $subject,
     'mail_text' => $mailtext,
     'send_headers' => $send_headers
);

Next, we need to assign the scheduled task. For this, we use our custom action hook. Now we understand why it was so important to pay close attention to the number of parameters and their order: the hook only allows you to set the number of arguments and the function – which is now removed by one degree of separation from the schedule – needs those arguments sent to it in the proper order. We call the WordPress scheduling function like so:

wp_schedule_single_event(time()+$time, 'bhd_cron_send_hook', $mail_bundle);

The first argument is simply the Unix system time plus an interval of seconds after which we would like the scheduled task to be fired. Since we want the first of our batch email events to happen twenty or so minutes after the post has been published, $time was defined as follows:

$time = rand(1000, 1200);

“Wait,” you say, “why the random number?”

If you look at the multi-dimensional array of scheduled events created by the WP-Cron system, you will find that the primary key for identifying events is the Unix timestamp. The second-level identifier is an MD5 of the arguments. In our example, it’s possible that someone could inadvertently set up the cron events twice by correcting the post after they’ve published it: both events fire the ‘publish_post’ event, which the mailing list uses to start it’s process.

However it happens, the possibility is that two events could have exactly the same timestamp and arguments, which would mean that one overwrites the other in the array. To avoid this, I included the slight randomization, which only alters the schedule by a maximum of three minutes.

So, now let’s look at the entire process in code (with some pseudo-code) in order to understand how everything fits together:

//======================================
// Description: Our scheduling function.  Sends the email in 50-recipient chunks
function bhd_schedule_message($sender_email, $subject, $list, $headers, $mailtext) {
// Establish our random time interval between the publishing of the post and the first email blast:
	$time = rand(1000, 1200);
	for($x = 0; $x  $sender_email,
				'subject' => $subject,
				'mail_text' => $mailtext,
				'send_headers' => $send_headers
			);
// schedule the email blast, increment the $time variable by twenty minutes for the next loop:
			wp_schedule_single_event(time()+$time, 'bhd_cron_send_hook', $mail_bundle);
			$time = $time + 1200;
		}
	}
}

//======================================
// Description: here is our actual cron job.
function bhd_cron_send($sender_email, $subject, $mail_text, $send_headers) {
	wp_mail($sender_email, $subject, $mail_text, $send_headers);
}

//======================================
// Description: Finally, here is our custom action.
add_action( 'bhd_cron_send_hook', 'bhd_cron_send', 10, 4 );

Note: we have not included the function that will be called when the post is published, because of course we’re not dealing with adding actions onto that particular hook. But for reference’s sake, such a function would look something like this:

add_action('publish_post', 'my_mailinglist_function');

This may be apropos of nothing for a lot of people, but just as a helpful little hint, you can get the theme used by a blog by calling the get_option() method thusly:

get_option('current_theme');

I’m using this on a plugin that changes the colors of certain objects (not CSS directable) by checking the current theme and setting matching colors. Think “custom color YouTube video window.”

MU admins, you could use get_site_option() if you’re planning on doing something global.

Summary:  Returns an array of all users who have Administrative roles on a given domain.

Detail:  This function checks the database for all Site Admin users on the specified domain ($sitedomain and $path).  The returned value is an associative array of usernames and passwords.  This function works not only site-wide but across multiple domains, if you’re environment is set up in that way.

Summary: A companion function to the switch_to_blog() function, this function takes the settings stored in the temporary array setup by switch_to_blog() and swaps them back, thus restoring the original blog.

Detail: This very simple function simply undoes what was done in the switch_to_blog() function. It restores the settings to the $wpdb function and user roles that were held in the temp array setup by the switch_to_blog() function.

As previously noted in the switch_to_blog() post on this site, you cannot expect the temp array to hold more than one blog’s settings, thus if you are iterating over several blogs, you’ll need to either continuously use restore_blog() to get back to the original blog in between iterations, or else use switch_to_blog() to go back to the original, ignoring the temp array altogether.

Summary: Toggles all settings to a different blog’s settings, thus allowing you to perform functions as though you were on that blog. Stores the current blog’s settings in a temporary array, so they can be restored when desired.

Detail: This function switches all the settings in the $wpdb object to the new blog ($new_blog), while also updating the current user’s role as per the new blog. The end result is that all operations performed after using the switch_to_blog function will behave as you would anticipate if you were on that new blog. You would presumably use this function along with the restore_blog() function.

This is probably the single most important and strangely, least-used function in the WPMU platform. The advantages of using switch_to_blog are many. For one, it saves you the trouble of having to rewrite the database blog prefix (wp_24_posts, for example), and instead let’s you query the dB using the same pseudonyms you would use in any WordPress setting ($wpdb->posts). For another, you can use all the standard, built in WordPress functions such as get_permalink() exactly as you would in any other setting. Best of all, because it stores all the current blog’s settings in a temporary array, you can quickly restore the settings without a lot of complex coding.

Additionally, since the user’s roles are carried over, and because you’re using the standard WordPress functions, information normally not visible to a user remains invisible without complex coding. For example, if you want to display the most recent posts from a given blog, you can simply switch to that blog, use the get_posts() function to perform whatever tasks you’d like, and switch back. In doing this, you can avoid showing posts from private, spam or adult blogs. Moreover, on each blog, you can eliminate the possibility that visitors could see private or unpublished posts, or that posts set to be visible only to certain user levels. However, users who do have the proper privileges on that second blog will be able to see those additional posts. Thus you can write supple, flexible plugins that play ball with most available plugins on all public blogs. No additional coding is required, nor are lengthy database queries.

A word of warning: do not attempt to use this function to iterate over a variety of blog ID’s hoping to restore the original blog’s ID at the end, unless you switch back to the original blog in between. At first, I thought that this would be possible, but it is not. Thus, swapping back and forth between several blogs would need to be done like this pseudo-code:

switch_to_blog(4)
. . . do some stuff . . .
restore_blog()
switch_to_blog(5)
. . . do some stuff . . .
restore_blog()

Summary: High-level function that creates a new blog with the given parameters

Detail:  This function is a high-level function that combines a number of other functions to create a new blog.  It uses the install_blog(), install_blog_defaults() and add_user_to_blog() to set all the needed values in the database.  This would be the preferred function to use when creating a new blog, rather than all the lower-level functions.

Summary:  Installs the default values of a WP blog during the creation of a new blog

Details:  This function does a lot, and depending on how you want your blogs setup, may be useful to modify.  It sets up default values for such things as the blogroll, the first post, the first comment, the default category.  Of these options, only the default post can be set in Blog Options, so for finer tuning of the end-user experience, editing this file might be helpful.