2 namespace TYPO3\CMS\Scheduler;
41 $this->extConf = unserialize(
$GLOBALS[
'TYPO3_CONF_VARS'][
'EXT'][
'extConf'][
'scheduler']);
42 if (empty($this->extConf[
'maxLifetime'])) {
43 $this->extConf[
'maxLifetime'] = 1440;
45 if (empty($this->extConf[
'useAtdaemon'])) {
46 $this->extConf[
'useAtdaemon'] = 0;
58 public function addTask(Task\AbstractTask $task)
60 $taskUid = $task->getTaskUid();
61 if (empty($taskUid)) {
64 'disable' => $task->isDisabled(),
65 'description' => $task->getDescription(),
66 'task_group' => $task->getTaskGroup(),
67 'serialized_task_object' =>
'RESERVED'
96 $res = $db->exec_SELECTquery(
'uid, serialized_executions, serialized_task_object',
'tx_scheduler_task',
'serialized_executions <> \'\'');
97 $maxDuration = $this->extConf[
'maxLifetime'] * 60;
98 while ($row = $db->sql_fetch_assoc($res)) {
99 $executions = array();
100 if ($serialized_executions = unserialize($row[
'serialized_executions'])) {
101 foreach ($serialized_executions as $task) {
102 if ($tstamp - $task < $maxDuration) {
103 $executions[] = $task;
105 $task = unserialize($row[
'serialized_task_object']);
106 $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());
107 $this->log($logMessage);
111 $executionCount = count($executions);
112 if (count($serialized_executions) !== $executionCount) {
113 if ($executionCount === 0) {
116 $value = serialize($executions);
118 $db->exec_UPDATEquery('tx_scheduler_task
', 'uid =
' . (int)$row['uid
'], array('serialized_executions
' => $value));
121 $db->sql_free_result($res);
133 public function executeTask(Task\AbstractTask $task)
135 // Trigger the saving of the task, as this will calculate its next execution time
136 // This should be calculated all the time, even if the execution is skipped
137 // (in case it is skipped, this pushes back execution to the next possible date)
139 // Set a scheduler object for the task again,
140 // as it was removed during the save operation
141 $task->setScheduler();
143 // Task is already running and multiple executions are not allowed
144 if (!$task->areMultipleExecutionsAllowed() && $task->isExecutionRunning()) {
145 // Log multiple execution error
146 $logMessage = 'Task is already running and multiple executions are not allowed, skipping! Class:
' . get_class($task) . ', UID:
' . $task->getTaskUid();
147 $this->log($logMessage);
150 // Log scheduler invocation
151 $logMessage = 'Start execution. Class:
' . get_class($task) . ', UID:
' . $task->getTaskUid();
152 $this->log($logMessage);
153 // Register execution
154 $executionID = $task->markExecution();
158 $successfullyExecuted = $task->execute();
159 if (!$successfullyExecuted) {
160 throw new FailedExecutionException('Task failed to execute successfully. Class:
' . get_class($task) . ', UID:
' . $task->getTaskUid(), 1250596541);
162 } catch (\Exception $e) {
163 // Store exception, so that it can be saved to database
166 // make sure database-connection is fine
167 // for long-running tasks the database might meanwhile have disconnected
168 $this->getDatabaseConnection()->isConnected();
169 // Un-register execution
170 $task->unmarkExecution($executionID, $failure);
171 // Log completion of execution
172 $logMessage = 'Task executed. Class:
' . get_class($task) . ', UID:
' . $task->getTaskUid();
173 $this->log($logMessage);
174 // Now that the result of the task execution has been handled,
175 // throw the exception again, if any
176 if ($failure instanceof \Exception) {
189 public function recordLastRun($type = 'cron
')
191 // Validate input value
192 if ($type !== 'manual
' && $type !== 'cli-by-
id') {
196 $registry = GeneralUtility::makeInstance(Registry::class);
197 $runInformation = array('start
' => $GLOBALS['EXEC_TIME
'], 'end
' => time(), 'type
' => $type);
198 $registry->set('tx_scheduler
', 'lastRun
', $runInformation);
209 public function removeTask(Task\AbstractTask $task)
211 $taskUid = $task->getTaskUid();
212 if (!empty($taskUid)) {
213 $result = $this->getDatabaseConnection()->exec_DELETEquery('tx_scheduler_task
', 'uid =
' . $taskUid);
218 $this->scheduleNextSchedulerRunUsingAtDaemon();
229 public function saveTask(Task\AbstractTask $task)
231 $taskUid = $task->getTaskUid();
232 if (!empty($taskUid)) {
234 $executionTime = $task->getNextDueExecution();
235 $task->setExecutionTime($executionTime);
236 } catch (\Exception $e) {
237 $task->setDisabled(true);
240 $task->unsetScheduler();
242 'nextexecution
' => $executionTime,
243 'disable
' => $task->isDisabled(),
244 'description
' => $task->getDescription(),
245 'task_group
' => $task->getTaskGroup(),
246 'serialized_task_object
' => serialize($task)
248 $result = $this->getDatabaseConnection()->exec_UPDATEquery('tx_scheduler_task
', 'uid =
' . $taskUid, $fields);
253 $this->scheduleNextSchedulerRunUsingAtDaemon();
268 public function fetchTask($uid = 0)
270 // Define where clause
271 // If no uid is given, take any non-disabled task which has a next execution time in the past
274 'SELECT
' => 'tx_scheduler_task.uid AS uid, serialized_task_object
',
275 'FROM
' => 'tx_scheduler_task LEFT JOIN tx_scheduler_task_group ON tx_scheduler_task.task_group = tx_scheduler_task_group.uid
',
276 '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)
',
281 'SELECT
' => 'uid, serialized_task_object
',
282 'FROM
' => 'tx_scheduler_task
',
283 'WHERE
' => 'uid =
' . (int)$uid,
288 $db = $this->getDatabaseConnection();
289 $res = $db->exec_SELECT_queryArray($queryArray);
290 if ($res === false) {
291 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);
293 // If there are no available tasks, thrown an exception
294 if ($db->sql_num_rows($res) == 0) {
295 throw new \OutOfBoundsException('No task
', 1247827244);
297 $row = $db->sql_fetch_assoc($res);
299 $task = unserialize($row['serialized_task_object
']);
300 if ($this->isValidTaskObject($task)) {
301 // The task is valid, return it
302 $task->setScheduler();
304 // Forcibly set the disable flag to 1 in the database,
305 // so that the task does not come up again and again for execution
306 $db->exec_UPDATEquery('tx_scheduler_task
', 'uid =
' . $row['uid
'], array('disable
' => 1));
307 // Throw an exception to raise the problem
308 throw new \UnexpectedValueException('Could not unserialize task
', 1255083671);
310 $db->sql_free_result($res);
324 public function fetchTaskRecord($uid)
326 $db = $this->getDatabaseConnection();
327 $res = $db->exec_SELECTquery('*
', 'tx_scheduler_task
', 'uid =
' . (int)$uid);
328 // If the task is not found, throw an exception
329 if ($db->sql_num_rows($res) == 0) {
330 throw new \OutOfBoundsException('No task
', 1247827245);
332 $row = $db->sql_fetch_assoc($res);
333 $db->sql_free_result($res);
346 public function fetchTasksWithCondition($where, $includeDisabledTasks = false)
350 if (!empty($where)) {
351 $whereClause = $where;
353 if (!$includeDisabledTasks) {
354 if (!empty($whereClause)) {
355 $whereClause .= ' AND
';
357 $whereClause .= 'disable = 0
';
359 $db = $this->getDatabaseConnection();
360 $res = $db->exec_SELECTquery('serialized_task_object
', 'tx_scheduler_task
', $whereClause);
362 while ($row = $db->sql_fetch_assoc($res)) {
364 $task = unserialize($row['serialized_task_object
']);
365 // Add the task to the list only if it is valid
366 if ($this->isValidTaskObject($task)) {
367 $task->setScheduler();
371 $db->sql_free_result($res);
388 public function isValidTaskObject($task)
390 return $task instanceof Task\AbstractTask;
402 public function log($message, $status = 0, $code = 'scheduler
')
404 // Log only if enabled
405 if (!empty($this->extConf['enableBELog
'])) {
406 $GLOBALS['BE_USER
']->writelog(4, 0, $status, $code, '[scheduler]:
' . $message, array());
417 public function scheduleNextSchedulerRunUsingAtDaemon()
419 if ((int)$this->extConf['useAtdaemon
'] !== 1) {
423 $registry = GeneralUtility::makeInstance(Registry::class);
424 // Get at job id from registry and remove at job
425 $atJobId = $registry->get('tx_scheduler
', 'atJobId
');
426 if (MathUtility::canBeInterpretedAsInteger($atJobId)) {
427 shell_exec('atrm
' . (int)$atJobId . ' 2>&1
');
429 // Can not use fetchTask() here because if tasks have just executed
430 // they are not in the list of next executions
431 $tasks = $this->fetchTasksWithCondition('');
432 $nextExecution = false;
433 foreach ($tasks as $task) {
436 $tempNextExecution = $task->getNextDueExecution();
437 if ($nextExecution === false || $tempNextExecution < $nextExecution) {
438 $nextExecution = $tempNextExecution;
440 } catch (\OutOfBoundsException $e) {
441 // The event will not be executed again or has already ended - we don't have to consider it
for
445 if ($nextExecution !==
false) {
446 if ($nextExecution >
$GLOBALS[
'EXEC_TIME']) {
447 $startTime = strftime(
'%H:%M %F', $nextExecution);
449 $startTime =
'now+1minute';
451 $cliDispatchPath = PATH_site .
'typo3/cli_dispatch.phpsh';
452 list($cliDispatchPathEscaped, $startTimeEscaped) =
454 $cmd =
'echo ' . $cliDispatchPathEscaped .
' scheduler | at ' . $startTimeEscaped .
' 2>&1';
455 $output = shell_exec($cmd);
457 foreach (explode(LF, $output) as $outputLine) {
459 $outputParts = explode(
' ', $outputLine, 3);
464 $atJobId = (int)$outputParts[1];
465 $registry->set(
'tx_scheduler',
'atJobId', $atJobId);