Scheduling Tasks in WordPress: a Plugin Developer’s Guide
- A website analytics plugin should be able to generate a weeklyreport on site usage statistics, and then send an email to the websiteadministrator.
- An email newsletter plugin might need to deliver emails on serverswhere there are hourly sending limits. The plugin would have to queueoutgoing emails and send a given number each hour.
- A chart image generator can gather data from a database, generatethe image, and save it to a cache instead of generating it on everyrequest—this will avoid unnecessary load on the database.
- A backup plugin will need a function to be activated once everyweek, so that it can generate the database backup and send an email tothe website administrator.
- A membership website plugin would require the ability to expiresubscriptions at the end of a month.
- A Twitter sidebar widget plugin that makes a request to theTwitter service only once an hour to fetch and load your latesttweets.
In this article I’ll show you how to work with the WordPress API toschedule tasks to run at specific times or intervals. It’s assumed thatyou’re already familiar with the basics of building WordPress plugins;otherwise, have a read through the Writing a Plugin page onthe WordPress Codex for some starting pointers.
Before we look at how to set up scheduled tasks in your WordPressplugin, you need to understand how WordPress handles scheduled tasksbehind the scenes.Every time someone visits a page of the site, WordPress checks tosee if any of the functions that have been scheduled need to be executed.This is based on the interval since the last execution or the time whenthe task was scheduled.WordPress uses the fsockopen
PHP functionto make a request to the wp-cron.php
file that’slocated in the WordPress installation directory. To make sure that thewebsite visitor is unaffected by this call, the timeout for the request isvery small (0.1 seconds). Having initiated the request, WordPress doesn’twait for a response, and continues serving the page to the user. The 0.1second timeout is enough to initiate execution of thewp-cron.php
file.The first statement the wp-cron.php
fileexecutes is:
ignore_user_abort(true);
This ensures that execution continues, even if the client whorequested the page closes the connection (this would otherwise terminateexecution). This script will check for any tasks that should be run, basedon the current time and the time they were last executed. For example,let’s say you have a task that’s scheduled to run hourly, and was last runat 4:13 p.m. When a user requests a page from your site at 5:11 p.m.,wp-cron.php
will skip your task, as it was last runless than an hour prior. A few minutes later, at 5:15 p.m., when anotheruser views a page, wp-cron.php
will run yourtask.
In order to schedule a function to be run, either as a one-off taskor as a recurring task, you simply attach it to an action hook. Thisallows several functions to be assigned to the same scheduled hook. Incase you’re unfamiliar with WordPress action hooks, let’s do a quickreview before looking at how they’re applied to scheduling.If you’re already familiar with WordPress action hooks, feel free toskip this section and go straight to the section called “Scheduling Tasks”.
An action hook is a named group of functions to be executed at agiven point. Any function attached to a given action hook will beexecuted when that action is performed. The WordPress core defines manyactions, which are in turn triggered at many places in the WordPresscore code.Let’s look at an example: WordPress’s core includes thewp_head
function, which you’re probablyfamiliar with. Inside that function you’ll find this line ofcode:
do_action('wp_head');
The wp_head
function is called within the<head>
section of theheader.php
file in a WordPress theme. When thatfunction is called, the above line of code will trigger the execution ofany functions that have been linked to the wp_head
action hook. It’s important to distinguish between thewp_head
method and thewp_head
action hook: they have the same name but aredistinct.If we want to add a style sheet to all pages without editing theheader.php
file, we simply create a function of ourown that generates the desired <link/>
tag, and then attach this function to the wp_head
action. The code below does this:
//This function generates the link tagfunction myplugin_generatestylesheet(){ //Form the URL to the css file stored in our plugin directory $pluginURL = get_bloginfo('home').'/wp-content/plugins/myplugin/mycss.css'; //Write it to the browser echo '<link href="'.$pluginURL.'" type="text/css" rel="stylesheet" />';}//Now we add this function to the wp_head action.add_action('wp_head','myplugin_generatestylesheet');
The add_action
function is the key here:it attaches our myplugin_generatestylesheet
function to the wp_head
action hook. Now, when thewp_head
method callsdo_action('wp-head')
, our function will be called,and the link tag will be output.You can define your own actions and add as many functions to thataction as you’d like. This turns out to be the key to WordPress’sscheduling functionality, as we’ll see shortly.
In WordPress, we can schedule functions to be called in twodifferent ways:
- One-off task—a task that is to be executed once at a specifiedtime
- Recurring task—a task that’s executed at regularintervals
I’ll go over each of them in turn.
To schedule actions that are executed once at a specific time andnever again, we use thewp_schedule_single_event
function.First, we define the function that we want to schedule:
function my_function(){ // Do something. Anything.}
Next, we add this function to our action hook. Wait, we need tocreate the action hook first, right? Wrong. It turns out we don’t. Justadding the function to an action with a new name creates the new action.So, in this step, we create both the my_action
action, and add the my_function
function tothat action:
add_action('my_action','my_function');
Finally, we schedule the action hook to run at the desiredtime:
wp_schedule_single_event($timestamp,'my_action');
In the above line of code, $timestamp
is thetime we want the action to run, formatted as a Unix timestamp.
Functions that are to be called at regular intervals are scheduledusing the wp_schedule_event
function. The stepsinvolved in defining a recurring task are otherwise very similar to thesteps for a one-off task. Again, we start by defining a functioncontaining the code we want to execute periodically:
function my_periodic_function(){ // Do something regularly.}
We add the function to the new action hook:
add_action('my_periodic_action','my_periodic_function');
Finally, we schedule the action to be executed with the desiredfrequency:
wp_schedule_event($timestamp, 'hourly', 'my_periodic_action');
This function takes a new parameter: the frequency('hourly'
in the above example).$timestamp
is the time when the action hook shouldfirst be run, after which it will be run at a frequency determined bythe second parameter. The final parameter, as withwp_schedule_single_event
, is the action hook toschedule.WordPress defines three recurrence frequencies: hourly, daily, andtwicedaily. However, should you need a different frequency, never fear! Youcan define your own recurrence frequencies.
What if you need a recurrence period that’s shorter, such as everyfive minutes, or longer, such as once every month? In these cases, we candefine our own recurrence frequency and export it to WordPress. This isdone by attaching a function to the cron_schedules
filter:
add_filter('cron_schedules','my_cron_definer'); function my_cron_definer($schedules){ $schedules['monthly'] = array( 'interval'=> 2592000, 'display'=> __('Once Every 30 Days') ); return $schedules;}
With this done, we can now use the new frequency when callingwp_schedule_event
:
wp_schedule_event(time(),'monthly','my_action');
This will call my_action
once every 30 days,starting from the current time.
Let’s say the administrator finds that their inbox keeps filling upwith backup emails when using our database backup plugin. Perhaps thedaily backup is overkill, considering the modest level of activity ontheir blog. So the admin logs into the WordPress dashboard and changes thebackup frequency to weekly instead of daily.In this scenario, the database plugin should change the frequency ofthe backup routine. Here we run into a bit of a roadblock: WordPress’sscheduling API doesn’t allow us to change the periodicity of a hook thathas already been scheduled. We need to first remove the daily schedule,and then attach a new weekly hook.
To remove all future instances of a recurring action, thewp_clear_scheduled_hook
function should beused. The syntax couldn’t be simpler:
wp_clear_scheduled_hook('name_of_hook');
One-off events are unscheduled by using thewp_unschedule_event
function. Using thisfunction is less straightforward than the unscheduling of recurringevents.The syntax is as follows:
wp_unschedule_event($timestamp, $hook, $arguments);
Here, the $timestamp
argument is the time whenthe event is scheduled to run. As you may be unaware of when the actionhas been scheduled to run, you can use thewp_next_scheduled
function to fetch the time,and then pass that intowp_unschedule_event
:
$whenNext = wp_next_scheduled('my_action');wp_unschedule_event($whenNext,'my_action');
Now that you know how to schedule and unschedule tasks, you’re leftwith the question of whereabouts in your code to set up this schedule. Ifyour plugin should start the execution of the tasks immediately after theplugin is activated, the tasks must be registered within the activationhook. The activation hook is provided by WordPress, and is triggered whenthe plugin is first activated.When your plugin is deactivated, all tasks scheduled by your pluginshould be unscheduled. This makes sure that no duplicate tasks arescheduled by WordPress if your plugin is reactivated. This also ensuresthat any other plugin that makes use of the tasks scheduled by your plugindoesn’t produce undesired behavior.This very simple plugin demonstrates both the scheduling of taskswhen the plugin is activated, and their removal when the plugin isdeactivated:
<?php /* Plugin Name: My Plugin Plugin URI: https://www.sitepoint.com/ Description: A plugin to learn crontasks. Version: 2.9.2 Author: Raj Sekharan Author URI: http://www.krusible.com *//* The activation hook is executed when the plugin is activated. */register_activation_hook(__FILE__,'myplugin_activation');/* The deactivation hook is executed when the plugin is deactivated */register_deactivation_hook(__FILE__,'myplugin_deactivation');/* This function is executed when the user activates the plugin */function myplugin_activation(){ wp_schedule_event(time(), 'hourly', 'my_hook');}/* This function is executed when the user deactivates the plugin */function myplugin_deactivation(){ wp_clear_scheduled_hook('my_hook');}/* We add a function of our own to the my_hook action.add_action('my_hook','my_function');/* This is the function that is executed by the hourly recurring action my_hook */function my_function(){ //do something.}?>
While developing plugins that use scheduled tasks, it’s oftenhelpful to be able to see if the task has indeed been scheduled, andwhether it has been assigned the correct frequency. This can be done usingthe ControlCore plugin.The Control Core plugin was created to aid plugin developers intheir work, by providing control of certain aspects of the WordPress core.Control Core adds a section to the administration panel that shows all thetasks that are currently scheduled.
This method of scheduling tasks comes with a few warninglabels:
- Be aware that it’s not 100% precise
- For those of you accustomed to system-based cron jobs, oneobvious disadvantage of the WordPress scheduling API is that it’simpossible to ensure that the action you scheduled will run at anexact time. Although the scheduled functions will definitely beexecuted, they may run a little later than you’d like them to. Thisis because WordPress relies on website traffic to triggertasks.In cases where a website may lack adequate traffic, ascheduled task might not execute at the proper times. While anaction was scheduled to run at 12 a.m., it may actually run at 12:45a.m., because no one visited the website that late at night.
- Use appropriate frequencies
- Some time ago, I created a plugin that generated some contentpages for a blog by querying the Yahoo Search web service and theTwitter web service. The script was scheduled to run every fiveminutes. I wrote the plugin and configured it to run on myblog.The next day, my web hosting account was temporarily suspendedfor using excessive resources. It turned out that the scriptsometimes ran for far more than five minutes. This meant that a newtask would often start before the previous one had completed; aftercouple of hours there were 12 instances of the script running. Soonthere was no more memory left over for any other process, and thescript started to affect other website owners on the shared hostingserver. The lesson here is that you should ensure that you choosethe recurrence interval for your resource-consuming procedurescarefully.
- Use flags when using shared resources
- Scheduled tasks will often make use of the file system, orcertain database tables. Simultaneous access or operations on theseresources can corrupt or destroy the integrity of the data. This canhappen when your plugin has two or more tasks that rely on the samedata file or database table. Be sure to use read/write locks toprotect access to your resources.
Although it’s possible to create WordPress plugins that performperiodic tasks without the WordPress scheduling API, it’s disadvantageousto do it that way. The procedure to set up your plugin will become morecomplicated, as the users will have to set up the crons manually.Complicated procedures only reduce the number of bloggers who can benefitfrom using your plugin.The setup process for a plugin should be as simple as clicking
in the Plugins section of the WordPressadministration panel. By making use of WordPress’s built-in schedulingfunctionality, you can give your plugins sophisticated features withoutmaking them more complicated to use.