Schedule Functions in Your Plugins with WordPress Cron

Schedule Functions in Your Plugins with WordPress Cron

WordPress Cron-Woman holding appointment reminder noteSometimes you need your website to perform certain tasks at a later time. Scheduling a post in WordPress is a perfect example:

  1. You write the most excellent post in the universe.
  2. You do not want to unleash this post to the public until 10a Tuesday morning.
  3. Tuesday morning you have a hot date with your dentist you just don’t want to miss.
  4. You edit the “Publish” date and time accordingly, click “Schedule,” and stop worrying about it.
  5. The first time someone hits your website after Tuesday, 10a, WordPress sets your post to “Published.”

Pretty snazzy.

Other scheduled tasks are run over and over again — perhaps twice a day — and you don’t even know it. Checking for publicly-available updates to your installed plugins and themes is one example you might say “A-ha!” to.

Your own personal scheduling assistant

WordPress makes it easy to schedule your own actions, as well. Using what we call WP-Cron functions, you can schedule a function to run once, or to run periodically–like every 2 hours or so.

What’s in a name?

While WP-Cron sounds like a cool Terminator-style enemy, the name actually comes from the Unix command “cron.” Cron has a similar function: it runs specific commands on a periodic basis.

Why reinvent the cron wheel?

If Unix / Linux already has cron, why was WP-Cron developed? The short answer is: easy compatibility. WordPress runs on so many different server setups, it can’t be assumed cron will always run properly or even be available at all. WP-Cron, however, has a good chance of getting the job done, regardless of your underlying server setup.

When exactly does WP-Cron run?

You may have noticed above we said “The first time someone hits your website after Tuesday, 10a, WordPress sets your post to published.” Technically speaking, that might not be until 10:30a. Or, if you’re running an intranet that doesn’t get visited very much, it might not happen until 1p in the afternoon.

The point is, while Unix cron always attempts to run right on schedule as a system task, WP-Cron doesn’t even think about running until WordPress core is invoked to serve up a page.

Before you go freaking out saying WP-Cron is worthless, just think about it. Your site is being hit by people and search engines on a fairly consistent basis. Each time it’s hit, WordPress checks to see if WP-Cron needs to call any scheduled functions. If someone visits your site at Noon and sees your post that says it was published at 10a, it doesn’t really matter if WP-Cron didn’t really publish it until 10:15a–the first time the site was hit after 10a. There are times when this is a problem, though, as we’ll see next.

Nothing can go wrnog.

I built a plugin called “Content Scheduler,” and boy, have I received my share of support requests. Most of them had to do with WP-Cron taxing the server or not running at all. It’s one of those deals where I didn’t create the problem, but had to deal with it anyway.

Multiple instances of wp-cron running the same queue of functions

WP-Cron is launched and run in the background, so it does not interfere with loading requested pages. On very busy sites, WP-Cron can time out, and another instance of WP-Cron might be launched and start running the same tasks the previous WP-Cron instance is working on. As more WP-Cron instances are launched, your server can become taxed. Good progress has been made on preventing this, and we hopefully won’t have to worry about this too much in coming releases, around WordPress 3.4 and beyond.

Schedules being missed

On very low-traffic sites, it is possible for WP-Cron jobs to be missed. If the scheduled action has something to do with changing the status of a post, this isn’t really a problem, as WP-Cron will eventually run once someone accesses your site. However, if you’re counting on WP-Cron to do something specific like send out an email every 3 hours with a site status report, you might run in to issues. If noone accesses your site for 6 hours, you’ll be missing a couple of emails.

WP-Cron failing completely

Depending on your web server setup, it is possible for WP-Cron to not work at all. Some hosts–either on purpose or unknowingly–do not allow loopback HTTP requests, which are required for WP-Cron to work normally.

Scheduling functions in your own plugins

Let’s see how to add scheduled calls to certain functions in your own plugins.

Step 1: Getting on the schedule

Your plugin needs to tell WP-Cron you have scheduled functions, and how often those functions should be run. For many plugins, registering with WP-Cron at activation time is an obvious choice. In our plugin’s main class constructor, we hook the activation event to a function that takes care of registering WP-Cron schedules. Our main class contains the “run_on_activate” function, which is called whenever the plugin is activated.

register_activation_hook( __FILE__, array($this, 'run_on_activate') );

function run_on_activate()
{
    // for notifications
    if( !wp_next_scheduled( 'content_scheduler_notify' ) )
    {
        wp_schedule_event( time(), 'hourly', 'content_scheduler_notify' );
    }
    // for expirations
    if( !wp_next_scheduled( 'content_scheduler' ) )
    {
        wp_schedule_event( time(), 'quarterday', 'content_scheduler' );
    }

} // end run_on_activate()

What run_on_activate() does

First, we check to see if “content_scheduler_notify” is already on WP-Cron’s scheduling list. The function “wp_next_scheduled” returns a timestamp for the next time the scheduled “content_scheduler_notify” action should run, or returns false if “content_scheduler_notify” is not in WP-Cron.

Second, if “content_scheduler_notify” isn’t scheduled, we schedule it. Our call to the “wp_schedule_event” function:

  1. adds “content_scheduler_notify” to the WP-Cron hook list…
  2. starting at time() (i.e., right now)…
  3. to be run each hour

This example shows two different hooks being added to WP-Cron. The second hook is “content_scheduler,” and is managed similarly. The one difference you’ll notice is “quarterday” in the place of “hourly” to indicate how often this hook should be run. This is a unique name added for this specific plugin, to allow for a custom period of recurrence. Let’s see how this is done.

Recurrence periods for WP-Cron

WordPress has 3 different recurrence periods built in for use with WP-Cron. Their names clearly indicate how often they recur.

  • hourly
  • twicedaily
  • daily

If you need to, you can add a custom recurrence period for your plugin. Our plugin’s constructor uses add_filter to help us add a new item to an existing WordPress array named “cron_schedules.”

add_filter('cron_schedules', array( $this, 'add_cs_cron_fn' ) );

Our class function “add_cs_cron_fn” receives the current list of recurrence periods and adds another to it. It then returns it. This is very important! If the function does not return the array of recurrence periods, WP-Cron will not work correctly on your site at all.

function add_cs_cron_fn( $array )
{
    // set our 6 hours, units in seconds
    $period = 6*3600;
    // use that for 'interval' below.
    // 'quarterday' is a unique name for our custom period
    $array['quarterday'] = array(
        'interval' => $period,
        'display' => 'Every 6 hours'
    );
    return $array;
} // end add_cs_cron_fn()

Something to hook your teeth into

At this point, WP-Cron will periodically run whatever function is attached to the “content_scheduler_notify” and “content_scheduler” actions we’ve scheduled. We’d better hook functions to those actions, then! We take care of this in the plugin’s class constructor.

// add an action hook for expiration check and notification check
add_action ('content_scheduler', array( $this, 'answer_expiration_event') );
add_action ('content_scheduler_notify', array( $this, 'answer_notification_event') );

When WP-Cron fires for our scheduled “content_scheduler” action, the class function ‘answer_expiration_event’ will fire. Likewise, the ‘answer_notification_event’ is called when “content_scheduler_notify” fires.

The guts are up to you

What happens in these functions is up to you! Whatever you want to happen periodically should occur in these new functions.

For example, in the Content Scheduler plugin:

  • “answer_expiration_event” triggers logic that changes a post’s categories, tags, status, and more–based on some user-configurable options
  • “answer_notification_event” notifies specified users when posts are nearing expiration. The sky is the limit!

Cleanup on aisle 5

We added our hooks to WP-Cron’s list when the plugin was activated, and we need to remove our hooks when the plugin is deactivated. If we don’t, WordPress will continue to respond to those hooks, attempting to run functions hooked to appropriate actions–but those functions no longer exist once your plugin is deactivated!

Cleanup is pretty simple. Our plugin’s constructor gives instructions on what to do when the plugin is deactivated.

register_deactivation_hook( __FILE__, array($this, 'run_on_deactivate') );

The class function “run_on_deactivate” simply removes our registered hooks from WP-Cron.

function run_on_deactivate()
{
    // for expirations
    wp_clear_scheduled_hook('content_scheduler');
    // for notifications
    wp_clear_scheduled_hook('content_scheduler_notify');
} // end run_on_activate()

The function “wp_clear_scheduled_hook” takes the name of a hook scheduled in WP-Cron and clears it from the schedule. Simple as that.

Checking the status of cron jobs on your system.

When developing and debugging your plugin that uses WP-Cron, you’ll need to check on the status of scheduled hooks. While there are a few different plugins to show you what tasks are scheduled in wp-cron, I’ve usually used the “Cron View” plugin.
http://wordpress.org/extend/plugins/cron-view/

Credits