[!!!][TASK] TCA: Remove wizard _HIDDENFIELD and hideParent
[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
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[' . GeneralUtility::quoteJSvalue($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 $otherWizards[] = '<a id="' . $md5ID . '" class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '"><span class="t3-icon fa fa-eyedropper"></span></a>';
349 break;
350 case 'slider':
351 $params = array();
352 $params['fieldConfig'] = $fieldConfig;
353 $params['field'] = $field;
354 $params['flexFormPath'] = $flexFormPath;
355 $params['md5ID'] = $md5ID;
356 $params['itemName'] = $itemName;
357 $params['fieldChangeFunc'] = $fieldChangeFunc;
358 $params['wConf'] = $wizardConfiguration;
359 $params['row'] = $row;
360
361 /** @var ValueSliderWizard $wizard */
362 $wizard = GeneralUtility::makeInstance(ValueSliderWizard::class);
363 $otherWizards[] = $wizard->renderWizard($params);
364 break;
365
366 case 'select':
367 $fieldValue = array('config' => $wizardConfiguration);
368 $TSconfig = FormEngineUtility::getTSconfigForTableRow($table, $row);
369 $TSconfig[$field] = $TSconfig[$field]['wizards.'][$wizardIdentifier . '.'];
370 $selItems = FormEngineUtility::addSelectOptionsToItemArray(FormEngineUtility::initItemArray($fieldValue), $fieldValue, $TSconfig, $field);
371 // Process items by a user function:
372 if (!empty($wizardConfiguration['itemsProcFunc'])) {
373 $funcConfig = !empty($wizardConfiguration['itemsProcFunc.']) ? $wizardConfiguration['itemsProcFunc.'] : array();
374 $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
375 $selItems = $dataPreprocessor->procItems($selItems, $funcConfig, $wizardConfiguration, $table, $row, $field);
376 }
377 $options = array();
378 $options[] = '<option>' . $iTitle . '</option>';
379 foreach ($selItems as $p) {
380 $options[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>';
381 }
382 if ($wizardConfiguration['mode'] == 'append') {
383 $assignValue = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
384 } elseif ($wizardConfiguration['mode'] == 'prepend') {
385 $assignValue = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value+=\'\'+this.options[this.selectedIndex].value';
386 } else {
387 $assignValue = 'document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value=this.options[this.selectedIndex].value';
388 }
389 $otherWizards[] =
390 '<select' .
391 ' id="' . str_replace('.', '', uniqid('tceforms-select-', TRUE)) . '"' .
392 ' class="form-control tceforms-select tceforms-wizardselect"' .
393 ' name="_WIZARD' . $fName . '"' .
394 ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"'.
395 '>' .
396 implode('', $options) .
397 '</select>';
398 break;
399 case 'suggest':
400 if (!empty($PA['fieldTSConfig']['suggest.']['default.']['hide'])) {
401 break;
402 }
403 /** @var SuggestWizard $suggestWizard */
404 $suggestWizard = GeneralUtility::makeInstance(SuggestWizard::class);
405 $otherWizards[] = $suggestWizard->renderSuggestSelector($PA['itemFormElName'], $table, $field, $row, $PA);
406 break;
407 }
408 }
409
410 // For each rendered wizard, put them together around the item.
411 if (!empty($buttonWizards) || !empty($otherWizards)) {
412 $innerContent = '';
413 if (!empty($buttonWizards)) {
414 $innerContent .= '<div class="btn-group' . ($wizConf['_VERTICAL'] ? ' btn-group-vertical' : '') . '">' . implode('', $buttonWizards) . '</div>';
415 }
416 $innerContent .= implode(' ', $otherWizards);
417
418 // Position
419 $classes = array('form-wizards-wrap');
420 if ($wizConf['_POSITION'] === 'left') {
421 $classes[] = 'form-wizards-aside';
422 $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>';
423 } elseif ($wizConf['_POSITION'] === 'top') {
424 $classes[] = 'form-wizards-top';
425 $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>';
426 } elseif ($wizConf['_POSITION'] === 'bottom') {
427 $classes[] = 'form-wizards-bottom';
428 $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>';
429 } else {
430 $classes[] = 'form-wizards-aside';
431 $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>';
432 }
433 $item = '
434 <div class="' . implode(' ', $classes) . '">
435 ' . $innerContent . '
436 </div>';
437 }
438
439 return $item;
440 }
441
442 /**
443 * Prints the selector box form-field for the db/file/select elements (multiple)
444 *
445 * @param string $fName Form element name
446 * @param string $mode Mode "db", "file" (internal_type for the "group" type) OR blank (then for the "select" type)
447 * @param string $allowed Commalist of "allowed
448 * @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.
449 * @param string $selector Alternative selector box.
450 * @param array $params An array of additional parameters, eg: "size", "info", "headers" (array with "selector" and "items"), "noBrowser", "thumbnails
451 * @param string $onFocus On focus attribute string
452 * @param string $table (optional) Table name processing for
453 * @param string $field (optional) Field of table name processing for
454 * @param string $uid (optional) uid of table record processing for
455 * @param array $config (optional) The TCA field config
456 * @return string The form fields for the selection.
457 * @throws \UnexpectedValueException
458 */
459 protected function dbFileIcons($fName, $mode, $allowed, $itemArray, $selector = '', $params = array(), $onFocus = '', $table = '', $field = '', $uid = '', $config = array()) {
460 $languageService = $this->getLanguageService();
461 $disabled = '';
462 if ($this->isGlobalReadonly() || $params['readOnly']) {
463 $disabled = ' disabled="disabled"';
464 }
465 // INIT
466 $uidList = array();
467 $opt = array();
468 $itemArrayC = 0;
469 // Creating <option> elements:
470 if (is_array($itemArray)) {
471 $itemArrayC = count($itemArray);
472 switch ($mode) {
473 case 'db':
474 foreach ($itemArray as $pp) {
475 $pRec = BackendUtility::getRecordWSOL($pp['table'], $pp['id']);
476 if (is_array($pRec)) {
477 $pTitle = BackendUtility::getRecordTitle($pp['table'], $pRec, FALSE, TRUE);
478 $pUid = $pp['table'] . '_' . $pp['id'];
479 $uidList[] = $pUid;
480 $title = htmlspecialchars($pTitle);
481 $opt[] = '<option value="' . htmlspecialchars($pUid) . '" title="' . $title . '">' . $title . '</option>';
482 }
483 }
484 break;
485 case 'file_reference':
486
487 case 'file':
488 foreach ($itemArray as $item) {
489 $itemParts = explode('|', $item);
490 $uidList[] = ($pUid = ($pTitle = $itemParts[0]));
491 $title = htmlspecialchars(rawurldecode($itemParts[1]));
492 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($itemParts[0])) . '" title="' . $title . '">' . $title . '</option>';
493 }
494 break;
495 case 'folder':
496 foreach ($itemArray as $pp) {
497 $pParts = explode('|', $pp);
498 $uidList[] = ($pUid = ($pTitle = $pParts[0]));
499 $title = htmlspecialchars(rawurldecode($pParts[0]));
500 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pParts[0])) . '" title="' . $title . '">' . $title . '</option>';
501 }
502 break;
503 default:
504 foreach ($itemArray as $pp) {
505 $pParts = explode('|', $pp, 2);
506 $uidList[] = ($pUid = $pParts[0]);
507 $pTitle = $pParts[1];
508 $title = htmlspecialchars(rawurldecode($pTitle));
509 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pUid)) . '" title="' . $title . '">' . $title . '</option>';
510 }
511 }
512 }
513 // Create selector box of the options
514 $sSize = $params['autoSizeMax']
515 ? MathUtility::forceIntegerInRange($itemArrayC + 1, MathUtility::forceIntegerInRange($params['size'], 1), $params['autoSizeMax'])
516 : $params['size'];
517 if (!$selector) {
518 $isMultiple = $params['maxitems'] != 1 && $params['size'] != 1;
519 $selector = '<select id="' . str_replace('.', '', uniqid('tceforms-multiselect-', TRUE)) . '" '
520 . ($params['noList'] ? 'style="display: none"' : 'size="' . $sSize . '" class="form-control tceforms-multiselect"')
521 . ($isMultiple ? ' multiple="multiple"' : '')
522 . ' name="' . $fName . '_list" ' . $this->getValidationDataAsDataAttribute($config) . $onFocus . $params['style'] . $disabled . '>' . implode('', $opt)
523 . '</select>';
524 }
525 $icons = array(
526 'L' => array(),
527 'R' => array()
528 );
529 $rOnClickInline = '';
530 if (!$params['readOnly'] && !$params['noList']) {
531 if (!$params['noBrowser']) {
532 // Check against inline uniqueness
533 /** @var InlineStackProcessor $inlineStackProcessor */
534 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
535 $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
536 $inlineParent = $inlineStackProcessor->getStructureLevel(-1);
537 $aOnClickInline = '';
538 if (is_array($inlineParent) && $inlineParent['uid']) {
539 if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
540 $objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']) . '-' . $table;
541 $aOnClickInline = $objectPrefix . '|inline.checkUniqueElement|inline.setUniqueElement';
542 $rOnClickInline = 'inline.revertUnique(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',null,' . GeneralUtility::quoteJSvalue($uid) . ');';
543 }
544 }
545 if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserType'])) {
546 $elementBrowserType = $config['appearance']['elementBrowserType'];
547 } else {
548 $elementBrowserType = $mode;
549 }
550 if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserAllowed'])) {
551 $elementBrowserAllowed = $config['appearance']['elementBrowserAllowed'];
552 } else {
553 $elementBrowserAllowed = $allowed;
554 }
555 $aOnClick = 'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($elementBrowserType) . ','
556 . GeneralUtility::quoteJSvalue(($fName . '|||' . $elementBrowserAllowed . '|' . $aOnClickInline)) . '); return false;';
557 $icons['R'][] = '
558 <a href="#"
559 onclick="' . htmlspecialchars($aOnClick) . '"
560 class="btn btn-default"
561 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.browse_' . ($mode == 'db' ? 'db' : 'file'))) . '">
562 ' . IconUtility::getSpriteIcon('actions-insert-record') . '
563 </a>';
564 }
565 if (!$params['dontShowMoveIcons']) {
566 if ($sSize >= 5) {
567 $icons['L'][] = '
568 <a href="#"
569 class="btn btn-default t3-btn-moveoption-top"
570 data-fieldname="' . $fName . '"
571 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_to_top')) . '">
572 ' . IconUtility::getSpriteIcon('actions-move-to-top') . '
573 </a>';
574
575 }
576 $icons['L'][] = '
577 <a href="#"
578 class="btn btn-default t3-btn-moveoption-up"
579 data-fieldname="' . $fName . '"
580 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_up')) . '">
581 ' . IconUtility::getSpriteIcon('actions-move-up') . '
582 </a>';
583 $icons['L'][] = '
584 <a href="#"
585 class="btn btn-default t3-btn-moveoption-down"
586 data-fieldname="' . $fName . '"
587 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_down')) . '">
588 ' . IconUtility::getSpriteIcon('actions-move-down') . '
589 </a>';
590 if ($sSize >= 5) {
591 $icons['L'][] = '
592 <a href="#"
593 class="btn btn-default t3-btn-moveoption-bottom"
594 data-fieldname="' . $fName . '"
595 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_to_bottom')) . '">
596 ' . IconUtility::getSpriteIcon('actions-move-to-bottom') . '
597 </a>';
598 }
599 }
600 $clipElements = $this->getClipboardElements($allowed, $mode);
601 if (!empty($clipElements)) {
602 $aOnClick = '';
603 foreach ($clipElements as $elValue) {
604 if ($mode == 'db') {
605 list($itemTable, $itemUid) = explode('|', $elValue);
606 $recordTitle = BackendUtility::getRecordTitle($itemTable, BackendUtility::getRecordWSOL($itemTable, $itemUid));
607 $itemTitle = GeneralUtility::quoteJSvalue($recordTitle);
608 $elValue = $itemTable . '_' . $itemUid;
609 } else {
610 // 'file', 'file_reference' and 'folder' mode
611 $itemTitle = 'unescape(' . GeneralUtility::quoteJSvalue(rawurlencode(basename($elValue))) . ')';
612 }
613 $aOnClick .= 'setFormValueFromBrowseWin(' . GeneralUtility::quoteJSvalue($fName) . ',unescape('
614 . GeneralUtility::quoteJSvalue(rawurlencode(str_replace('%20', ' ', $elValue))) . '),' . $itemTitle . ',' . $itemTitle . ');';
615 }
616 $aOnClick .= 'return false;';
617 $icons['R'][] = '
618 <a href="#"
619 onclick="' . htmlspecialchars($aOnClick) . '"
620 title="' . htmlspecialchars(sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.clipInsert_' . ($mode == 'db' ? 'db' : 'file')), count($clipElements))) . '">
621 ' . IconUtility::getSpriteIcon('actions-document-paste-into') . '
622 </a>';
623 }
624 }
625 if (!$params['readOnly'] && !$params['noDelete']) {
626 $icons['L'][] = '
627 <a href="#"
628 class="btn btn-default t3-btn-removeoption"
629 onClick="' . $rOnClickInline . '"
630 data-fieldname="' . $fName . '"
631 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.remove_selected')) . '">
632 ' . IconUtility::getSpriteIcon('actions-selection-delete') . '
633 </a>';
634 }
635
636 // Thumbnails
637 $imagesOnly = FALSE;
638 if ($params['thumbnails'] && $params['allowed']) {
639 // In case we have thumbnails, check if only images are allowed.
640 // In this case, render them below the field, instead of to the right
641 $allowedExtensionList = $params['allowed'];
642 $imageExtensionList = GeneralUtility::trimExplode(',', strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']), TRUE);
643 $imagesOnly = TRUE;
644 foreach ($allowedExtensionList as $allowedExtension) {
645 if (!ArrayUtility::inArray($imageExtensionList, $allowedExtension)) {
646 $imagesOnly = FALSE;
647 break;
648 }
649 }
650 }
651 $thumbnails = '';
652 if (is_array($params['thumbnails']) && !empty($params['thumbnails'])) {
653 if ($imagesOnly) {
654 $thumbnails .= '<ul class="list-inline">';
655 foreach ($params['thumbnails'] as $thumbnail) {
656 $thumbnails .= '<li><span class="thumbnail">' . $thumbnail['image'] . '</span></li>';
657 }
658 $thumbnails .= '</ul>';
659 } else {
660 $thumbnails .= '<div class="table-fit"><table class="table table-white"><tbody>';
661 foreach ($params['thumbnails'] as $thumbnail) {
662 $thumbnails .= '
663 <tr>
664 <td class="col-icon">
665 ' . ($config['internal_type'] === 'db'
666 ? $this->getControllerDocumentTemplate()->wrapClickMenuOnIcon($thumbnail['image'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view')
667 : $thumbnail['image']) . '
668 </td>
669 <td class="col-title">
670 ' . ($config['internal_type'] === 'db'
671 ? $this->getControllerDocumentTemplate()->wrapClickMenuOnIcon($thumbnail['name'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view')
672 : $thumbnail['name']) . '
673 ' . ($config['internal_type'] === 'db' ? ' <span class="text-muted">[' . $thumbnail['uid'] . ']</span>' : '') . '
674 </td>
675 </tr>
676 ';
677 }
678 $thumbnails .= '</tbody></table></div>';
679 }
680 }
681
682 // Allowed Tables
683 $allowedTables = '';
684 if (is_array($params['allowedTables']) && !empty($params['allowedTables'])) {
685 $allowedTables .= '<div class="help-block">';
686 foreach ($params['allowedTables'] as $key => $item) {
687 if (is_array($item)) {
688 if (empty($params['readOnly'])) {
689 $allowedTables .= '<a href="#" onClick="' . htmlspecialchars($item['onClick']) . '" class="btn btn-default">' . $item['icon'] . ' ' . htmlspecialchars($item['name']) . '</a> ';
690 } else {
691 $allowedTables .= '<span>' . htmlspecialchars($item['name']) . '</span> ';
692 }
693 } elseif($key === 'name') {
694 $allowedTables .= '<span>' . htmlspecialchars($item) . '</span> ';
695 }
696 }
697 $allowedTables .= '</div>';
698 }
699 // Allowed
700 $allowedList = '';
701 if (is_array($params['allowed']) && !empty($params['allowed'])) {
702 foreach ($params['allowed'] as $item) {
703 $allowedList .= '<span class="label label-success">' . strtoupper($item) . '</span> ';
704 }
705 }
706 // Disallowed
707 $disallowedList = '';
708 if (is_array($params['disallowed']) && !empty($params['disallowed'])) {
709 foreach ($params['disallowed'] as $item) {
710 $disallowedList .= '<span class="label label-danger">' . strtoupper($item) . '</span> ';
711 }
712 }
713 // Rightbox
714 $rightbox = ($params['rightbox'] ?: '');
715
716 // Hook: dbFileIcons_postProcess (requested by FAL-team for use with the "fal" extension)
717 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'])) {
718 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'] as $classRef) {
719 $hookObject = GeneralUtility::getUserObj($classRef);
720 if (!$hookObject instanceof DatabaseFileIconsHookInterface) {
721 throw new \UnexpectedValueException('$hookObject must implement interface ' . DatabaseFileIconsHookInterface::class, 1290167704);
722 }
723 $additionalParams = array(
724 'mode' => $mode,
725 'allowed' => $allowed,
726 'itemArray' => $itemArray,
727 'onFocus' => $onFocus,
728 'table' => $table,
729 'field' => $field,
730 'uid' => $uid,
731 'config' => $GLOBALS['TCA'][$table]['columns'][$field]
732 );
733 $hookObject->dbFileIcons_postProcess($params, $selector, $thumbnails, $icons, $rightbox, $fName, $uidList, $additionalParams, $this);
734 }
735 }
736
737 // Output
738 $str = '
739 ' . ($params['headers']['selector'] ? '<label>' . $params['headers']['selector'] . '</label>' : '') . '
740 <div class="form-wizards-wrap form-wizards-aside">
741 <div class="form-wizards-element">
742 ' . $selector . '
743 ' . (!$params['noList'] && !empty($allowedTables) ? $allowedTables : '') . '
744 ' . (!$params['noList'] && (!empty($allowedList) || !empty($disallowedList))
745 ? '<div class="help-block">' . $allowedList . $disallowedList . ' </div>'
746 : '') . '
747 </div>
748 ' . (!empty($icons['L']) ? '<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['L']) . '</div></div>' : '' ) . '
749 ' . (!empty($icons['R']) ? '<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['R']) . '</div></div>' : '' ) . '
750 </div>
751 ';
752 if ($rightbox) {
753 $str = '
754 <div class="form-multigroup-wrap t3js-formengine-field-group">
755 <div class="form-multigroup-item form-multigroup-element">' . $str . '</div>
756 <div class="form-multigroup-item form-multigroup-element">
757 ' . ($params['headers']['items'] ? '<label>' . $params['headers']['items'] . '</label>' : '') . '
758 ' . ($params['headers']['selectorbox'] ? '<div class="form-multigroup-item-wizard">' . $params['headers']['selectorbox'] . '</div>' : '') . '
759 ' . $rightbox . '
760 </div>
761 </div>
762 ';
763 }
764 $str .= $thumbnails;
765
766 // Creating the hidden field which contains the actual value as a comma list.
767 $str .= '<input type="hidden" name="' . $fName . '" value="' . htmlspecialchars(implode(',', $uidList)) . '" />';
768 return $str;
769 }
770
771 /**
772 * Returns array of elements from clipboard to insert into GROUP element box.
773 *
774 * @param string $allowed Allowed elements, Eg "pages,tt_content", "gif,jpg,jpeg,png
775 * @param string $mode Mode of relations: "db" or "file
776 * @return array Array of elements in values (keys are insignificant), if none found, empty array.
777 */
778 protected function getClipboardElements($allowed, $mode) {
779 if (!is_object($this->clipboard)) {
780 $this->clipboard = GeneralUtility::makeInstance(Clipboard::class);
781 $this->clipboard->initializeClipboard();
782 }
783
784 $output = array();
785 switch ($mode) {
786 case 'file_reference':
787
788 case 'file':
789 $elFromTable = $this->clipboard->elFromTable('_FILE');
790 $allowedExts = GeneralUtility::trimExplode(',', $allowed, TRUE);
791 // If there are a set of allowed extensions, filter the content:
792 if ($allowedExts) {
793 foreach ($elFromTable as $elValue) {
794 $pI = pathinfo($elValue);
795 $ext = strtolower($pI['extension']);
796 if (in_array($ext, $allowedExts)) {
797 $output[] = $elValue;
798 }
799 }
800 } else {
801 // If all is allowed, insert all: (This does NOT respect any disallowed extensions,
802 // but those will be filtered away by the backend TCEmain)
803 $output = $elFromTable;
804 }
805 break;
806 case 'db':
807 $allowedTables = GeneralUtility::trimExplode(',', $allowed, TRUE);
808 // All tables allowed for relation:
809 if (trim($allowedTables[0]) === '*') {
810 $output = $this->clipboard->elFromTable('');
811 } else {
812 // Only some tables, filter them:
813 foreach ($allowedTables as $tablename) {
814 $elFromTable = $this->clipboard->elFromTable($tablename);
815 $output = array_merge($output, $elFromTable);
816 }
817 }
818 $output = array_keys($output);
819 break;
820 }
821
822 return $output;
823 }
824
825 /**
826 * @return LanguageService
827 */
828 protected function getLanguageService() {
829 return $GLOBALS['LANG'];
830 }
831
832 /**
833 * @return DocumentTemplate
834 */
835 protected function getControllerDocumentTemplate() {
836 // $GLOBALS['SOBE'] might be any kind of PHP class (controller most of the times)
837 // These classes do not inherit from any common class, but they all seem to have a "doc" member
838 return $GLOBALS['SOBE']->doc;
839 }
840
841 }