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