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