[CLEANUP] Use Permission constants consistently
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / ContentElement / NewContentElementController.php
1 <?php
2 namespace TYPO3\CMS\Backend\Controller\ContentElement;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Template\DocumentTemplate;
20 use TYPO3\CMS\Backend\Template\ModuleTemplate;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
22 use TYPO3\CMS\Backend\View\BackendLayoutView;
23 use TYPO3\CMS\Backend\Wizard\NewContentElementWizardHookInterface;
24 use TYPO3\CMS\Core\Imaging\Icon;
25 use TYPO3\CMS\Core\Service\DependencyOrderingService;
26 use TYPO3\CMS\Core\Type\Bitmask\Permission;
27 use TYPO3\CMS\Core\Utility\ArrayUtility;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\CMS\Fluid\View\StandaloneView;
30
31 /**
32 * Script Class for the New Content element wizard
33 */
34 class NewContentElementController
35 {
36 /**
37 * Page id
38 *
39 * @var int
40 */
41 public $id;
42
43 /**
44 * Sys language
45 *
46 * @var int
47 */
48 public $sys_language = 0;
49
50 /**
51 * Return URL.
52 *
53 * @var string
54 */
55 public $R_URI = '';
56
57 /**
58 * If set, the content is destined for a specific column.
59 *
60 * @var int|null
61 */
62 public $colPos;
63
64 /**
65 * @var int
66 */
67 public $uid_pid;
68
69 /**
70 * Module TSconfig.
71 *
72 * @var array
73 */
74 public $modTSconfig = [];
75
76 /**
77 * Internal backend template object
78 *
79 * @var DocumentTemplate
80 */
81 public $doc;
82
83 /**
84 * Used to accumulate the content of the module.
85 *
86 * @var string
87 */
88 public $content;
89
90 /**
91 * Access boolean.
92 *
93 * @var bool
94 */
95 public $access;
96
97 /**
98 * config of the wizard
99 *
100 * @var array
101 */
102 public $config;
103
104 /**
105 * @var array
106 */
107 protected $pageInfo;
108
109 /**
110 * @var string
111 */
112 protected $onClickEvent;
113
114 /**
115 * @var array
116 */
117 protected $MCONF;
118
119 /**
120 * @var StandaloneView
121 */
122 protected $view;
123
124 /**
125 * @var StandaloneView
126 */
127 protected $menuItemView;
128
129 /**
130 * ModuleTemplate object
131 *
132 * @var ModuleTemplate
133 */
134 protected $moduleTemplate;
135
136 /**
137 * Constructor
138 */
139 public function __construct()
140 {
141 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
142 $GLOBALS['SOBE'] = $this;
143 $this->view = $this->getFluidTemplateObject();
144 $this->menuItemView = $this->getFluidTemplateObject('MenuItem.html');
145 $this->init();
146 }
147
148 /**
149 * Constructor, initializing internal variables.
150 */
151 public function init()
152 {
153 $lang = $this->getLanguageService();
154 $lang->includeLLFile('EXT:lang/Resources/Private/Language/locallang_misc.xlf');
155 $LOCAL_LANG_orig = $GLOBALS['LOCAL_LANG'];
156 $lang->includeLLFile('EXT:backend/Resources/Private/Language/locallang_db_new_content_el.xlf');
157 ArrayUtility::mergeRecursiveWithOverrule($LOCAL_LANG_orig, $GLOBALS['LOCAL_LANG']);
158 $GLOBALS['LOCAL_LANG'] = $LOCAL_LANG_orig;
159
160 // Setting internal vars:
161 $this->id = (int)GeneralUtility::_GP('id');
162 $this->sys_language = (int)GeneralUtility::_GP('sys_language_uid');
163 $this->R_URI = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
164 $this->colPos = GeneralUtility::_GP('colPos') === null ? null : (int)GeneralUtility::_GP('colPos');
165 $this->uid_pid = (int)GeneralUtility::_GP('uid_pid');
166 $this->MCONF['name'] = 'xMOD_db_new_content_el';
167 $this->modTSconfig = BackendUtility::getModTSconfig($this->id, 'mod.wizards.newContentElement');
168 $config = BackendUtility::getPagesTSconfig($this->id);
169 $this->config = $config['mod.']['wizards.']['newContentElement.'];
170 // Starting the document template object:
171 // We keep this here in case somebody relies on it in a hook or alike
172 $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
173 // Setting up the context sensitive menu:
174 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
175 // Getting the current page and receiving access information (used in main())
176 $perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
177 $this->pageInfo = BackendUtility::readPageAccess($this->id, $perms_clause);
178 $this->access = is_array($this->pageInfo);
179 }
180
181 /**
182 * Injects the request object for the current request or subrequest
183 * As this controller goes only through the main() method, it is rather simple for now
184 *
185 * @param ServerRequestInterface $request the current request
186 * @param ResponseInterface $response
187 * @return ResponseInterface the response with the content
188 */
189 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
190 {
191 $this->main();
192 $this->moduleTemplate->setContent($this->content);
193 $response->getBody()->write($this->moduleTemplate->renderContent());
194 return $response;
195 }
196
197 /**
198 * Creating the module output.
199 *
200 * @throws \UnexpectedValueException
201 */
202 public function main()
203 {
204 $hasAccess = true;
205 if ($this->id && $this->access) {
206
207 // Init position map object:
208 $posMap = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\View\ContentCreationPagePositionMap::class);
209 $posMap->cur_sys_language = $this->sys_language;
210 // If a column is pre-set:
211 if (isset($this->colPos)) {
212 if ($this->uid_pid < 0) {
213 $row = [];
214 $row['uid'] = abs($this->uid_pid);
215 } else {
216 $row = '';
217 }
218 $this->onClickEvent = $posMap->onClickInsertRecord(
219 $row,
220 $this->colPos,
221 '',
222 $this->uid_pid,
223 $this->sys_language
224 );
225 } else {
226 $this->onClickEvent = '';
227 }
228 // ***************************
229 // Creating content
230 // ***************************
231 // Wizard
232 $wizardItems = $this->wizardArray();
233 // Wrapper for wizards
234 // Hook for manipulating wizardItems, wrapper, onClickEvent etc.
235 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms']['db_new_content_el']['wizardItemsHook'] ?? [] as $className) {
236 $hookObject = GeneralUtility::makeInstance($className);
237 if (!$hookObject instanceof NewContentElementWizardHookInterface) {
238 throw new \UnexpectedValueException(
239 $className . ' must implement interface ' . NewContentElementWizardHookInterface::class,
240 1227834741
241 );
242 }
243 $hookObject->manipulateWizardItems($wizardItems, $this);
244 }
245 // Add document inline javascript
246 $this->moduleTemplate->addJavaScriptCode(
247 'NewContentElementWizardInlineJavascript',
248 '
249 function goToalt_doc() {
250 ' . $this->onClickEvent . '
251 }'
252 );
253
254 // Traverse items for the wizard.
255 // An item is either a header or an item rendered with a radio button and title/description and icon:
256 $cc = ($key = 0);
257 $menuItems = [];
258
259 $this->view->assign('onClickEvent', $this->onClickEvent);
260
261 foreach ($wizardItems as $wizardKey => $wInfo) {
262 $wizardOnClick = '';
263 if ($wInfo['header']) {
264 $menuItems[] = [
265 'label' => $wInfo['header'],
266 'content' => ''
267 ];
268 $key = count($menuItems) - 1;
269 } else {
270 if (!$this->onClickEvent) {
271 // Radio button:
272 $wizardOnClick = 'document.editForm.defValues.value=unescape(' . GeneralUtility::quoteJSvalue(rawurlencode($wInfo['params'])) . ');goToalt_doc();' . (!$this->onClickEvent ? 'window.location.hash=\'#sel2\';' : '');
273 // Onclick action for icon/title:
274 $aOnClick = 'document.getElementsByName(\'tempB\')[' . $cc . '].checked=1;' . $wizardOnClick . 'return false;';
275 } else {
276 $aOnClick = "document.editForm.defValues.value=unescape('" . rawurlencode($wInfo['params']) . "');goToalt_doc();" . (!$this->onClickEvent?"window.location.hash='#sel2';":'');
277 }
278
279 $icon = $this->moduleTemplate->getIconFactory()->getIcon($wInfo['iconIdentifier'])->render();
280
281 $this->menuItemView->assignMultiple([
282 'onClickEvent' => $this->onClickEvent,
283 'aOnClick' => $aOnClick,
284 'wizardInformation' => $wInfo,
285 'icon' => $icon,
286 'wizardOnClick' => $wizardOnClick,
287 'wizardKey' => $wizardKey
288 ]);
289 $menuItems[$key]['content'] .= $this->menuItemView->render();
290 $cc++;
291 }
292 }
293
294 $this->view->assign('renderedTabs', $this->moduleTemplate->getDynamicTabMenu(
295 $menuItems,
296 'new-content-element-wizard'
297 ));
298
299 // If the user must also select a column:
300 if (!$this->onClickEvent) {
301
302 // Load SHARED page-TSconfig settings and retrieve column list from there, if applicable:
303 $colPosArray = GeneralUtility::callUserFunction(
304 BackendLayoutView::class . '->getColPosListItemsParsed',
305 $this->id,
306 $this
307 );
308 $colPosIds = array_column($colPosArray, 1);
309 // Removing duplicates, if any
310 $colPosList = implode(',', array_unique(array_map('intval', $colPosIds)));
311 // Finally, add the content of the column selector to the content:
312 $this->view->assign('posMap', $posMap->printContentElementColumns($this->id, 0, $colPosList, 1, $this->R_URI));
313 }
314 } else {
315 // In case of no access:
316 $hasAccess = false;
317 }
318 $this->view->assign('hasAccess', $hasAccess);
319
320 $this->content = $this->view->render();
321 // Setting up the buttons and markers for docheader
322 $this->getButtons();
323 }
324
325 /**
326 * Create the panel of buttons for submitting the form or otherwise perform operations.
327 */
328 protected function getButtons()
329 {
330 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
331 if ($this->R_URI) {
332 $backButton = $buttonBar->makeLinkButton()
333 ->setHref($this->R_URI)
334 ->setTitle($this->getLanguageService()->getLL('goBack'))
335 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
336 'actions-view-go-back',
337 Icon::SIZE_SMALL
338 ));
339 $buttonBar->addButton($backButton);
340 }
341 $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('new_ce');
342 $buttonBar->addButton($cshButton);
343 }
344
345 /***************************
346 *
347 * OTHER FUNCTIONS:
348 *
349 ***************************/
350
351 /**
352 * Returns the array of elements in the wizard display.
353 * For the plugin section there is support for adding elements there from a global variable.
354 *
355 * @return array
356 */
357 public function wizardArray()
358 {
359 $wizardItems = [];
360 if (is_array($this->config)) {
361 $wizards = $this->config['wizardItems.'];
362 $appendWizards = $this->wizard_appendWizards($wizards['elements.']);
363 if (is_array($wizards)) {
364 foreach ($wizards as $groupKey => $wizardGroup) {
365 $this->prepareDependencyOrdering($wizards[$groupKey], 'before');
366 $this->prepareDependencyOrdering($wizards[$groupKey], 'after');
367 }
368 $wizards = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($wizards);
369
370 foreach ($wizards as $groupKey => $wizardGroup) {
371 $groupKey = rtrim($groupKey, '.');
372 $showItems = GeneralUtility::trimExplode(',', $wizardGroup['show'], true);
373 $showAll = in_array('*', $showItems, true);
374 $groupItems = [];
375 if (is_array($appendWizards[$groupKey . '.']['elements.'])) {
376 $wizardElements = array_merge((array)$wizardGroup['elements.'], $appendWizards[$groupKey . '.']['elements.']);
377 } else {
378 $wizardElements = $wizardGroup['elements.'];
379 }
380 if (is_array($wizardElements)) {
381 foreach ($wizardElements as $itemKey => $itemConf) {
382 $itemKey = rtrim($itemKey, '.');
383 if ($showAll || in_array($itemKey, $showItems)) {
384 $tmpItem = $this->wizard_getItem($groupKey, $itemKey, $itemConf);
385 if ($tmpItem) {
386 $groupItems[$groupKey . '_' . $itemKey] = $tmpItem;
387 }
388 }
389 }
390 }
391 if (!empty($groupItems)) {
392 $wizardItems[$groupKey] = $this->wizard_getGroupHeader($groupKey, $wizardGroup);
393 $wizardItems = array_merge($wizardItems, $groupItems);
394 }
395 }
396 }
397 }
398 // Remove elements where preset values are not allowed:
399 $this->removeInvalidElements($wizardItems);
400 return $wizardItems;
401 }
402
403 /**
404 * @param mixed $wizardElements
405 * @return array
406 */
407 public function wizard_appendWizards($wizardElements)
408 {
409 if (!is_array($wizardElements)) {
410 $wizardElements = [];
411 }
412 if (is_array($GLOBALS['TBE_MODULES_EXT']['xMOD_db_new_content_el']['addElClasses'])) {
413 foreach ($GLOBALS['TBE_MODULES_EXT']['xMOD_db_new_content_el']['addElClasses'] as $class => $path) {
414 require_once $path;
415 $modObj = GeneralUtility::makeInstance($class);
416 $wizardElements = $modObj->proc($wizardElements);
417 }
418 }
419 $returnElements = [];
420 foreach ($wizardElements as $key => $wizardItem) {
421 preg_match('/^[a-zA-Z0-9]+_/', $key, $group);
422 $wizardGroup = $group[0] ? substr($group[0], 0, -1) . '.' : $key;
423 $returnElements[$wizardGroup]['elements.'][substr($key, strlen($wizardGroup)) . '.'] = $wizardItem;
424 }
425 return $returnElements;
426 }
427
428 /**
429 * @param string $groupKey Not used
430 * @param string $itemKey Not used
431 * @param array $itemConf
432 * @return array
433 */
434 public function wizard_getItem($groupKey, $itemKey, $itemConf)
435 {
436 $itemConf['title'] = $this->getLanguageService()->sL($itemConf['title']);
437 $itemConf['description'] = $this->getLanguageService()->sL($itemConf['description']);
438 $itemConf['tt_content_defValues'] = $itemConf['tt_content_defValues.'];
439 unset($itemConf['tt_content_defValues.']);
440 return $itemConf;
441 }
442
443 /**
444 * @param string $groupKey Not used
445 * @param array $wizardGroup
446 * @return array
447 */
448 public function wizard_getGroupHeader($groupKey, $wizardGroup)
449 {
450 return [
451 'header' => $this->getLanguageService()->sL($wizardGroup['header'])
452 ];
453 }
454
455 /**
456 * Checks the array for elements which might contain unallowed default values and will unset them!
457 * Looks for the "tt_content_defValues" key in each element and if found it will traverse that array as fieldname /
458 * value pairs and check.
459 * The values will be added to the "params" key of the array (which should probably be unset or empty by default).
460 *
461 * @param array $wizardItems Wizard items, passed by reference
462 */
463 public function removeInvalidElements(&$wizardItems)
464 {
465 // Get TCEFORM from TSconfig of current page
466 $row = ['pid' => $this->id];
467 $TCEFORM_TSconfig = BackendUtility::getTCEFORM_TSconfig('tt_content', $row);
468 $headersUsed = [];
469 // Traverse wizard items:
470 foreach ($wizardItems as $key => $cfg) {
471 // Exploding parameter string, if any (old style)
472 if ($wizardItems[$key]['params']) {
473 // Explode GET vars recursively
474 $tempGetVars = GeneralUtility::explodeUrl2Array($wizardItems[$key]['params'], true);
475 // If tt_content values are set, merge them into the tt_content_defValues array,
476 // unset them from $tempGetVars and re-implode $tempGetVars into the param string
477 // (in case remaining parameters are around).
478 if (is_array($tempGetVars['defVals']['tt_content'])) {
479 $wizardItems[$key]['tt_content_defValues'] = array_merge(
480 is_array($wizardItems[$key]['tt_content_defValues']) ? $wizardItems[$key]['tt_content_defValues'] : [],
481 $tempGetVars['defVals']['tt_content']
482 );
483 unset($tempGetVars['defVals']['tt_content']);
484 $wizardItems[$key]['params'] = GeneralUtility::implodeArrayForUrl('', $tempGetVars);
485 }
486 }
487 // If tt_content_defValues are defined...:
488 if (is_array($wizardItems[$key]['tt_content_defValues'])) {
489 $backendUser = $this->getBackendUser();
490 // Traverse field values:
491 foreach ($wizardItems[$key]['tt_content_defValues'] as $fN => $fV) {
492 if (is_array($GLOBALS['TCA']['tt_content']['columns'][$fN])) {
493 // Get information about if the field value is OK:
494 $config = &$GLOBALS['TCA']['tt_content']['columns'][$fN]['config'];
495 $authModeDeny = $config['type'] === 'select' && $config['authMode']
496 && !$backendUser->checkAuthMode('tt_content', $fN, $fV, $config['authMode']);
497 // explode TSconfig keys only as needed
498 if (!isset($removeItems[$fN]) && isset($TCEFORM_TSconfig[$fN]['removeItems']) && $TCEFORM_TSconfig[$fN]['removeItems'] !== '') {
499 $removeItems[$fN] = array_flip(GeneralUtility::trimExplode(
500 ',',
501 $TCEFORM_TSconfig[$fN]['removeItems'],
502 true
503 ));
504 }
505 if (!isset($keepItems[$fN]) && isset($TCEFORM_TSconfig[$fN]['keepItems']) && $TCEFORM_TSconfig[$fN]['keepItems'] !== '') {
506 $keepItems[$fN] = array_flip(GeneralUtility::trimExplode(
507 ',',
508 $TCEFORM_TSconfig[$fN]['keepItems'],
509 true
510 ));
511 }
512 $isNotInKeepItems = !empty($keepItems[$fN]) && !isset($keepItems[$fN][$fV]);
513 if ($authModeDeny || ($fN === 'CType' && (isset($removeItems[$fN][$fV]) || $isNotInKeepItems))) {
514 // Remove element all together:
515 unset($wizardItems[$key]);
516 break;
517 }
518 // Add the parameter:
519 $wizardItems[$key]['params'] .= '&defVals[tt_content][' . $fN . ']=' . rawurlencode($this->getLanguageService()->sL($fV));
520 $tmp = explode('_', $key);
521 $headersUsed[$tmp[0]] = $tmp[0];
522 }
523 }
524 }
525 }
526 // remove headers without elements
527 foreach ($wizardItems as $key => $cfg) {
528 $tmp = explode('_', $key);
529 if ($tmp[0] && !$tmp[1] && !in_array($tmp[0], $headersUsed)) {
530 unset($wizardItems[$key]);
531 }
532 }
533 }
534
535 /**
536 * Prepare a wizard tab configuration for sorting.
537 *
538 * @param array $wizardGroup TypoScript wizard tab configuration
539 * @param string $key Which array key should be prepared
540 */
541 protected function prepareDependencyOrdering(&$wizardGroup, $key)
542 {
543 if (isset($wizardGroup[$key])) {
544 $wizardGroup[$key] = GeneralUtility::trimExplode(',', $wizardGroup[$key]);
545 $wizardGroup[$key] = array_map(function ($s) {
546 return $s . '.';
547 }, $wizardGroup[$key]);
548 }
549 }
550
551 /**
552 * Returns LanguageService
553 *
554 * @return \TYPO3\CMS\Core\Localization\LanguageService
555 */
556 protected function getLanguageService()
557 {
558 return $GLOBALS['LANG'];
559 }
560
561 /**
562 * Returns the current BE user.
563 *
564 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
565 */
566 protected function getBackendUser()
567 {
568 return $GLOBALS['BE_USER'];
569 }
570
571 /**
572 * returns a new standalone view, shorthand function
573 *
574 * @param string $filename
575 * @return StandaloneView
576 */
577 protected function getFluidTemplateObject(string $filename = 'Main.html'):StandaloneView
578 {
579 /** @var StandaloneView $view */
580 $view = GeneralUtility::makeInstance(StandaloneView::class);
581 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/NewContentElement/' . $filename));
582 $view->getRequest()->setControllerExtensionName('Backend');
583 return $view;
584 }
585 }