‪TYPO3CMS  9.5
GroupElement.php
Go to the documentation of this file.
1 <?php
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 
24 
29 {
35  protected ‪$defaultFieldInformation = [
36  'tcaDescription' => [
37  'renderType' => 'tcaDescription',
38  ],
39  ];
40 
46  protected ‪$defaultFieldControl = [
47  'elementBrowser' => [
48  'renderType' => 'elementBrowser',
49  ],
50  'insertClipboard' => [
51  'renderType' => 'insertClipboard',
52  'after' => [ 'elementBrowser' ],
53  ],
54  'editPopup' => [
55  'renderType' => 'editPopup',
56  'disabled' => true,
57  'after' => [ 'insertClipboard' ],
58  ],
59  'addRecord' => [
60  'renderType' => 'addRecord',
61  'disabled' => true,
62  'after' => [ 'editPopup' ],
63  ],
64  'listModule' => [
65  'renderType' => 'listModule',
66  'disabled' => true,
67  'after' => [ 'addRecord' ],
68  ],
69  ];
70 
76  protected ‪$defaultFieldWizard = [
77  'tableList' => [
78  'renderType' => 'tableList',
79  ],
80  'fileTypeList' => [
81  'renderType' => 'fileTypeList',
82  'after' => [ 'tableList' ],
83  ],
84  'fileThumbnails' => [
85  'renderType' => 'fileThumbnails',
86  'after' => [ 'fileTypeList' ],
87  ],
88  'recordsOverview' => [
89  'renderType' => 'recordsOverview',
90  'after' => [ 'fileThumbnails' ],
91  ],
92  'fileUpload' => [
93  'renderType' => 'fileUpload',
94  'after' => [ 'recordsOverview' ],
95  ],
96  'localizationStateSelector' => [
97  'renderType' => 'localizationStateSelector',
98  'after' => [ 'fileUpload' ],
99  ],
100  'otherLanguageContent' => [
101  'renderType' => 'otherLanguageContent',
102  'after' => [ 'localizationStateSelector' ],
103  ],
104  'defaultLanguageDifferences' => [
105  'renderType' => 'defaultLanguageDifferences',
106  'after' => [ 'otherLanguageContent' ],
107  ],
108  ];
109 
117  public function ‪render()
118  {
119  $languageService = $this->‪getLanguageService();
120  $backendUser = $this->‪getBackendUserAuthentication();
121  $resultArray = $this->‪initializeResultArray();
122 
123  $table = $this->data['tableName'];
124  $fieldName = $this->data['fieldName'];
125  $row = $this->data['databaseRow'];
126  $parameterArray = $this->data['parameterArray'];
127  $config = $parameterArray['fieldConf']['config'];
128  $elementName = $parameterArray['itemFormElName'];
129 
130  $selectedItems = $parameterArray['itemFormElValue'];
131  $selectedItemsCount = count($selectedItems);
132 
133  $maxItems = $config['maxitems'];
134  $autoSizeMax = ‪MathUtility::forceIntegerInRange($config['autoSizeMax'], 0);
135  $size = 5;
136  if (isset($config['size'])) {
137  $size = (int)$config['size'];
138  }
139  if ($autoSizeMax >= 1) {
140  $size = ‪MathUtility::forceIntegerInRange($selectedItemsCount + 1, ‪MathUtility::forceIntegerInRange($size, 1), $autoSizeMax);
141  }
142 
143  $internalType = (string)$config['internal_type'];
144  $maxTitleLength = $backendUser->uc['titleLen'];
145 
146  $listOfSelectedValues = [];
147  $selectorOptionsHtml = [];
148  if ($internalType === 'file_reference' || $internalType === 'file') {
149  // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class.
150  foreach ($selectedItems as $selectedItem) {
151  $uidOrPath = $selectedItem['uidOrPath'];
152  $listOfSelectedValues[] = $uidOrPath;
153  $title = $selectedItem['title'];
154  $shortenedTitle = GeneralUtility::fixed_lgd_cs($title, $maxTitleLength);
155  $selectorOptionsHtml[] =
156  '<option value="' . htmlspecialchars($uidOrPath) . '" title="' . htmlspecialchars($title) . '">'
157  . htmlspecialchars($this->‪appendValueToLabelInDebugMode($shortenedTitle, $uidOrPath))
158  . '</option>';
159  }
160  } elseif ($internalType === 'folder') {
161  foreach ($selectedItems as $selectedItem) {
162  $folder = $selectedItem['folder'];
163  $listOfSelectedValues[] = $folder;
164  $selectorOptionsHtml[] =
165  '<option value="' . htmlspecialchars($folder) . '" title="' . htmlspecialchars($folder) . '">'
166  . htmlspecialchars($folder)
167  . '</option>';
168  }
169  } elseif ($internalType === 'db') {
170  foreach ($selectedItems as $selectedItem) {
171  $tableWithUid = $selectedItem['table'] . '_' . $selectedItem['uid'];
172  $listOfSelectedValues[] = $tableWithUid;
173  $title = $selectedItem['title'];
174  if (empty($title)) {
175  $title = '[' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title') . ']';
176  }
177  $shortenedTitle = GeneralUtility::fixed_lgd_cs($title, $maxTitleLength);
178  $selectorOptionsHtml[] =
179  '<option value="' . htmlspecialchars($tableWithUid) . '" title="' . htmlspecialchars($title) . '">'
180  . htmlspecialchars($this->‪appendValueToLabelInDebugMode($shortenedTitle, $tableWithUid))
181  . '</option>';
182  }
183  } else {
184  throw new \RuntimeException(
185  'internal_type missing on type="group" field',
186  1485007097
187  );
188  }
189 
190  $fieldInformationResult = $this->‪renderFieldInformation();
191  $fieldInformationHtml = $fieldInformationResult['html'];
192  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
193 
194  if (isset($config['readOnly']) && $config['readOnly']) {
195  // Return early if element is read only
196  $html = [];
197  $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
198  $html[] = $fieldInformationHtml;
199  $html[] = '<div class="form-wizards-wrap">';
200  $html[] = '<div class="form-wizards-element">';
201  $html[] = '<select';
202  $html[] = ' size="' . $size . '"';
203  $html[] = ' disabled="disabled"';
204  $html[] = ' class="form-control tceforms-multiselect"';
205  $html[] = ($maxItems !== 1 && $size !== 1) ? ' multiple="multiple"' : '';
206  $html[] = '>';
207  $html[] = implode(LF, $selectorOptionsHtml);
208  $html[] = '</select>';
209  $html[] = '</div>';
210  $html[] = '<div class="form-wizards-items-aside">';
211  $html[] = '</div>';
212  $html[] = '</div>';
213  $html[] = '</div>';
214  $resultArray['html'] = implode(LF, $html);
215  return $resultArray;
216  }
217 
218  // Need some information if in flex form scope for the suggest element
219  $dataStructureIdentifier = '';
220  $flexFormSheetName = '';
221  $flexFormFieldName = '';
222  $flexFormContainerName = '';
223  $flexFormContainerFieldName = '';
224  if ($this->data['processedTca']['columns'][$fieldName]['config']['type'] === 'flex') {
225  $flexFormConfig = $this->data['processedTca']['columns'][$fieldName];
226  $dataStructureIdentifier = $flexFormConfig['config']['dataStructureIdentifier'];
227  if (!isset($flexFormConfig['config']['dataStructureIdentifier'])) {
228  throw new \RuntimeException(
229  'A data structure identifier must be set in [\'config\'] part of a flex form.'
230  . ' This is usually added by TcaFlexPrepare data processor',
231  1485206970
232  );
233  }
234  if (isset($this->data['flexFormSheetName'])) {
235  $flexFormSheetName = $this->data['flexFormSheetName'];
236  }
237  if (isset($this->data['flexFormFieldName'])) {
238  $flexFormFieldName = $this->data['flexFormFieldName'];
239  }
240  if (isset($this->data['flexFormContainerName'])) {
241  $flexFormContainerName = $this->data['flexFormContainerName'];
242  }
243  if (isset($this->data['flexFormContainerFieldName'])) {
244  $flexFormContainerFieldName = $this->data['flexFormContainerFieldName'];
245  }
246  }
247  // Get minimum characters for suggest from TCA and override by TsConfig
248  $suggestMinimumCharacters = 0;
249  if (isset($config['suggestOptions']['default']['minimumCharacters'])) {
250  $suggestMinimumCharacters = (int)$config['suggestOptions']['default']['minimumCharacters'];
251  }
252  if (isset($parameterArray['fieldTSConfig']['suggest.']['default.']['minimumCharacters'])) {
253  $suggestMinimumCharacters = (int)$parameterArray['fieldTSConfig']['suggest.']['default.']['minimumCharacters'];
254  }
255  $suggestMinimumCharacters = $suggestMinimumCharacters > 0 ? $suggestMinimumCharacters : 2;
256 
257  $itemCanBeSelectedMoreThanOnce = !empty($config['multiple']);
258 
259  $showMoveIcons = true;
260  if (isset($config['hideMoveIcons']) && $config['hideMoveIcons']) {
261  $showMoveIcons = false;
262  }
263  $showDeleteControl = true;
264  if (isset($config['hideDeleteIcon']) && $config['hideDeleteIcon']) {
265  $showDeleteControl = false;
266  }
267 
268  // Check against inline uniqueness - Create some onclick js for delete control and element browser
269  // to override record selection in some FAL scenarios - See 'appearance' docs of group element
270  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
271  $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
272  $deleteControlOnClick = '';
273  if ($this->data['isInlineChild']
274  && $this->data['inlineParentUid']
275  && $this->data['inlineParentConfig']['foreign_table'] === $table
276  && $this->data['inlineParentConfig']['foreign_unique'] === $fieldName
277  ) {
278  $objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']) . '-' . $table;
279  $deleteControlOnClick = 'inline.revertUnique(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',null,' . GeneralUtility::quoteJSvalue($row['uid']) . ');';
280  }
281 
282  $selectorAttributes = [
283  'id' => ‪StringUtility::getUniqueId('tceforms-multiselect-'),
284  'data-formengine-input-name' => htmlspecialchars($elementName),
285  'data-formengine-validation-rules' => $this->‪getValidationDataAsJsonString($config),
286  'size' => $size,
287  ];
288  $selectorClasses = [
289  'form-control',
290  'tceforms-multiselect',
291  ];
292  if ($maxItems === 1) {
293  $selectorClasses[] = 'form-select-no-siblings';
294  }
295  $selectorAttributes['class'] = implode(' ', $selectorClasses);
296  if ($maxItems !== 1 && $size !== 1) {
297  $selectorAttributes['multiple'] = 'multiple';
298  }
299 
300  $fieldControlResult = $this->‪renderFieldControl();
301  $fieldControlHtml = $fieldControlResult['html'];
302  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
303 
304  $fieldWizardResult = $this->‪renderFieldWizard();
305  $fieldWizardHtml = $fieldWizardResult['html'];
306  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
307 
308  $html = [];
309  $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
310  $html[] = $fieldInformationHtml;
311  $html[] = '<div class="form-wizards-wrap">';
312  if ($internalType === 'db' && (!isset($config['hideSuggest']) || (bool)$config['hideSuggest'] !== true)) {
313  $html[] = '<div class="form-wizards-items-top">';
314  $html[] = '<div class="autocomplete t3-form-suggest-container">';
315  $html[] = '<div class="input-group">';
316  $html[] = '<span class="input-group-addon">';
317  $html[] = $this->iconFactory->getIcon('actions-search', ‪Icon::SIZE_SMALL)->render();
318  $html[] = '</span>';
319  $html[] = '<input type="search" class="t3-form-suggest form-control"';
320  $html[] = ' placeholder="' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.findRecord') . '"';
321  $html[] = ' data-fieldname="' . htmlspecialchars($fieldName) . '"';
322  $html[] = ' data-tablename="' . htmlspecialchars($table) . '"';
323  $html[] = ' data-field="' . htmlspecialchars($elementName) . '"';
324  $html[] = ' data-uid="' . htmlspecialchars($this->data['databaseRow']['uid']) . '"';
325  $html[] = ' data-pid="' . htmlspecialchars($this->data['parentPageRow']['uid'] ?? 0) . '"';
326  $html[] = ' data-fieldtype="' . htmlspecialchars($config['type']) . '"';
327  $html[] = ' data-minchars="' . htmlspecialchars($suggestMinimumCharacters) . '"';
328  $html[] = ' data-datastructureidentifier="' . htmlspecialchars($dataStructureIdentifier) . '"';
329  $html[] = ' data-flexformsheetname="' . htmlspecialchars($flexFormSheetName) . '"';
330  $html[] = ' data-flexformfieldname="' . htmlspecialchars($flexFormFieldName) . '"';
331  $html[] = ' data-flexformcontainername="' . htmlspecialchars($flexFormContainerName) . '"';
332  $html[] = ' data-flexformcontainerfieldname="' . htmlspecialchars($flexFormContainerFieldName) . '"';
333  $html[] = '/>';
334  $html[] = '</div>';
335  $html[] = '</div>';
336  $html[] = '</div>';
337  }
338  $html[] = '<div class="form-wizards-element">';
339  $html[] = '<input type="hidden" class="t3js-group-hidden-field" data-formengine-input-name="' . htmlspecialchars($elementName) . '" value="' . $itemCanBeSelectedMoreThanOnce . '" />';
340  $html[] = '<select ' . GeneralUtility::implodeAttributes($selectorAttributes, true) . '>';
341  $html[] = implode(LF, $selectorOptionsHtml);
342  $html[] = '</select>';
343  $html[] = '</div>';
344  $html[] = '<div class="form-wizards-items-aside">';
345  $html[] = '<div class="btn-group-vertical">';
346  if ($maxItems > 1 && $size >=5 && $showMoveIcons) {
347  $html[] = '<a href="#"';
348  $html[] = ' class="btn btn-default t3js-btn-moveoption-top"';
349  $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
350  $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_to_top')) . '"';
351  $html[] = '>';
352  $html[] = $this->iconFactory->getIcon('actions-move-to-top', ‪Icon::SIZE_SMALL)->render();
353  $html[] = '</a>';
354  }
355  if ($maxItems > 1 && $size > 1 && $showMoveIcons) {
356  $html[] = '<a href="#"';
357  $html[] = ' class="btn btn-default t3js-btn-moveoption-up"';
358  $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
359  $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_up')) . '"';
360  $html[] = '>';
361  $html[] = $this->iconFactory->getIcon('actions-move-up', ‪Icon::SIZE_SMALL)->render();
362  $html[] = '</a>';
363  $html[] = '<a href="#"';
364  $html[] = ' class="btn btn-default t3js-btn-moveoption-down"';
365  $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
366  $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_down')) . '"';
367  $html[] = '>';
368  $html[] = $this->iconFactory->getIcon('actions-move-down', ‪Icon::SIZE_SMALL)->render();
369  $html[] = '</a>';
370  }
371  if ($maxItems > 1 && $size >= 5 && $showMoveIcons) {
372  $html[] = '<a href="#"';
373  $html[] = ' class="btn btn-default t3js-btn-moveoption-bottom"';
374  $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
375  $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_to_bottom')) . '"';
376  $html[] = '>';
377  $html[] = $this->iconFactory->getIcon('actions-move-to-bottom', ‪Icon::SIZE_SMALL)->render();
378  $html[] = '</a>';
379  }
380  if ($showDeleteControl) {
381  $html[] = '<a href="#"';
382  $html[] = ' class="btn btn-default t3js-btn-removeoption"';
383  $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
384  $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.remove_selected')) . '"';
385  $html[] = ' onClick="' . $deleteControlOnClick . '"';
386  $html[] = '>';
387  $html[] = $this->iconFactory->getIcon('actions-selection-delete', ‪Icon::SIZE_SMALL)->render();
388  $html[] = '</a>';
389  }
390  $html[] = '</div>';
391  $html[] = '</div>';
392  $html[] = '<div class="form-wizards-items-aside">';
393  $html[] = '<div class="btn-group-vertical">';
394  $html[] = $fieldControlHtml;
395  $html[] = '</div>';
396  $html[] = '</div>';
397  if (!empty($fieldWizardHtml)) {
398  $html[] = '<div class="form-wizards-items-bottom">';
399  $html[] = $fieldWizardHtml;
400  $html[] = '</div>';
401  }
402  $html[] = '</div>';
403  $html[] = '<input type="hidden" name="' . htmlspecialchars($elementName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
404  $html[] = '</div>';
405 
406  $resultArray['html'] = implode(LF, $html);
407  return $resultArray;
408  }
409 
413  protected function ‪getBackendUserAuthentication()
414  {
415  return ‪$GLOBALS['BE_USER'];
416  }
417 
421  protected function ‪getLanguageService()
422  {
423  return ‪$GLOBALS['LANG'];
424  }
425 }
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:29
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldInformation
‪array renderFieldInformation()
Definition: AbstractFormElement.php:71
‪TYPO3\CMS\Backend\Form\Element\GroupElement\$defaultFieldWizard
‪array $defaultFieldWizard
Definition: GroupElement.php:73
‪TYPO3\CMS\Backend\Form\AbstractNode\mergeChildReturnIntoExistingResult
‪array mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
Definition: AbstractNode.php:115
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪array initializeResultArray()
Definition: AbstractNode.php:88
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:25
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\appendValueToLabelInDebugMode
‪string int appendValueToLabelInDebugMode($label, $value)
Definition: AbstractFormElement.php:377
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
‪TYPO3\CMS\Backend\Form\Element\GroupElement\render
‪array render()
Definition: GroupElement.php:114
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement
Definition: AbstractFormElement.php:31
‪TYPO3\CMS\Backend\Form\Element\GroupElement\getBackendUserAuthentication
‪BackendUserAuthentication getBackendUserAuthentication()
Definition: GroupElement.php:410
‪TYPO3\CMS\Backend\Form\Element
Definition: AbstractFormElement.php:2
‪TYPO3\CMS\Backend\Form\Element\GroupElement
Definition: GroupElement.php:29
‪TYPO3\CMS\Backend\Form\AbstractNode\getValidationDataAsJsonString
‪string getValidationDataAsJsonString(array $config)
Definition: AbstractNode.php:153
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldControl
‪array renderFieldControl()
Definition: AbstractFormElement.php:87
‪TYPO3\CMS\Backend\Form\Element\GroupElement\getLanguageService
‪LanguageService getLanguageService()
Definition: GroupElement.php:418
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:45
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static string getUniqueId($prefix='')
Definition: StringUtility.php:91
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:29
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:29
‪TYPO3\CMS\Backend\Form\Element\GroupElement\$defaultFieldInformation
‪array $defaultFieldInformation
Definition: GroupElement.php:34
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:21
‪TYPO3\CMS\Backend\Form\Element\GroupElement\$defaultFieldControl
‪array $defaultFieldControl
Definition: GroupElement.php:44
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldWizard
‪array renderFieldWizard()
Definition: AbstractFormElement.php:103