1<?php
2/**
3 * @package Habari
4 *
5 */
6
7/**
8 * Static class to build and read cron entries
9 *
10 */
11class CronTab extends ActionHandler
12{
13	/**
14	 * Executes all cron jobs in the DB if there are any to run.
15	 *
16	 * @param boolean $async If true, allows execution to continue by making an asynchronous request to a cron URL
17	 */
18	static function run_cron( $async = false )
19	{
20		// check if it's time to run crons, and if crons are already running.
21		$next_cron = HabariDateTime::date_create( Options::get( 'next_cron' ) );
22		$time = HabariDateTime::date_create();
23		if ( ( $next_cron->int > $time->int )
24			|| ( Options::get( 'cron_running' ) && Options::get( 'cron_running' ) > microtime( true ) )
25			) {
26			return;
27		}
28
29		// cron_running will timeout in 10 minutes
30		// round cron_running to 4 decimals
31		$run_time = microtime( true ) + 600;
32		$run_time = sprintf( "%.4f", $run_time );
33		Options::set( 'cron_running', $run_time );
34
35		if ( $async ) {
36			// Timeout is really low so that it doesn't wait for the request to finish
37			$cronurl = URL::get( 'cron',
38				array(
39					'time' => $run_time,
40					'asyncronous' => Utils::crypt( Options::get( 'GUID' ) ) )
41				);
42			$request = new RemoteRequest( $cronurl, 'GET', 1 );
43
44			try {
45				$request->execute();
46			}
47			catch ( RemoteRequest_Timeout $e ) {
48				// the request timed out - we knew that would happen
49			}
50			catch ( Exception $e ) {
51				// some other error occurred. we still don't care
52			}
53		}
54		else {
55			// @todo why do we usleep() and why don't we just call act_poll_cron()?
56			usleep( 5000 );
57			if ( Options::get( 'cron_running' ) != $run_time ) {
58				return;
59			}
60
61			$time = HabariDateTime::date_create();
62			$crons = DB::get_results(
63				'SELECT * FROM {crontab} WHERE start_time <= ? AND next_run <= ?',
64				array( $time->sql, $time->sql ),
65				'CronJob'
66				);
67			if ( $crons ) {
68				foreach ( $crons as $cron ) {
69					$cron->execute();
70				}
71			}
72
73			EventLog::log( _t( 'CronTab run completed.' ), 'debug', 'crontab', 'habari', $crons );
74
75			// set the next run time to the lowest next_run OR a max of one day.
76			$next_cron = DB::get_value( 'SELECT next_run FROM {crontab} ORDER BY next_run ASC LIMIT 1', array() );
77			Options::set( 'next_cron', min( intval( $next_cron ), $time->modify( '+1 day' )->int ) );
78			Options::set( 'cron_running', false );
79		}
80	}
81
82	/**
83	 * Handles asyncronous cron calls.
84	 *
85	 * @todo next_cron should be the actual next run time and update it when new
86	 * crons are added instead of just maxing out at one day..
87	 */
88	function act_poll_cron()
89	{
90		Utils::check_request_method( array( 'GET', 'HEAD', 'POST' ) );
91
92		$time = doubleval( $this->handler_vars['time'] );
93		if ( $time != Options::get( 'cron_running' ) ) {
94			return;
95		}
96
97		// allow script to run for 10 minutes. This only works on host with safe mode DISABLED
98		if ( !ini_get( 'safe_mode' ) ) {
99			set_time_limit( 600 );
100		}
101		$time = HabariDateTime::date_create();
102		$crons = DB::get_results(
103			'SELECT * FROM {crontab} WHERE start_time <= ? AND next_run <= ?',
104			array( $time->sql, $time->sql ),
105			'CronJob'
106			);
107
108		if ( $crons ) {
109			foreach ( $crons as $cron ) {
110				$cron->execute();
111			}
112		}
113
114		// set the next run time to the lowest next_run OR a max of one day.
115		$next_cron = DB::get_value( 'SELECT next_run FROM {crontab} ORDER BY next_run ASC LIMIT 1', array() );
116		Options::set( 'next_cron', min( intval( $next_cron ), $time->modify( '+1 day' )->int ) );
117		Options::set( 'cron_running', false );
118	}
119
120	/**
121	 * Get a Cron Job by name or id from the Database.
122	 *
123	 * @param mixed $name The name or id of the cron job to retreive.
124	 * @return CronJob The cron job retreived from the DB
125	 */
126	static function get_cronjob( $name )
127	{
128		if ( is_int( $name ) ) {
129			$cron = DB::get_row( 'SELECT * FROM {crontab} WHERE cron_id = ?', array( $name ), 'CronJob' );
130		}
131		else {
132			$cron = DB::get_row( 'SELECT * FROM {crontab} WHERE name = ?', array( $name ), 'CronJob' );
133		}
134		return $cron;
135	}
136
137	/**
138	 * Delete a Cron Job by name or id from the Database.
139	 *
140	 * @param mixed $name The name or id of the cron job to delete.
141	 * @return bool Wheather or not the delete was successfull
142	 */
143	static function delete_cronjob( $name )
144	{
145		$cron = self::get_cronjob( $name );
146		if ( $cron ) {
147			return $cron->delete();
148		}
149		return false;
150	}
151
152	/**
153	 * Add a new cron job to the DB.
154	 *
155	 * @see CronJob
156	 * @param array $paramarray A paramarray of cron job feilds.
157	 */
158	static function add_cron( $paramarray )
159	{
160		$cron = new CronJob( $paramarray );
161		$result = $cron->insert();
162
163		//If the new cron should run earlier than the others, rest next_cron to its strat time.
164		$next_cron = DB::get_value( 'SELECT next_run FROM {crontab} ORDER BY next_run ASC LIMIT 1', array() );
165		if ( intval( Options::get( 'next_cron' ) ) > intval( $next_cron ) ) {
166			Options::set( 'next_cron', $next_cron );
167		}
168		return $result;
169	}
170
171	/**
172	 * Add a new cron job to the DB, that runs only once.
173	 *
174	 * @param string $name The name of the cron job.
175	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
176	 * @param HabariDateTime $run_time The time to execute the cron.
177	 * @param string $description The description of the cron job.
178	 */
179	static function add_single_cron( $name, $callback, $run_time, $description = '' )
180	{
181		$paramarray = array(
182			'name' => $name,
183			'callback' => $callback,
184			'start_time' => $run_time,
185			'end_time' => $run_time, // only run once
186			'description' => $description
187		);
188		return self::add_cron( $paramarray );
189	}
190
191	/**
192	 * Add a new cron job to the DB, that runs hourly.
193	 *
194	 * @param string $name The name of the cron job.
195	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
196	 * @param string $description The description of the cron job.
197	 */
198	static function add_hourly_cron( $name, $callback, $description = '' )
199	{
200		$paramarray = array(
201			'name' => $name,
202			'callback' => $callback,
203			'increment' => 3600, // one hour
204			'description' => $description
205		);
206		return self::add_cron( $paramarray );
207	}
208
209	/**
210	 * Add a new cron job to the DB, that runs daily.
211	 *
212	 * @param string $name The name of the cron job.
213	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
214	 * @param string $description The description of the cron job.
215	 */
216	static function add_daily_cron( $name, $callback, $description = '' )
217	{
218		$paramarray = array(
219			'name' => $name,
220			'callback' => $callback,
221			'increment' => 86400, // one day
222			'description' => $description
223		);
224		return self::add_cron( $paramarray );
225	}
226
227	/**
228	 * Add a new cron job to the DB, that runs weekly.
229	 *
230	 * @param string $name The name of the cron job.
231	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
232	 * @param string $description The description of the cron job.
233	 */
234	static function add_weekly_cron( $name, $callback, $description = '' )
235	{
236		$paramarray = array(
237			'name' => $name,
238			'callback' => $callback,
239			'increment' => 604800, // one week (7 days)
240			'description' => $description
241		);
242		return self::add_cron( $paramarray );
243	}
244
245	/**
246	 * Add a new cron job to the DB, that runs monthly.
247	 *
248	 * @param string $name The name of the cron job.
249	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
250	 * @param string $description The description of the cron job.
251	 */
252	static function add_monthly_cron( $name, $callback, $description = '' )
253	{
254		$paramarray = array(
255			'name' => $name,
256			'callback' => $callback,
257			'increment' => 2592000, // one month (30 days)
258			'description' => $description
259		);
260		return self::add_cron( $paramarray );
261	}
262}
263
264?>
265