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