Location: PHPKode > projects > Habari > system/classes/cronjob.php
<?php
/**
 * @package Habari
 *
 */

/**
 * CronJob is a single cron task
 *
 * @property string $name The name of the cron job.
 * @property mixed $callback The callback function or plugin action for the cron job to execute.
 * @property HabariDateTime $start_time The time the cron job entry will begin executing.
 * @property HabariDateTime $end_time The time the cron job entry will end executing and delete.
 * @property HabariDateTime $last_run The time job was last run.
 * @property HabariDateTime $next_run The time the job will run next.
 * @property int $increment The amount of time, in seconds, between each execution.
 * @property string $result The result of the last run. Either null, 'executed', or 'failed'.
 * @property string $description The description of the cron job.
 * @property int $cron_class The type of cron job.
 * @property string $notify Not implemented.
 */
class CronJob extends QueryRecord
{
	const CRON_SYSTEM = 1;
	const CRON_THEME = 2;
	const CRON_PLUGIN = 4;
	const CRON_CUSTOM = 8;
	
	/**
	 * The internally stored execution time of this cronjob. (unix timestamp)
	 *
	 * @var int
	 */
	private $now;


	/**
	 * Returns the defined database columns for a cronjob.
	 *
	 * @return array Array of default columns in the crontab table
	 */
	public static function default_fields()
	{
		return array(
			'cron_id' => 0,
			'name' => '',
			'callback' => '',
			'last_run' => null,
			'next_run' => HabariDateTime::date_create(),
			'increment' => 86400, // one day
			'start_time' => HabariDateTime::date_create(),
			'end_time' => null,
			'result' => '',
			'cron_class' => self::CRON_CUSTOM,
			'description' => '',
			'notify' => '',
			'failures' => 0,
			'active' => 1,
		);
	}

	/**
	 * Constructor for the CronJob class.
	 *
	 * @see QueryRecord::__construct()
	 * @param array $paramarray an associative array or querystring of initial field values
	 */
	public function __construct( $paramarray = array() )
	{
		$this->now = HabariDateTime::date_create();

		// Defaults
		$this->fields = array_merge(
			self::default_fields(),
			$this->fields
		);

		// maybe serialize the callback
		$paramarray = Utils::get_params( $paramarray );
		if ( isset( $paramarray['callback'] )
			&& (
				is_array( $paramarray['callback'] )
				|| is_object( $paramarray['callback'] )
			)
		) {
			$paramarray['callback'] = serialize( $paramarray['callback'] );
		}

		parent::__construct( $paramarray );
		$this->exclude_fields( 'cron_id' );
	}

	/**
	 * Runs this job.
	 *
	 * Executes the Cron Job callback. Deletes the Cron Job if end_time is reached
	 * or if it failed to execute the last # consecutive attempts. Also sends notification
	 * by email to specified address.
	 * Note: end_time can be null, ie. "The Never Ending Cron Job".
	 *
	 * Callback is passed a param_array of the Cron Job fields and the execution time
	 * as the 'now' field. The 'result' field contains the result of the last execution; either
	 * 'executed' or 'failed'.
	 *
	 * @todo send notification of execution/failure.
	 */
	public function execute()
	{
		$paramarray = array_merge( array( 'now' => $this->now ), $this->to_array() );

		// this is an ugly hack that we could probably work around better by forking each cron into its own process
		// we increment the failure count now so that if we don't return after calling the callback (ie: a fatal error) it still counts against it, rather than simply never running
		// and preventing all those queued up after it from running
		$this->failures = $this->failures + 1;

		// check to see if we have failed too many times before we update, we might go ahead and skip this one
		if ( $this->failures > Options::get( 'cron_max_failures', 10 ) ) {
			EventLog::log( _t( 'CronJob %s has failed %d times and is being deactivated!', array( $this->name, $this->failures - 1 ) ), 'alert', 'cron' );
			$this->active = false;
		}

		// update before we run it
		$this->update();

		// if the check has been deactivated, just return
		if ( $this->active == false ) {
			return;
		}

		if ( is_callable( $this->callback ) ) {
			// this is a callable we can actually call, so do it
			$result = call_user_func( $this->callback, $paramarray );
		}
		else if ( !is_string($this->callback) && is_callable( $this->callback, true, $callable_name ) ) {
			// this looks like a callable to PHP, but it cannot be called at present and should not be assumed to be a plugin filter name
			// there is nothing for us to do, but it was a specifically-named function for us to call, so assume this is a failure
			$result = false;
		}
		else {
			// this is not callable and doesn't look like one - it should simply be a textual plugin filter name
			$result = true;
			$result = Plugins::filter( $this->callback, $result, $paramarray );
		}

		if ( $result === false ) {
			$this->result = 'failed';

			// simply increment the failure counter. if it's over the limit it'll be deactivated on the next go-around
			$this->failures = $this->failures + 1;

			EventLog::log( _t( 'CronJob %s failed.', array( $this->name ) ), 'err', 'cron' );
		}
		else {
			$this->result = 'executed';

			// reset failures, we were successful
			$this->failures = 0;

			EventLog::log( _t( 'CronJob %s completed successfully.', array( $this->name ) ), 'debug', 'cron' );

			// it ran successfully, so check if it's time to delete it.
			if ( ! is_null( $this->end_time ) && ( $this->now >= $this->end_time ) ) {
				EventLog::log( _t( 'CronJob %s is not scheduled to run again and is being deleted.', array( $this->name ) ), 'debug', 'cron' );
				$this->delete();
				return;
			}
		}

		$this->last_run = $this->now;
		$this->next_run = $this->now->int + $this->increment;
		$this->update();
	}

	/**
	 * Magic property setter to set the cronjob properties.
	 * Serializes the callback if needed.
	 *
	 * @see QueryRecord::__set()
	 * @param string $name The name of the property to set.
	 * @param mixed $value The value of the property to set.
	 * @return mixed The new value of the property.
	 */
	public function __set( $name, $value )
	{
		switch ( $name ) {
			case 'callback':
				if ( is_array( $value ) || is_object( $value ) ) {
					$value = serialize( $value );
				}
				break;
			case 'next_run':
			case 'last_run':
			case 'start_time':
			case 'end_time':
				if ( !( $value instanceOf HabariDateTime ) && ! is_null( $value ) ) {
					$value = HabariDateTime::date_create( $value );
				}
				break;
		}
		return parent::__set( $name, $value );
	}

	/**
	 * Magic property getter to get the cronjob properties.
	 * Unserializes the callback if called.
	 *
	 * @see QueryRecord::__get()
	 * @param string $name The name of the property to get.
	 * @return mixed The value of the property, or null if no property by that name.
	 */
	public function __get( $name )
	{
		if ( $name == 'callback' ) {
			if ( false !== $res = @ unserialize( parent::__get( $name ) ) ) {
				return $res;
			}
		}
		return parent::__get( $name );
	}


	/**
	 * Saves a new cron job to the crontab table.
	 *
	 * @see QueryRecord::insertRecord()
	 * @return CronJob The newly inserted cron job, or false if failed.
	 */
	public function insert()
	{
		return parent::insertRecord( DB::table( 'crontab' ) );
	}

	/**
	 * Updates an existing cron job to the crontab table.
	 *
	 * @see QueryRecord::updateRecord()
	 * @return CronJob The updated cron job, or false if failed.
	 */
	public function update()
	{
		return parent::updateRecord( DB::table( 'crontab' ), array( 'cron_id'=>$this->cron_id ) );
	}

	/**
	 * Deletes an existing cron job.
	 *
	 * @see QueryRecord::deleteRecord()
	 * @return bool If the delete was successful
	 */
	public function delete()
	{
		return parent::deleteRecord( DB::table( 'crontab' ), array( 'cron_id'=>$this->cron_id ) );
	}
}

?>
Return current item: Habari