scheduler = GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Scheduler::class); $this->execution = GeneralUtility::makeInstance(Execution::class); } /** * This is the main method that is called when a task is executed * It MUST be implemented by all classes inheriting from this one * Note that there is no error handling, errors and failures are expected * to be handled and logged by the client implementations. * Should return TRUE on successful execution, FALSE on error. * * @return bool Returns TRUE on successful execution, FALSE on error */ abstract public function execute(); /** * This method is designed to return some additional information about the task, * that may help to set it apart from other tasks from the same class * This additional information is used - for example - in the Scheduler's BE module * This method should be implemented in most task classes * * @return string Information to display */ public function getAdditionalInformation() { return ''; } /** * This method is used to set the unique id of the task * * @param int $id Primary key (from the database record) of the scheduled task */ public function setTaskUid($id) { $this->taskUid = (int)$id; } /** * This method returns the unique id of the task * * @return int The id of the task */ public function getTaskUid() { return $this->taskUid; } /** * This method returns the title of the scheduler task * * @return string */ public function getTaskTitle() { return $GLOBALS['LANG']->sL($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][static::class]['title']); } /** * This method returns the description of the scheduler task * * @return string */ public function getTaskDescription() { return $GLOBALS['LANG']->sL($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][static::class]['description']); } /** * This method returns the class name of the scheduler task * * @return string */ public function getTaskClassName() { return static::class; } /** * This method returns the disable status of the task * * @return bool TRUE if task is disabled, FALSE otherwise */ public function isDisabled() { return $this->disabled; } /** * This method is used to set the disable status of the task * * @param bool $flag TRUE if task should be disabled, FALSE otherwise */ public function setDisabled($flag) { if ($flag) { $this->disabled = true; } else { $this->disabled = false; } } /** * This method set the flag for next cron job execution * * @param bool $flag TRUE if task should run with the next cron job, FALSE otherwise */ public function setRunOnNextCronJob($flag) { $this->runOnNextCronJob = $flag; } /** * This method returns the run on next cron job status of the task * * @return bool TRUE if task should run on next cron job, FALSE otherwise */ public function getRunOnNextCronJob() { return $this->runOnNextCronJob; } /** * This method is used to set the timestamp corresponding to the next execution time of the task * * @param int $timestamp Timestamp of next execution */ public function setExecutionTime($timestamp) { $this->executionTime = (int)$timestamp; } /** * This method returns the task group (uid) of the task * * @return int Uid of task group */ public function getTaskGroup() { return $this->taskGroup; } /** * This method is used to set the task group (uid) of the task * * @param int $taskGroup Uid of task group */ public function setTaskGroup($taskGroup) { $this->taskGroup = (int)$taskGroup; } /** * This method returns the timestamp corresponding to the next execution time of the task * * @return int Timestamp of next execution */ public function getExecutionTime() { return $this->executionTime; } /** * This method is used to set the description of the task * * @param string $description Description */ public function setDescription($description) { $this->description = $description; } /** * This method returns the description of the task * * @return string Description */ public function getDescription() { return $this->description; } /** * Sets the internal reference to the singleton instance of the Scheduler * and the logger instance in case it was unserialized */ public function setScheduler() { $this->scheduler = GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Scheduler::class); $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__); } /** * Unsets the internal reference to the singleton instance of the Scheduler * and the logger instance. * This is done before a task is serialized, so that the scheduler instance * and the logger instance are not saved to the database */ public function unsetScheduler() { $this->scheduler = null; $this->logger = null; } /** * Registers a single execution of the task * * @param int $timestamp Timestamp of the next execution */ public function registerSingleExecution($timestamp) { /** @var Execution $execution */ $execution = GeneralUtility::makeInstance(Execution::class); $execution->setStart($timestamp); $execution->setInterval(0); $execution->setEnd($timestamp); $execution->setCronCmd(''); $execution->setMultiple(0); $execution->setIsNewSingleExecution(true); // Replace existing execution object $this->execution = $execution; } /** * Registers a recurring execution of the task * * @param int $start The first date/time where this execution should occur (timestamp) * @param string $interval Execution interval in seconds * @param int $end The last date/time where this execution should occur (timestamp) * @param bool $multiple Set to FALSE if multiple executions of this task are not permitted in parallel * @param string $cron_cmd Used like in crontab (minute hour day month weekday) */ public function registerRecurringExecution($start, $interval, $end = 0, $multiple = false, $cron_cmd = '') { /** @var Execution $execution */ $execution = GeneralUtility::makeInstance(Execution::class); // Set general values $execution->setStart($start); $execution->setEnd($end); $execution->setMultiple($multiple); if (empty($cron_cmd)) { // Use interval $execution->setInterval($interval); $execution->setCronCmd(''); } else { // Use cron syntax $execution->setInterval(0); $execution->setCronCmd($cron_cmd); } // Replace existing execution object $this->execution = $execution; } /** * Sets the internal execution object * * @param Execution $execution The execution to add */ public function setExecution(Execution $execution) { $this->execution = $execution; } /** * Returns the execution object * * @return Execution The internal execution object */ public function getExecution() { return $this->execution; } /** * Returns the timestamp for next due execution of the task * * @return int Date and time of the next execution as a timestamp */ public function getNextDueExecution() { // NOTE: this call may throw an exception, but we let it bubble up return $this->execution->getNextExecution(); } /** * Returns TRUE if several runs of the task are allowed concurrently * * @return bool TRUE if concurrent executions are allowed, FALSE otherwise */ public function areMultipleExecutionsAllowed() { return $this->execution->getMultiple(); } /** * Returns TRUE if an instance of the task is already running * * @return bool TRUE if an instance is already running, FALSE otherwise */ public function isExecutionRunning() { $isRunning = false; $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('tx_scheduler_task'); $row = $queryBuilder ->select('serialized_executions') ->from('tx_scheduler_task') ->where( $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($this->taskUid, \PDO::PARAM_INT)) ) ->execute() ->fetch(); if ($row && !empty($row['serialized_executions'])) { $isRunning = true; } return $isRunning; } /** * This method adds current execution to the execution list * It also logs the execution time and mode * * @return int Execution id */ public function markExecution() { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('tx_scheduler_task'); $row = $queryBuilder ->select('serialized_executions') ->from('tx_scheduler_task') ->where( $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($this->taskUid, \PDO::PARAM_INT)) ) ->execute() ->fetch(); $runningExecutions = []; if ($row && !empty($row['serialized_executions'])) { $runningExecutions = unserialize($row['serialized_executions']); } // Count the number of existing executions and use that number as a key // (we need to know that number, because it is returned at the end of the method) $numExecutions = count($runningExecutions); $runningExecutions[$numExecutions] = time(); GeneralUtility::makeInstance(ConnectionPool::class) ->getConnectionForTable('tx_scheduler_task') ->update( 'tx_scheduler_task', [ 'serialized_executions' => serialize($runningExecutions), 'lastexecution_time' => time(), // Define the context in which the script is running 'lastexecution_context' => Environment::isCli() ? 'CLI' : 'BE' ], [ 'uid' => $this->taskUid ], [ 'serialized_executions' => Connection::PARAM_LOB, ] ); return $numExecutions; } /** * Removes given execution from list * * @param int $executionID Id of the execution to remove. * @param \Throwable $failure An exception to signal a failed execution */ public function unmarkExecution($executionID, \Throwable $failure = null) { // Get the executions for the task $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('tx_scheduler_task'); $row = $queryBuilder ->select('serialized_executions') ->from('tx_scheduler_task') ->where( $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($this->taskUid, \PDO::PARAM_INT)) ) ->execute() ->fetch(); if ($row && $row['serialized_executions'] !== '') { $runningExecutions = unserialize($row['serialized_executions']); // Remove the selected execution unset($runningExecutions[$executionID]); if (!empty($runningExecutions)) { // Re-serialize the updated executions list (if necessary) $runningExecutionsSerialized = serialize($runningExecutions); } else { $runningExecutionsSerialized = ''; } if ($failure instanceof \Throwable) { // Log failed execution $logMessage = 'Task failed to execute successfully. Class: ' . static::class . ', UID: ' . $this->taskUid . ', Code: ' . $failure->getCode() . ', ' . $failure->getMessage(); $this->logger->error($logMessage, ['exception' => $failure]); // Do not serialize the complete exception or the trace, this can lead to huge strings > 50MB $failureString = serialize([ 'code' => $failure->getCode(), 'message' => $failure->getMessage(), 'file' => $failure->getFile(), 'line' => $failure->getLine(), 'traceString' => $failure->getTraceAsString(), ]); } else { $failureString = ''; } // Save the updated executions list GeneralUtility::makeInstance(ConnectionPool::class) ->getConnectionForTable('tx_scheduler_task') ->update( 'tx_scheduler_task', [ 'serialized_executions' => $runningExecutionsSerialized, 'lastexecution_failure' => $failureString ], [ 'uid' => $this->taskUid ], [ 'serialized_executions' => Connection::PARAM_LOB, ] ); } } /** * Clears all marked executions * * @return bool TRUE if the clearing succeeded, FALSE otherwise */ public function unmarkAllExecutions() { // Set the serialized executions field to empty $result = GeneralUtility::makeInstance(ConnectionPool::class) ->getConnectionForTable('tx_scheduler_task') ->update( 'tx_scheduler_task', [ 'serialized_executions' => '' ], [ 'uid' => $this->taskUid ], [ 'serialized_executions' => Connection::PARAM_LOB, ] ); return (bool)$result; } /** * Saves the details of the task to the database. * * @return bool */ public function save() { return $this->scheduler->saveTask($this); } /** * Stops the task, by replacing the execution object by an empty one * NOTE: the task still needs to be saved after that */ public function stop() { $this->execution = GeneralUtility::makeInstance(Execution::class); } /** * Removes the task totally from the system. */ public function remove() { $this->scheduler->removeTask($this); } /** * Guess task type from the existing information * If an interval or a cron command is defined, it's a recurring task * * @return int */ public function getType() { if (!empty($this->getExecution()->getInterval()) || !empty($this->getExecution()->getCronCmd())) { return self::TYPE_RECURRING; } return self::TYPE_SINGLE; } /** * @param \Exception $e */ protected function logException(\Exception $e) { $this->logger->error('A Task Exception was captured: ' . $e->getMessage() . ' (' . $e->getCode() . ')', ['exception' => $e]); } }