[BUGFIX] Log exception as array instead of serialized
[Packages/TYPO3.CMS.git] / typo3 / sysext / scheduler / Classes / Task / AbstractTask.php
1 <?php
2 namespace TYPO3\CMS\Scheduler\Task;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * This is the base class for all Scheduler tasks
19 * It's an abstract class, not designed to be instantiated directly
20 * All Scheduler tasks should inherit from this class
21 */
22 abstract class AbstractTask {
23
24 /**
25 * Reference to a scheduler object
26 *
27 * @var \TYPO3\CMS\Scheduler\Scheduler
28 */
29 protected $scheduler;
30
31 /**
32 * The unique id of the task used to identify it in the database
33 *
34 * @var int
35 */
36 protected $taskUid;
37
38 /**
39 * Disable flag, TRUE if task is disabled, FALSE otherwise
40 *
41 * @var bool
42 */
43 protected $disabled = FALSE;
44
45 /**
46 * The execution object related to the task
47 *
48 * @var \TYPO3\CMS\Scheduler\Execution
49 */
50 protected $execution;
51
52 /**
53 * This variable contains the time of next execution of the task
54 *
55 * @var int
56 */
57 protected $executionTime = 0;
58
59 /**
60 * Description for the task
61 *
62 * @var string
63 */
64 protected $description = '';
65
66 /**
67 * Task group for this task
68 *
69 * @var int
70 */
71 protected $taskGroup;
72
73 /**
74 * Constructor
75 */
76 public function __construct() {
77 $this->setScheduler();
78 $this->execution = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Execution::class);
79 }
80
81 /**
82 * This is the main method that is called when a task is executed
83 * It MUST be implemented by all classes inheriting from this one
84 * Note that there is no error handling, errors and failures are expected
85 * to be handled and logged by the client implementations.
86 * Should return TRUE on successful execution, FALSE on error.
87 *
88 * @return bool Returns TRUE on successful execution, FALSE on error
89 */
90 abstract public function execute();
91
92 /**
93 * This method is designed to return some additional information about the task,
94 * that may help to set it apart from other tasks from the same class
95 * This additional information is used - for example - in the Scheduler's BE module
96 * This method should be implemented in most task classes
97 *
98 * @return string Information to display
99 */
100 public function getAdditionalInformation() {
101 return '';
102 }
103
104 /**
105 * This method is used to set the unique id of the task
106 *
107 * @param int $id Primary key (from the database record) of the scheduled task
108 * @return void
109 */
110 public function setTaskUid($id) {
111 $this->taskUid = (int)$id;
112 }
113
114 /**
115 * This method returns the unique id of the task
116 *
117 * @return int The id of the task
118 */
119 public function getTaskUid() {
120 return $this->taskUid;
121 }
122
123 /**
124 * This method returns the title of the scheduler task
125 *
126 * @return string
127 */
128 public function getTaskTitle() {
129 return $GLOBALS['LANG']->sL($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][get_class($this)]['title']);
130 }
131
132 /**
133 * This method returns the description of the scheduler task
134 *
135 * @return string
136 */
137 public function getTaskDescription() {
138 return $GLOBALS['LANG']->sL($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][get_class($this)]['description']);
139 }
140
141 /**
142 * This method returns the class name of the scheduler task
143 *
144 * @return string
145 */
146 public function getTaskClassName() {
147 return get_class($this);
148 }
149
150 /**
151 * This method returns the disable status of the task
152 *
153 * @return bool TRUE if task is disabled, FALSE otherwise
154 */
155 public function isDisabled() {
156 return $this->disabled;
157 }
158
159 /**
160 * This method is used to set the disable status of the task
161 *
162 * @param bool $flag TRUE if task should be disabled, FALSE otherwise
163 * @return void
164 */
165 public function setDisabled($flag) {
166 if ($flag) {
167 $this->disabled = TRUE;
168 } else {
169 $this->disabled = FALSE;
170 }
171 }
172
173 /**
174 * This method is used to set the timestamp corresponding to the next execution time of the task
175 *
176 * @param int $timestamp Timestamp of next execution
177 * @return void
178 */
179 public function setExecutionTime($timestamp) {
180 $this->executionTime = (int)$timestamp;
181 }
182
183 /**
184 * This method returns the task group (uid) of the task
185 *
186 * @return int Uid of task group
187 */
188 public function getTaskGroup() {
189 return $this->taskGroup;
190 }
191
192 /**
193 * This method is used to set the task group (uid) of the task
194 *
195 * @param int $timestamp Uid of task group
196 * @return void
197 */
198 public function setTaskGroup($taskGroup) {
199 $this->taskGroup = (int)$taskGroup;
200 }
201
202 /**
203 * This method returns the timestamp corresponding to the next execution time of the task
204 *
205 * @return int Timestamp of next execution
206 */
207 public function getExecutionTime() {
208 return $this->executionTime;
209 }
210
211 /**
212 * This method is used to set the description of the task
213 *
214 * @param string $description Description
215 * @return void
216 */
217 public function setDescription($description) {
218 $this->description = $description;
219 }
220
221 /**
222 * This method returns the description of the task
223 *
224 * @return string Description
225 */
226 public function getDescription() {
227 return $this->description;
228 }
229
230 /**
231 * Sets the internal reference to the singleton instance of the Scheduler
232 *
233 * @return void
234 */
235 public function setScheduler() {
236 $this->scheduler = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Scheduler::class);
237 }
238
239 /**
240 * Unsets the internal reference to the singleton instance of the Scheduler
241 * This is done before a task is serialized, so that the scheduler instance
242 * is not saved to the database too
243 *
244 * @return void
245 */
246 public function unsetScheduler() {
247 unset($this->scheduler);
248 }
249
250 /**
251 * Registers a single execution of the task
252 *
253 * @param int $timestamp Timestamp of the next execution
254 */
255 public function registerSingleExecution($timestamp) {
256 /** @var $execution \TYPO3\CMS\Scheduler\Execution */
257 $execution = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Execution::class);
258 $execution->setStart($timestamp);
259 $execution->setInterval(0);
260 $execution->setEnd($timestamp);
261 $execution->setCronCmd('');
262 $execution->setMultiple(0);
263 $execution->setIsNewSingleExecution(TRUE);
264 // Replace existing execution object
265 $this->execution = $execution;
266 }
267
268 /**
269 * Registers a recurring execution of the task
270 *
271 * @param int $start The first date/time where this execution should occur (timestamp)
272 * @param string $interval Execution interval in seconds
273 * @param int $end The last date/time where this execution should occur (timestamp)
274 * @param bool $multiple Set to FALSE if multiple executions of this task are not permitted in parallel
275 * @param string $cron_cmd Used like in crontab (minute hour day month weekday)
276 * @return void
277 */
278 public function registerRecurringExecution($start, $interval, $end = 0, $multiple = FALSE, $cron_cmd = '') {
279 /** @var $execution \TYPO3\CMS\Scheduler\Execution */
280 $execution = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Execution::class);
281 // Set general values
282 $execution->setStart($start);
283 $execution->setEnd($end);
284 $execution->setMultiple($multiple);
285 if (empty($cron_cmd)) {
286 // Use interval
287 $execution->setInterval($interval);
288 $execution->setCronCmd('');
289 } else {
290 // Use cron syntax
291 $execution->setInterval(0);
292 $execution->setCronCmd($cron_cmd);
293 }
294 // Replace existing execution object
295 $this->execution = $execution;
296 }
297
298 /**
299 * Sets the internal execution object
300 *
301 * @param \TYPO3\CMS\Scheduler\Execution $execution The execution to add
302 */
303 public function setExecution(\TYPO3\CMS\Scheduler\Execution $execution) {
304 $this->execution = $execution;
305 }
306
307 /**
308 * Returns the execution object
309 *
310 * @return \TYPO3\CMS\Scheduler\Execution The internal execution object
311 */
312 public function getExecution() {
313 return $this->execution;
314 }
315
316 /**
317 * Returns the timestamp for next due execution of the task
318 *
319 * @return int Date and time of the next execution as a timestamp
320 */
321 public function getNextDueExecution() {
322 // NOTE: this call may throw an exception, but we let it bubble up
323 return $this->execution->getNextExecution();
324 }
325
326 /**
327 * Returns TRUE if several runs of the task are allowed concurrently
328 *
329 * @return bool TRUE if concurrent executions are allowed, FALSE otherwise
330 */
331 public function areMultipleExecutionsAllowed() {
332 return $this->execution->getMultiple();
333 }
334
335 /**
336 * Returns TRUE if an instance of the task is already running
337 *
338 * @return bool TRUE if an instance is already running, FALSE otherwise
339 */
340 public function isExecutionRunning() {
341 $isRunning = FALSE;
342 $queryArr = array(
343 'SELECT' => 'serialized_executions',
344 'FROM' => 'tx_scheduler_task',
345 'WHERE' => 'uid = ' . $this->taskUid,
346 'LIMIT' => 1
347 );
348 $res = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryArr);
349 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
350 if (!empty($row['serialized_executions'])) {
351 $isRunning = TRUE;
352 }
353 }
354 $GLOBALS['TYPO3_DB']->sql_free_result($res);
355 return $isRunning;
356 }
357
358 /**
359 * This method adds current execution to the execution list
360 * It also logs the execution time and mode
361 *
362 * @return int Execution id
363 */
364 public function markExecution() {
365 $queryArr = array(
366 'SELECT' => 'serialized_executions',
367 'FROM' => 'tx_scheduler_task',
368 'WHERE' => 'uid = ' . $this->taskUid,
369 'LIMIT' => 1
370 );
371 $res = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryArr);
372 $runningExecutions = array();
373 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
374 if ($row['serialized_executions'] !== '') {
375 $runningExecutions = unserialize($row['serialized_executions']);
376 }
377 }
378 $GLOBALS['TYPO3_DB']->sql_free_result($res);
379 // Count the number of existing executions and use that number as a key
380 // (we need to know that number, because it is returned at the end of the method)
381 $numExecutions = count($runningExecutions);
382 $runningExecutions[$numExecutions] = time();
383 // Define the context in which the script is running
384 $context = 'BE';
385 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI) {
386 $context = 'CLI';
387 }
388 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('tx_scheduler_task', 'uid = ' . $this->taskUid, array(
389 'serialized_executions' => serialize($runningExecutions),
390 'lastexecution_time' => time(),
391 'lastexecution_context' => $context
392 ));
393 return $numExecutions;
394 }
395
396 /**
397 * Removes given execution from list
398 *
399 * @param int $executionID Id of the execution to remove.
400 * @param \Exception $failure An exception to signal a failed execution
401 * @return void
402 */
403 public function unmarkExecution($executionID, \Exception $failure = NULL) {
404 // Get the executions for the task
405 $queryArr = array(
406 'SELECT' => 'serialized_executions',
407 'FROM' => 'tx_scheduler_task',
408 'WHERE' => 'uid = ' . $this->taskUid,
409 'LIMIT' => 1
410 );
411 $res = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryArr);
412 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
413 if ($row['serialized_executions'] !== '') {
414 $runningExecutions = unserialize($row['serialized_executions']);
415 // Remove the selected execution
416 unset($runningExecutions[$executionID]);
417 if (!empty($runningExecutions)) {
418 // Re-serialize the updated executions list (if necessary)
419 $runningExecutionsSerialized = serialize($runningExecutions);
420 } else {
421 $runningExecutionsSerialized = '';
422 }
423 if ($failure instanceof \Exception) {
424 // Log failed execution
425 $logMessage = 'Task failed to execute successfully. Class: ' . get_class($this) . ', UID: ' . $this->taskUid . '. ' . $failure->getMessage();
426 $this->scheduler->log($logMessage, 1, $failure->getCode());
427 // Do not serialize the complete exception or the trace, this can lead to huge strings > 50MB
428 $failureString = serialize(array(
429 'code' => $failure->getCode(),
430 'message' => $failure->getMessage(),
431 'file' => $failure->getFile(),
432 'line' => $failure->getLine(),
433 'traceString' => $failure->getTraceAsString(),
434 ));
435 } else {
436 $failureString = '';
437 }
438 // Save the updated executions list
439 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('tx_scheduler_task', 'uid = ' . $this->taskUid, array(
440 'serialized_executions' => $runningExecutionsSerialized,
441 'lastexecution_failure' => $failureString
442 ));
443 }
444 }
445 $GLOBALS['TYPO3_DB']->sql_free_result($res);
446 }
447
448 /**
449 * Clears all marked executions
450 *
451 * @return bool TRUE if the clearing succeeded, FALSE otherwise
452 */
453 public function unmarkAllExecutions() {
454 // Set the serialized executions field to empty
455 $result = $GLOBALS['TYPO3_DB']->exec_UPDATEquery('tx_scheduler_task', 'uid = ' . $this->taskUid, array(
456 'serialized_executions' => ''
457 ));
458 return $result;
459 }
460
461 /**
462 * Saves the details of the task to the database.
463 *
464 * @return bool
465 */
466 public function save() {
467 return $this->scheduler->saveTask($this);
468 }
469
470 /**
471 * Stops the task, by replacing the execution object by an empty one
472 * NOTE: the task still needs to be saved after that
473 *
474 * @return void
475 */
476 public function stop() {
477 $this->execution = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Execution::class);
478 }
479
480 /**
481 * Removes the task totally from the system.
482 *
483 * @return void
484 */
485 public function remove() {
486 $this->scheduler->removeTask($this);
487 }
488
489 }