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