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