diff --git a/Build/Sources/TypeScript/scheduler/scheduler.ts b/Build/Sources/TypeScript/scheduler/scheduler.ts index 5dc3f32b4e49b970682c1fe9aef403087ec65d2d..15851b8b90c1fa7728a187f2f61d828c7900d9ed 100644 --- a/Build/Sources/TypeScript/scheduler/scheduler.ts +++ b/Build/Sources/TypeScript/scheduler/scheduler.ts @@ -55,15 +55,15 @@ class Scheduler { private static storeCollapseState(table: string, isCollapsed: boolean): void { let storedModuleData = {}; - if (PersistentStorage.isset('moduleData.system_txschedulerM1')) { - storedModuleData = PersistentStorage.get('moduleData.system_txschedulerM1'); + if (PersistentStorage.isset('moduleData.scheduler_manage')) { + storedModuleData = PersistentStorage.get('moduleData.scheduler_manage'); } const collapseConfig: any = {}; collapseConfig[table] = isCollapsed ? 1 : 0; $.extend(storedModuleData, collapseConfig); - PersistentStorage.set('moduleData.system_txschedulerM1', storedModuleData); + PersistentStorage.set('moduleData.scheduler_manage', storedModuleData); } constructor() { diff --git a/typo3/sysext/core/Tests/Acceptance/Application/Frontend/SitemapXmlCest.php b/typo3/sysext/core/Tests/Acceptance/Application/Frontend/SitemapXmlCest.php index bdb856e0e6b72b872784340a4b0f63f56f9bd5b4..77e80e86cfe1f7ed94d6882c6e23f690c04f35b8 100644 --- a/typo3/sysext/core/Tests/Acceptance/Application/Frontend/SitemapXmlCest.php +++ b/typo3/sysext/core/Tests/Acceptance/Application/Frontend/SitemapXmlCest.php @@ -35,18 +35,11 @@ final class SitemapXmlCest $I->waitForElement('#typo3-pagetree-tree .nodes .node', 5); $pageTree->openPath(['styleguide frontend demo']); $I->switchToContentFrame(); - $I->click('.t3js-module-docheader-bar a[title="View webpage"]'); - $I->executeInSelenium(function (RemoteWebDriver $webdriver) { - $handles = $webdriver->getWindowHandles(); - $lastWindow = end($handles); - $webdriver->switchTo()->window($lastWindow); - }); - - // Get current url - $url = $this->getCurrentURL($I); - + $I->waitForElementNotVisible('#nprogress'); + $dataDispatchArgs = $I->grabAttributeFrom('.module-docheader-bar-column-left a:first-child', 'data-dispatch-args'); + $url = json_decode($dataDispatchArgs, false, 512, JSON_THROW_ON_ERROR); // Add Sitemap parameter to URL - $I->amOnUrl($url . '?type=1533906435'); + $I->amOnPage(str_replace('/typo3temp/var/tests/acceptance', '', $url[0]) . '?type=1533906435'); } private function sitemapDataProvider(): array @@ -97,24 +90,6 @@ final class SitemapXmlCest $I->assertIsNumeric($priority); } - private function getCurrentURL(ApplicationTester $I, int $attempt = 1): string - { - $url = $I->executeInSelenium(function (RemoteWebDriver $webdriver) { - return $webdriver->getCurrentURL(); - }); - - if ($attempt > 4) { - return $url ?? ''; - } - - if (!$url || str_contains($url, 'about:blank')) { - $I->wait(0.5); - $url = $this->getCurrentURL($I, $attempt + 1); - } - - return $url; - } - /** * Find text by given slug part */ diff --git a/typo3/sysext/core/Tests/Acceptance/Application/Scheduler/TasksCest.php b/typo3/sysext/core/Tests/Acceptance/Application/Scheduler/TasksCest.php index bbf4b4afca7c51bcd7db37b39f3fc6ba9006741b..cc3a9ef6b30cebd01fc1eedf5d456d81049f6c93 100644 --- a/typo3/sysext/core/Tests/Acceptance/Application/Scheduler/TasksCest.php +++ b/typo3/sysext/core/Tests/Acceptance/Application/Scheduler/TasksCest.php @@ -28,9 +28,9 @@ final class TasksCest public function _before(ApplicationTester $I): void { $I->useExistingSession('admin'); - $I->scrollTo('[data-modulemenu-identifier="system_txschedulerM1"]'); - $I->see('Scheduler', '[data-modulemenu-identifier="system_txschedulerM1"]'); - $I->click('[data-modulemenu-identifier="system_txschedulerM1"]'); + $I->scrollTo('[data-modulemenu-identifier="scheduler"]'); + $I->see('Scheduler', '[data-modulemenu-identifier="scheduler"]'); + $I->click('[data-modulemenu-identifier="scheduler"]'); $I->switchToContentFrame(); } @@ -61,7 +61,7 @@ final class TasksCest // run the task $I->click('button[name="execute"]'); $I->waitForText('Task "System Status Update (reports)" with uid "1" has been executed.'); - $I->seeElement('[data-module-name="system_txschedulerM1"] .disabled'); + $I->seeElement('[data-module-name="scheduler_manage"] .disabled'); $I->see('disabled'); } @@ -86,7 +86,7 @@ final class TasksCest { $I->wantTo('See a enable button for a task'); $I->click('//button[contains(@title, "Enable")]', '#tx_scheduler_form'); - $I->dontSeeElement('[data-module-name="system_txschedulerM1"] .disabled'); + $I->dontSeeElement('[data-module-name="scheduler_manage"] .disabled'); $I->dontSee('disabled'); $I->wantTo('See a disable button for a task'); // Give tooltips some time to fully init @@ -94,8 +94,8 @@ final class TasksCest $I->moveMouseOver('//button[contains(@title, "Disable")]'); $I->wait(1); $I->click('//button[contains(@title, "Disable")]'); - $I->waitForElementVisible('[data-module-name="system_txschedulerM1"]'); - $I->seeElement('[data-module-name="system_txschedulerM1"] .disabled'); + $I->waitForElementVisible('[data-module-name="scheduler_manage"]'); + $I->seeElement('[data-module-name="scheduler_manage"] .disabled'); $I->see('disabled'); } @@ -125,18 +125,18 @@ final class TasksCest public function canSwitchToSetupCheck(ApplicationTester $I): void { - $I->selectOption('select[name=SchedulerJumpMenu]', 'Scheduler setup check'); - $I->waitForElementVisible('[data-module-name="system_txschedulerM1"]'); + $I->selectOption('select[name=moduleMenu]', 'Scheduler setup check'); + $I->waitForElementVisible('[data-module-name="scheduler_setupcheck"]'); $I->see('Scheduler setup check'); $I->see('This screen checks if the requisites for running the Scheduler as a cron job are fulfilled'); } public function canSwitchToInformation(ApplicationTester $I): void { - $I->selectOption('select[name=SchedulerJumpMenu]', 'Available scheduler tasks'); - $I->waitForElementVisible('[data-module-name="system_txschedulerM1"]'); + $I->selectOption('select[name=moduleMenu]', 'Available scheduler tasks'); + $I->waitForElementVisible('[data-module-name="scheduler_availabletasks"]'); $I->see('Available scheduler tasks'); - $I->canSeeNumberOfElements('[data-module-name="system_txschedulerM1"] table tbody tr', [1, 10000]); + $I->canSeeNumberOfElements('[data-module-name="scheduler_availabletasks"] table tbody tr', [1, 10000]); } public function canCreateNewTaskGroupFromEditForm(ApplicationTester $I): void @@ -164,7 +164,7 @@ final class TasksCest $I->click('button[value="save"]'); $I->waitForElementNotVisible('#t3js-ui-block'); $I->click('a[title="Close"]'); - $I->waitForElementVisible('[data-module-name="system_txschedulerM1"]'); + $I->waitForElementVisible('[data-module-name="scheduler_manage"]'); $I->canSee('new task group', '.panel-heading'); } diff --git a/typo3/sysext/scheduler/Classes/Controller/AvailableSchedulerTasksController.php b/typo3/sysext/scheduler/Classes/Controller/AvailableSchedulerTasksController.php new file mode 100644 index 0000000000000000000000000000000000000000..6a5f01bd4f9ee8fedd769bd7172c237c000ff05b --- /dev/null +++ b/typo3/sysext/scheduler/Classes/Controller/AvailableSchedulerTasksController.php @@ -0,0 +1,99 @@ +<?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\Controller as BackendController; +use TYPO3\CMS\Backend\Template\ModuleTemplate; +use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; +use TYPO3\CMS\Core\Localization\LanguageService; + +/** + * Render information about available task classes. + * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. + */ +#[BackendController] +class AvailableSchedulerTasksController +{ + public function __construct( + protected readonly ModuleTemplateFactory $moduleTemplateFactory, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $languageService = $this->getLanguageService(); + $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', + ]); + + $view->assign('registeredClasses', $this->getRegisteredClasses()); + $view->setTitle( + $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'), + $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info') + ); + $view->makeDocHeaderModuleMenu(); + $this->addDocHeaderShortcutButton($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info')); + return $view->renderResponse('InfoScreen'); + } + + protected function addDocHeaderShortcutButton(ModuleTemplate $moduleTemplate, string $name): void + { + $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar(); + $shortcutButton = $buttonBar->makeShortcutButton() + ->setRouteIdentifier('scheduler_availabletasks') + ->setDisplayName($name); + $buttonBar->addButton($shortcutButton); + } + + /** + * This method fetches a list of all classes that have been registered with the Scheduler + * For each item the following information is provided, as an associative array: + * + * ['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 + * ['provider'] => Name of class that implements the interface for additional fields, if necessary + * + * The name of the class itself is used as the key of the list array + */ + protected function getRegisteredClasses(): array + { + $languageService = $this->getLanguageService(); + $list = []; + foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'] ?? [] as $class => $registrationInformation) { + $title = isset($registrationInformation['title']) ? $languageService->sL($registrationInformation['title']) : ''; + $description = isset($registrationInformation['description']) ? $languageService->sL($registrationInformation['description']) : ''; + $list[$class] = [ + 'extension' => $registrationInformation['extension'], + 'title' => $title, + 'description' => $description, + 'provider' => $registrationInformation['additionalFields'] ?? '', + ]; + } + return $list; + } + + protected function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php index 9fdb694740e8fd6817d511f173009012a3ab2375..30fd674de027b4895017c58d2896d693d83271aa 100644 --- a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php +++ b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php @@ -19,6 +19,7 @@ namespace TYPO3\CMS\Scheduler\Controller; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Backend\Attribute\Controller as BackendController; use TYPO3\CMS\Backend\Module\ModuleData; use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Template\Components\ButtonBar; @@ -26,19 +27,16 @@ use TYPO3\CMS\Backend\Template\ModuleTemplate; use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; 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\Icon; use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Localization\LanguageService; -use TYPO3\CMS\Core\Registry; use TYPO3\CMS\Core\SysLog\Action\Database as SystemLogDatabaseAction; use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification; use TYPO3\CMS\Core\SysLog\Type as SystemLogType; use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper; use TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface; use TYPO3\CMS\Scheduler\CronCommand\NormalizeCommand; use TYPO3\CMS\Scheduler\Exception\InvalidDateException; @@ -54,6 +52,7 @@ use TYPO3\CMS\Scheduler\Task\TaskSerializer; * * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. */ +#[BackendController] class SchedulerModuleController { protected Action $currentAction; @@ -71,10 +70,9 @@ class SchedulerModuleController /** * Entry dispatcher method. * - * There are three arguments involved regarding main module routing: - * * 'submodule': Third level module selection - "scheduler" (list, add, edit), "info", "check" - * * 'action': Sub module "scheduler" only: add, edit, delete, toggleHidden, ... - * * 'CMD': Sub module "scheduler" only. "save", "close", "new" when adding / editing a task. + * There are two arguments involved regarding main module routing: + * * 'action': add, edit, delete, toggleHidden, ... + * * 'CMD': "save", "close", "new" when adding / editing a task. * A better naming would be "nextAction", but the split button ModuleTemplate and * DocumentSaveActions.ts can not cope with a renaming here and need "CMD". */ @@ -89,21 +87,8 @@ class SchedulerModuleController 'time' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] ?? 'H:i', ]); - // See if action from main module drop down is given, else fetch from user data and update if needed. $backendUser = $this->getBackendUser(); $moduleData = $request->getAttribute('moduleData'); - if ($moduleData->clean('subModule', ['scheduler', 'info', 'check'])) { - $backendUser->pushModuleData($moduleData->getModuleIdentifier(), $moduleData->toArray()); - } - $requestedSubModule = (string)$moduleData->get('subModule'); - - // 'info' and 'check' submodules have no other action and can be rendered directly. - if ($requestedSubModule === 'info') { - return $this->renderInfoView($view); - } - if ($requestedSubModule === 'check') { - return $this->renderCheckView($view); - } // Simple actions from list view. if (!empty($parsedBody['action']['toggleHidden'])) { @@ -193,87 +178,6 @@ class SchedulerModuleController return $this->currentAction; } - /** - * Render 'Setup Check' view. - */ - protected function renderCheckView(ModuleTemplate $view): ResponseInterface - { - $languageService = $this->getLanguageService(); - - // Display information about last automated run, as stored in the system registry. - $registry = GeneralUtility::makeInstance(Registry::class); - $lastRun = $registry->get('tx_scheduler', 'lastRun'); - $lastRunMessageLabel = 'msg.noLastRun'; - $lastRunMessageLabelArguments = []; - $lastRunSeverity = InfoboxViewHelper::STATE_WARNING; - if (is_array($lastRun)) { - if (empty($lastRun['end']) || empty($lastRun['start']) || empty($lastRun['type'])) { - $lastRunMessageLabel = 'msg.incompleteLastRun'; - $lastRunSeverity = InfoboxViewHelper::STATE_WARNING; - } 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 = InfoboxViewHelper::STATE_INFO; - } - } - - // Information about cli script. - $script = $this->determineExecutablePath(); - $isExecutableMessageLabel = 'msg.cliScriptNotExecutable'; - $isExecutableSeverity = InfoboxViewHelper::STATE_ERROR; - $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 = InfoboxViewHelper::STATE_OK; - } - } - - $view->assignMultiple([ - 'composerMode' => $composerMode, - 'script' => $script, - 'lastRunMessageLabel' => $lastRunMessageLabel, - 'lastRunMessageLabelArguments' => $lastRunMessageLabelArguments, - 'lastRunSeverity' => $lastRunSeverity, - 'isExecutableMessageLabel' => $isExecutableMessageLabel, - 'isExecutableSeverity' => $isExecutableSeverity, - ]); - $view->setTitle( - $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'), - $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check') - ); - $this->addDocHeaderModuleDropDown($view, 'check'); - $this->addDocHeaderShortcutButton($view, 'check', $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check')); - return $view->renderResponse('CheckScreen'); - } - - /** - * Render information about available task classes. - */ - protected function renderInfoView(ModuleTemplate $view): ResponseInterface - { - $languageService = $this->getLanguageService(); - $view->assign('registeredClasses', $this->getRegisteredClasses()); - $view->setTitle( - $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'), - $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info') - ); - $this->addDocHeaderModuleDropDown($view, 'info'); - $this->addDocHeaderShortcutButton($view, 'info', $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info')); - return $view->renderResponse('InfoScreen'); - } - /** * Set a task to deleted. */ @@ -445,13 +349,13 @@ class SchedulerModuleController // Adding a group in edit view switches to formEngine. returnUrl is needed to go back to edit view on group record close. 'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(), ]); - $this->addDocHeaderModuleDropDown($view, 'scheduler'); + $view->makeDocHeaderModuleMenu(); $this->addDocHeaderCloseAndSaveButtons($view); $view->setTitle( $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'), $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add') ); - $this->addDocHeaderShortcutButton($view, 'scheduler', $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add'), 'add'); + $this->addDocHeaderShortcutButton($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add'), 'add'); return $view->renderResponse('AddTaskForm'); } @@ -544,7 +448,7 @@ class SchedulerModuleController // Adding a group in edit view switches to formEngine. returnUrl is needed to go back to edit view on group record close. 'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(), ]); - $this->addDocHeaderModuleDropDown($view, 'scheduler'); + $view->makeDocHeaderModuleMenu(); $this->addDocHeaderCloseAndSaveButtons($view); $view->setTitle( $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'), @@ -554,7 +458,6 @@ class SchedulerModuleController $this->addDocHeaderDeleteButton($view, $taskUid); $this->addDocHeaderShortcutButton( $view, - 'scheduler', sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.edit'), $taskName), 'edit', $taskUid @@ -740,12 +643,12 @@ class SchedulerModuleController $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'), $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler') ); - $this->addDocHeaderModuleDropDown($view, 'scheduler'); + $view->makeDocHeaderModuleMenu(); $this->addDocHeaderReloadButton($view); if (!empty($registeredClasses)) { $this->addDocHeaderAddButton($view); } - $this->addDocHeaderShortcutButton($view, 'scheduler', $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler')); + $this->addDocHeaderShortcutButton($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler')); return $view->renderResponse('ListTasks'); } @@ -983,23 +886,6 @@ class SchedulerModuleController return $currentAdditionalFields; } - protected function addDocHeaderModuleDropDown(ModuleTemplate $moduleTemplate, string $activeEntry): void - { - $languageService = $this->getLanguageService(); - $menu = $moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu(); - $menu->setIdentifier('SchedulerJumpMenu'); - foreach (['scheduler', 'check', 'info'] as $entry) { - $item = $menu->makeMenuItem() - ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1', ['subModule' => $entry])) - ->setTitle($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.' . $entry)); - if ($entry === $activeEntry) { - $item->setActive(true); - } - $menu->addMenuItem($item); - } - $moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu); - } - protected function addDocHeaderReloadButton(ModuleTemplate $moduleTemplate): void { $languageService = $this->getLanguageService(); @@ -1007,7 +893,7 @@ class SchedulerModuleController $reloadButton = $buttonBar->makeLinkButton() ->setTitle($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload')) ->setIcon($this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)) - ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1')); + ->setHref((string)$this->uriBuilder->buildUriFromRoute('scheduler_manage')); $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT, 1); } @@ -1019,7 +905,7 @@ class SchedulerModuleController ->setTitle($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.add')) ->setShowLabelText(true) ->setIcon($this->iconFactory->getIcon('actions-plus', Icon::SIZE_SMALL)) - ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1', ['action' => 'add'])); + ->setHref((string)$this->uriBuilder->buildUriFromRoute('scheduler_manage', ['action' => 'add'])); $buttonBar->addButton($addButton, ButtonBar::BUTTON_POSITION_LEFT, 2); } @@ -1031,7 +917,7 @@ class SchedulerModuleController ->setTitle($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:close')) ->setIcon($this->iconFactory->getIcon('actions-close', Icon::SIZE_SMALL)) ->setShowLabelText(true) - ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1')); + ->setHref((string)$this->uriBuilder->buildUriFromRoute('scheduler_manage')); $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 2); $saveAndCloseButton = $buttonBar->makeInputButton() ->setName('CMD') @@ -1070,7 +956,7 @@ class SchedulerModuleController $languageService = $this->getLanguageService(); $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar(); $deleteButton = $buttonBar->makeLinkButton() - ->setHref((string)$this->uriBuilder->buildUriFromRoute('system_txschedulerM1', ['action' => ['delete' => $taskUid]])) + ->setHref((string)$this->uriBuilder->buildUriFromRoute('scheduler_manage', ['action' => ['delete' => $taskUid]])) ->setClasses('t3js-modal-trigger') ->setDataAttributes([ 'severity' => 'warning', @@ -1084,10 +970,10 @@ class SchedulerModuleController $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 6); } - protected function addDocHeaderShortcutButton(ModuleTemplate $moduleTemplate, string $moduleMenuIdentifier, string $name, string $action = '', int $taskUid = 0): void + protected function addDocHeaderShortcutButton(ModuleTemplate $moduleTemplate, string $name, string $action = '', int $taskUid = 0): void { $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar(); - $shortcutArguments = ['subModule' => $moduleMenuIdentifier]; + $shortcutArguments = []; if ($action) { $shortcutArguments['action'] = $action; } @@ -1095,30 +981,12 @@ class SchedulerModuleController $shortcutArguments['uid'] = $taskUid; } $shortcutButton = $buttonBar->makeShortcutButton() - ->setRouteIdentifier('system_txschedulerM1') + ->setRouteIdentifier('scheduler_manage') ->setDisplayName($name) ->setArguments($shortcutArguments); $buttonBar->addButton($shortcutButton); } - 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); - } - protected function getHumanReadableTaskName(AbstractTask $task): string { $class = get_class($task); diff --git a/typo3/sysext/scheduler/Classes/Controller/SchedulerSetupCheckController.php b/typo3/sysext/scheduler/Classes/Controller/SchedulerSetupCheckController.php new file mode 100644 index 0000000000000000000000000000000000000000..146d5fa18e9777c7ffb8258c3de94d4e3d4756f8 --- /dev/null +++ b/typo3/sysext/scheduler/Classes/Controller/SchedulerSetupCheckController.php @@ -0,0 +1,142 @@ +<?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 Psr\Http\Server\RequestHandlerInterface; +use TYPO3\CMS\Backend\Attribute\Controller as BackendController; +use TYPO3\CMS\Backend\Template\ModuleTemplate; +use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; +use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Core\Registry; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper; + +/** + * Render 'Setup Check' view. + * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. + */ +#[BackendController] +class SchedulerSetupCheckController implements RequestHandlerInterface +{ + public function __construct( + protected readonly Registry $registry, + protected readonly ModuleTemplateFactory $moduleTemplateFactory, + ) { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $languageService = $this->getLanguageService(); + $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', + ]); + + // Display information about last automated run, as stored in the system registry. + $lastRun = $this->registry->get('tx_scheduler', 'lastRun'); + $lastRunMessageLabel = 'msg.noLastRun'; + $lastRunMessageLabelArguments = []; + $lastRunSeverity = InfoboxViewHelper::STATE_WARNING; + if (is_array($lastRun)) { + if (empty($lastRun['end']) || empty($lastRun['start']) || empty($lastRun['type'])) { + $lastRunMessageLabel = 'msg.incompleteLastRun'; + $lastRunSeverity = InfoboxViewHelper::STATE_WARNING; + } 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 = InfoboxViewHelper::STATE_INFO; + } + } + + // Information about cli script. + $script = $this->determineExecutablePath(); + $isExecutableMessageLabel = 'msg.cliScriptNotExecutable'; + $isExecutableSeverity = InfoboxViewHelper::STATE_ERROR; + $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 = InfoboxViewHelper::STATE_OK; + } + } + + $view->assignMultiple([ + 'composerMode' => $composerMode, + 'script' => $script, + 'lastRunMessageLabel' => $lastRunMessageLabel, + 'lastRunMessageLabelArguments' => $lastRunMessageLabelArguments, + 'lastRunSeverity' => $lastRunSeverity, + 'isExecutableMessageLabel' => $isExecutableMessageLabel, + 'isExecutableSeverity' => $isExecutableSeverity, + ]); + $view->setTitle( + $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'), + $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check') + ); + $view->makeDocHeaderModuleMenu(); + $this->addDocHeaderShortcutButton($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check')); + return $view->renderResponse('CheckScreen'); + } + + protected function addDocHeaderShortcutButton(ModuleTemplate $moduleTemplate, string $name): void + { + $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar(); + $shortcutButton = $buttonBar->makeShortcutButton() + ->setRouteIdentifier('scheduler_availabletasks') + ->setDisplayName($name); + $buttonBar->addButton($shortcutButton); + } + + 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); + } + + protected function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/typo3/sysext/scheduler/Classes/SystemInformation/ToolbarItemProvider.php b/typo3/sysext/scheduler/Classes/SystemInformation/ToolbarItemProvider.php index c1a9761ffe85584ecf2f6ef6386fc6cdc65d1b66..8913cfa3a4c711dc18850011b705ef22f31b3871 100644 --- a/typo3/sysext/scheduler/Classes/SystemInformation/ToolbarItemProvider.php +++ b/typo3/sysext/scheduler/Classes/SystemInformation/ToolbarItemProvider.php @@ -54,21 +54,19 @@ final class ToolbarItemProvider return; } $languageService = $this->getLanguageService(); - $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); if (!$this->schedulerWasExecuted()) { + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); // Display system message if the Scheduler has never yet run - $moduleIdentifier = 'system_txschedulerM1'; - $moduleParams = ['subModule' => 'check']; + $moduleIdentifier = 'scheduler_setupcheck'; $systemInformationToolbarItem->addSystemMessage( sprintf( $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:systemmessage.noLastRun'), - (string)$uriBuilder->buildUriFromRoute($moduleIdentifier, $moduleParams) + (string)$uriBuilder->buildUriFromRoute($moduleIdentifier) ), InformationStatus::STATUS_WARNING, 1, $moduleIdentifier, - http_build_query($moduleParams) ); } else { // Display information about the last Scheduler execution diff --git a/typo3/sysext/scheduler/Configuration/Backend/Modules.php b/typo3/sysext/scheduler/Configuration/Backend/Modules.php index 016abc5e90f1003936e52a0aab55723400945bb2..0c504fd0d214498ccc3d20f694b8116b00726b51 100644 --- a/typo3/sysext/scheduler/Configuration/Backend/Modules.php +++ b/typo3/sysext/scheduler/Configuration/Backend/Modules.php @@ -6,10 +6,10 @@ use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController; * Definitions for modules provided by EXT:scheduler */ return [ - 'system_txschedulerM1' => [ + 'scheduler' => [ 'parent' => 'system', 'access' => 'admin', - 'path' => '/module/system/scheduler', + 'path' => '/module/scheduler', 'iconIdentifier' => 'module-scheduler', 'labels' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf', 'routes' => [ @@ -17,8 +17,39 @@ return [ 'target' => SchedulerModuleController::class . '::handleRequest', ], ], - 'moduleData' => [ - 'subModule' => 'scheduler', + ], + 'scheduler_manage' => [ + 'parent' => 'scheduler', + 'access' => 'inherit', + 'path' => '/module/scheduler/manage', + 'labels' => ['title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.scheduler'], + 'routes' => [ + '_default' => [ + 'target' => SchedulerModuleController::class . '::handleRequest', + ], + ], + 'aliases' => ['system_txschedulerM1'], + ], + 'scheduler_availabletasks' => [ + 'parent' => 'scheduler', + 'access' => 'inherit', + 'path' => '/module/scheduler/available-tasks', + 'labels' => ['title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.info'], + 'routes' => [ + '_default' => [ + 'target' => \TYPO3\CMS\Scheduler\Controller\AvailableSchedulerTasksController::class . '::handle', + ], + ], + ], + 'scheduler_setupcheck' => [ + 'parent' => 'scheduler', + 'access' => 'inherit', + 'path' => '/module/scheduler/check-setup', + 'labels' => ['title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:function.check'], + 'routes' => [ + '_default' => [ + 'target' => \TYPO3\CMS\Scheduler\Controller\SchedulerSetupCheckController::class . '::handle', + ], ], ], ]; diff --git a/typo3/sysext/scheduler/Configuration/Services.yaml b/typo3/sysext/scheduler/Configuration/Services.yaml index 4742b815a72d4948be957206e383b87b87389dac..d039d85a37f2870952cfae9b485adf0143ab3bb0 100644 --- a/typo3/sysext/scheduler/Configuration/Services.yaml +++ b/typo3/sysext/scheduler/Configuration/Services.yaml @@ -7,9 +7,6 @@ services: TYPO3\CMS\Scheduler\: resource: '../Classes/*' - TYPO3\CMS\Scheduler\Controller\SchedulerModuleController: - tags: [ 'backend.controller' ] - TYPO3\CMS\Scheduler\Command\SchedulerCommand: tags: - name: 'console.command' diff --git a/typo3/sysext/scheduler/Resources/Private/Partials/TaskList.html b/typo3/sysext/scheduler/Resources/Private/Partials/TaskList.html index ca48a26c79973b09f97f911f79d0323ba4275a87..f85213d7fbd331de8dce6827a23ef7aaf7f36dd6 100644 --- a/typo3/sysext/scheduler/Resources/Private/Partials/TaskList.html +++ b/typo3/sysext/scheduler/Resources/Private/Partials/TaskList.html @@ -142,7 +142,7 @@ <td class="right">{task.uid}</td> <td class="nowrap-disabled"> <div class="name"> - <f:be.link route="system_txschedulerM1" parameters="{action: 'edit', 'uid': task.uid}"> + <f:be.link route="scheduler_manage" parameters="{action: 'edit', 'uid': task.uid}"> <strong>{task.classTitle} ({task.classExtension})</strong> <f:if condition="{task.progress}"> <div class="progress"> @@ -267,7 +267,7 @@ <f:then> <div class="btn-group" role="group"> <f:comment>@todo: Next one should be a button type="submit".</f:comment> - <f:be.link route="system_txschedulerM1" + <f:be.link route="scheduler_manage" parameters="{action: {'stop': task.uid}}" class="btn btn-default t3js-modal-trigger" data="{ @@ -283,7 +283,7 @@ </f:then> <f:else> <div class="btn-group" role="group"> - <f:be.link route="system_txschedulerM1" + <f:be.link route="scheduler_manage" parameters="{action: 'edit', 'uid': task.uid}" class="btn btn-default" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:edit')}" @@ -315,7 +315,7 @@ </f:else> </f:if> <f:comment>@todo: Next one should be a button type="submit", too. Similar to the "toggle" button above.</f:comment> - <f:be.link route="system_txschedulerM1" + <f:be.link route="scheduler_manage" parameters="{action: {'delete': task.uid}}" class="btn btn-default t3js-modal-trigger" data="{ @@ -406,7 +406,7 @@ <div class="btn-group" role="group"> <f:comment>@todo: Next one should be a button type="submit"</f:comment> <f:be.link - route="system_txschedulerM1" + route="scheduler_manage" parameters="{action: {'delete': errorClass.uid}}" class="btn btn-default t3js-modal-trigger" data="{ diff --git a/typo3/sysext/scheduler/Resources/Private/Templates/InfoScreen.html b/typo3/sysext/scheduler/Resources/Private/Templates/InfoScreen.html index 4d54ff74fbd097eec38581dc687d19b23f6377c3..1d58f4cbf0797e53b4443862944dec90d7053aa9 100644 --- a/typo3/sysext/scheduler/Resources/Private/Templates/InfoScreen.html +++ b/typo3/sysext/scheduler/Resources/Private/Templates/InfoScreen.html @@ -29,7 +29,7 @@ <tr> <td> <a - href="{be:moduleLink(route: 'system_txschedulerM1', query: 'subModule=scheduler&action=add&tx_scheduler[class]={class}')}" + href="{be:moduleLink(route: 'scheduler_manage', query: 'action=add&tx_scheduler[class]={class}')}" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:new')}" > {classInfo.title} @@ -41,7 +41,7 @@ <div class="btn-group" role="group"> <span class="btn btn-default"> <a - href="{be:moduleLink(route: 'system_txschedulerM1', query: 'subModule=scheduler&action=add&tx_scheduler[class]={class}')}" + href="{be:moduleLink(route: 'scheduler_manage', query: 'action=add&tx_scheduler[class]={class}')}" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:new')}" > <core:icon identifier="actions-plus" /> diff --git a/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js b/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js index f2493dd6e824a15e8e7a9ba9da9f48628aca5b18..9449b08da2ee1eb23d14dd92ec913df2606353f7 100644 --- a/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js +++ b/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import $ from"jquery";import Tablesort from"tablesort";import DocumentSaveActions from"@typo3/backend/document-save-actions.js";import RegularEvent from"@typo3/core/event/regular-event.js";import Modal from"@typo3/backend/modal.js";import Icons from"@typo3/backend/icons.js";import{MessageUtility}from"@typo3/backend/utility/message-utility.js";import PersistentStorage from"@typo3/backend/storage/persistent.js";import DateTimePicker from"@typo3/backend/date-time-picker.js";import{MultiRecordSelectionSelectors}from"@typo3/backend/multi-record-selection.js";class Scheduler{static updateElementBrowserTriggers(){document.querySelectorAll(".t3js-element-browser").forEach((e=>{const t=document.getElementById(e.dataset.triggerFor);e.dataset.params=t.name+"|||pages"}))}static resolveDefaultNumberOfDays(){const e=document.getElementById("task_tableGarbageCollection_numberOfDays");return null===e||void 0===e.dataset.defaultNumberOfDays?null:JSON.parse(e.dataset.defaultNumberOfDays)}static storeCollapseState(e,t){let a={};PersistentStorage.isset("moduleData.system_txschedulerM1")&&(a=PersistentStorage.get("moduleData.system_txschedulerM1"));const l={};l[e]=t?1:0,$.extend(a,l),PersistentStorage.set("moduleData.system_txschedulerM1",a)}constructor(){this.initializeEvents(),this.initializeDefaultStates(),DocumentSaveActions.getInstance().addPreSubmitCallback((()=>{let e=$("#task_class").val();e=e.toLowerCase().replace(/\\/g,"-"),$(".extraFields").appendTo($("#extraFieldsHidden")),$(".extra_fields_"+e).appendTo($("#extraFieldsSection"))}))}actOnChangedTaskClass(e){let t=e.val();t=t.toLowerCase().replace(/\\/g,"-"),$(".extraFields").hide(),$(".extra_fields_"+t).show()}actOnChangedTaskType(e){this.toggleFieldsByTaskType($(e.currentTarget).val())}actOnChangeSchedulerTableGarbageCollectionAllTables(e){let t=$("#task_tableGarbageCollection_numberOfDays"),a=$("#task_tableGarbageCollection_table");if(e.prop("checked"))a.prop("disabled",!0),t.prop("disabled",!0);else{let e=parseInt(t.val(),10);if(e<1){let t=a.val();const l=Scheduler.resolveDefaultNumberOfDays();null!==l&&(e=l[t])}a.prop("disabled",!1),e>0&&t.prop("disabled",!1)}}actOnChangeSchedulerTableGarbageCollectionTable(e){let t=$("#task_tableGarbageCollection_numberOfDays");const a=Scheduler.resolveDefaultNumberOfDays();null!==a&&a[e.val()]>0?(t.prop("disabled",!1),t.val(a[e.val()])):(t.prop("disabled",!0),t.val(0))}toggleFieldsByTaskType(e){e=parseInt(e+"",10),$("#task_end_col").toggle(2===e),$("#task_frequency_row").toggle(2===e)}initializeEvents(){$("#task_class").on("change",(e=>{this.actOnChangedTaskClass($(e.currentTarget))})),$("#task_type").on("change",this.actOnChangedTaskType.bind(this)),$("#task_tableGarbageCollection_allTables").on("change",(e=>{this.actOnChangeSchedulerTableGarbageCollectionAllTables($(e.currentTarget))})),$("#task_tableGarbageCollection_table").on("change",(e=>{this.actOnChangeSchedulerTableGarbageCollectionTable($(e.currentTarget))})),$("[data-update-task-frequency]").on("change",(e=>{const t=$(e.currentTarget);$("#task_frequency").val(t.val()),t.val(t.attr("value")).trigger("blur")}));const e=document.querySelector("table.taskGroup-table");null!==e&&new Tablesort(e),document.querySelectorAll("#tx_scheduler_form .t3js-datetimepicker").forEach((e=>DateTimePicker.initialize(e))),$(document).on("click",".t3js-element-browser",(e=>{e.preventDefault();const t=e.currentTarget;Modal.advanced({type:Modal.types.iframe,content:t.href+"&mode="+t.dataset.mode+"&bparams="+t.dataset.params,size:Modal.sizes.large})})),new RegularEvent("show.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("hide.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go",this.executeTasks.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go_cron",this.executeTasks.bind(this)).bindTo(document),window.addEventListener("message",this.listenOnElementBrowser.bind(this))}initializeDefaultStates(){let e=$("#task_type");e.length&&this.toggleFieldsByTaskType(e.val());let t=$("#task_class");t.length&&(this.actOnChangedTaskClass(t),Scheduler.updateElementBrowserTriggers())}listenOnElementBrowser(e){if(!MessageUtility.verifyOrigin(e.origin))throw"Denied message sent by "+e.origin;if("typo3:elementBrowser:elementAdded"===e.data.actionName){if(void 0===e.data.fieldName)throw"fieldName not defined in message";if(void 0===e.data.value)throw"value not defined in message";document.querySelector('input[name="'+e.data.fieldName+'"]').value=e.data.value.split("_").pop()}}toggleCollapseIcon(e){const t="hide.bs.collapse"===e.type,a=document.querySelector('.t3js-toggle-table[data-bs-target="#'+e.target.id+'"] .collapseIcon');null!==a&&Icons.getIcon(t?"actions-view-list-expand":"actions-view-list-collapse",Icons.sizes.small).then((e=>{a.innerHTML=e})),Scheduler.storeCollapseState($(e.target).data("table"),t)}executeTasks(e){const t=document.querySelector("#tx_scheduler_form");if(null===t)return;const a=[];if(e.detail.checkboxes.forEach((e=>{const t=e.closest(MultiRecordSelectionSelectors.elementSelector);null!==t&&t.dataset.taskId&&a.push(t.dataset.taskId)})),a.length){if("multiRecordSelection:action:go_cron"===e.type){const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","scheduleCron"),e.setAttribute("value",a.join(",")),t.append(e)}else{const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","execute"),e.setAttribute("value",a.join(",")),t.append(e)}t.submit()}}}export default new Scheduler; \ No newline at end of file +import $ from"jquery";import Tablesort from"tablesort";import DocumentSaveActions from"@typo3/backend/document-save-actions.js";import RegularEvent from"@typo3/core/event/regular-event.js";import Modal from"@typo3/backend/modal.js";import Icons from"@typo3/backend/icons.js";import{MessageUtility}from"@typo3/backend/utility/message-utility.js";import PersistentStorage from"@typo3/backend/storage/persistent.js";import DateTimePicker from"@typo3/backend/date-time-picker.js";import{MultiRecordSelectionSelectors}from"@typo3/backend/multi-record-selection.js";class Scheduler{static updateElementBrowserTriggers(){document.querySelectorAll(".t3js-element-browser").forEach((e=>{const t=document.getElementById(e.dataset.triggerFor);e.dataset.params=t.name+"|||pages"}))}static resolveDefaultNumberOfDays(){const e=document.getElementById("task_tableGarbageCollection_numberOfDays");return null===e||void 0===e.dataset.defaultNumberOfDays?null:JSON.parse(e.dataset.defaultNumberOfDays)}static storeCollapseState(e,t){let a={};PersistentStorage.isset("moduleData.scheduler_manage")&&(a=PersistentStorage.get("moduleData.scheduler_manage"));const l={};l[e]=t?1:0,$.extend(a,l),PersistentStorage.set("moduleData.scheduler_manage",a)}constructor(){this.initializeEvents(),this.initializeDefaultStates(),DocumentSaveActions.getInstance().addPreSubmitCallback((()=>{let e=$("#task_class").val();e=e.toLowerCase().replace(/\\/g,"-"),$(".extraFields").appendTo($("#extraFieldsHidden")),$(".extra_fields_"+e).appendTo($("#extraFieldsSection"))}))}actOnChangedTaskClass(e){let t=e.val();t=t.toLowerCase().replace(/\\/g,"-"),$(".extraFields").hide(),$(".extra_fields_"+t).show()}actOnChangedTaskType(e){this.toggleFieldsByTaskType($(e.currentTarget).val())}actOnChangeSchedulerTableGarbageCollectionAllTables(e){let t=$("#task_tableGarbageCollection_numberOfDays"),a=$("#task_tableGarbageCollection_table");if(e.prop("checked"))a.prop("disabled",!0),t.prop("disabled",!0);else{let e=parseInt(t.val(),10);if(e<1){let t=a.val();const l=Scheduler.resolveDefaultNumberOfDays();null!==l&&(e=l[t])}a.prop("disabled",!1),e>0&&t.prop("disabled",!1)}}actOnChangeSchedulerTableGarbageCollectionTable(e){let t=$("#task_tableGarbageCollection_numberOfDays");const a=Scheduler.resolveDefaultNumberOfDays();null!==a&&a[e.val()]>0?(t.prop("disabled",!1),t.val(a[e.val()])):(t.prop("disabled",!0),t.val(0))}toggleFieldsByTaskType(e){e=parseInt(e+"",10),$("#task_end_col").toggle(2===e),$("#task_frequency_row").toggle(2===e)}initializeEvents(){$("#task_class").on("change",(e=>{this.actOnChangedTaskClass($(e.currentTarget))})),$("#task_type").on("change",this.actOnChangedTaskType.bind(this)),$("#task_tableGarbageCollection_allTables").on("change",(e=>{this.actOnChangeSchedulerTableGarbageCollectionAllTables($(e.currentTarget))})),$("#task_tableGarbageCollection_table").on("change",(e=>{this.actOnChangeSchedulerTableGarbageCollectionTable($(e.currentTarget))})),$("[data-update-task-frequency]").on("change",(e=>{const t=$(e.currentTarget);$("#task_frequency").val(t.val()),t.val(t.attr("value")).trigger("blur")}));const e=document.querySelector("table.taskGroup-table");null!==e&&new Tablesort(e),document.querySelectorAll("#tx_scheduler_form .t3js-datetimepicker").forEach((e=>DateTimePicker.initialize(e))),$(document).on("click",".t3js-element-browser",(e=>{e.preventDefault();const t=e.currentTarget;Modal.advanced({type:Modal.types.iframe,content:t.href+"&mode="+t.dataset.mode+"&bparams="+t.dataset.params,size:Modal.sizes.large})})),new RegularEvent("show.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("hide.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go",this.executeTasks.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go_cron",this.executeTasks.bind(this)).bindTo(document),window.addEventListener("message",this.listenOnElementBrowser.bind(this))}initializeDefaultStates(){let e=$("#task_type");e.length&&this.toggleFieldsByTaskType(e.val());let t=$("#task_class");t.length&&(this.actOnChangedTaskClass(t),Scheduler.updateElementBrowserTriggers())}listenOnElementBrowser(e){if(!MessageUtility.verifyOrigin(e.origin))throw"Denied message sent by "+e.origin;if("typo3:elementBrowser:elementAdded"===e.data.actionName){if(void 0===e.data.fieldName)throw"fieldName not defined in message";if(void 0===e.data.value)throw"value not defined in message";document.querySelector('input[name="'+e.data.fieldName+'"]').value=e.data.value.split("_").pop()}}toggleCollapseIcon(e){const t="hide.bs.collapse"===e.type,a=document.querySelector('.t3js-toggle-table[data-bs-target="#'+e.target.id+'"] .collapseIcon');null!==a&&Icons.getIcon(t?"actions-view-list-expand":"actions-view-list-collapse",Icons.sizes.small).then((e=>{a.innerHTML=e})),Scheduler.storeCollapseState($(e.target).data("table"),t)}executeTasks(e){const t=document.querySelector("#tx_scheduler_form");if(null===t)return;const a=[];if(e.detail.checkboxes.forEach((e=>{const t=e.closest(MultiRecordSelectionSelectors.elementSelector);null!==t&&t.dataset.taskId&&a.push(t.dataset.taskId)})),a.length){if("multiRecordSelection:action:go_cron"===e.type){const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","scheduleCron"),e.setAttribute("value",a.join(",")),t.append(e)}else{const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","execute"),e.setAttribute("value",a.join(",")),t.append(e)}t.submit()}}}export default new Scheduler; \ No newline at end of file