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