From 4392dbe2cea51e46f402bf9f991db5bbd8d73c4f Mon Sep 17 00:00:00 2001 From: Sven Wappler Date: Mon, 10 Aug 2026 22:31:38 +0200 Subject: [PATCH] TYPO3 v15 dev-main snapshot () --- .gitignore | 1 + Classes/Command/SchedulerCommand.php | 259 +++++++ Classes/Command/SchedulerExecuteCommand.php | 173 +++++ Classes/Command/SchedulerListCommand.php | 193 +++++ .../Controller/NewSchedulerTaskController.php | 156 ++++ .../Controller/SchedulerModuleController.php | 509 +++++++++++++ Classes/CronCommand/CronCommand.php | 213 ++++++ Classes/CronCommand/NormalizeCommand.php | 336 +++++++++ .../Repository/SchedulerTaskRepository.php | 682 ++++++++++++++++++ Classes/Event/AfterTaskExecutionEvent.php | 48 ++ ...ModifyNewSchedulerTaskWizardItemsEvent.php | 56 ++ ...ddSchedulableCommandsAsNativeTaskTypes.php | 77 ++ .../ReplaceAddNewButtonToFormEngine.php | 94 +++ Classes/Exception.php | 25 + Classes/Exception/InvalidDateException.php | 25 + Classes/Exception/InvalidTaskException.php | 26 + Classes/Execution.php | 371 ++++++++++ Classes/FailedExecutionException.php | 21 + Classes/Form/Element/RegisteredExtractors.php | 85 +++ ...SchedulableCommandConfigurationElement.php | 214 ++++++ Classes/Form/Element/TaskTypeInfoElement.php | 70 ++ Classes/Form/Element/TimingOptionsElement.php | 170 +++++ .../ExpirePeriodInformation.php | 66 ++ .../SchedulerTaskPersistenceValidator.php | 280 +++++++ .../SchedulerDatabaseStorageMigration.php | 235 ++++++ Classes/ProgressProviderInterface.php | 29 + Classes/Scheduler.php | 225 ++++++ Classes/Service/TaskService.php | 274 +++++++ .../SystemInformation/ToolbarItemProvider.php | 135 ++++ Classes/Task/AbstractTask.php | 326 +++++++++ .../CachingFrameworkGarbageCollectionTask.php | 96 +++ .../Task/ExecuteSchedulableCommandTask.php | 310 ++++++++ Classes/Task/FileStorageExtractionTask.php | 88 +++ Classes/Task/FileStorageIndexingTask.php | 73 ++ Classes/Task/IpAnonymizationTask.php | 193 +++++ Classes/Task/OptimizeDatabaseTableTask.php | 193 +++++ .../Task/RecyclerGarbageCollectionTask.php | 109 +++ Classes/Task/TableGarbageCollectionTask.php | 188 +++++ Classes/Task/TaskSerializer.php | 110 +++ Classes/Task/TaskStatus.php | 43 ++ .../Validation/Validator/TaskValidator.php | 42 ++ Configuration/Backend/AjaxRoutes.php | 24 + Configuration/Backend/Modules.php | 23 + Configuration/JavaScriptModules.php | 11 + Configuration/Services.yaml | 8 + ...hing_framework_garbage_collection_task.php | 48 ++ ...scheduler_file_storage_extraction_task.php | 56 ++ .../scheduler_file_storage_indexing_task.php | 30 + .../scheduler_ip_anonymization_task.php | 86 +++ ...scheduler_optimize_database_table_task.php | 44 ++ ...duler_recycler_garbage_collection_task.php | 43 ++ ...cheduler_table_garbage_collection_task.php | 97 +++ Configuration/TCA/tx_scheduler_task.php | 204 ++++++ Configuration/TCA/tx_scheduler_task_group.php | 72 ++ Configuration/user.tsconfig | 1 + .../Administration/BackendModule/Index.rst | 120 +++ .../Administration/ConsoleTools/Index.rst | 119 +++ .../Administration/ConsoleTools/Running.rst | 141 ++++ .../Administration/DeleteTask/Index.rst | 34 + .../Administration/EditTask/Index.rst | 104 +++ .../Administration/GroupTask/Index.rst | 69 ++ Documentation/Administration/Index.rst | 25 + .../Administration/ManualExecution/Index.rst | 56 ++ .../Administration/StopTask/Index.rst | 92 +++ .../BasicTasks/GarbageCollection.rst | 94 +++ Documentation/BasicTasks/Index.rst | 42 ++ Documentation/BasicTasks/IpAnonymization.rst | 53 ++ .../_codesnippets/_additional.php.inc | 16 + .../_ext_localconf_ip_anonymization.php.inc | 17 + .../_tx_scheduler_garbage_collection.php.inc | 17 + .../_tx_scheduler_ip_anonymization.php.inc | 14 + .../DevelopersGuide/CreatingTasks/Index.rst | 129 ++++ .../CreatingTasks/Migration.rst | 110 +++ .../_codesnippets/_MyTask.php.inc | 27 + .../_MyTaskWithAdditionalFields.php.inc | 77 ++ ...yTaskWithAdditionalFieldsMigration.php.inc | 96 +++ .../_ext_localconf_deprecated.php.inc | 15 + ..._scheduler_my_task_type-additional.php.inc | 65 ++ .../_tx_scheduler_my_task.php.inc | 25 + .../DevelopersGuide/Events/Index.rst | 54 ++ Documentation/DevelopersGuide/Index.rst | 19 + .../DevelopersGuide/SchedulerApi/Index.rst | 30 + .../DevelopersGuide/TaskStorage/Index.rst | 58 ++ Documentation/Images/AddingATask.png | Bin 0 -> 29269 bytes .../Images/BackendModuleMainView.png | Bin 0 -> 21565 bytes Documentation/Images/EmptySchedulerModule.png | Bin 0 -> 32809 bytes .../Images/ExtensionConfiguration.png | Bin 0 -> 11019 bytes Documentation/Images/GroupDisabled.png | Bin 0 -> 11292 bytes Documentation/Images/GroupEdit.png | Bin 0 -> 24690 bytes Documentation/Images/GroupedTasks.png | Bin 0 -> 39634 bytes Documentation/Images/InstallActivate.png | Bin 0 -> 14523 bytes Documentation/Images/LateTask.png | Bin 0 -> 4019 bytes Documentation/Images/ManualExecution.png | Bin 0 -> 18840 bytes Documentation/Images/MissingTaskClass.png | Bin 0 -> 26199 bytes Documentation/Images/RestoreTask.png | Bin 0 -> 44549 bytes Documentation/Images/SetupCheck.png | Bin 0 -> 29550 bytes Documentation/Images/SetupCheckButton.png | Bin 0 -> 18460 bytes Documentation/Images/StoppingATask.png | Bin 0 -> 12738 bytes ...leGarbageCollectionTaskConfiguration-2.png | Bin 0 -> 77041 bytes ...ableGarbageCollectionTaskConfiguration.png | Bin 0 -> 12316 bytes Documentation/Images/TaskCreationWizard.png | Bin 0 -> 24893 bytes Documentation/Images/TaskExecutionDetails.png | Bin 0 -> 36431 bytes Documentation/Includes.rst.txt | 1 + Documentation/Index.rst | 55 ++ Documentation/Installation/CronJob/Index.rst | 133 ++++ Documentation/Installation/Index.rst | 15 + .../Installation/Installing/Index.rst | 75 ++ .../Installation/SetupCheck/Index.rst | 26 + Documentation/Introduction/Index.rst | 103 +++ Documentation/KnownProblems/Index.rst | 15 + Documentation/Sitemap.rst | 9 + .../_Includes/_ExtendingSchedulerTca.rst.txt | 6 + Documentation/guides.xml | 21 + LICENSE.txt | 339 +++++++++ README.rst | 11 + Resources/Private/Language/label.xlf | 15 + Resources/Private/Language/locallang.xlf | 579 +++++++++++++++ Resources/Private/Language/locallang_em.xlf | 11 + Resources/Private/Language/locallang_tca.xlf | 104 +++ Resources/Private/Language/module.xlf | 17 + .../Partials/GroupUnusedList.fluid.html | 90 +++ .../MultiRecordSelectionActions.fluid.html | 25 + .../Private/Partials/ServerTime.fluid.html | 20 + .../Private/Partials/TaskList.fluid.html | 491 +++++++++++++ .../Private/Templates/CheckScreen.fluid.html | 30 + .../Templates/CommandConfiguration.fluid.html | 67 ++ .../Templates/Email/TestTask.fluid.html | 14 + .../Templates/Email/TestTask.fluid.txt | 15 + .../Private/Templates/ListTasks.fluid.html | 48 ++ .../NewSchedulerTask/Wizard.fluid.html | 11 + .../Private/Templates/ServerTime.fluid.html | 10 + Resources/Public/Icons/Extension.png | Bin 0 -> 861 bytes .../element/timing-options-element.js | 13 + .../new-scheduler-task-wizard-button.js | 13 + .../Public/JavaScript/scheduler-add-group.js | 13 + .../JavaScript/scheduler-sortable-groups.js | 13 + Resources/Public/JavaScript/scheduler.js | 13 + .../Public/JavaScript/setup-check-button.js | 13 + composer.json | 53 ++ ext_conf_template.txt | 2 + ext_localconf.php | 45 ++ ext_tables.sql | 6 + 142 files changed, 11824 insertions(+) create mode 100644 .gitignore create mode 100644 Classes/Command/SchedulerCommand.php create mode 100644 Classes/Command/SchedulerExecuteCommand.php create mode 100644 Classes/Command/SchedulerListCommand.php create mode 100644 Classes/Controller/NewSchedulerTaskController.php create mode 100644 Classes/Controller/SchedulerModuleController.php create mode 100644 Classes/CronCommand/CronCommand.php create mode 100644 Classes/CronCommand/NormalizeCommand.php create mode 100644 Classes/Domain/Repository/SchedulerTaskRepository.php create mode 100644 Classes/Event/AfterTaskExecutionEvent.php create mode 100644 Classes/Event/ModifyNewSchedulerTaskWizardItemsEvent.php create mode 100644 Classes/EventListener/AddSchedulableCommandsAsNativeTaskTypes.php create mode 100644 Classes/EventListener/ReplaceAddNewButtonToFormEngine.php create mode 100644 Classes/Exception.php create mode 100644 Classes/Exception/InvalidDateException.php create mode 100644 Classes/Exception/InvalidTaskException.php create mode 100644 Classes/Execution.php create mode 100644 Classes/FailedExecutionException.php create mode 100644 Classes/Form/Element/RegisteredExtractors.php create mode 100644 Classes/Form/Element/SchedulableCommandConfigurationElement.php create mode 100644 Classes/Form/Element/TaskTypeInfoElement.php create mode 100644 Classes/Form/Element/TimingOptionsElement.php create mode 100644 Classes/Form/FieldInformation/ExpirePeriodInformation.php create mode 100644 Classes/Hooks/SchedulerTaskPersistenceValidator.php create mode 100644 Classes/Migration/SchedulerDatabaseStorageMigration.php create mode 100644 Classes/ProgressProviderInterface.php create mode 100644 Classes/Scheduler.php create mode 100644 Classes/Service/TaskService.php create mode 100644 Classes/SystemInformation/ToolbarItemProvider.php create mode 100644 Classes/Task/AbstractTask.php create mode 100644 Classes/Task/CachingFrameworkGarbageCollectionTask.php create mode 100644 Classes/Task/ExecuteSchedulableCommandTask.php create mode 100644 Classes/Task/FileStorageExtractionTask.php create mode 100644 Classes/Task/FileStorageIndexingTask.php create mode 100644 Classes/Task/IpAnonymizationTask.php create mode 100644 Classes/Task/OptimizeDatabaseTableTask.php create mode 100644 Classes/Task/RecyclerGarbageCollectionTask.php create mode 100644 Classes/Task/TableGarbageCollectionTask.php create mode 100644 Classes/Task/TaskSerializer.php create mode 100644 Classes/Task/TaskStatus.php create mode 100644 Classes/Validation/Validator/TaskValidator.php create mode 100644 Configuration/Backend/AjaxRoutes.php create mode 100644 Configuration/Backend/Modules.php create mode 100644 Configuration/JavaScriptModules.php create mode 100644 Configuration/Services.yaml create mode 100644 Configuration/TCA/Overrides/scheduler_caching_framework_garbage_collection_task.php create mode 100644 Configuration/TCA/Overrides/scheduler_file_storage_extraction_task.php create mode 100644 Configuration/TCA/Overrides/scheduler_file_storage_indexing_task.php create mode 100644 Configuration/TCA/Overrides/scheduler_ip_anonymization_task.php create mode 100644 Configuration/TCA/Overrides/scheduler_optimize_database_table_task.php create mode 100644 Configuration/TCA/Overrides/scheduler_recycler_garbage_collection_task.php create mode 100644 Configuration/TCA/Overrides/scheduler_table_garbage_collection_task.php create mode 100644 Configuration/TCA/tx_scheduler_task.php create mode 100644 Configuration/TCA/tx_scheduler_task_group.php create mode 100644 Configuration/user.tsconfig create mode 100644 Documentation/Administration/BackendModule/Index.rst create mode 100644 Documentation/Administration/ConsoleTools/Index.rst create mode 100644 Documentation/Administration/ConsoleTools/Running.rst create mode 100644 Documentation/Administration/DeleteTask/Index.rst create mode 100644 Documentation/Administration/EditTask/Index.rst create mode 100644 Documentation/Administration/GroupTask/Index.rst create mode 100644 Documentation/Administration/Index.rst create mode 100644 Documentation/Administration/ManualExecution/Index.rst create mode 100644 Documentation/Administration/StopTask/Index.rst create mode 100644 Documentation/BasicTasks/GarbageCollection.rst create mode 100644 Documentation/BasicTasks/Index.rst create mode 100644 Documentation/BasicTasks/IpAnonymization.rst create mode 100644 Documentation/BasicTasks/_codesnippets/_additional.php.inc create mode 100644 Documentation/BasicTasks/_codesnippets/_ext_localconf_ip_anonymization.php.inc create mode 100644 Documentation/BasicTasks/_codesnippets/_tx_scheduler_garbage_collection.php.inc create mode 100644 Documentation/BasicTasks/_codesnippets/_tx_scheduler_ip_anonymization.php.inc create mode 100644 Documentation/DevelopersGuide/CreatingTasks/Index.rst create mode 100644 Documentation/DevelopersGuide/CreatingTasks/Migration.rst create mode 100644 Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTask.php.inc create mode 100644 Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTaskWithAdditionalFields.php.inc create mode 100644 Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTaskWithAdditionalFieldsMigration.php.inc create mode 100644 Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_ext_localconf_deprecated.php.inc create mode 100644 Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_scheduler_my_task_type-additional.php.inc create mode 100644 Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_tx_scheduler_my_task.php.inc create mode 100644 Documentation/DevelopersGuide/Events/Index.rst create mode 100644 Documentation/DevelopersGuide/Index.rst create mode 100644 Documentation/DevelopersGuide/SchedulerApi/Index.rst create mode 100644 Documentation/DevelopersGuide/TaskStorage/Index.rst create mode 100644 Documentation/Images/AddingATask.png create mode 100644 Documentation/Images/BackendModuleMainView.png create mode 100644 Documentation/Images/EmptySchedulerModule.png create mode 100644 Documentation/Images/ExtensionConfiguration.png create mode 100644 Documentation/Images/GroupDisabled.png create mode 100644 Documentation/Images/GroupEdit.png create mode 100644 Documentation/Images/GroupedTasks.png create mode 100644 Documentation/Images/InstallActivate.png create mode 100644 Documentation/Images/LateTask.png create mode 100644 Documentation/Images/ManualExecution.png create mode 100644 Documentation/Images/MissingTaskClass.png create mode 100644 Documentation/Images/RestoreTask.png create mode 100644 Documentation/Images/SetupCheck.png create mode 100644 Documentation/Images/SetupCheckButton.png create mode 100644 Documentation/Images/StoppingATask.png create mode 100644 Documentation/Images/TableGarbageCollectionTaskConfiguration-2.png create mode 100644 Documentation/Images/TableGarbageCollectionTaskConfiguration.png create mode 100644 Documentation/Images/TaskCreationWizard.png create mode 100644 Documentation/Images/TaskExecutionDetails.png create mode 100644 Documentation/Includes.rst.txt create mode 100644 Documentation/Index.rst create mode 100644 Documentation/Installation/CronJob/Index.rst create mode 100644 Documentation/Installation/Index.rst create mode 100644 Documentation/Installation/Installing/Index.rst create mode 100644 Documentation/Installation/SetupCheck/Index.rst create mode 100644 Documentation/Introduction/Index.rst create mode 100644 Documentation/KnownProblems/Index.rst create mode 100644 Documentation/Sitemap.rst create mode 100644 Documentation/_Includes/_ExtendingSchedulerTca.rst.txt create mode 100644 Documentation/guides.xml create mode 100644 LICENSE.txt create mode 100644 README.rst create mode 100644 Resources/Private/Language/label.xlf create mode 100644 Resources/Private/Language/locallang.xlf create mode 100644 Resources/Private/Language/locallang_em.xlf create mode 100644 Resources/Private/Language/locallang_tca.xlf create mode 100644 Resources/Private/Language/module.xlf create mode 100644 Resources/Private/Partials/GroupUnusedList.fluid.html create mode 100644 Resources/Private/Partials/MultiRecordSelectionActions.fluid.html create mode 100644 Resources/Private/Partials/ServerTime.fluid.html create mode 100644 Resources/Private/Partials/TaskList.fluid.html create mode 100644 Resources/Private/Templates/CheckScreen.fluid.html create mode 100644 Resources/Private/Templates/CommandConfiguration.fluid.html create mode 100644 Resources/Private/Templates/Email/TestTask.fluid.html create mode 100644 Resources/Private/Templates/Email/TestTask.fluid.txt create mode 100644 Resources/Private/Templates/ListTasks.fluid.html create mode 100644 Resources/Private/Templates/NewSchedulerTask/Wizard.fluid.html create mode 100644 Resources/Private/Templates/ServerTime.fluid.html create mode 100644 Resources/Public/Icons/Extension.png create mode 100644 Resources/Public/JavaScript/form-engine/element/timing-options-element.js create mode 100644 Resources/Public/JavaScript/new-scheduler-task-wizard-button.js create mode 100644 Resources/Public/JavaScript/scheduler-add-group.js create mode 100644 Resources/Public/JavaScript/scheduler-sortable-groups.js create mode 100644 Resources/Public/JavaScript/scheduler.js create mode 100644 Resources/Public/JavaScript/setup-check-button.js create mode 100644 composer.json create mode 100644 ext_conf_template.txt create mode 100644 ext_localconf.php create mode 100644 ext_tables.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/Classes/Command/SchedulerCommand.php b/Classes/Command/SchedulerCommand.php new file mode 100644 index 0000000..dc34345 --- /dev/null +++ b/Classes/Command/SchedulerCommand.php @@ -0,0 +1,259 @@ +setHelp('If no parameter is given, the scheduler executes any tasks that are overdue to run. +Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f') + ->addOption( + 'task', + 'i', + InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'UID of a specific task. Can be provided multiple times to execute multiple tasks sequentially.' + ) + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force execution of the task which is passed with --task option' + ) + ->addOption( + 'stop', + 's', + InputOption::VALUE_NONE, + 'Stop the task which is passed with --task option' + ); + } + + /** + * Execute scheduler tasks + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->io = new SymfonyStyle($input, $output); + + // Make sure the _cli_ user is loaded + Bootstrap::initializeBackendAuthentication(); + + $overwrittenTaskList = $input->getOption('task'); + $overwrittenTaskList = is_array($overwrittenTaskList) ? $overwrittenTaskList : []; + $overwrittenTaskList = array_filter($overwrittenTaskList, static fn($value) => MathUtility::canBeInterpretedAsInteger($value)); + $overwrittenTaskList = array_map('intval', $overwrittenTaskList); + if ($overwrittenTaskList !== []) { + $this->overwrittenTaskList = $overwrittenTaskList; + } + + $this->forceExecution = (bool)$input->getOption('force'); + $this->stopTasks = $this->shouldStopTasks((bool)$input->getOption('stop')); + return $this->loopTasks() ? Command::SUCCESS : Command::FAILURE; + } + + /** + * Checks if the tasks should be stopped instead of being executed. + * + * Stopping is only performed when the --stop option is provided together with the --task option. + * + * @param bool $stopOption + */ + protected function shouldStopTasks(bool $stopOption): bool + { + if (!$stopOption) { + return false; + } + + if ($this->overwrittenTaskList !== []) { + return true; + } + + if ($this->io->isVerbose()) { + $this->io->warning('Stopping tasks is only possible when the --task option is provided.'); + } + return false; + } + + /** + * Stop task + */ + protected function stopTask(AbstractTask $task) + { + $this->taskRepository->removeAllRegisteredExecutionsForTask($task); + if ($this->io->isVeryVerbose()) { + $this->io->writeln(sprintf('Task #%d was stopped', $task->getTaskUid())); + } + } + + /** + * Return task a task for a given UID + */ + protected function getTask(int $taskUid): ?AbstractTask + { + $force = $this->stopTasks || $this->forceExecution; + if ($force) { + return $this->taskRepository->findByUid($taskUid); + } + + return $this->taskRepository->findNextExecutableTaskForUid($taskUid); + } + + /** + * Execute tasks in loop that are ready to execute + */ + protected function loopTasks(): bool + { + $hasError = false; + do { + $task = null; + // Try getting the next task and execute it + // If there are no more tasks to execute, an exception is thrown by \TYPO3\CMS\Scheduler\Scheduler::fetchTask() + try { + $task = $this->fetchNextTask(); + if ($task === null) { + break; + } + try { + $this->executeOrStopTask($task); + } catch (\Exception $e) { + $taskDetails = $this->taskService->getTaskDetailsFromTask($task); + $messages = [ + $e->getMessage() . PHP_EOL, + 'Exception in scheduler task #' . $task->getTaskUid() . ' (' . $task->getTaskType() . ' - ' . $taskDetails['title'] . ')', + ]; + $messages[] = 'File: ' . $e->getFile() . ':' . $e->getLine(); + $this->io->getErrorStyle()->error($messages); + $hasError = true; + // We ignore any exception that may have been thrown during execution, + // as this is a background process. + // The exception message has been recorded to the database anyway + continue; + } + } catch (\UnexpectedValueException $e) { + $this->io->getErrorStyle()->error($e->getMessage()); + $hasError = true; + continue; + } + } while ($task !== null); + // Record the run in the system registry + $this->scheduler->recordLastRun(); + return !$hasError; + } + + /** + * When the --task option is provided, the next task is fetched from the provided task UIDs. Depending + * on the --force option the task is fetched even if it is not marked for execution. + * + * Without the --task option we ask the scheduler for the next task with pending execution. + * + * @throws \UnexpectedValueException When no task is found by the provided UID or the task is not marked for execution. + */ + protected function fetchNextTask(): ?AbstractTask + { + if ($this->overwrittenTaskList === null) { + return $this->taskRepository->findNextExecutableTask(); + } + + if (count($this->overwrittenTaskList) === 0) { + return null; + } + + $taskUid = (int)array_shift($this->overwrittenTaskList); + $task = $this->getTask($taskUid); + if (!(new TaskValidator())->isValid($task)) { + throw new \UnexpectedValueException( + sprintf('The task #%d is not scheduled for execution or does not exist.', $taskUid), + 1547675557 + ); + } + return $task; + } + + /** + * When in stop mode the given task is stopped. Otherwise the task is executed. + */ + protected function executeOrStopTask(AbstractTask $task): void + { + if ($this->stopTasks) { + $this->stopTask($task); + return; + } + + $this->scheduler->executeTask($task); + if ($this->io->isVeryVerbose()) { + $this->io->writeln(sprintf('Task #%d was executed', $task->getTaskUid())); + } + } +} diff --git a/Classes/Command/SchedulerExecuteCommand.php b/Classes/Command/SchedulerExecuteCommand.php new file mode 100644 index 0000000..a2425da --- /dev/null +++ b/Classes/Command/SchedulerExecuteCommand.php @@ -0,0 +1,173 @@ +addOption( + 'task', + 't', + InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'Execute tasks by given id. To run all tasks of a group prefix the group id with "g:", e.g. "g:1"', + ); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + // Make sure the _cli_ user is loaded + Bootstrap::initializeBackendAuthentication(); + $this->io = new SymfonyStyle($input, $output); + + if (count($input->getOption('task')) > 0) { + $taskGroups = $this->taskRepository->getGroupedTasks()['taskGroupsWithTasks']; + $tasksToRun = $this->getTasksToRun($taskGroups, $input->getOption('task')); + $this->runTasks($tasksToRun, $taskGroups); + + return Command::SUCCESS; + } + + $this->askForTasksAndRun($input, $output); + return Command::SUCCESS; + } + + private function askForTasksAndRun(InputInterface $input, OutputInterface $output): void + { + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + $taskGroups = $this->taskRepository->getGroupedTasks()['taskGroupsWithTasks']; + $selectableTasks = $this->getSelectableTasks($taskGroups); + if ($selectableTasks === []) { + $this->io->note('No tasks available.'); + return; + } + + $tasksToRunQuestion = new ChoiceQuestion('Run tasks (comma-separated list): ', $selectableTasks); + $tasksToRunQuestion->setAutocompleterValues(array_keys($selectableTasks)); + $tasksToRunQuestion->setMultiselect(true); + $tasksToRun = $questionHelper->ask($input, $output, $tasksToRunQuestion); + + $this->runTasks($tasksToRun, $taskGroups); + } + + private function runTasks($selectedTasks, $taskGroups): void + { + $taskUids = $this->getTaskUidsFromSelection($selectedTasks, $taskGroups); + ksort($taskUids); + $numLength = strlen((string)array_reverse($taskUids)[0]); + + foreach ($taskUids as $taskUid) { + try { + $uid = (int)$taskUid; + $task = $this->taskRepository->findByUid($uid); + $additionalInformation = $task->getAdditionalInformation() === '' ? '' : ' (' . $task->getAdditionalInformation() . ')'; + $taskDetails = $this->taskService->getTaskDetailsFromTask($task); + $space = str_repeat(' ', $numLength - strlen((string)$task->getTaskUid())); + $this->io->writeln('[ TASK:' . $task->getTaskUid() . $space . ' ] Running "' . $taskDetails['title'] . $additionalInformation . '"'); + $this->scheduler->executeTask($task); + } catch (\Throwable $exception) { + $this->io->writeln($exception->getMessage()); + } + } + } + + private function getTaskUidsFromSelection(array $list, array $groups): array + { + $taskUids = []; + foreach ($list as $uid) { + [$keyword, $group] = [...explode(':', (string)$uid), null]; + if ($keyword === 'g') { + if (!array_key_exists($group, $groups)) { + throw new \InvalidArgumentException('Group with id "' . $group . '" does not exist.', 1679683415); + } + $taskUidsInGroup = array_column($groups[$group]['tasks'], 'uid'); + $taskUids += $taskUidsInGroup; + } else { + $taskUids[] = $uid; + } + } + return $taskUids; + } + + private function getLanguageService(): LanguageService + { + return GeneralUtility::makeInstance(LanguageServiceFactory::class)->create('en'); + } + + private function getTasksToRun(array $taskGroups, array $taskList): array + { + $taskUids = array_unique($this->getTaskUidsFromSelection($taskList, $taskGroups)); + foreach ($taskUids as $taskUid) { + // This will throw an exception if the task uid was not found and print it to the console. + $this->taskRepository->findByUid((int)$taskUid); + } + return $taskUids; + } + + protected function getSelectableTasks(mixed $taskGroups): array + { + $selectableTasks = []; + foreach ($taskGroups as $uid => $group) { + $groupLabel = ($group['groupName'] ?? $this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.noGroup')); + $selectableTasks['g:' . $uid] = '' . $groupLabel . ''; + + foreach ($group['tasks'] as $task) { + $additionalInformation = $task['additionalInformation'] === '' ? '' : ' (' . $task['additionalInformation'] . ')'; + $selectableTasks[$task['uid']] = $task['fullTitle'] . $additionalInformation; + } + } + return $selectableTasks; + } +} diff --git a/Classes/Command/SchedulerListCommand.php b/Classes/Command/SchedulerListCommand.php new file mode 100644 index 0000000..d39b439 --- /dev/null +++ b/Classes/Command/SchedulerListCommand.php @@ -0,0 +1,193 @@ +addOption( + 'group', + 'g', + InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'Show only groups with given uid', + ) + ->addOption( + 'watch', + 'w', + InputOption::VALUE_OPTIONAL, + 'Start watcher mode (polling)', + ); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + if (!$output instanceof ConsoleOutputInterface) { + throw new \InvalidArgumentException('This command accepts only an instance of "ConsoleOutputInterface".', 1678645754); + } + + // Make sure the _cli_ user is loaded + Bootstrap::initializeBackendAuthentication(); + $this->io = new SymfonyStyle($input, $output); + $languageService = $this->getLanguageService(); + + $tableHeader = [ + $languageService->sL('scheduler.messages:label.id'), + $languageService->sL('scheduler.messages:task'), + $languageService->sL('scheduler.messages:label.description'), + $languageService->sL('scheduler.messages:label.frequency'), + $languageService->sL('scheduler.messages:status'), + ]; + + $tableSection = $output->section(); + $tableBuffer = new BufferedOutput(OutputInterface::VERBOSITY_NORMAL, true); + $table = new Table($tableBuffer); + $table->setHeaders($tableHeader); + $showGroups = $input->getOption('group'); + $rows = $this->getTableRows($showGroups); + $table->setRows($rows); + $table->setColumnMaxWidth(1, 50); + $table->setColumnMaxWidth(2, 50); + $table->setColumnMaxWidth(4, 50); + $table->render(); + + $bufferedData = $tableBuffer->fetch(); + $tableSection->overwrite($bufferedData); + + $doWatch = $input->hasParameterOption('--watch') || $input->hasParameterOption('-w'); + if ($doWatch) { + $infoSection = $output->section(); + $interval = (int)($input->getOption('watch') ?: 1); + $infoSection->write('Watching tasks every ' . $interval . ' seconds, press CTRL+C to stop watching'); + + while (true) { // @phpstan-ignore while.alwaysTrue (intentional infinite loop for CLI watch mode, terminated by CTRL+C signal) + sleep($interval); + $this->updateTable($input, $table, $tableBuffer, $tableSection); + } + } + + return Command::SUCCESS; + } + + private function getTableRows(array $groups = []): array + { + $tasks = $this->taskRepository->getGroupedTasks(); + $languageService = $this->getLanguageService(); + + $rows = []; + foreach ($tasks['taskGroupsWithTasks'] as $uid => $group) { + if (!in_array($uid, $groups) && count($groups) > 0) { + continue; + } + + // Flag as disabled group + $groupDisabledLabel = $group['hidden'] ? '' . $this->getLanguageService()->sL('scheduler.messages:status.disabled') . '' : ''; + $groupLabel = ($group['groupName'] ?? $languageService->sL('scheduler.messages:label.noGroup')) . ' (id:' . $uid . ') ' . $groupDisabledLabel; + + $rows[] = [new TableSeparator(['colspan' => 5])]; + $rows[] = [new TableCell('' . $groupLabel . '', ['colspan' => 5])]; + $rows[] = [new TableSeparator(['colspan' => 5])]; + + foreach ($group['tasks'] as $task) { + $progress = $task['progress'] ?? false; + $taskStatus = []; + + /** @var TaskStatus $status */ + foreach ($task['statuses'] as $status) { + $color = match ($status->severity) { + ContextualFeedbackSeverity::OK => 'green', + ContextualFeedbackSeverity::INFO => 'blue', + ContextualFeedbackSeverity::WARNING => 'yellow', + ContextualFeedbackSeverity::ERROR => 'red', + ContextualFeedbackSeverity::NOTICE => 'gray', + }; + $label = $languageService->sL($status->label); + if ($status->type === 'running' && $progress) { + $label .= ' (' . $progress . ')'; + } + // The console has no tooltip, so the more detailed message + // (e.g. the failure reason) is shown inline instead. + if ($status->message !== '') { + $label .= ' (' . vsprintf($languageService->sL($status->message), $status->messageArguments) . ')'; + } + $taskStatus[] = '' . $label . ''; + } + + $taskTitle = $task['fullTitle'] . (empty($task['additionalInformation']) ? '' : ' (' . $task['additionalInformation'] . ')'); + $rows[] = [ + $task['uid'], + $taskTitle, + $task['description'], + $task['frequency'], + implode(', ', $taskStatus), + ]; + } + } + + return $rows; + } + + private function getLanguageService(): LanguageService + { + return GeneralUtility::makeInstance(LanguageServiceFactory::class)->create('en'); + } + + protected function updateTable(InputInterface $input, Table $table, BufferedOutput $buffer, ConsoleSectionOutput $tableSection): void + { + $tableSection->overwrite(''); + $rows = $this->getTableRows($input->getOption('group')); + $table->setRows($rows)->render(); + $bufferedData = $buffer->fetch(); + $tableSection->overwrite($bufferedData); + } +} diff --git a/Classes/Controller/NewSchedulerTaskController.php b/Classes/Controller/NewSchedulerTaskController.php new file mode 100644 index 0000000..ef7fbd3 --- /dev/null +++ b/Classes/Controller/NewSchedulerTaskController.php @@ -0,0 +1,156 @@ +returnUrl = GeneralUtility::sanitizeLocalUrl((string)($request->getParsedBody()['returnUrl'] ?? $request->getQueryParams()['returnUrl'] ?? ''), $request); + $this->defaultValues = $request->getParsedBody()['defaultValues'] ?? $request->getQueryParams()['defaultValues'] ?? []; + + $wizardItems = $this->eventDispatcher->dispatch( + new ModifyNewSchedulerTaskWizardItemsEvent( + $this->getWizardItemsFromTaskRegistry(), + $request, + ) + )->getWizardItems(); + + $view = $this->backendViewFactory->create($request); + $view->assign('categoriesJson', GeneralUtility::jsonEncodeForHtmlAttribute($this->organizeWizardItems($wizardItems), false)); + return new HtmlResponse($view->render('NewSchedulerTask/Wizard')); + } + + /** + * Convert TaskService categorized tasks to wizard items + */ + protected function getWizardItemsFromTaskRegistry(): array + { + $wizardItems = []; + + foreach ($this->taskService->getCategorizedTaskTypes() as $category => $tasks) { + // Add category header + $wizardItems[$category] = [ + 'header' => ucfirst($category), + ]; + // Add tasks for this category + foreach ($tasks as $taskType => $taskInfo) { + $wizardItems[$category . '_' . str_replace('\\', '_', $taskType)] = [ + 'title' => $taskInfo['title'], + 'description' => $taskInfo['description'], + // @todo change once tx_scheduler_task get's icons + 'icon' => $taskInfo['icon'] ?? 'mimetypes-x-tx_scheduler_task_group', + 'iconOverlay' => $taskInfo['iconOverlay'] ?? '', + 'taskType' => $taskType, + 'taskClass' => $taskInfo['className'], + ]; + } + } + + return $wizardItems; + } + + /** + * Organize wizard items into the categories + */ + protected function organizeWizardItems(array $wizardItems): array + { + $categories = []; + $currentKey = ''; + + foreach ($wizardItems as $wizardKey => $wizardItem) { + if (isset($wizardItem['header'])) { + // This is a category + $currentKey = $wizardKey; + $categories[$currentKey] = [ + 'identifier' => $currentKey, + 'label' => $wizardItem['header'], + 'items' => [], + ]; + } else { + if (!($wizardItem['taskType'] ?? false)) { + continue; + } + // This is a task item + $item = [ + 'identifier' => $wizardKey, + 'icon' => $wizardItem['icon'] ?? 'mimetypes-x-tx_scheduler_task_group', + 'iconOverlay' => $wizardItem['iconOverlay'] ?? '', + 'label' => $wizardItem['title'] ?? '', + 'description' => $wizardItem['description'] ?? '', + 'taskType' => $wizardItem['taskType'], + ]; + + $item['url'] = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [ + 'edit' => [ + 'tx_scheduler_task' => [ + 0 => 'new', + ], + ], + 'defVals' => [ + 'tx_scheduler_task' => array_replace_recursive($this->defaultValues, [ + 'pid' => 0, + 'tasktype' => $wizardItem['taskType'], + ]), + ], + 'returnUrl' => $this->returnUrl, + ]); + + if (!empty($currentKey)) { + $categories[$currentKey]['items'][] = $item; + } + } + } + + // Remove empty categories + return array_filter($categories, static fn(array $category): bool => !empty($category['items'])); + } + + protected function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/Classes/Controller/SchedulerModuleController.php b/Classes/Controller/SchedulerModuleController.php new file mode 100644 index 0000000..5343d6d --- /dev/null +++ b/Classes/Controller/SchedulerModuleController.php @@ -0,0 +1,509 @@ +getParsedBody(); + + $view = $this->moduleTemplateFactory->create($request); + $view->assign('dateFormat', [ + 'day' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?? 'd-m-y', + 'time' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] ?? 'H:i', + ]); + + $moduleData = $request->getAttribute('moduleData'); + + // Simple actions from list view. + if (!empty($parsedBody['action']['toggleHidden'])) { + $this->toggleDisabledFlag($view, (int)$parsedBody['action']['toggleHidden']); + } elseif (!empty($parsedBody['action']['stop'])) { + $this->stopTask($view, (int)$parsedBody['action']['stop']); + } elseif (!empty($parsedBody['action']['execute'])) { + $this->executeTasks($view, (string)$parsedBody['action']['execute']); + } elseif (!empty($parsedBody['action']['scheduleCron'])) { + $this->scheduleCrons($view, (string)$parsedBody['action']['scheduleCron']); + } elseif (!empty($parsedBody['action']['group']['uid'])) { + $this->groupDisable((int)$parsedBody['action']['group']['uid'], (int)($parsedBody['action']['group']['hidden'] ?? 0)); + } elseif (!empty($parsedBody['action']['delete'])) { + $this->deleteTask($view, (int)$parsedBody['action']['delete']); + } elseif (!empty($parsedBody['action']['groupRemove'])) { + $rows = $this->groupRemove((int)$parsedBody['action']['groupRemove']); + if ($rows > 0) { + $view->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.group.deleted')); + } else { + $view->addFlashMessage($this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.group.delete.failed'), '', ContextualFeedbackSeverity::WARNING); + } + } + return $this->renderListTasksView($view, $moduleData, $request); + } + + /** + * AJAX endpoint for setup check modal content. + */ + public function setupCheckAction(ServerRequestInterface $request): ResponseInterface + { + $view = $this->moduleTemplateFactory->create($request); + $view->assign('dateFormat', [ + 'day' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?? 'd-m-y', + 'time' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] ?? 'H:i', + ]); + $this->addSetupCheckInformation($view); + return $view->renderResponse('CheckScreen'); + } + + /** + * Mark a task as deleted. + */ + private function deleteTask(ModuleTemplate $view, int $taskUid): void + { + $languageService = $this->getLanguageService(); + if ($taskUid <= 0) { + throw new \RuntimeException('Expecting a valid task uid', 1641670374); + } + try { + // Try to fetch the task and delete it + $task = $this->taskRepository->findByUid($taskUid); + if ($this->taskRepository->isTaskMarkedAsRunning($task)) { + // If the task is currently running, it may not be deleted + $this->addMessage($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.canNotDeleteRunningTask'), ContextualFeedbackSeverity::ERROR); + } else { + if ($this->taskRepository->remove($task)) { + $this->addMessage($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.deleteSuccess')); + } else { + $this->addMessage($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.deleteError')); + } + } + } catch (\UnexpectedValueException) { + // The task could not be unserialized, simply update the database record setting it to deleted + $result = $this->taskRepository->remove($taskUid); + if ($result) { + $this->addMessage($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.deleteSuccess')); + } else { + $this->addMessage($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.deleteError'), ContextualFeedbackSeverity::ERROR); + } + } catch (\OutOfBoundsException) { + // The task was not found, for some reason + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskNotFound'), $taskUid), ContextualFeedbackSeverity::ERROR); + } + } + + /** + * Clears the registered running executions from the task. + * Note this doesn't actually stop the running script. It just unmarks execution. + * @todo find a way to really kill the running task. + */ + private function stopTask(ModuleTemplate $view, int $taskUid): void + { + $languageService = $this->getLanguageService(); + if ($taskUid <= 0) { + throw new \RuntimeException('Expecting a valid task uid', 1641670375); + } + try { + // Try to fetch the task and stop it + $task = $this->taskRepository->findByUid($taskUid); + if ($this->taskRepository->isTaskMarkedAsRunning($task)) { + // If the task is indeed currently running, clear marked executions + $result = $this->taskRepository->removeAllRegisteredExecutionsForTask($task); + if ($result) { + $this->addMessage($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.stopSuccess')); + } else { + $this->addMessage($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.stopError'), ContextualFeedbackSeverity::ERROR); + } + } else { + // The task is not running, nothing to unmark + $this->addMessage($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.maynotStopNonRunningTask'), ContextualFeedbackSeverity::WARNING); + } + } catch (\OutOfBoundsException $e) { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskNotFound'), $taskUid), ContextualFeedbackSeverity::ERROR); + } catch (\UnexpectedValueException $e) { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.stopTaskFailed'), $taskUid, $e->getMessage()), ContextualFeedbackSeverity::ERROR); + } + } + + /** + * Toggle the disabled state of a task and register for next execution if a task is of type "single execution". + */ + private function toggleDisabledFlag(ModuleTemplate $view, int $taskUid): void + { + $languageService = $this->getLanguageService(); + if ($taskUid <= 0) { + throw new \RuntimeException('Expecting a valid task uid to toggle disabled state', 1641670373); + } + try { + $task = $this->taskRepository->findByUid($taskUid); + // Toggle the task state and add a flash message + $taskName = $this->taskService->getHumanReadableTaskName($task); + $isTaskDisabled = $task->isDisabled(); + // If a disabled single task is enabled again, register it for a single execution at next scheduler run. + if ($isTaskDisabled && $task->getExecution()->isSingleRun()) { + $task->setDisabled(false); + $task->setRunOnNextCronJob(true); + $execution = Execution::createSingleExecution($this->context->getAspect('date')->get('timestamp')); + $task->setExecution($execution); + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskEnabledAndQueuedForExecution'), $taskName, $taskUid)); + } elseif ($isTaskDisabled) { + $task->setDisabled(false); + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskEnabled'), $taskName, $taskUid)); + } else { + $task->setDisabled(true); + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskDisabled'), $taskName, $taskUid)); + } + $this->taskRepository->updateExecution($task); + } catch (\OutOfBoundsException) { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskNotFound'), $taskUid), ContextualFeedbackSeverity::ERROR); + } catch (\UnexpectedValueException $e) { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.toggleDisableFailed'), $taskUid, $e->getMessage()), ContextualFeedbackSeverity::ERROR); + } + } + + /** + * Execute a list of tasks. + */ + private function executeTasks(ModuleTemplate $view, string $taskUids): void + { + $taskUids = GeneralUtility::intExplode(',', $taskUids, true); + if (empty($taskUids)) { + throw new \RuntimeException('Expecting a list of task uids to execute', 1641715832); + } + // Loop selected tasks and execute. + $languageService = $this->getLanguageService(); + foreach ($taskUids as $uid) { + try { + $task = $this->taskRepository->findByUid($uid); + $name = $this->taskService->getHumanReadableTaskName($task); + // Try to execute it and report result + $result = $this->scheduler->executeTask($task); + if ($result) { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.executed'), $name, $uid)); + } else { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.notExecuted'), $name, $uid), ContextualFeedbackSeverity::ERROR); + } + $this->scheduler->recordLastRun('manual'); + } catch (\OutOfBoundsException $e) { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskNotFound'), $uid), ContextualFeedbackSeverity::ERROR); + } catch (\Exception $e) { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.executionFailed'), $uid, $e->getMessage()), ContextualFeedbackSeverity::ERROR); + } + } + } + + /** + * Schedule selected tasks to be executed on next cron run + */ + private function scheduleCrons(ModuleTemplate $view, string $taskUids): void + { + $taskUids = GeneralUtility::intExplode(',', $taskUids, true); + if (empty($taskUids)) { + throw new \RuntimeException('Expecting a list of task uids to schedule', 1641715833); + } + // Loop selected tasks and register for next cron run. + $languageService = $this->getLanguageService(); + foreach ($taskUids as $uid) { + try { + $task = $this->taskRepository->findByUid($uid); + $name = $this->taskService->getHumanReadableTaskName($task); + $task->setRunOnNextCronJob(true); + if ($task->isDisabled()) { + $task->setDisabled(false); + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskEnabledAndQueuedForExecution'), $name, $uid)); + } else { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskQueuedForExecution'), $name, $uid)); + } + $this->taskRepository->updateExecution($task); + } catch (\OutOfBoundsException $e) { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskNotFound'), $uid), ContextualFeedbackSeverity::ERROR); + } catch (\UnexpectedValueException $e) { + $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.schedulingFailed'), $uid, $e->getMessage()), ContextualFeedbackSeverity::ERROR); + } + } + } + + /** + * Assemble a listing of scheduled tasks + */ + private function renderListTasksView(ModuleTemplate $view, ModuleData $moduleData, ServerRequestInterface $request): ResponseInterface + { + $languageService = $this->getLanguageService(); + $data = $this->taskRepository->getGroupedTasks(); + $hasAvailableTaskTypes = $this->taskService->getAllTaskTypes() !== []; + + $groups = $data['taskGroupsWithTasks'] ?? []; + $groups = array_map( + static fn(int $key, array $group): array => array_merge($group, ['taskGroupCollapsed' => (bool)($moduleData->get('task-group-' . $key, false))]), + array_keys($groups), + $groups + ); + + // Move "not assigned to group" to the end + if (array_key_exists('uid', $groups[0] ?? []) && $groups[0]['uid'] === null) { + $groupWithoutTaskGroup = $groups[0]; + unset($groups[0]); + $groups[0] = $groupWithoutTaskGroup; + } + + $this->pageRenderer->loadJavaScriptModule('@typo3/scheduler/new-scheduler-task-wizard-button.js'); + $this->pageRenderer->loadJavaScriptModule('@typo3/scheduler/setup-check-button.js'); + + $view->assignMultiple([ + 'groups' => $groups, + 'groupsWithoutTasks' => $this->getGroupsWithoutTasks($groups), + 'hasAvailableTaskTypes' => $hasAvailableTaskTypes, + 'errorClasses' => $data['errorClasses'], + 'returnUrl' => $this->uriBuilder->buildUriFromRoute('scheduler'), + 'errorClassesCollapsed' => (bool)($moduleData->get('task-group-missing', false)), + ]); + $view->setTitle( + $languageService->translate('title', 'scheduler.module'), + $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler') + ); + $view->makeDocHeaderModuleMenu(); + if ($hasAvailableTaskTypes) { + $addTaskUrl = (string)$this->uriBuilder->buildUriFromRoute('ajax_new_scheduler_task_wizard', [ + 'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(), + ]); + $view->assign('addTaskUrl', $addTaskUrl); + $this->addDocHeaderAddTaskButton($view, $addTaskUrl); + $this->addDocHeaderAddTaskGroupButton($view); + $this->addDocHeaderSetupCheckButton($view); + } + $this->addDocHeaderShortcutButton($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler')); + return $view->renderResponse('ListTasks'); + } + + private function addDocHeaderAddTaskButton(ModuleTemplate $moduleTemplate, string $url): void + { + $languageService = $this->getLanguageService(); + $addButton = $this->componentFactory->createGenericButton() + ->setTag('typo3-scheduler-new-task-wizard-button') + ->setIcon($this->iconFactory->getIcon('actions-plus', IconSize::SMALL)) + ->setLabel($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add')) + ->setShowLabelText(true) + ->setAttributes([ + 'url' => $url, + 'subject' => $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add'), + ]); + $moduleTemplate->addButtonToButtonBar($addButton, ButtonBar::BUTTON_POSITION_LEFT, 2); + } + + private function addDocHeaderAddTaskGroupButton(ModuleTemplate $moduleTemplate): void + { + $languageService = $this->getLanguageService(); + $addButton = $this->componentFactory->createInputButton() + ->setTitle($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.group.add')) + ->setShowLabelText(true) + ->setIcon($this->iconFactory->getIcon('actions-plus', IconSize::SMALL)) + ->setName('createSchedulerGroup') + ->setValue('1') + ->setClasses('t3js-create-group'); + $moduleTemplate->addButtonToButtonBar($addButton, ButtonBar::BUTTON_POSITION_LEFT, 3); + } + + private function addDocHeaderSetupCheckButton(ModuleTemplate $moduleTemplate): void + { + $languageService = $this->getLanguageService(); + $setupCheckButton = $this->componentFactory->createGenericButton() + ->setTag('typo3-scheduler-setup-check-button') + ->setIcon($this->iconFactory->getIcon('actions-window-cog', IconSize::SMALL)) + ->setLabel($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check')) + ->setShowLabelText(true) + ->setAttributes([ + 'url' => (string)$this->uriBuilder->buildUriFromRoute('ajax_scheduler_setup_check'), + 'subject' => $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check'), + ]); + $moduleTemplate->addButtonToButtonBar($setupCheckButton, ButtonBar::BUTTON_POSITION_RIGHT, 0); + } + + private function addDocHeaderShortcutButton(ModuleTemplate $moduleTemplate, string $name): void + { + $moduleTemplate->getDocHeaderComponent()->setShortcutContext( + 'scheduler', + $name + ); + } + + /** + * Add a flash message to the flash message queue of this module. + */ + private function addMessage(ModuleTemplate $moduleTemplate, string $message, ContextualFeedbackSeverity $severity = ContextualFeedbackSeverity::OK): void + { + $moduleTemplate->addFlashMessage($message, '', $severity); + } + + private function getGroupsWithoutTasks(array $taskGroupsWithTasks): array + { + $uidGroupsWithTasks = array_filter(array_column($taskGroupsWithTasks, 'uid')); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_scheduler_task_group'); + $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class); + $resultEmptyGroups = $queryBuilder->select('*') + ->from('tx_scheduler_task_group') + ->orderBy('groupName'); + + // Only add where statement if we have taskGroups to consider. + if (!empty($uidGroupsWithTasks)) { + $resultEmptyGroups->where($queryBuilder->expr()->notIn('uid', $uidGroupsWithTasks)); + } + + return $resultEmptyGroups->executeQuery()->fetchAllAssociative(); + } + + private function groupRemove(int $groupId): int + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_scheduler_task_group'); + return $queryBuilder->update('tx_scheduler_task_group') + ->where($queryBuilder->expr()->eq('uid', $groupId)) + ->set('deleted', 1) + ->executeStatement(); + } + + private function groupDisable(int $groupId, int $hidden): void + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_scheduler_task_group'); + $queryBuilder->update('tx_scheduler_task_group') + ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($groupId))) + ->set('hidden', $hidden) + ->executeStatement(); + } + + private function addSetupCheckInformation(ViewInterface $view): void + { + $languageService = $this->getLanguageService(); + // Display information about the last automated run, as stored in the system registry. + $lastRun = $this->registry->get('tx_scheduler', 'lastRun'); + $lastRunMessageLabel = 'msg.noLastRun'; + $lastRunMessageLabelArguments = []; + $lastRunSeverity = ContextualFeedbackSeverity::WARNING->value; + if (is_array($lastRun)) { + if (empty($lastRun['end']) || empty($lastRun['start']) || empty($lastRun['type'])) { + $lastRunMessageLabel = 'msg.incompleteLastRun'; + $lastRunSeverity = ContextualFeedbackSeverity::WARNING->value; + } else { + $lastRunMessageLabelArguments = [ + $lastRun['type'] === 'manual' + ? $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.manually') + : $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.automatically'), + date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['start']), + date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['start']), + date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['end']), + date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['end']), + ]; + $lastRunMessageLabel = 'msg.lastRun'; + $lastRunSeverity = ContextualFeedbackSeverity::INFO->value; + } + } + + // Information about cli script. + $script = $this->determineExecutablePath(); + $isExecutableMessageLabel = 'msg.cliScriptNotExecutable'; + $isExecutableSeverity = ContextualFeedbackSeverity::ERROR->value; + $composerMode = !$script && Environment::isComposerMode(); + if (!$composerMode) { + // Check if CLI script is executable or not. Skip this check if running Windows since executable detection + // is not reliable on this platform, the script will always appear as *not* executable. + $isExecutable = Environment::isWindows() ? true : ($script && is_executable($script)); + if ($isExecutable) { + $isExecutableMessageLabel = 'msg.cliScriptExecutable'; + $isExecutableSeverity = ContextualFeedbackSeverity::OK->value; + } + } + + $view->assignMultiple([ + 'composerMode' => $composerMode, + 'script' => $script, + 'lastRunMessageLabel' => $lastRunMessageLabel, + 'lastRunMessageLabelArguments' => $lastRunMessageLabelArguments, + 'lastRunSeverity' => $lastRunSeverity, + 'isExecutableMessageLabel' => $isExecutableMessageLabel, + 'isExecutableSeverity' => $isExecutableSeverity, + ]); + } + + private function determineExecutablePath(): ?string + { + if (!Environment::isComposerMode()) { + return GeneralUtility::getFileAbsFileName('EXT:core/bin/typo3'); + } + $composerJsonFile = getenv('TYPO3_PATH_COMPOSER_ROOT') . '/composer.json'; + if (!file_exists($composerJsonFile) || !($jsonContent = file_get_contents($composerJsonFile))) { + return null; + } + $jsonConfig = @json_decode($jsonContent, true); + if (empty($jsonConfig) || !is_array($jsonConfig)) { + return null; + } + $vendorDir = trim($jsonConfig['config']['vendor-dir'] ?? 'vendor', '/'); + $binDir = trim($jsonConfig['config']['bin-dir'] ?? $vendorDir . '/bin', '/'); + return sprintf('%s/%s/typo3', getenv('TYPO3_PATH_COMPOSER_ROOT'), $binDir); + } + + private function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/Classes/CronCommand/CronCommand.php b/Classes/CronCommand/CronCommand.php new file mode 100644 index 0000000..20e0101 --- /dev/null +++ b/Classes/CronCommand/CronCommand.php @@ -0,0 +1,213 @@ +cronCommandSections = GeneralUtility::trimExplode(' ', $cronCommand); + // Initialize the values with the starting time + // This takes care that the calculated time is always in the future + if ($timestamp === false) { + $timestamp = strtotime('+1 minute'); + } else { + $timestamp += 60; + } + $this->timestamp = $this->roundTimestamp($timestamp); + } + + /** + * Calculates the date of the next execution. + * + * @throws \RuntimeException + */ + public function calculateNextValue(): void + { + $newTimestamp = $this->getTimestamp(); + // Calculate next minute and hour field + $loopCount = 0; + while (true) { + $loopCount++; + // If there was no match within two days, cron command is invalid. + // The second day is needed to catch the summertime leap in some countries. + if ($loopCount > 2880) { + throw new \RuntimeException('Unable to determine next execution timestamp: Hour and minute combination is invalid.', 1291494126); + } + if ($this->minuteAndHourMatchesCronCommand($newTimestamp)) { + break; + } + $newTimestamp += 60; + } + $loopCount = 0; + while (true) { + $loopCount++; + // A date must match within the next 4 years, this high number makes + // sure leap year cron command configuration are caught. + // If the loop runs longer than that, the cron command is invalid. + if ($loopCount > 1464) { + throw new \RuntimeException('Unable to determine next execution timestamp: Day of month, month and day of week combination is invalid.', 1291501280); + } + if ($this->dayMatchesCronCommand($newTimestamp)) { + break; + } + $newTimestamp += $this->numberOfSecondsInDay($newTimestamp); + } + $this->timestamp = $newTimestamp; + } + + /** + * Get next timestamp + */ + public function getTimestamp(): int + { + return $this->timestamp; + } + + /** + * Get cron command sections. Array of strings, each containing either + * a list of comma separated integers or * + */ + public function getCronCommandSections(): array + { + return $this->cronCommandSections; + } + + /** + * Determine if current timestamp matches minute and hour cron command restriction. + */ + protected function minuteAndHourMatchesCronCommand(int $timestamp): bool + { + $minute = (int)date('i', $timestamp); + $hour = (int)date('G', $timestamp); + $commandMatch = false; + if ($this->isInCommandList($this->cronCommandSections[0], $minute) && $this->isInCommandList($this->cronCommandSections[1], $hour)) { + $commandMatch = true; + } + return $commandMatch; + } + + /** + * Determine if current timestamp matches day of month, month and day of week + * cron command restriction + */ + protected function dayMatchesCronCommand(int $timestamp): bool + { + $dayOfMonth = (int)date('j', $timestamp); + $month = (int)date('n', $timestamp); + $dayOfWeek = (int)date('N', $timestamp); + $isInDayOfMonth = $this->isInCommandList($this->cronCommandSections[2], $dayOfMonth); + $isInMonth = $this->isInCommandList($this->cronCommandSections[3], $month); + $isInDayOfWeek = $this->isInCommandList($this->cronCommandSections[4], $dayOfWeek); + // Quote from vixiecron: + // Note: The day of a command's execution can be specified by two fields — day of month, and day of week. + // If both fields are restricted (i.e., aren't *), the command will be run when either field + // matches the current time. For example, `30 4 1,15 * 5' would cause + // a command to be run at 4:30 am on the 1st and 15th of each month, plus every Friday. + $isDayOfMonthRestricted = (string)$this->cronCommandSections[2] !== '*'; + $isDayOfWeekRestricted = (string)$this->cronCommandSections[4] !== '*'; + if (!$isInMonth) { + return false; + } + + // If both day-of-month and day-of-week are unrestricted, month match is enough. + if (!$isDayOfMonthRestricted && !$isDayOfWeekRestricted) { + return true; + } + + // Otherwise, at least one restriction must match. + return ($isInDayOfMonth && $isDayOfMonthRestricted) || ($isInDayOfWeek && $isDayOfWeekRestricted); + } + + /** + * Determine if a given number validates a cron command section. The given cron + * command must be a 'normalized' list with only comma separated integers or '*' + */ + protected function isInCommandList(string $commandExpression, int $numberToMatch): bool + { + if ($commandExpression === '*') { + $inList = true; + } else { + $inList = GeneralUtility::inList($commandExpression, (string)$numberToMatch); + } + return $inList; + } + + /** + * Helper method to calculate number of seconds in a day. + * + * This is not always 86400 (60*60*24) and depends on the timezone: + * Some countries like Germany have a summertime / wintertime switch, + * on every last sunday in march clocks are forwarded by one hour (set from 2:00 to 3:00), + * and on last sunday of october they are set back one hour (from 3:00 to 2:00). + * This shortens and lengthens the length of a day by one hour. + */ + protected function numberOfSecondsInDay(int $timestamp): int + { + $now = mktime(0, 0, 0, (int)date('n', $timestamp), (int)date('j', $timestamp), (int)date('Y', $timestamp)); + // Make sure to be in next day, even if day has 25 hours + $nextDay = $now + 60 * 60 * 25; + $nextDay = mktime(0, 0, 0, (int)date('n', $nextDay), (int)date('j', $nextDay), (int)date('Y', $nextDay)); + return $nextDay - $now; + } + + /** + * Round a timestamp down to full minute. + */ + protected function roundTimestamp(int $timestamp): int + { + return (int)(floor($timestamp / 60) * 60); + } +} diff --git a/Classes/CronCommand/NormalizeCommand.php b/Classes/CronCommand/NormalizeCommand.php new file mode 100644 index 0000000..69f1b7d --- /dev/null +++ b/Classes/CronCommand/NormalizeCommand.php @@ -0,0 +1,336 @@ + $upperBound) { + throw new \InvalidArgumentException('An element in the list is higher than allowed.', 1291470170); + } + $fieldValues = implode(',', $fieldList); + } + return $fieldValues; + } + + /** + * Convert a range of integers to a list: 4-6 results in a string '4,5,6' + * + * @param string $range integer-integer + * @throws \InvalidArgumentException If range can not be converted to list + */ + protected static function convertRangeToListOfValues(string $range): string + { + if ($range === '') { + throw new \InvalidArgumentException('Unable to convert range to list of values with empty string.', 1291234985); + } + $rangeArray = explode('-', $range); + // Sanitize fields and cast to integer + foreach ($rangeArray as $fieldNumber => $fieldValue) { + if (!MathUtility::canBeInterpretedAsInteger($fieldValue)) { + throw new \InvalidArgumentException('Unable to convert value to integer.', 1291237668); + } + $rangeArray[$fieldNumber] = (int)$fieldValue; + } + + $rangeArrayCount = count($rangeArray); + if ($rangeArrayCount === 1) { + $resultList = $rangeArray[0]; + } elseif ($rangeArrayCount === 2) { + $left = $rangeArray[0]; + $right = $rangeArray[1]; + if ($left > $right) { + throw new \InvalidArgumentException('Unable to convert range to list: Left integer must not be greater than right integer.', 1291237145); + } + $resultListArray = []; + for ($i = $left; $i <= $right; $i++) { + $resultListArray[] = $i; + } + $resultList = implode(',', $resultListArray); + } else { + throw new \InvalidArgumentException('Unable to convert range to list of values.', 1291234986); + } + return (string)$resultList; + } + + /** + * Reduce a given list of values by step value. + * Following a range with ``/'' specifies skips of the number's value through the range. + * 1-5/2 -> 1,3,5 + * 2-10/3 -> 2,5,8 + * + * @return string comma-separated list of valid values + * @throws \InvalidArgumentException if step value is invalid or if resulting list is empty + */ + protected static function reduceListOfValuesByStepValue(string $stepExpression): string + { + if ($stepExpression === '') { + throw new \InvalidArgumentException('Unable to convert step values.', 1291234987); + } + $stepValuesAndStepArray = explode('/', $stepExpression); + $stepValuesAndStepArrayCount = count($stepValuesAndStepArray); + if ($stepValuesAndStepArrayCount > 2) { + throw new \InvalidArgumentException('Unable to convert step values: Multiple slashes found.', 1291242168); + } + $left = $stepValuesAndStepArray[0]; + $right = $stepValuesAndStepArray[1] ?? ''; + if ($left === '') { + throw new \InvalidArgumentException('Unable to convert step values: Left part of / is empty.', 1291414955); + } + if ($right === '') { + throw new \InvalidArgumentException('Unable to convert step values: Right part of / is empty.', 1291414956); + } + if (!MathUtility::canBeInterpretedAsInteger($right)) { + throw new \InvalidArgumentException('Unable to convert step values: Right part must be a single integer.', 1291414957); + } + $right = (int)$right; + $leftArray = explode(',', $left); + $validValues = []; + $currentStep = $right; + foreach ($leftArray as $leftValue) { + if (!MathUtility::canBeInterpretedAsInteger($leftValue)) { + throw new \InvalidArgumentException('Unable to convert step values: Left part must be a single integer or comma separated list of integers.', 1291414958); + } + if ($currentStep === 0) { + $currentStep = $right; + } + if ($currentStep === $right) { + $validValues[] = (int)$leftValue; + } + $currentStep--; + } + if (empty($validValues)) { + throw new \InvalidArgumentException('Unable to convert step values: Result value list is empty.', 1291414959); + } + return implode(',', $validValues); + } + + /** + * Dispatcher method for normalizeMonth and normalizeWeekday + */ + protected static function normalizeMonthAndWeekday(string $expression, bool $isMonth = true): string + { + $expression = $isMonth ? self::normalizeMonth($expression) : self::normalizeWeekday($expression); + return (string)$expression; + } + + /** + * Accept a string representation or integer number of a month like + * 'jan', 'February', 01, ... and convert to normalized integer value between 1 and 12 + * + * @throws \InvalidArgumentException If month string can not be converted to integer + */ + protected static function normalizeMonth(string $month): int + { + $timestamp = strtotime('2010-' . $month . '-01'); + // timestamp must be >= 2010-01-01 and <= 2010-12-01 + if (!$timestamp || $timestamp < strtotime('2010-01-01') || $timestamp > strtotime('2010-12-01')) { + throw new \InvalidArgumentException('Unable to convert given month name.', 1291083486); + } + return (int)date('n', $timestamp); + } + + /** + * Accept a string representation or integer number of a weekday like + * 'mon', 'Friday', 3, ... and convert to normalized integer value between 1 and 7 + * + * @throws \InvalidArgumentException If weekday string can not be converted + */ + protected static function normalizeWeekday(string $weekday): int + { + $normalizedWeekday = false; + // 0 (sunday) -> 7 + if ($weekday === '0') { + $weekday = 7; + } + if ($weekday >= 1 && $weekday <= 7) { + $normalizedWeekday = (int)$weekday; + } + if (!$normalizedWeekday) { + // Convert string representation like 'sun' to integer + $timestamp = strtotime('next ' . $weekday, (int)mktime(0, 0, 0, 1, 1, 2010)); + if (!$timestamp || $timestamp < strtotime('2010-01-01') || $timestamp > strtotime('2010-01-08')) { + throw new \InvalidArgumentException('Unable to convert given weekday name.', 1291163589); + } + $normalizedWeekday = (int)date('N', $timestamp); + } + return $normalizedWeekday; + } +} diff --git a/Classes/Domain/Repository/SchedulerTaskRepository.php b/Classes/Domain/Repository/SchedulerTaskRepository.php new file mode 100644 index 0000000..0f42af3 --- /dev/null +++ b/Classes/Domain/Repository/SchedulerTaskRepository.php @@ -0,0 +1,682 @@ +getTaskUid(); + if (!empty($taskUid)) { + return false; + } + $fields = $this->taskService->getFieldsForRecord($task); + $fields['pid'] = 0; + $newId = uniqid('NEW'); + $dataHandler = GeneralUtility::makeInstance(DataHandler::class); + $dataHandler->start([ + self::TABLE_NAME => [ + $newId => $fields, + ], + ], []); + $dataHandler->process_datamap(); + $taskUid = (int)$dataHandler->substNEWwithIDs[$newId]; + if ($taskUid) { + $task->setTaskUid($taskUid); + return true; + } + return false; + } + + /** + * Removes a task completely from the system. + * + * @param int|AbstractTask $task The object representing the task to delete + * @return bool TRUE if the task was successfully deleted, FALSE otherwise + */ + public function remove(int|AbstractTask $task): bool + { + $taskUid = is_int($task) ? $task : $task->getTaskUid(); + if (empty($taskUid)) { + return false; + } + $dataHandler = GeneralUtility::makeInstance(DataHandler::class); + $dataHandler->start([], [ + self::TABLE_NAME => [ + $taskUid => [ + 'delete' => 1, + ], + ], + ]); + $dataHandler->process_cmdmap(); + return $dataHandler->errorLog === []; + } + + /** + * Update a task in the pool. + */ + public function update(AbstractTask $task, ?array $fields = null): bool + { + $taskUid = $task->getTaskUid(); + if (empty($taskUid)) { + return false; + } + $fields = $fields ?? $this->taskService->getFieldsForRecord($task); + $dataHandler = GeneralUtility::makeInstance(DataHandler::class); + $dataHandler->start([ + self::TABLE_NAME => [ + $taskUid => $fields, + ], + ], []); + $dataHandler->process_datamap(); + return true; + } + + /** + * Update a task in the pool but only the execution information. + */ + public function updateExecution(AbstractTask $task, bool $forceDisablingTask = false): void + { + $taskUid = $task->getTaskUid(); + if (empty($taskUid)) { + return; + } + $fields = $this->taskService->getFieldsForRecord($task); + $fields = [ + 'nextexecution' => $fields['nextexecution'], + 'disable' => $forceDisablingTask ? true : $fields['disable'], + 'execution_details' => $fields['execution_details'], + ]; + $backendUser = $GLOBALS['BE_USER'] ?? null; + if ($backendUser === null && Environment::isCli()) { + /** @var CommandLineUserAuthentication $backendUser */ + $backendUser = Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class); + $backendUser->authenticate(); + } + $dataHandler = GeneralUtility::makeInstance(DataHandler::class); + // We don't want every execution information logged + $dataHandler->enableLogging = false; + $dataHandler->start([ + self::TABLE_NAME => [ + $taskUid => $fields, + ], + ], [], $backendUser); + $dataHandler->process_datamap(); + } + + /** + * Fetches a task object from the db with the given $uid. The object representing + * the next due task is returned. + * If there are no tasks due, the method throws an exception. + * + * @param int $uid Primary key of a task + * @throws \OutOfBoundsException + * @throws \UnexpectedValueException + */ + public function findByUid(int $uid): AbstractTask + { + $row = BackendUtility::getRecord(self::TABLE_NAME, $uid); + if (empty($row)) { + // Although an uid was passed, no task with given was found + throw new \OutOfBoundsException('No task with id ' . $uid . ' found', 1422044826); + } + + return $this->createValidTaskObjectOrDisableTask($row); + } + + /** + * Fetches the DB record for a given task UID. + * + * @param int $uid Primary key of the task to get + * @return array|null Database record for the task + * @see findByUid() + */ + public function findRecordByUid(int $uid): ?array + { + $row = BackendUtility::getRecord(self::TABLE_NAME, $uid); + if (empty($row)) { + return null; + } + return $row; + } + + /** + * Fetch and unserialize a task object from the db. Returns the object representing the + * next due task is returned. If there are no due tasks, the method throws an exception. + * + * @throws \UnexpectedValueException + */ + public function findNextExecutableTask(): ?AbstractTask + { + // If no uid is given, take any non-disabled task that has a next execution time in the past + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME); + $queryBuilder->select( + 't.*' + ) + ->from(self::TABLE_NAME, 't') + ->setMaxResults(1); + // Define where clause + $queryBuilder->getRestrictions()->removeAll(); + $queryBuilder->leftJoin( + 't', + 'tx_scheduler_task_group', + 'g', + $queryBuilder->expr()->eq('g.uid', $queryBuilder->expr()->castInt($queryBuilder->quoteIdentifier('t.task_group'))) + ); + $queryBuilder->where( + $queryBuilder->expr()->eq('t.disable', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)), + $queryBuilder->expr()->neq( + 't.nextexecution', + $queryBuilder->createNamedParameter(0, Connection::PARAM_INT) + ), + $queryBuilder->expr()->lte( + 't.nextexecution', + $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], Connection::PARAM_INT) + ), + $queryBuilder->expr()->or( + $queryBuilder->expr()->eq('g.hidden', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)), + $queryBuilder->expr()->isNull('g.hidden') + ), + $queryBuilder->expr()->eq('t.deleted', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)) + ); + $queryBuilder->orderBy('t.priority', 'DESC')->addOrderBy('t.nextexecution', 'ASC'); + + $row = $queryBuilder->executeQuery()->fetchAssociative(); + if (empty($row)) { + return null; + } + + return $this->createValidTaskObjectOrDisableTask($row); + } + + /** + * @todo This will get split up into errored classes + */ + public function getGroupedTasks(): array + { + // Get all registered tasks + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME); + $queryBuilder->getRestrictions()->removeAll(); + $result = $queryBuilder->select('t.*') + ->addSelect( + 'g.groupName AS taskGroupName', + 'g.description AS taskGroupDescription', + 'g.uid AS taskGroupId', + 'g.color AS taskGroupColor', + 'g.deleted AS isTaskGroupDeleted', + 'g.hidden AS isTaskGroupHidden', + ) + ->from(self::TABLE_NAME, 't') + ->leftJoin( + 't', + 'tx_scheduler_task_group', + 'g', + $queryBuilder->expr()->eq('g.uid', $queryBuilder->expr()->castInt($queryBuilder->quoteIdentifier('t.task_group'))) + ) + ->where( + $queryBuilder->expr()->eq('t.deleted', 0) + ) + ->orderBy('g.sorting') + ->executeQuery(); + + $taskGroupsWithTasks = []; + $errorClasses = []; + while ($row = $result->fetchAssociative()) { + $taskData = [ + 'uid' => (int)$row['uid'], + 'lastExecutionTime' => (int)$row['lastexecution_time'], + 'lastExecutionContext' => $row['lastexecution_context'], + 'errorMessage' => '', + 'description' => $row['description'], + ]; + + try { + $taskObject = $this->taskSerializer->deserialize($row); + } catch (InvalidTaskException $e) { + $taskData['errorMessage'] = $e->getMessage(); + $taskData['taskType'] = $row['tasktype'] ?: $this->taskSerializer->extractClassName($row['serialized_task_object']); + $errorClasses[] = $taskData; + continue; + } + + $taskData['taskType'] = $taskObject->getTaskType(); + + if (!$this->isValidTaskObject($taskObject)) { + $taskData['errorMessage'] = 'The task "' . $taskObject->getTaskType() . ' is not a valid task'; + $errorClasses[] = $taskData; + continue; + } + + $taskInformation = $this->taskService->getTaskDetailsFromTask($taskObject); + if ($taskInformation === null) { + $taskData['errorMessage'] = 'The task ' . $taskObject->getTaskType() . ' is not a registered task'; + $errorClasses[] = $taskData; + continue; + } + + if ($taskObject instanceof ProgressProviderInterface) { + $taskData['progress'] = round((float)$taskObject->getProgress(), 2); + } + $taskData['fullTitle'] = $taskInformation['fullTitle']; + $taskData['additionalInformation'] = $taskObject->getAdditionalInformation(); + $taskData['disabled'] = (bool)$row['disable']; + $taskData['isRunning'] = !empty($row['serialized_executions']); + $taskData['nextExecution'] = (int)$row['nextexecution']; + $taskData['runningType'] = 'single'; + $taskData['frequency'] = ''; + if ($taskObject->getExecution()->isRecurring()) { + $taskData['runningType'] = 'recurring'; + $taskData['frequency'] = $taskObject->getExecution()->getCronCmd() ?: $taskObject->getExecution()->getInterval(); + } + $taskData['multiple'] = (bool)$taskObject->getExecution()->isParallelExecutionAllowed(); + $taskData['priority'] = (int)$row['priority']; + $taskData['priorityLabel'] = $this->resolvePriorityLabel((int)$row['priority']); + $taskData['lastExecutionFailure'] = false; + if (!empty($row['lastexecution_failure'])) { + $taskData['lastExecutionFailure'] = true; + // only scalars are serialized in \TYPO3\CMS\Scheduler\Scheduler::executeTask + $exceptionArray = @unserialize($row['lastexecution_failure'], ['allowed_classes' => false]); + $taskData['lastExecutionFailureCode'] = ''; + $taskData['lastExecutionFailureMessage'] = ''; + if (is_array($exceptionArray)) { + $taskData['lastExecutionFailureCode'] = $exceptionArray['code']; + $taskData['lastExecutionFailureMessage'] = $exceptionArray['message']; + } + } + + $taskData['statuses'] = $this->buildTaskStatuses($taskData, (bool)$row['isTaskGroupHidden']); + + // If a group is deleted or no group is set it needs to go into "not assigned groups" + $groupIndex = $row['isTaskGroupDeleted'] === 1 || $row['isTaskGroupDeleted'] === null ? 0 : (int)$row['task_group']; + if (!isset($taskGroupsWithTasks[$groupIndex])) { + $taskGroupsWithTasks[$groupIndex] = [ + 'uid' => $row['taskGroupId'], + 'groupName' => $row['taskGroupName'], + 'description' => $row['taskGroupDescription'], + 'color' => $row['taskGroupColor'], + 'hidden' => $row['isTaskGroupHidden'], + 'tasks' => [], + ]; + } + $taskGroupsWithTasks[$groupIndex]['tasks'][] = $taskData; + } + + return [ + 'taskGroupsWithTasks' => $taskGroupsWithTasks, + 'errorClasses' => $errorClasses, + ]; + } + + protected function createValidTaskObjectOrDisableTask(array $row): AbstractTask + { + $isInvalidTask = false; + $task = null; + try { + $task = $this->taskSerializer->deserialize($row); + } catch (InvalidTaskException) { + $isInvalidTask = true; + } + if ($isInvalidTask || !$this->isValidTaskObject($task)) { + $fieldName = $this->tcaSchemaFactory + ->get(self::TABLE_NAME) + ->getCapability(TcaSchemaCapability::RestrictionDisabledField) + ->getFieldName(); + if ((bool)$row[$fieldName] !== true) { + // Forcibly set the disabled flag to 1 in the database (if not already set), so that the + // task does not come up again and again for execution. Execute a simple update statement + // to avoid triggering any DH hook again, which would lead to an infinity loop. + $this->connectionPool + ->getConnectionForTable(self::TABLE_NAME) + ->update(self::TABLE_NAME, [$fieldName => 1], ['uid' => (int)$row['uid']]); + } + // Throw an exception to raise the problem + // @todo: This should most likely be changed to a specific exception. + throw new \UnexpectedValueException('Could not unserialize task', 1255083671); + } + + // The task is valid, return it + if ($task->getTaskGroup() === null) { + // Fix invalid task_group=NULL settings in order to avoid exceptions when saving on PostgreSQL + $task->setTaskGroup(0); + } + return $task; + } + + /** + * Fetch and unserialize task objects selected with some (SQL) condition + */ + public function findNextExecutableTaskForUid(int $uid): ?AbstractTask + { + $queryBuilder = $this->connectionPool + ->getQueryBuilderForTable(self::TABLE_NAME); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)) + ->add(GeneralUtility::makeInstance(HiddenRestriction::class)); + + $queryBuilder + ->select('*') + ->from(self::TABLE_NAME) + ->where( + $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)), + $queryBuilder->expr()->neq('nextexecution', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)), + $queryBuilder->expr()->lte('nextexecution', $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], Connection::PARAM_INT)), + ); + + $result = $queryBuilder->executeQuery(); + while ($row = $result->fetchAssociative()) { + try { + $task = $this->taskSerializer->deserialize($row); + } catch (InvalidTaskException) { + continue; + } + + // Add the task to the list only if it is valid + if ($this->isValidTaskObject($task)) { + return $task; + } + } + return null; + } + + public function isTaskMarkedAsRunning(AbstractTask $task): bool + { + $row = BackendUtility::getRecord(self::TABLE_NAME, $task->getTaskUid()); + return !empty($row['serialized_executions'] ?? null); + } + + /** + * This method adds current execution to the execution list. + * It also logs the execution time and mode + * + * The execution id is guaranteed to start from zero if the task has no + * currently running execution at the time of id allocation. + * + * @return int Execution id + */ + public function addExecutionToTask(AbstractTask $task): int + { + while (true) { + $row = BackendUtility::getRecord(self::TABLE_NAME, $task->getTaskUid()); + + if ($row === null) { + throw new \InvalidArgumentException( + 'Given task must have a persistence record associated with it', + 1741257045 + ); + } + + $previousExecutions = isset($row['serialized_executions']) + ? (string)$row['serialized_executions'] + : null; + + $runningExecutions = $previousExecutions !== null + && $previousExecutions !== '' + // serialized in \TYPO3\CMS\Scheduler\Domain\Repository\SchedulerTaskRepository::addExecutionToTask as `array` + ? unserialize($previousExecutions, ['allowed_classes' => false]) + : []; + + // 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(); + $updateCount = $this->connectionPool + ->getConnectionForTable(self::TABLE_NAME) + ->update( + self::TABLE_NAME, + [ + 'serialized_executions' => serialize($runningExecutions), + 'lastexecution_time' => time(), + // Define the context in which the script is running + 'lastexecution_context' => Environment::isCli() ? 'CLI' : 'BE', + ], + [ + 'uid' => $task->getTaskUid(), + 'serialized_executions' => $previousExecutions, + ], + [ + 'serialized_executions' => Connection::PARAM_LOB, + ] + ); + + if ($updateCount === 1) { + return $numExecutions; + } + } + } + + /** + * Removes a given execution from the list + * + * @param int $executionID Id of the execution to remove. + * @param string|array|null $failureReason Details of an exception to signal a failed execution. + */ + public function removeExecutionOfTask(AbstractTask $task, int $executionID, array|string|null $failureReason = null): void + { + while ($row = BackendUtility::getRecord(self::TABLE_NAME, $task->getTaskUid())) { + $previousExecutions = (string)($row['serialized_executions'] ?? ''); + if ($previousExecutions === '') { + break; + } + // serialized in \TYPO3\CMS\Scheduler\Domain\Repository\SchedulerTaskRepository::addExecutionToTask as `array` + $runningExecutions = unserialize($previousExecutions, ['allowed_classes' => false]); + // Remove the selected execution + unset($runningExecutions[$executionID]); + if (!empty($runningExecutions)) { + // Re-serialize the updated executions list (if necessary) + $runningExecutionsSerialized = serialize($runningExecutions); + } else { + $runningExecutionsSerialized = ''; + } + if (is_array($failureReason)) { + $failureReason = json_encode($failureReason); + } + // Save the updated executions list + $fieldUpdates = [ + 'serialized_executions' => $runningExecutionsSerialized, + ]; + if ($failureReason !== null) { + $fieldUpdates['lastexecution_failure'] = (string)$failureReason; + } + $updateCount = $this->connectionPool + ->getConnectionForTable(self::TABLE_NAME) + ->update( + self::TABLE_NAME, + $fieldUpdates, + [ + 'uid' => $task->getTaskUid(), + 'serialized_executions' => $previousExecutions, + ], + [ + 'serialized_executions' => Connection::PARAM_LOB, + ] + ); + if ($updateCount === 1) { + break; + } + } + } + + /** + * Clears all marked executions + * + * @return bool TRUE if the clearing succeeded, FALSE otherwise + */ + public function removeAllRegisteredExecutionsForTask(AbstractTask $task): bool + { + // Set the serialized executions field to empty + $result = $this->connectionPool + ->getConnectionForTable(self::TABLE_NAME) + ->update( + self::TABLE_NAME, + ['serialized_executions' => ''], + ['uid' => $task->getTaskUid()], + ['serialized_executions' => Connection::PARAM_LOB] + ); + return (bool)$result; + } + + /** + * See if there are any tasks configured at all. + */ + public function hasTasks(): bool + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + $queryBuilder + ->count('*') + ->from(self::TABLE_NAME); + return $queryBuilder->executeQuery()->fetchOne() > 0; + } + + protected function isValidTaskObject($task): bool + { + return (new TaskValidator())->isValid($task); + } + + /** + * Resolves the status flags of a single task into an ordered list, shared as a single + * source of truth by the backend module listing and the "scheduler:list" CLI command. + * + * @return list + */ + private function buildTaskStatuses(array $task, bool $groupHidden): array + { + $now = $this->context->getAspect('date')->get('timestamp'); + $statuses = []; + + if ($task['isRunning']) { + $statuses[] = new TaskStatus( + type: 'running', + severity: ContextualFeedbackSeverity::INFO, + state: 'running', + label: 'scheduler.messages:status.running', + ); + } + if ($task['nextExecution'] && $task['nextExecution'] < $now && !$groupHidden && !$task['disabled']) { + $statuses[] = new TaskStatus( + type: 'late', + severity: ContextualFeedbackSeverity::WARNING, + state: 'warning', + label: 'scheduler.messages:status.late', + ); + } + if ($task['disabled'] && !$task['isRunning']) { + $statuses[] = new TaskStatus( + type: 'disabled', + severity: ContextualFeedbackSeverity::NOTICE, + state: 'disabled', + label: 'scheduler.messages:status.disabled', + ); + } + if ($groupHidden && !$task['isRunning']) { + $statuses[] = new TaskStatus( + type: 'disabledByGroup', + severity: ContextualFeedbackSeverity::NOTICE, + state: 'disabled', + label: 'scheduler.messages:status.disabledByGroup', + ); + } + if ($task['lastExecutionFailure'] ?? false) { + if (($task['lastExecutionFailureMessage'] ?? '') !== '') { + $statuses[] = new TaskStatus( + type: 'failure', + severity: ContextualFeedbackSeverity::ERROR, + state: 'danger', + label: 'scheduler.messages:status.failure', + message: 'scheduler.messages:msg.executionFailureReport', + messageArguments: [ + $task['lastExecutionFailureCode'], + $task['lastExecutionFailureMessage'], + ], + ); + } else { + $statuses[] = new TaskStatus( + type: 'failure', + severity: ContextualFeedbackSeverity::ERROR, + state: 'default', + label: 'scheduler.messages:status.failure', + message: 'scheduler.messages:msg.executionFailureDefault', + ); + } + } + + return $statuses; + } + + private function resolvePriorityLabel(int $priority): string + { + $field = $this->tcaSchemaFactory->get(self::TABLE_NAME)->getField('priority'); + if ($field instanceof StaticSelectFieldType) { + foreach ($field->getItems() as $item) { + if ((int)$item->getValue() === $priority) { + return $item->getLabel(); + } + } + } + return (string)$priority; + } +} diff --git a/Classes/Event/AfterTaskExecutionEvent.php b/Classes/Event/AfterTaskExecutionEvent.php new file mode 100644 index 0000000..92e818a --- /dev/null +++ b/Classes/Event/AfterTaskExecutionEvent.php @@ -0,0 +1,48 @@ +task; + } + + public function isSuccess(): bool + { + return $this->success; + } + + public function getException(): ?\Throwable + { + return $this->exception; + } +} diff --git a/Classes/Event/ModifyNewSchedulerTaskWizardItemsEvent.php b/Classes/Event/ModifyNewSchedulerTaskWizardItemsEvent.php new file mode 100644 index 0000000..a55388b --- /dev/null +++ b/Classes/Event/ModifyNewSchedulerTaskWizardItemsEvent.php @@ -0,0 +1,56 @@ +wizardItems; + } + + public function setWizardItems(array $wizardItems): void + { + $this->wizardItems = $wizardItems; + } + + public function addWizardItem(string $key, array $wizardItem): void + { + $this->wizardItems[$key] = $wizardItem; + } + + public function removeWizardItem(string $key): void + { + unset($this->wizardItems[$key]); + } + + public function getRequest(): ServerRequestInterface + { + return $this->request; + } +} diff --git a/Classes/EventListener/AddSchedulableCommandsAsNativeTaskTypes.php b/Classes/EventListener/AddSchedulableCommandsAsNativeTaskTypes.php new file mode 100644 index 0000000..bf8a044 --- /dev/null +++ b/Classes/EventListener/AddSchedulableCommandsAsNativeTaskTypes.php @@ -0,0 +1,77 @@ +getTca(); + foreach ($this->commandRegistry->getSchedulableCommandsConfiguration() as $commandIdentifier => $commandConfiguration) { + if (($commandConfiguration['aliasFor'] ?? '') !== '') { + // If an alias is set, we need to filter out the alias to prevent duplicate scheduler items. + continue; + } + $tca['tx_scheduler_task']['columns']['tasktype']['config']['items'][] = [ + 'label' => $commandConfiguration['name'], + 'description' => $commandConfiguration['description'], + 'value' => $commandIdentifier, + 'icon' => 'mimetypes-x-tx_scheduler_task_group', + 'group' => explode(':', $commandIdentifier)[0], + ]; + $tca['tx_scheduler_task']['types'][$commandIdentifier] = [ + 'showitem' => ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + parameters, + --div--;core.form.tabs:timing, + --palette--;;execution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended, + ', + 'columnsOverrides' => [ + 'parameters' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.schedulableCommand.command_configuration', + 'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.schedulableCommand.command_configuration.description', + 'config' => [ + 'renderType' => 'schedulableCommandConfiguration', + ], + ], + ], + 'taskOptions' => [ + // @todo When introducing the AsRecordTypeHandler attribute we can get rid of this option again + 'className' => ExecuteSchedulableCommandTask::class, + ], + ]; + } + $event->setTca($tca); + } +} diff --git a/Classes/EventListener/ReplaceAddNewButtonToFormEngine.php b/Classes/EventListener/ReplaceAddNewButtonToFormEngine.php new file mode 100644 index 0000000..a56e4a1 --- /dev/null +++ b/Classes/EventListener/ReplaceAddNewButtonToFormEngine.php @@ -0,0 +1,94 @@ +getRequest(); + + if (($request->getAttribute('routing')?->getRoute()?->getOptions()['_identifier'] ?? '') !== 'record_edit') { + return; + } + + $editConfig = $request->getQueryParams()['edit'] ?? null; + if (!is_array($editConfig) || $editConfig === [] || count($editConfig) > 1 || key($editConfig) !== 'tx_scheduler_task') { + return; + } + + $buttons = $event->getButtons(); + $leftButtons = $buttons['left'] ?? []; + + $this->pageRenderer->loadJavaScriptModule('@typo3/scheduler/new-scheduler-task-wizard-button.js'); + + $addTaskUrl = (string)$this->uriBuilder->buildUriFromRoute('ajax_new_scheduler_task_wizard', [ + 'returnUrl' => GeneralUtility::sanitizeLocalUrl($request->getQueryParams()['returnUrl'] ?? '', $request) ?: $request->getAttribute('normalizedParams')->getRequestUri(), + ]); + + $languageService = $this->getLanguageService(); + $newButton = $this->componentFactory->createGenericButton() + ->setTag('typo3-scheduler-new-task-wizard-button') + ->setIcon($this->iconFactory->getIcon('actions-plus', IconSize::SMALL)) + ->setLabel($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add')) + ->setShowLabelText(true) + ->setAttributes([ + 'url' => $addTaskUrl, + 'subject' => $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add'), + ]); + + // Find and replace t3js-editform-new button + // By replacing the existing button we ensure to respect TSconfig and that user has necessary permissions + foreach ($leftButtons as $groupIndex => $buttonGroup) { + foreach ($buttonGroup as $buttonIndex => $button) { + if (method_exists($button, 'getClasses') && str_contains($button->getClasses(), 't3js-editform-new')) { + $leftButtons[$groupIndex][$buttonIndex] = $newButton; + } + } + } + + $buttons['left'] = $leftButtons; + $event->setButtons($buttons); + } + + private function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/Classes/Exception.php b/Classes/Exception.php new file mode 100644 index 0000000..1c8c9ed --- /dev/null +++ b/Classes/Exception.php @@ -0,0 +1,25 @@ +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, + ]; + } +} diff --git a/Classes/FailedExecutionException.php b/Classes/FailedExecutionException.php new file mode 100644 index 0000000..5e52fe0 --- /dev/null +++ b/Classes/FailedExecutionException.php @@ -0,0 +1,21 @@ +getLanguageService(); + $extractors = $this->extractorRegistry->getExtractors(); + + if ($extractors !== []) { + $bullets = []; + foreach ($extractors as $extractor) { + $bullets[] = sprintf( + '
  • %s%s
  • ', + get_class($extractor), + sprintf( + $lang->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.registeredExtractors.extractor'), + $this->formatExtractorClassName($extractor), + $extractor->getPriority() + ), + $this->getBackendUser()->shallDisplayDebugInformation() ? (' [' . get_class($extractor) . ']') : '' + ); + } + $html = ' +
    ' . htmlspecialchars($lang->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.registeredExtractors.with_extractors')) . '
    +
      ' . implode(LF, $bullets) . '
    + '; + } else { + $html = '
    ' . htmlspecialchars($lang->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.registeredExtractors.without_extractors')) . '/div>'; + } + + $resultArray['html'] = ' +
    + + ' . htmlspecialchars($lang->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.registeredExtractors')) . ' + + ' . $html . ' +
    + '; + + return $resultArray; + } + + /** + * Since the class name can be very long considering the namespace, only take the final + * part for better readability. The FQN of the class will be displayed as tooltip. + */ + private function formatExtractorClassName(ExtractorInterface $extractor): string + { + $extractorParts = explode('\\', get_class($extractor)); + return (string)array_pop($extractorParts); + } +} diff --git a/Classes/Form/Element/SchedulableCommandConfigurationElement.php b/Classes/Form/Element/SchedulableCommandConfigurationElement.php new file mode 100644 index 0000000..d8c4beb --- /dev/null +++ b/Classes/Form/Element/SchedulableCommandConfigurationElement.php @@ -0,0 +1,214 @@ +initializeResultArray(); + $selectedTaskType = $this->data['databaseRow']['tasktype'][0] ?? ''; + if ($selectedTaskType === '') { + return $resultArray; + } + $parameterArray = $this->data['parameterArray']; + $itemName = $parameterArray['itemFormElName']; + + try { + $taskObject = $this->taskRepository->findByUid((int)$this->data['databaseRow']['uid']); + } catch (\OutOfBoundsException) { + // This happens for new tasks when 'uid' is set to "0" because we have a Task Type from defVals + try { + $taskObject = $this->taskService->createNewTask($selectedTaskType); + } catch (InvalidTaskException) { + // Given task type is not registered - skip this element + return $resultArray; + } + } + + if ($taskObject instanceof ExecuteSchedulableCommandTask === false) { + // Task is not an executable schedulable command task + return $resultArray; + } + + try { + $command = $this->commandRegistry->get($selectedTaskType); + } catch (CommandNotFoundException) { + // Command not found + return $resultArray; + } + + $argumentFields = $this->getCommandArgumentFields($command->getDefinition(), $taskObject); + $optionFields = $this->getCommandOptionFields($command->getDefinition(), $taskObject); + + if ($argumentFields !== [] || $optionFields !== []) { + + $fieldInformationResult = $this->renderFieldInformation(); + $fieldInformationHtml = $fieldInformationResult['html']; + $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false); + + $html = []; + $html[] = '
    '; + $html[] = $fieldInformationHtml; + $html[] = '
    '; + $html[] = $this->renderCommandConfiguration(array_merge($argumentFields, $optionFields), $selectedTaskType, $itemName); + $html[] = '
    '; + if ($this->data['command'] === 'edit') { + $html[] = $this->getRunOnCliInfo($taskObject, $command); + } + $html[] = '
    '; + + $resultArray['html'] = $this->wrapWithFieldsetAndLegend(implode(LF, $html)); + } + + return $resultArray; + } + + protected function getCommandArgumentFields(InputDefinition $inputDefinition, ExecuteSchedulableCommandTask $task): array + { + $fields = []; + $argumentValues = $task->getArguments(); + foreach ($inputDefinition->getArguments() as $argument) { + $name = $argument->getName(); + $defaultValue = $argument->getDefault(); + $task->addDefaultValue($name, $defaultValue); + $value = $argumentValues[$name] ?? $defaultValue; + + if (is_array($value) && $argument->isArray()) { + $value = implode(',', $value); + } + + $fields['arguments'][$name] = [ + 'label' => 'Argument "' . $argument->getName() . '"', + 'description' => $argument->getDescription(), + 'value' => $value, + 'required' => $argument->isRequired(), + ]; + } + + return $fields; + } + + protected function getCommandOptionFields(InputDefinition $inputDefinition, ExecuteSchedulableCommandTask $task): array + { + $fields = []; + $enabledOptions = $task->getOptions(); + $optionValues = $task->getOptionValues(); + foreach ($inputDefinition->getOptions() as $option) { + $name = $option->getName(); + $defaultValue = $option->getDefault(); + $task->addDefaultValue($name, $defaultValue); + $enabled = $enabledOptions[$name] ?? false; + $value = $optionValues[$name] ?? $defaultValue; + + if (is_array($value) && $option->isArray()) { + $value = implode(',', $value); + } + + $fields['options'][$name] = [ + 'label' => 'Option "' . $option->getName() . '"', + 'description' => $option->getDescription(), + 'enabled' => $enabled, + 'value' => $value, + 'valueOption' => $option->isValueRequired() || $option->isValueOptional() || $option->isArray(), + ]; + } + + return $fields; + } + + protected function getRunOnCliInfo(ExecuteSchedulableCommandTask $taskObject, Command $command): string + { + $options = []; + foreach ($taskObject->getOptions() as $name => $enabled) { + if ($enabled) { + $value = $taskObject->getOptionValues()[$name] ?? null; + $options['--' . $name] = ($value === true) ? '' : $value; + } + } + + $parameters = array_merge($taskObject->getArguments(), $options); + + try { + $input = new ArrayInput($parameters, $command->getDefinition()); + $arguments = $input->__toString(); + $cliCommand = '
    ' . $command->getName() . ' ' . $arguments . '
    '; + } catch (RuntimeException|InvalidArgumentException $e) { + $cliCommand = '
    ' . htmlspecialchars(sprintf($this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.errorParsingArguments'), $e->getMessage())) . '
    '; + } catch (InvalidOptionException $e) { + $cliCommand = '
    ' . htmlspecialchars(sprintf($this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.errorParsingOptions'), $e->getMessage())) . '
    '; + } + + return ' +
    +
    +
    +

    ' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.runOnCli')) . '

    + ' . $cliCommand . ' +
    +
    +
    + '; + } + + protected function renderCommandConfiguration(array $fields, string $taskType, string $itemName): string + { + return $this->viewFactory->create( + new ViewFactoryData( + templateRootPaths: ['EXT:scheduler/Resources/Private/Templates'], + partialRootPaths: ['EXT:scheduler/Resources/Private/Partials'], + layoutRootPaths: ['EXT:scheduler/Resources/Private/Layouts'], + request: $this->data['request'], + format: 'html', + ) + )->assignMultiple([ + 'taskType' => $taskType, + 'fields' => $fields, + 'itemName' => $itemName, + 'renderDebug' => $this->getBackendUser()->shallDisplayDebugInformation(), + ])->render('CommandConfiguration'); + } +} diff --git a/Classes/Form/Element/TaskTypeInfoElement.php b/Classes/Form/Element/TaskTypeInfoElement.php new file mode 100644 index 0000000..a0eee40 --- /dev/null +++ b/Classes/Form/Element/TaskTypeInfoElement.php @@ -0,0 +1,70 @@ +getLanguageService(); + $resultArray = $this->initializeResultArray(); + $parameterArray = $this->data['parameterArray']; + $selectedValue = ''; + if (!empty($parameterArray['itemFormElValue'])) { + if (is_array($parameterArray['itemFormElValue'])) { + $selectedValue = (string)$parameterArray['itemFormElValue'][0]; + } else { + $selectedValue = (string)$parameterArray['itemFormElValue']; + } + } + + $taskDetails = $this->taskService->getTaskDetailsFromTaskType($selectedValue); + if ($taskDetails) { + $resultArray['html'] = ' +
    +
    +
    + ' . $this->iconFactory->getIcon(($taskDetails['icon'] ?? '') ?: 'mimetypes-x-tx_scheduler_task_group')->render() . ' +
    +
    +

    ' . htmlspecialchars($taskDetails['title']) . '

    + ' . htmlspecialchars($taskDetails['description']) . ' +
    +
    +
    + '; + } else { + $resultArray['html'] = '
    ' . htmlspecialchars($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidTaskType')) . ': ' . htmlspecialchars($selectedValue) . '
    '; + } + + $resultArray['html'] .= ''; + return $resultArray; + } +} diff --git a/Classes/Form/Element/TimingOptionsElement.php b/Classes/Form/Element/TimingOptionsElement.php new file mode 100644 index 0000000..31eec01 --- /dev/null +++ b/Classes/Form/Element/TimingOptionsElement.php @@ -0,0 +1,170 @@ +getLanguageService(); + $resultArray = $this->initializeResultArray(); + $parameterArray = $this->data['parameterArray']; + $itemValue = $parameterArray['itemFormElValue']; + $itemName = $parameterArray['itemFormElName']; + + if (is_array($itemValue) && $itemValue !== []) { + $executionDetails = Execution::createFromDetails($itemValue); + } else { + $executionDetails = new Execution(); + // Set the default value to "in 5 minutes" + $executionDetails->setStart($this->context->getPropertyFromAspect('date', 'accessTime') + (5 * 60)); + } + + $fieldsHtml = ''; + + $runningType = GeneralUtility::makeInstance(RadioElement::class); + $runningType->data = $this->data; + $runningType->data['containerFieldName'] = 'runningType'; + $runningType->data['parameterArray']['fieldConf']['label'] = htmlspecialchars($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:runningType')); + $runningType->data['parameterArray']['itemFormElName'] .= '[runningType]'; + $runningType->data['parameterArray']['fieldConf']['config']['items'] = [ + ['label' => $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.type.single'), 'value' => 1], + ['label' => $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.type.recurring'), 'value' => 2], + ]; + $runningType->data['parameterArray']['itemFormElValue'] = $executionDetails->isSingleRun() ? 1 : 2; + $runningType->data['parameterArray']['fieldChangeFunc'] = []; + $runningType->data['parameterArray']['fieldConf'] = array_replace_recursive($runningType->data['parameterArray']['fieldConf'], $parameterArray['fieldConf']['config']['overrideFieldTca']['runningType'] ?? []); + $subFieldResult = $runningType->render(); + $resultArray['javaScriptModules'] = array_merge($resultArray['javaScriptModules'], $subFieldResult['javaScriptModules']); + $fieldsHtml .= '
    ' . str_replace('"form-check"', '"form-check form-inline me-2"', $subFieldResult['html']) . '
    '; + + $multiple = GeneralUtility::makeInstance(CheckboxElement::class); + $multiple->data = $this->data; + $multiple->data['containerFieldName'] = 'multiple'; + $multiple->data['parameterArray']['fieldConf']['label'] = htmlspecialchars($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.parallel.long')); + $multiple->data['parameterArray']['itemFormElName'] .= '[multiple]'; + $multiple->data['parameterArray']['fieldConf']['config']['items'] = []; + $multiple->data['parameterArray']['fieldChangeFunc'] = []; + $multiple->data['parameterArray']['itemFormElValue'] = $executionDetails->isParallelExecutionAllowed(); + $multiple->data['parameterArray']['fieldConf'] = array_replace_recursive($multiple->data['parameterArray']['fieldConf'], $parameterArray['fieldConf']['config']['overrideFieldTca']['multiple'] ?? []); + $subFieldResult = $multiple->render(); + $resultArray['javaScriptModules'] = array_merge($resultArray['javaScriptModules'], $subFieldResult['javaScriptModules']); + $fieldsHtml .= '
    ' . $subFieldResult['html'] . '
    '; + + $start = GeneralUtility::makeInstance(DatetimeElement::class); + $start->data = $this->data; + $start->data['containerFieldName'] = 'start'; + $start->data['parameterArray']['fieldConf']['label'] = htmlspecialchars($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:scheduledFrom')); + $start->data['parameterArray']['itemFormElName'] .= '[start]'; + $start->data['parameterArray']['itemFormElValue'] = DateTimeFactory::createFromTimestamp($executionDetails->getStart() ?: $this->context->getPropertyFromAspect('date', 'timestamp')); + $start->data['parameterArray']['fieldConf'] = array_replace_recursive($start->data['parameterArray']['fieldConf'], $parameterArray['fieldConf']['config']['overrideFieldTca']['start'] ?? []); + $subFieldResult = $start->render(); + $resultArray['javaScriptModules'] = array_merge($resultArray['javaScriptModules'], $subFieldResult['javaScriptModules']); + $fieldsHtml .= '
    ' . $subFieldResult['html'] . '
    '; + + $end = GeneralUtility::makeInstance(DatetimeElement::class); + $end->data = $this->data; + $end->data['containerFieldName'] = 'end'; + $end->data['parameterArray']['fieldConf']['label'] = htmlspecialchars($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:scheduledUntil')); + $end->data['parameterArray']['itemFormElName'] .= '[end]'; + $end->data['parameterArray']['itemFormElValue'] = $executionDetails->getEnd() ? DateTimeFactory::createFromTimestamp($executionDetails->getEnd()) : null; + $end->data['parameterArray']['fieldConf'] = array_replace_recursive($end->data['parameterArray']['fieldConf'], $parameterArray['fieldConf']['config']['overrideFieldTca']['end'] ?? []); + $subFieldResult = $end->render(); + $resultArray['javaScriptModules'] = array_merge($resultArray['javaScriptModules'], $subFieldResult['javaScriptModules']); + $fieldsHtml .= '
    ' . $subFieldResult['html'] . '
    '; + + $frequency = GeneralUtility::makeInstance(InputTextElement::class); + $frequency->data = $this->data; + $frequency->data['containerFieldName'] = 'frequency'; + $frequency->data['parameterArray']['fieldConf']['label'] = htmlspecialchars($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.frequency.long')); + $frequency->data['parameterArray']['itemFormElName'] .= '[frequency]'; + $frequency->data['parameterArray']['itemFormElValue'] = $executionDetails->getCronCmd() ?: $executionDetails->getInterval(); + $frequency->data['parameterArray']['fieldChangeFunc'] = []; + $frequency->data['parameterArray']['fieldConf']['config']['size'] = 40; + $frequency->data['parameterArray']['fieldConf'] = array_replace_recursive($frequency->data['parameterArray']['fieldConf'], $parameterArray['fieldConf']['config']['overrideFieldTca']['frequency'] ?? []); + $subFieldResult = $frequency->render(); + $resultArray['javaScriptModules'] = array_merge($resultArray['javaScriptModules'], $subFieldResult['javaScriptModules']); + $fieldsHtml .= '
    ' . $subFieldResult['html'] . '
    '; + + $fieldInformationResult = $this->renderFieldInformation(); + $fieldInformationHtml = $fieldInformationResult['html']; + $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false); + + $html = []; + $html[] = ''; + $html[] = $fieldInformationHtml; + $html[] = '
    '; + $html[] = '
    '; + $html[] = '
    '; + $html[] = '
    ' . $fieldsHtml . $this->renderServerTime() . '
    '; + $html[] = '
    '; + $html[] = '
    '; + $html[] = '
    '; + $html[] = '
    '; + + $resultArray['html'] = $this->wrapWithFieldsetAndLegend(implode(LF, $html)); + $resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@typo3/scheduler/form-engine/element/timing-options-element.js'); + + return $resultArray; + } + + protected function renderServerTime(): string + { + $view = $this->viewFactory->create( + new ViewFactoryData( + templateRootPaths: ['EXT:scheduler/Resources/Private/Templates'], + partialRootPaths: ['EXT:scheduler/Resources/Private/Partials'], + layoutRootPaths: ['EXT:scheduler/Resources/Private/Layouts'], + request: $this->data['request'], + format: 'html', + ) + ); + $view->assignMultiple([ + 'dateFormat' => [ + 'day' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?? 'd-m-y', + 'time' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] ?? 'H:i', + ], + ]); + return $view->render('ServerTime'); + } +} diff --git a/Classes/Form/FieldInformation/ExpirePeriodInformation.php b/Classes/Form/FieldInformation/ExpirePeriodInformation.php new file mode 100644 index 0000000..e851b70 --- /dev/null +++ b/Classes/Form/FieldInformation/ExpirePeriodInformation.php @@ -0,0 +1,66 @@ +initializeResultArray(); + + if ($this->data['command'] !== 'edit' + || $this->data['tableName'] !== 'tx_scheduler_task' + || (int)($this->data['parameterArray']['itemFormElValue'] ?? 0) > 0 + ) { + return $resultArray; + } + + $refField = (string)($this->data['renderData']['fieldInformationOptions']['refField'] ?? ''); + if (($this->data['databaseRow'][$refField] ?? false) === false) { + return $resultArray; + } + + $selectedTable = (string)(is_array($this->data['databaseRow'][$refField]) ? $this->data['databaseRow'][$refField][0] : $this->data['databaseRow'][$refField]); + $tableConfiguration = GeneralUtility::makeInstance(TableGarbageCollectionTask::class)->getTableConfiguration()[$selectedTable] ?? []; + if (!isset($tableConfiguration['expirePeriod'])) { + return $resultArray; + } + + $resultArray['html'] = ' +
    + ' . sprintf($this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.defaultExpirePeriod'), (int)$tableConfiguration['expirePeriod'], $selectedTable) . ' +
    '; + + return $resultArray; + } + + protected function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/Classes/Hooks/SchedulerTaskPersistenceValidator.php b/Classes/Hooks/SchedulerTaskPersistenceValidator.php new file mode 100644 index 0000000..7618957 --- /dev/null +++ b/Classes/Hooks/SchedulerTaskPersistenceValidator.php @@ -0,0 +1,280 @@ +flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier(); + } + + /** + * If the task is not valid, this hook will create an error log message AND make the incomingFieldArray + * a non-array (e.g. false) to skip saving this record. + */ + public function processDatamap_preProcessFieldArray(&$incomingFieldArray, $table, $id, DataHandler $dataHandler): void + { + if ($table !== 'tx_scheduler_task') { + return; + } + $isNewTask = false; + if (MathUtility::canBeInterpretedAsInteger($id)) { + // Only execution is updated, do not validate anything else + if (count($incomingFieldArray) === 3 && isset($incomingFieldArray['nextexecution'], $incomingFieldArray['disable'], $incomingFieldArray['execution_details'])) { + return; + } + + // Update process + $fullRecord = BackendUtility::getRecord($table, $id); + $changedTaskType = ($incomingFieldArray['tasktype'] ?? false) !== ($fullRecord['tasktype'] ?? false); + if (!isset($incomingFieldArray['tasktype'])) { + $taskType = $fullRecord['tasktype']; + } else { + $taskType = $incomingFieldArray['tasktype']; + } + if (!empty($fullRecord['serialized_executions'])) { + // If there's a registered execution, the task should not be edited. May happen if a cron started the task meanwhile. + $this->addErrorMessage($dataHandler, $id, 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.maynotEditRunningTask'); + } + $task = $this->taskRepository->findByUid((int)$id); + } else { + $isNewTask = true; + $changedTaskType = true; + $taskType = $incomingFieldArray['tasktype']; + try { + $task = $this->taskService->createNewTask($taskType); + } catch (InvalidTaskException $e) { + // Task can not be further processed since task type is not valid + $dataHandler->log('tx_scheduler_task', $id, 1, null, SystemLogErrorClassification::WARNING, 'Task can not be further processed since task type ' . $taskType . ' is not valid'); + $incomingFieldArray = false; + return; + } + } + $decodedAndExtractedFieldArray = $this->decodeValues($incomingFieldArray); + if (!$this->isSubmittedTaskDataValid($dataHandler, $id, $decodedAndExtractedFieldArray, $task)) { + // Custom AdditionalFieldProvider may have added error messages via the FlashMessageQueue (as recommended) + // which is needed to render them properly in FormEngine via $dataHandler->printLogErrorMessages(); + $this->convertErrorMessagesToDataHandlerLog($dataHandler, $id); + // Setting this to a "non-array" will skip further persistence chain + $incomingFieldArray = false; + return; + } + // Now let's transform our data + $this->setTaskDataFromRequest($task, $decodedAndExtractedFieldArray); + $incomingFieldArray = array_replace_recursive($incomingFieldArray, $this->taskService->getFieldsForRecord($task)); + if ($isNewTask) { + $incomingFieldArray['parameters'] = $incomingFieldArray['parameters'] ?? []; + $incomingFieldArray['pid'] = 0; + } elseif ($changedTaskType) { + $incomingFieldArray['parameters'] = []; + $incomingFieldArray['tasktype'] = $taskType; + } + } + + private function convertErrorMessagesToDataHandlerLog(DataHandler $dataHandler, string|int $taskId): void + { + $messages = $this->flashMessageQueue->getAllMessagesAndFlush(); + foreach ($messages as $message) { + $messageError = match ($message->getSeverity()) { + ContextualFeedbackSeverity::WARNING => SystemLogErrorClassification::WARNING, + ContextualFeedbackSeverity::OK => SystemLogErrorClassification::MESSAGE, + default => SystemLogErrorClassification::USER_ERROR, + }; + $dataHandler->log( + 'tx_scheduler_task', + $taskId, + MathUtility::canBeInterpretedAsInteger($taskId) ? 2 : 1, + null, + $messageError, + $message->getMessage() + ); + } + } + + private function addErrorMessage(DataHandler $dataHandler, string|int $taskId, string $message, ...$args): void + { + $languageService = $this->getLanguageService(); + $message = $languageService->sL($message); + $dataHandler->log( + 'tx_scheduler_task', + $taskId, + MathUtility::canBeInterpretedAsInteger($taskId) ? 2 : 1, + null, + SystemLogErrorClassification::USER_ERROR, + $message, + null, + $args, + 0 + ); + } + + private function isSubmittedTaskDataValid(DataHandler $dataHandler, string|int $taskId, array $parsedBody, AbstractTask $task): bool + { + $startTime = $parsedBody['start'] ?? 0; + $endTime = $parsedBody['end'] ?? 0; + $frequency = $parsedBody['frequency'] ?? $parsedBody['cronCmd'] ?? ''; + $runningType = (int)($parsedBody['runningType'] ?? ($frequency ? AbstractTask::TYPE_RECURRING : AbstractTask::TYPE_SINGLE)); + $result = true; + if ($runningType !== AbstractTask::TYPE_SINGLE && $runningType !== AbstractTask::TYPE_RECURRING) { + $result = false; + $this->addErrorMessage($dataHandler, $taskId, 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidRunningType'); + } + if (empty($startTime)) { + $result = false; + $this->addErrorMessage($dataHandler, $taskId, 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.noStartDate'); + } else { + try { + $startTime = $this->getTimestampFromDateString($startTime); + } catch (InvalidDateException) { + $result = false; + $this->addErrorMessage($dataHandler, $taskId, 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidStartDate'); + } + } + if ($runningType === AbstractTask::TYPE_RECURRING && !empty($endTime)) { + try { + $endTime = $this->getTimestampFromDateString($endTime); + } catch (InvalidDateException) { + $result = false; + $this->addErrorMessage($dataHandler, $taskId, 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidStartDate'); + } + } + if ($runningType === AbstractTask::TYPE_RECURRING && $endTime > 0 && $endTime < $startTime) { + $result = false; + $this->addErrorMessage($dataHandler, $taskId, 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.endDateSmallerThanStartDate'); + } + if ($runningType === AbstractTask::TYPE_RECURRING) { + if (empty(trim($frequency))) { + $result = false; + $this->addErrorMessage($dataHandler, $taskId, 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.noFrequency'); + } elseif (!is_numeric(trim($frequency))) { + try { + NormalizeCommand::normalize(trim($frequency)); + } catch (\InvalidArgumentException $e) { + $result = false; + $this->addErrorMessage($dataHandler, $taskId, 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.frequencyError', $e->getMessage(), $e->getCode()); + } + } + } + return $result && (!method_exists($task, 'validateTaskParameters') || $task->validateTaskParameters($parsedBody)); + } + + /** + * Convert input to DateTime and retrieve timestamp. + * + * @throws InvalidDateException + */ + private function getTimestampFromDateString(int|string $input): int + { + if ($input === '' || $input === 0) { + return 0; + } + if (MathUtility::canBeInterpretedAsInteger($input)) { + // Already looks like a timestamp + return (int)$input; + } + try { + // Convert from ISO 8601 dates + $value = (new \DateTime($input))->getTimestamp(); + } catch (\Exception $e) { + throw new InvalidDateException($e->getMessage(), 1747813335); + } + return $value; + } + + private function decodeValues(array $fieldArray): array + { + foreach (['execution_details', 'parameters'] as $possibleEncodedValueKey) { + $value = $fieldArray[$possibleEncodedValueKey] ?? []; + if (is_string($value) && $value !== '') { + try { + $value = json_decode($value, true, 512, JSON_THROW_ON_ERROR); + $fieldArray[$possibleEncodedValueKey] = $value; + } catch (\JsonException) { + // Skip failed decoding + } + } + if (is_array($value) && $value !== []) { + // Extract "values" so additional field providers can directly + // access the values without going via the json field. + $fieldArray = array_merge($value, $fieldArray); + } + } + + return $fieldArray; + } + + private function setTaskDataFromRequest(AbstractTask $task, array $incomingData): void + { + $endTime = $incomingData['end'] ?? ''; + $frequency = $incomingData['frequency'] ?? $incomingData['cronCmd'] ?? ''; + $runningType = (int)($incomingData['runningType'] ?? ($frequency ? AbstractTask::TYPE_RECURRING : AbstractTask::TYPE_SINGLE)); + if ($runningType === AbstractTask::TYPE_SINGLE) { + $execution = Execution::createSingleExecution($this->getTimestampFromDateString($incomingData['start'])); + } else { + $execution = Execution::createRecurringExecution( + $this->getTimestampFromDateString($incomingData['start']), + is_numeric($frequency) ? (int)$frequency : 0, + !empty($endTime) ? $this->getTimestampFromDateString($endTime) : 0, + (bool)($incomingData['multiple'] ?? false), + !is_numeric($frequency) ? $frequency : '', + ); + } + $task->setExecution($execution); + $task->setDisabled($incomingData['disable'] ?? false); + $task->setDescription($incomingData['description'] ?? ''); + if (str_starts_with((string)($incomingData['task_group'] ?? ''), 'tx_scheduler_task_group_')) { + $incomingData['task_group'] = (int)substr($incomingData['task_group'], 24); + } + $task->setTaskGroup((int)($incomingData['task_group'] ?? 0)); + } + + private function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/Classes/Migration/SchedulerDatabaseStorageMigration.php b/Classes/Migration/SchedulerDatabaseStorageMigration.php new file mode 100644 index 0000000..37c8d64 --- /dev/null +++ b/Classes/Migration/SchedulerDatabaseStorageMigration.php @@ -0,0 +1,235 @@ +hasRecordsToUpdate(); + } + + public function executeUpdate(): bool + { + $connection = $this->getConnectionPool()->getConnectionForTable(self::TABLE_NAME); + $table = $this->getConnectionPool()->getConnectionForTable(self::TABLE_NAME)->createSchemaManager()->introspectSchema()->getTable(self::TABLE_NAME); + $taskSerializer = GeneralUtility::makeInstance(TaskSerializer::class); + $taskService = GeneralUtility::makeInstance(TaskService::class); + $hasFailures = false; + foreach ($this->getRecordsToUpdate() as $record) { + try { + // Base migration was already done, but not the migration to additional fields, so we'll do this now + if (!empty($record['tasktype'])) { + $taskObject = $taskSerializer->deserialize($record); + } else { + // unserialize() will only give a E_NOTICE and false result, not throw an error. Silence this + // (for tests) and operate on the "false". If future PHP promotes this to an exception, the Throwable + // catch will kick in. + $taskObject = $this->deserializer->deserialize($record['serialized_task_object']); + } + if ($taskObject instanceof AbstractTask) { + $fieldsToUpdate = [ + 'tasktype' => $taskObject->getTaskType(), + 'execution_details' => $taskObject->getExecution()?->toArray(), + ]; + $taskDetails = $taskService->getTaskDetailsFromTask($taskObject); + $taskParameters = $taskObject->getTaskParameters(); + if (($taskDetails['isNativeTask'] ?? false) && $taskDetails['className'] !== ExecuteSchedulableCommandTask::class) { + // map native types to real fields, and do not use the parameters' value. Only + // exception to this are console commands, which are native types but use the + // parameters as well, because they have dynamic configuration (arguments, options). + if (is_array($taskDetails['additionalFields'] ?? false) && $taskDetails['additionalFields'] !== []) { + foreach ($taskDetails['additionalFields'] as $additionalFieldName) { + $fieldsToUpdate[$additionalFieldName] = $taskParameters[$additionalFieldName] ?? null; + } + } + $fieldsToUpdate['parameters'] = null; + } else { + $fieldsToUpdate['parameters'] = $taskParameters; + } + $connection->update( + self::TABLE_NAME, + array_filter($fieldsToUpdate, static fn($column) => $table->hasColumn($column), ARRAY_FILTER_USE_KEY), + ['uid' => (int)$record['uid']] + ); + } elseif ($taskObject instanceof \__PHP_Incomplete_Class) { + $objectVars = get_mangled_object_vars($taskObject); + $properties = []; + $executionDetails = null; + $taskType = null; + foreach ($objectVars as $key => $value) { + $key = trim($key); + $key = trim($key, "*\0"); + $key = trim($key); + if ($key === '__PHP_Incomplete_Class_Name') { + $taskType = $value; + } else { + switch ($key) { + case '__PHP_Incomplete_Class_Name': + $taskType = $value; + break; + case 'execution': + $executionDetails = $value; + break; + case 'progress': + case 'scheduler': + case 'taskUid': + case 'disabled': + case 'runOnNextCronJob': // mapped to "task_group" in the database + case 'executionTime': // mapped to "next_execution" in the database + case 'taskGroup': // mapped to "task_group" in the database + case 'description': + break; + default: + if (is_scalar($value) || is_null($value)) { + $properties[$key] = $value; + } + } + } + } + $connection->update( + self::TABLE_NAME, + [ + 'tasktype' => $taskType, + 'parameters' => $properties, + 'execution_details' => $executionDetails?->toArray(), + ], + ['uid' => (int)$record['uid']] + ); + } else { + // This happens if unserialize() failed (gracefully). + // Wizard shall not be marked as completed and show up again to let people know. + $hasFailures = true; + } + } catch (\Throwable) { + // Mark wizard as failed so the upgrade wizard will show up again, and people know there is a problem. + $hasFailures = true; + } + } + + return !$hasFailures; + } + + protected function hasRecordsToUpdate(): bool + { + // Check if table exists + if (!$this->getConnectionPool()->getConnectionForTable(self::TABLE_NAME)->createSchemaManager()->tableExists(self::TABLE_NAME)) { + return false; + } + return (bool)$this->getPreparedQueryBuilder()->count('uid')->executeQuery()->fetchOne(); + } + + protected function getRecordsToUpdate(): array + { + return $this->getPreparedQueryBuilder()->select('*')->executeQuery()->fetchAllAssociative(); + } + + protected function getPreparedQueryBuilder(): QueryBuilder + { + $nativeTaskTypesWithAdditionalFields = $this->getAllNativeTaskTypesWithAdditionalFields(); + + $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE_NAME); + // This is done by intention, so the upgrade wizard continues to work even if we introduce further TCA details for tx_scheduler_task + $queryBuilder->getRestrictions()->removeAll(); + $queryBuilder + ->from(self::TABLE_NAME) + ->where( + $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, ParameterType::INTEGER)), + $queryBuilder->expr()->or( + // Find all where the task type is empty (legacy serialized storage) + // OR where we have a native task type, that contains additional fields we can migrate + $queryBuilder->expr()->or( + $queryBuilder->expr()->eq( + 'tasktype', + $queryBuilder->createNamedParameter('') + ), + $queryBuilder->expr()->isNull('tasktype') + ), + $queryBuilder->expr()->and( + $queryBuilder->expr()->in( + 'tasktype', + $queryBuilder->createNamedParameter(array_keys($nativeTaskTypesWithAdditionalFields), ArrayParameterType::STRING) + ), + $queryBuilder->expr()->isNotNull('parameters'), + ) + ) + ); + + return $queryBuilder; + } + + protected function getAllNativeTaskTypesWithAdditionalFields(): array + { + $taskService = GeneralUtility::makeInstance(TaskService::class); + $allTaskInformation = $taskService->getAllTaskTypes(); + $nativeTaskTypesWithAdditionalFields = []; + foreach ($allTaskInformation as $taskType => $taskInformation) { + if (($taskInformation['isNativeTask'] ?? false) && $taskInformation['className'] !== ExecuteSchedulableCommandTask::class) { + // Native tasks can define "additionalFields". However, console commands, which are + // native tasks as well, do not define real fields but use the "parameters" feature. + $nativeTaskTypesWithAdditionalFields[$taskType] = $taskInformation['additionalFields'] ?? []; + } + } + return $nativeTaskTypesWithAdditionalFields; + } + + protected function getConnectionPool(): ConnectionPool + { + return GeneralUtility::makeInstance(ConnectionPool::class); + } +} diff --git a/Classes/ProgressProviderInterface.php b/Classes/ProgressProviderInterface.php new file mode 100644 index 0000000..d8d6ea4 --- /dev/null +++ b/Classes/ProgressProviderInterface.php @@ -0,0 +1,29 @@ +extConf = $extensionConfiguration->get('scheduler'); + if (empty($this->extConf['maxLifetime'])) { + $this->extConf['maxLifetime'] = 1440; + } + // Clean up the serialized execution arrays + $this->cleanExecutionArrays(); + } + + /** + * Cleans the execution lists of the scheduled tasks, executions older than 24h are removed + * @todo find a way to actually kill the job + */ + protected function cleanExecutionArrays() + { + $tstamp = $GLOBALS['EXEC_TIME']; + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_scheduler_task'); + + // Select all tasks with executions + // NOTE: this cleanup is done for disabled tasks too, + // to avoid leaving old executions lying around + $result = $queryBuilder->select('*') + ->from('tx_scheduler_task') + ->where( + $queryBuilder->expr()->neq( + 'serialized_executions', + $queryBuilder->createNamedParameter('') + ), + $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)) + ) + ->executeQuery(); + $maxDuration = $this->extConf['maxLifetime'] * 60; + while ($row = $result->fetchAssociative()) { + $executions = []; + // serialized in \TYPO3\CMS\Scheduler\Domain\Repository\SchedulerTaskRepository::addExecutionToTask as `array` + if ($serialized_executions = unserialize($row['serialized_executions'], ['allowed_classes' => false])) { + foreach ($serialized_executions as $task) { + if ($tstamp - $task < $maxDuration) { + $executions[] = $task; + } else { + try { + $schedulerTask = $this->taskSerializer->deserialize($row); + $taskType = $schedulerTask->getTaskType(); + $executionTime = date('Y-m-d H:i:s', $schedulerTask->getExecutionTime()); + } catch (InvalidTaskException $e) { + $taskType = 'unknown type'; + $executionTime = 'unknown time'; + } + $this->logger->info( + 'Removing logged execution, assuming that the process is dead. Execution of \'{taskType} \' (UID: {taskId}) was started at {executionTime}', + [ + 'taskType' => $taskType, + 'taskId' => $row['uid'], + 'executionTime' => $executionTime, + ] + ); + } + } + } + $executionCount = count($executions); + if (!is_array($serialized_executions) || count($serialized_executions) !== $executionCount) { + if ($executionCount === 0) { + $value = ''; + } else { + $value = serialize($executions); + } + $this->connectionPool->getConnectionForTable('tx_scheduler_task')->update( + 'tx_scheduler_task', + ['serialized_executions' => $value], + ['uid' => (int)$row['uid']], + ['serialized_executions' => Connection::PARAM_LOB] + ); + } + } + } + + /** + * This method executes the given task and properly marks and records that execution + * It is expected to return FALSE if the task was barred from running or if it was not saved properly + * + * @param Task\AbstractTask $task The task to execute + * @return bool Whether the task was saved successfully to the database or not + * @throws \Throwable + */ + public function executeTask(AbstractTask $task): bool + { + $task->setRunOnNextCronJob(false); + // Trigger the saving of the task, as this will calculate its next execution time + // This should be calculated all the time, even if the execution is skipped + // (in case it is skipped, this pushes back execution to the next possible date) + $this->schedulerTaskRepository->updateExecution($task, $task->getExecution()->isSingleRun()); + + // Reserve an id for the upcoming execution + $executionID = $this->schedulerTaskRepository->addExecutionToTask($task); + // Make sure we're the only one executing a single-execution-only task + if (!$task->getExecution()?->isParallelExecutionAllowed() && $executionID > 0) { + $this->schedulerTaskRepository->removeExecutionOfTask($task, $executionID); + $this->logger->info('Task is already running and multiple executions are not allowed, skipping! Task Type: {taskType}, UID: {uid}', [ + 'taskType' => $task->getTaskType(), + 'uid' => $task->getTaskUid(), + ]); + return false; + } + + // Log scheduler invocation + $this->logger->info('Start execution. Task Type: {taskType}, UID: {uid}', [ + 'taskType' => $task->getTaskType(), + 'uid' => $task->getTaskUid(), + ]); + + $failureString = ''; + $success = false; + $e = null; + try { + // Execute task + $successfullyExecuted = $task->execute(); + if (!$successfullyExecuted) { + throw new FailedExecutionException('Task failed to execute successfully. Task Type: ' . $task->getTaskType() . ', UID: ' . $task->getTaskUid(), 1250596541); + } + $success = true; + return true; + } catch (\Throwable $e) { + // Log failed execution + $this->logger->error('Task failed to execute successfully. Task Type: {taskType}, UID: {taskId}, Code: {code}, "{message}" in {exceptionFile} at line {exceptionLine}', [ + 'taskType' => $task->getTaskType(), + 'taskId' => $task->getTaskUid(), + 'exception' => $e, + 'exceptionFile' => $e->getFile(), + 'exceptionLine' => $e->getLine(), + 'code' => $e->getCode(), + 'message' => $e->getMessage(), + ]); + // Store exception, so that it can be saved to database + // Do not serialize the complete exception or the trace, this can lead to huge strings > 50MB + $failureString = serialize([ + 'code' => $e->getCode(), + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'traceString' => $e->getTraceAsString(), + ]); + // Now that the result of the task execution has been handled, + // throw the exception again, if any + throw $e; + } finally { + // Un-register execution + $this->schedulerTaskRepository->removeExecutionOfTask($task, $executionID, $failureString); + // Log completion of execution + $this->logger->info('Task executed. Task Type: {taskType}, UID: {uid}', [ + 'taskType' => $task->getTaskType(), + 'uid' => $task->getTaskUid(), + ]); + $this->eventDispatcher->dispatch( + new AfterTaskExecutionEvent($task, $success, $e) + ); + } + } + + /** + * This method stores information about the last run of the Scheduler into the system registry + * + * @param string $type Type of run (manual or command-line (assumed to be cron)) + */ + public function recordLastRun($type = 'cron') + { + // Validate input value + if ($type !== 'manual' && $type !== 'cli-by-id') { + $type = 'cron'; + } + $runInformation = ['start' => $GLOBALS['EXEC_TIME'], 'end' => time(), 'type' => $type]; + $this->registry->set('tx_scheduler', 'lastRun', $runInformation); + } +} diff --git a/Classes/Service/TaskService.php b/Classes/Service/TaskService.php new file mode 100644 index 0000000..5cf550f --- /dev/null +++ b/Classes/Service/TaskService.php @@ -0,0 +1,274 @@ + Name of the task PHP class + * ['extension'] => Key of the extension which provides the class + * ['filename'] => Path to the file containing the class + * ['title'] => String (possibly localized) containing a human-readable name for the class + * + * The name of the class itself is used as the key of the list array + */ + protected function getAvailableTaskTypes(bool $includeNativeTypes = true): array + { + $languageService = $this->getLanguageService(); + $list = []; + // @deprecated will be removed in v16: SC_OPTIONS-based scheduler task registration + // is intentionally still read here in v15 to keep legacy (non-native) + // tasks listed and migratable. The per-task ['options']['tables'] fallbacks + // in IpAnonymizationTask and TableGarbageCollectionTask are kept for the + // same reason. Remove all of this together. + foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'] ?? [] as $className => $registrationInformation) { + $title = isset($registrationInformation['title']) ? ($languageService?->sL($registrationInformation['title']) ?? $registrationInformation['title']) : ''; + $description = isset($registrationInformation['description']) ? ($languageService?->sL($registrationInformation['description']) ?? $registrationInformation['description']) : ''; + $list[$className] = [ + 'className' => $className, + 'extension' => $registrationInformation['extension'] ?? '', + 'icon' => $registrationInformation['icon'] ?? '', + 'title' => $title, + 'description' => $description, + 'isNativeTask' => false, + 'additionalFields' => [], + ]; + } + if ($includeNativeTypes) { + $schema = $this->tcaSchemaFactory->get('tx_scheduler_task'); + $defaultFields = ['tasktype', 'task_group', 'description', 'parameters', 'execution_details', 'nextexecution', 'lastexecution_context', 'lastexecution_time', 'lastexecution_failure', 'disable', 'priority']; + // Loop over TCA items, and check if the task type is registered via TCA + foreach ($schema->getField('tasktype')->getConfiguration()['items'] ?? [] as $item) { + if (is_array($item) && $item['value'] !== 'div') { + $taskType = $className = $item['value']; + + $additionalFields = []; + if ($schema->hasSubSchema($taskType)) { + $subSchema = $schema->getSubSchema($taskType); + if ($subSchema->getRawConfiguration()['taskOptions']['className'] ?? false) { + $className = $subSchema->getRawConfiguration()['taskOptions']['className']; + } + $additionalFields = $subSchema->getFields(fn($field) => !in_array($field->getName(), $defaultFields, true) && $field instanceof NoneFieldType === false); + $additionalFields = $additionalFields->getNames(); + } + + $list[$taskType] = [ + 'taskType' => $taskType, + 'className' => $className, + 'extension' => $item['group'] ?? '', + 'icon' => $item['icon'] ?? '', + 'iconOverlay' => $item['iconOverlay'] ?? '', + 'title' => $languageService?->sL($item['label'] ?? '') ?? $item['label'] ?? '', + 'description' => $languageService?->sL($item['description'] ?? '') ?? $item['description'] ?? '', + 'isNativeTask' => true, + 'additionalFields' => $additionalFields, + ]; + } + } + } + return $list; + } + + public function getAllTaskTypes(bool $includeNativeTypes = true): array + { + $taskTypes = []; + foreach ($this->getAvailableTaskTypes($includeNativeTypes) as $taskType => $registrationInformation) { + $taskTypes[$taskType] = [ + 'className' => $registrationInformation['className'], + 'taskType' => $taskType, + 'category' => $registrationInformation['extension'], + 'icon' => $registrationInformation['icon'], + // @todo Remove null coalescing once definition via $GLOBALS['TYPO3_CONF_VARS'] is removed + 'iconOverlay' => $registrationInformation['iconOverlay'] ?? '', + 'title' => $registrationInformation['title'], + 'fullTitle' => $registrationInformation['title'] . ' [' . $registrationInformation['extension'] . ']', + 'description' => $registrationInformation['description'], + 'isNativeTask' => $registrationInformation['isNativeTask'], + 'additionalFields' => $registrationInformation['additionalFields'], + ]; + } + ksort($taskTypes); + return $taskTypes; + } + + public function getCategorizedTaskTypes(): array + { + $categorizedTaskTypes = []; + foreach ($this->getAllTaskTypes() as $taskType => $taskInformation) { + $categorizedTaskTypes[$taskInformation['category']][$taskType] = $taskInformation; + } + ksort($categorizedTaskTypes); + return $categorizedTaskTypes; + } + + public function getTaskDetailsFromTask(AbstractTask $taskObject): ?array + { + $allTaskTypes = $this->getAllTaskTypes(); + if (isset($allTaskTypes[$taskObject->getTaskType()])) { + return $allTaskTypes[$taskObject->getTaskType()]; + } + if (isset($allTaskTypes[get_class($taskObject)])) { + return $allTaskTypes[get_class($taskObject)]; + } + foreach ($allTaskTypes as $taskInformation) { + if ($taskInformation['className'] === get_class($taskObject)) { + return $taskInformation; + } + } + return null; + } + + public function getTaskDetailsFromTaskType(string $taskType): ?array + { + $allTaskTypes = $this->getAllTaskTypes(); + if (isset($allTaskTypes[$taskType])) { + return $allTaskTypes[$taskType]; + } + foreach ($allTaskTypes as $taskInformation) { + if ($taskInformation['className'] === $taskType) { + return $taskInformation; + } + } + return null; + } + + public function isTaskTypeRegistered(string $taskType): bool + { + $allTaskTypes = $this->getAllTaskTypes(); + if (isset($allTaskTypes[$taskType])) { + return true; + } + foreach ($allTaskTypes as $taskInformation) { + if ($taskInformation['className'] === $taskType) { + return true; + } + } + + return false; + } + + /** + * Native fields are added / managed via FormEngine + dataHandler, + * so this only returns additional fields from the task object that are needed. + */ + public function getFieldsForRecord(AbstractTask $task): array + { + try { + if ($task->getRunOnNextCronJob()) { + $executionTime = time(); + } else { + $executionTime = $task->getExecution()->getNextExecution(); + } + $task->setExecutionTime($executionTime); + } catch (\Exception) { + $task->setDisabled(true); + $executionTime = 0; + } + $fields = [ + 'nextexecution' => $executionTime, + 'disable' => (int)$task->isDisabled(), + 'description' => $task->getDescription(), + 'task_group' => $task->getTaskGroup(), + 'tasktype' => $task->getTaskType(), + 'execution_details' => $task->getExecution()->toArray(), + ]; + $taskDetails = $this->getTaskDetailsFromTask($task); + // Put the parameters in a separate field + if (!($taskDetails['isNativeTask'] ?? false)) { + $fields['parameters'] = $task->getTaskParameters(); + } + return $fields; + } + + public function createNewTask(string $taskType): AbstractTask + { + if (!$this->isTaskTypeRegistered($taskType)) { + throw new InvalidTaskException('Can not create task for unknown type ' . $taskType . '.', 1758885935); + } + /** @var AbstractTask $task */ + $task = GeneralUtility::makeInstance($this->getTaskDetailsFromTaskType($taskType)['className']); + if ($task instanceof ExecuteSchedulableCommandTask) { + $task->setTaskType($taskType); + } + return $task; + } + + public function getHumanReadableTaskName(AbstractTask $task): string + { + if (!$this->isTaskTypeRegistered($task->getTaskType())) { + throw new \RuntimeException('Task Type ' . $task->getTaskType() . ' not found in list of registered tasks', 1641658569); + } + return $this->getAllTaskTypes()[$task->getTaskType()]['fullTitle']; + } + + /** + * Used in FormEngine. Actually, this is only needed to task types can be "validated" by form data providers. + * There is no possibility to "select" a task type in FormEngine. The field is a readonly information. + */ + public function getTaskTypesForTcaItems(array &$config, mixed $_ = null, bool $includeNativeItems = false): array + { + $taskTypes = $this->getAllTaskTypes($includeNativeItems); + foreach ($taskTypes as $taskType => $taskInformation) { + $config['items'][] = new SelectItem( + type: 'select', + label: $taskInformation['fullTitle'], + value: $taskType, + group: $taskInformation['category'], + description: $taskInformation['description'], + ); + } + // Sort all items by group, and groups as well + usort($config['items'], static function (SelectItem $a, SelectItem $b): int { + $groupComparison = strnatcasecmp($a->getGroup(), $b->getGroup()); + if ($groupComparison !== 0) { + return $groupComparison; + } + return strnatcasecmp($a->getLabel(), $b->getLabel()); + }); + return $config; + } + + private function getLanguageService(): ?LanguageService + { + return $GLOBALS['LANG'] ?? null; + } +} diff --git a/Classes/SystemInformation/ToolbarItemProvider.php b/Classes/SystemInformation/ToolbarItemProvider.php new file mode 100644 index 0000000..83757e3 --- /dev/null +++ b/Classes/SystemInformation/ToolbarItemProvider.php @@ -0,0 +1,135 @@ +lastRunInformation = $registry->get('tx_scheduler', 'lastRun', []); + } + + #[AsEventListener('scheduler/show-latest-errors')] + public function getItem(SystemInformationToolbarCollectorEvent $event): void + { + $systemInformationToolbarItem = $event->getToolbarItem(); + // No tasks configured, so nothing is shown at all + if (!$this->hasConfiguredTasks()) { + return; + } + $languageService = $this->getLanguageService(); + + if (!$this->schedulerWasExecuted()) { + // Display system message if the Scheduler has never yet run + $moduleIdentifier = 'scheduler'; + $systemInformationToolbarItem->addSystemMessage( + sprintf( + $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:systemmessage.noLastRun'), + (string)$this->uriBuilder->buildUriFromRoute($moduleIdentifier) + ), + InformationStatus::WARNING, + 1, + $moduleIdentifier, + ); + } else { + // Display information about the last Scheduler execution + if (!$this->lastRunInfoExists()) { + // Show warning if the information of the last run is incomplete + $message = $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.incompleteLastRun'); + $severity = InformationStatus::WARNING; + } else { + $start = DateTimeFactory::createFromTimestamp($this->lastRunInformation['start']); + $end = DateTimeFactory::createFromTimestamp($this->lastRunInformation['end']); + $startDate = $start->format($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy']); + $startTime = $start->format($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm']); + $duration = (new DateFormatter())->formatDateInterval( + $end->diff($start, true), + $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears') + ); + $severity = InformationStatus::NOTICE; + $label = 'automatically'; + if ($this->lastRunInformation['type'] === 'manual') { + $label = 'manually'; + } + $type = $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.' . $label); + $message = sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:systeminformation.lastRunValue'), $startDate, $startTime, $duration, $type); + } + $systemInformationToolbarItem->addSystemInformation( + 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:systeminformation.lastRunLabel', + $message, + 'actions-play', + $severity + ); + } + } + + /** + * Check whether the scheduler was already executed + */ + private function schedulerWasExecuted(): bool + { + return !empty($this->lastRunInformation); + } + + /** + * Check if the last scheduler run array contains all information + */ + private function lastRunInfoExists(): bool + { + return !empty($this->lastRunInformation['end']) + || !empty($this->lastRunInformation['start']) + || !empty($this->lastRunInformation['type']); + } + + /** + * See if there are any tasks configured at all. + */ + private function hasConfiguredTasks(): bool + { + return GeneralUtility::makeInstance(SchedulerTaskRepository::class)->hasTasks(); + } + + private function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/Classes/Task/AbstractTask.php b/Classes/Task/AbstractTask.php new file mode 100644 index 0000000..b462699 --- /dev/null +++ b/Classes/Task/AbstractTask.php @@ -0,0 +1,326 @@ +execution = new Execution(); + } + + /** + * 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): void + { + $this->taskUid = (int)$id; + } + + /** + * This method returns the unique id of the task + * + * @return int The id of the task + */ + public function getTaskUid(): int + { + return $this->taskUid; + } + + /** + * This method returns the disabled 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 disabled 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|null Uid of task group or null if it came back from the DB without the task group set. + */ + 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): void + { + $this->description = (string)$description; + } + + /** + * This method returns the description of the task + * + * @return string Description + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the internal execution object + * + * @param Execution $execution The execution to add + * @internal since TYPO3 v12.3, not part of TYPO3 Public API anymore. + */ + public function setExecution(Execution $execution): void + { + $this->execution = $execution; + } + + /** + * Returns the execution object + * + * @return Execution|object|null The internal execution object - when an invalid task is being unserialized, the Execution object might not be available + * @internal since TYPO3 v12.3, not part of TYPO3 Public API anymore. + */ + public function getExecution() + { + return $this->execution; + } + + /** + * Guess recurring type from the existing information + * If an interval or a cron command is defined, it's a recurring task + */ + public function getType(): int + { + if ($this->execution->isRecurring()) { + return self::TYPE_RECURRING; + } + return self::TYPE_SINGLE; + } + + protected function logException(\Exception $e) + { + $this->logger?->error('A Task Exception was captured.', ['exception' => $e]); + } + + protected function getLanguageService(): ?LanguageService + { + return $GLOBALS['LANG'] ?? null; + } + + public function getTaskType(): string + { + return static::class; + } + + /** + * It is recommended to implement this method in the respective task class. + */ + public function getTaskParameters(): array + { + $vars = get_object_vars($this); + $parameters = []; + foreach ($vars as $key => $value) { + $key = trim($key); + $key = trim($key, "*\0"); + $key = trim($key); + $parameters[$key] = $value; + } + unset( + // Needs to be kept until TYPO3 v16.0 until the upgrade wizard was run through + $parameters['scheduler'], + $parameters['logger'], + $parameters['taskUid'], + $parameters['disabled'], + $parameters['runOnNextCronJob'], + $parameters['execution'], + $parameters['executionTime'], + $parameters['description'], + $parameters['taskGroup'], + ); + return $parameters; + } + + /** + * Used to fill fields of this class, e.g. also when instantiating this class when no parameters are + * given but native DB fields are coming in. + * @param array $parameters + */ + public function setTaskParameters(array $parameters): void + { + foreach ($parameters as $key => $value) { + // Ensure a member property exists; Task objects might have old configuration data changed with + // attributes that were removed meanwhile. This would otherwise trigger a PHP notice like + // "PHP Runtime Deprecation Notice: Creation of dynamic property TYPO3\CMS\Linkvalidator\Task\ValidatorTask::$fileConfiguration is deprecated" + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + } +} diff --git a/Classes/Task/CachingFrameworkGarbageCollectionTask.php b/Classes/Task/CachingFrameworkGarbageCollectionTask.php new file mode 100644 index 0000000..832537a --- /dev/null +++ b/Classes/Task/CachingFrameworkGarbageCollectionTask.php @@ -0,0 +1,96 @@ + $cacheConfiguration) { + // The cache backend used for this cache + $usedCacheBackend = $cacheConfiguration['backend'] ?? Typo3DatabaseBackend::class; + if (in_array($usedCacheBackend, $this->selectedBackends, true)) { + GeneralUtility::makeInstance(CacheManager::class)->getCache($cacheName)->collectGarbage(); + } + } + } + return true; + } + + public function getTaskParameters(): array + { + return [ + 'cache_backends' => implode(',', $this->selectedBackends), + ]; + } + + public function setTaskParameters(array $parameters): void + { + $selectedBackends = $parameters['selectedBackends'] ?? $parameters['cache_backends'] ?? []; + if (!is_array($selectedBackends)) { + $selectedBackends = GeneralUtility::trimExplode(',', $selectedBackends, true); + } + $this->selectedBackends = $selectedBackends; + } + + /** + * Get all registered caching framework backends + */ + public function getRegisteredBackends(array &$config): void + { + $backends = []; + $cacheConfigurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']; + foreach ($cacheConfigurations ?? [] as $cacheConfiguration) { + $backend = (string)($cacheConfiguration['backend'] ?? Typo3DatabaseBackend::class); + if (!in_array($backend, $backends, true)) { + $backends[] = $backend; + } + } + foreach ($backends as $backend) { + $config['items'][] = ['value' => $backend, 'label' => $backend]; + } + } +} diff --git a/Classes/Task/ExecuteSchedulableCommandTask.php b/Classes/Task/ExecuteSchedulableCommandTask.php new file mode 100644 index 0000000..8848c86 --- /dev/null +++ b/Classes/Task/ExecuteSchedulableCommandTask.php @@ -0,0 +1,310 @@ +get($this->commandIdentifier); + } catch (CommandNotFoundException $e) { + throw new \RuntimeException( + sprintf( + $this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.unregisteredCommand'), + $this->commandIdentifier + ), + 1505055445, + $e + ); + } + + $input = new ArrayInput($this->getParameters(false)); + $input->setInteractive(false); + + $output = new NullOutput(); + + return $schedulableCommand->run($input, $output) === 0; + } + + /** + * Return a text representation of the selected command and arguments + * + * @return string Information to display + */ + public function getAdditionalInformation(): string + { + try { + $commandRegistry = GeneralUtility::makeInstance(CommandRegistry::class); + $schedulableCommand = $commandRegistry->get($this->commandIdentifier); + } catch (CommandNotFoundException $e) { + return sprintf( + $this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.unregisteredCommand'), + $this->commandIdentifier + ); + } + + try { + $input = new ArrayInput($this->getParameters(true), $schedulableCommand->getDefinition()); + $arguments = $input->__toString(); + } catch (\Symfony\Component\Console\Exception\RuntimeException|InvalidArgumentException $e) { + return $this->commandIdentifier . "\n" + . sprintf( + $this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.errorParsingArguments'), + $e->getMessage() + ); + } catch (InvalidOptionException $e) { + return $this->commandIdentifier . "\n" + . sprintf( + $this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.errorParsingOptions'), + $e->getMessage() + ); + } + if ($arguments !== '') { + return $this->commandIdentifier . ' ' . $arguments; + } + + return ''; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function getOptions(): array + { + return $this->options; + } + + public function getOptionValues(): array + { + return $this->optionValues; + } + + public function addDefaultValue(string $argumentName, mixed $argumentValue): void + { + if (is_bool($argumentValue)) { + $argumentValue = (int)$argumentValue; + } + $this->defaults[$argumentName] = $argumentValue; + } + + private function getParameters(bool $forDisplay): array + { + $options = []; + foreach ($this->options as $name => $enabled) { + if ($enabled) { + $value = $this->optionValues[$name] ?? null; + $options['--' . $name] = ($forDisplay && $value === true) ? '' : $value; + } + } + return array_merge($this->arguments, $options); + } + + public function getTaskType(): string + { + return $this->commandIdentifier; + } + + public function setTaskType(string $taskType): void + { + $this->commandIdentifier = $taskType; + } + + public function getTaskParameters(): array + { + return [ + 'commandIdentifier' => $this->commandIdentifier, + 'arguments' => $this->arguments, + 'options' => $this->options, + 'optionValues' => $this->optionValues, + ]; + } + public function setTaskParameters(array $parameters): void + { + $this->commandIdentifier = $parameters['commandIdentifier'] ?? $this->commandIdentifier; + $this->arguments = $this->processArguments($parameters); + $processedOptions = $this->processOptions($parameters); + $this->options = $processedOptions['options'] ?? []; + $this->optionValues = $processedOptions['optionValues'] ?? []; + } + + public function validateTaskParameters(array $parameters): bool + { + $result = true; + $commandRegistry = GeneralUtility::makeInstance(CommandRegistry::class); + $flashMessageQueue = GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier(); + if ($commandRegistry->has($this->getTaskType()) + && (is_array($parameters['arguments'] ?? false) || is_array($parameters['options'] ?? false)) + ) { + // If this is a registered console command, validate given arguments / options + $command = $commandRegistry->get($this->getTaskType()); + foreach ($command->getDefinition()->getArguments() as $argument) { + foreach (($parameters['arguments'] ?? []) as $argumentName => $argumentValue) { + if ($argument->getName() !== $argumentName) { + continue; + } + if ($argument->isRequired() && trim($argumentValue) === '') { + $flashMessageQueue->addMessage( + new FlashMessage(sprintf( + $this->getLanguageService()?->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.mandatoryArgumentMissing'), + $argumentName + ), '', ContextualFeedbackSeverity::ERROR) + ); + $result = false; + } + } + } + foreach ($command->getDefinition()->getOptions() as $optionDefinition) { + $optionEnabled = $parameters['options'][$optionDefinition->getName()] ?? false; + $optionValue = $parameters['optionValues'][$optionDefinition->getName()] ?? $optionDefinition->getDefault(); + if ($optionEnabled && $optionDefinition->isValueRequired()) { + if ($optionDefinition->isArray()) { + $testValues = is_array($optionValue) ? $optionValue : GeneralUtility::trimExplode(',', $optionValue, false); + } else { + $testValues = [$optionValue]; + } + foreach ($testValues as $testValue) { + if ($testValue === null || trim($testValue) === '') { + // An option that requires a value is used with an empty value + $flashMessageQueue->addMessage( + new FlashMessage(sprintf( + $this->getLanguageService()?->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.mandatoryArgumentMissing'), + $optionDefinition->getName() + ), '', ContextualFeedbackSeverity::ERROR) + ); + $result = false; + } + } + } + } + } + return $result; + } + + protected function processArguments(array $paremeters): array + { + if (!is_array($paremeters['arguments'] ?? false)) { + return []; + } + try { + $commandRegistry = GeneralUtility::makeInstance(CommandRegistry::class); + $command = $commandRegistry->get($this->commandIdentifier); + } catch (CommandNotFoundException) { + return []; + } + $arguments = []; + foreach ($paremeters['arguments'] as $argumentName => $argumentValue) { + try { + $argumentDefinition = $command->getDefinition()->getArgument($argumentName); + } catch (InvalidArgumentException) { + continue; + } + if ($argumentDefinition->isArray() && is_string($argumentValue)) { + $argumentValue = GeneralUtility::trimExplode(',', $argumentValue, true); + } + $arguments[$argumentName] = $argumentValue; + } + return $arguments; + } + + protected function processOptions(array $parameters): array + { + if (!is_array($parameters['options'] ?? false)) { + return []; + } + try { + $commandRegistry = GeneralUtility::makeInstance(CommandRegistry::class); + $command = $commandRegistry->get($this->commandIdentifier); + } catch (CommandNotFoundException) { + return []; + } + $options = []; + $optionValues = []; + foreach ($command->getDefinition()->getOptions() as $optionDefinition) { + $optionEnabled = $parameters['options'][$optionDefinition->getName()] ?? false; + $options[$optionDefinition->getName()] = (bool)$optionEnabled; + if ($optionDefinition->isValueRequired() || $optionDefinition->isValueOptional() || $optionDefinition->isArray()) { + $optionValue = $parameters['optionValues'][$optionDefinition->getName()] ?? $optionDefinition->getDefault(); + if ($optionDefinition->isArray() && is_string($optionValue)) { + // Do not remove empty array values. + // One empty array element indicates the existence of one occurrence of an array option (InputOption::VALUE_IS_ARRAY) without a value. + // Empty array elements are also required for command options like "-vvv" (can be entered as ",,"). + $optionValue = GeneralUtility::trimExplode(',', $optionValue); + } + } else { + // boolean flag: option value must be true if option is added or false otherwise + $optionValue = (bool)$optionEnabled; + } + $optionValues[$optionDefinition->getName()] = $optionValue; + } + return ['options' => $options, 'optionValues' => $optionValues]; + } +} diff --git a/Classes/Task/FileStorageExtractionTask.php b/Classes/Task/FileStorageExtractionTask.php new file mode 100644 index 0000000..68f391c --- /dev/null +++ b/Classes/Task/FileStorageExtractionTask.php @@ -0,0 +1,88 @@ +storageUid > 0) { + $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid($this->storageUid); + if ($storage === null) { + throw new \RuntimeException(self::class . ' misconfiguration: "Storage to index" must be an existing storage.', 1615020909); + } + $currentEvaluatePermissionsValue = $storage->getEvaluatePermissions(); + $storage->setEvaluatePermissions(false); + $indexer = $this->getIndexer($storage); + try { + $indexer->runMetaDataExtraction((int)$this->maxFileCount); + $success = true; + } catch (\Exception $e) { + $success = false; + $this->logException($e); + } + $storage->setEvaluatePermissions($currentEvaluatePermissionsValue); + } + return $success; + } + + protected function getIndexer(ResourceStorage $storage): Indexer + { + return GeneralUtility::makeInstance(Indexer::class, $storage); + } + + public function getTaskParameters(): array + { + return [ + 'file_storage' => $this->storageUid, + 'max_file_count' => $this->maxFileCount, + ]; + } + public function setTaskParameters(array $parameters): void + { + $this->storageUid = (int)($parameters['storageUid'] ?? $parameters['file_storage'] ?? -1); + $this->maxFileCount = (int)($parameters['maxFileCount'] ?? $parameters['max_file_count'] ?? 100); + } +} diff --git a/Classes/Task/FileStorageIndexingTask.php b/Classes/Task/FileStorageIndexingTask.php new file mode 100644 index 0000000..e21f1ab --- /dev/null +++ b/Classes/Task/FileStorageIndexingTask.php @@ -0,0 +1,73 @@ +storageUid > 0) { + $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid($this->storageUid); + if ($storage === null) { + throw new \RuntimeException(self::class . ' misconfiguration: "Storage to index" must be an existing storage.', 1615020908); + } + $currentEvaluatePermissionsValue = $storage->getEvaluatePermissions(); + $storage->setEvaluatePermissions(false); + $indexer = $this->getIndexer($storage); + $indexer->processChangesInStorages(); + $storage->setEvaluatePermissions($currentEvaluatePermissionsValue); + } + return true; + } + + protected function getIndexer(ResourceStorage $storage): Indexer + { + return GeneralUtility::makeInstance(Indexer::class, $storage); + } + + public function getTaskParameters(): array + { + return [ + 'file_storage' => $this->storageUid, + ]; + } + + public function setTaskParameters(array $parameters): void + { + $this->storageUid = $parameters['storageUid'] ?? $parameters['file_storage'] ?? 0; + } +} diff --git a/Classes/Task/IpAnonymizationTask.php b/Classes/Task/IpAnonymizationTask.php new file mode 100644 index 0000000..5b41634 --- /dev/null +++ b/Classes/Task/IpAnonymizationTask.php @@ -0,0 +1,193 @@ +getTableConfiguration()[$this->table] ?? []; + if (empty($configuration)) { + throw new \RuntimeException(self::class . ' misconfiguration: ' . $this->table . ' does not exist in configuration', 1524502548); + } + $this->handleTable($this->table, $configuration); + return true; + } + + /** + * Execute clean up of a specific table + * + * @throws \RuntimeException If table configuration is broken + * @param string $table The table to handle + * @param array $configuration Clean up configuration + * @return bool TRUE if cleanup was successful + */ + protected function handleTable(string $table, array $configuration) + { + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); + $queryBuilder = $connection->createQueryBuilder(); + $queryBuilder->getRestrictions()->removeAll(); + + if (empty($configuration['dateField'])) { + throw new \RuntimeException(self::class . ' misconfiguration: "dateField" must be defined for table ' . $table, 1524502549); + } + if (empty($configuration['ipField'])) { + throw new \RuntimeException(self::class . ' misconfiguration: "ipField" must be defined for table ' . $table, 1524502666); + } + $deleteTimestamp = strtotime('-' . $this->numberOfDays . 'days'); + if ($deleteTimestamp === false) { + throw new \RuntimeException(self::class . ' misconfiguration: number of days could not be calculated for table ' . $table, 1524526354); + } + if ($this->mask === 2) { + $notLikeMaskPattern = '%.0.0'; + } else { + $notLikeMaskPattern = '%.0'; + } + try { + $result = $queryBuilder + ->select('uid', $configuration['ipField']) + ->where( + $queryBuilder->expr()->lt( + $configuration['dateField'], + $queryBuilder->createNamedParameter($deleteTimestamp, Connection::PARAM_INT) + ), + $queryBuilder->expr()->neq( + $configuration['ipField'], + $queryBuilder->createNamedParameter('') + ), + $queryBuilder->expr()->isNotNull($configuration['ipField']), + $queryBuilder->expr()->notLike( + $configuration['ipField'], + $queryBuilder->createNamedParameter($notLikeMaskPattern) + ), + $queryBuilder->expr()->notLike( + $configuration['ipField'], + $queryBuilder->createNamedParameter('%::') + ) + ) + ->from($table) + ->executeQuery(); + + while ($row = $result->fetchAssociative()) { + $ip = (string)$row[$configuration['ipField']]; + + $connection->update( + $table, + [ + $configuration['ipField'] => IpAnonymizationUtility::anonymizeIp($ip, (int)$this->mask), + ], + [ + 'uid' => $row['uid'], + ] + ); + } + } catch (\Exception $e) { + throw new \RuntimeException(self::class . ' failed for table ' . $this->table . ' with error: ' . $e->getMessage(), 1524502550); + } + return true; + } + + public function getAdditionalInformation() + { + return sprintf($this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.additionalInformationTable'), $this->table, $this->numberOfDays); + } + + public function getTaskParameters(): array + { + return [ + 'number_of_days' => $this->numberOfDays, + 'ip_mask' => $this->mask, + 'selected_tables' => $this->table, + ]; + } + + public function setTaskParameters(array $parameters): void + { + $this->table = (string)($parameters['table'] ?? $parameters['selected_tables'] ?? ''); + $this->numberOfDays = (int)($parameters['numberOfDays'] ?? $parameters['number_of_days'] ?? 180); + $this->mask = (int)($parameters['mask'] ?? $parameters['ip_mask'] ?? 2); + } + + public function getAnonymizableTables(array &$config): void + { + foreach ($this->getTableConfiguration() as $tableName => $tableConfiguration) { + $config['items'][] = [ + 'label' => $tableName . (($tableConfiguration['ipField'] ?? false) ? ' [ipField: ' . $tableConfiguration['ipField'] . ']' : '') . (($tableConfiguration['dateField'] ?? false) ? ' [dateField: ' . $tableConfiguration['dateField'] . ']' : ''), + 'value' => $tableName, + ]; + } + } + + public function getTableConfiguration(): array + { + $tableConfiguration = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get('tx_scheduler_task.' . self::class)->getRawConfiguration()['taskOptions']['tables'] ?? []; + + $tableConfigurationFromConfVars = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][self::class]['options']['tables'] ?? []; + if (!empty($tableConfigurationFromConfVars)) { + // @deprecated will be removed in v16: this SC_OPTIONS fallback is intentionally + // kept beyond v15 because SC_OPTIONS-based scheduler task registration + // is still read in TaskService::getAvailableTaskTypes() to keep legacy + // (non-native) tasks migratable. Remove together with that support. + trigger_error('Usage of $GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'scheduler\'][\'tasks\'][' . self::class . '][\'options\'][\'tables\'] to define table options is deprecated and will stop working in TYPO3 v16. Use $tca[\'tx_scheduler_task\'][\'types\'][' . self::class . '][\'taskOptions\'][\'tables\'] instead.', E_USER_DEPRECATED); + if (is_array($tableConfigurationFromConfVars)) { + $tableConfiguration = array_replace_recursive($tableConfiguration, $tableConfigurationFromConfVars); + } + } + + return $tableConfiguration; + } +} diff --git a/Classes/Task/OptimizeDatabaseTableTask.php b/Classes/Task/OptimizeDatabaseTableTask.php new file mode 100644 index 0000000..d868d6f --- /dev/null +++ b/Classes/Task/OptimizeDatabaseTableTask.php @@ -0,0 +1,193 @@ +selectedTables as $tableName) { + $connection = $connectionPool->getConnectionForTable($tableName); + $platform = $connection->getDatabasePlatform(); + + if ($platform instanceof DoctrineMariaDBPlatform || $platform instanceof DoctrineMySQLPlatform) { + try { + // `OPTIMIZE TABLE` returns a result set and must be executed using `executeQuery()`, + // otherwise following database queries would fail with a database exception because of a + // not-consumed query buffer with `pdo_mysql` driver and the full result set is retrieved + // with `fetchAllAssociative()` and discarded as handling is not intended here. + $connection->executeQuery('OPTIMIZE TABLE ' . $connection->quoteIdentifier($tableName))->fetchAllAssociative(); + } catch (DBALException $e) { + throw new \RuntimeException( + TableGarbageCollectionTask::class . ' failed for: ' . $tableName . ': ' + . $e->getMessage(), + 1441390263 + ); + } + } + } + + return true; + } + + /** + * Output the selected tables + * + * @return string + */ + public function getAdditionalInformation() + { + return implode(', ', $this->selectedTables); + } + + public function getTaskParameters(): array + { + return [ + 'selected_tables' => implode(',', $this->selectedTables), + ]; + } + + public function setTaskParameters(array $parameters): void + { + $selectedTables = $parameters['selected_tables'] ?? $parameters['tables'] ?? []; + if (!is_array($selectedTables)) { + $selectedTables = GeneralUtility::trimExplode(',', $selectedTables, true); + } + $this->selectedTables = $selectedTables; + } + + /** + * TCA itemsProcFunc + * Get all tables that are capable of optimization + */ + public function getOptimizableTables(array &$config): array + { + $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); + $defaultConnection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); + + // Retrieve all optimizable tables for the default connection + $optimizableTables = $this->getOptimizableTablesForConnection($defaultConnection); + + // Retrieve additional optimizable tables that have been remapped to a different connection + $tableMap = $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'] ?? []; + if ($tableMap) { + // Remove all remapped tables from the list of optimizable tables + // These tables will be rechecked and possibly re-added to the list + // of optimizable tables. This ensures that no orphaned table from + // the default connection gets mistakenly labeled as optimizable. + $optimizableTables = array_diff($optimizableTables, array_keys($tableMap)); + + // Walk each connection and check all tables that have been + // remapped to it for optimization support. + $connectionNames = array_keys(array_flip($tableMap)); + foreach ($connectionNames as $connectionName) { + $connection = $connectionPool->getConnectionByName($connectionName); + $tablesOnConnection = array_keys(array_filter( + $tableMap, + static function ($value) use ($connectionName) { + return $value === $connectionName; + } + )); + $tables = $this->getOptimizableTablesForConnection($connection, $tablesOnConnection); + $optimizableTables = array_merge($optimizableTables, $tables); + } + } + + sort($optimizableTables); + foreach ($optimizableTables as $tableName) { + $config['items'][] = [ + 'label' => $tableName, + 'value' => $tableName, + ]; + } + return $optimizableTables; + } + + /** + * Retrieve all optimizable tables for a connection, optionally restricted to the subset + * of table names in the $tableNames array. + */ + protected function getOptimizableTablesForConnection(Connection $connection, array $tableNames = []): array + { + // Return empty list if the database platform is not MySQL/MariaDB + $platform = $connection->getDatabasePlatform(); + if (!($platform instanceof DoctrineMariaDBPlatform || $platform instanceof DoctrineMySQLPlatform)) { + return []; + } + + // Retrieve all tables from the MySQL information schema that have an engine type + // that supports the OPTIMIZE TABLE command. + $queryBuilder = $connection->createQueryBuilder(); + $queryBuilder->select('TABLE_NAME AS Table', 'ENGINE AS Engine') + ->from('information_schema.TABLES') + ->where( + $queryBuilder->expr()->eq( + 'TABLE_TYPE', + $queryBuilder->createNamedParameter('BASE TABLE') + ), + $queryBuilder->expr()->in( + 'ENGINE', + $queryBuilder->createNamedParameter(['InnoDB', 'MyISAM', 'ARCHIVE'], Connection::PARAM_STR_ARRAY) + ), + $queryBuilder->expr()->eq( + 'TABLE_SCHEMA', + $queryBuilder->createNamedParameter($connection->getDatabase()) + ) + ); + + if (!empty($tableNames)) { + $queryBuilder->andWhere( + $queryBuilder->expr()->in( + 'TABLE_NAME', + $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY) + ) + ); + } + + $tables = $queryBuilder->executeQuery()->fetchAllAssociative(); + + return array_column($tables, 'Table'); + } +} diff --git a/Classes/Task/RecyclerGarbageCollectionTask.php b/Classes/Task/RecyclerGarbageCollectionTask.php new file mode 100644 index 0000000..1f9cca7 --- /dev/null +++ b/Classes/Task/RecyclerGarbageCollectionTask.php @@ -0,0 +1,109 @@ +findAll() as $storage) { + $rootLevelFolder = $storage->getRootLevelFolder(false); + foreach ($rootLevelFolder->getSubfolders() as $subFolder) { + if ($subFolder->getRole() === $subFolder::ROLE_RECYCLER) { + $recyclerFolders[] = $subFolder; + break; + } + } + } + + // Execute cleanup + $seconds = 60 * 60 * 24 * (int)$this->numberOfDays; + $timestamp = $GLOBALS['EXEC_TIME'] - $seconds; + foreach ($recyclerFolders as $recyclerFolder) { + $this->cleanupRecycledFiles($recyclerFolder, $timestamp); + } + return true; + } + + /** + * Gets a list of all files in a directory recursively and removes + * old ones. + * + * @param Folder $folder the folder + * @param int $timestamp Timestamp of the last file modification + */ + protected function cleanupRecycledFiles(Folder $folder, $timestamp) + { + foreach ($folder->getFiles() as $file) { + if ($timestamp > $file->getModificationTime()) { + $file->delete(); + } + } + foreach ($folder->getSubfolders() as $subFolder) { + $this->cleanupRecycledFiles($subFolder, $timestamp); + // if no more files and subdirectories are in the folder, remove the folder as well + if ($subFolder->getFileCount() === 0 && count($subFolder->getSubfolders()) === 0) { + $subFolder->delete(true); + } + } + } + + public function getTaskParameters(): array + { + return [ + 'number_of_days' => $this->numberOfDays, + ]; + } + + public function setTaskParameters(array $parameters): void + { + $this->numberOfDays = (int)($parameters['numberOfDays'] ?? $parameters['number_of_days'] ?? 0); + } +} diff --git a/Classes/Task/TableGarbageCollectionTask.php b/Classes/Task/TableGarbageCollectionTask.php new file mode 100644 index 0000000..f1ef800 --- /dev/null +++ b/Classes/Task/TableGarbageCollectionTask.php @@ -0,0 +1,188 @@ +getTableConfiguration(); + $tableHandled = false; + foreach ($tableConfigurations as $tableName => $configuration) { + if ($this->allTables || $tableName === $this->table) { + $this->handleTable($tableName, $configuration); + $tableHandled = true; + } + } + if (!$tableHandled) { + throw new \RuntimeException(self::class . ' misconfiguration: ' . $this->table . ' does not exist in configuration', 1308354399); + } + return true; + } + + /** + * Execute clean up of a specific table + * + * @throws \RuntimeException If table configuration is broken + * @param string $table The table to handle + * @param array $configuration Clean up configuration + * @return bool TRUE if cleanup was successful + */ + protected function handleTable(string $table, array $configuration): bool + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); + $queryBuilder->delete($table); + if (!empty($configuration['expireField'])) { + $field = $configuration['expireField']; + $dateLimit = $GLOBALS['EXEC_TIME']; + // If expire field value is 0, do not delete + // Expire field = 0 means no expiration + $queryBuilder->where( + $queryBuilder->expr()->lte($field, $queryBuilder->createNamedParameter($dateLimit, Connection::PARAM_INT)), + $queryBuilder->expr()->gt($field, $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)) + ); + } elseif (!empty($configuration['dateField'])) { + if (!$this->allTables) { + $numberOfDays = $this->numberOfDays; + if (isset($configuration['expirePeriod']) && $numberOfDays <= 0) { + $numberOfDays = (int)$configuration['expirePeriod']; + } + $deleteTimestamp = strtotime('-' . $numberOfDays . 'days'); + } else { + if (!isset($configuration['expirePeriod'])) { + throw new \RuntimeException(self::class . ' misconfiguration: No expirePeriod defined for table ' . $table, 1308355095); + } + $deleteTimestamp = strtotime('-' . $configuration['expirePeriod'] . 'days'); + } + $queryBuilder->where( + $queryBuilder->expr()->lt( + $configuration['dateField'], + $queryBuilder->createNamedParameter($deleteTimestamp, Connection::PARAM_INT) + ) + ); + } else { + throw new \RuntimeException(self::class . ' misconfiguration: Either expireField or dateField must be defined for table ' . $table, 1308355268); + } + + try { + $queryBuilder->executeStatement(); + } catch (DBALException $e) { + throw new \RuntimeException(self::class . ' failed for table ' . $this->table . ' with error: ' . $e->getMessage(), 1308255491); + } + return true; + } + + /** + * This method returns the selected table as additional information + * + * @return string Information to display + */ + public function getAdditionalInformation() + { + if ($this->allTables) { + $message = $this->getLanguageService()?->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.additionalInformationAllTables'); + } else { + $message = sprintf($this->getLanguageService()?->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.additionalInformationTable'), $this->table); + } + return $message; + } + + public function getTaskParameters(): array + { + return [ + 'all_tables' => $this->allTables, + 'number_of_days' => $this->numberOfDays, + 'selected_tables' => $this->table, + ]; + } + + public function setTaskParameters(array $parameters): void + { + $this->allTables = (bool)($parameters['allTables'] ?? $parameters['all_tables'] ?? false); + $this->numberOfDays = (int)($parameters['numberOfDays'] ?? $parameters['number_of_days'] ?? 0); + $this->table = (string)($parameters['table'] ?? $parameters['selected_tables'] ?? ''); + } + + public function getCleanableTables(array &$config): void + { + foreach ($this->getTableConfiguration() as $tableName => $tableConfiguration) { + $config['items'][] = [ + 'label' => $tableName . (($tableConfiguration['expirePeriod'] ?? false) ? ' [expirePeriod: ' . $tableConfiguration['expirePeriod'] . ']' : '') . (($tableConfiguration['dateField'] ?? false) ? ' [dateField: ' . $tableConfiguration['dateField'] . ']' : ''), + 'value' => $tableName, + ]; + } + } + + public function getTableConfiguration(): array + { + $tableConfiguration = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get('tx_scheduler_task.' . self::class)->getRawConfiguration()['taskOptions']['tables'] ?? []; + + $tableConfigurationFromConfVars = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][self::class]['options']['tables'] ?? []; + if (!empty($tableConfigurationFromConfVars)) { + // @deprecated will be removed in v16: this SC_OPTIONS fallback is intentionally + // kept beyond v15 because SC_OPTIONS-based scheduler task registration + // is still read in TaskService::getAvailableTaskTypes() to keep legacy + // (non-native) tasks migratable. Remove together with that support. + trigger_error('Usage of $GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'scheduler\'][\'tasks\'][' . self::class . '][\'options\'][\'tables\'] to define table options is deprecated and will stop working in TYPO3 v16. Use $tca[\'tx_scheduler_task\'][\'types\'][' . self::class . '][\'taskOptions\'][\'tables\'] instead.', E_USER_DEPRECATED); + if (is_array($tableConfigurationFromConfVars)) { + $tableConfiguration = array_replace_recursive($tableConfiguration, $tableConfigurationFromConfVars); + } + } + + return $tableConfiguration; + } +} diff --git a/Classes/Task/TaskSerializer.php b/Classes/Task/TaskSerializer.php new file mode 100644 index 0000000..b04da01 --- /dev/null +++ b/Classes/Task/TaskSerializer.php @@ -0,0 +1,110 @@ +taskService->isTaskTypeRegistered($taskType)) { + $taskInformation = $this->taskService->getTaskDetailsFromTaskType($taskType); + $className = $taskInformation['className']; + try { + $taskObject = $this->container->get($className); + } catch (ServiceNotFoundException) { + $taskObject = GeneralUtility::makeInstance($className); + } + } else { + throw new InvalidTaskException('Task type ' . $taskType . ' not found. Probably not registered?', 1742584362); + } + + if (!$taskObject instanceof AbstractTask) { + throw new InvalidTaskException('The deserialized task in not an instance of AbstractTask', 1642954501); + } + if ($taskObject instanceof ExecuteSchedulableCommandTask) { + $taskObject->setTaskType($taskType); + } + $taskObject->setTaskUid((int)$row['uid']); + $taskObject->setTaskGroup((int)$row['task_group']); + $taskParameters = json_decode($row['parameters'] ?? '', true) ?: []; + // Set additional fields from the row with the parameters stored + // in the parameters field for native types. + if ($taskInformation['isNativeTask'] ?? false) { + // If there are native registered fields, they take precedence over the values. + foreach ($taskInformation['additionalFields'] ?? [] as $additionalFieldName) { + $taskParameters[$additionalFieldName] = $taskParameters[$additionalFieldName] ?? $row[$additionalFieldName] ?? null; + } + } + $taskObject->setTaskParameters($taskParameters); + $taskObject->setDescription((string)$row['description']); + $taskObject->setExecutionTime((int)$row['nextexecution']); + $taskObject->setTaskGroup((int)$row['task_group']); + $taskObject->setDisabled((bool)$row['disable']); + $executionDetails = json_decode($row['execution_details'] ?? '', true); + if ($executionDetails !== null) { + $taskObject->setExecution(Execution::createFromDetails($executionDetails)); + } + return $taskObject; + } + throw new InvalidTaskException('No task type given for task ID : ' . $row['uid'], 1740514192); + } + + /** + * If the task class couldn't be figured out from the unserialization (because of uninstalled extensions or exceptions), + * try to find it in the serialized string with a simple preg match. + */ + public function extractClassName(string $serializedTask): ?string + { + if (preg_match('/^O:[0-9]+:"(?P[^"]+)"/', $serializedTask, $matches) === 1) { + return $matches['classname']; + } + return null; + } +} diff --git a/Classes/Task/TaskStatus.php b/Classes/Task/TaskStatus.php new file mode 100644 index 0000000..6f9c4eb --- /dev/null +++ b/Classes/Task/TaskStatus.php @@ -0,0 +1,43 @@ + $messageArguments arguments for the message + */ + public function __construct( + public string $type, + public ContextualFeedbackSeverity $severity, + public string $state, + public string $label, + public string $message = '', + public array $messageArguments = [], + ) {} +} diff --git a/Classes/Validation/Validator/TaskValidator.php b/Classes/Validation/Validator/TaskValidator.php new file mode 100644 index 0000000..30cc7fb --- /dev/null +++ b/Classes/Validation/Validator/TaskValidator.php @@ -0,0 +1,42 @@ +getExecution() !== null + && get_class($value->getExecution()) !== \__PHP_Incomplete_Class::class; + } +} diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php new file mode 100644 index 0000000..8d086d0 --- /dev/null +++ b/Configuration/Backend/AjaxRoutes.php @@ -0,0 +1,24 @@ + [ + 'path' => '/scheduler/task/wizard/new', + 'target' => NewSchedulerTaskController::class . '::handleRequest', + 'methods' => ['GET'], + 'inheritAccessFromModule' => 'scheduler', + ], + // Register scheduler setup check (used in a modal) + 'scheduler_setup_check' => [ + 'path' => '/scheduler/setup-check', + 'target' => SchedulerModuleController::class . '::setupCheckAction', + 'methods' => ['GET'], + 'inheritAccessFromModule' => 'scheduler', + ], +]; diff --git a/Configuration/Backend/Modules.php b/Configuration/Backend/Modules.php new file mode 100644 index 0000000..9ed4f5c --- /dev/null +++ b/Configuration/Backend/Modules.php @@ -0,0 +1,23 @@ + [ + 'parent' => 'admin', + 'position' => ['after' => 'backend_user_management', 'before' => 'permissions_pages'], + 'access' => 'admin', + 'path' => '/module/scheduler', + 'workspaces' => 'live', + 'iconIdentifier' => 'module-scheduler', + 'labels' => 'scheduler.module', + 'routes' => [ + '_default' => [ + 'target' => SchedulerModuleController::class . '::handleRequest', + ], + ], + ], +]; diff --git a/Configuration/JavaScriptModules.php b/Configuration/JavaScriptModules.php new file mode 100644 index 0000000..c6be8a4 --- /dev/null +++ b/Configuration/JavaScriptModules.php @@ -0,0 +1,11 @@ + [ + 'backend', + 'core', + ], + 'imports' => [ + '@typo3/scheduler/' => 'EXT:scheduler/Resources/Public/JavaScript/', + ], +]; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml new file mode 100644 index 0000000..e854733 --- /dev/null +++ b/Configuration/Services.yaml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + TYPO3\CMS\Scheduler\: + resource: '../Classes/*' diff --git a/Configuration/TCA/Overrides/scheduler_caching_framework_garbage_collection_task.php b/Configuration/TCA/Overrides/scheduler_caching_framework_garbage_collection_task.php new file mode 100644 index 0000000..9f06fc8 --- /dev/null +++ b/Configuration/TCA/Overrides/scheduler_caching_framework_garbage_collection_task.php @@ -0,0 +1,48 @@ + [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.cachingFrameworkGarbageCollection.selectBackends', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'itemsProcFunc' => CachingFrameworkGarbageCollectionTask::class . '->getRegisteredBackends', + 'size' => 10, + 'minitems' => 0, + 'maxitems' => 100, + 'default' => '', + ], + ], + ] +); + +ExtensionManagementUtility::addRecordType( + [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:cachingFrameworkGarbageCollection.name', + 'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:cachingFrameworkGarbageCollection.description', + 'value' => CachingFrameworkGarbageCollectionTask::class, + 'icon' => 'mimetypes-x-tx_scheduler_task_group', + 'group' => 'scheduler', + ], + ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + cache_backends;LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.cachingFrameworkGarbageCollection.selectBackends, + --div--;core.form.tabs:timing, + --palette--;;execution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended,', + [], + '', + 'tx_scheduler_task' +); diff --git a/Configuration/TCA/Overrides/scheduler_file_storage_extraction_task.php b/Configuration/TCA/Overrides/scheduler_file_storage_extraction_task.php new file mode 100644 index 0000000..7b866c5 --- /dev/null +++ b/Configuration/TCA/Overrides/scheduler_file_storage_extraction_task.php @@ -0,0 +1,56 @@ + [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.fileCount', + 'config' => [ + 'type' => 'number', + 'size' => 10, + 'default' => 0, + 'range' => [ + 'lower' => 1, + 'upper' => 9999, + ], + ], + ], + 'registered_extractors' => [ + 'config' => [ + 'type' => 'none', + 'renderType' => 'registeredExtractors', + ], + ], + ] +); + +ExtensionManagementUtility::addRecordType( + [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:fileStorageExtraction.name', + 'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:fileStorageExtraction.description', + 'value' => FileStorageExtractionTask::class, + 'icon' => 'mimetypes-x-tx_scheduler_task_group', + 'group' => 'scheduler', + ], + ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + file_storage;LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageIndexing.storage, + max_file_count, + registered_extractors, + --div--;core.form.tabs:timing, + --palette--;;execution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended,', + [], + '', + 'tx_scheduler_task' +); diff --git a/Configuration/TCA/Overrides/scheduler_file_storage_indexing_task.php b/Configuration/TCA/Overrides/scheduler_file_storage_indexing_task.php new file mode 100644 index 0000000..1b9db35 --- /dev/null +++ b/Configuration/TCA/Overrides/scheduler_file_storage_indexing_task.php @@ -0,0 +1,30 @@ + 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:fileStorageIndexing.name', + 'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:fileStorageIndexing.description', + 'value' => FileStorageIndexingTask::class, + 'icon' => 'mimetypes-x-tx_scheduler_task_group', + 'group' => 'scheduler', + ], + ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + file_storage;LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageIndexing.storage, + --div--;core.form.tabs:timing, + --palette--;;execution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended,', + [], + '', + 'tx_scheduler_task' +); diff --git a/Configuration/TCA/Overrides/scheduler_ip_anonymization_task.php b/Configuration/TCA/Overrides/scheduler_ip_anonymization_task.php new file mode 100644 index 0000000..296b8a8 --- /dev/null +++ b/Configuration/TCA/Overrides/scheduler_ip_anonymization_task.php @@ -0,0 +1,86 @@ + [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.mask', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'default' => 2, + 'items' => [ + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.mask.1', 'value' => 1], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.mask.2', 'value' => 2], + ], + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + ] +); + +ExtensionManagementUtility::addRecordType( + [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:ipAnonymization.name', + 'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:ipAnonymization.description', + 'value' => IpAnonymizationTask::class, + 'icon' => 'mimetypes-x-tx_scheduler_task_group', + 'group' => 'scheduler', + ], + ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + ip_mask, + selected_tables;LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.table, + number_of_days;LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.numberOfDays, + --div--;core.form.tabs:timing, + --palette--;;execution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended,', + [ + 'columnsOverrides' => [ + 'selected_tables' => [ + 'displayCond' => 'FIELD:all_tables:=:0', + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.table', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'size' => 1, + 'minitems' => 1, + 'maxitems' => 1, + 'itemsProcFunc' => IpAnonymizationTask::class . '->getAnonymizableTables', + ], + ], + 'number_of_days' => [ + 'displayCond' => 'FIELD:all_tables:=:0', + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.numberOfDays', + 'config' => [ + 'type' => 'number', + 'default' => 0, + 'range' => [ + 'lower' => 0, + ], + ], + ], + ], + 'taskOptions' => [ + 'tables' => [ + 'sys_log' => [ + 'dateField' => 'tstamp', + 'ipField' => 'IP', + ], + ], + ], + ], + '', + 'tx_scheduler_task' +); diff --git a/Configuration/TCA/Overrides/scheduler_optimize_database_table_task.php b/Configuration/TCA/Overrides/scheduler_optimize_database_table_task.php new file mode 100644 index 0000000..66b031c --- /dev/null +++ b/Configuration/TCA/Overrides/scheduler_optimize_database_table_task.php @@ -0,0 +1,44 @@ + 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:optimizeDatabaseTable.name', + 'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:optimizeDatabaseTable.description', + 'value' => OptimizeDatabaseTableTask::class, + 'icon' => 'mimetypes-x-tx_scheduler_task_group', + 'group' => 'scheduler', + ], + ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + selected_tables;LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.optimizeDatabaseTables.selectTables, + --div--;core.form.tabs:timing, + --palette--;;execution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended,', + [ + 'columnsOverrides' => [ + 'selected_tables' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.optimizeDatabaseTables.selectTables', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'size' => 10, + 'minitems' => 1, + 'maxitems' => 100, + 'itemsProcFunc' => OptimizeDatabaseTableTask::class . '->getOptimizableTables', + ], + ], + ], + ], + '', + 'tx_scheduler_task' +); diff --git a/Configuration/TCA/Overrides/scheduler_recycler_garbage_collection_task.php b/Configuration/TCA/Overrides/scheduler_recycler_garbage_collection_task.php new file mode 100644 index 0000000..1823857 --- /dev/null +++ b/Configuration/TCA/Overrides/scheduler_recycler_garbage_collection_task.php @@ -0,0 +1,43 @@ + 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:recyclerGarbageCollection.name', + 'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:recyclerGarbageCollection.description', + 'value' => RecyclerGarbageCollectionTask::class, + 'icon' => 'mimetypes-x-tx_scheduler_task_group', + 'group' => 'scheduler', + ], + ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + number_of_days;LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.recyclerGarbageCollection.numberOfDays, + --div--;core.form.tabs:timing, + --palette--;;execution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended,', + [ + 'columnsOverrides' => [ + 'number_of_days' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.recyclerGarbageCollection.numberOfDays', + 'config' => [ + 'type' => 'number', + 'default' => 30, + 'range' => [ + 'lower' => 0, + ], + ], + ], + ], + ], + '', + 'tx_scheduler_task' +); diff --git a/Configuration/TCA/Overrides/scheduler_table_garbage_collection_task.php b/Configuration/TCA/Overrides/scheduler_table_garbage_collection_task.php new file mode 100644 index 0000000..8c2808c --- /dev/null +++ b/Configuration/TCA/Overrides/scheduler_table_garbage_collection_task.php @@ -0,0 +1,97 @@ + [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.allTables', + 'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.allTables.description', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'default' => 0, + ], + ], + ] +); + +ExtensionManagementUtility::addRecordType( + [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:tableGarbageCollection.name', + 'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:tableGarbageCollection.description', + 'value' => TableGarbageCollectionTask::class, + 'icon' => 'mimetypes-x-tx_scheduler_task_group', + 'group' => 'scheduler', + ], + ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + all_tables, + selected_tables;LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.table, + number_of_days;LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.numberOfDays, + --div--;core.form.tabs:timing, + --palette--;;execution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended,', + [ + 'columnsOverrides' => [ + 'selected_tables' => [ + 'displayCond' => 'FIELD:all_tables:=:0', + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.table', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'size' => 1, + 'minitems' => 1, + 'maxitems' => 1, + 'itemsProcFunc' => TableGarbageCollectionTask::class . '->getCleanableTables', + ], + ], + 'number_of_days' => [ + 'displayCond' => 'FIELD:all_tables:=:0', + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.numberOfDays', + 'config' => [ + 'type' => 'number', + 'default' => 0, + 'range' => [ + 'lower' => 0, + ], + 'fieldInformation' => [ + 'expirePeriodInformation' => [ + 'renderType' => 'expirePeriodInformation', + 'options' => [ + 'refField' => 'selected_tables', + ], + ], + ], + ], + ], + ], + 'taskOptions' => [ + 'tables' => [ + 'sys_log' => [ + 'dateField' => 'tstamp', + 'expirePeriod' => 180, + ], + 'sys_http_report' => [ + 'dateField' => 'changed', + 'expirePeriod' => 30, + ], + 'sys_history' => [ + 'dateField' => 'tstamp', + 'expirePeriod' => 30, + ], + ], + ], + ], + '', + 'tx_scheduler_task' +); diff --git a/Configuration/TCA/tx_scheduler_task.php b/Configuration/TCA/tx_scheduler_task.php new file mode 100644 index 0000000..b17b454 --- /dev/null +++ b/Configuration/TCA/tx_scheduler_task.php @@ -0,0 +1,204 @@ + [ + 'label' => 'tasktype', + 'label_alt' => 'description', + 'label_alt_force' => true, + 'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task', + 'crdate' => 'crdate', + 'delete' => 'deleted', + 'default_sortby' => 'nextexecution', + 'typeicon_classes' => [ + // @todo, TYPO3.icons needs to introduce tx_scheduler_task and use the current icon for "tx_scheduler_task_group" + 'default' => 'mimetypes-x-tx_scheduler_task_group', + ], + 'type' => 'tasktype', + 'hideTable' => true, // Disabled for now until sorting and grouping is usable in records module + 'adminOnly' => true, // Only admin users can edit + 'groupName' => 'system', + 'rootLevel' => 1, + 'enablecolumns' => [ + 'disabled' => 'disable', + ], + ], + 'columns' => [ + 'tasktype' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task.tasktype', + 'config' => [ + 'type' => 'select', + 'renderType' => 'taskTypeInfo', + 'itemsProcFunc' => TaskService::class . '->getTaskTypesForTcaItems', + // Always select the first tasktype + 'items' => [], + 'default' => '', + 'required' => true, + // relevant for migration + 'nullable' => true, + ], + ], + 'task_group' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task.task_group', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_scheduler_task_group', + 'items' => [ + ['label' => '', 'value' => 0], + ], + 'size' => 1, + 'default' => 0, + ], + ], + 'priority' => [ + 'label' => 'scheduler.tca:tx_scheduler_task.priority', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['label' => 'scheduler.tca:tx_scheduler_task.priority.high', 'value' => 150], + ['label' => 'scheduler.tca:tx_scheduler_task.priority.regular', 'value' => 100], + ['label' => 'scheduler.tca:tx_scheduler_task.priority.low', 'value' => 50], + ], + 'default' => 100, + ], + ], + 'description' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task.description', + 'config' => [ + 'type' => 'text', + ], + ], + 'parameters' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task.parameters', + 'config' => [ + 'type' => 'json', + ], + ], + 'execution_details' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task.execution_details', + 'config' => [ + 'type' => 'json', + 'renderType' => 'schedulerTimingOptions', + 'overrideFieldTca' => [ + 'frequency' => [ + 'config' => [ + 'valuePicker' => [ + 'items' => [ + [ 'value' => '0 9,15 * * 1-5', 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:command.example1' ], + [ 'value' => '0 */2 * * *', 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:command.example2' ], + [ 'value' => '*/20 * * * *', 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:command.example3' ], + [ 'value' => '0 7 * * 2', 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:command.example4' ], + ], + ], + ], + ], + ], + ], + ], + 'nextexecution' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task.nextexecution', + 'config' => [ + 'type' => 'datetime', + 'readOnly' => true, + ], + ], + 'lastexecution_time' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task.lastexecution_time', + 'config' => [ + 'type' => 'datetime', + 'readOnly' => true, + ], + ], + 'lastexecution_failure' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task.lastexecution_failure', + 'config' => [ + 'type' => 'text', + 'readOnly' => true, + ], + ], + 'lastexecution_context' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task.lastexecution_context', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['value' => 'CLI', 'label' => 'CLI'], + ['value' => 'BE', 'label' => 'BE'], + ['value' => '', 'label' => ''], + ], + 'readOnly' => true, + 'dbFieldLength' => 3, + 'default' => '', + ], + ], + 'serialized_executions' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'number_of_days' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:tx_scheduler_task.number_of_days', + 'config' => [ + 'type' => 'number', + 'default' => 0, + ], + ], + 'selected_tables' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:tx_scheduler_task.selected_tables', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'size' => 10, + 'dbFieldLength' => 4000, + 'minitems' => 1, + 'maxitems' => 100, + 'items' => [], + ], + ], + 'file_storage' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:tx_scheduler_task.file_storage', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_file_storage', + 'size' => 1, + 'minitems' => 1, + 'maxitems' => 1, + 'items' => [], + ], + ], + ], + 'types' => [ + '0' => [ + 'showitem' => ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + --div--;core.form.tabs:timing, + --palette--;;execution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended, + ', + ], + ], + 'palettes' => [ + 'execution' => [ + 'showitem' => ' + execution_details, + --linebreak--, + nextexecution, + priority, + --linebreak--, + lastexecution_context, + lastexecution_time, + --linebreak--, + lastexecution_failure, + ', + ], + ], +]; diff --git a/Configuration/TCA/tx_scheduler_task_group.php b/Configuration/TCA/tx_scheduler_task_group.php new file mode 100644 index 0000000..6faf604 --- /dev/null +++ b/Configuration/TCA/tx_scheduler_task_group.php @@ -0,0 +1,72 @@ + [ + 'label' => 'groupName', + 'tstamp' => 'tstamp', + 'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group', + 'crdate' => 'crdate', + 'delete' => 'deleted', + 'sortby' => 'sorting', + 'typeicon_classes' => [ + 'default' => 'mimetypes-x-tx_scheduler_task_group', + ], + 'adminOnly' => true, // Only admin users can edit + 'groupName' => 'system', + 'rootLevel' => 1, + 'enablecolumns' => [ + 'disabled' => 'hidden', + ], + ], + 'columns' => [ + 'groupName' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.groupName', + 'config' => [ + 'type' => 'input', + 'size' => 35, + 'max' => 80, + 'required' => true, + 'eval' => 'unique,trim', + 'softref' => 'substitute', + ], + ], + 'color' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color', + 'config' => [ + 'type' => 'color', + 'size' => 10, + 'valuePicker' => [ + 'items' => [ + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.typo3Orange', 'value' => '#FF8700'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.white', 'value' => '#ffffff'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.gray', 'value' => '#808080'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.black', 'value' => '#000000'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.blue', 'value' => '#2671d9'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.purple', 'value' => '#5e4db2'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.teal', 'value' => '#2da8d2'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.green', 'value' => '#3cc38c'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.magenta', 'value' => '#c6398f'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.yellow', 'value' => '#ffbf00'], + ['label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.color.red', 'value' => '#d13a2e'], + ], + ], + ], + ], + 'description' => [ + 'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.description', + 'config' => [ + 'type' => 'text', + ], + ], + ], + 'types' => [ + '1' => [ + 'showitem' => ' + --div--;core.form.tabs:general, groupName, color, + --div--;core.form.tabs:access, hidden, + --div--;core.form.tabs:notes, description, + --div--;core.form.tabs:extended, + ', + ], + ], +]; diff --git a/Configuration/user.tsconfig b/Configuration/user.tsconfig new file mode 100644 index 0000000..e04bc11 --- /dev/null +++ b/Configuration/user.tsconfig @@ -0,0 +1 @@ +options.showDuplicate.tx_scheduler_task = 0 diff --git a/Documentation/Administration/BackendModule/Index.rst b/Documentation/Administration/BackendModule/Index.rst new file mode 100644 index 0000000..98bfd81 --- /dev/null +++ b/Documentation/Administration/BackendModule/Index.rst @@ -0,0 +1,120 @@ +:navigation-title: Backend Module + +.. include:: /Includes.rst.txt +.. _backend-module: + +============================== +The "Scheduler" backend module +============================== + +.. contents:: Table of contents + +.. _setup-check-screen: + +The setup check screen +====================== + +This screen has already been mentioned in the :ref:`Installation chapter `. +It is mostly useful when setting up the Scheduler, as it indicates +whether the CLI script is executable or not. When everything is +running fine, it contains mostly one useful piece of information: when +the last run took place, when it ended and whether it was started +manually (i.e. from the BE module) or automatically (i.e. from the +command line). + + +.. _scheduled-tasks-screen: + +The scheduled tasks screen +========================== + +This is the main screen when administering tasks. At first it will be +empty and just offer a link to add a new task. When such registered +tasks exists, this screen will show a list with various pieces of +information. + +.. figure:: /Images/BackendModuleMainView.png + :alt: Scheduler main screen + + Main screen of the Scheduler BE module + +Disabled tasks have a gray label sign near the task name. A disabled task is a +task that will not be run automatically by the command-line script, +but may still be executed from the BE module. + +A late task will appear with an orange label sign near the task name: + +.. figure:: /Images/LateTask.png + :alt: A late task in the Scheduler main screen + + A late task in the main screen of the Scheduler BE module + + +The task list can be sorted by clicking the column label. With every click it +switches between ascending and descending order of the items of the associated column. + +The table at the center of the above screenshot shows the +following: + +- The first column contains checkboxes. Clicking on a checkbox will + select that particular scheduled task for immediate execution. + Clicking on the icon at the top of the column will toggle all + checkboxes. To execute the selected tasks, click on the "Execute + selected tasks" button. Read more in "Manually executing a task" + below. + +- The second column simply displays the id of the task. + +- The third column contains the name of the task, the extension it is + coming from and any additional information specific to the task. It also shows + a summary of the task's status with an icon. + +- The fourth column shows whether the task is recurring or will run only + a single time. + +- The fifth column shows the frequency. + +- The sixth columns indicates whether parallel executions are allowed + or not. + +- The seventh column shows the last execution time and indicates whether + the task was launched manually or was run via the command-line script + (cron). + +- The eighth column shows the planned execution time. If the task is + overdue, the time will show up in bold, red numbers. A task may have + no future execution date if it has reached its end date, if it was + meant to run a single time and that execution is done, or if the task + is disabled. The next execution time is also hidden for running tasks, + as this information makes no sense at that point in time. + +- The last column contains possible actions, mainly editing, disable or + deleting a task. There are also buttons for running the task on the + next cron job or run it directly. + The actions will be unavailable for a task that is currently running, + as it is unwise to edit or delete it a task in such a case. Instead a + running task will display a "stop" button (see "Stopping a task" below). + + + +Note that all dates and times are displayed in the server's time zone. +The server time appears at the bottom of the screen. + +At the top of the screen is a link to add a new task. +If there are a lot of tasks that appear late, +consider changing the frequency at which the cron job is running (see +"Choosing a frequency" above). + +Occasionally the following display may appear: + +.. figure:: ../../Images/MissingTaskClass.png + :alt: A broken task + + A scheduled task missing its corresponding class + +This will typically happen when a task provided by some +extension was registered, then the extension was uninstalled, but the +task was not deleted beforehand. In such a case, this task stays but +the Scheduler doesn't know how to handle it anymore. The solution is +either to install the related extension again or delete the registered +task. diff --git a/Documentation/Administration/ConsoleTools/Index.rst b/Documentation/Administration/ConsoleTools/Index.rst new file mode 100644 index 0000000..24cf078 --- /dev/null +++ b/Documentation/Administration/ConsoleTools/Index.rst @@ -0,0 +1,119 @@ +:navigation-title: Console tools + +.. include:: /Includes.rst.txt +.. _console-tools: + +======================================= +Console tools to manage scheduler tasks +======================================= + +Console commands to manage scheduler tasks include :command:`typo3 scheduler:list`, +:command:`typo3 scheduler:execute` and :command:`typo3 scheduler:run`. + +You can display detailed help on these commands, by using the `--help` parameter to +display the help: + +.. tabs:: + + .. group-tab:: Composer mode + + .. code-block:: bash + + vendor/bin/typo3 scheduler:list --help + + .. group-tab:: Classic mode + + .. code-block:: bash + + typo3/sysext/core/bin/typo3 scheduler:list --help + +See also: `Command usage in terminal environments `_. + +.. contents:: Table of contents + +.. toctree:: + :glob: + :caption: Subpages + :titlesonly: + + * + +.. _console-run: + +Running the scheduler +===================== + +The command :command:`typo3 scheduler:run` is usually called by the +`cron job `_. + +It looks for tasks that are **due**, and runs them. You can +optionally target specific task IDs, force them even if not due, or stop them. + +.. tabs:: + + .. group-tab:: Composer mode + + .. code-block:: bash + + vendor/bin/typo3 scheduler:run + + .. group-tab:: Classic mode + + .. code-block:: bash + + typo3/sysext/core/bin/typo3 scheduler:run + +.. seealso:: + `Running the scheduler: typo3 scheduler:run `_ + +.. _console-execute: + +Executing scheduler tasks +========================= + +The command :command:`typo3 scheduler:execute` is a "manual fire" runner. You pick +tasks (IDs or whole groups) and it **executes them on demand**, regardless of +whether they are due. It can also prompt you interactively to choose. + +.. tabs:: + + .. group-tab:: Composer mode + + .. code-block:: bash + + # Note the id of the task + vendor/bin/typo3 scheduler:list + + vendor/bin/typo3 scheduler:execute --task= + + .. group-tab:: Classic mode + + .. code-block:: bash + + # Find the id of the task + typo3/sysext/core/bin/typo3 scheduler:list + + typo3/sysext/core/bin/typo3 scheduler:execute --task= + +.. _console-list: + +Listing all scheduler tasks +=========================== + +Command :command:`typo3 scheduler:list` can be used to list all available tasks. +This command basically displays the same information as the backend module +:guilabel:`Administration > Scheduler`. + +.. tabs:: + + .. group-tab:: Composer mode + + .. code-block:: bash + + vendor/bin/typo3 scheduler:list + + .. group-tab:: Classic mode + + .. code-block:: bash + + typo3/sysext/core/bin/typo3 scheduler:list diff --git a/Documentation/Administration/ConsoleTools/Running.rst b/Documentation/Administration/ConsoleTools/Running.rst new file mode 100644 index 0000000..31023c3 --- /dev/null +++ b/Documentation/Administration/ConsoleTools/Running.rst @@ -0,0 +1,141 @@ +:navigation-title: Run the scheduler + +.. include:: /Includes.rst.txt +.. _scheduler-shell-script: + +========================================== +Running the scheduler: typo3 scheduler:run +========================================== + +The scheduler provides a PHP shell script designed to be run using +TYPO3's command-line dispatcher. To try and run that script a first +time, type the following command. + +.. tabs:: + + .. group-tab:: Composer-based installation + + .. code-block:: bash + + vendor/bin/typo3 scheduler:run + + .. group-tab:: Classic installation + + .. code-block:: bash + + typo3/sysext/core/bin/typo3 scheduler:run + +See also `TYPO3 Explained: Run a command from the command +line `_. + +.. contents:: Table of contents + +.. _scheduler-shell-script-help: + +Show help +========= + +In order to show help: + +.. tabs:: + + .. group-tab:: Composer-based installation + + .. code-block:: bash + + vendor/bin/typo3 scheduler:run --help + + .. group-tab:: Classic installation + + .. code-block:: bash + + typo3/sysext/core/bin/typo3 scheduler:run --help + +.. _scheduler-shell-script-options: + +Providing options to the shell script +===================================== + +The shell scripts accepts a number of options which can be provided in any +order. + +.. _scheduler-shell-script-options-i: + +`--task (-i)` +------------- + +To run a specific scheduler task you need to provide the uid of the task: + +.. tabs:: + + .. group-tab:: Composer-based installation + + .. code-block:: bash + + # Run task with uid 42 + vendor/bin/typo3 scheduler:run --task=42 + + # Run tasks with uid 3 and 14 + vendor/bin/typo3 scheduler:run --task=3 --task=14 + + .. group-tab:: Classic installation + + .. code-block:: bash + + # Run task with uid 42 + typo3/sysext/core/bin/typo3 scheduler:run --task=42 + + # Run tasks with uid 3 and 14 + typo3/sysext/core/bin/typo3 scheduler:run --task=3 --task=14 + +The tasks will be executed in the order in which the parameters are provided. + +.. _scheduler-shell-script-options-f: + +`--force (-f)` +-------------- + +To run a task even if it is disabled (or not scheduled to be run yet), +you need to provide the force option: + +.. tabs:: + + .. group-tab:: Composer-based installation + + .. code-block:: bash + + # Run task with uid 42, even if disabled + vendor/bin/typo3 scheduler:run --task=42 --force + + .. group-tab:: Classic installation + + .. code-block:: bash + + # Run task with uid 42, even if disabled + typo3/sysext/core/bin/typo3 scheduler:run --task=42 --force + +This will also run the task with uid 42 if it is disabled. + +.. _scheduler-shell-script-options-v: + +`--verbose (-v)` +---------------- + +A single `-v` flag will output errors only. Two `-vv` flags will also output +additional information: + +.. tabs:: + + .. group-tab:: Composer-based installation + + .. code-block:: bash + + # Run task with uid 42, with detailed stack traces + vendor/bin/typo3 scheduler:run --task=42 -vv + + .. group-tab:: Classic installation + + .. code-block:: bash + + # Run task with uid 42, with detailed stack traces + typo3/sysext/core/bin/typo3 scheduler:run --task=42 -vv diff --git a/Documentation/Administration/DeleteTask/Index.rst b/Documentation/Administration/DeleteTask/Index.rst new file mode 100644 index 0000000..ed33fe0 --- /dev/null +++ b/Documentation/Administration/DeleteTask/Index.rst @@ -0,0 +1,34 @@ +.. include:: /Includes.rst.txt + +.. _deleting-a-task: + +=============== +Deleting a task +=============== + +When choosing to delete a task, a pop-up window will appear requesting +confirmation. + +Deleted tasks can be recovered in the module :guilabel:`Content > Recycler` if +installed or by unsetting the deleted flag in the database. + +.. _deleting-a-task-restoration: + +Restoring a deleted scheduler task +================================== + +.. versionchanged:: 14.0 + Previously removed tasks can be restored via recycler. + +If the system extension :composer:`typo3/cms-recycler` is installed, go to +module :guilabel:`Content > Recycler`. + +Choose the page root (page 0) in the page tree, all scheduler tasks are stored +here. + +You can use the field "Type" to filter for "Scheduler task" only. + +.. figure:: /Images/RestoreTask.png + :alt: Screenshot of the TYPO3 Backend Recycler module on the root page 0 with type "Scheduler task" selected + + A task can be restored or permanently deleted in the recycler module diff --git a/Documentation/Administration/EditTask/Index.rst b/Documentation/Administration/EditTask/Index.rst new file mode 100644 index 0000000..88f50cc --- /dev/null +++ b/Documentation/Administration/EditTask/Index.rst @@ -0,0 +1,104 @@ +:navigation-title: Adding / Editing + +.. include:: /Includes.rst.txt +.. _adding-editing-task: + +======================== +Adding or editing a task +======================== + +Administrators can add or edit scheduler tasks in the backend module +:guilabel:`Administration > Scheduler`. + +When adding a new scheduler task a wizard will allow you to select a task +type from several categories. + +.. seealso:: + Developers can implement and register custom tasks: + `Creating a custom scheduler task `_ + +.. contents:: Table of contents + +.. _information-screen: +.. _adding-editing-task-wizard: + +The scheduler task wizard +========================= + +.. figure:: /Images/EmptySchedulerModule.png + :alt: Screenshot of an empty scheduler module, No tasks defined yet. + + Click on "New task" to add a task + +.. figure:: /Images/TaskCreationWizard.png + :alt: Screenshot of the "New task" wizard in the scheduler backend module + + Choose the task to be created + +.. seealso:: + Developers can listen to event `ModifyNewSchedulerTaskWizardItemsEvent `_ + to influence the items displayed here. + +.. _adding-editing-task-form: + +The scheduler task form +======================= + +When adding or editing a task, the following form will show up: + +.. figure:: /Images/AddingATask.png + :alt: Screenshot of the form to Create new Scheduler task on root level + + Adding a new scheduled task + +Some fields require additional explanations (inline help is +available by moving the mouse over the field labels): + +- A disabled task will be skipped by the command-line script. It may + still be launched manually, as described above. + +.. versionadded:: 13.3 + Similar to editing regular content elements, it is now possible to save + scheduler tasks being edited via keyboard shortcuts as well. + +It is possible to invoke the :kbd:`Ctrl`/:kbd:`Cmd` + :kbd:`s` hotkey to save a +scheduler task, altogether with the hotkey :kbd:`Ctrl`/:kbd:`Cmd` + :kbd:`Shift` + :kbd:`S` +to save and close a scheduler task. + +.. _adding-editing-task-form-settings: + +Scheduler task settings +======================= + +Some tasks allow additional settings to be made in the area :guilabel:`Settings`. +These fields differ from task to task + +.. _adding-editing-task-form-timing: + +Task executions timing details +============================== + +.. figure:: /Images/TaskExecutionDetails.png + :alt: Screenshot of tab "Timing" the scheduler task form + + Choosing a frequency for a recurring task + +- A task must have a start date. It defaults to the time of creation. + The server's time appears at the bottom of the form. + +- Task can be run a single time or recurring. + +- The frequency needs be entered only for recurring tasks. + It can be either an integer number of seconds or a cron-like schedule expression. + Scheduler supports ranges, steps and keywords like ``@weekly``. + See `en.wikipedia.org `_ for more information. + See :php:`\TYPO3\CMS\Scheduler\CronCommand\CronCommand` + and :php:`\TYPO3\CMS\Scheduler\CronCommand\NormalizeCommand` + class references in the TYPO3 CMS source code for definitive rules. + +- Parallel executions are denied for recurring tasks. They can be allowed by + checking "Allow Parallel Execution" + +If an error occurs when validating a cron definition, the +Scheduler's built-in cron parser tries to provide an explanation about +what's wrong. diff --git a/Documentation/Administration/GroupTask/Index.rst b/Documentation/Administration/GroupTask/Index.rst new file mode 100644 index 0000000..24c08d6 --- /dev/null +++ b/Documentation/Administration/GroupTask/Index.rst @@ -0,0 +1,69 @@ +:navigation-title: Grouping + +.. include:: /Includes.rst.txt +.. _grouping-tasks: + +======================================================= +Grouping tasks together in the Scheduler backend module +======================================================= + +In case of a high number of different tasks, it may be useful to visually +group similar tasks together: + +.. figure:: /Images/GroupedTasks.png + :alt: Screenshot of the TYPO3 backend module scheduler with buttons regarding groups highlighted + + Use button :guilabel:`New group` (1) to create a group, click on the title (2) to edit the group + +You can sort and disable groups (3). + +Unused groups are displayed at the bottom of the page. Only unused groups can +be deleted (4). + +When editing a group you can change its title and color. + +.. figure:: /Images/GroupEdit.png + :alt: Screenshot of the task group form + +Color options can be customized by manipulating the +:php:`$GLOBAS['TCA']['tx_scheduler_task_group']['columns']['color']['config']['valuePicker']['items']` +array in your TCA overrides file. + +.. code-block:: php + :caption: packages/my-sitepackage/Configuration/Overrides/tx_scheduler_task_group.php + + $GLOBALS['TCA']['tx_scheduler_task_group']['columns']['color']['config']['valuePicker']['items'][] = [ + 'label' => 'My Color', + 'value' => '#ABCDEF' + ]; + +.. _grouping-tasks-edit: + +Editing task groups +=================== + +Scheduler task groups can be created, edited and deleted from the module +:guilabel:`Administration > Scheduler`. + +Technically they are records stored on the root page (pid=0). They can also be +created, edited and sorted with module :guilabel:`Content > Records`. + +It is also possible to create a new task group from within the edit task form by +clicking on the `+` icon next to the task group select box. + +.. _grouping-tasks-disable: + +Disabling task groups +===================== + +You can use button :guilabel:`Disable group` to disable all tasks in a group +at once. + +.. figure:: /Images/GroupDisabled.png + :alt: Screenshot a disabled task group, all tasks are marked as disabled by group + + Use button :guilabel:`Enable group` to enable all tasks that had not been manually disabled. + +Tasks in a disabled group, just like disabled tasks in general are not executed +when the scheduler is called by the cron job. They can, however, be executed +manually by clicking the :guilabel:`Run task` button. diff --git a/Documentation/Administration/Index.rst b/Documentation/Administration/Index.rst new file mode 100644 index 0000000..3c1a754 --- /dev/null +++ b/Documentation/Administration/Index.rst @@ -0,0 +1,25 @@ +:navigation-title: Administration + +.. include:: /Includes.rst.txt +.. _administration: + +================================================= +Backend administration in the "Scheduler" module +================================================= + +The Scheduler provides a BE module to manage tasks. It provides three +screens: a setup check, an information screen and the main (default) +one for actually managing the tasks. + +.. toctree:: + :maxdepth: 5 + :titlesonly: + :glob: + + BackendModule/Index + EditTask/Index + DeleteTask/Index + GroupTask/Index + StopTask/Index + ManualExecution/Index + ConsoleTools/Index diff --git a/Documentation/Administration/ManualExecution/Index.rst b/Documentation/Administration/ManualExecution/Index.rst new file mode 100644 index 0000000..37fd662 --- /dev/null +++ b/Documentation/Administration/ManualExecution/Index.rst @@ -0,0 +1,56 @@ +:navigation-title: Manual Execution + +.. include:: /Includes.rst.txt +.. _manually-executing-a-task: +.. _executing-a-task-on-next-cronjob: + +=========================================================== +Manually executing a task from the Scheduler backend module +=========================================================== + +You can manually execute tasks from the BE module. After execution, each +task shows success or failure. + +* If a task was overdue, a new execution date is calculated. +* If it was not overdue, the existing next execution date remains. + +Running tasks: + +* To run a single task, press the button in its row. +* To run multiple tasks, select their checkboxes and press the button below the list. + +There are two options: + +* Run the task immediately. (Button 2 in the screenshot) +* Schedule the task. (Button 1 in the screenshot) The selected tasks will + then run on the next cron job. + +.. figure:: /Images/ManualExecution.png + :alt: Scheduler backend module with the buttons "Run task on next cron job" (1) and "Run task" (2) highlighted + + Button 2 runs the task immediately, while button 1 schedules it fot the next cronjob run + +.. _manually-executing-a-task-cli: + +Manually executing a task from the console +========================================== + +.. tabs:: + + .. group-tab:: Composer mode + + .. code-block:: bash + + # Note the id of the task + vendor/bin/typo3 scheduler:list + + vendor/bin/typo3 scheduler:execute --task= + + .. group-tab:: Classic mode + + .. code-block:: bash + + # Find the id of the task + typo3/sysext/core/bin/typo3 scheduler:list + + typo3/sysext/core/bin/typo3 scheduler:execute --task= diff --git a/Documentation/Administration/StopTask/Index.rst b/Documentation/Administration/StopTask/Index.rst new file mode 100644 index 0000000..0ec4bb9 --- /dev/null +++ b/Documentation/Administration/StopTask/Index.rst @@ -0,0 +1,92 @@ +:navigation-title: Stopping a Task + +.. include:: /Includes.rst.txt +.. _stopping-a-task: + +=============================================== +Stopping a task in the Scheduler backend module +=============================================== + +A task is marked as "running" while it runs. If the process crashes or +is killed, the task may remain marked as "running". This is usually +cleaned up automatically based on the maximum lifetime parameter, +but manual cleanup may sometimes be needed. + +.. figure:: /Images/StoppingATask.png + :alt: Stopping a task + + Stopping a running task from the main screen + +Use the **stop** button to clear the execution mark for a task. +This allows the task to run again. + +Note: This does **not** terminate an actual running or hanging process. + +.. _stopping-a-task-cli: + +Stopping a task via console command +=================================== + +You can also use a command to stop the task: + +.. tabs:: + + .. group-tab:: Composer mode + + .. code-block:: bash + + # Note the id of the task + vendor/bin/typo3 scheduler:list + + vendor/bin/typo3 scheduler:run --task= --stop + + .. group-tab:: Classic mode + + .. code-block:: bash + + # Find the id of the task + typo3/sysext/core/bin/typo3 scheduler:list + + typo3/sysext/core/bin/typo3 scheduler:run --task= --stop + +.. _kill-task: + +How to handle a truly "hung" task +================================= + +If a task hangs or is stuck (for example due to an infinite loop or external I/O), +then stopping it via TYPO3 (either UI or CLI) will only clear TYPO3’s internal flag. + +The actual PHP process running the task will continue on the system +until it finishes or the OS/PHP process manager kills it. + +If a task keeps running indefinitely: + +#. **Identify the process PID** You can find the hanging PHP process using +tools like `top`, `ps aux | grep scheduler`, or via `systemctl status` if you run TYPO3 via a +systemd service. +#. **Manually terminate the PHP process** (for example, `kill `). +#. **Clear the TYPO3 execution flag** by running: +:command:`vendor/bin/typo3 scheduler:run --task= --stop` + +.. warning:: + Manually terminating a scheduler process using `sudo kill ` should only + be used as a *last resort*. + + Killing a running PHP process may interrupt database or file operations and + leave the system in an inconsistent state. + + Always analyze why the task is hanging before killing it. + +.. code-block:: bash + + ps aux | grep scheduler + + # you might find something like this: + www-data 12345 99.0 5.2 php vendor/bin/typo3 scheduler:run + + # To force-stop that OS-level process: + sudo kill 12345 + + # Then clear the mark again via: + vendor/bin/typo3 scheduler:run --task=13 --stop diff --git a/Documentation/BasicTasks/GarbageCollection.rst b/Documentation/BasicTasks/GarbageCollection.rst new file mode 100644 index 0000000..651b0a1 --- /dev/null +++ b/Documentation/BasicTasks/GarbageCollection.rst @@ -0,0 +1,94 @@ +:navigation-title: Table Garbage Collection + +.. include:: /Includes.rst.txt + +.. _table-garbage-collection-task: + +============================= +Table garbage collection task +============================= + +The table garbage collection task can take a more elaborate +configuration which is detailed below. + +.. contents:: Table of contents + +.. _table-garbage-collection-task-usage: + +Using the garbage collection task +================================= + +The task can be registered to clean up a particular table, in which +case you simply choose the table and the minimum age of the records to +delete from the task configuration screen. + +.. figure:: /Images/TableGarbageCollectionTaskConfiguration.png + :alt: Table Garbage Collection task configuration + + Configuring the table garbage collection task + +In case no minimum age is choosen, the configured :php:`expirePeriod` is used. + +.. figure:: /Images/TableGarbageCollectionTaskConfiguration-2.png + :alt: Table Garbage Collection task configuration default expire period + + Configuring the table garbage collection task with default expire period + +It is also possible to clean up all configured table by +checking the "Clean all available tables" box. + +The configuration for +the tables to clean up is stored in the TCA of table `tx_scheduler_task`, in +field `tables`. + +This configuration is an array with the table names as fields and the following +entries: + +- option :php:`expireField` can be used to point to a table field + containing an expiry timestamp. This timestamp will then be used to + decide whether a record has expired or not. If its timestamp is in the + past, the record will be deleted. + +- if a table has no expiry field, one can use a combination of a date + field and an expiry period to decide which records should be deleted. + The corresponding options are :php:`dateField` and :php:`expirePeriod`. + The expiry period is expressed in days. + +.. _table-garbage-collection-task-example: + +Example: Configure additional tables for the "Garbage Collection" task +====================================================================== + +.. deprecated:: 14.0 + The previous configuration method using + :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables']` + has been deprecated and will be removed in TYPO3 v15. + + See also: `Changelog Deprecation: #107550 - Table Garbage Collection Task configuration via $GLOBALS `_ + +.. literalinclude:: _codesnippets/_tx_scheduler_garbage_collection.php.inc + :language: php + :caption: packages/my_extension/Configuration/TCA/Overrides/tx_scheduler_garbage_collection.php + +.. include:: /_Includes/_ExtendingSchedulerTca.rst.txt + +The first part of the configuration indicates that records older than +180 days should be removed from table :code:`tx_myextension_my_table` , +based on the timestamp field called "tstamp". The second part +indicates that old records should be removed from table +:code:`tx_myextension_my_other_table` directly based on the field `expire` +which contains expiration dates for each record. + + +.. _table-garbage-collection-task-migration: + +Migration: Supporting custom tables for garbage collection for both TYPO3 13 and 14 +=================================================================================== + +If your extension supports both TYPO3 13 (or below) and 14 keep the registration +of additional tables in the extensions :file:`ext_localconf.php` until support +for TYPO3 13 is removed: + +.. literalinclude:: _codesnippets/_additional.php.inc + :language: php + :caption: packages/my_extension/ext_localconf.php diff --git a/Documentation/BasicTasks/Index.rst b/Documentation/BasicTasks/Index.rst new file mode 100644 index 0000000..bc0d26c --- /dev/null +++ b/Documentation/BasicTasks/Index.rst @@ -0,0 +1,42 @@ +:navigation-title: Basic tasks + +.. include:: /Includes.rst.txt +.. _base-tasks: + +========================================== +The basic tasks provided by the TYPO3 Core +========================================== + +The Scheduler comes by default with several tasks: + +- **Caching framework garbage collection** : some cache backends do not + have an automatic garbage collection process. For these it is useful + to run this Scheduler task to regularly free some space. + +- **Fileadmin garbage collection** : empties :file:`_recycler_` folders in + the fileadmin. + +- **Table garbage collection** : cleans up old records from any table in + the database. See related section below for more information on + configuration. + +Most TYPO3 console command can also be executed via scheduler. + +The following tasks provide configuration options that need dedicated chapters: + +.. toctree:: + :glob: + :titlesonly: + + * + +.. _other-tasks: + +Providing custom tasks from your extension +========================================== + +More tasks are provided by system extensions, such as the Extension +Manager, which defines one for updating the available extensions list. + +The base tasks are also there to serve as examples for task developers +(see :ref:`developer-guide`). diff --git a/Documentation/BasicTasks/IpAnonymization.rst b/Documentation/BasicTasks/IpAnonymization.rst new file mode 100644 index 0000000..26db9c5 --- /dev/null +++ b/Documentation/BasicTasks/IpAnonymization.rst @@ -0,0 +1,53 @@ +:navigation-title: IP Anonymization task + +.. include:: /Includes.rst.txt + +.. _ip-anonymization-task: + +===================== +IP anonymization task +===================== + +The IP Anonymization task can take a more elaborate +configuration which is detailed below. + +The task anonymizes the IP addresses to enforce the privacy of the persisted data. + +.. contents:: Table of contents + +.. _ip-anonymization-task-example: + +Example: Configure additional tables for the "IP Anonymization" task +==================================================================== + +.. deprecated:: 14.0 + The previous configuration method using + :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\IpAnonymizationTask::class]['options']['tables']` + has been deprecated and will be removed in TYPO3 v15. + + See also: `Changelog Deprecation: #107562 - Ip Anonymization Task configuration via $GLOBALS `_ + +.. literalinclude:: _codesnippets/_tx_scheduler_ip_anonymization.php.inc + :language: php + :caption: packages/my_extension/Configuration/TCA/Overrides/tx_scheduler_ip_anonymization.php + +.. include:: /_Includes/_ExtendingSchedulerTca.rst.txt + +This entry configures that the field `private_ip` of table +`tx_myextension_my_table` can be anonymized after a chosen number of days. + +The field `tstamp` will be taken into account to determine when the database +record was last changed. + +.. _ip-anonymization-task-migration: + +Migration: Supporting custom tables for "IP Anonymization" tasks for both TYPO3 13 and 14 +========================================================================================= + +If your extension supports both TYPO3 13 (or below) and 14 keep the registration +of additional tables in the extensions :file:`ext_localconf.php` until support +for TYPO3 13 is removed: + +.. literalinclude:: _codesnippets/_ext_localconf_ip_anonymization.php.inc + :language: php + :caption: packages/my_extension/ext_localconf.php diff --git a/Documentation/BasicTasks/_codesnippets/_additional.php.inc b/Documentation/BasicTasks/_codesnippets/_additional.php.inc new file mode 100644 index 0000000..a35136a --- /dev/null +++ b/Documentation/BasicTasks/_codesnippets/_additional.php.inc @@ -0,0 +1,16 @@ +getMajorVersion() < 14) { + // TODO: Remove once TYPO3 13 support is dropped + // TYPO3 14 configuration can be found in Configuration/TCA/Overrides/tx_scheduler_garbage_collection.php + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][TableGarbageCollectionTask::class]['options']['tables']['tx_myextension_errorlog'] = [ + 'dateField' => 'tstamp', + 'expirePeriod' => '180', + ]; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][TableGarbageCollectionTask::class]['options']['tables']['tx_myextension_uniqalias'] = [ + 'expireField' => 'expire', + ]; +} diff --git a/Documentation/BasicTasks/_codesnippets/_ext_localconf_ip_anonymization.php.inc b/Documentation/BasicTasks/_codesnippets/_ext_localconf_ip_anonymization.php.inc new file mode 100644 index 0000000..ef61263 --- /dev/null +++ b/Documentation/BasicTasks/_codesnippets/_ext_localconf_ip_anonymization.php.inc @@ -0,0 +1,17 @@ +getMajorVersion() < 14) { + // TODO: Remove once TYPO3 13 support is dropped + // TYPO3 14 configuration can be found in Configuration/TCA/Overrides/tx_scheduler_ip_anonymization.php + $garbageCollectionTables =& $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][IpAnonymizationTask::class]['options']['tables']; + + $garbageCollectionTables = array_replace($garbageCollectionTables ?? [], [ + 'tx_myextension_my_table' => [ + 'dateField' => 'tstamp', + 'ipField' => 'private_ip', + ], + ]); +} diff --git a/Documentation/BasicTasks/_codesnippets/_tx_scheduler_garbage_collection.php.inc b/Documentation/BasicTasks/_codesnippets/_tx_scheduler_garbage_collection.php.inc new file mode 100644 index 0000000..9f3b878 --- /dev/null +++ b/Documentation/BasicTasks/_codesnippets/_tx_scheduler_garbage_collection.php.inc @@ -0,0 +1,17 @@ + [ + 'dateField' => 'tstamp', + 'expirePeriod' => 180, + ], + 'tx_myextension_my_other_table' => [ + 'expireField' => 'expire', + ], + ]); +} diff --git a/Documentation/BasicTasks/_codesnippets/_tx_scheduler_ip_anonymization.php.inc b/Documentation/BasicTasks/_codesnippets/_tx_scheduler_ip_anonymization.php.inc new file mode 100644 index 0000000..8101ebd --- /dev/null +++ b/Documentation/BasicTasks/_codesnippets/_tx_scheduler_ip_anonymization.php.inc @@ -0,0 +1,14 @@ + [ + 'dateField' => 'tstamp', + 'ipField' => 'private_ip', + ], + ]); +} diff --git a/Documentation/DevelopersGuide/CreatingTasks/Index.rst b/Documentation/DevelopersGuide/CreatingTasks/Index.rst new file mode 100644 index 0000000..8be9381 --- /dev/null +++ b/Documentation/DevelopersGuide/CreatingTasks/Index.rst @@ -0,0 +1,129 @@ +:navigation-title: Task development + +.. include:: /Includes.rst.txt +.. _creating-tasks: + +================================ +Creating a custom scheduler task +================================ + +.. important:: + .. versionchanged:: 14.0 + Custom scheduler tasks can be registered as TCA types in table + `tx_scheduler_task`. + + See also: `Changelog Feature: #107526 - Custom TCA types for scheduler tasks `_. + +.. contents:: Table of contents + +.. toctree:: + :glob: + :titlesonly: + + * + +.. seealso:: + Symfony console commands can also be executed as scheduler task: + See :ref:`Create and use Symfony commands in TYPO3 `. + +.. _creating-tasks-implementation: + +Implementation of a custom scheduler task +========================================= + +All scheduler task implementations **must** extend +:php:`\TYPO3\CMS\Scheduler\Task\AbstractTask`. + +.. literalinclude:: _codesnippets/_MyTask.php.inc + :language: php + :caption: packages/my_extension/Classes/MyTask.php + +A custom task implementation **must** override the method `execute(): bool`. +It is the main method that is called when a task is executed. +This method Should return `true` on successful execution, `false` on error. + +.. note:: + There is no error handling by default, errors and failures are expected + to be handled and logged by the client implementation. + +Method `getAdditionalInformation()` **should** be implemented to provide +additional information in the schedulers backend module. + +Scheduler task implementations that provide `additional fields `_ +**should** implement additional methods, expecially `getTaskParameters()`. + +.. _creating-tasks-registration: + +Scheduler task registration and configuration +============================================= + +.. deprecated:: 14.0 + Registering tasks and additional field providers via + :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']` has + been deprecated. + +Custom scheduler tasks can be registered via TCA overrides, for example in +:file:`EXT:my_extension/Configuration/TCA/Overrides/tx_scheduler_my_task.php` + +.. literalinclude:: _codesnippets/_tx_scheduler_my_task.php.inc + :language: php + :caption: EXT:my_extension/Configuration/TCA/Overrides/tx_scheduler_my_task.php + +.. tip:: + + Using the :php:`iconOverlay` option on task type registration, an icon + overlay can be added, which is then displayed in the wizard. This can + be useful for similar task types that use the same "base" `icon`, but + still have to be differentiated. + +.. include:: /_Includes/_ExtendingSchedulerTca.rst.txt + +.. _additional-fields: + +Providing additional fields for scheduler task +============================================== + +.. deprecated:: 14.0 + Registering tasks and additional field providers via + :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']` has + been deprecated. + + The :php-short:`\TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface` and + :php-short:`\TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider` have also + been deprecated. + + Tasks in general and additional fields for tasks are registered via TCA + instead. + + See also: `Migrating tasks with AdditionalFieldProviders to TCA registration `_ + +Additional fields for scheduler tasks are handled via FormEngine and can be +configured via TCA. + +If the task should provide additional fields for configuration options in +the backend module, you need to implement a second class, extending +:php-short:`\TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider`. + +The task needs to be registered via TCA override: + +.. literalinclude:: _codesnippets/_scheduler_my_task_type-additional.php.inc + :language: php + :caption: EXT:my_extension/Configuration/TCA/Overrides/scheduler_my_task_type.php + +And implemented the following methods in your scheduler task if needed: + +.. literalinclude:: _codesnippets/_MyTaskWithAdditionalFields.php.inc + :language: php + :caption: packages/my_extension/Classes/MyTask.php + +.. note:: + Method `getTaskParameters()` should be implemented when + `migrating tasks `_ + + For native TCA tasks, this method is typically no longer needed in custom + tasks after the migration has been done, since field values are then stored + directly in database columns. + +.. seealso:: + There are additional examples in described in the + `Changelog Feature: #107526 - Custom TCA types for scheduler tasks `_. diff --git a/Documentation/DevelopersGuide/CreatingTasks/Migration.rst b/Documentation/DevelopersGuide/CreatingTasks/Migration.rst new file mode 100644 index 0000000..2e92f7e --- /dev/null +++ b/Documentation/DevelopersGuide/CreatingTasks/Migration.rst @@ -0,0 +1,110 @@ +:navigation-title: Migration + +.. include:: /Includes.rst.txt + +.. _task-migration: + +===================================================== +Migration to the TCA registration for scheduler tasks +===================================================== + +.. deprecated:: 14.0 + Registering tasks and additional field providers via + :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']` has + been deprecated. + + The :php-short:`\TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface` and + :php-short:`\TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider` have also + been deprecated. + +.. contents:: Table of contents + + Tasks in general and additional fields for tasks are registered via TCA + instead. +.. _additional-fields-migration: + +Migrating tasks with AdditionalFieldProviders to TCA registration +================================================================= + +Scheduler tasks should now be registered as native task types using TCA. +This provides a more integrated and maintainable approach to task configuration. + +.. _additional-fields-migration-steps: + +Migration steps: +---------------- + +1. Remove the registration from :file:`ext_localconf.php` +2. Create a TCA override file in :file:`Configuration/TCA/Overrides/scheduler_my_task_type.php` +3. Update your task class to implement the new parameter methods +4. Remove the :php:`AdditionalFieldProvider` class if it exists + +.. note:: + The new TCA-based approach automatically migrates existing task data. + When upgrading, existing task configurations are preserved through the + :php:`getTaskParameters()` and :php:`setTaskParameters()` methods. + +.. _additional-fields-migration-example: + +Example migration: Scheduler task with additional fields suppporting TYPO3 13 and 14 +------------------------------------------------------------------------------------ + +Remove the registration from :file:`ext_localconf.php` once TYPO3 13 support is +dropped: + +.. literalinclude:: _codesnippets/_ext_localconf_deprecated.php.inc + :language: php + :caption: packages/my_extension/ext_localconf.php + +And also remove the :php:`MyTaskAdditionalFieldProvider` class once +TYPO3 13 support is dropped. + +Create a TCA override file in :file:`Configuration/TCA/Overrides/scheduler_my_task_type.php`: + +.. literalinclude:: _codesnippets/_scheduler_my_task_type-additional.php.inc + :language: php + :caption: EXT:my_extension/Configuration/TCA/Overrides/scheduler_my_task_type.php + +Update your (existing) task class to implement the new methods: + +.. literalinclude:: _codesnippets/_MyTaskWithAdditionalFieldsMigration.php.inc + :language: php + :caption: packages/my_extension/Classes/MyTask.php + +The new TCA-based approach uses three key methods for parameter handling: + +**getTaskParameters(): array** + This method is already implemented in ``AbstractTask`` to handle task class + properties automatically, but can be overridden in task classes for custom + behavior. + + The method is primarily used: + + * For migration from old serialized task format to new TCA structure + * For non-native (deprecated) task types to store their values in the legacy ``parameters`` field + + For native TCA tasks, this method is typically no longer needed in custom + tasks after the migration has been done, since field values are then stored + directly in database columns. + +**setTaskParameters(array $parameters): void** + Sets field values from an associative array. This method handles: + + * Migration from old AdditionalFieldProvider field names to new TCA field names + * Loading saved task configurations when editing or executing tasks + * Parameter mapping during task creation and updates + * The method should always be implemented, especially for native tasks + + The migration pattern is: :php:`$this->myField = $parameters['oldName'] ?? $parameters['new_tca_field_name'] ?? '';` + +**validateTaskParameters(array $parameters): bool** + *Optional method.* Only implement this for validation that cannot be handled by FormEngine. + + * Basic validation (required, trim, etc.) should be done via TCA configuration (``required`` property and ``eval`` options) + * Use this method for complex business logic validation (e.g., email format validation, external API checks) + * Return ``false`` and add FlashMessage for validation errors + * FormEngine automatically handles standard TCA validation rules + +For a complete working example, see :php:`\TYPO3\CMS\Reports\Task\SystemStatusUpdateTask` +and its corresponding TCA configuration in +:file:`EXT:reports/Configuration/TCA/Overrides/scheduler_system_status_update_task.php`. diff --git a/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTask.php.inc b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTask.php.inc new file mode 100644 index 0000000..1c63387 --- /dev/null +++ b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTask.php.inc @@ -0,0 +1,27 @@ +run('arg1', 'arg2', '…'); + } + + public function getAdditionalInformation() + { + $this->getLanguageService()->sL('LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myTaskInformation'); + } +} diff --git a/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTaskWithAdditionalFields.php.inc b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTaskWithAdditionalFields.php.inc new file mode 100644 index 0000000..2ec92cd --- /dev/null +++ b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTaskWithAdditionalFields.php.inc @@ -0,0 +1,77 @@ +run($this->myField, $this->emailList, '…'); + } + + /** + * Set field values from associative array. + * + * @param array $parameters Values from TCA fields + */ + public function setTaskParameters(array $parameters): void + { + $this->myField = $parameters['my_extension_field'] ?? ''; + $this->emailList = $parameters['my_extension_email_list'] ?? ''; + } + + /** + * Validate task parameters. + * Only implement this method for validation that cannot be handled by FormEngine. + * Basic validation like 'required' should be done via TCA 'eval' configuration. + */ + public function validateTaskParameters(array $parameters): bool + { + $isValid = true; + + // Example: Custom email validation (beyond basic 'required' check) + $emailList = $parameters['my_extension_email_list'] ?? ''; + if (!empty($emailList)) { + $emails = GeneralUtility::trimExplode(',', $emailList, true); + foreach ($emails as $email) { + if (!GeneralUtility::validEmail($email)) { + GeneralUtility::makeInstance(FlashMessageService::class) + ->getMessageQueueByIdentifier() + ->addMessage( + GeneralUtility::makeInstance( + FlashMessage::class, + 'Invalid email address: ' . $email, + '', + ContextualFeedbackSeverity::ERROR + ) + ); + $isValid = false; + } + } + } + + return $isValid; + } + public function getAdditionalInformation(): string + { + return sprintf( + 'Field: %s, Emails: %s', + $this->myField, + $this->emailList + ); + } +} diff --git a/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTaskWithAdditionalFieldsMigration.php.inc b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTaskWithAdditionalFieldsMigration.php.inc new file mode 100644 index 0000000..1e4c6f0 --- /dev/null +++ b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_MyTaskWithAdditionalFieldsMigration.php.inc @@ -0,0 +1,96 @@ +run($this->myField, $this->emailList, '…'); + } + + /** + * Return current field values as associative array. + * This method is called during migration from old serialized tasks + * and when displaying task information. + */ + public function getTaskParameters(): array + { + return [ + 'my_extension_field' => $this->myField, + 'my_extension_email_list' => $this->emailList, + ]; + } + /** + * Set field values from associative array. + * This method handles both old and new parameter formats for migration. + * + * @param array $parameters Values from either old AdditionalFieldProvider or new TCA fields + */ + public function setTaskParameters(array $parameters): void + { + // Handle migration: check old parameter names first, then new TCA field names + $this->myField = $parameters['myField'] ?? $parameters['my_extension_field'] ?? ''; + $this->emailList = $parameters['emailList'] ?? $parameters['my_extension_email_list'] ?? ''; + } + + /** + * Validate task parameters. + * Only implement this method for validation that cannot be handled by FormEngine. + * Basic validation like 'required' should be done via TCA 'eval' configuration. + */ + public function validateTaskParameters(array $parameters): bool + { + $isValid = true; + + // Example: Custom email validation (beyond basic 'required' check) + $emailList = $parameters['my_extension_email_list'] ?? ''; + if (!empty($emailList)) { + $emails = GeneralUtility::trimExplode(',', $emailList, true); + foreach ($emails as $email) { + if (!GeneralUtility::validEmail($email)) { + GeneralUtility::makeInstance(FlashMessageService::class) + ->getMessageQueueByIdentifier() + ->addMessage( + GeneralUtility::makeInstance( + FlashMessage::class, + 'Invalid email address: ' . $email, + '', + ContextualFeedbackSeverity::ERROR + ) + ); + $isValid = false; + } + } + } + + return $isValid; + } + public function getAdditionalInformation(): string + { + $info = []; + if ($this->myField !== '') { + $info[] = 'Field: ' . $this->myField; + } + if ($this->emailList !== '') { + $info[] = 'Emails: ' . $this->emailList; + } + return implode(', ', $info); + } + +} diff --git a/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_ext_localconf_deprecated.php.inc b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_ext_localconf_deprecated.php.inc new file mode 100644 index 0000000..4e2293a --- /dev/null +++ b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_ext_localconf_deprecated.php.inc @@ -0,0 +1,15 @@ +getMajorVersion() < 14) { + // Todo: Remove when TYPO3 13 support is dropped + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][MyTask::class] = [ + 'extension' => 'my_extension', + 'title' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myTask.title', + 'description' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myTask.description', + 'additionalFields' => \MyVendor\MyExtension\Task\MyTaskAdditionalFieldProvider::class, + ]; +} diff --git a/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_scheduler_my_task_type-additional.php.inc b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_scheduler_my_task_type-additional.php.inc new file mode 100644 index 0000000..aa55820 --- /dev/null +++ b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_scheduler_my_task_type-additional.php.inc @@ -0,0 +1,65 @@ + [ + 'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:field.label', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'required' => true, + 'eval' => 'trim', + 'placeholder' => 'Enter value here...', + ], + ], + 'my_extension_email_list' => [ + 'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:emailList.label', + 'config' => [ + 'type' => 'text', + 'rows' => 3, + 'required' => true, + 'placeholder' => 'admin@example.com', + ], + ], + ] + ); + + // Register the task type + ExtensionManagementUtility::addRecordType( + [ + 'label' => 'Some title or LLL:EXT reference', + 'description' => 'Some description or LLL:EXT reference', + 'value' => MyTask::class, + 'icon' => 'mimetypes-x-tx_scheduler_task_group', + 'iconOverlay' => 'content-clock', + 'group' => 'my_extension', + ], + ' + --div--;core.form.tabs:general, + tasktype, + task_group, + description, + my_extension_field, + my_extension_email_list, + --div--;core.form.tabs:timing, + execution_details, + nextexecution, + --palette--;;lastexecution, + --div--;core.form.tabs:access, + disable, + --div--;core.form.tabs:extended,', + [], + '', + 'tx_scheduler_task' + ); +} diff --git a/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_tx_scheduler_my_task.php.inc b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_tx_scheduler_my_task.php.inc new file mode 100644 index 0000000..5f7a663 --- /dev/null +++ b/Documentation/DevelopersGuide/CreatingTasks/_codesnippets/_tx_scheduler_my_task.php.inc @@ -0,0 +1,25 @@ + 'My Custom Task', + 'description' => 'Description of what this task does', + 'value' => MyTask::class, + 'icon' => 'my-custom-icon', + 'iconOverlay' => 'content-clock', + 'group' => 'my_extension', + ], + $GLOBALS['TCA']['tx_scheduler_task']['types']['0']['showitem'], + [], + '', + 'tx_scheduler_task' + ); +} diff --git a/Documentation/DevelopersGuide/Events/Index.rst b/Documentation/DevelopersGuide/Events/Index.rst new file mode 100644 index 0000000..72668f8 --- /dev/null +++ b/Documentation/DevelopersGuide/Events/Index.rst @@ -0,0 +1,54 @@ +:navigation-title: Events + +.. include:: /Includes.rst.txt +.. _scheduler-events: + +========================================== +Events provided by the scheduler extension +========================================== + +The system extension :composer:`typo3/cms-scheduler` provides the following +events: + +.. contents:: Table of contents + +.. _ModifyNewSchedulerTaskWizardItemsEvent: + +ModifyNewSchedulerTaskWizardItemsEvent +====================================== + +The PSR-14 event :php:`\TYPO3\CMS\Scheduler\Event\ModifyNewSchedulerTaskWizardItemsEvent` +allows extensions to modify the items in the +`scheduler task wizard `_. + +.. seealso:: + + `ModifyNewSchedulerTaskWizardItemsEvent (TYPO3 explained) `_ + for examples and the api information. + +.. _AfterTaskExecutionEvent: + +AfterTaskExecutionEvent +======================= + +The PSR-14 event :php:`\TYPO3\CMS\Scheduler\Event\AfterTaskExecutionEvent` +is dispatched after a scheduled task (including command tasks) has been executed. +It provides the following information: + +* :php:`getTask(): AbstractTask` — the executed task object +* :php:`isSuccess(): bool` — whether the task completed without exception +* :php:`getException(): ?\Throwable` — the thrown exception on failure, or :php:`null` on success + +Example listener:: + + use TYPO3\CMS\Scheduler\Event\AfterTaskExecutionEvent; + + final class SchedulerTaskResultListener + { + public function __invoke(AfterTaskExecutionEvent $event): void + { + $task = $event->getTask(); + $status = $event->isSuccess() ? 'success' : 'failure'; + // e.g. send a notification, write to a custom log, etc. + } + } diff --git a/Documentation/DevelopersGuide/Index.rst b/Documentation/DevelopersGuide/Index.rst new file mode 100644 index 0000000..fd88bc4 --- /dev/null +++ b/Documentation/DevelopersGuide/Index.rst @@ -0,0 +1,19 @@ +:navigation-title: Development + +.. include:: /Includes.rst.txt +.. _developer-guide: + +========================== +Scheduler task development +========================== + +The Scheduler makes it very easy to create a new task class. +Furthermore the tasks packaged with this extension provide +a good basis to learn by example. + +.. toctree:: + :maxdepth: 5 + :titlesonly: + :glob: + + */Index diff --git a/Documentation/DevelopersGuide/SchedulerApi/Index.rst b/Documentation/DevelopersGuide/SchedulerApi/Index.rst new file mode 100644 index 0000000..0259142 --- /dev/null +++ b/Documentation/DevelopersGuide/SchedulerApi/Index.rst @@ -0,0 +1,30 @@ +.. include:: /Includes.rst.txt +.. _scheduler-api: + +============= +Scheduler API +============= + +It is possible to refer to the Scheduler from other extensions. Once a +:php:`\TYPO3\CMS\Scheduler\Scheduler` object has been instantiated all of its +public methods can be used. The PHPdoc of the methods should be enough to +understand what each is to be used for. + +The extension ships with a +:php:`\TYPO3\CMS\Scheduler\Domain\Repository\SchedulerTaskRepository` class, +which provides some helpful methods, for example: + +* :php:`findByUid(int $uid)`: this method is used to fetch a registered task + from the database given an ID. + +* :php:`findNextExecutableTask()`: this method returns the next due task. The + return value is the unserialized task object. + +* :php:`findRecordByUid(int $uid)`: is also used to retrieve a registered task + from the database, but it returns the record corresponding to the task + registration and not the task object itself. + +These are the main methods that will be used from outside the +Scheduler as they can retrieve registered tasks from the database. +When a task has been fetched, all public methods from the +:php:`\TYPO3\CMS\Scheduler\Task\AbstractTask` class can be used. diff --git a/Documentation/DevelopersGuide/TaskStorage/Index.rst b/Documentation/DevelopersGuide/TaskStorage/Index.rst new file mode 100644 index 0000000..f9502f2 --- /dev/null +++ b/Documentation/DevelopersGuide/TaskStorage/Index.rst @@ -0,0 +1,58 @@ +.. include:: /Includes.rst.txt +.. _technical-background: +.. _scheduler-task-storage: + +====================== +Scheduler task storage +====================== + +.. versionchanged:: 14.0 + With TYPO3 v14.0 the storage of scheduler tasks switched from the + PHP-serialized storage format in the database to a JSON-based format. + +The scheduler tasks displayed in backend module :guilabel:`Administration > Scheduler` +are stored in the **database** table :sql:`tx_scheduler_task`. Task groups are +stored in table :sql:`tx_scheduler_task_group`. + +.. contents:: Table of contents + +.. _scheduler-task-storage-fields: + +tx_scheduler_task fields +======================== + +The database table :sql:`tx_scheduler_task` contains the following fields: + +`tasktype` + Typically contains the fully qualified class name of the task, for example + :php:`\TYPO3\CMS\Scheduler\Task\CachingFrameworkGarbageCollectionTask` or + the command name, for example `language:update` if the task is implemented as + `Symfony console command `_. +`parameters` + The options configuring the task as JSON-encoded value. +`execution_details` + Contains all details on execution like the type (Recurring / Single), + start and end dates and the frequency in which recurring tasks should be + executed. + +Additionally it stores some information on the last and next execution of the +task, and fields for a description and the group. + +.. _serialized-objects: +.. _save-task-state: +.. _serialized-objects_migration: + +Migration from serialized task objects +====================================== + +.. attention:: + + .. versionchanged:: 14.0 + + The storage format was changed with TYPO3 v14. If the database field + :sql:`tx_scheduler_task:tasktype` is empty this hints at a missing or failed + migration by the upgrade wizard. See also: + `Important: #106532 - Changed database storage format for Scheduler Tasks `_ + + A manual migration is necessary. You can also delete the task and + create it newly. diff --git a/Documentation/Images/AddingATask.png b/Documentation/Images/AddingATask.png new file mode 100644 index 0000000000000000000000000000000000000000..07d7ddd4e127e079a680c8690ee2af49736eb8f4 GIT binary patch literal 29269 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ⅇU04O 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 I-R9x{0mpmCpa1{> literal 0 HcmV?d00001 diff --git a/Documentation/Images/BackendModuleMainView.png b/Documentation/Images/BackendModuleMainView.png new file mode 100644 index 0000000000000000000000000000000000000000..48415eb2f14cf82e95e541172c68f2f95e226fc2 GIT binary patch literal 21565 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 S1K@oFUA^L9)o|(eKmHd>=xY)H literal 0 HcmV?d00001 diff --git a/Documentation/Images/GroupDisabled.png b/Documentation/Images/GroupDisabled.png new file mode 100644 index 0000000000000000000000000000000000000000..fb6706d620af2848d36ffbb4e2421847af6f90d4 GIT binary patch literal 11292 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 I7B}?!AI@6Sga7~l literal 0 HcmV?d00001 diff --git a/Documentation/Images/GroupEdit.png b/Documentation/Images/GroupEdit.png new file mode 100644 index 0000000000000000000000000000000000000000..26120b8f0a29249c9c99e863fdb43bebc0921262 GIT binary patch literal 24690 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
  • a?5k-#{z$ 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 literal 0 HcmV?d00001 diff --git a/Documentation/Images/GroupedTasks.png b/Documentation/Images/GroupedTasks.png new file mode 100644 index 0000000000000000000000000000000000000000..695aad3d797c9d51ac2da6f59234ac6d6f2f55ff GIT binary patch literal 39634 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 P3-|cNy*$2m20FiQMrgAc1>0OX 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 CYLSWn literal 0 HcmV?d00001 diff --git a/Documentation/Images/LateTask.png b/Documentation/Images/LateTask.png new file mode 100644 index 0000000000000000000000000000000000000000..85e0fdbbb611dca8b3effa9dda7e08435f3c7ae6 GIT binary patch literal 4019 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 KSC^n|_wrvC;J$VM literal 0 HcmV?d00001 diff --git a/Documentation/Images/ManualExecution.png b/Documentation/Images/ManualExecution.png new file mode 100644 index 0000000000000000000000000000000000000000..62fb7f7af8ef555a7e34ac3692e1224fad7bf932 GIT binary patch literal 18840 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 BOqBot literal 0 HcmV?d00001 diff --git a/Documentation/Images/MissingTaskClass.png b/Documentation/Images/MissingTaskClass.png new file mode 100644 index 0000000000000000000000000000000000000000..45e93d69d7ca43ae77c4e4fe009a8a72772e4c27 GIT binary patch literal 26199 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 literal 0 HcmV?d00001 diff --git a/Documentation/Images/RestoreTask.png b/Documentation/Images/RestoreTask.png new file mode 100644 index 0000000000000000000000000000000000000000..ea7a019409daa2359d8e0c5fa5924199a268cfc3 GIT binary patch literal 44549 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

    6SZczoZ3tL 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�G7IRE!`_ItRPLM*bT6w?S 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 Y+=;y3Sk#plC@Z$AvettVMe7&;0nQbuVgLXD literal 0 HcmV?d00001 diff --git a/Documentation/Images/SetupCheckButton.png b/Documentation/Images/SetupCheckButton.png new file mode 100644 index 0000000000000000000000000000000000000000..4156ad90ce846de26cc76d2567349a59bd4c3862 GIT binary patch literal 18460 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 literal 0 HcmV?d00001 diff --git a/Documentation/Images/StoppingATask.png b/Documentation/Images/StoppingATask.png new file mode 100644 index 0000000000000000000000000000000000000000..1b17ae69fe96033fe570732c38c19a837ebbbcf5 GIT binary patch literal 12738 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 literal 0 HcmV?d00001 diff --git a/Documentation/Images/TableGarbageCollectionTaskConfiguration-2.png b/Documentation/Images/TableGarbageCollectionTaskConfiguration-2.png new file mode 100644 index 0000000000000000000000000000000000000000..ed932435cdc31251fe053703924e9d035a0a7327 GIT binary patch literal 77041 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 literal 0 HcmV?d00001 diff --git a/Documentation/Images/TableGarbageCollectionTaskConfiguration.png b/Documentation/Images/TableGarbageCollectionTaskConfiguration.png new file mode 100644 index 0000000000000000000000000000000000000000..e4bff33903e5be39830e4471133e19f17eaf3dad GIT binary patch literal 12316 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. + +:Rendered: + |today| + +---- + +The Scheduler supports one-time or periodic execution of tasks that can be +delivered by any extension. + +---- + +**Table of Contents:** + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + Introduction/Index + Installation/Index + BasicTasks/Index + Administration/Index + DevelopersGuide/Index + KnownProblems/Index + +.. Meta Menu + +.. toctree:: + :hidden: + + Sitemap diff --git a/Documentation/Installation/CronJob/Index.rst b/Documentation/Installation/CronJob/Index.rst new file mode 100644 index 0000000..6718333 --- /dev/null +++ b/Documentation/Installation/CronJob/Index.rst @@ -0,0 +1,133 @@ +:navigation-title: Cron Job Set up + +.. include:: /Includes.rst.txt +.. _cron-job: + +================================================== +Setting up the cron job to run the scheduler tasks +================================================== + +Tasks registered with the Scheduler can be run manually from the backend +module. However this is of limited use. To really benefit from the +Scheduler, it must be set up on the server to run regularly. The +following chapters describe how to set this up on Unix or Unix-like +system (including Mac OS X) and on Windows. + +.. _frequency: + +Choosing a frequency +==================== + +Whatever system the Scheduler will run on, the first step is to define +the frequency at which it should run. The Scheduler script should set +up to run pretty often, but not unnecessarily often either. The +frequency should be that of the most often running task or some +frequency that fits all tasks. + +For example, if you have some tasks running every quarter of an hour +and some others running every hour, it is useless to have the +Scheduler run every 5 minutes. On the other hand, if you have tasks +scheduled to run every 10 minutes and others every 15 minutes, you +will want to run the Scheduler every 5 minutes. Indeed, if you run it +only at 10-minute intervals, it will run – assuming it is 8 o'clock – +at 08:10, 08:20, 08:30, etc. So the tasks that should run at 08:15 +will actually run 5 minutes late. + + +.. _unix-mac: + +On Unix and Mac OS X +==================== + +On such systems the Scheduler must be set up as a cron job. There are +several ways to achieve this, although the simplest is probably to add +it to some user's crontab. Edit that user's crontab using: + +.. code-block:: bash + + crontab -e + +and add a line like + +.. tabs:: + + .. group-tab:: Composer-based installation + + .. code-block:: bash + + */15 * * * * /usr/local/bin/php /home/user/www/vendor/bin/typo3 scheduler:run + + .. group-tab:: Legacy installation + + .. code-block:: bash + + */15 * * * * /usr/local/bin/php /home/user/www/typo3/sysext/core/bin/typo3 scheduler:run + +Save the modified crontab. Obviously, the paths have to be adapted to +your system. The above command will call up the Scheduler every 15 +minutes. + +.. seealso:: + + See :ref:`scheduler-shell-script` for more information about + calling the scheduler from the command line. + +If you are editing system crontabs (for example :file:`/etc/crontab` +and :file:`/etc/cron.d/*` ), there will be one additional parameter +to enter, i.e. the user with which the job should run. Example: + +.. tabs:: + + .. group-tab:: Composer-based installation + + .. code-block:: bash + + */15 * * * * www /usr/local/bin/php /home/user/www/vendor/bin/typo3 scheduler:run + + .. group-tab:: Classic installation + + .. code-block:: bash + + */15 * * * * www /usr/local/bin/php /home/user/www/typo3/sysext/core/bin/typo3 scheduler:run + +This will run the job as user "www". + +If you are not familiar with cron syntax, refer to some Unix +administration book or start with the Wikipedia page about it +(https://en.wikipedia.org/wiki/Cron). + +.. _windows: + +On Windows +========== + +On Windows, cron jobs are called "Scheduled tasks" and run with the +:file:`schtasks` utility. :file:`SchTasks.exe` performs operations +similar to those provided by Scheduled Tasks in the Control Panel. You +can use either tool to create, delete, configure, or simply display +scheduled tasks. + +Assuming you want to run the TYPO3 Scheduler every 15 minutes, use the +following command line to create a new task: + +.. tabs:: + + .. group-tab:: Composer-based installation + + .. code-block:: bash + + schtasks /create /sc minute /mo 15 /tn "T3scheduler" /tr "c:\winstaller\php\php.exe c:\winstaller\htdocs\quickstart\vendor\bin\typo3 scheduler:run" + + .. group-tab:: Classic installation + + .. code-block:: bash + + schtasks /create /sc minute /mo 15 /tn "T3scheduler" /tr "c:\winstaller\php\php.exe c:\winstaller\htdocs\quickstart\typo3/sysext/core/bin/typo3 scheduler:run" + +At task creation you will be prompted to give a password or you can +use the :code:`/u` and :code:`/p` switches to provide user and +password information. Note that the user must be a member of the +Administrators group on the computer where the command will run. + +The full reference for :file:`schtasks` is available at: +https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks diff --git a/Documentation/Installation/Index.rst b/Documentation/Installation/Index.rst new file mode 100644 index 0000000..d3bbebe --- /dev/null +++ b/Documentation/Installation/Index.rst @@ -0,0 +1,15 @@ +.. include:: /Includes.rst.txt +.. _installation: + +==================== +Installation & Setup +==================== + +.. toctree:: + :maxdepth: 5 + :titlesonly: + :glob: + + Installing/Index + SetupCheck/Index + CronJob/Index diff --git a/Documentation/Installation/Installing/Index.rst b/Documentation/Installation/Installing/Index.rst new file mode 100644 index 0000000..83001ca --- /dev/null +++ b/Documentation/Installation/Installing/Index.rst @@ -0,0 +1,75 @@ +.. include:: /Includes.rst.txt +.. _installing: + +============ +Installation +============ + +This extension is part of the TYPO3 Core, but not installed by default. + +.. contents:: Table of contents + :local: + +.. _installing-composer: + +Installation with Composer +========================== + +Check whether you are already using the extension with: + +.. code-block:: bash + + composer show | grep scheduler + +This should either give you no result or something similar to: + +.. code-block:: none + + typo3/cms-scheduler v12.4.11 + +If it is not installed yet, use the ``composer require`` command to install +the extension: + +.. code-block:: bash + + composer require typo3/cms-scheduler + +The given version depends on the version of the TYPO3 Core you are using. + + +.. _installing-classic: + +Classic installation without Composer +===================================== + +In an installation without Composer, the extension is already shipped but might +not be activated yet. Activate it as follows: + +#. In the backend, navigate to the :guilabel:`System > Extensions` + module. +#. Click the :guilabel:`Activate` icon for the Scheduler extension. + +.. figure:: /Images/InstallActivate.png + :class: with-border + :alt: Extension manager showing Scheduler extension + + Extension manager showing Scheduler extension + +.. _installing-cnext: + +Next steps +========== + +Once the extension is installed, the following setting is available: + +- **Maximum lifetime** : it may happen that a task crashes while + executing. In this case it will stay in a state marked as "running". + That may prevent it from being executed again, if parallel executions + are denied (see "Tasks execution" above). The maximum lifetime + parameter ensures that old executions are removed after a while. The + lifetime is expressed in **minutes** . The default is 15 minutes. + +.. figure:: /Images/ExtensionConfiguration.png + :alt: Extension configuration + + Configuring the extension settings diff --git a/Documentation/Installation/SetupCheck/Index.rst b/Documentation/Installation/SetupCheck/Index.rst new file mode 100644 index 0000000..524d197 --- /dev/null +++ b/Documentation/Installation/SetupCheck/Index.rst @@ -0,0 +1,26 @@ +:navigation-title: Setup Check + +.. include:: /Includes.rst.txt +.. _setup-check: + +============================================= +Checking the setup of the scheduler extension +============================================= + +The scheduler check provides useful information for setting up cronjobs. + +.. figure:: /Images/SetupCheckButton.png + :alt: The TYPO3 Backend module "Scheduler" with Button "Setup check" highlighted + + Click on the button :guilabel:`Setup check` to open the popup + +.. figure:: /Images/SetupCheck.png + :alt: The "Setup check" modal popup in module "Scheduler" + +The first message shows when the scheduler was last run. If it was never run +there will be a warning displayed. + +The second messages tells you which command (with absolute paths) must be +executed by the cron job. + +The third message shows information about the current server time. diff --git a/Documentation/Introduction/Index.rst b/Documentation/Introduction/Index.rst new file mode 100644 index 0000000..f5697d0 --- /dev/null +++ b/Documentation/Introduction/Index.rst @@ -0,0 +1,103 @@ +.. include:: /Includes.rst.txt +.. _introduction: + +============ +Introduction +============ + +The Scheduler is a system extension that provides a simple interface to automate +the running of tasks in TYPO3. It provides an easier alternative to having to set +up command-line scripts in cron jobs. + +.. _task-management: + +Task management +=============== + +A task is created by extending the Scheduler base class and then registering it so +that it appears in the Scheduler backend module. + +.. _screenshots: + +Screenshots +=========== + +Below is the Scheduler backend module, showing a task that has been registered. +The module shows details about the tasks, i.e. status, type and whether it failed during the +last execution, and individual tasks can be rerun manually by clicking on the +'play' button. + +.. figure:: ../Images/BackendModuleMainView.png + :alt: Scheduler main screen + + Main screen of the Scheduler BE module + +.. _tasks-execution: + +Task execution +============== + +The Scheduler includes a command-line script that needs to be registered once +in the server's crontab to set it up. It is run by the TYPO3 command-line +dispatcher. Every time the Scheduler is launched by the cron daemon it looks for +tasks that are due (or overdue) and executes them. + +When a task is executed it is marked as being +executed in the Scheduler database record (in the +`serialized_executions` field). When the task has finished running, the +execution status is removed from the database record. This makes it +clear whether a task is currently running and also +prevents multiple executions. If a task requires +more time to run than the frequency it is set up for, a +new run will start (which is not always desirable). It is possible +to prevent such parallel (or multiple) executions. + +.. _follow-up: + +Follow-up +========= + +Log messages are written out to the TYPO3 system :guilabel:`Administration > Log` +when a task starts and ends and when parallel execution has been blocked. This +provides a trace of events of the tasks. + +A task that fails may also raise an exception reporting the reasons for failure. +The exception message will be logged in the Scheduler database table and +displayed in the backend module. + +Tasks have no command-line output as they are designed to run in the background. + +Symfony Console commands can also be run as tasks (see +`https://docs.typo3.org/permalink/t3coreapi:symfony-console-commands`__). +These tasks can specify all commandline arguments that are available to Symfony +Console commands. + +.. _glossary: + +Glossary +======== + +Task + Specifically, a piece of code that does a precise task and can be registered + with the Scheduler in order to execute that piece of code at a precise time, + either once or recurrently. + +Task class + A type of task, for example, the "IP Anonymization" task is one + particular task class. Its function is to anonymize IP addresses to enforce + the privacy of persisted data. The "Optimize MySQL database tables" + task executes "OPTIMIZE TABLE" statements on selected database tables. + +Registered task + An instance of a task class that has been + registered with the Scheduler. A given task class may be registered + several times, for example if it needs to be executed with different + parameters. + +.. _credits: + +Credits +======= + +The Scheduler is a derivation of the Gabriel extension originally developed by +Christian Jul Jensen and further developed by Markus Friedrich. diff --git a/Documentation/KnownProblems/Index.rst b/Documentation/KnownProblems/Index.rst new file mode 100644 index 0000000..1a891ea --- /dev/null +++ b/Documentation/KnownProblems/Index.rst @@ -0,0 +1,15 @@ +.. include:: /Includes.rst.txt +.. _known-problems: + +============== +Known problems +============== + +The main problem currently is that a running task cannot be killed, +because no relation exists to the (cron) process that is running the +Scheduler. The process pid could be retrieved, but that may not work +on all platforms. And can the process be killed afterwards? Anyway it +may not be safe to do that. + +.. seealso:: + `How to handle a truly “hung” task `_ diff --git a/Documentation/Sitemap.rst b/Documentation/Sitemap.rst new file mode 100644 index 0000000..cd06f7d --- /dev/null +++ b/Documentation/Sitemap.rst @@ -0,0 +1,9 @@ +:template: sitemap.html + +.. include:: /Includes.rst.txt + +======= +Sitemap +======= + +.. The sitemap.html template will insert here the page tree automatically. diff --git a/Documentation/_Includes/_ExtendingSchedulerTca.rst.txt b/Documentation/_Includes/_ExtendingSchedulerTca.rst.txt new file mode 100644 index 0000000..a34425c --- /dev/null +++ b/Documentation/_Includes/_ExtendingSchedulerTca.rst.txt @@ -0,0 +1,6 @@ +.. warning:: + If your extension overrides the TCA of the scheduler extension, it **must** + be loaded **after** :composer:`typo3/cms-scheduler`, otherwise the + configuration might take no effect. + + See `Extension loading order `_ diff --git a/Documentation/guides.xml b/Documentation/guides.xml new file mode 100644 index 0000000..b179688 --- /dev/null +++ b/Documentation/guides.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..a5a5bc4 --- /dev/null +++ b/README.rst @@ -0,0 +1,11 @@ +============================= +TYPO3 extension ``scheduler`` +============================= + +The Scheduler supports one-time or periodic execution of tasks that can be +delivered by any extension. + +:Repository: https://github.com/typo3/typo3 +:Issues: https://forge.typo3.org/ +:Read online: https://docs.typo3.org/c/typo3/cms-scheduler/main/en-us/ +:Packagist: https://packagist.org/packages/typo3/cms-scheduler diff --git a/Resources/Private/Language/label.xlf b/Resources/Private/Language/label.xlf new file mode 100644 index 0000000..69eef81 --- /dev/null +++ b/Resources/Private/Language/label.xlf @@ -0,0 +1,15 @@ + + + + + + Delete task group + + + + + Delete task + + + + diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000..a6d017b --- /dev/null +++ b/Resources/Private/Language/locallang.xlf @@ -0,0 +1,579 @@ + + + +

    + + + Cancel + + + Create group + + + Scheduled tasks + + + Setup check + + + New task + + + New group + + + Group name + + + CLI script + + + System is running in Composer Mode + + + + The system is running in composer mode, the path to the `typo3` executable could not be reliably determined. + By default, the command to run in composer mode is `vendor/bin/typo3 scheduler:run`. + + + + Last run + + + automatically + + + Backend types + + + Number of days until removing files + + + Check/uncheck all + + + Task + + + Cron + + + Definition type + + + Interval + + + Cron command + + + Description + + + Email + + + End (HH:MM DD-MM-YYYY) + + + Start at + + + Run periodically until + + + Execute + + + Execute on next cron job + + + Frequency + + + Frequency (seconds or cron command) + + + ID + + + Last Execution + + + Manual + + + manually + + + Next Execution + + + Parallel Execution + + + Allow Parallel Execution + + + Server time + + + Sleep time + + + Start (HH:MM DD-MM-YYYY) + + + Storage to index + + + Number of files per run + + + Registered extractors + + + %1$s with priority %2$d + + + Following extractors have been registered and will be used when running this task + + + There currently is no extractor registered. This task will have no effect. + + + Clean all available tables + + + Note: Entries from all available tables are deleted based on the table-specific "expirePeriod" option. + + + Table to clean up + + + Table: %s + + + All tables + + + Delete entries older than given number of days + + + Database tables + + + Status + + + Type + + + Run task multiple times? + + + Recurring + + + Single + + + Task group + + + Not assigned to any task group + + + disabled + + + Unused task groups + + + Group + + + Enable group + + + Disable group + + + Edit group + + + Broken tasks + + + Save and create new task + + + Add option + + + Set option + + + Run on CLI + + + The script to execute the Scheduler from the command line is: <strong>"%s scheduler:run"</strong>. + + + The web server user is allowed execute this script. + + + The web server user is not allowed to execute this script. + + + Are you sure you want to delete this task? + + + The task could not be deleted. + + + The task was successfully deleted. + + + The end date is before the start date. + + + Task "%s" with uid "%s" has been executed. + + + Invalid frequency. Please enter either a number of seconds or a valid cron command. The cron parser said: %1$s [code: %2$d]. + + + The information about the last execution of the Scheduler is incomplete. + + + Invalid frequency. Please enter either a number of seconds or a valid cron command. + + + Please enter a sleep time greater or equal to 0 (zero). + + + The Scheduler was last started (%1$s) on %2$s at %3$s and ended on %4$s at %5$s. + + + A running task may not be deleted. + + + A running task may not be edited. + + + The task is not running. There are no executions to unmark. + + + Please enter an email address. + + + No frequency was defined, either as an interval or as a cron command. + + + The Scheduler has never yet run or the information about the last run has been lost. + + + The selected task type must be either "single execution" or "recurring execution". + + + No tasks found + + + There are currently no configured tasks found. You can create a new one. + + + There are currently no tasks available in the system to be configured. + + + Task "%s" with uid "%s" has not been executed. + + + Execution of task "%1$s" failed with the following message: %2$s + + + Scheduling of task "%1$s" failed with the following message: %2$s + + + Toggling disabled state of task "%1$s" failed with the following message: %2$s + + + Stopping task "%1$s" failed with the following message: %2$s + + + The execution failed, but the error message could not be retrieved (it was probably too large). Sorry. + + + Execution failed: %1$d, %2$s + + + Please define a start date. + + + Start date is invalid. + + + End date is invalid. + + + Number of days is invalid. + + + Some of the selected cache backends do not exist. Please select valid backends only. + + + Please select at least one cache backend. + + + The following tasks are broken. If your task doesn't have a registered class, you should consider reinstalling the extension that provided them or simply deleting the task. In case the error is "Cannot unserialize [class]", the announced class must be removed from your task class, as it is not allowed to be used due to security reasons. Please check also if any of the listed tasks have to be recreated manually (which can be necessary after an extension update due to breaking changes or missing migration scripts).]]> + + + Error message + + + %s registered scheduler tasks can not be executed + + + Please check the erroneous tasks.]]> + + + This screen checks if the requisites for running the Scheduler as a cron job are fulfilled. It also displays information about the last run of the Scheduler. + + + Current server time is %s. + + + All dates and times in the Scheduler are measured according to the server's time, as the Scheduler is run purely on the server-side. + + + Are you sure you want to mark this task as not running? Note that this will not stop the actual script (if unsure please refer to the manual). + + + The task could not be marked as non-running. + + + The task was successfully marked as non-running. + + + The requested task (UID: %d) was not found. + + + The task could not be updated. + + + Some of the selected database tables do not exist. Please select only valid tables. + + + Please select at least one database table. + + + Command with identifier "%s" has not been registered. + + + Error parsing current set of arguments: "%s". + + + Error parsing current set of options: "%s". + + + Argument "%s" is mandatory. + + + Task "%s" with uid "%s" has been enabled. + + + Task "%s" with uid "%s" has been disabled. + + + Task "%s" with uid "%s" has been enabled and queued for execution at next scheduler CLI run. + + + Task "%s" with uid "%s" has been queued for execution at next scheduler CLI run. + + + Successfully deleted scheduler group. + + + Failed to delete scheduler group. + + + Invalid Task type + + + Default expire period of %d days, configured for selected table "%s", will be used. + + + None + + + Status + + + Progress + + + disabled + + + disabled by group + + + failure + + + late + + + running + + + scheduled + + + Task + + + TYPO3 Scheduler administration module + + + Run task + + + Run task on next cron job + + + Caching framework garbage collection + + + This task calls the garbage collection of configured caching framework caches which use one of the selected backends. This will free some space in cache backends which do not have an internal garbage collection. In case of the default database backend it is advisable to run this task once a day when the database is mostly idle. + + + [OBSOLETE] File Abstraction Layer: Indexing job + + + Runs indexing tasks based on an indexing configuration and a storage/folder information. + + + File Abstraction Layer: Update storage index + + + Updates the Index/Cache Data of a Storage; only needed if changes to the storage are possible outside the backend (FTP, RemoteStorages). + + + File Abstraction Layer: Extract metadata in storage + + + Extracts metadata for all files in storage which have been changed since last run. + + + Table garbage collection + + + Task to delete old entries from specific tables like sys_log. + + + Fileadmin garbage collection + + + This task empties all "_recycler_" folders below fileadmin. This helps free some space in the file system. + + + Execute console commands + + + Allows regular console commands to be configured and executed through the scheduler framework. + + + Optimize MySQL database tables + + + This task executes "OPTIMIZE TABLE" statements on the selected database tables. This helps to reduce storage space and improve I/O efficiency. Warning: tables will be locked during the optimization process. + + + Anonymize IP addresses in database tables + + + This task anonymizes the IP addresses from specific tables like sys_log to enforce the privacy of the persisted data. + + + Table to anonymize + + + Table: %s after %s days + + + All tables + + + Handle entries older than given number of days + + + Mask level + + + Last byte for IPv4 / Interface ID for IPv6 + + + Last 2 bytes for IPv4 / Interface & SLA ID for IPv6 + + + Command configuration + + + Configure available arguments and options for the command. + + + Every day-of-week from Monday through Friday at 09:00 and 15:00 + + + Every 2 hours + + + Every 20 minutes + + + Every Tuesday at 07:00 + + + configuration.]]> + + + Last Scheduler run + + + %1$s at %2$s, Duration %3$s, (started %4$s) + + + Error + + + Could not create group + + + Enter a group name + + + Edit group name + + + Search for any kind of tasks + + + Unfortunately no scheduler task matches your query, please try a different one. + + + + Delete this task group? You can restore it in the module "Recycler". + + + Delete this task? You can restore it in the module "Recycler". + + + {count, plural, one {# task} other {# tasks}} + + + {count, plural, one {# unused task group} other {# unused task groups}} + + + + diff --git a/Resources/Private/Language/locallang_em.xlf b/Resources/Private/Language/locallang_em.xlf new file mode 100644 index 0000000..b1c35d6 --- /dev/null +++ b/Resources/Private/Language/locallang_em.xlf @@ -0,0 +1,11 @@ + + + +
    + + + Maximum lifetime: Set the maximum runtime (in minutes) for a scheduler task. Tasks still running after this time are removed from the execution list but are not stopped. + + + + diff --git a/Resources/Private/Language/locallang_tca.xlf b/Resources/Private/Language/locallang_tca.xlf new file mode 100644 index 0000000..2eec90b --- /dev/null +++ b/Resources/Private/Language/locallang_tca.xlf @@ -0,0 +1,104 @@ + + + +
    + + + Scheduler task + + + Task type + + + Belongs to task group + + + Priority + + + High + + + Regular + + + Low + + + Description + + + Settings + + + Schedule + + + Next run planned at + + + Last run started at + + + Error details from last run + + + Last run via + + + Handle entries older than given number of days + + + Database tables + + + File storage + + + Scheduler task group + + + Group name + + + Color + + + TYPO3 Orange + + + White + + + Gray + + + Black + + + Blue + + + Purple + + + Teal + + + Green + + + Magenta + + + Yellow + + + Red + + + Description + + + + diff --git a/Resources/Private/Language/module.xlf b/Resources/Private/Language/module.xlf new file mode 100644 index 0000000..9897bf4 --- /dev/null +++ b/Resources/Private/Language/module.xlf @@ -0,0 +1,17 @@ + + + +
    + + + TYPO3 Scheduler + + + Scheduler administration module. Check all registered tasks. Add, modify or delete tasks. + + + Scheduler + + + + diff --git a/Resources/Private/Partials/GroupUnusedList.fluid.html b/Resources/Private/Partials/GroupUnusedList.fluid.html new file mode 100644 index 0000000..0c6f440 --- /dev/null +++ b/Resources/Private/Partials/GroupUnusedList.fluid.html @@ -0,0 +1,90 @@ + + +
    + + + + + + + + + + + + + + + + + +
    + + {group.groupName} + + +

    {group.description}

    +
    +
    +
    + +
    + + + + +
    +
    + + + + + + + + + +
    +
    +
    + +
    +
    + +
    +
    + + diff --git a/Resources/Private/Partials/MultiRecordSelectionActions.fluid.html b/Resources/Private/Partials/MultiRecordSelectionActions.fluid.html new file mode 100644 index 0000000..22c642f --- /dev/null +++ b/Resources/Private/Partials/MultiRecordSelectionActions.fluid.html @@ -0,0 +1,25 @@ + + + + + diff --git a/Resources/Private/Partials/ServerTime.fluid.html b/Resources/Private/Partials/ServerTime.fluid.html new file mode 100644 index 0000000..5d915f5 --- /dev/null +++ b/Resources/Private/Partials/ServerTime.fluid.html @@ -0,0 +1,20 @@ + + + +

    +

    + + +

    +
    + + diff --git a/Resources/Private/Partials/TaskList.fluid.html b/Resources/Private/Partials/TaskList.fluid.html new file mode 100644 index 0000000..947e984 --- /dev/null +++ b/Resources/Private/Partials/TaskList.fluid.html @@ -0,0 +1,491 @@ + + + + + + + + + + +
    + + + +
    + + +
    +
    +
    + +
    +
    +
    +
    + + + + +
    {f:translate(key:'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidTaskClass') -> f:format.raw()}
    + + + + + + + + + + + + + + + + + + + + + + + + +
    {errorClass.uid}{errorClass.class}{errorClass.errorMessage} +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    + +
    + +
    +
    +
    + + + {f:translate(key: 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.noGroup')} + + + + {group.groupName} + + + + +

    {group.description}

    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + +
    +
    +
    + +
    + + + +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + {task.uid} +
    + + {task.fullTitle} + + + + +
    {task.additionalInformation}
    +
    +
    +
    + +
    {task.description}
    +
    +
    + + + + + + + + + {label.text} + + + + + + + + + + + + + - + {task.frequency} + +
    {f:translate(key: task.priorityLabel, default: task.priorityLabel)}
    +
    + + + + + + + + + + + +
    + + + + + + + + +
    + - +
    +
    + + - + + + + + + + + + + + + + + +
    + +
    +
    + +
    + + + + + + + + + + + + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + +
    +
    + diff --git a/Resources/Private/Templates/CheckScreen.fluid.html b/Resources/Private/Templates/CheckScreen.fluid.html new file mode 100644 index 0000000..9699858 --- /dev/null +++ b/Resources/Private/Templates/CheckScreen.fluid.html @@ -0,0 +1,30 @@ + + +

    + + + + + + +

    {f:translate(key: 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.composerMode')}

    +
    +
    + + +

    {f:translate(key: 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.cliScript', arguments: '{0: script}') -> f:format.raw()}

    + +
    +
    +
    + + + + diff --git a/Resources/Private/Templates/CommandConfiguration.fluid.html b/Resources/Private/Templates/CommandConfiguration.fluid.html new file mode 100644 index 0000000..9822e96 --- /dev/null +++ b/Resources/Private/Templates/CommandConfiguration.fluid.html @@ -0,0 +1,67 @@ + + + +
    +
    + +
    + +
    {field.description}
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    {field.description}
    +
    + + +
    + + +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/Resources/Private/Templates/Email/TestTask.fluid.html b/Resources/Private/Templates/Email/TestTask.fluid.html new file mode 100644 index 0000000..6605b57 --- /dev/null +++ b/Resources/Private/Templates/Email/TestTask.fluid.html @@ -0,0 +1,14 @@ + +Scheduler Test Task + + UID: {data.uid}
    + Sitename: {typo3.sitename}
    + Called by: {data.calledBy}
    + Tstamp: {f:format.date(format:'Y-m-d H:i:s',date:data.tstamp)} [{data.tstamp}]
    + maxLifetime: {data.maxLifetime}
    + Start: {f:format.date(format:'Y-m-d H:i:s', date:exec.start)} [{exec.start}]
    + End: {f:format.date(format:'Y-m-d H:i:s', date:exec.end)} [{exec.end}]-
    + Interval: {exec.interval}
    + multiple: {f:if(condition:exec.multiple,then:'yes',else:'no')}
    + cronCmd: {f:if(condition:exec.cronCmd,then:cronCmd,else:'not used')} +
    diff --git a/Resources/Private/Templates/Email/TestTask.fluid.txt b/Resources/Private/Templates/Email/TestTask.fluid.txt new file mode 100644 index 0000000..8578534 --- /dev/null +++ b/Resources/Private/Templates/Email/TestTask.fluid.txt @@ -0,0 +1,15 @@ + +Scheduler Test Task + +UID: {data.uid} +Sitename: {typo3.sitename} +Called by: {data.calledBy} +Tstamp: {f:format.date(format:'Y-m-d H:i:s',date:data.tstamp)} [{data.tstamp}] +maxLifetime: {data.maxLifetime} +start: {f:format.date(format:'Y-m-d H:i:s', date:exec.start)} [{exec.start}] +end: {f:format.date(format:'Y-m-d H:i:s', date:exec.end)} [{exec.end}]- +interval: {exec.interval} +multiple: {f:if(condition:exec.multiple,then:'yes',else:'no')} +cronCmd: {f:if(condition:exec.cronCmd,then:cronCmd,else:'not used')} + + diff --git a/Resources/Private/Templates/ListTasks.fluid.html b/Resources/Private/Templates/ListTasks.fluid.html new file mode 100644 index 0000000..3079a3e --- /dev/null +++ b/Resources/Private/Templates/ListTasks.fluid.html @@ -0,0 +1,48 @@ + + + + + + + + + + + + + +

    + + + + + + + + +

    + + {f:translate(key: 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add')} + +
    + +

    +
    +
    +
    +
    +
    + + +

    + +
    + + +
    + + diff --git a/Resources/Private/Templates/NewSchedulerTask/Wizard.fluid.html b/Resources/Private/Templates/NewSchedulerTask/Wizard.fluid.html new file mode 100644 index 0000000..7e009be --- /dev/null +++ b/Resources/Private/Templates/NewSchedulerTask/Wizard.fluid.html @@ -0,0 +1,11 @@ + + + + + + diff --git a/Resources/Private/Templates/ServerTime.fluid.html b/Resources/Private/Templates/ServerTime.fluid.html new file mode 100644 index 0000000..e8f2fdc --- /dev/null +++ b/Resources/Private/Templates/ServerTime.fluid.html @@ -0,0 +1,10 @@ + + +
    + +
    + + diff --git a/Resources/Public/Icons/Extension.png b/Resources/Public/Icons/Extension.png new file mode 100644 index 0000000000000000000000000000000000000000..3196735cee2f26729be521e4ca17efec7fc46670 GIT binary patch literal 861 zcmWmDc}$ZB7zXeH3WSJ)`)pv$iQup-BpyVP5l28_XVAlMKW$5&s2*#~~s`B7qGF2Z>xrd5}gyb_&U)Xr#m-mBh!_vB-!+rU2)Jkc*Hd zMverEB%D8sykz93BR>NLnJ7GmA~}@VxSWIHTofx%avoRmPVW0YsMUD z25)C!nS=E$tQO2$u<$PyY*<{t(ju0Zu(FKR6|AmeZ4K+|*w_Fta<;OXTvE@Zhn@8B zoNJqm+RpLV{GuXed%@OEIE19~8iUH<5cQ3nZZXH~!nC`)KDWUBqQ>*`4x=$}BWvjx zjR^Lw_fh+QA0^TcvhTd(nEc%S`dNEBoEPZqXJ~V;quCqhE5v;1iXo34ZW|jN<6kN3 zxM1}kuU*=EqdnBj)A0AX#!scnW)9@O9@go_)>_uBk2?BcmwOKetfS>@!|qGPxGptJX*KKE*ybD~sp)ZDXklcx9L-?bx6|4`HU hft7~}*))03e-2(nEj11Su~zb5C`?8q{q7M#!T(yV-KPKm literal 0 HcmV?d00001 diff --git a/Resources/Public/JavaScript/form-engine/element/timing-options-element.js b/Resources/Public/JavaScript/form-engine/element/timing-options-element.js new file mode 100644 index 0000000..678e5e7 --- /dev/null +++ b/Resources/Public/JavaScript/form-engine/element/timing-options-element.js @@ -0,0 +1,13 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +import s from"@typo3/backend/form-engine.js";import{selector as a}from"@typo3/core/literals.js";import g from"@typo3/core/event/regular-event.js";var i;(function(t){t.single="1",t.recurring="2"})(i||(i={}));class u extends HTMLElement{async connectedCallback(){await s.ready(),this.fieldPrefix=this.getAttribute("fieldPrefix"),this.registerEventHandler()}registerEventHandler(){new g("change",()=>{this.toggleRunningType()}).delegateTo(this,a`input[name='${this.fieldPrefix}[runningType]']`),this.toggleRunningType()}toggleRunningType(){const l=this.querySelector(a`input[name='${this.fieldPrefix}[runningType]']:checked`).value;this.querySelectorAll(".t3js-timing-options-end, .t3js-timing-options-parallel, .t3js-timing-options-frequency").forEach(n=>{if(n.style.display=l===i.recurring?"block":"none",n.classList.contains("t3js-timing-options-frequency")){const e=n.querySelector("input[data-formengine-validation-rules]");let r,o;l===i.recurring?(r=e.getAttribute("data-formengine-validation-rules")==="[]",o='[{"type":"required"}]'):(r=e.getAttribute("data-formengine-validation-rules")!=="[]",o="[]"),r&&(e.setAttribute("data-formengine-validation-rules",o),TYPO3.FormEngine.Validation.initializeInputField(e.dataset.formengineInputName),TYPO3.FormEngine.Validation.validate())}})}}window.customElements.define("typo3-formengine-element-timing-options",u); diff --git a/Resources/Public/JavaScript/new-scheduler-task-wizard-button.js b/Resources/Public/JavaScript/new-scheduler-task-wizard-button.js new file mode 100644 index 0000000..88106e8 --- /dev/null +++ b/Resources/Public/JavaScript/new-scheduler-task-wizard-button.js @@ -0,0 +1,13 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +import{property as f,customElement as a}from"lit/decorators.js";import{PseudoButtonLitElement as d}from"@typo3/backend/element/pseudo-button.js";import c from"@typo3/backend/modal.js";import{SeverityEnum as m}from"@typo3/backend/enum/severity.js";import"@typo3/backend/new-record-wizard.js";var s=function(i,e,r,n){var p=arguments.length,t=p<3?e:n===null?n=Object.getOwnPropertyDescriptor(e,r):n,u;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")t=Reflect.decorate(i,e,r,n);else for(var l=i.length-1;l>=0;l--)(u=i[l])&&(t=(p<3?u(t):p>3?u(e,r,t):u(e,r))||t);return p>3&&t&&Object.defineProperty(e,r,t),t};let o=class extends d{buttonActivated(){this.url&&c.advanced({content:this.url,title:this.subject,severity:m.notice,size:c.sizes.large,type:c.types.ajax})}};s([f({type:String})],o.prototype,"url",void 0),s([f({type:String})],o.prototype,"subject",void 0),o=s([a("typo3-scheduler-new-task-wizard-button")],o);export{o as NewSchedulerTaskWizardButton}; diff --git a/Resources/Public/JavaScript/scheduler-add-group.js b/Resources/Public/JavaScript/scheduler-add-group.js new file mode 100644 index 0000000..fbe8a1a --- /dev/null +++ b/Resources/Public/JavaScript/scheduler-add-group.js @@ -0,0 +1,13 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +import r from"@typo3/backend/modal.js";import{html as d}from"lit";import m from"@typo3/core/ajax/ajax-request.js";import l from"@typo3/backend/notification.js";import n from"~labels/scheduler.messages";class p{constructor(){this.selector=".t3js-create-group",this.initialize()}initialize(){const o=document.querySelector(this.selector);o&&o.addEventListener("click",s=>{s.preventDefault();const e=d`
    the whole record to add a custom color and description.
    `;r.advanced({content:e,title:n.get("function.group.add"),size:r.sizes.small,buttons:[{trigger:()=>r.dismiss(),text:n.get("button.cancel"),btnClass:"btn-default",name:"cancel"},{trigger:()=>{r.currentModal.querySelector('form[name="scheduler-create-group"]').requestSubmit()},text:n.get("button.group.modalOk"),btnClass:"btn-primary",name:"ok"}]}).addEventListener("typo3-modal-shown",()=>{r.currentModal.querySelector('input[name="action[createGroup]"]').focus()})})}createGroup(o){o.preventDefault();const e=new FormData(o.target).get("action[createGroup]").toString(),t="NEW"+Math.random().toString(36).slice(2,7),c="&data[tx_scheduler_task_group]["+t+"][pid]=0&data[tx_scheduler_task_group]["+t+"][groupName]="+encodeURIComponent(e);return new m(TYPO3.settings.ajaxUrls.record_process).post(c,{headers:{"Content-Type":"application/x-www-form-urlencoded","X-Requested-With":"XMLHttpRequest"}}).then(async a=>await a.resolve()).then(a=>(a.hasErrors&&l.error(n.get("msg.group.notification.error.title"),n.get("msg.group.notification.error.message")+' "'+e+'"!'),a.messages.forEach(i=>{l.info(i.title,i.message)}),a)).catch(()=>{l.error(n.get("msg.group.notification.error.title"),n.get("msg.group.notification.error.message")+' "'+e+'"!')}).finally(()=>{if(document.querySelector("#task_group")){const i=document.forms[0],u=i.querySelector('[name="tx_scheduler[select_latest_group]"]');u.value="1",i.submit()}else window.location.reload();r.dismiss()})}editWholeRecord(o){o.preventDefault();const s=r.currentModal.querySelector('input[name="action[createGroup]"]'),e=s?s.value.trim():"",t=new URL(top.TYPO3.settings.FormEngine.moduleUrl,window.location.origin);t.searchParams.set("edit[tx_scheduler_task_group][0]","new"),e&&t.searchParams.set("defVals[tx_scheduler_task_group][groupName]",e),t.searchParams.set("returnUrl",window.location.href),r.dismiss(),window.location.href=t.toString()}}var g=new p;export{g as default}; diff --git a/Resources/Public/JavaScript/scheduler-sortable-groups.js b/Resources/Public/JavaScript/scheduler-sortable-groups.js new file mode 100644 index 0000000..d8d9ef8 --- /dev/null +++ b/Resources/Public/JavaScript/scheduler-sortable-groups.js @@ -0,0 +1,13 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +import n from"sortablejs";import l from"@typo3/backend/ajax-data-handler.js";class i{constructor(){this.container=".t3js-group-draggable-container",this.dragHandle=".t3js-group-draggable-handle",this.initialize()}initialize(){const r=document.querySelector(this.container);r&&(new n(r,{handle:this.dragHandle,onMove:e=>"taskGroupId"in e.related.dataset&&Number(e.related.dataset.taskGroupId)!==0,onSort:e=>{const o=e.target.children[e.newDraggableIndex-1];let d=0;o&&(d=+("-"+o.dataset.taskGroupId));const t=Number(e.item.dataset.taskGroupId),a="tx_scheduler_task_group",s={component:"contextmenu",action:"delete",table:a,uid:t};l.process("cmd["+a+"]["+t+"][move][action]=paste&cmd["+a+"]["+t+"][move][target]="+d+"&cmd["+a+"]["+t+"][move][update][colPos]=0&cmd["+a+"]["+t+"][move][update][sys_language_uid]=0",s)}}),document.querySelectorAll(this.dragHandle).forEach(e=>{e.disabled=!1}))}}var c=new i;export{c as default}; diff --git a/Resources/Public/JavaScript/scheduler.js b/Resources/Public/JavaScript/scheduler.js new file mode 100644 index 0000000..8c41a32 --- /dev/null +++ b/Resources/Public/JavaScript/scheduler.js @@ -0,0 +1,13 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +import r from"@typo3/backend/sortable-table.js";import i from"@typo3/core/event/regular-event.js";import c from"@typo3/backend/icons.js";import l from"@typo3/backend/storage/persistent.js";import{MultiRecordSelectionSelectors as d}from"@typo3/backend/multi-record-selection.js";import u from"@typo3/core/document-service.js";class a{constructor(){u.ready().then(()=>{this.initializeEvents()})}static storeCollapseState(o,s){let t={};l.isset("moduleData.scheduler")&&(t=l.get("moduleData.scheduler"));const e={};e[o]=s?1:0,t={...t,...e},l.set("moduleData.scheduler",t)}initializeEvents(){document.querySelectorAll("[data-scheduler-table]").forEach(o=>{new r(o)}),new i("show.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new i("hide.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new i("multiRecordSelection:action:go",this.executeTasks.bind(this)).bindTo(document),new i("multiRecordSelection:action:go_cron",this.executeTasks.bind(this)).bindTo(document)}toggleCollapseIcon(o){const s=o.type==="hide.bs.collapse",t=document.querySelector('.t3js-toggle-table[data-bs-target="#'+o.target.id+'"] .t3js-icon');t!==null&&c.getIcon(s?"actions-view-list-expand":"actions-view-list-collapse",c.sizes.small).then(e=>{t.replaceWith(document.createRange().createContextualFragment(e))}),a.storeCollapseState(o.target.dataset.table,s)}executeTasks(o){const s=document.querySelector('[data-multi-record-selection-form="'+o.detail.identifier+'"]');if(s===null)return;const t=[];if(o.detail.checkboxes.forEach(e=>{const n=e.closest(d.elementSelector);n!==null&&n.dataset.taskId&&t.push(n.dataset.taskId)}),t.length){if(o.type==="multiRecordSelection:action:go_cron"){const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","action[scheduleCron]"),e.setAttribute("value",t.join(",")),s.append(e)}else{const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","action[execute]"),e.setAttribute("value",t.join(",")),s.append(e)}s.submit()}}}var m=new a;export{m as default}; diff --git a/Resources/Public/JavaScript/setup-check-button.js b/Resources/Public/JavaScript/setup-check-button.js new file mode 100644 index 0000000..f991030 --- /dev/null +++ b/Resources/Public/JavaScript/setup-check-button.js @@ -0,0 +1,13 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +import{property as s,customElement as m}from"lit/decorators.js";import{PseudoButtonLitElement as y}from"@typo3/backend/element/pseudo-button.js";import l from"@typo3/backend/modal.js";import{SeverityEnum as d}from"@typo3/backend/enum/severity.js";var f=function(n,e,o,i){var p=arguments.length,t=p<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,o):i,u;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")t=Reflect.decorate(n,e,o,i);else for(var c=n.length-1;c>=0;c--)(u=n[c])&&(t=(p<3?u(t):p>3?u(e,o,t):u(e,o))||t);return p>3&&t&&Object.defineProperty(e,o,t),t};let r=class extends y{buttonActivated(){this.url&&l.advanced({content:this.url,title:this.subject,severity:d.notice,size:l.sizes.large,type:l.types.ajax})}};f([s({type:String})],r.prototype,"url",void 0),f([s({type:String})],r.prototype,"subject",void 0),r=f([m("typo3-scheduler-setup-check-button")],r);export{r as SetupCheckButton}; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f7e26b1 --- /dev/null +++ b/composer.json @@ -0,0 +1,53 @@ +{ + "name": "typo3/cms-scheduler", + "type": "typo3-cms-framework", + "description": "TYPO3 CMS Scheduler - Schedule tasks to run once or periodically at a specific time.", + "homepage": "https://typo3.community/", + "funding": [ + { + "type": "membership", + "url": "https://typo3.org/membership" + } + ], + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "TYPO3 Core Team", + "email": "typo3cms@typo3.org", + "role": "Developer" + } + ], + "support": { + "issues": "https://forge.typo3.org/issues/", + "forum": "https://talk.typo3.org/", + "source": "https://github.com/TYPO3/typo3/", + "docs": "https://docs.typo3.org/c/typo3/cms-scheduler/main/en-us/", + "rss": "https://news.typo3.com/rss/", + "chat": "https://typo3.community/meet/slack/", + "security": "https://typo3.org/security/" + }, + "config": { + "sort-packages": true + }, + "require": { + "typo3/cms-core": "15.0.*@dev" + }, + "conflict": { + "typo3/cms": "*" + }, + "extra": { + "branch-alias": { + "dev-main": "15.0.x-dev" + }, + "typo3/cms": { + "extension-key": "scheduler" + } + }, + "autoload": { + "psr-4": { + "TYPO3\\CMS\\Scheduler\\": "Classes/" + } + } +} diff --git a/ext_conf_template.txt b/ext_conf_template.txt new file mode 100644 index 0000000..3535108 --- /dev/null +++ b/ext_conf_template.txt @@ -0,0 +1,2 @@ +# cat=basic//; type=string; label=LLL:EXT:scheduler/Resources/Private/Language/locallang_em.xlf:scheduler.config.maxLifetime +maxLifetime = 1440 diff --git a/ext_localconf.php b/ext_localconf.php new file mode 100644 index 0000000..412efd8 --- /dev/null +++ b/ext_localconf.php @@ -0,0 +1,45 @@ + 'schedulerTimingOptions', + 'priority' => 40, + 'class' => TimingOptionsElement::class, +]; + +$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1758189546] = [ + 'nodeName' => 'taskTypeInfo', + 'priority' => 40, + 'class' => TaskTypeInfoElement::class, +]; + +$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1758791054] = [ + 'nodeName' => 'registeredExtractors', + 'priority' => 40, + 'class' => RegisteredExtractors::class, +]; + +$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1758906785] = [ + 'nodeName' => 'expirePeriodInformation', + 'priority' => 40, + 'class' => ExpirePeriodInformation::class, +]; + +$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1759164368] = [ + 'nodeName' => 'schedulableCommandConfiguration', + 'priority' => 40, + 'class' => SchedulableCommandConfigurationElement::class, +]; + +// Register hook for datamap +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = SchedulerTaskPersistenceValidator::class; diff --git a/ext_tables.sql b/ext_tables.sql new file mode 100644 index 0000000..a8805c4 --- /dev/null +++ b/ext_tables.sql @@ -0,0 +1,6 @@ +CREATE TABLE tx_scheduler_task ( + # @deprecated will be removed in TYPO3 v16 when the upgrade wizard is going to be removed + serialized_task_object mediumblob, + serialized_executions mediumblob, + KEY index_nextexecution (nextexecution) +);