2 namespace TYPO3\CMS\Backend\Form\Element
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Backend\Clipboard\Clipboard
;
18 use TYPO3\CMS\Backend\Form\AbstractNode
;
19 use TYPO3\CMS\Backend\Form\DatabaseFileIconsHookInterface
;
20 use TYPO3\CMS\Backend\Form\FormDataCompiler
;
21 use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly
;
22 use TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems
;
23 use TYPO3\CMS\Backend\Form\InlineStackProcessor
;
24 use TYPO3\CMS\Backend\Form\NodeFactory
;
25 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility
;
26 use TYPO3\CMS\Backend\Form\Wizard\SuggestWizard
;
27 use TYPO3\CMS\Backend\Form\Wizard\ValueSliderWizard
;
28 use TYPO3\CMS\Backend\Utility\BackendUtility
;
29 use TYPO3\CMS\Core\Imaging\Icon
;
30 use TYPO3\CMS\Core\Imaging\IconFactory
;
31 use TYPO3\CMS\Core\Utility\ArrayUtility
;
32 use TYPO3\CMS\Core\Utility\GeneralUtility
;
33 use TYPO3\CMS\Core\Utility\MathUtility
;
34 use TYPO3\CMS\Core\Utility\StringUtility
;
35 use TYPO3\CMS\Lang\LanguageService
;
38 * Base class for form elements of FormEngine. Contains several helper methods used by single elements.
40 abstract class AbstractFormElement
extends AbstractNode
43 * Default width value for a couple of elements like text
47 protected $defaultInputWidth = 30;
50 * Minimum width value for a couple of elements like text
54 protected $minimumInputWidth = 10;
57 * Maximum width value for a couple of elements like text
61 protected $maxInputWidth = 50;
64 * @var \TYPO3\CMS\Backend\Clipboard\Clipboard|NULL
66 protected $clipboard = null
;
69 * Container objects give $nodeFactory down to other containers.
71 * @param NodeFactory $nodeFactory
74 public function __construct(NodeFactory
$nodeFactory, array $data)
76 parent
::__construct($nodeFactory, $data);
77 $this->iconFactory
= GeneralUtility
::makeInstance(IconFactory
::class);
78 // @todo: this must vanish as soon as elements are clean
79 $this->nodeFactory
= $nodeFactory;
83 * @return bool TRUE if wizards are disabled on a global level
85 protected function isWizardsDisabled()
87 return !empty($this->data
['disabledWizards']);
91 * Returns the max width in pixels for an elements like input and text
93 * @param int $size The abstract size value (1-48)
94 * @return int Maximum width in pixels
96 protected function formMaxWidth($size = 48)
98 $compensationForLargeDocuments = 1.33;
99 $compensationForFormFields = 12;
101 $size = round($size * $compensationForLargeDocuments);
102 return ceil($size * $compensationForFormFields);
108 protected $iconFactory;
111 * Rendering wizards for form fields.
113 * @param array $itemKinds Array with the real item in the first value
114 * @param array $wizConf The "wizards" key from the config array for the field (from TCA)
115 * @param string $table Table name
116 * @param array $row The record array
117 * @param string $field The field name
118 * @param array $PA Additional configuration array.
119 * @param string $itemName The field name
120 * @param array $specConf Special configuration if available.
121 * @param bool $RTE Whether the RTE could have been loaded.
123 * @return string The new item value.
124 * @throws \InvalidArgumentException
126 protected function renderWizards($itemKinds, $wizConf, $table, $row, $field, $PA, $itemName, $specConf, $RTE = false
)
128 // Return not changed main item directly if wizards are disabled
129 if (!is_array($wizConf) ||
$this->isWizardsDisabled()) {
130 return $itemKinds[0];
133 $languageService = $this->getLanguageService();
135 $fieldChangeFunc = $PA['fieldChangeFunc'];
136 $item = $itemKinds[0];
137 $md5ID = 'ID' . GeneralUtility
::shortmd5($itemName);
138 $prefixOfFormElName = 'data[' . $table . '][' . $row['uid'] . '][' . $field . ']';
140 if (GeneralUtility
::isFirstPartOfStr($PA['itemFormElName'], $prefixOfFormElName)) {
141 $flexFormPath = str_replace('][', '/', substr($PA['itemFormElName'], strlen($prefixOfFormElName) +
1, -1));
144 // Add a suffix-value if the item is a selector box with renderType "selectSingleBox":
145 if ($PA['fieldConf']['config']['type'] === 'select' && (int)$PA['fieldConf']['config']['maxitems'] > 1 && $PA['fieldConf']['config']['renderType'] === 'selectSingleBox') {
149 // Contains wizard identifiers enabled for this record type, see "special configuration" docs
150 $wizardsEnabledByType = $specConf['wizards']['parameters'];
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/script.php in your TCA. ', 1437750231);
160 // If an identifier starts with "_", this is a configuration option like _POSITION and not a wizard
161 if ($wizardIdentifier[0] === '_') {
165 // Sanitize wizard type
166 $wizardConfiguration['type'] = (string)$wizardConfiguration['type'];
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
;
172 isset($wizardConfiguration['enableByTypeConfig'])
173 && (bool
)$wizardConfiguration['enableByTypeConfig']
174 && (!is_array($wizardsEnabledByType) ||
!in_array($wizardIdentifier, $wizardsEnabledByType))
176 $wizardIsEnabled = false
;
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
;
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
;
186 // Wizard types script, colorbox and popup must contain a module name configuration
187 if (!isset($wizardConfiguration['module']['name']) && in_array($wizardConfiguration['type'], ['script', 'colorbox', 'popup'], true
)) {
188 $wizardIsEnabled = false
;
191 if (!$wizardIsEnabled) {
196 $iTitle = htmlspecialchars($languageService->sL($wizardConfiguration['title']));
197 if (isset($wizardConfiguration['icon'])) {
198 $icon = FormEngineUtility
::getIconHtml($wizardConfiguration['icon'], $iTitle, $iTitle);
203 switch ($wizardConfiguration['type']) {
206 $params['params'] = $wizardConfiguration['params'];
207 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
208 $params['table'] = $table;
209 $params['uid'] = $row['uid'];
210 $params['pid'] = $row['pid'];
211 $params['field'] = $field;
212 $params['flexFormPath'] = $flexFormPath;
213 $params['md5ID'] = $md5ID;
214 $params['returnUrl'] = $this->data
['returnUrl'];
216 $params['formName'] = 'editform';
217 $params['itemName'] = $itemName;
218 $params['hmac'] = GeneralUtility
::hmac($params['formName'] . $params['itemName'], 'wizard_js');
219 $params['fieldChangeFunc'] = $fieldChangeFunc;
220 $params['fieldChangeFuncHash'] = GeneralUtility
::hmac(serialize($fieldChangeFunc));
222 $params['item'] = &$item;
223 $params['icon'] = $icon;
224 $params['iTitle'] = $iTitle;
225 $params['wConf'] = $wizardConfiguration;
226 $params['row'] = $row;
227 $otherWizards[] = GeneralUtility
::callUserFunction($wizardConfiguration['userFunc'], $params, $this);
232 $params['params'] = $wizardConfiguration['params'];
233 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
234 $params['table'] = $table;
235 $params['uid'] = $row['uid'];
236 $params['pid'] = $row['pid'];
237 $params['field'] = $field;
238 $params['flexFormPath'] = $flexFormPath;
239 $params['md5ID'] = $md5ID;
240 $params['returnUrl'] = $this->data
['returnUrl'];
242 // Resolving script filename and setting URL.
244 if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) {
245 $urlParameters = $wizardConfiguration['module']['urlParameters'];
247 $wScript = BackendUtility
::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, '');
248 $url = $wScript . (strstr($wScript, '?') ?
'' : '?') . GeneralUtility
::implodeArrayForUrl('', ['P' => $params]);
250 '<a class="btn btn-default" href="' . htmlspecialchars($url) . '" onclick="this.blur(); return !TBE_EDITOR.isFormChanged();">'
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->data
['returnUrl'];
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));
273 // Resolving script filename and setting URL.
275 if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) {
276 $urlParameters = $wizardConfiguration['module']['urlParameters'];
278 $wScript = BackendUtility
::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, '');
279 $url = $wScript . (strstr($wScript, '?') ?
'' : '?') . GeneralUtility
::implodeArrayForUrl('', ['P' => $params]);
281 $onlyIfSelectedJS = '';
282 if (isset($wizardConfiguration['popup_onlyOpenIfSelected']) && $wizardConfiguration['popup_onlyOpenIfSelected']) {
283 $notSelectedText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.noSelItemForEdit');
285 'if (!TBE_EDITOR.curSelected(' . GeneralUtility
::quoteJSvalue($itemName) . ')){' .
286 'alert(' . GeneralUtility
::quoteJSvalue($notSelectedText) . ');' .
293 'vHWin=window.open(' . GeneralUtility
::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' .
294 'document.editform[' . GeneralUtility
::quoteJSvalue($itemName) . '].value,300' .
296 '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility
::quoteJSvalue($itemName) . '),' .
297 GeneralUtility
::quoteJSvalue('popUp' . $md5ID) . ',' .
298 GeneralUtility
::quoteJSvalue($wizardConfiguration['JSopenParams']) .
304 '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '">' .
311 $params['params'] = $wizardConfiguration['params'];
312 $params['exampleImg'] = $wizardConfiguration['exampleImg'];
313 $params['table'] = $table;
314 $params['uid'] = $row['uid'];
315 $params['pid'] = $row['pid'];
316 $params['field'] = $field;
317 $params['flexFormPath'] = $flexFormPath;
318 $params['md5ID'] = $md5ID;
319 $params['returnUrl'] = $this->data
['returnUrl'];
321 $params['formName'] = 'editform';
322 $params['itemName'] = $itemName;
323 $params['hmac'] = GeneralUtility
::hmac($params['formName'] . $params['itemName'], 'wizard_js');
324 $params['fieldChangeFunc'] = $fieldChangeFunc;
325 $params['fieldChangeFuncHash'] = GeneralUtility
::hmac(serialize($fieldChangeFunc));
327 // Resolving script filename and setting URL.
329 if (isset($wizardConfiguration['module']['urlParameters']) && is_array($wizardConfiguration['module']['urlParameters'])) {
330 $urlParameters = $wizardConfiguration['module']['urlParameters'];
332 $wScript = BackendUtility
::getModuleUrl($wizardConfiguration['module']['name'], $urlParameters, '');
333 $url = $wScript . (strstr($wScript, '?') ?
'' : '?') . GeneralUtility
::implodeArrayForUrl('', ['P' => $params]);
337 'vHWin=window.open(' . GeneralUtility
::quoteJSvalue($url) . '+\'&P[currentValue]=\'+TBE_EDITOR.rawurlencode(' .
338 'document.editform[' . GeneralUtility
::quoteJSvalue($itemName) . '].value,300' .
340 '+\'&P[currentSelectedValues]=\'+TBE_EDITOR.curSelected(' . GeneralUtility
::quoteJSvalue($itemName) . '),' .
341 GeneralUtility
::quoteJSvalue('popUp' . $md5ID) . ',' .
342 GeneralUtility
::quoteJSvalue($wizardConfiguration['JSopenParams']) .
347 $otherWizards[] = '<a id="' . $md5ID . '" class="btn btn-default" href="#" onclick="' . htmlspecialchars($aOnClick) . '"><span class="t3-icon fa fa-eyedropper"></span></a>';
351 $params['fieldConfig'] = $PA['fieldConf']['config'];
352 $params['field'] = $field;
353 $params['table'] = $table;
354 $params['flexFormPath'] = $flexFormPath;
355 $params['md5ID'] = $md5ID;
356 $params['itemName'] = $itemName;
357 $params['wConf'] = $wizardConfiguration;
358 $params['row'] = $row;
360 /** @var ValueSliderWizard $wizard */
361 $wizard = GeneralUtility
::makeInstance(ValueSliderWizard
::class);
362 $otherWizards[] = $wizard->renderWizard($params);
366 // The select wizard is a select drop down added to the main element. It provides all the functionality
367 // that select items can do for us, so we process this element via data processing.
368 // @todo: This should be embedded in an own provider called in the main data group to not handle this on the fly here
370 // Select wizard page TS can be set in TCEFORM."table"."field".wizards."wizardName"
372 if (isset($this->data
['pageTsConfig']['TCEFORM.'][$table . '.'][$field . '.']['wizards.'][$wizardIdentifier . '.'])
373 && is_array($this->data
['pageTsConfig']['TCEFORM.'][$table . '.'][$field . '.']['wizards.'][$wizardIdentifier . '.'])
375 $pageTsConfig['TCEFORM.']['dummySelectWizard.'][$wizardIdentifier . '.'] = $this->data
['pageTsConfig']['TCEFORM.'][$table . '.'][$field . '.']['wizards.'][$wizardIdentifier . '.'];
377 $selectWizardDataInput = [
378 'tableName' => 'dummySelectWizard',
380 'pageTsConfig' => $pageTsConfig,
384 $wizardIdentifier => [
386 'renderType' => 'selectSingle',
387 'config' => $wizardConfiguration,
392 /** @var OnTheFly $formDataGroup */
393 $formDataGroup = GeneralUtility
::makeInstance(OnTheFly
::class);
394 $formDataGroup->setProviderList([ TcaSelectItems
::class ]);
395 /** @var FormDataCompiler $formDataCompiler */
396 $formDataCompiler = GeneralUtility
::makeInstance(FormDataCompiler
::class, $formDataGroup);
397 $compilerResult = $formDataCompiler->compile($selectWizardDataInput);
398 $selectWizardItems = $compilerResult['processedTca']['columns'][$wizardIdentifier]['config']['items'];
401 $options[] = '<option>' . $iTitle . '</option>';
402 foreach ($selectWizardItems as $selectWizardItem) {
403 $options[] = '<option value="' . htmlspecialchars($selectWizardItem[1]) . '">' . htmlspecialchars($selectWizardItem[0]) . '</option>';
405 if ($wizardConfiguration['mode'] == 'append') {
406 $assignValue = 'document.querySelectorAll(' . GeneralUtility
::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0].value+=\'\'+this.options[this.selectedIndex].value';
407 } elseif ($wizardConfiguration['mode'] == 'prepend') {
408 $assignValue = 'document.querySelectorAll(' . GeneralUtility
::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0].value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility
::quoteJSvalue($itemName) . '].value';
410 $assignValue = 'document.querySelectorAll(' . GeneralUtility
::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0].value=this.options[this.selectedIndex].value';
414 ' id="' . StringUtility
::getUniqueId('tceforms-select-') . '"' .
415 ' class="form-control tceforms-select tceforms-wizardselect"' .
416 ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"' .
418 implode('', $options) .
422 if (!empty($PA['fieldTSConfig']['suggest.']['default.']['hide'])) {
425 /** @var SuggestWizard $suggestWizard */
426 $suggestWizard = GeneralUtility
::makeInstance(SuggestWizard
::class);
427 $otherWizards[] = $suggestWizard->renderSuggestSelector($PA['itemFormElName'], $table, $field, $row, $PA);
432 // For each rendered wizard, put them together around the item.
433 if (!empty($buttonWizards) ||
!empty($otherWizards)) {
435 if (!empty($buttonWizards)) {
436 $innerContent .= '<div class="btn-group' . ($wizConf['_VERTICAL'] ?
' btn-group-vertical' : '') . '">' . implode('', $buttonWizards) . '</div>';
438 $innerContent .= implode(' ', $otherWizards);
441 $classes = ['form-wizards-wrap'];
442 if ($wizConf['_POSITION'] === 'left') {
443 $classes[] = 'form-wizards-aside';
444 $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>';
445 } elseif ($wizConf['_POSITION'] === 'top') {
446 $classes[] = 'form-wizards-top';
447 $innerContent = '<div class="form-wizards-items">' . $innerContent . '</div><div class="form-wizards-element">' . $item . '</div>';
448 } elseif ($wizConf['_POSITION'] === 'bottom') {
449 $classes[] = 'form-wizards-bottom';
450 $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>';
452 $classes[] = 'form-wizards-aside';
453 $innerContent = '<div class="form-wizards-element">' . $item . '</div><div class="form-wizards-items">' . $innerContent . '</div>';
456 <div class="' . implode(' ', $classes) . '">
457 ' . $innerContent . '
465 * Prints the selector box form-field for the db/file/select elements (multiple)
467 * @param string $fName Form element name
468 * @param string $mode Mode "db", "file" (internal_type for the "group" type) OR blank (then for the "select" type)
469 * @param string $allowed Commalist of "allowed
470 * @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.
471 * @param string $selector Alternative selector box.
472 * @param array $params An array of additional parameters, eg: "size", "info", "headers" (array with "selector" and "items"), "noBrowser", "thumbnails
473 * @param string $onFocus On focus attribute string
474 * @param string $table (optional) Table name processing for
475 * @param string $field (optional) Field of table name processing for
476 * @param string $uid (optional) uid of table record processing for
477 * @param array $config (optional) The TCA field config
478 * @return string The form fields for the selection.
479 * @throws \UnexpectedValueException
480 * @todo: Hack this mess into pieces and inline to group / select element depending on what they need
482 protected function dbFileIcons($fName, $mode, $allowed, $itemArray, $selector = '', $params = [], $onFocus = '', $table = '', $field = '', $uid = '', $config = [])
484 $languageService = $this->getLanguageService();
486 if ($params['readOnly']) {
487 $disabled = ' disabled="disabled"';
493 // Creating <option> elements:
494 if (is_array($itemArray)) {
495 $itemArrayC = count($itemArray);
498 foreach ($itemArray as $pp) {
499 $pRec = BackendUtility
::getRecordWSOL($pp['table'], $pp['id']);
500 if (is_array($pRec)) {
501 $pTitle = BackendUtility
::getRecordTitle($pp['table'], $pRec, false
, true
);
502 $pUid = $pp['table'] . '_' . $pp['id'];
504 $title = htmlspecialchars($pTitle);
505 $opt[] = '<option value="' . htmlspecialchars($pUid) . '" title="' . $title . '">' . $title . '</option>';
509 case 'file_reference':
512 foreach ($itemArray as $item) {
513 $itemParts = explode('|', $item);
514 $uidList[] = ($pUid = ($pTitle = $itemParts[0]));
515 $title = htmlspecialchars(rawurldecode($itemParts[1]));
516 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($itemParts[0])) . '" title="' . $title . '">' . $title . '</option>';
520 foreach ($itemArray as $pp) {
521 $pParts = explode('|', $pp);
522 $uidList[] = ($pUid = ($pTitle = $pParts[0]));
523 $title = htmlspecialchars(rawurldecode($pParts[0]));
524 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pParts[0])) . '" title="' . $title . '">' . $title . '</option>';
528 foreach ($itemArray as $pp) {
529 $pParts = explode('|', $pp, 2);
530 $uidList[] = ($pUid = $pParts[0]);
531 $pTitle = $pParts[1];
532 $title = htmlspecialchars(rawurldecode($pTitle));
533 $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pUid)) . '" title="' . $title . '">' . $title . '</option>';
537 // Create selector box of the options
538 $sSize = $params['autoSizeMax']
539 ? MathUtility
::forceIntegerInRange($itemArrayC +
1, MathUtility
::forceIntegerInRange($params['size'], 1), $params['autoSizeMax'])
542 $maxItems = isset($params['maxitems']) ?
(int)$params['maxitems'] : 0;
543 $size = isset($params['size']) ?
(int)$params['size'] : 0;
544 $classes = ['form-control', 'tceforms-multiselect'];
545 if ($maxItems === 1) {
546 $classes[] = 'form-select-no-siblings';
548 $isMultiple = $maxItems !== 1 && $size !== 1;
549 $selector = '<select id="' . StringUtility
::getUniqueId('tceforms-multiselect-') . '" '
550 . ($params['noList'] ?
'style="display: none"' : 'size="' . $sSize . '" class="' . implode(' ', $classes) . '"')
551 . ($isMultiple ?
' multiple="multiple"' : '')
552 . ' data-formengine-input-name="' . htmlspecialchars($fName) . '" ' . $this->getValidationDataAsDataAttribute($config) . $onFocus . $params['style'] . $disabled . '>' . implode('', $opt)
559 $rOnClickInline = '';
560 if (!$params['readOnly'] && !$params['noList']) {
561 if (!$params['noBrowser']) {
562 // Check against inline uniqueness
563 /** @var InlineStackProcessor $inlineStackProcessor */
564 $inlineStackProcessor = GeneralUtility
::makeInstance(InlineStackProcessor
::class);
565 $inlineStackProcessor->initializeByGivenStructure($this->data
['inlineStructure']);
566 $aOnClickInline = '';
567 if ($this->data
['isInlineChild'] && $this->data
['inlineParentUid']) {
568 if ($this->data
['inlineParentConfig']['foreign_table'] === $table
569 && $this->data
['inlineParentConfig']['foreign_unique'] === $field
571 $objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data
['inlineFirstPid']) . '-' . $table;
572 $aOnClickInline = $objectPrefix . '|inline.checkUniqueElement|inline.setUniqueElement';
573 $rOnClickInline = 'inline.revertUnique(' . GeneralUtility
::quoteJSvalue($objectPrefix) . ',null,' . GeneralUtility
::quoteJSvalue($uid) . ');';
576 if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserType'])) {
577 $elementBrowserType = $config['appearance']['elementBrowserType'];
579 $elementBrowserType = $mode;
581 if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserAllowed'])) {
582 $elementBrowserAllowed = $config['appearance']['elementBrowserAllowed'];
584 $elementBrowserAllowed = $allowed;
586 $aOnClick = 'setFormValueOpenBrowser(' . GeneralUtility
::quoteJSvalue($elementBrowserType) . ','
587 . GeneralUtility
::quoteJSvalue(($fName . '|||' . $elementBrowserAllowed . '|' . $aOnClickInline)) . '); return false;';
590 onclick="' . htmlspecialchars($aOnClick) . '"
591 class="btn btn-default"
592 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.browse_' . ($mode == 'db' ?
'db' : 'file'))) . '">
593 ' . $this->iconFactory
->getIcon('actions-insert-record', Icon
::SIZE_SMALL
)->render() . '
596 if (!$params['dontShowMoveIcons']) {
600 class="btn btn-default t3js-btn-moveoption-top"
601 data-fieldname="' . $fName . '"
602 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_to_top')) . '">
603 ' . $this->iconFactory
->getIcon('actions-move-to-top', Icon
::SIZE_SMALL
)->render() . '
608 class="btn btn-default t3js-btn-moveoption-up"
609 data-fieldname="' . $fName . '"
610 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_up')) . '">
611 ' . $this->iconFactory
->getIcon('actions-move-up', Icon
::SIZE_SMALL
)->render() . '
615 class="btn btn-default t3js-btn-moveoption-down"
616 data-fieldname="' . $fName . '"
617 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_down')) . '">
618 ' . $this->iconFactory
->getIcon('actions-move-down', Icon
::SIZE_SMALL
)->render() . '
623 class="btn btn-default t3js-btn-moveoption-bottom"
624 data-fieldname="' . $fName . '"
625 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move_to_bottom')) . '">
626 ' . $this->iconFactory
->getIcon('actions-move-to-bottom', Icon
::SIZE_SMALL
)->render() . '
630 $clipElements = $this->getClipboardElements($allowed, $mode);
631 if (!empty($clipElements)) {
633 foreach ($clipElements as $elValue) {
635 list($itemTable, $itemUid) = explode('|', $elValue);
636 $recordTitle = BackendUtility
::getRecordTitle($itemTable, BackendUtility
::getRecordWSOL($itemTable, $itemUid));
637 $itemTitle = GeneralUtility
::quoteJSvalue($recordTitle);
638 $elValue = $itemTable . '_' . $itemUid;
640 // 'file', 'file_reference' and 'folder' mode
641 $itemTitle = 'unescape(' . GeneralUtility
::quoteJSvalue(rawurlencode(basename($elValue))) . ')';
643 $aOnClick .= 'setFormValueFromBrowseWin(' . GeneralUtility
::quoteJSvalue($fName) . ',unescape('
644 . GeneralUtility
::quoteJSvalue(rawurlencode(str_replace('%20', ' ', $elValue))) . '),' . $itemTitle . ',' . $itemTitle . ');';
646 $aOnClick .= 'return false;';
649 class="btn btn-default"
650 onclick="' . htmlspecialchars($aOnClick) . '"
651 title="' . htmlspecialchars(sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.clipInsert_' . ($mode == 'db' ?
'db' : 'file')), count($clipElements))) . '">
652 ' . $this->iconFactory
->getIcon('actions-document-paste-into', Icon
::SIZE_SMALL
)->render() . '
656 if (!$params['readOnly'] && !$params['noDelete']) {
659 class="btn btn-default t3js-btn-removeoption"
660 onClick="' . $rOnClickInline . '"
661 data-fieldname="' . $fName . '"
662 title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.remove_selected')) . '">
663 ' . $this->iconFactory
->getIcon('actions-selection-delete', Icon
::SIZE_SMALL
)->render() . '
669 if ($params['thumbnails'] && $params['allowed']) {
670 // In case we have thumbnails, check if only images are allowed.
671 // In this case, render them below the field, instead of to the right
672 $allowedExtensionList = $params['allowed'];
673 $imageExtensionList = GeneralUtility
::trimExplode(',', strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']), true
);
675 foreach ($allowedExtensionList as $allowedExtension) {
676 if (!ArrayUtility
::inArray($imageExtensionList, $allowedExtension)) {
683 if (is_array($params['thumbnails']) && !empty($params['thumbnails'])) {
685 $thumbnails .= '<ul class="list-inline">';
686 foreach ($params['thumbnails'] as $thumbnail) {
687 $thumbnails .= '<li><span class="thumbnail">' . $thumbnail['image'] . '</span></li>';
689 $thumbnails .= '</ul>';
691 $thumbnails .= '<div class="table-fit"><table class="table table-white"><tbody>';
692 foreach ($params['thumbnails'] as $thumbnail) {
695 <td class="col-icon">
696 ' . ($config['internal_type'] === 'db'
697 ? BackendUtility
::wrapClickMenuOnIcon($thumbnail['image'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view')
698 : $thumbnail['image']) . '
700 <td class="col-title">
701 ' . ($config['internal_type'] === 'db'
702 ? BackendUtility
::wrapClickMenuOnIcon($thumbnail['name'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view')
703 : $thumbnail['name']) . '
704 ' . ($config['internal_type'] === 'db' ?
' <span class="text-muted">[' . $thumbnail['uid'] . ']</span>' : '') . '
709 $thumbnails .= '</tbody></table></div>';
715 if (is_array($params['allowedTables']) && !empty($params['allowedTables'])) {
716 $allowedTables .= '<div class="help-block">';
717 foreach ($params['allowedTables'] as $key => $item) {
718 if (is_array($item)) {
719 if (empty($params['readOnly'])) {
720 $allowedTables .= '<a href="#" onClick="' . htmlspecialchars($item['onClick']) . '" class="btn btn-default">' . $item['icon'] . ' ' . htmlspecialchars($item['name']) . '</a> ';
722 $allowedTables .= '<span>' . htmlspecialchars($item['name']) . '</span> ';
724 } elseif ($key === 'name') {
725 $allowedTables .= '<span>' . htmlspecialchars($item) . '</span> ';
728 $allowedTables .= '</div>';
732 if (is_array($params['allowed']) && !empty($params['allowed'])) {
733 foreach ($params['allowed'] as $item) {
734 $allowedList .= '<span class="label label-success">' . strtoupper($item) . '</span> ';
738 $disallowedList = '';
739 if (is_array($params['disallowed']) && !empty($params['disallowed'])) {
740 foreach ($params['disallowed'] as $item) {
741 $disallowedList .= '<span class="label label-danger">' . strtoupper($item) . '</span> ';
745 $rightbox = ($params['rightbox'] ?
: '');
747 // Hook: dbFileIcons_postProcess (requested by FAL-team for use with the "fal" extension)
748 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'])) {
749 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'] as $classRef) {
750 $hookObject = GeneralUtility
::getUserObj($classRef);
751 if (!$hookObject instanceof DatabaseFileIconsHookInterface
) {
752 throw new \
UnexpectedValueException($classRef . ' must implement interface ' . DatabaseFileIconsHookInterface
::class, 1290167704);
754 $additionalParams = [
756 'allowed' => $allowed,
757 'itemArray' => $itemArray,
758 'onFocus' => $onFocus,
762 'config' => $GLOBALS['TCA'][$table]['columns'][$field]
764 $hookObject->dbFileIcons_postProcess($params, $selector, $thumbnails, $icons, $rightbox, $fName, $uidList, $additionalParams, $this);
770 ' . ($params['headers']['selector'] ?
'<label>' . $params['headers']['selector'] . '</label>' : '') . '
771 <div class="form-wizards-wrap form-wizards-aside">
772 <div class="form-wizards-element">
774 ' . (!$params['noList'] && !empty($allowedTables) ?
$allowedTables : '') . '
775 ' . (!$params['noList'] && (!empty($allowedList) ||
!empty($disallowedList))
776 ?
'<div class="help-block">' . $allowedList . $disallowedList . ' </div>'
779 ' . (!empty($icons['L']) ?
'<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['L']) . '</div></div>' : '') . '
780 ' . (!empty($icons['R']) ?
'<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['R']) . '</div></div>' : '') . '
785 <div class="form-multigroup-wrap t3js-formengine-field-group">
786 <div class="form-multigroup-item form-multigroup-element">' . $str . '</div>
787 <div class="form-multigroup-item form-multigroup-element">
788 ' . ($params['headers']['items'] ?
'<label>' . $params['headers']['items'] . '</label>' : '') . '
789 ' . ($params['headers']['selectorbox'] ?
'<div class="form-multigroup-item-wizard">' . $params['headers']['selectorbox'] . '</div>' : '') . '
797 // Creating the hidden field which contains the actual value as a comma list.
798 $str .= '<input type="hidden" name="' . $fName . '" value="' . htmlspecialchars(implode(',', $uidList)) . '" />';
803 * Returns array of elements from clipboard to insert into GROUP element box.
805 * @param string $allowed Allowed elements, Eg "pages,tt_content", "gif,jpg,jpeg,png
806 * @param string $mode Mode of relations: "db" or "file
807 * @return array Array of elements in values (keys are insignificant), if none found, empty array.
809 protected function getClipboardElements($allowed, $mode)
811 if (!is_object($this->clipboard
)) {
812 $this->clipboard
= GeneralUtility
::makeInstance(Clipboard
::class);
813 $this->clipboard
->initializeClipboard();
818 case 'file_reference':
821 $elFromTable = $this->clipboard
->elFromTable('_FILE');
822 $allowedExts = GeneralUtility
::trimExplode(',', $allowed, true
);
823 // If there are a set of allowed extensions, filter the content:
825 foreach ($elFromTable as $elValue) {
826 $pI = pathinfo($elValue);
827 $ext = strtolower($pI['extension']);
828 if (in_array($ext, $allowedExts)) {
829 $output[] = $elValue;
833 // If all is allowed, insert all: (This does NOT respect any disallowed extensions,
834 // but those will be filtered away by the backend TCEmain)
835 $output = $elFromTable;
839 $allowedTables = GeneralUtility
::trimExplode(',', $allowed, true
);
840 // All tables allowed for relation:
841 if (trim($allowedTables[0]) === '*') {
842 $output = $this->clipboard
->elFromTable('');
844 // Only some tables, filter them:
845 foreach ($allowedTables as $tablename) {
846 $elFromTable = $this->clipboard
->elFromTable($tablename);
847 $output = array_merge($output, $elFromTable);
850 $output = array_keys($output);
858 * @return LanguageService
860 protected function getLanguageService()
862 return $GLOBALS['LANG'];