Kama_Cron

A small class for easily adding WP Cron tasks (jobs).

This Class allow you to create WordPress Cron tasks in a quick and simple way. In order not to confuse anything, all settings are specified in the first parameter when calling the class. The class takes care of the entire routine of correctly registering the Cron task and their intervals. The task handler (the function) need to be written separately in PHP!

Kama_Cron class code

GitHub
<?php

namespace Kama\WP;

/**
 * Convenient way to add cron tasks in WordPress.
 *
 * INFO: For debugging go to: http://site.com/wp-cron.php
 *
 * Usage Example:
 *
 * ```php
 * new \Kama\WP\Kama_Cron( [
 *     'wpkama_cron_func' => [
 *         'callback'      => 'wpkama_cron_func', // PHP function to run on job
 *         'interval_name' => '10 min',           // you can set already registered interval: hourly, twicedaily, daily
 *     ],
 * ] );
 *
 * new \Kama\WP\Kama_Cron( [
 *     'single_job' => [
 *         'callback' => 'single_job_func',
 *         'start_time' => strtotime( '2021-06-05' ),
 *     ],
 * ] );
 *
 * new \Kama\WP\Kama_Cron( [
 *     'id'     => 'my_cron_jobs', // not required param
 *     'events' => [
 *         // first task
 *         'wpkama_cron_func' => [
 *             'callback'      => 'wpkama_cron_func', // PHP function to run on job
 *             'interval_name' => '10 minutes',       // you can set already registered interval: hourly, twicedaily, daily
 *         ],
 *         // second task
 *         'wpkama_cron_func_2' => [
 *             'callback'      => 'wpkama_cron_func_2',
 *             'interval_name' => '2 hours',
 *             'start_time'    => strtotime('tomorrow 6am'), // start tomorrow at 6:00am + site gtm_offset
 *         ],
 *         // third task
 *         'wpkama_cron_func_3' => [
 *             'callback'      => 'wpkama_cron_func_3',
 *             'interval_name' => 'hourly', // this is already a known WP interval
 *         ],
 *     ],
 * ] );
 * ```
 *
 * @changelog: https://github.com/doiftrue/Kama_Cron/blob/master/changelog.md
 *
 * @author Kama (wp-kama.com)
 *
 * @requires-php 7.1
 * @version 1.4
 */
class Kama_Cron {

	/**
	 * Allowed arguments for constructor.
	 *
	 * @see __construct
	 * @var array
	 */
	protected static $default_args = [
		'id' => '',
		'auto_activate' => true,
		'events' => [
			'hook_name' => [
				'callback'      => [ __CLASS__, 'default_callback' ],
				'args'          => [],
				'interval_name' => '',
				'interval_sec'  => 0,
				'interval_desc' => '',
				'start_time'    => 0,
			],
		],
	];

	/**
	 * Current instance args.
	 *
	 * @var array
	 */
	protected $args = [];

	/**
	 * Container for every instance.
	 * To have acces to instance use `Kama_Cron::get()` method.
	 *
	 * @var array
	 */
	protected static $instances = [];

	/**
	 * ID cron args. Internal - not uses for cron.
	 *
	 * @var string
	 */
	protected $id = '';

	/**
	 * Constructor.
	 *
	 * @param array     $args {
	 *     Args.
	 *
	 *     @type string $id             A unique identifier that can then be used to access the settings externally.
	 *                                  Default: keys of the $events parameter.
	 *     @type bool   $auto_activate  true - automatically creates the specified event when visiting the admin panel.
	 *                                  In this case, you do not need to call {@see self::activate} method separately.
	 *     @type array  $events {
	 *        An array of events to add to the crown. The element key will be used in the cron hook.
	 *        The element value is an array of event parameters that can contain the following keys:
	 *
	 *        @type callable  $callback       The name of the cron task function.
	 *        @type mixed     $args           What parameters should be passed to the cron task function.
	 *        @type string    $interval_name  The name of the interval, for example: 'half_an_hover'.
	 *                                        You can specify the name in the following format:
	 *                                        `N (min|hour|day|month)s` — 10 minutes, 2 hours, 5 days, 2 months,
	 *                                        then the number will be taken to 'interval_sec' parameter.
	 *                                        You can specify an existing WP interval: hourly, twicedaily, daily.
	 *                                        Omite this parameter to register single cron job.
	 *        @type int       $interval_sec   Interval time, for example HOUR_IN_SECONDS / 2.
	 *                                        You don't need to specify this papameter when $interval_name one of:
	 *                                        N (min|hour|day|month)s, hourly, twicedaily, daily.
	 *        @type string    $interval_desc  Description of the interval, for example, 'Every half hour'.
	 *                                        You don't need to specify this param when $interval_name one of:
	 *                                        N (min|hour|day|month)s, hourly, twicedaily, daily.
	 *        @type int       $start_time     UNIX timestamp. When to start the event. Default: time(). If you need to start event
	 *                                        at, for example, tomorrow 6 AM (with site time), you must get timestamp and fix
	 *                                        it with site gtm_offset: `strtotime('tomorrow 6am') - (int) get_option('gtm_offset')`.
	 *     }
	 *
	 * }
	 */
	public function __construct( array $args ){

		$this->set_args( $args );
		$this->init();

		self::$instances[ $this->args['id'] ] = $this;
	}

	/**
	 * Gets instance by id.
	 */
	public static function get( string $instance_id ): self {

		return self::$instances[ $instance_id ] ?? new self( [ 'id' => 'stub', 'events' => [] ] );
	}

	protected function set_args( array $args ): void {

		// if direct events data passed
		if( ! isset( $args['events'] ) ){
			$args = [ 'events' => $args ];
		}

		// add default values to $args
		$args += [
			'id' => implode( '|', array_keys( $args['events'] ) ),
			'auto_activate' => self::$default_args['auto_activate'],
		];

		// add default values to each "event"
		foreach( $args['events'] as $indx => $_event ){
			$args['events'][ $indx ] += self::$default_args['events']['hook_name'];
		}

		$this->args = $args;
	}

	protected function init(): void {

		if( ! $this->args['events'] ){
			return;
		}

		add_filter( 'cron_schedules', [ $this, 'add_intervals_callback' ] );

		// add cron hooks
		foreach( $this->args['events'] as $hook_name => $task_data ){
			add_action( $hook_name, $task_data['callback'], 10, count( $task_data['args'] ) );
		}

		// after 'cron_schedules'
		if( $this->args['auto_activate'] && ( is_admin() || defined( 'WP_CLI' ) || defined( 'DOING_CRON' ) ) ){
			$this->activate();
		}
	}

	/**
	 * Removes all cron tasks of current instance.
	 * Should be called on plugin deactivation.
	 */
	public function deactivate(): void {

		foreach( $this->args['events'] as $hook => $data ){
			wp_clear_scheduled_hook( $hook, $data['args'] );
		}
	}

	/**
	 * Add all cron tasks of current instance.
	 * Should be called on plugin activation.
	 * Can be called somewhere else, for example, when updating the settings.
	 */
	public function activate(): void {

		foreach( $this->args['events'] as $hook => $data ){

			if( wp_next_scheduled( $hook, $data['args'] ) ){
				continue;
			}

			if( $data['interval_name'] ){
				$wp_error = wp_schedule_event( $data['start_time'] ?: time(), $data['interval_name'], $hook, $data['args'], true );
			}
			// single event
			elseif( ! $data['start_time'] ){
				$msg = "ERROR: nor `interval_name` OR `start_time` was not set for the Kama Cron event `$hook`.";
				_doing_it_wrong( __METHOD__, $msg, '' );
			}
			elseif( $data['start_time'] > time() ){
				$wp_error = wp_schedule_single_event( $data['start_time'], $hook, $data['args'], true );
			}

			if ( is_wp_error( $wp_error ?? null ) ) {
				trigger_error( __METHOD__ . ': ' . $wp_error->get_error_message() );
			}
		}
	}

	/**
	 * @private
	 */
	public function add_intervals_callback( $schedules ){

		foreach( $this->args['events'] as $data ){

			$interval_name = $data['interval_name'];

			if(
				// it is a single event.
				! $interval_name
				// already exists
				|| isset( $schedules[ $interval_name ] )
				// internal WP intervals
				|| in_array( $interval_name, [ 'hourly', 'twicedaily', 'daily' ] )
			){
				continue;
			}

			// allow set only `interval_name` parameter like: 10_min, 2_hours, 5_days, 2_month
			if( ! $data['interval_sec'] ){

				if( preg_match( '/^(\d+)[ _-](min(?:ute)?|hour|day|month)s?/', $interval_name, $mm ) ){
					$min = $minute = 60;
					$hour = $min * 60;
					$day = $hour * 24;
					$month = $day * 30;

					$data['interval_sec'] = $mm[1] * ${ $mm[2] };
				}
				else {
					echo 'ERROR: Kama_Cron required `interval_sec` parameter not set.';
					/** @noinspection ForgottenDebugOutputInspection */
					echo "\n\n". debug_print_backtrace();
					die();
				}
			}

			$schedules[ $interval_name ] = [
				'interval' => $data['interval_sec'],
				'display'  => $data['interval_desc'] ?: $data['interval_name'],
			];
		}

		return $schedules;
	}

	public static function default_callback(): void {

		echo 'ERROR: One of Kama_Cron callback function not set.';
		echo "\n\nKama_Cron::\$instance = " . print_r( self::$instances, true );
		echo "\n\n\n\n_get_cron_array() =" . print_r( _get_cron_array(), true );
	}

}

Installation

Copy the class code and paste it into the plugin or the functions.php theme.

Using composer:

composer require doiftrue/wp-kama-cron

Examples

By default, the tasks are registered automatically (it works very fast) when you visit the admin panel OR at WP_CLI request OR at any cron request. If automatic registration is not needed, specify parameter auto_activate' => false and activate tasks manually using method activate(). See the example below.

You can call Kama_Cron at the earliest stage of WP loading, starting from the earliest action muplugins_loaded.

IMPORTANT! The Kama_Cron must be triggered (called) on Cron requests as well, since it registers the necessary WP hooks that do the staff on Cron requests. In other words, you cannot register a Cron task with this code and delete it.

Repeatable job

Use the known WP interval (hourly):

new \Kama\WP\Kama_Cron( [
	'wpkama_core_data_check_update' => [
		'callback'      => 'wpkama_core_data_check_update',
		'interval_name' => 'hourly',
	]
] );

function wpkama_core_data_check_update(){
	// your code to do the cron job
}

wpkama_core_data_check_update - it's an internal name of WP hook you don't need to use it anywhere in your code - just specify unique understandable name (it may be good idea to name it as callback function name).

Use the unknown WP interval (10 minutes):

new \Kama\WP\Kama_Cron( [
	'wpkama_cron_hook' => [
		'callback'      => 'wpkama_cron_func',
		'interval_name' => '10 minutes',
	],
] );

function wpkama_cron_func(){
	// your code to do the cron job
}

In this case the class will parse the string 10 minutes and fill in the interval_sec and interval_desc parameters itself.

In interval_name you can specify the name in the following format: N (min|minutes|hour|day|month)s10 minutes, 2 hours, 5 days, 2 months, then the number will be taken to 'interval_sec' parameter. OR you can specify an existing WP interval: hourly, twicedaily, daily.

Single job

Single job (once):

new \Kama\WP\Kama_Cron( [
	'single_job' => [
		'callback' => 'single_job_func',
		'start_time' => 1679205600, //= strtotime('tomorrow 6am') - (int) get_option('gtm_offset'),
	],
] );

Repeatable Single job (once at time):

new \Kama\WP\Kama_Cron( [
	'single_job' => [
		'callback' => 'single_job_func',
		// start event every day at 6am by site time
		'start_time' => strtotime('tomorrow 6am') - (int) get_option('gtm_offset'),
	],
] );

Register more than one task at once:

Let's create 4 task with different intervals. Tasks are registered automatically (it works very fast) when you visit admin panel OR from CLI OR from Cron request.

Add following code anywhere, for example in functions.php OR in plugin.

new \Kama\WP\Kama_Cron( [
	'id'     => 'my_cron_jobs',
	'events' => [
		// first task
		'wpkama_cron_func' => [
			'callback'      => [ MyCronCallbacks::class, 'wpkama_cron_func' ],
			'interval_name' => '10 min',
		],
		//
		'wpkama_cron_func_2' => [
			'callback'      => [ MyCronCallbacks::class, 'wpkama_cron_func_2' ],
			'interval_name' => '2 hours',
			'start_time'    => 1679205600, // start at specified UNIX time
		],
		// second task
		'wpkama_cron_func_3' => [
			'callback'      => [ MyCronCallbacks::class, 'wpkama_cron_func_3' ],
			'interval_name' => '2 hours',
			'start_time'    => strtotime('tomorrow 6am'), // run at 6 a.m. (site time will be added to this time)
		],
		//
		'wpkama_cron_func_4' => [
			'callback'      => [ MyCronCallbacks::class, 'wpkama_cron_func_4' ],
			'interval_name' => 'hourly', // this is already a known WP interval
		],
	],
] );

class MyCronCallbacks {

	public static function wpkama_cron_func(){
		$file = dirname( ABSPATH ) .'/__cron_check.txt';
		$content = current_time('mysql') ."\n";
		file_put_contents( $file, $content, FILE_APPEND );
	}

	public static function wpkama_cron_func_2(){
		// do something
	}

	public static function wpkama_cron_func_3(){
		// do something
	}

	public static function wpkama_cron_func_4(){
		// do something
	}
}

Register tasks when activating the plugin

The code below shows how to activate and deactivate tasks customly - when activating/deactivating the plugin.

IMPORTANT: in this case the parameter auto_activate must be false: 'auto_activate' => false!

// Example of activation and deactivation when the `auto_activate = false`
register_activation_hook( __FILE__, function(){
	\Kama\WP\Kama_Cron::get( 'my_cron_jobs_2' )->activate();
} );

register_deactivation_hook( __FILE__, function(){
	\Kama\WP\Kama_Cron::get( 'my_cron_jobs_2' )->deactivate();
} );

new \Kama\WP\Kama_Cron( [
	'id' => 'my_cron_jobs_2',
	'auto_activate' => false, // !IMPORTANT
	'events' => [
		'wpkama_cron_func_4' => [
			'callback'      => 'wpkama_cron_func_4',
			'interval_name' => 'twicedaily',
		],
		'wpkama_cron_func_5' => [
			'callback'      => 'wpkama_cron_func_5',
			'interval_name' => '2 hours',
		],
	],
] );

function wpkama_cron_func_4(){
	// code here
}

function wpkama_cron_func_5(){
	// code here
}

INFO: deactivate() method will deactivate all the jobs from current pack (in the example above these are two jobs).

Constructor parameters

$args(array)

Args.

  • id(atring)
    A unique identifier that can then be used to access the settings externally.
    Default: keys of the $events parameter

  • auto_activate(true|false)
    true - automatically creates the specified event when visiting the admin panel. In this case, you do not need to call {@see self::activate} method separately.

  • events(array)
    An array of events to add to the crown. The element key will be used in the cron hook. The element value is an array of event parameters that can contain the following keys:

    • callback(callable)
      The name of the cron task function.

    • args(разное)
      What parameters should be passed to the cron task function.

    • interval_name(atring)
      The name of the interval, for example: 'half_an_hover'. You can specify the name in the following format: N (min|hour|day|month)s — 10 minutes, 2 hours, 5 days, 2 months, then the number will be taken to 'interval_sec' parameter. You can specify an existing WP interval: hourly, twicedaily, daily. Omite this parameter to register single cron job.

    • interval_sec(int)
      Interval time, for example HOUR_IN_SECONDS / 2. You don't need to specify this papameter when $interval_name one of: N (min|hour|day|month)s, hourly, twicedaily, daily.

    • interval_desc(atring)
      Description of the interval, for example, 'Every half hour'. You don't need to specify this param when $interval_name one of: N (min|hour|day|month)s, hourly, twicedaily, daily.

    • start_time(int)
      UNIX timestamp. When to start the event. If you need to start event at, for example, tomorrow 6 AM (with site time), you must get timestamp and fix it with site gtm_offset: strtotime('tomorrow 6am') - (int) get_option('gtm_offset').
      Default: time()

Debug

Useful code for debugging:

add_action( 'wp_loaded', function() {

	echo sprintf( "Current time: %s\n\n\nExisting Intervals:\n%s\n\n\n%s",
		time(), print_r( wp_get_schedules(), 1 ), print_r( _get_cron_array(), 1 )
	);
} );

You can also use the plugin for debugging: WP Crontrol

Or use WP-CLI: wp cron.