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