From 30056ef1d6f3b041b0a864edd2f9ba6d6047e5c3 Mon Sep 17 00:00:00 2001 From: mhofmann Date: Fri, 31 May 2024 23:57:44 +0200 Subject: [PATCH] [FEATURE] Add new task wizard with multiple stages The new task wizard has been implemented with multiple stages including template selection, general settings, new editor setup, and template settings. An AJAX backend controller, along with relevant routes, has been added to handle these stages. The script for the wizard has also been added under public javascript. Improved extension configuration, adding options like defaultSiteSorter, groupPrefix, and unifyShortName, and now the ProcessService class encompasses a method for creating backend user groups. --- Classes/ContextMenu/ItemProvider.php | 64 ++++++ .../Backend/CreateNewTaskAjaxController.php | 169 ++++++++++++++++ Configuration/Backend/AjaxRoutes.php | 23 +++ Configuration/Services.php | 5 + Resources/Private/Templates/.gitkeep | 0 .../Templates/Backend/EditorFields.html | 28 +++ .../Templates/Backend/GeneralFields.html | 40 ++++ .../Templates/Backend/GetTemplates.html | 28 +++ .../Templates/Backend/TemplateFields.html | 28 +++ .../JavaScript/CreateWizardTaskWizard.js | 183 ++++++++++++++++++ Resources/Public/Js/.gitkeep | 0 ext_localconf.php | 4 + 12 files changed, 572 insertions(+) create mode 100644 Classes/ContextMenu/ItemProvider.php create mode 100644 Classes/Controller/Backend/CreateNewTaskAjaxController.php create mode 100644 Configuration/Backend/AjaxRoutes.php delete mode 100644 Resources/Private/Templates/.gitkeep create mode 100644 Resources/Private/Templates/Backend/EditorFields.html create mode 100644 Resources/Private/Templates/Backend/GeneralFields.html create mode 100644 Resources/Private/Templates/Backend/GetTemplates.html create mode 100644 Resources/Private/Templates/Backend/TemplateFields.html create mode 100644 Resources/Public/JavaScript/CreateWizardTaskWizard.js delete mode 100644 Resources/Public/Js/.gitkeep diff --git a/Classes/ContextMenu/ItemProvider.php b/Classes/ContextMenu/ItemProvider.php new file mode 100644 index 0000000..1610a6e --- /dev/null +++ b/Classes/ContextMenu/ItemProvider.php @@ -0,0 +1,64 @@ + [ + 'type' => 'item', + 'label' => 'Create new from page...', // you can use "LLL:" syntax here + 'iconIdentifier' => 'actions-document-info', + 'callbackAction' => 'createTask', //name of the function in the JS file + ], + ]; + + public function getPriority(): int + { + return 90; + } + + public function canHandle(): bool + { + return $this->table === 'pages'; + } + + public function addItems(array $items): array + { + $this->initDisabledItems(); + $localItems = $this->prepareItems($this->itemsConfiguration); + $items = $items + $localItems; + return $items; + } + + protected function getAdditionalAttributes(string $itemName): array + { + return [ + 'data-callback-module' => 'TYPO3/CMS/Sudhaus7Wizard/CreateWizardTaskWizard', + ]; + } + + protected function canRender(string $itemName, string $type): bool + { + if (in_array($itemName, $this->disabledItems, true)) { + return false; + } + + $canRender = false; + if ($itemName == 's7wizard') { + $canRender = $this->isSiteRoot(); + } + return $canRender; + } + + private function isSiteRoot(): bool + { + $pageRecord = BackendUtility::getRecord('pages', $this->identifier); + return ($pageRecord['is_siteroot'] ?? 0) === 1; + } +} diff --git a/Classes/Controller/Backend/CreateNewTaskAjaxController.php b/Classes/Controller/Backend/CreateNewTaskAjaxController.php new file mode 100644 index 0000000..aa3c372 --- /dev/null +++ b/Classes/Controller/Backend/CreateNewTaskAjaxController.php @@ -0,0 +1,169 @@ +uriBuilder = $uriBuilder; + } + + public function getTemplatesAction(ServerRequestInterface $request): ResponseInterface + { + $templates = []; + + /** @var WizardProcessInterface $registeredTemplateExtention */ + foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['Sudhaus7Wizard']['registeredTemplateExtentions'] ?? [] as $registeredTemplateExtension) { + /** @var WizardTemplateConfigInterface $configuration */ + $configuration = $registeredTemplateExtension::getWizardConfig(); + $templates[] = [ + 'title' => $configuration->getDescription(), + 'value' => $configuration->getExtension(), + ]; + } + + $success = count($templates) > 0; + + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplatePathAndFilename('EXT:sudhaus7_wizard/Resources/Private/Templates/Backend/GetTemplates.html'); + $view->assign('templates', $templates); + + $data = [ + 'success' => $success, + 'templates' => $templates, + 'html' => $view->render(), + ]; + + return new JsonResponse($data); + } + + public function getGeneralFieldsAction(ServerRequestInterface $request): ResponseInterface + { + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplatePathAndFilename('EXT:sudhaus7_wizard/Resources/Private/Templates/Backend/GeneralFields.html'); + $data = [ + 'html' => $view->render(), + ]; + + return new JsonResponse($data); + } + + public function getEditorFieldsAction(ServerRequestInterface $request): ResponseInterface + { + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplatePathAndFilename('EXT:sudhaus7_wizard/Resources/Private/Templates/Backend/EditorFields.html'); + $data = [ + 'html' => $view->render(), + ]; + + return new JsonResponse($data); + } + + public function getTemplateFieldsAction(ServerRequestInterface $request): ResponseInterface + { + /** @var WizardProcessInterface|null $template */ + $template = $request->getQueryParams()['template'] ?? null; + if ($template === null || !in_array(WizardProcessInterface::class, class_implements($template), true)) { + $data = [ + 'success' => false, + ]; + + return new JsonResponse($data); + } + $configuration = $template::getWizardConfig(); + $flexInfoFile = $configuration->getFlexinfoFile(); + if (!empty($flexInfoFile)) { + $realPathToFlexFile = GeneralUtility::getFileAbsFileName($flexInfoFile); + // @todo render/prepare Flex + } + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplatePathAndFilename('EXT:sudhaus7_wizard/Resources/Private/Templates/Backend/TemplateFields.html'); + $data = [ + 'html' => $view->render(), + ]; + + return new JsonResponse($data); + } + + public function createNewTask(ServerRequestInterface $request): ResponseInterface + { + $wizard = $this->getParamsFromRequest($request)['wizard'] ?? []; + $wizard['pid'] = 0; + $newWizardIdentifier = StringUtility::getUniqueId('NEW'); + $data = [ + 'tx_sudhaus7wizard_domain_model_creator' => [ + $newWizardIdentifier => $wizard, + ], + ]; + + $dataHandler = GeneralUtility::makeInstance(DataHandler::class); + $dataHandler->start($data, []); + $dataHandler->process_datamap(); + if ($dataHandler->errorLog !== []) { + $returnData = [ + 'success' => false, + 'error' => [], + ]; + foreach ($dataHandler->errorLog as $errorLog) { + $returnData['error'][] = $errorLog; + } + return new JsonResponse($returnData); + } + + $newWizardTaskId = $dataHandler->substNEWwithIDs[$newWizardIdentifier]; + + $redirectUri = $this->uriBuilder + ->buildUriFromRoute( + 'record_edit', + [ + 'edit' => [ + 'tt_address' => [ + $newWizardTaskId => 'edit', + ], + ], + ] + ); + $returnData = [ + 'success' => true, + 'redirectUrl' => (string)$redirectUri, + ]; + return new JsonResponse($returnData); + } + + /** + * @return array + */ + protected function getParamsFromRequest(ServerRequestInterface $request): array + { + $postParams = $request->getParsedBody(); + + try { + $postArrayParams = match (gettype($postParams)) { + 'array' => $postParams, + 'object' => json_decode(json_encode($postParams) ?: '{}', true, 512, JSON_THROW_ON_ERROR), + default => [] + }; + } catch (\JsonException $_) { + $postArrayParams = []; + } + + return $postArrayParams; + } +} diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php new file mode 100644 index 0000000..60e274e --- /dev/null +++ b/Configuration/Backend/AjaxRoutes.php @@ -0,0 +1,23 @@ + [ + 'path' => '/s7wizard/getTemplates', + 'method' => ['GET'], + 'target' => CreateNewTaskAjaxController::class . '::getTemplatesAction', + ], + 's7wizardGetGeneralFields' => [ + 'path' => '/s7wizard/getGeneralFields', + 'method' => ['GET'], + 'target' => CreateNewTaskAjaxController::class . '::getGeneralFieldsAction', + ], + 's7wizardGetEditorFields' => [ + 'path' => '/s7wizard/getEditorFields', + 'method' => ['GET'], + 'target' => CreateNewTaskAjaxController::class . '::getEditorFieldsAction', + ], +]; diff --git a/Configuration/Services.php b/Configuration/Services.php index c20c16b..74565bf 100644 --- a/Configuration/Services.php +++ b/Configuration/Services.php @@ -14,6 +14,7 @@ */ use SUDHAUS7\Sudhaus7Wizard\Cli\RunCommand; +use SUDHAUS7\Sudhaus7Wizard\Controller\Backend\CreateNewTaskAjaxController; use SUDHAUS7\Sudhaus7Wizard\EventHandlers\DefaultSiteSorterListener; use SUDHAUS7\Sudhaus7Wizard\EventHandlers\Extensions\TxNewsFixRecordHandler; use SUDHAUS7\Sudhaus7Wizard\EventHandlers\Extensions\TxNewsPluginHandlerEvent; @@ -38,6 +39,7 @@ __DIR__ . '/../Classes/Domain/Model/', __DIR__ . '/../Classes/Events/', __DIR__ . '/../Classes/Backend/', + __DIR__ . '/../Classes/ContextMenu/', ]); $services->alias(SourceInterface::class, LocalDatabase::class); @@ -48,6 +50,9 @@ 'schedulable' => true, ]); + $services->set(CreateNewTaskAjaxController::class) + ->public(); + $services->set(PreSysFileReferenceEventHandler::class) ->tag('event.listener', ['identifier' => 's7wizardBaseHandleSysFileReferences']); diff --git a/Resources/Private/Templates/.gitkeep b/Resources/Private/Templates/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Resources/Private/Templates/Backend/EditorFields.html b/Resources/Private/Templates/Backend/EditorFields.html new file mode 100644 index 0000000..ebfe12a --- /dev/null +++ b/Resources/Private/Templates/Backend/EditorFields.html @@ -0,0 +1,28 @@ + +
+
+ + +
+
+ + +
+
+ + +
+
+ diff --git a/Resources/Private/Templates/Backend/GeneralFields.html b/Resources/Private/Templates/Backend/GeneralFields.html new file mode 100644 index 0000000..62eafe7 --- /dev/null +++ b/Resources/Private/Templates/Backend/GeneralFields.html @@ -0,0 +1,40 @@ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ diff --git a/Resources/Private/Templates/Backend/GetTemplates.html b/Resources/Private/Templates/Backend/GetTemplates.html new file mode 100644 index 0000000..f74252d --- /dev/null +++ b/Resources/Private/Templates/Backend/GetTemplates.html @@ -0,0 +1,28 @@ + +
+ + +
+ diff --git a/Resources/Private/Templates/Backend/TemplateFields.html b/Resources/Private/Templates/Backend/TemplateFields.html new file mode 100644 index 0000000..a462be4 --- /dev/null +++ b/Resources/Private/Templates/Backend/TemplateFields.html @@ -0,0 +1,28 @@ + +
+
+ + +
+
+ + +
+
+ + +
+
+ diff --git a/Resources/Public/JavaScript/CreateWizardTaskWizard.js b/Resources/Public/JavaScript/CreateWizardTaskWizard.js new file mode 100644 index 0000000..e699947 --- /dev/null +++ b/Resources/Public/JavaScript/CreateWizardTaskWizard.js @@ -0,0 +1,183 @@ +'use strict'; + +define([ + 'jquery', + 'TYPO3/CMS/Backend/Modal', + 'TYPO3/CMS/Backend/Severity', + 'TYPO3/CMS/Backend/MultiStepWizard', + 'TYPO3/CMS/Backend/Icons', + 'TYPO3/CMS/Backend/Notification', + 'TYPO3/CMS/Core/SecurityUtility', +], function($, Modal, Severity, MultiStepWizard, Icons, Notification) { + let CreateWizardTaskWizard = {}; + + CreateWizardTaskWizard.createTask = function (table, uid) { + let createTaskForm = new FormData(); + createTaskForm.set('wizard[sourcepid]', uid); + MultiStepWizard.addSlide( + 's7wizard-select-template', + 'Select template page', + '', + Severity.info, + 'Template', + slide => { + MultiStepWizard.lockNextStep(); + MultiStepWizard.lockPrevStep(); + MultiStepWizard.blurCancelStep(); + fetch(TYPO3.settings.ajaxUrls.s7wizardGetTemplates, { + method: 'GET', + credentials: 'same-origin' + }) + .then(response => response.text()) + .then(data => { + const response = JSON.parse(data); + if (response.success === false) { + Notification.error( + 'No templates found' + ); + return; + } + if (response.templates.length === 1) { + const selectedTemplate = response.templates.pop(); + createTaskForm.set('wizard[base]', selectedTemplate.value); + MultiStepWizard.unlockNextStep(); + MultiStepWizard.triggerStepButton('next'); + } + slide.html(response.html); + $('#selectTemplate', slide).on('change', () => { + if (this.value !== '') { + createTaskForm.set('wizard[base]', this.value); + MultiStepWizard.unlockNextStep(); + } + }) + }) + } + ).addSlide( + 's7wizard-general-settings', + 'Set general settings', + '', + Severity.info, + 'General', + slide => { + fetch(TYPO3.settings.ajaxUrls.s7wizardGetGeneralFields, { + method: 'GET', + credentials: 'same-origin' + }) + .then(response => response.text()) + .then(data => { + const response = JSON.parse(data); + slide.html(response.html); + let form = $('form#generalWizardForm', slide)[0]; + + $('input.form-control', slide).each(function () { + this.addEventListener('keyup', function () { + if (form.checkValidity()) { + $('input.form-control', slide).each(function () { + let formKey = 'address[' + this.name + ']'; + + createTaskForm.set(formKey, this.value); + }); + MultiStepWizard.unlockNextStep(); + } + }); + }); + }) + } + ).addSlide( + 's7wizard-editor-settings', + 'Set up new editor', + '', + Severity.info, + 'Editor', + slide => { + fetch(TYPO3.settings.ajaxUrls.s7wizardGetEditorFields, { + method: 'GET', + credentials: 'same-origin' + }) + .then(response => response.text()) + .then(data => { + const response = JSON.parse(data); + slide.html(response.html); + let form = $('form#editorWizardForm', slide)[0]; + + $('input.form-control', slide).each(function () { + this.addEventListener('keyup', function () { + if (form.checkValidity()) { + $('input.form-control', slide).each(function () { + let formKey = 'address[' + this.name + ']'; + + createTaskForm.set(formKey, this.value); + }); + MultiStepWizard.unlockNextStep(); + } + }); + }); + }) + } + ).addSlide( + 's7wizard-template-settings', + 'Set template settings', + '', + Severity.info, + 'Template', + slide => { + const templateSettingsUrl = TYPO3.settings.ajaxUrls.s7wizardGetTemplateSettings + '&template=' + createTaskForm.get('wizard[base]'); + fetch(templateSettingsUrl, { + method: 'GET', + credentials: 'same-origin' + }) + .then(response => response.text()) + .then(data => { + const response = JSON.parse(data); + slide.html(response.html); + let form = $('form#templateWizardForm', slide)[0]; + + $('input.form-control', slide).each(function () { + this.addEventListener('keyup', function () { + if (form.checkValidity()) { + $('input.form-control', slide).each(function () { + let formKey = 'address[' + this.name + ']'; + + createTaskForm.set(formKey, this.value); + }); + MultiStepWizard.unlockNextStep(); + } + }); + }); + }) + } + ).addFinalProcessingSlide(function () { + fetch(TYPO3.settings.ajaxUrls.wizardCreateNewTask, { + method: 'POST', + cache: 'no-cache', + body: createTaskForm, + }) + .then((response) => response.text()) + .then(function (text) { + const data = JSON.parse(text); + + if (data.success === false) { + Array.prototype.forEach.call( + data.error, + function (errorText) { + Notification.error('ERROR', errorText); + }, + ); + document.location = data.returnUrl; + } + + document.location = data.redirectUrl; + MultiStepWizard.dismiss(); + }); + }) + .done(function () { + MultiStepWizard.show(); + MultiStepWizard.getComponent().on('wizard-dismiss', function () { + // hard reset old wizard steps here + createTaskForm = new FormData(); + }); + }); + } + + return CreateWizardTaskWizard; +}); diff --git a/Resources/Public/Js/.gitkeep b/Resources/Public/Js/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ext_localconf.php b/ext_localconf.php index d2cc618..45740c1 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -10,3 +10,7 @@ * * The TYPO3 project - inspiring people to share! */ +(static function (): void { + $GLOBALS['TYPO3_CONF_VARS']['BE']['ContextMenu']['ItemProviders'][1717198724657] = + \SUDHAUS7\Sudhaus7Wizard\ContextMenu\ItemProvider::class; +})();