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