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