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; } }