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