setStart((int)($details['start'] ?? 0)); $obj->setEnd((int)($details['end'] ?? 0)); $obj->setInterval((int)($details['interval'] ?? 0)); $obj->setMultiple((bool)($details['multiple'] ?? false)); $obj->setCronCmd((string)($details['cronCmd'] ?? '')); $obj->setIsNewSingleExecution((bool)($details['isNewSingleExecution'] ?? false)); return $obj; } /** * Registers a single execution of the task * * @param int $timestamp Timestamp of the next execution */ public static function createSingleExecution(int $timestamp): self { $obj = new self(); $obj->setStart($timestamp); $obj->setInterval(0); $obj->setEnd(0); $obj->setCronCmd(''); $obj->setMultiple(false); $obj->setIsNewSingleExecution(true); return $obj; } /** * Registers a recurring execution of the task * * @param int $start The first date/time when this execution should occur (timestamp) * @param int $interval Execution interval in seconds * @param int $end The last date/time when this execution should occur (timestamp) * @param bool $multiple Set to FALSE if multiple executions of this task are not permitted in parallel * @param string $cronCmd Used like in crontab (minute hour day month weekday) */ public static function createRecurringExecution(int $start, int $interval, int $end = 0, bool $multiple = false, string $cronCmd = ''): self { $obj = new self(); // Set general values $obj->setStart($start); $obj->setEnd($end); $obj->setMultiple($multiple); if (empty($cronCmd)) { // Use interval $obj->setInterval($interval); $obj->setCronCmd(''); } else { // Use cron syntax $obj->setInterval(0); $obj->setCronCmd($cronCmd); } return $obj; } /********************************** * Setters and getters **********************************/ /** * This method is used to set the start date * * @param int $start Start date (timestamp) */ public function setStart($start) { $this->start = (int)$start; } /** * This method is used to get the start date * * @return int Start date (timestamp) */ public function getStart() { return (int)$this->start; } /** * This method is used to set the end date * * @param int $end End date (timestamp) */ public function setEnd($end) { $this->end = (int)$end; } /** * This method is used to get the end date * * @return int End date (timestamp) */ public function getEnd() { return (int)$this->end; } /** * This method is used to set the interval * * @param int $interval Interval (in seconds) */ public function setInterval($interval) { $this->interval = (int)$interval; } /** * This method is used to get the interval * * @return int Interval (in seconds) */ public function getInterval() { return (int)$this->interval; } /** * This method is used to set the multiple execution flag * * @param bool $multiple TRUE if concurrent executions are allowed, FALSE otherwise */ public function setMultiple($multiple) { $this->multiple = (bool)$multiple; } /** * This method is used to get the multiple execution flag * * @return bool TRUE if concurrent executions are allowed, FALSE otherwise */ public function isParallelExecutionAllowed(): bool { return (bool)$this->multiple; } /** * Set the value of the cron command * * @param string $cmd Cron command, using cron-like syntax */ public function setCronCmd($cmd) { $this->cronCmd = $cmd; } /** * Get the value of the cron command * * @return string Cron command, using cron-like syntax */ public function getCronCmd() { return $this->cronCmd; } /** * Set whether this is a newly created single execution. * This is necessary for the following reason: if a new single-running task * is created and its start date is in the past (even for only a few seconds), * the next run time calculation (which happens upon saving) will disable * that task, because it was meant to run only once and is in the past. * Setting this flag to TRUE preserves this task for a single run. * Upon next execution, this flag is set to FALSE. * * @param bool $isNewSingleExecution Is newly created single execution? * @see \TYPO3\CMS\Scheduler\Execution::getNextExecution() */ public function setIsNewSingleExecution($isNewSingleExecution) { $this->isNewSingleExecution = (bool)$isNewSingleExecution; } /** * Get whether this is a newly created single execution * * @return bool Is newly created single execution? */ public function getIsNewSingleExecution() { return (bool)$this->isNewSingleExecution; } /********************************** * Execution calculations and logic **********************************/ /** * This method gets or calculates the next execution date * * @return int Timestamp of the next execution * @throws \OutOfBoundsException */ public function getNextExecution() { if ($this->getIsNewSingleExecution()) { $this->setIsNewSingleExecution(false); return $this->getStart(); } if (!$this->isEnded()) { // If the schedule has not yet run out, find out the next date if (!$this->isStarted()) { // If the schedule hasn't started yet, next date is start date $date = $this->getStart(); } else { // If the schedule has already started, calculate next date if ($this->cronCmd) { // If it uses cron-like syntax, calculate next date $date = $this->getNextCronExecution(); } elseif ($this->getInterval() == 0) { // If not and there's no interval either, it's a singe execution: use start date $date = $this->getStart(); } else { // Otherwise calculate date based on interval $now = time(); $date = $now + $this->getInterval() - ($now - $this->getStart()) % $this->getInterval(); } // If date is in the future, throw an exception if (!empty($this->getEnd()) && $date > $this->getEnd()) { throw new \OutOfBoundsException('Next execution date is past end date.', 1250715528); } } } else { // The event has ended, throw an exception throw new \OutOfBoundsException('Task is past end date.', 1250715544); } return $date; } /** * Calculates the next execution from a cron command * * @return int Next execution (timestamp) */ public function getNextCronExecution() { $cronCmd = GeneralUtility::makeInstance(CronCommand::class, $this->getCronCmd()); $cronCmd->calculateNextValue(); return (int)$cronCmd->getTimestamp(); } /** * Checks if the schedule for a task is started or not * * @return bool TRUE if the schedule is already active, FALSE otherwise */ public function isStarted() { return $this->getStart() < time(); } /** * Checks if the schedule for a task is passed or not * * @return bool TRUE if the schedule is not active anymore, FALSE otherwise */ public function isEnded() { if ($this->getEnd() === 0) { // If no end is defined, the schedule never ends $result = false; } else { // Otherwise check if end is in the past $result = $this->getEnd() < time(); } return $result; } /** * Guess recurring type from the existing information * If an interval or a cron command is defined, it's a recurring task */ public function isRecurring(): bool { return !empty($this->getInterval()) || !empty($this->getCronCmd()); } public function isSingleRun(): bool { return !$this->isRecurring(); } public function toArray(): array { // The type cast is necessary as long as the DB migration (upgrade wizard) exists, // Because this way, the serialization (from unserialize()) kicks in // and cleans the values right away. // @todo We can then strong-type-hint in TYPO3 v16.0. return [ 'start' => (int)$this->start, 'end' => (int)$this->end, 'interval' => (int)$this->interval, 'multiple' => (bool)$this->multiple, 'cronCmd' => (string)$this->cronCmd, 'isNewSingleExecution' => (bool)$this->isNewSingleExecution, ]; } }