2 declare(strict_types
=1);
4 namespace TYPO3\CMS\Backend\Controller\Wizard
;
7 * This file is part of the TYPO3 CMS project.
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
16 * The TYPO3 project - inspiring people to share!
19 use Psr\Http\Message\ResponseInterface
;
20 use Psr\Http\Message\ServerRequestInterface
;
21 use TYPO3\CMS\Backend\Template\ModuleTemplate
;
22 use TYPO3\CMS\Backend\Tree\View\ContentCreationPagePositionMap
;
23 use TYPO3\CMS\Backend\Utility\BackendUtility
;
24 use TYPO3\CMS\Backend\View\BackendLayoutView
;
25 use TYPO3\CMS\Backend\Wizard\NewContentElementWizardHookInterface
;
26 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication
;
27 use TYPO3\CMS\Core\Imaging\Icon
;
28 use TYPO3\CMS\Core\Localization\LanguageService
;
29 use TYPO3\CMS\Core\Service\DependencyOrderingService
;
30 use TYPO3\CMS\Core\Type\Bitmask\Permission
;
31 use TYPO3\CMS\Core\Utility\ArrayUtility
;
32 use TYPO3\CMS\Core\Utility\GeneralUtility
;
33 use TYPO3\CMS\Fluid\View\StandaloneView
;
36 * Script Class for the New Content element wizard
38 class NewContentElementWizardController
41 * ModuleTemplate object
45 protected $moduleTemplate;
59 protected $sysLanguage = 0;
66 protected $returnUrl = '';
69 * If set, the content is destined for a specific column.
85 protected $modTsConfig = [];
88 * Used to accumulate the content of the module.
102 * config of the wizard
116 protected $onClickEvent;
121 protected $moduleConfiguration;
124 * @var StandaloneView
129 * @var StandaloneView
131 protected $menuItemView;
136 * @var ServerRequestInterface
143 public function __construct()
145 $this->moduleTemplate
= GeneralUtility
::makeInstance(ModuleTemplate
::class);
146 $GLOBALS['SOBE'] = $this;
147 $this->view
= $this->getFluidTemplateObject();
148 $this->menuItemView
= $this->getFluidTemplateObject('MenuItem.html');
153 * returns a new standalone view, shorthand function
155 * @param string $filename
156 * @return StandaloneView
158 protected function getFluidTemplateObject(string $filename = 'Main.html'): StandaloneView
160 /** @var StandaloneView $view */
161 $view = GeneralUtility
::makeInstance(StandaloneView
::class);
162 $view->setTemplatePathAndFilename(GeneralUtility
::getFileAbsFileName('EXT:backend/Resources/Private/Templates/NewContentElement/' . $filename));
163 $view->getRequest()->setControllerExtensionName('Backend');
168 * Constructor, initializing internal variables.
170 protected function init()
172 $lang = $this->getLanguageService();
173 $lang->includeLLFile('EXT:lang/Resources/Private/Language/locallang_misc.xlf');
174 $originalLocalLanguage = $GLOBALS['LOCAL_LANG'];
175 $lang->includeLLFile('EXT:backend/Resources/Private/Language/locallang_db_new_content_el.xlf');
176 ArrayUtility
::mergeRecursiveWithOverrule($originalLocalLanguage, $GLOBALS['LOCAL_LANG']);
177 $GLOBALS['LOCAL_LANG'] = $originalLocalLanguage;
179 // Setting internal vars:
180 $this->id
= (int)GeneralUtility
::_GP('id');
181 $this->sysLanguage
= (int)GeneralUtility
::_GP('sys_language_uid');
182 $this->returnUrl
= GeneralUtility
::sanitizeLocalUrl(GeneralUtility
::_GP('returnUrl'));
183 // original variable needs to be kept as is for position map
184 $GLOBALS['SOBE']->R_URI
= $this->returnUrl
;
185 $this->colPos
= GeneralUtility
::_GP('colPos') === null ?
null : (int)GeneralUtility
::_GP('colPos');
186 $this->uidPid
= (int)GeneralUtility
::_GP('uid_pid');
187 $this->moduleConfiguration
['name'] = 'xMOD_db_new_content_el';
188 $this->modTsConfig
= BackendUtility
::getModTSconfig($this->id
, 'mod.wizards.newContentElement');
189 $configuration = BackendUtility
::getPagesTSconfig($this->id
);
190 $this->configuration
= $configuration['mod.']['wizards.']['newContentElement.'];
191 // Getting the current page and receiving access information (used in main())
192 $permissionsClause = $this->getBackendUser()->getPagePermsClause(Permission
::PAGE_SHOW
);
193 $this->pageInfo
= BackendUtility
::readPageAccess($this->id
, $permissionsClause);
194 $this->access
= is_array($this->pageInfo
);
198 * Injects the request object for the current request or subrequest
199 * As this controller goes only through the main() method, it is rather simple for now
201 * @param ServerRequestInterface $request the current request
202 * @param ResponseInterface $response
203 * @return ResponseInterface the response with the content
205 public function mainAction(ServerRequestInterface
$request, ResponseInterface
$response): ResponseInterface
208 $response->getBody()->write($this->content
);
213 * Creating the module output.
215 * @throws \UnexpectedValueException
217 protected function main()
220 if ($this->id
&& $this->access
) {
222 // Init position map object:
223 $positionMap = GeneralUtility
::makeInstance(ContentCreationPagePositionMap
::class);
224 $positionMap->cur_sys_language
= $this->sysLanguage
;
225 // If a column is pre-set:
226 if (isset($this->colPos
)) {
227 if ($this->uidPid
< 0) {
229 $row['uid'] = abs($this->uidPid
);
233 $this->onClickEvent
= $positionMap->onClickInsertRecord(
241 $this->onClickEvent
= '';
243 // ***************************
245 // ***************************
247 $wizardItems = $this->getWizardItems();
248 // Wrapper for wizards
249 // Hook for manipulating wizardItems, wrapper, onClickEvent etc.
250 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms']['db_new_content_el']['wizardItemsHook'] ??
[] as $className) {
251 $hookObject = GeneralUtility
::makeInstance($className);
252 if (!$hookObject instanceof NewContentElementWizardHookInterface
) {
253 throw new \
UnexpectedValueException(
254 $className . ' must implement interface ' . NewContentElementWizardHookInterface
::class,
258 $hookObject->manipulateWizardItems($wizardItems, $this);
261 // Traverse items for the wizard.
262 // An item is either a header or an item rendered with a radio button and title/description and icon:
263 $counter = ($key = 0);
266 $this->view
->assign('onClickEvent', $this->onClickEvent
);
268 foreach ($wizardItems as $wizardKey => $wizardInformation) {
270 if ($wizardInformation['header']) {
272 'label' => $wizardInformation['header'],
275 $key = count($menuItems) - 1;
277 if (!$this->onClickEvent
) {
279 $wizardOnClick = 'document.editForm.defValues.value=unescape(' . GeneralUtility
::quoteJSvalue(rawurlencode($wizardInformation['params'])) . '); window.location.hash=\'#sel2\';';
280 // Onclick action for icon/title:
281 $actionOnClick = 'document.getElementsByName(\'tempB\')[' . $counter . '].checked=1;' . $wizardOnClick . 'return false;';
283 $actionOnClick = 'document.editForm.defValues.value=unescape("' . rawurlencode($wizardInformation['params']) . '");goToalt_doc();' . (!$this->onClickEvent ?
'window.location.hash=\'#sel2\';' : '');
286 $icon = $this->moduleTemplate
->getIconFactory()->getIcon($wizardInformation['iconIdentifier'])->render();
288 $this->menuItemView
->assignMultiple(
290 'onClickEvent' => $this->onClickEvent
,
291 'aOnClick' => $actionOnClick,
292 'wizardInformation' => $wizardInformation,
294 'wizardOnClick' => $wizardOnClick,
295 'wizardKey' => $wizardKey
298 $menuItems[$key]['content'] .= $this->menuItemView
->render();
303 $this->view
->assign('renderedTabs', $this->moduleTemplate
->getDynamicTabMenu(
305 'new-content-element-wizard'
308 // If the user must also select a column:
309 if (!$this->onClickEvent
) {
311 // Load SHARED page-TSconfig settings and retrieve column list from there, if applicable:
312 $colPosArray = GeneralUtility
::callUserFunction(
313 BackendLayoutView
::class . '->getColPosListItemsParsed',
317 $colPosIds = array_column($colPosArray, 1);
318 // Removing duplicates, if any
319 $colPosList = implode(',', array_unique(array_map('intval', $colPosIds)));
320 // Finally, add the content of the column selector to the content:
323 $positionMap->printContentElementColumns($this->id
, 0, $colPosList, 1, $this->returnUrl
)
327 // In case of no access:
330 $this->view
->assign('hasAccess', $hasAccess);
332 $this->content
= $this->view
->render();
333 // Setting up the buttons and markers for docheader
338 * Returns the array of elements in the wizard display.
339 * For the plugin section there is support for adding elements there from a global variable.
343 protected function getWizardItems(): array
346 if (isset($this->configuration
['wizardItems.'])) {
347 $wizards = $this->configuration
['wizardItems.'];
348 if (empty($wizards['elements.'])) {
349 $wizards['elements.'] = [];
351 $appendWizards = $this->appendWizards($wizards['elements.']);
352 if (is_array($wizards)) {
353 foreach ($wizards as $groupKey => $wizardGroup) {
354 if (is_array($wizards[$groupKey])) {
355 $this->prepareDependencyOrdering($wizards[$groupKey], 'before');
356 $this->prepareDependencyOrdering($wizards[$groupKey], 'after');
359 $wizards = GeneralUtility
::makeInstance(DependencyOrderingService
::class)->orderByDependencies($wizards);
361 foreach ($wizards as $groupKey => $wizardGroup) {
362 if (is_array($wizardGroup)) {
363 $groupKey = rtrim($groupKey, '.');
364 $showItems = GeneralUtility
::trimExplode(',', $wizardGroup['show'], true);
365 $showAll = in_array('*', $showItems, true);
367 if (is_array($appendWizards[$groupKey . '.']['elements.'])) {
368 $wizardElements = array_merge(
369 (array)$wizardGroup['elements.'],
370 $appendWizards[$groupKey . '.']['elements.']
373 $wizardElements = $wizardGroup['elements.'];
375 if (is_array($wizardElements)) {
376 foreach ($wizardElements as $itemKey => $itemConfiguration) {
377 $itemKey = rtrim($itemKey, '.');
378 if (($showAll ||
in_array($itemKey, $showItems) && is_array($itemConfiguration))) {
379 $item = $this->getItem($itemConfiguration);
381 $groupItems[$groupKey . '_' . $itemKey] = $item;
386 if (!empty($groupItems)) {
387 $wizardItems[$groupKey] = $this->getGroupHeader($wizardGroup);
388 $wizardItems = array_merge($wizardItems, $groupItems);
394 // Remove elements where preset values are not allowed:
395 $this->removeInvalidElements($wizardItems);
400 * @param array $wizardElements
401 * @return array $returnElements
403 protected function appendWizards(array $wizardElements): array
405 if (is_array($GLOBALS['TBE_MODULES_EXT']['xMOD_db_new_content_el']['addElClasses'])) {
406 foreach ($GLOBALS['TBE_MODULES_EXT']['xMOD_db_new_content_el']['addElClasses'] as $class => $path) {
407 if (!class_exists($class) && file_exists($path)) {
410 $moduleObject = GeneralUtility
::makeInstance($class);
411 if (method_exists($moduleObject, 'proc')) {
412 $wizardElements = $moduleObject->proc($wizardElements);
416 $returnElements = [];
417 foreach ($wizardElements as $key => $wizardItem) {
418 preg_match('/^[a-zA-Z0-9]+_/', $key, $group);
419 $wizardGroup = $group[0] ?
substr($group[0], 0, -1) . '.' : $key;
420 $returnElements[$wizardGroup]['elements.'][substr($key, strlen($wizardGroup)) . '.'] = $wizardItem;
422 return $returnElements;
426 * Prepare a wizard tab configuration for sorting.
428 * @param array $wizardGroup TypoScript wizard tab configuration
429 * @param string $key Which array key should be prepared
431 protected function prepareDependencyOrdering(array &$wizardGroup, string $key)
433 if (isset($wizardGroup[$key])) {
434 $wizardGroup[$key] = GeneralUtility
::trimExplode(',', $wizardGroup[$key]);
435 $wizardGroup[$key] = array_map(function ($s) {
437 }, $wizardGroup[$key]);
442 * @param array $itemConfiguration
443 * @return array $itemConfiguration
445 protected function getItem(array $itemConfiguration): array
447 $itemConfiguration['title'] = $this->getLanguageService()->sL($itemConfiguration['title']);
448 $itemConfiguration['description'] = $this->getLanguageService()->sL($itemConfiguration['description']);
449 $itemConfiguration['tt_content_defValues'] = $itemConfiguration['tt_content_defValues.'];
450 unset($itemConfiguration['tt_content_defValues.']);
451 return $itemConfiguration;
455 * @param array $wizardGroup
458 protected function getGroupHeader(array $wizardGroup): array
461 'header' => $this->getLanguageService()->sL($wizardGroup['header'])
466 * Checks the array for elements which might contain unallowed default values and will unset them!
467 * Looks for the "tt_content_defValues" key in each element and if found it will traverse that array as fieldname /
468 * value pairs and check.
469 * The values will be added to the "params" key of the array (which should probably be unset or empty by default).
471 * @param array $wizardItems Wizard items, passed by reference
473 protected function removeInvalidElements(&$wizardItems)
475 // Get TCEFORM from TSconfig of current page
476 $row = ['pid' => $this->id
];
477 $tceFormTsConfig = BackendUtility
::getTCEFORM_TSconfig('tt_content', $row);
479 // Traverse wizard items:
480 foreach ($wizardItems as $key => $configuration) {
481 // Exploding parameter string, if any (old style)
482 if ($wizardItems[$key]['params']) {
483 // Explode GET vars recursively
484 $temporaryGetVariables = GeneralUtility
::explodeUrl2Array($wizardItems[$key]['params'], true);
485 // If tt_content values are set, merge them into the tt_content_defValues array,
486 // unset them from $temporaryGetVariables and re-implode $temporaryGetVariables into the param string
487 // (in case remaining parameters are around).
488 if (is_array($temporaryGetVariables['defVals']['tt_content'])) {
489 $wizardItems[$key]['tt_content_defValues'] = array_merge(
490 is_array($wizardItems[$key]['tt_content_defValues']) ?
$wizardItems[$key]['tt_content_defValues'] : [],
491 $temporaryGetVariables['defVals']['tt_content']
493 unset($temporaryGetVariables['defVals']['tt_content']);
494 $wizardItems[$key]['params'] = GeneralUtility
::implodeArrayForUrl('', $temporaryGetVariables);
497 // If tt_content_defValues are defined...:
498 if (is_array($wizardItems[$key]['tt_content_defValues'])) {
499 $backendUser = $this->getBackendUser();
500 // Traverse field values:
501 foreach ($wizardItems[$key]['tt_content_defValues'] as $fieldName => $fieldValue) {
502 if (is_array($GLOBALS['TCA']['tt_content']['columns'][$fieldName])) {
503 // Get information about if the field value is OK:
504 $configuration = &$GLOBALS['TCA']['tt_content']['columns'][$fieldName]['config'];
505 $authenticationModeDeny = $configuration['type'] === 'select' && $configuration['authMode']
506 && !$backendUser->checkAuthMode(
510 $configuration['authMode']
512 // explode TSconfig keys only as needed
513 if (!isset($removeItems[$fieldName])) {
514 $removeItems[$fieldName] = GeneralUtility
::trimExplode(
516 $tceFormTsConfig[$fieldName]['removeItems'],
520 if (!isset($keepItems[$fieldName])) {
521 $keepItems[$fieldName] = GeneralUtility
::trimExplode(
523 $tceFormTsConfig[$fieldName]['keepItems'],
527 $isNotInKeepItems = !empty($keepItems[$fieldName]) && !in_array(
529 $keepItems[$fieldName]
531 if ($authenticationModeDeny ||
$fieldName === 'CType' && (in_array(
533 $removeItems[$fieldName]
534 ) ||
$isNotInKeepItems)
536 // Remove element all together:
537 unset($wizardItems[$key]);
540 // Add the parameter:
541 $wizardItems[$key]['params'] .= '&defVals[tt_content][' . $fieldName . ']=' . rawurlencode($this->getLanguageService()->sL($fieldValue));
542 $headerKey = explode('_', $key);
543 $headersUsed[$headerKey[0]] = $headerKey[0];
548 // remove headers without elements
549 foreach ($wizardItems as $key => $configuration) {
550 $headerKey = explode('_', $key);
551 if ($headerKey[0] && !$headerKey[1] && !in_array($headerKey[0], $headersUsed)) {
552 unset($wizardItems[$key]);
558 * Create the panel of buttons for submitting the form or otherwise perform operations.
560 protected function getButtons()
562 $buttonBar = $this->moduleTemplate
->getDocHeaderComponent()->getButtonBar();
563 if ($this->returnUrl
) {
564 $backButton = $buttonBar->makeLinkButton()
565 ->setHref($this->returnUrl
)
566 ->setTitle($this->getLanguageService()->getLL('goBack'))
567 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
568 'actions-view-go-back',
571 $buttonBar->addButton($backButton);
573 $contextSensitiveHelpButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('new_ce');
574 $buttonBar->addButton($contextSensitiveHelpButton);
578 * Returns LanguageService
580 * @return LanguageService
582 protected function getLanguageService(): LanguageService
584 return $GLOBALS['LANG'];
588 * Returns the current BE user.
590 * @return BackendUserAuthentication
592 protected function getBackendUser(): BackendUserAuthentication
594 return $GLOBALS['BE_USER'];