diff --git a/typo3/sysext/backend/Classes/Controller/ContentElement/NewContentElementController.php b/typo3/sysext/backend/Classes/Controller/ContentElement/NewContentElementController.php index 7145657a1ea16195d73cbd4cc75ecb70b0312842..90a8e02e4fd3970f8e692a9692a4baa61a0d3ecd 100644 --- a/typo3/sysext/backend/Classes/Controller/ContentElement/NewContentElementController.php +++ b/typo3/sysext/backend/Classes/Controller/ContentElement/NewContentElementController.php @@ -20,25 +20,27 @@ namespace TYPO3\CMS\Backend\Controller\ContentElement; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Routing\UriBuilder; -use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; use TYPO3\CMS\Backend\Tree\View\ContentCreationPagePositionMap; use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Backend\View\BackendViewFactory; use TYPO3\CMS\Backend\Wizard\NewContentElementWizardHookInterface; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Localization\LanguageService; -use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Service\DependencyOrderingService; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\StringUtility; -use TYPO3\CMS\Fluid\View\BackendTemplateView; /** - * Script Class for the New Content element wizard + * New Content element wizard. This is the modal that pops up when clicking "+content" in page module, which + * will trigger wizardAction() since there is a colPos given. Method positionMapAction() is triggered for + * instance from the list module "+content" on tt_content table header, and from list module doc-header "+" + * and then "Click here for wizard". + * * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. */ class NewContentElementController @@ -82,18 +84,14 @@ class NewContentElementController protected $pageInfo; public function __construct( - protected IconFactory $iconFactory, - protected PageRenderer $pageRenderer, - protected UriBuilder $uriBuilder, - protected ModuleTemplateFactory $moduleTemplateFactory, + protected readonly IconFactory $iconFactory, + protected readonly UriBuilder $uriBuilder, + protected readonly BackendViewFactory $backendViewFactory, ) { } /** * Process incoming request and dispatch to the requested action - * - * @param ServerRequestInterface $request - * @return ResponseInterface */ public function handleRequest(ServerRequestInterface $request): ResponseInterface { @@ -123,19 +121,14 @@ class NewContentElementController /** * Renders the wizard */ - public function wizardAction(ServerRequestInterface $request): ResponseInterface + protected function wizardAction(ServerRequestInterface $request): ResponseInterface { - $view = $this->getFluidTemplateObject(); - - $hasAccess = $this->id && $this->pageInfo !== []; - $view->assign('hasAccess', $hasAccess); - if (!$hasAccess) { - return new HtmlResponse($view->render()); + if (!$this->id || $this->pageInfo === []) { + // No pageId or no access. + return new HtmlResponse('No Access'); } // Whether position selection must be performed (no colPos was yet defined) $positionSelection = !isset($this->colPos); - $view->assign('positionSelection', $positionSelection); - // Get processed wizard items from configuration $wizardItems = $this->getWizards(); @@ -153,13 +146,12 @@ class NewContentElementController $key = 0; $menuItems = []; - // Traverse items for the wizard. - // An item is either a header or an item rendered with title/description and icon: foreach ($wizardItems as $wizardKey => $wInfo) { + // An item is either a header or an item rendered with title/description and icon: if (isset($wInfo['header'])) { $menuItems[] = [ 'label' => $wInfo['header'] ?: '-', - 'content' => '', + 'contentItems' => [], ]; $key = count($menuItems) - 1; } else { @@ -169,16 +161,14 @@ class NewContentElementController 'wizardKey' => $wizardKey, 'icon' => $this->iconFactory->getIcon(($wInfo['iconIdentifier'] ?? ''), Icon::SIZE_DEFAULT, ($wInfo['iconOverlay'] ?? ''))->render(), ]; - // Check wizardItem for defVals $itemParams = []; parse_str($wInfo['params'] ?? '', $itemParams); $defVals = $itemParams['defVals']['tt_content'] ?? []; - - // In case no position has to be selected, we can just add the target if (!$positionSelection) { - // Go to DataHandler directly instead of FormEngine + // In case no position has to be selected, we can just add the target if (($wInfo['saveAndClose'] ?? false)) { + // Go to DataHandler directly instead of FormEngine $viewVariables['target'] = (string)$this->uriBuilder->buildUriFromRoute('tce_db', [ 'data' => [ 'tt_content' => [ @@ -219,30 +209,30 @@ class NewContentElementController 'saveAndClose' => (bool)($wInfo['saveAndClose'] ?? false), ], true); } - - $menuItems[$key]['content'] .= $this->getFluidTemplateObject()->assignMultiple($viewVariables)->render('NewContentElement/MenuItem'); + $menuItems[$key]['contentItems'][] = $viewVariables; } } - $view->assign('renderedTabs', $this->moduleTemplateFactory->create($request)->getDynamicTabMenu( - $menuItems, - 'new-content-element-wizard' - )); - + $view = $this->backendViewFactory->create($request, 'typo3/cms-backend'); + $view->assignMultiple([ + 'positionSelection' => $positionSelection, + 'tabsMenuItems' => $menuItems, + 'tabsMenuId' => 'DTM-a31afc8fb616dc290e6626a9f3c9c433', // Just a unique id starting with DTM- + ]); return new HtmlResponse($view->render('NewContentElement/Wizard')); } /** * Renders the position map */ - public function positionMapAction(ServerRequestInterface $request): ResponseInterface + protected function positionMapAction(ServerRequestInterface $request): ResponseInterface { $posMap = GeneralUtility::makeInstance(ContentCreationPagePositionMap::class); $posMap->cur_sys_language = $this->sys_language; $posMap->defVals = (array)($request->getParsedBody()['defVals'] ?? []); $posMap->saveAndClose = (bool)($request->getParsedBody()['saveAndClose'] ?? false); $posMap->R_URI = $this->R_URI; - $view = $this->getFluidTemplateObject(); + $view = $this->backendViewFactory->create($request, 'typo3/cms-backend'); $view->assign('posMap', $posMap->printContentElementColumns($this->id)); return new HtmlResponse($view->render('NewContentElement/PositionMap')); } @@ -250,8 +240,6 @@ class NewContentElementController /** * Returns the array of elements in the wizard display. * For the plugin section there is support for adding elements there from a global variable. - * - * @return array */ protected function getWizards(): array { @@ -298,10 +286,6 @@ class NewContentElementController return $wizardItems; } - /** - * @param array $wizardElements - * @return array - */ protected function getAppendWizards(array $wizardElements): array { $returnElements = []; @@ -313,10 +297,6 @@ class NewContentElementController return $returnElements; } - /** - * @param array $itemConf - * @return array - */ protected function getWizardItem(array $itemConf): array { $itemConf['title'] = $this->getLanguageService()->sL($itemConf['title']); @@ -327,10 +307,6 @@ class NewContentElementController return $itemConf; } - /** - * @param array $wizardGroup - * @return array - */ protected function getWizardGroupHeader(array $wizardGroup): array { return [ @@ -447,13 +423,6 @@ class NewContentElementController return $GLOBALS['BE_USER']; } - protected function getFluidTemplateObject(): BackendTemplateView - { - $view = GeneralUtility::makeInstance(BackendTemplateView::class); - $view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates']); - return $view; - } - /** * Provide information about the current page making use of the wizard. */ diff --git a/typo3/sysext/backend/Resources/Private/Templates/NewContentElement/MenuItem.html b/typo3/sysext/backend/Resources/Private/Partials/NewContentElement/MenuItem.html similarity index 61% rename from typo3/sysext/backend/Resources/Private/Templates/NewContentElement/MenuItem.html rename to typo3/sysext/backend/Resources/Private/Partials/NewContentElement/MenuItem.html index 2f2ae8008603286ccf39ef3b9960c1164c7ec20d..11d870cea720d91166249fd0090266d5690c359b 100644 --- a/typo3/sysext/backend/Resources/Private/Templates/NewContentElement/MenuItem.html +++ b/typo3/sysext/backend/Resources/Private/Partials/NewContentElement/MenuItem.html @@ -3,14 +3,14 @@ data-namespace-typo3-fluid="true" > <div class="t3js-media-new-content-element-wizard media-new-content-element-wizard"> - <f:if condition="{target}"> + <f:if condition="{item.target}"> <f:then> - <button type="button" class="btn btn-link media p-0 text-start" data-target="{target}"> + <button type="button" class="btn btn-link media p-0 text-start" data-target="{item.target}"> <f:render section="itemContent" arguments="{_all}" /> </button> </f:then> <f:else> - <button type="button" class="btn btn-link media p-0 text-start" data-position-map-arguments="{positionMapArguments -> f:format.raw()}"> + <button type="button" class="btn btn-link media p-0 text-start" data-position-map-arguments="{item.positionMapArguments -> f:format.raw()}"> <f:render section="itemContent" arguments="{_all}" /> </button> </f:else> @@ -18,16 +18,16 @@ </div> <f:section name="itemContent"> - <f:if condition="{content}"> - {content -> f:format.raw()} + <f:if condition="{item.content}"> + {item.content -> f:format.raw()} </f:if> <div class="media-left"> - {icon -> f:format.raw()} + {item.icon -> f:format.raw()} </div> <div class="media-body"> - <strong>{wizardInformation.title -> f:format.htmlspecialchars()}</strong> + <strong>{item.wizardInformation.title -> f:format.htmlspecialchars()}</strong> <br/> - {wizardInformation.description -> f:format.nl2br() -> f:format.htmlspecialchars()} + {item.wizardInformation.description -> f:format.nl2br() -> f:format.htmlspecialchars()} </div> </f:section> </html> diff --git a/typo3/sysext/backend/Resources/Private/Templates/NewContentElement/Wizard.html b/typo3/sysext/backend/Resources/Private/Templates/NewContentElement/Wizard.html index 1e7d09d820a0adfd5b5b54294884107793b76828..4aa6988e8c7f917caf62e70d42241646b7b1e92f 100644 --- a/typo3/sysext/backend/Resources/Private/Templates/NewContentElement/Wizard.html +++ b/typo3/sysext/backend/Resources/Private/Templates/NewContentElement/Wizard.html @@ -2,39 +2,65 @@ xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true" > -<f:if condition="{hasAccess}"> - <f:then> - <div class="t3-new-content-element-wizard-inner"> - <f:if condition="{positionSelection}"> - <div class="t3-new-content-element-wizard-title"> - <h2>{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:1_selectType')}</h2> + +<f:be.pageRenderer + includeRequireJsModules="{ + 0: 'TYPO3/CMS/Backend/Tabs' + }" +/> + +<div class="t3-new-content-element-wizard-inner"> + <f:if condition="{positionSelection}"> + <div class="t3-new-content-element-wizard-title"> + <h2>{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:1_selectType')}</h2> + </div> + </f:if> + <div class="container-fluid mt-2"> + <div class="navbar-header"> + <div class="form-group"> + <div class="input-group"> + <span class="input-group-addon">{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:newContentElement.filter.label')}</span> + <input + type="search" + autocomplete="off" + class="form-control t3js-contentWizard-search" + placeholder="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:newContentElement.filter.placeholder')}" + /> </div> - </f:if> - <div class="container-fluid mt-2"> - <div class="navbar-header"> - <div class="form-group"> - <div class="input-group"> - <span class="input-group-addon">{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:newContentElement.filter.label')}</span> - <input - type="search" - autocomplete="off" - class="form-control t3js-contentWizard-search" - placeholder="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:newContentElement.filter.placeholder')}" - /> + </div> + </div> + </div> + + <div role="tabpanel"> + <ul class="nav nav-tabs t3js-tabs" role="tablist" id="tabs-{tabsMenuId}" data-store-last-tab="1"> + <f:for each="{tabsMenuItems}" as="item" iteration="iteration"> + <li role="presentation" class="t3js-tabmenu-item nav-item"> + <a href="#{tabsMenuId}-{iteration.cycle}" class="nav-link {f:if(condition: '{iteration.cycle} == 1', then: ' active')}" aria-controls="{tabsMenuId}-{iteration.cycle}" role="tab" data-bs-toggle="tab"> + {item.label} + </a> + </li> + </f:for> + </ul> + <div class="tab-content"> + <f:for each="{tabsMenuItems}" as="item" iteration="iteration"> + <div role="tabpanel" class="tab-pane{f:if(condition: '{iteration.cycle} == 1', then: ' active')}" id="{tabsMenuId}-{iteration.cycle}"> + <div class="panel panel-tab"> + <div class="panel-body"> + <f:for each="{item.contentItems}" as="contentItem"> + <f:render partial="NewContentElement/MenuItem" arguments="{item: contentItem}" /> + </f:for> </div> </div> </div> - </div> - {renderedTabs -> f:format.raw()} - <div class="container-fluid"> - <div class="alert alert-danger hidden t3js-filter-noresult" role="alert"> - <f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:newContentElement.filter.noResults" /> - </div> - </div> + </f:for> + </div> + </div> + + <div class="container-fluid"> + <div class="alert alert-danger hidden t3js-filter-noresult" role="alert"> + <f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:newContentElement.filter.noResults" /> </div> - </f:then> - <f:else> - <h1>{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:newContentElement')}</h1> - </f:else> -</f:if> + </div> +</div> + </html>