TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Scheduler\Controller;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Scheduler\Event\ModifyNewSchedulerTaskWizardItemsEvent;
|
||||
use TYPO3\CMS\Scheduler\Service\TaskService;
|
||||
|
||||
/**
|
||||
* New Scheduler Task wizard. This is the modal that pops up when clicking "New Task" in scheduler module.
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
class NewSchedulerTaskController
|
||||
{
|
||||
protected string $returnUrl = '';
|
||||
protected array $defaultValues = [];
|
||||
|
||||
public function __construct(
|
||||
protected readonly UriBuilder $uriBuilder,
|
||||
protected readonly BackendViewFactory $backendViewFactory,
|
||||
protected readonly EventDispatcherInterface $eventDispatcher,
|
||||
protected readonly TaskService $taskService,
|
||||
) {}
|
||||
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->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'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Scheduler\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Module\ModuleData;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\View\ViewInterface;
|
||||
use TYPO3\CMS\Scheduler\Domain\Repository\SchedulerTaskRepository;
|
||||
use TYPO3\CMS\Scheduler\Execution;
|
||||
use TYPO3\CMS\Scheduler\Scheduler;
|
||||
use TYPO3\CMS\Scheduler\Service\TaskService;
|
||||
|
||||
/**
|
||||
* Scheduler backend module.
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
final readonly class SchedulerModuleController
|
||||
{
|
||||
public function __construct(
|
||||
private Scheduler $scheduler,
|
||||
private SchedulerTaskRepository $taskRepository,
|
||||
private IconFactory $iconFactory,
|
||||
private UriBuilder $uriBuilder,
|
||||
private ModuleTemplateFactory $moduleTemplateFactory,
|
||||
private ComponentFactory $componentFactory,
|
||||
private Context $context,
|
||||
private TaskService $taskService,
|
||||
private PageRenderer $pageRenderer,
|
||||
private Registry $registry,
|
||||
private ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Entry dispatcher method.
|
||||
*/
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$parsedBody = $request->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'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user