‪TYPO3CMS  ‪main
Scheduler.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
16 namespace ‪TYPO3\CMS\Scheduler;
17 
18 use Psr\Log\LoggerInterface;
31 
36 {
37  protected LoggerInterface ‪$logger;
40 
44  public ‪$extConf = [];
45 
50  {
51  $this->logger = ‪$logger;
52  $this->taskSerializer = ‪$taskSerializer;
53  $this->schedulerTaskRepository = ‪$schedulerTaskRepository;
54  // Get configuration from the extension manager
55  $this->extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('scheduler');
56  if (empty($this->extConf['maxLifetime'])) {
57  $this->extConf['maxLifetime'] = 1440;
58  }
59  // Clean up the serialized execution arrays
60  $this->‪cleanExecutionArrays();
61  }
62 
70  public function ‪addTask(‪AbstractTask $task)
71  {
72  trigger_error('Scheduler->' . __METHOD__ . ' will be removed in TYPO3 v13.0. Use SchedulerTaskRepository instead.', E_USER_DEPRECATED);
73  return $this->schedulerTaskRepository->add($task);
74  }
75 
80  protected function ‪cleanExecutionArrays()
81  {
82  $tstamp = ‪$GLOBALS['EXEC_TIME'];
83  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
84  $queryBuilder = $connectionPool->getQueryBuilderForTable('tx_scheduler_task');
85 
86  // Select all tasks with executions
87  // NOTE: this cleanup is done for disabled tasks too,
88  // to avoid leaving old executions lying around
89  $result = $queryBuilder->select('uid', 'serialized_executions', 'serialized_task_object')
90  ->from('tx_scheduler_task')
91  ->where(
92  $queryBuilder->expr()->neq(
93  'serialized_executions',
94  $queryBuilder->createNamedParameter('')
95  ),
96  $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT))
97  )
98  ->executeQuery();
99  $maxDuration = $this->extConf['maxLifetime'] * 60;
100  while ($row = $result->fetchAssociative()) {
101  $executions = [];
102  if ($serialized_executions = unserialize($row['serialized_executions'])) {
103  foreach ($serialized_executions as $task) {
104  if ($tstamp - $task < $maxDuration) {
105  $executions[] = $task;
106  } else {
107  try {
108  $schedulerTask = $this->taskSerializer->deserialize($row['serialized_task_object']);
109  $taskClass = get_class($schedulerTask);
110  $executionTime = date('Y-m-d H:i:s', $schedulerTask->getExecutionTime());
111  } catch (InvalidTaskException $e) {
112  $taskClass = 'unknown class';
113  $executionTime = 'unknown time';
114  }
115  $this->‪log('Removing logged execution, assuming that the process is dead. Execution of \'' . $taskClass . '\' (UID: ' . $row['uid'] . ') was started at ' . $executionTime);
116  }
117  }
118  }
119  $executionCount = count($executions);
120  if (!is_array($serialized_executions) || count($serialized_executions) !== $executionCount) {
121  if ($executionCount === 0) {
122  $value = '';
123  } else {
124  $value = serialize($executions);
125  }
126  $connectionPool->getConnectionForTable('tx_scheduler_task')->update(
127  'tx_scheduler_task',
128  ['serialized_executions' => $value],
129  ['uid' => (int)$row['uid']],
130  ['serialized_executions' => Connection::PARAM_LOB]
131  );
132  }
133  }
134  }
135 
144  public function executeTask(AbstractTask $task)
145  {
146  $task->setRunOnNextCronJob(false);
147  // Trigger the saving of the task, as this will calculate its next execution time
148  // This should be calculated all the time, even if the execution is skipped
149  // (in case it is skipped, this pushes back execution to the next possible date)
150  $this->schedulerTaskRepository->update($task);
151  // Set a scheduler object for the task again,
152  // as it was removed during the save operation
153  $task->setScheduler();
154  $result = true;
155  // Task is already running and multiple executions are not allowed
156  if (!$task->areMultipleExecutionsAllowed() && $this->schedulerTaskRepository->isTaskMarkedAsRunning($task)) {
157  // Log multiple execution error
158  $this->logger->info('Task is already running and multiple executions are not allowed, skipping! Class: {class}, UID: {uid}', [
159  'class' => get_class($task),
160  'uid' => $task->getTaskUid(),
161  ]);
162  $result = false;
163  } else {
164  // Log scheduler invocation
165  $this->logger->info('Start execution. Class: {class}, UID: {uid}', [
166  'class' => get_class($task),
167  'uid' => $task->getTaskUid(),
168  ]);
169  // Register execution
170  $executionID = $this->schedulerTaskRepository->addExecutionToTask($task);
171  $failureString = '';
172  $e = null;
173  try {
174  // Execute task
175  $successfullyExecuted = $task->execute();
176  if (!$successfullyExecuted) {
177  throw new FailedExecutionException('Task failed to execute successfully. Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid(), 1250596541);
178  }
179  } catch (\Throwable $e) {
180  // Log failed execution
181  $this->logger->error('Task failed to execute successfully. Class: {class}, UID: {uid}', [
182  'class' => get_class($task),
183  'uid' => $task->getTaskUid(),
184  'exception' => $e,
185  ]);
186  // Store exception, so that it can be saved to database
187  // Do not serialize the complete exception or the trace, this can lead to huge strings > 50MB
188  $failureString = serialize([
189  'code' => $e->getCode(),
190  'message' => $e->getMessage(),
191  'file' => $e->getFile(),
192  'line' => $e->getLine(),
193  'traceString' => $e->getTraceAsString(),
194  ]);
195  }
196  // Un-register execution
197  $this->schedulerTaskRepository->removeExecutionOfTask($task, $executionID, $failureString);
198  // Log completion of execution
199  $this->logger->info('Task executed. Class: {class}, UID: {uid}', [
200  'class' => get_class($task),
201  'uid' => $task->getTaskUid(),
202  ]);
203  // Now that the result of the task execution has been handled,
204  // throw the exception again, if any
205  if ($e instanceof \Throwable) {
206  throw $e;
207  }
208  }
209  return $result;
210  }
211 
217  public function recordLastRun($type = 'cron')
218  {
219  // Validate input value
220  if ($type !== 'manual' && $type !== 'cli-by-id') {
221  $type = 'cron';
222  }
223  $registry = GeneralUtility::makeInstance(Registry::class);
224  $runInformation = ['start' => $GLOBALS['EXEC_TIME'], 'end' => time(), 'type' => $type];
225  $registry->set('tx_scheduler', 'lastRun', $runInformation);
226  }
227 
237  public function removeTask(AbstractTask $task)
238  {
239  trigger_error('Scheduler->' . __METHOD__ . ' will be removed in ‪TYPO3 v13.0. Use SchedulerTaskRepository instead.', E_USER_DEPRECATED);
240  return $this->schedulerTaskRepository->remove($task);
241  }
242 
247  public function saveTask(AbstractTask $task): bool
248  {
249  trigger_error('Scheduler->' . __METHOD__ . ' will be removed in ‪TYPO3 v13.0. Use SchedulerTaskRepository instead.', E_USER_DEPRECATED);
250  return $this->schedulerTaskRepository->update($task);
251  }
252 
264  public function fetchTask($uid = 0): AbstractTask
265  {
266  trigger_error('Scheduler->' . __METHOD__ . ' will be removed in ‪TYPO3 v13.0. Use SchedulerTaskRepository instead.', E_USER_DEPRECATED);
267  if ($uid > 0) {
268  return $this->schedulerTaskRepository->findByUid((int)$uid);
269  }
270  return $this->schedulerTaskRepository->findNextExecutableTask();
271  }
272 
283  public function fetchTaskRecord($uid)
284  {
285  trigger_error('Scheduler->' . __METHOD__ . ' will be removed in ‪TYPO3 v13.0. Use SchedulerTaskRepository instead.', E_USER_DEPRECATED);
286  $row = $this->schedulerTaskRepository->findRecordByUid((int)$uid);
287  // If the task is not found, throw an exception
288  if (empty($row)) {
289  throw new \OutOfBoundsException('No task', 1247827245);
290  }
291  return $row;
292  }
293 
303  public function fetchTasksWithCondition($where, $includeDisabledTasks = false)
304  {
305  trigger_error('Scheduler->' . __METHOD__ . ' will be removed in ‪TYPO3 v13.0. Use SchedulerTaskRepository instead.', E_USER_DEPRECATED);
306  $tasks = [];
307  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
308  ->getQueryBuilderForTable('tx_scheduler_task');
309 
310  $queryBuilder
311  ->select('serialized_task_object')
312  ->from('tx_scheduler_task')
313  ->where(
314  $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT))
315  );
316 
317  if (!$includeDisabledTasks) {
318  $queryBuilder->andWhere(
319  $queryBuilder->expr()->eq('disable', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT))
320  );
321  }
322 
323  if (!empty($where)) {
324  $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($where));
325  }
326 
327  $result = $queryBuilder->executeQuery();
328  while ($row = $result->fetchAssociative()) {
329  try {
330  $task = $this->taskSerializer->deserialize($row['serialized_task_object']);
331  } catch (InvalidTaskException) {
332  continue;
333  }
334 
335  // Add the task to the list only if it is valid
336  if ((new TaskValidator())->isValid($task)) {
337  $task->setScheduler();
338  $tasks[] = $task;
339  }
340  }
341 
342  return $tasks;
343  }
344 
358  public function isValidTaskObject($task)
359  {
360  trigger_error('Scheduler->' . __METHOD__ . ' will be removed in ‪TYPO3 v13.0. Use SchedulerTaskRepository instead.', E_USER_DEPRECATED);
361  return (new TaskValidator())->isValid($task);
362  }
363 
373  public function log($message, $status = 0, $code = '')
374  {
375  $messageTemplate = '[scheduler]: {code} - {original_message}';
376  // @todo Replace these magic numbers with constants or enums.
377  switch ((int)$status) {
378  // error (user problem)
379  case 1:
380  $this->logger->alert($messageTemplate, ['code' => $code, 'original_message' => $message]);
381  break;
382  // System Error (which should not happen)
383  case 2:
384  $this->logger->error($messageTemplate, ['code' => $code, 'original_message' => $message]);
385  break;
386  // security notice (admin)
387  case 3:
388  $this->logger->emergency($messageTemplate, ['code' => $code, 'original_message' => $message]);
389  break;
390  // regular message (= 0)
391  default:
392  $this->logger->info($messageTemplate, ['code' => $code, 'original_message' => $message]);
393  }
394  }
395 }
‪TYPO3\CMS\Scheduler\Scheduler\$taskSerializer
‪TaskSerializer $taskSerializer
Definition: Scheduler.php:38
‪TYPO3\CMS\Scheduler
Definition: AbstractAdditionalFieldProvider.php:18
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:47
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration
Definition: ExtensionConfiguration.php:47
‪TYPO3
‪TYPO3\CMS\Core\Registry
Definition: Registry.php:33
‪TYPO3\CMS\Scheduler\Domain\Repository\SchedulerTaskRepository
Definition: SchedulerTaskRepository.php:36
‪TYPO3\CMS\Scheduler\Scheduler\$extConf
‪array $extConf
Definition: Scheduler.php:43
‪TYPO3\CMS\Scheduler\Task\TaskSerializer
Definition: TaskSerializer.php:28
‪TYPO3\CMS\Scheduler\Scheduler\log
‪log($message, $status=0, $code='')
Definition: Scheduler.php:372
‪TYPO3\CMS\Scheduler\Scheduler\addTask
‪bool addTask(AbstractTask $task)
Definition: Scheduler.php:69
‪TYPO3\CMS\Scheduler\Scheduler\$logger
‪LoggerInterface $logger
Definition: Scheduler.php:37
‪TYPO3\CMS\Scheduler\Task\AbstractTask
Definition: AbstractTask.php:33
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Scheduler\Scheduler\__construct
‪__construct(LoggerInterface $logger, TaskSerializer $taskSerializer, SchedulerTaskRepository $schedulerTaskRepository)
Definition: Scheduler.php:48
‪TYPO3\CMS\Scheduler\Scheduler
Definition: Scheduler.php:36
‪TYPO3\CMS\Scheduler\Scheduler\cleanExecutionArrays
‪cleanExecutionArrays()
Definition: Scheduler.php:79
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:36
‪TYPO3\CMS\Scheduler\Validation\Validator\TaskValidator
Definition: TaskValidator.php:23
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:23
‪TYPO3\CMS\Scheduler\Scheduler\$schedulerTaskRepository
‪SchedulerTaskRepository $schedulerTaskRepository
Definition: Scheduler.php:39
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:51
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Scheduler\Exception\InvalidTaskException
Definition: InvalidTaskException.php:27