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