[TASK] Use GeneralUtility::quoteJSvalue() where needed part 2
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / AbstractFormElement.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Element;
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\Form\FormEngine;
18 use TYPO3\CMS\Backend\Form\DataPreprocessor;
19 use TYPO3\CMS\Backend\Template\DocumentTemplate;
20 use TYPO3\CMS\Lang\LanguageService;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
23 use TYPO3\CMS\Core\Utility\MathUtility;
24 use TYPO3\CMS\Backend\Utility\BackendUtility;
25 use TYPO3\CMS\Backend\Utility\IconUtility;
26 use TYPO3\CMS\Backend\Form\Wizard\SuggestWizard;
27 use TYPO3\CMS\Backend\Form\Wizard\ValueSliderWizard;
28 use TYPO3\CMS\Core\Utility\ArrayUtility;
29 use TYPO3\CMS\Backend\Form\DatabaseFileIconsHookInterface;
30 use TYPO3\CMS\Backend\Clipboard\Clipboard;
31 use TYPO3\CMS\Backend\Form\AbstractNode;
32 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
33 use TYPO3\CMS\Backend\Form\NodeFactory;
34
35 /**
36 * Base class for form elements of FormEngine. Contains several helper methods used by single elements.
37 */
38 abstract class AbstractFormElement extends AbstractNode {
39
40 /**
41 * Default width value for a couple of elements like text
42 *
43 * @var int
44 */
45 protected $defaultInputWidth = 30;
46
47 /**
48 * Minimum width value for a couple of elements like text
49 *
50 * @var int
51 */
52 protected $minimumInputWidth = 10;
53
54 /**
55 * Maximum width value for a couple of elements like text
56 *
57 * @var int
58 */
59 protected $maxInputWidth = 50;
60
61 /**
62 * @var \TYPO3\CMS\Backend\Clipboard\Clipboard|NULL
63 */
64 protected $clipboard = NULL;
65
66 /**
67 * @return bool TRUE if field is set to read only
68 */
69 protected function isGlobalReadonly() {
70 return !empty($this->globalOptions['renderReadonly']);
71 }
72
73 /**
74 * @return bool TRUE if wizards are disabled on a global level
75 */
76 protected function isWizardsDisabled() {
77 return !empty($this->globalOptions['disabledWizards']);
78 }
79
80 /**
81 * @return string URL to return to this entry script
82 */
83 protected function getReturnUrl() {
84 return isset($this->globalOptions['returnUrl']) ? $this->globalOptions['returnUrl'] : '';
85 }
86
87 /**
88 * Returns the max width in pixels for a elements like input and text
89 *
90 * @param int $size The abstract size value (1-48)
91 * @return int Maximum width in pixels
92 */
93 protected function formMaxWidth($size = 48) {
94 $compensationForLargeDocuments = 1.33;
95 $compensationForFormFields = 12;
96
97 $size = round($size * $compensationForLargeDocuments);
98 return ceil($size * $compensationForFormFields);
99 }
100
101 /**
102 * Rendering wizards for form fields.
103 *
104 * @param array $itemKinds Array with the real item in the first value, and an alternative item in the second value.
105 * @param array $wizConf The "wizard" key from the config array for the field (from TCA)
106 * @param string $table Table name
107 * @param array $row The record array
108 * @param string $field The field name
109 * @param array $PA Additional configuration array.
110 * @param string $itemName The field name
111 * @param array $specConf Special configuration if available.
112 * @param bool $RTE Whether the RTE could have been loaded.
113 * @return string The new item value.
114 */
115 protected function renderWizards($itemKinds, $wizConf, $table, $row, $field, $PA, $itemName, $specConf, $RTE = FALSE) {
116 // Return not changed main item directly if wizards are disabled
117 if (!is_array($wizConf) || $this->isWizardsDisabled()) {
118 return $itemKinds[0];
119 }
120
121 $languageService = $this->getLanguageService();
122
123 $fieldChangeFunc = $PA['fieldChangeFunc'];
124 $item = $itemKinds[0];
125 $fName = '[' . $table . '][' . $row['uid'] . '][' . $field . ']';
126 $md5ID = 'ID' . GeneralUtility::shortmd5($itemName);
127 $fieldConfig = $PA['fieldConf']['config'];
128 $prefixOfFormElName = 'data[' . $table . '][' . $row['uid'] . '][' . $field . ']';
129 $flexFormPath = '';
130 if (GeneralUtility::isFirstPartOfStr($PA['itemFormElName'], $prefixOfFormElName)) {
131 $flexFormPath = str_replace('][', '/', substr($PA['itemFormElName'], strlen($prefixOfFormElName) + 1, -1));
132 }
133
134 // Manipulate the field name (to be the TRUE form field name) and remove
135 // a suffix-value if the item is a selector box with renderMode "singlebox":
136 $listFlag = '_list';
137 if ($PA['fieldConf']['config']['type'] == 'select') {
138 // Single select situation:
139 if ($PA['fieldConf']['config']['maxitems'] <= 1) {
140 $listFlag = '';
141 } elseif ($PA['fieldConf']['config']['renderMode'] == 'singlebox') {
142 $itemName .= '[]';
143 $listFlag = '';
144 }
145 }
146
147 // Contains wizard identifiers enabled for this record type, see "special configuration" docs
148 $wizardsEnabledByType = $specConf['wizards']['parameters'];
149
150 $buttonWizards = array();
151 $otherWizards = array();
152 foreach ($wizConf as $wizardIdentifier => $wizardConfiguration) {
153 // If an identifier starts with "_", this is a configuration option like _POSITION and not a wizard
154 if ($wizardIdentifier[0] === '_') {
155 continue;
156 }
157
158 // Sanitize wizard type
159 $wizardConfiguration['type'] = (string)$wizardConfiguration['type'];
160
161 // Wizards can be shown based on selected "type" of record. If this is the case, the wizard configuration
162 // is set to enableByTypeConfig = 1, and the wizardIdentifier is found in $wizardsEnabledByType
163 $wizardIsEnabled = TRUE;
164 if (
165 isset($wizardConfiguration['enableByTypeConfig'])
166 && (bool)$wizardConfiguration['enableByTypeConfig']
167 && (!is_array($wizardsEnabledByType) || !in_array($wizardIdentifier, $wizardsEnabledByType))
168 ) {
169 $wizardIsEnabled = FALSE;
170 }
171 // Disable if wizard is for RTE fields only and the handled field is no RTE field or RTE can not be loaded
172 if (isset($wizardConfiguration['RTEonly']) && (bool)$wizardConfiguration['RTEonly'] && !$RTE) {
173 $wizardIsEnabled = FALSE;
174 }
175 // Disable if wizard is for not-new records only and we're handling a new record
176 if (isset($wizardConfiguration['notNewRecords']) && $wizardConfiguration['notNewRecords'] && !MathUtility::canBeInterpretedAsInteger($row['uid'])) {
177 $wizardIsEnabled = FALSE;
178 }
179 // Wizard types script, colorbox and popup must contain a module name configuration
180 if (!isset($wizardConfiguration['module']['name']) && in_array($wizardConfiguration['type'], array('script', 'colorbox', 'popup'), TRUE)) {
181 $wizardIsEnabled = FALSE;
182 }
183
184 if (!$wizardIsEnabled) {
185 continue;
186 }
187
188 // Title / icon:
189 $iTitle = htmlspecialchars($languageService->sL($wizardConfiguration['title']));
190 if (isset($wizardConfiguration['icon'])) {
191 $icon = FormEngineUtility::getIconHtml($wizardConfiguration['icon'], $iTitle, $iTitle);
192 } else {
193 $icon = $iTitle;
194 }
195
196 switch ($wizardConfiguration['type']) {
197 case 'userFunc':
198 $params = array();
199 $params['fieldConfig'] = $fieldConfig;
200 $params['params'] = $wizardConfiguration['params'];
201 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
202 $params['table'] = $table;
203 $params['uid'] = $row['uid'];
204 $params['pid'] = $row['pid'];
205 $params['field'] = $field;
206 $params['flexFormPath'] = $flexFormPath;
207 $params['md5ID'] = $md5ID;
208 $params['returnUrl'] = $this->getReturnUrl();
209
210 $params['formName'] = 'editform';
211 $params['itemName'] = $itemName;
212 $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js');
213 $params['fieldChangeFunc'] = $fieldChangeFunc;
214 $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc));
215
216 $params['item'] = &$item;
217 $params['icon'] = $icon;
218 $params['iTitle'] = $iTitle;
219 $params['wConf'] = $wizardConfiguration;
220 $params['row'] = $row;
221 $formEngineDummy = new FormEngine;
222 $otherWizards[] = GeneralUtility::callUserFunction($wizardConfiguration['userFunc'], $params, $formEngineDummy);
223 break;
224
225 case 'script':
226 $params = array();
227 // Including the full fieldConfig from TCA may produce too long an URL
228 if ($wizardIdentifier != 'RTE') {
229 $params['fieldConfig'] = $fieldConfig;
230 }
231 $params['params'] = $wizardConfiguration['params'];
232 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
233 $params['table'] = $table;
234 $params['uid'] = $row['uid'];
235 $params['pid'] = $row['pid'];
236 $params['field'] = $field;
237 $params['flexFormPath'] = $flexFormPath;
238 $params['md5ID'] = $md5ID;
239 $params['returnUrl'] = $this->getReturnUrl();
240
241 // Resolving script filename and setting URL.
242 $urlParameters = array();
243 if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) {
244 $urlParameters = $wizardConfiguration['module']['urlParameters'];
245 }
246 $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, '');
247 $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', array('P' => $params));
248 $buttonWizards[] =
249 '<a class="btn btn-default" href="' . htmlspecialchars($url) . '" onclick="this.blur(); return !TBE_EDITOR.isFormChanged();">'
250 . $icon .
251 '</a>';
252 break;
253
254 case 'popup':
255 $params = array();
256 $params['fieldConfig'] = $fieldConfig;
257 $params['params'] = $wizardConfiguration['params'];
258 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
259 $params['table'] = $table;
260 $params['uid'] = $row['uid'];
261 $params['pid'] = $row['pid'];
262 $params['field'] = $field;
263 $params['flexFormPath'] = $flexFormPath;
264 $params['md5ID'] = $md5ID;
265 $params['returnUrl'] = $this->getReturnUrl();
266
267 $params['formName'] = 'editform';
268 $params['itemName'] = $itemName;
269 $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js');
270 $params['fieldChangeFunc'] = $fieldChangeFunc;
271 $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc));
272
273 // Resolving script filename and setting URL.
274 $urlParameters = array();
275 if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) {
276 $urlParameters = $wizardConfiguration['module']['urlParameters'];
277 }
278 $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, '');
279 $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', array('P' => $params));
280
281 $onlyIfSelectedJS = '';
282 if (isset($wizardConfiguration['popup_onlyOpenIfSelected']) && $wizardConfiguration['popup_onlyOpenIfSelected']) {
283 $notSelectedText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.noSelItemForEdit');
284 $onlyIfSelectedJS =
285 'if (!TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName . $listFlag) . ')){' .
286 'alert(' . GeneralUtility::quoteJSvalue($notSelectedText) . ');' .
287 'return false;' .
288 '}';
289 }
290 $aOnClick =
291 'this.blur();' .
292 $onlyIfSelectedJS .
293 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' .
294 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value,200' .
295 ')' .
296 '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName . $listFlag) . '),' .
297 GeneralUtility::quoteJSvalue('popUp' . $md5ID) . ',' .
298 GeneralUtility::quoteJSvalue($wizardConfiguration['JSopenParams']) .
299 ');' .
300 'vHWin.focus();' .
301 'return false;';
302
303 $buttonWizards[] =
304 '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '">' .
305 $icon .
306 '</a>';
307 break;
308
309 case 'colorbox':
310 $params = array();
311 $params['fieldConfig'] = $fieldConfig;
312 $params['params'] = $wizardConfiguration['params'];
313 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
314 $params['table'] = $table;
315 $params['uid'] = $row['uid'];
316 $params['pid'] = $row['pid'];
317 $params['field'] = $field;
318 $params['flexFormPath'] = $flexFormPath;
319 $params['md5ID'] = $md5ID;
320 $params['returnUrl'] = $this->getReturnUrl();
321
322 $params['formName'] = 'editform';
323 $params['itemName'] = $itemName;
324 $params['hmac'] = GeneralUtility::hmac($params['formName'] . $params['itemName'], 'wizard_js');
325 $params['fieldChangeFunc'] = $fieldChangeFunc;
326 $params['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($fieldChangeFunc));
327
328 // Resolving script filename and setting URL.
329 $urlParameters = array();
330 if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) {
331 $urlParameters = $wizardConfiguration['module']['urlParameters'];
332 }
333 $wScript = BackendUtility::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, '');
334 $url = $wScript . (strstr($wScript, '?') ? '' : '?') . GeneralUtility::implodeArrayForUrl('', array('P' => $params));
335
336 $aOnClick =
337 'this.blur();' .
338 'vHWin=window.open('. GeneralUtility::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' .
339 'document.editform[\'' . $itemName . '\'].value,200' .
340 ')' .
341 '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility::quoteJSvalue($itemName . $listFlag) . '),' .
342 GeneralUtility::quoteJSvalue('popUp' . $md5ID) . ',' .
343 GeneralUtility::quoteJSvalue($wizardConfiguration['JSopenParams']) .
344 ');' .
345 'vHWin.focus();' .
346 'return false;';
347
348 $dim = GeneralUtility::intExplode('x', $wizardConfiguration['dim']);
349 $dX = MathUtility::forceIntegerInRange($dim[0], 1, 200, 20);
350 $dY = MathUtility::forceIntegerInRange($dim[1], 1, 200, 20);
351 $color = $PA['itemFormElValue'] ? ' bgcolor="' . htmlspecialchars($PA['itemFormElValue']) . '"' : '';
352 $skinImg = IconUtility::skinImg(
353 '',
354 $PA['itemFormElValue'] === '' ? 'gfx/colorpicker_empty.png' : 'gfx/colorpicker.png',
355 'width="' . $dX . '" height="' . $dY . '"' . BackendUtility::titleAltAttrib(trim($iTitle . ' ' . $PA['itemFormElValue'])) . ' border="0"'
356 );
357 $otherWizards[] =
358 '<table border="0" id="' . $md5ID . '"' . $color . ' style="' . htmlspecialchars($wizardConfiguration['tableStyle']) . '">' .
359 '<tr>' .
360 '<td>' .
361 '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . '<img ' . $skinImg . '>' . '</a>' .
362 '</td>' .
363 '</tr>' .
364 '</table>';
365 break;
366
367 case 'slider':
368 $params = array();
369 $params['fieldConfig'] = $fieldConfig;
370 $params['field'] = $field;
371 $params['flexFormPath'] = $flexFormPath;
372 $params['md5ID'] = $md5ID;
373 $params['itemName'] = $itemName;
374 $params['fieldChangeFunc'] = $fieldChangeFunc;
375 $params['wConf'] = $wizardConfiguration;
376 $params['row'] = $row;
377
378 /** @var ValueSliderWizard $wizard */
379 $wizard = GeneralUtility::makeInstance(ValueSliderWizard::class);
380 $otherWizards[] = $wizard->renderWizard($params);
381 break;
382
383 case 'select':
384 $fieldValue = array('config' => $wizardConfiguration);
385 $TSconfig = FormEngineUtility::getTSconfigForTableRow($table, $row);
386 $TSconfig[$field] = $TSconfig[$field]['wizards.'][$wizardIdentifier . '.'];
387 $selItems = FormEngineUtility::addSelectOptionsToItemArray(FormEngineUtility::initItemArray($fieldValue), $fieldValue, $TSconfig, $field);
388 // Process items by a user function:
389 if (!empty($wizardConfiguration['itemsProcFunc'])) {
390 $funcConfig = !empty($wizardConfiguration['itemsProcFunc.']) ? $wizardConfiguration['itemsProcFunc.'] : array();
391 $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
392 $selItems = $dataPreprocessor->procItems($selItems, $funcConfig, $wizardConfiguration, $table, $row, $field);
393 }
394 $options = array();
395 $options[] = '<option>' . $iTitle . '</option>';
396 foreach ($selItems as $p) {
397 $options[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>';
398 }
399 if ($wizardConfiguration['mode'] == 'append') {
400 $assignValue = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
401 } elseif ($wizardConfiguration['mode'] == 'prepend') {
402 $assignValue = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value+=\'\'+this.options[this.selectedIndex].value';
403 } else {
404 $assignValue = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value=this.options[this.selectedIndex].value';
405 }
406 $otherWizards[] =
407 '<select' .
408 ' id="' . str_replace('.', '', uniqid('tceforms-select-', TRUE)) . '"' .
409 ' class="form-control tceforms-select tceforms-wizardselect"' .
410 ' name="_WIZARD' . $fName . '"' .
411 ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"'.
412 '>' .
413 implode('', $options) .
414 '</select>';
415 break;
416 case 'suggest':
417 if (!empty($PA['fieldTSConfig']['suggest.']['default.']['hide'])) {
418 break;
419 }
420 /** @var SuggestWizard $suggestWizard */
421 $suggestWizard = GeneralUtility::makeInstance(SuggestWizard::class);
422 $otherWizards[] = $suggestWizard->renderSuggestSelector($PA['itemFormElName'], $table, $field, $row, $PA);
423 break;
424 }
425
426 // Hide the real form element?
427 if (is_array($wizardConfiguration['hideParent']) || $wizardConfiguration['hideParent']) {
428 // Setting the item to a hidden-field.
429 $item = $itemKinds[1];
430 if (is_array($wizardConfiguration['hideParent'])) {
431 $options = $this->globalOptions;
432 $options['parameterArray'] = array(
433 'fieldConf' => array(
434 'config' => $wizardConfiguration['hideParent'],
435 ),
436 'itemFormElValue' => $PA['itemFormElValue'],
437 );
438 $options['type'] = 'none';
439 /** @var NodeFactory $nodeFactory */
440 $nodeFactory = $this->globalOptions['nodeFactory'];
441 $noneElementResult = $nodeFactory->create($options)->render();
442 $item .= $noneElementResult['html'];
443 }
444 }
445 }
446
447 // For each rendered wizard, put them together around the item.
448 if (!empty($buttonWizards) || !empty($otherWizards)) {
449 if ($wizConf['_HIDDENFIELD']) {
450 $item = $itemKinds[1];
451 }
452
453 $innerContent = '';
454 if (!empty($buttonWizards)) {
455 $innerContent .= '<div class="btn-group' . ($wizConf['_VERTICAL'] ? ' btn-group-vertical' : '') . '">' . implode('', $buttonWizards) . '</div>';
456 }
457 $innerContent .= implode(' ', $otherWizards);
458
459 // Position
460 $classes = array('form-wizards-wrap');
461 if ($wizConf['_POSITION'] === 'left') {
462 $classes[] = 'form-wizards-aside';
463 $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>';
464 } elseif ($wizConf['_POSITION'] === 'top') {
465 $classes[] = 'form-wizards-top';
466 $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>';
467 } elseif ($wizConf['_POSITION'] === 'bottom') {
468 $classes[] = 'form-wizards-bottom';
469 $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>';
470 } else {
471 $classes[] = 'form-wizards-aside';
472 $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>';
473 }
474 $item = '
475 <div class="' . implode(' ', $classes) . '">
476 ' . $innerContent . '
477 </div>';
478 }
479
480 return $item;
481 }
482
483 /**
484 * Prints the selector box form-field for the db/file/select elements (multiple)
485 *
486 * @param string $fName Form element name
487 * @param string $mode Mode "db", "file" (internal_type for the "group" type) OR blank (then for the "select" type)
488 * @param string $allowed Commalist of "allowed
489 * @param array $itemArray The array of items. For "select" and "group"/"file" this is just a set of value. For "db" its an array of arrays with table/uid pairs.
490 * @param string $selector Alternative selector box.
491 * @param array $params An array of additional parameters, eg: "size", "info", "headers" (array with "selector" and "items"), "noBrowser", "thumbnails
492 * @param string $onFocus On focus attribute string
493 * @param string $table (optional) Table name processing for
494 * @param string $field (optional) Field of table name processing for
495 * @param string $uid (optional) uid of table record processing for
496 * @param array $config (optional) The TCA field config
497 * @return string The form fields for the selection.
498 * @throws \UnexpectedValueException
499 */
500 protected function dbFileIcons($fName, $mode, $allowed, $itemArray, $selector = '', $params = array(), $onFocus = '', $table = '', $field = '', $uid = '', $config = array()) {
501 $languageService = $this->getLanguageService();
502 $disabled = '';
503 if ($this->isGlobalReadonly() || $params['readOnly']) {
504 $disabled = ' disabled="disabled"';
505 }
506 // INIT
507 $uidList = array();
508 $opt = array();
509 $itemArrayC = 0;
510 // Creating <option> elements:
511 if (is_array($itemArray)) {
512 $itemArrayC = count($itemArray);
513 switch ($mode) {
514 case 'db':
515 foreach ($itemArray as $pp) {
516 $pRec = BackendUtility::getRecordWSOL($pp['table'], $pp['id']);
517 if (is_array($pRec)) {
518 $pTitle = BackendUtility::getRecordTitle($pp['table'], $pRec, FALSE, TRUE);
519 $pUid = $pp['table'] . '_' . $pp['id'];
520 $uidList[] = $pUid;
521 $title = htmlspecialchars($pTitle);
522 $opt[] = '<option value="' . htmlspecialchars($pUid) . '" title="' . $title . '">' . $title . '</option>';
523 }
524 }
525 break;
526 case 'file_reference':
527
528 case 'file':
529 foreach ($itemArray as $item) {
530 $itemParts = explode('|', $item);
531 $uidList[] = ($pUid = ($pTitle = $itemParts[0]));
532 $title = htmlspecialchars(rawurldecode($itemParts[1]));
533 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($itemParts[0])) . '" title="' . $title . '">' . $title . '</option>';
534 }
535 break;
536 case 'folder':
537 foreach ($itemArray as $pp) {
538 $pParts = explode('|', $pp);
539 $uidList[] = ($pUid = ($pTitle = $pParts[0]));
540 $title = htmlspecialchars(rawurldecode($pParts[0]));
541 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pParts[0])) . '" title="' . $title . '">' . $title . '</option>';
542 }
543 break;
544 default:
545 foreach ($itemArray as $pp) {
546 $pParts = explode('|', $pp, 2);
547 $uidList[] = ($pUid = $pParts[0]);
548 $pTitle = $pParts[1];
549 $title = htmlspecialchars(rawurldecode($pTitle));
550 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pUid)) . '" title="' . $title . '">' . $title . '</option>';
551 }
552 }
553 }
554 // Create selector box of the options
555 $sSize = $params['autoSizeMax']
556 ? MathUtility::forceIntegerInRange($itemArrayC + 1, MathUtility::forceIntegerInRange($params['size'], 1), $params['autoSizeMax'])
557 : $params['size'];
558 if (!$selector) {
559 $isMultiple = $params['maxitems'] != 1 && $params['size'] != 1;
560 $selector = '<select id="' . str_replace('.', '', uniqid('tceforms-multiselect-', TRUE)) . '" '
561 . ($params['noList'] ? 'style="display: none"' : 'size="' . $sSize . '" class="form-control tceforms-multiselect"')
562 . ($isMultiple ? ' multiple="multiple"' : '')
563 . ' name="' . $fName . '_list" ' . $onFocus . $params['style'] . $disabled . '>' . implode('', $opt)
564 . '</select>';
565 }
566 $icons = array(
567 'L' => array(),
568 'R' => array()
569 );
570 $rOnClickInline = '';
571 if (!$params['readOnly'] && !$params['noList']) {
572 if (!$params['noBrowser']) {
573 // Check against inline uniqueness
574 /** @var InlineStackProcessor $inlineStackProcessor */
575 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
576 $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
577 $inlineParent = $inlineStackProcessor->getStructureLevel(-1);
578 $aOnClickInline = '';
579 if (is_array($inlineParent) && $inlineParent['uid']) {
580 if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
581 $objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']) . '-' . $table;
582 $aOnClickInline = $objectPrefix . '|inline.checkUniqueElement|inline.setUniqueElement';
583 $rOnClickInline = 'inline.revertUnique(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',null,' . GeneralUtility::quoteJSvalue($uid) . ');';
584 }
585 }
586 if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserType'])) {
587 $elementBrowserType = $config['appearance']['elementBrowserType'];
588 } else {
589 $elementBrowserType = $mode;
590 }
591 if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserAllowed'])) {
592 $elementBrowserAllowed = $config['appearance']['elementBrowserAllowed'];
593 } else {
594 $elementBrowserAllowed = $allowed;
595 }
596 $aOnClick = 'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($elementBrowserType) . ','
597 . GeneralUtility::quoteJSvalue(($fName . '|||' . $elementBrowserAllowed . '|' . $aOnClickInline)) . '); return false;';
598 $icons['R'][] = '
599 <a href="#"
600 onclick="' . htmlspecialchars($aOnClick) . '"
601 class="btn btn-default"
602 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.browse_' . ($mode == 'db' ? 'db' : 'file'))) . '">
603 ' . IconUtility::getSpriteIcon('actions-insert-record') . '
604 </a>';
605 }
606 if (!$params['dontShowMoveIcons']) {
607 if ($sSize >= 5) {
608 $icons['L'][] = '
609 <a href="#"
610 class="btn btn-default t3-btn-moveoption-top"
611 data-fieldname="' . $fName . '"
612 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_to_top')) . '">
613 ' . IconUtility::getSpriteIcon('actions-move-to-top') . '
614 </a>';
615
616 }
617 $icons['L'][] = '
618 <a href="#"
619 class="btn btn-default t3-btn-moveoption-up"
620 data-fieldname="' . $fName . '"
621 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_up')) . '">
622 ' . IconUtility::getSpriteIcon('actions-move-up') . '
623 </a>';
624 $icons['L'][] = '
625 <a href="#"
626 class="btn btn-default t3-btn-moveoption-down"
627 data-fieldname="' . $fName . '"
628 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_down')) . '">
629 ' . IconUtility::getSpriteIcon('actions-move-down') . '
630 </a>';
631 if ($sSize >= 5) {
632 $icons['L'][] = '
633 <a href="#"
634 class="btn btn-default t3-btn-moveoption-bottom"
635 data-fieldname="' . $fName . '"
636 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_to_bottom')) . '">
637 ' . IconUtility::getSpriteIcon('actions-move-to-bottom') . '
638 </a>';
639 }
640 }
641 $clipElements = $this->getClipboardElements($allowed, $mode);
642 if (count($clipElements)) {
643 $aOnClick = '';
644 foreach ($clipElements as $elValue) {
645 if ($mode == 'db') {
646 list($itemTable, $itemUid) = explode('|', $elValue);
647 $recordTitle = BackendUtility::getRecordTitle($itemTable, BackendUtility::getRecordWSOL($itemTable, $itemUid));
648 $itemTitle = GeneralUtility::quoteJSvalue($recordTitle);
649 $elValue = $itemTable . '_' . $itemUid;
650 } else {
651 // 'file', 'file_reference' and 'folder' mode
652 $itemTitle = 'unescape(' . GeneralUtility::quoteJSvalue(rawurlencode(basename($elValue))) . ')';
653 }
654 $aOnClick .= 'setFormValueFromBrowseWin(' . GeneralUtility::quoteJSvalue($fName) . ',unescape('
655 . GeneralUtility::quoteJSvalue(rawurlencode(str_replace('%20', ' ', $elValue))) . '),' . $itemTitle . ',' . $itemTitle . ');';
656 }
657 $aOnClick .= 'return false;';
658 $icons['R'][] = '
659 <a href="#"
660 onclick="' . htmlspecialchars($aOnClick) . '"
661 title="' . htmlspecialchars(sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.clipInsert_' . ($mode == 'db' ? 'db' : 'file')), count($clipElements))) . '">
662 ' . IconUtility::getSpriteIcon('actions-document-paste-into') . '
663 </a>';
664 }
665 }
666 if (!$params['readOnly'] && !$params['noDelete']) {
667 $icons['L'][] = '
668 <a href="#"
669 class="btn btn-default t3-btn-removeoption"
670 onClick="' . $rOnClickInline . '"
671 data-fieldname="' . $fName . '"
672 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.remove_selected')) . '">
673 ' . IconUtility::getSpriteIcon('actions-selection-delete') . '
674 </a>';
675 }
676
677 // Thumbnails
678 $imagesOnly = FALSE;
679 if ($params['thumbnails'] && $params['allowed']) {
680 // In case we have thumbnails, check if only images are allowed.
681 // In this case, render them below the field, instead of to the right
682 $allowedExtensionList = $params['allowed'];
683 $imageExtensionList = GeneralUtility::trimExplode(',', strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']), TRUE);
684 $imagesOnly = TRUE;
685 foreach ($allowedExtensionList as $allowedExtension) {
686 if (!ArrayUtility::inArray($imageExtensionList, $allowedExtension)) {
687 $imagesOnly = FALSE;
688 break;
689 }
690 }
691 }
692 $thumbnails = '';
693 if (is_array($params['thumbnails']) && !empty($params['thumbnails'])) {
694 if ($imagesOnly) {
695 $thumbnails .= '<ul class="list-inline">';
696 foreach ($params['thumbnails'] as $thumbnail) {
697 $thumbnails .= '<li><span class="thumbnail">' . $thumbnail['image'] . '</span></li>';
698 }
699 $thumbnails .= '</ul>';
700 } else {
701 $thumbnails .= '<div class="table-fit"><table class="table table-white"><tbody>';
702 foreach ($params['thumbnails'] as $thumbnail) {
703 $thumbnails .= '
704 <tr>
705 <td class="col-icon">
706 ' . ($config['internal_type'] === 'db'
707 ? $this->getControllerDocumentTemplate()->wrapClickMenuOnIcon($thumbnail['image'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view')
708 : $thumbnail['image']) . '
709 </td>
710 <td class="col-title">
711 ' . ($config['internal_type'] === 'db'
712 ? $this->getControllerDocumentTemplate()->wrapClickMenuOnIcon($thumbnail['name'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view')
713 : $thumbnail['name']) . '
714 ' . ($config['internal_type'] === 'db' ? ' <span class="text-muted">[' . $thumbnail['uid'] . ']</span>' : '') . '
715 </td>
716 </tr>
717 ';
718 }
719 $thumbnails .= '</tbody></table></div>';
720 }
721 }
722
723 // Allowed Tables
724 $allowedTables = '';
725 if (is_array($params['allowedTables']) && !empty($params['allowedTables'])) {
726 $allowedTables .= '<div class="help-block">';
727 foreach ($params['allowedTables'] as $key => $item) {
728 if (is_array($item)) {
729 if (empty($params['readOnly'])) {
730 $allowedTables .= '<a href="#" onClick="' . htmlspecialchars($item['onClick']) . '" class="btn btn-default">' . $item['icon'] . ' ' . htmlspecialchars($item['name']) . '</a> ';
731 } else {
732 $allowedTables .= '<span>' . htmlspecialchars($item['name']) . '</span> ';
733 }
734 } elseif($key === 'name') {
735 $allowedTables .= '<span>' . htmlspecialchars($item) . '</span> ';
736 }
737 }
738 $allowedTables .= '</div>';
739 }
740 // Allowed
741 $allowedList = '';
742 if (is_array($params['allowed']) && !empty($params['allowed'])) {
743 foreach ($params['allowed'] as $item) {
744 $allowedList .= '<span class="label label-success">' . strtoupper($item) . '</span> ';
745 }
746 }
747 // Disallowed
748 $disallowedList = '';
749 if (is_array($params['disallowed']) && !empty($params['disallowed'])) {
750 foreach ($params['disallowed'] as $item) {
751 $disallowedList .= '<span class="label label-danger">' . strtoupper($item) . '</span> ';
752 }
753 }
754 // Rightbox
755 $rightbox = ($params['rightbox'] ?: '');
756
757 // Hook: dbFileIcons_postProcess (requested by FAL-team for use with the "fal" extension)
758 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'])) {
759 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'] as $classRef) {
760 $hookObject = GeneralUtility::getUserObj($classRef);
761 if (!$hookObject instanceof DatabaseFileIconsHookInterface) {
762 throw new \UnexpectedValueException('$hookObject must implement interface ' . DatabaseFileIconsHookInterface::class, 1290167704);
763 }
764 $additionalParams = array(
765 'mode' => $mode,
766 'allowed' => $allowed,
767 'itemArray' => $itemArray,
768 'onFocus' => $onFocus,
769 'table' => $table,
770 'field' => $field,
771 'uid' => $uid,
772 'config' => $GLOBALS['TCA'][$table]['columns'][$field]
773 );
774 $hookObject->dbFileIcons_postProcess($params, $selector, $thumbnails, $icons, $rightbox, $fName, $uidList, $additionalParams, $this);
775 }
776 }
777
778 // Output
779 $str = '
780 ' . ($params['headers']['selector'] ? '<label>' . $params['headers']['selector'] . '</label>' : '') . '
781 <div class="form-wizards-wrap form-wizards-aside">
782 <div class="form-wizards-element">
783 ' . $selector . '
784 ' . (!$params['noList'] && !empty($allowedTables) ? $allowedTables : '') . '
785 ' . (!$params['noList'] && (!empty($allowedList) || !empty($disallowedList))
786 ? '<div class="help-block">' . $allowedList . $disallowedList . ' </div>'
787 : '') . '
788 </div>
789 ' . (!empty($icons['L']) ? '<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['L']) . '</div></div>' : '' ) . '
790 ' . (!empty($icons['R']) ? '<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['R']) . '</div></div>' : '' ) . '
791 </div>
792 ';
793 if ($rightbox) {
794 $str = '
795 <div class="form-multigroup-wrap t3js-formengine-field-group">
796 <div class="form-multigroup-item form-multigroup-element">' . $str . '</div>
797 <div class="form-multigroup-item form-multigroup-element">
798 ' . ($params['headers']['items'] ? '<label>' . $params['headers']['items'] . '</label>' : '') . '
799 ' . ($params['headers']['selectorbox'] ? '<div class="form-multigroup-item-wizard">' . $params['headers']['selectorbox'] . '</div>' : '') . '
800 ' . $rightbox . '
801 </div>
802 </div>
803 ';
804 }
805 $str .= $thumbnails;
806
807 // Creating the hidden field which contains the actual value as a comma list.
808 $str .= '<input type="hidden" name="' . $fName . '" value="' . htmlspecialchars(implode(',', $uidList)) . '" />';
809 return $str;
810 }
811
812 /**
813 * Returns array of elements from clipboard to insert into GROUP element box.
814 *
815 * @param string $allowed Allowed elements, Eg "pages,tt_content", "gif,jpg,jpeg,png
816 * @param string $mode Mode of relations: "db" or "file
817 * @return array Array of elements in values (keys are insignificant), if none found, empty array.
818 */
819 protected function getClipboardElements($allowed, $mode) {
820 if (!is_object($this->clipboard)) {
821 $this->clipboard = GeneralUtility::makeInstance(Clipboard::class);
822 $this->clipboard->initializeClipboard();
823 }
824
825 $output = array();
826 switch ($mode) {
827 case 'file_reference':
828
829 case 'file':
830 $elFromTable = $this->clipboard->elFromTable('_FILE');
831 $allowedExts = GeneralUtility::trimExplode(',', $allowed, TRUE);
832 // If there are a set of allowed extensions, filter the content:
833 if ($allowedExts) {
834 foreach ($elFromTable as $elValue) {
835 $pI = pathinfo($elValue);
836 $ext = strtolower($pI['extension']);
837 if (in_array($ext, $allowedExts)) {
838 $output[] = $elValue;
839 }
840 }
841 } else {
842 // If all is allowed, insert all: (This does NOT respect any disallowed extensions,
843 // but those will be filtered away by the backend TCEmain)
844 $output = $elFromTable;
845 }
846 break;
847 case 'db':
848 $allowedTables = GeneralUtility::trimExplode(',', $allowed, TRUE);
849 // All tables allowed for relation:
850 if (trim($allowedTables[0]) === '*') {
851 $output = $this->clipboard->elFromTable('');
852 } else {
853 // Only some tables, filter them:
854 foreach ($allowedTables as $tablename) {
855 $elFromTable = $this->clipboard->elFromTable($tablename);
856 $output = array_merge($output, $elFromTable);
857 }
858 }
859 $output = array_keys($output);
860 break;
861 }
862
863 return $output;
864 }
865
866 /**
867 * @return LanguageService
868 */
869 protected function getLanguageService() {
870 return $GLOBALS['LANG'];
871 }
872
873 /**
874 * @return DocumentTemplate
875 */
876 protected function getControllerDocumentTemplate() {
877 // $GLOBALS['SOBE'] might be any kind of PHP class (controller most of the times)
878 // These classes do not inherit from any common class, but they all seem to have a "doc" member
879 return $GLOBALS['SOBE']->doc;
880 }
881
882 }