|
TYPO3 API
SVNRelease
|
00001 <?php 00002 /*************************************************************** 00003 * Copyright notice 00004 * 00005 * (c) 2005 Christian Jul Jensen <julle@typo3.org> 00006 * 00007 * All rights reserved 00008 * 00009 * This script is part of the TYPO3 project. The TYPO3 project is 00010 * free software; you can redistribute it and/or modify 00011 * it under the terms of the GNU General Public License as published by 00012 * the Free Software Foundation; either version 2 of the License, or 00013 * (at your option) any later version. 00014 * 00015 * The GNU General Public License can be found at 00016 * http://www.gnu.org/copyleft/gpl.html. 00017 * 00018 * This script is distributed in the hope that it will be useful, 00019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00021 * GNU General Public License for more details. 00022 * 00023 * This copyright notice MUST APPEAR in all copies of the script! 00024 ***************************************************************/ 00025 /** 00026 * TYPO3 Scheduler. This class handles scheduling and execution of tasks. 00027 * Formerly known as "Gabriel TYPO3 arch angel" 00028 * 00029 * @author François Suter <francois@typo3.org> 00030 * @author Christian Jul Jensen <julle@typo3.org> 00031 * 00032 * @package TYPO3 00033 * @subpackage tx_scheduler 00034 * 00035 * $Id: class.tx_scheduler.php 9758 2010-12-05 11:25:36Z stephenking $ 00036 */ 00037 00038 class tx_scheduler implements t3lib_Singleton { 00039 /** 00040 * @var array $extConf: settings from the extension manager 00041 */ 00042 var $extConf = array(); 00043 00044 /** 00045 * Constructor, makes sure all derived client classes are included 00046 * 00047 * @return void 00048 */ 00049 public function __construct() { 00050 // Get configuration from the extension manager 00051 $this->extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['scheduler']); 00052 if (empty($this->extConf['maxLifetime'])) { 00053 $this->extConf['maxLifetime'] = 1440; 00054 } 00055 00056 // Clean up the serialized execution arrays 00057 $this->cleanExecutionArrays(); 00058 } 00059 00060 /** 00061 * Adds a task to the pool 00062 * 00063 * @param tx_scheduler_Task $task: the object representing the task to add 00064 * @param string $identifier: the identified of the task 00065 * @return boolean True if the task was successfully added, false otherwise 00066 */ 00067 public function addTask(tx_scheduler_Task $task) { 00068 $taskUid = $task->getTaskUid(); 00069 if (empty($taskUid)) { 00070 $fields = array( 00071 'crdate' => $GLOBALS['EXEC_TIME'], 00072 'classname' => get_class($task), 00073 'disable' => $task->isDisabled(), 00074 'serialized_task_object' => 'RESERVED' 00075 ); 00076 $result = $GLOBALS['TYPO3_DB']->exec_INSERTquery('tx_scheduler_task', $fields); 00077 if ($result) { 00078 $task->setTaskUid($GLOBALS['TYPO3_DB']->sql_insert_id()); 00079 $task->save(); 00080 $result = true; 00081 } else { 00082 $result = false; 00083 } 00084 } else { 00085 $result = false; 00086 } 00087 return $result; 00088 } 00089 00090 /** 00091 * Cleans the execution lists of the scheduled tasks, executions older than 24h are removed 00092 * TODO: find a way to actually kill the job 00093 * 00094 * @return void 00095 */ 00096 protected function cleanExecutionArrays() { 00097 $tstamp = $GLOBALS['EXEC_TIME']; 00098 00099 // Select all tasks with executions 00100 // NOTE: this cleanup is done for disabled tasks too, 00101 // to avoid leaving old executions lying around 00102 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery( 00103 'uid, classname, serialized_executions', 00104 'tx_scheduler_task', 00105 'serialized_executions != \'\'' 00106 ); 00107 00108 $maxDuration = $this->extConf['maxLifetime'] * 60; 00109 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) { 00110 if (($serialized_executions = unserialize($row['serialized_executions']))) { 00111 $executions = array(); 00112 foreach ($serialized_executions AS $task) { 00113 if (($tstamp - $task) < $maxDuration) { 00114 $executions[] = $task; 00115 } else { 00116 $logMessage = 'Removing logged execution, assuming that the process is dead. Execution of \'' . $row['classname'] . '\' (UID: ' . $row['uid']. ') was started at '.date('Y-m-d H:i:s', $task); 00117 $this->log($logMessage); 00118 } 00119 } 00120 } 00121 00122 if (count($serialized_executions) != count($executions)) { 00123 if (count($executions) == 0) { 00124 $value = ''; 00125 } else { 00126 $value = serialize($executions); 00127 } 00128 00129 $GLOBALS['TYPO3_DB']->exec_UPDATEquery( 00130 'tx_scheduler_task', 00131 'uid = ' . intval($row['uid']), 00132 array('serialized_executions' => $value) 00133 ); 00134 } 00135 } 00136 $GLOBALS['TYPO3_DB']->sql_free_result($res); 00137 } 00138 00139 /** 00140 * This method executes the given task and properly marks and records that execution 00141 * It is expected to return false if the task was barred from running or if it was not saved properly 00142 * 00143 * @param tx_scheduler_Task $task: the task to execute 00144 * @return boolean Whether the task was saved succesfully to the database or not 00145 */ 00146 public function executeTask(tx_scheduler_Task $task) { 00147 // Trigger the saving of the task, as this will calculate its next execution time 00148 // This should be calculated all the time, even if the execution is skipped 00149 // (in case it is skipped, this pushes back execution to the next possible date) 00150 $task->save(); 00151 // Set a scheduler object for the task again, 00152 // as it was removed during the save operation 00153 $task->setScheduler(); 00154 $result = true; 00155 00156 // Task is already running and multiple executions are not allowed 00157 if (!$task->areMultipleExecutionsAllowed() && $task->isExecutionRunning()) { 00158 // Log multiple execution error 00159 $logMessage = 'Task is already running and multiple executions are not allowed, skipping! Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid(); 00160 $this->log($logMessage); 00161 00162 $result = false; 00163 00164 // Task isn't running or multiple executions are allowed 00165 } else { 00166 // Log scheduler invocation 00167 $logMessage = 'Start execution. Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid(); 00168 $this->log($logMessage); 00169 00170 // Register execution 00171 $executionID = $task->markExecution(); 00172 00173 $failure = null; 00174 try { 00175 // Execute task 00176 $successfullyExecuted = $task->execute(); 00177 00178 if (!$successfullyExecuted) { 00179 throw new tx_scheduler_FailedExecutionException( 00180 'Task failed to execute successfully. Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid(), 00181 1250596541 00182 ); 00183 } 00184 } catch(Exception $e) { 00185 // Store exception, so that it can be saved to database 00186 $failure = $e; 00187 } 00188 00189 // Unregister excution 00190 $task->unmarkExecution($executionID, $failure); 00191 00192 // Log completion of execution 00193 $logMessage = 'Task executed. Class: ' . get_class($task). ', UID: ' . $task->getTaskUid(); 00194 $this->log($logMessage); 00195 00196 // Now that the result of the task execution has been handled, 00197 // throw the exception again, if any 00198 if ($failure instanceof Exception) { 00199 throw $failure; 00200 } 00201 } 00202 00203 return $result; 00204 } 00205 00206 /** 00207 * This method stores information about the last run of the Scheduler into the system registry 00208 * 00209 * @param string $type: Type of run (manual or command-line (assumed to be cron)) 00210 * @return void 00211 */ 00212 public function recordLastRun($type = 'cron') { 00213 // Validate input value 00214 if ($type != 'manual') { 00215 $type = 'cron'; 00216 } 00217 00218 /** 00219 * @var t3lib_Registry 00220 */ 00221 $registry = t3lib_div::makeInstance('t3lib_Registry'); 00222 $runInformation = array('start' => $GLOBALS['EXEC_TIME'], 'end' => time(), 'type' => $type); 00223 $registry->set('tx_scheduler', 'lastRun', $runInformation); 00224 } 00225 00226 /** 00227 * Removes a task completely from the system. 00228 * TODO: find a way to actually kill the existing jobs 00229 * 00230 * @param tx_scheduler_Task $task: the object representing the task to delete 00231 * @return boolean True if task was successfully deleted, false otherwise 00232 */ 00233 public function removeTask(tx_scheduler_Task $task) { 00234 $taskUid = $task->getTaskUid(); 00235 if (!empty($taskUid)) { 00236 return $GLOBALS['TYPO3_DB']->exec_DELETEquery('tx_scheduler_task', 'uid = ' . $taskUid); 00237 } else { 00238 return false; 00239 } 00240 } 00241 00242 /** 00243 * Updates a task in the pool 00244 * 00245 * @param tx_scheduler_Task $task: Scheduler task object 00246 * @return boolean False if submitted task was not of proper class 00247 */ 00248 public function saveTask(tx_scheduler_Task $task) { 00249 $taskUid = $task->getTaskUid(); 00250 if (!empty($taskUid)) { 00251 try { 00252 $executionTime = $task->getNextDueExecution(); 00253 $task->setExecutionTime($executionTime); 00254 } 00255 catch (Exception $e) { 00256 $task->setDisabled(true); 00257 $executionTime = 0; 00258 } 00259 $task->unsetScheduler(); 00260 $fields = array( 00261 'nextexecution' => $executionTime, 00262 'classname' => get_class($task), 00263 'disable' => $task->isDisabled(), 00264 'serialized_task_object' => serialize($task) 00265 ); 00266 return $GLOBALS['TYPO3_DB']->exec_UPDATEquery('tx_scheduler_task', "uid = '" . $taskUid . "'", $fields); 00267 } else { 00268 return false; 00269 } 00270 } 00271 00272 /** 00273 * Fetches and unserializes a task object from the db. If an uid is given the object 00274 * with the uid is returned, else the object representing the next due task is returned. 00275 * If there are no due tasks the method throws an exception. 00276 * 00277 * @param integer $uid: primary key of a task 00278 * @return tx_scheduler_Task The fetched task object 00279 */ 00280 public function fetchTask($uid = 0) { 00281 $whereClause = ''; 00282 // Define where clause 00283 // If no uid is given, take any non-disabled task which has a next execution time in the past 00284 if (empty($uid)) { 00285 $whereClause = 'disable = 0 AND nextexecution != 0 AND nextexecution <= ' . $GLOBALS['EXEC_TIME']; 00286 } else { 00287 $whereClause = 'uid = ' . intval($uid); 00288 } 00289 00290 $queryArray = array( 00291 'SELECT' => 'uid, serialized_task_object', 00292 'FROM' => 'tx_scheduler_task', 00293 'WHERE' => $whereClause, 00294 'LIMIT' => 1 00295 ); 00296 $res = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryArray); 00297 00298 // If there are no available tasks, thrown an exception 00299 if ($GLOBALS['TYPO3_DB']->sql_num_rows($res) == 0) { 00300 throw new OutOfBoundsException('No task', 1247827244); 00301 00302 // Otherwise unserialize the task and return it 00303 } else { 00304 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res); 00305 $task = unserialize($row['serialized_task_object']); 00306 00307 if ($this->isValidTaskObject($task)) { 00308 // The task is valid, return it 00309 00310 $task->setScheduler(); 00311 00312 } else { 00313 // Forcibly set the disable flag to 1 in the database, 00314 // so that the task does not come up again and again for execution 00315 00316 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('tx_scheduler_task', "uid = '" . $row['uid'] . "'", array('disable' => 1)); 00317 // Throw an exception to raise the problem 00318 throw new UnexpectedValueException('Could not unserialize task', 1255083671); 00319 } 00320 $GLOBALS['TYPO3_DB']->sql_free_result($res); 00321 } 00322 return $task; 00323 } 00324 00325 /** 00326 * This method is used to get the database record for a given task 00327 * It returns the database record and not the task object 00328 * 00329 * @param integer $uid: primary key of the task to get 00330 * @return array Database record for the task 00331 * @see tx_scheduler::fetchTask() 00332 */ 00333 public function fetchTaskRecord($uid) { 00334 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'tx_scheduler_task', "uid = '" . intval($uid) . "'"); 00335 00336 // If the task is not found, throw an exception 00337 if ($GLOBALS['TYPO3_DB']->sql_num_rows($res) == 0) { 00338 throw new OutOfBoundsException('No task', 1247827244); 00339 00340 // Otherwise get the task's record 00341 } else { 00342 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res); 00343 $GLOBALS['TYPO3_DB']->sql_free_result($res); 00344 } 00345 return $row; 00346 } 00347 00348 /** 00349 * Fetches and unserializes task objects selected with some (SQL) condition 00350 * Objects are returned as an array 00351 * 00352 * @param string $where: part of a SQL where clause (without the "WHERE" keyword) 00353 * @param boolean $includeDisabledTasks: true if disabled tasks should be fetched too, false otherwise 00354 * @return array List of task objects 00355 */ 00356 public function fetchTasksWithCondition($where, $includeDisabledTasks = false) { 00357 $tasks = array(); 00358 if (!empty($where)) { 00359 $whereClause = $where; 00360 } 00361 if (!$includeDisabledTasks) { 00362 if (!empty($whereClause)) { 00363 $whereClause .= ' AND '; 00364 } 00365 $whereClause .= 'disable = 0'; 00366 } 00367 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('serialized_task_object', 'tx_scheduler_task', $whereClause); 00368 if ($res) { 00369 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) { 00370 $task = unserialize($row['serialized_task_object']); 00371 // Add the task to the list only if it is valid 00372 if ($this->isValidTaskObject($task)) { 00373 $task->setScheduler(); 00374 $tasks[] = $task; 00375 } 00376 } 00377 $GLOBALS['TYPO3_DB']->sql_free_result($res); 00378 } 00379 return $tasks; 00380 } 00381 00382 /** 00383 * This method encapsulates a very simple test for the purpose of clarity. 00384 * Registered tasks are stored in the database along with a serialized task object. 00385 * When a registered task is fetched, its object is unserialized. 00386 * At that point, if the class corresponding to the object is not available anymore 00387 * (e.g. because the extension providing it has been uninstalled), 00388 * the unserialization will produce an incomplete object. 00389 * This test checks whether the unserialized object is of the right (parent) class or not. 00390 * 00391 * @param object The object to test 00392 * @return boolean True if object is a task, false otherwise 00393 */ 00394 public function isValidTaskObject($task) { 00395 return $task instanceof tx_scheduler_Task; 00396 } 00397 00398 /** 00399 * This is a utility method that writes some message to the BE Log 00400 * It could be expanded to write to some other log 00401 * 00402 * @param string The message to write to the log 00403 * @param integer Status (0 = message, 1 = error) 00404 * @param mixed Key for the message 00405 * @return void 00406 */ 00407 public function log($message, $status = 0, $code = 'scheduler') { 00408 // Log only if enabled 00409 if (!empty($this->extConf['enableBELog'])) { 00410 $GLOBALS['BE_USER']->writelog( 00411 4, 00412 0, 00413 $status, 00414 $code, 00415 '[scheduler]: ' . $message, 00416 array() 00417 ); 00418 } 00419 } 00420 } 00421 00422 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/scheduler/class.tx_scheduler.php'])) { 00423 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/scheduler/class.tx_scheduler.php']); 00424 } 00425 00426 00427 ?>
1.8.0