TYPO3 CMS  TYPO3_6-2
Scheduler.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Scheduler;
3 
24 
29  public $extConf = array();
30 
36  public function __construct() {
37  // Get configuration from the extension manager
38  $this->extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['scheduler']);
39  if (empty($this->extConf['maxLifetime'])) {
40  $this->extConf['maxLifetime'] = 1440;
41  }
42  if (empty($this->extConf['useAtdaemon'])) {
43  $this->extConf['useAtdaemon'] = 0;
44  }
45  // Clean up the serialized execution arrays
46  $this->cleanExecutionArrays();
47  }
48 
55  public function addTask(\TYPO3\CMS\Scheduler\Task\AbstractTask $task) {
56  $taskUid = $task->getTaskUid();
57  if (empty($taskUid)) {
58  $fields = array(
59  'crdate' => $GLOBALS['EXEC_TIME'],
60  'disable' => $task->isDisabled(),
61  'description' => $task->getDescription(),
62  'task_group' => $task->getTaskGroup(),
63  'serialized_task_object' => 'RESERVED'
64  );
65  $result = $GLOBALS['TYPO3_DB']->exec_INSERTquery('tx_scheduler_task', $fields);
66  if ($result) {
67  $task->setTaskUid($GLOBALS['TYPO3_DB']->sql_insert_id());
68  $task->save();
69  $result = TRUE;
70  } else {
71  $result = FALSE;
72  }
73  } else {
74  $result = FALSE;
75  }
76  return $result;
77  }
78 
85  protected function cleanExecutionArrays() {
86  $tstamp = $GLOBALS['EXEC_TIME'];
87  // Select all tasks with executions
88  // NOTE: this cleanup is done for disabled tasks too,
89  // to avoid leaving old executions lying around
90  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid, serialized_executions, serialized_task_object', 'tx_scheduler_task', 'serialized_executions <> \'\'');
91  $maxDuration = $this->extConf['maxLifetime'] * 60;
92  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
93  $executions = array();
94  if ($serialized_executions = unserialize($row['serialized_executions'])) {
95  foreach ($serialized_executions as $task) {
96  if ($tstamp - $task < $maxDuration) {
97  $executions[] = $task;
98  } else {
99  $task = unserialize($row['serialized_task_object']);
100  $logMessage = 'Removing logged execution, assuming that the process is dead. Execution of \'' . get_class($task) . '\' (UID: ' . $row['uid'] . ') was started at ' . date('Y-m-d H:i:s', $task->getExecutionTime());
101  $this->log($logMessage);
102  }
103  }
104  }
105  if (count($serialized_executions) != count($executions)) {
106  if (count($executions) == 0) {
107  $value = '';
108  } else {
109  $value = serialize($executions);
110  }
111  $GLOBALS['TYPO3_DB']->exec_UPDATEquery('tx_scheduler_task', 'uid = ' . (int)$row['uid'], array('serialized_executions' => $value));
112  }
113  }
114  $GLOBALS['TYPO3_DB']->sql_free_result($res);
115  }
116 
124  public function executeTask(\TYPO3\CMS\Scheduler\Task\AbstractTask $task) {
125  // Trigger the saving of the task, as this will calculate its next execution time
126  // This should be calculated all the time, even if the execution is skipped
127  // (in case it is skipped, this pushes back execution to the next possible date)
128  $task->save();
129  // Set a scheduler object for the task again,
130  // as it was removed during the save operation
131  $task->setScheduler();
132  $result = TRUE;
133  // Task is already running and multiple executions are not allowed
134  if (!$task->areMultipleExecutionsAllowed() && $task->isExecutionRunning()) {
135  // Log multiple execution error
136  $logMessage = 'Task is already running and multiple executions are not allowed, skipping! Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid();
137  $this->log($logMessage);
138  $result = FALSE;
139  } else {
140  // Log scheduler invocation
141  $logMessage = 'Start execution. Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid();
142  $this->log($logMessage);
143  // Register execution
144  $executionID = $task->markExecution();
145  $failure = NULL;
146  try {
147  // Execute task
148  $successfullyExecuted = $task->execute();
149  if (!$successfullyExecuted) {
150  throw new \TYPO3\CMS\Scheduler\FailedExecutionException('Task failed to execute successfully. Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid(), 1250596541);
151  }
152  } catch (\Exception $e) {
153  // Store exception, so that it can be saved to database
154  $failure = $e;
155  }
156  // make sure database-connection is fine
157  // for long-running tasks the database might meanwhile have disconnected
158  $GLOBALS['TYPO3_DB']->isConnected();
159  // Un-register execution
160  $task->unmarkExecution($executionID, $failure);
161  // Log completion of execution
162  $logMessage = 'Task executed. Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid();
163  $this->log($logMessage);
164  // Now that the result of the task execution has been handled,
165  // throw the exception again, if any
166  if ($failure instanceof \Exception) {
167  throw $failure;
168  }
169  }
170  return $result;
171  }
172 
179  public function recordLastRun($type = 'cron') {
180  // Validate input value
181  if ($type !== 'manual' && $type !== 'cli-by-id') {
182  $type = 'cron';
183  }
185  $registry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Registry');
186  $runInformation = array('start' => $GLOBALS['EXEC_TIME'], 'end' => time(), 'type' => $type);
187  $registry->set('tx_scheduler', 'lastRun', $runInformation);
188  }
189 
197  public function removeTask(\TYPO3\CMS\Scheduler\Task\AbstractTask $task) {
198  $taskUid = $task->getTaskUid();
199  if (!empty($taskUid)) {
200  $result = $GLOBALS['TYPO3_DB']->exec_DELETEquery('tx_scheduler_task', 'uid = ' . $taskUid);
201  } else {
202  $result = FALSE;
203  }
204  if ($result) {
205  $this->scheduleNextSchedulerRunUsingAtDaemon();
206  }
207  return $result;
208  }
209 
216  public function saveTask(\TYPO3\CMS\Scheduler\Task\AbstractTask $task) {
217  $taskUid = $task->getTaskUid();
218  if (!empty($taskUid)) {
219  try {
220  $executionTime = $task->getNextDueExecution();
221  $task->setExecutionTime($executionTime);
222  } catch (\Exception $e) {
223  $task->setDisabled(TRUE);
224  $executionTime = 0;
225  }
226  $task->unsetScheduler();
227  $fields = array(
228  'nextexecution' => $executionTime,
229  'disable' => $task->isDisabled(),
230  'description' => $task->getDescription(),
231  'task_group' => $task->getTaskGroup(),
232  'serialized_task_object' => serialize($task)
233  );
234  $result = $GLOBALS['TYPO3_DB']->exec_UPDATEquery('tx_scheduler_task', 'uid = ' . $taskUid, $fields);
235  } else {
236  $result = FALSE;
237  }
238  if ($result) {
239  $this->scheduleNextSchedulerRunUsingAtDaemon();
240  }
241  return $result;
242  }
243 
252  public function fetchTask($uid = 0) {
253  // Define where clause
254  // If no uid is given, take any non-disabled task which has a next execution time in the past
255  if (empty($uid)) {
256  $queryArray = array(
257  'SELECT' => 'tx_scheduler_task.uid AS uid, serialized_task_object',
258  'FROM' => 'tx_scheduler_task LEFT JOIN tx_scheduler_task_group ON tx_scheduler_task.task_group = tx_scheduler_task_group.uid',
259  'WHERE' => 'disable = 0 AND nextexecution != 0 AND nextexecution <= ' . $GLOBALS['EXEC_TIME'] . ' AND (tx_scheduler_task_group.hidden = 0 OR tx_scheduler_task_group.hidden IS NULL)',
260  'LIMIT' => 1
261  );
262  } else {
263  $queryArray = array(
264  'SELECT' => 'uid, serialized_task_object',
265  'FROM' => 'tx_scheduler_task',
266  'WHERE' => 'uid = ' . (int)$uid,
267  'LIMIT' => 1
268  );
269  }
270 
271  $res = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryArray);
272  if ($res === FALSE) {
273  throw new \OutOfBoundsException('Query could not be executed. Possible defect in tables tx_scheduler_task or tx_scheduler_task_group or DB server problems', 1422044826);
274  }
275  // If there are no available tasks, thrown an exception
276  if ($GLOBALS['TYPO3_DB']->sql_num_rows($res) == 0) {
277  throw new \OutOfBoundsException('No task', 1247827244);
278  } else {
279  $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
281  $task = unserialize($row['serialized_task_object']);
282  if ($this->isValidTaskObject($task)) {
283  // The task is valid, return it
284  $task->setScheduler();
285  } else {
286  // Forcibly set the disable flag to 1 in the database,
287  // so that the task does not come up again and again for execution
288  $GLOBALS['TYPO3_DB']->exec_UPDATEquery('tx_scheduler_task', 'uid = ' . $row['uid'], array('disable' => 1));
289  // Throw an exception to raise the problem
290  throw new \UnexpectedValueException('Could not unserialize task', 1255083671);
291  }
292  $GLOBALS['TYPO3_DB']->sql_free_result($res);
293  }
294  return $task;
295  }
296 
305  public function fetchTaskRecord($uid) {
306  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'tx_scheduler_task', 'uid = ' . (int)$uid);
307  // If the task is not found, throw an exception
308  if ($GLOBALS['TYPO3_DB']->sql_num_rows($res) == 0) {
309  throw new \OutOfBoundsException('No task', 1247827245);
310  } else {
311  $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
312  $GLOBALS['TYPO3_DB']->sql_free_result($res);
313  }
314  return $row;
315  }
316 
325  public function fetchTasksWithCondition($where, $includeDisabledTasks = FALSE) {
326  $whereClause = '';
327  $tasks = array();
328  if (!empty($where)) {
329  $whereClause = $where;
330  }
331  if (!$includeDisabledTasks) {
332  if (!empty($whereClause)) {
333  $whereClause .= ' AND ';
334  }
335  $whereClause .= 'disable = 0';
336  }
337  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('serialized_task_object', 'tx_scheduler_task', $whereClause);
338  if ($res) {
339  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
341  $task = unserialize($row['serialized_task_object']);
342  // Add the task to the list only if it is valid
343  if ($this->isValidTaskObject($task)) {
344  $task->setScheduler();
345  $tasks[] = $task;
346  }
347  }
348  $GLOBALS['TYPO3_DB']->sql_free_result($res);
349  }
350  return $tasks;
351  }
352 
365  public function isValidTaskObject($task) {
366  return $task instanceof \TYPO3\CMS\Scheduler\Task\AbstractTask;
367  }
368 
378  public function log($message, $status = 0, $code = 'scheduler') {
379  // Log only if enabled
380  if (!empty($this->extConf['enableBELog'])) {
381  $GLOBALS['BE_USER']->writelog(4, 0, $status, $code, '[scheduler]: ' . $message, array());
382  }
383  }
384 
392  public function scheduleNextSchedulerRunUsingAtDaemon() {
393  if ((int)$this->extConf['useAtdaemon'] !== 1) {
394  return FALSE;
395  }
397  $registry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Registry');
398  // Get at job id from registry and remove at job
399  $atJobId = $registry->get('tx_scheduler', 'atJobId');
400  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($atJobId)) {
401  shell_exec('atrm ' . (int)$atJobId . ' 2>&1');
402  }
403  // Can not use fetchTask() here because if tasks have just executed
404  // they are not in the list of next executions
405  $tasks = $this->fetchTasksWithCondition('');
406  $nextExecution = FALSE;
407  foreach ($tasks as $task) {
408  try {
410  $tempNextExecution = $task->getNextDueExecution();
411  if ($nextExecution === FALSE || $tempNextExecution < $nextExecution) {
412  $nextExecution = $tempNextExecution;
413  }
414  } catch (\OutOfBoundsException $e) {
415  // The event will not be executed again or has already ended - we don't have to consider it for
416  // scheduling the next "at" run
417  }
418  }
419  if ($nextExecution !== FALSE) {
420  if ($nextExecution > $GLOBALS['EXEC_TIME']) {
421  $startTime = strftime('%H:%M %F', $nextExecution);
422  } else {
423  $startTime = 'now+1minute';
424  }
425  $cliDispatchPath = PATH_site . 'typo3/cli_dispatch.phpsh';
426  $currentLocale = setlocale(LC_CTYPE, 0);
427  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
428  setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
429  }
430  $cmd = 'echo ' . escapeshellarg($cliDispatchPath) . ' scheduler | at ' . escapeshellarg($startTime) . ' 2>&1';
431  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
432  setlocale(LC_CTYPE, $currentLocale);
433  }
434  $output = shell_exec($cmd);
435  $outputParts = '';
436  foreach (explode(LF, $output) as $outputLine) {
437  if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($outputLine, 'job')) {
438  $outputParts = explode(' ', $outputLine, 3);
439  break;
440  }
441  }
442  if ($outputParts[0] === 'job' && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($outputParts[1])) {
443  $atJobId = (int)$outputParts[1];
444  $registry->set('tx_scheduler', 'atJobId', $atJobId);
445  }
446  }
447  return TRUE;
448  }
449 
450 }
$registry
Definition: ext_tables.php:46
if($list_of_literals) if(!empty($literals)) if(!empty($literals)) $result
Analyse literals to prepend the N char to them if their contents aren&#39;t numeric.
addTask(\TYPO3\CMS\Scheduler\Task\AbstractTask $task)
Definition: Scheduler.php:55
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]