[TASK] Render wizards of FormEngine elements only if needed
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / SelectMultipleSideBySideElement.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\Core\Authentication\BackendUserAuthentication;
18 use TYPO3\CMS\Core\Imaging\Icon;
19 use TYPO3\CMS\Core\Localization\LanguageService;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Core\Utility\MathUtility;
22 use TYPO3\CMS\Core\Utility\StringUtility;
23
24 /**
25 * Render a widget with two boxes side by side.
26 *
27 * This is rendered for config type=select, renderType=selectMultipleSideBySide set
28 */
29 class SelectMultipleSideBySideElement extends AbstractFormElement
30 {
31 /**
32 * Default field information enabled for this element.
33 *
34 * @var array
35 */
36 protected $defaultFieldInformation = [
37 'tcaDescription' => [
38 'renderType' => 'tcaDescription',
39 ],
40 ];
41
42 /**
43 * Default field controls for this element.
44 *
45 * @var array
46 */
47 protected $defaultFieldControl = [
48 'editPopup' => [
49 'renderType' => 'editPopup',
50 'disabled' => true,
51 ],
52 'addRecord' => [
53 'renderType' => 'addRecord',
54 'disabled' => true,
55 'after' => [ 'editPopup' ],
56 ],
57 'listModule' => [
58 'renderType' => 'listModule',
59 'disabled' => true,
60 'after' => [ 'addRecord' ],
61 ],
62 ];
63
64 /**
65 * Default field wizards enabled for this element.
66 *
67 * @var array
68 */
69 protected $defaultFieldWizard = [
70 'localizationStateSelector' => [
71 'renderType' => 'localizationStateSelector',
72 ],
73 'otherLanguageContent' => [
74 'renderType' => 'otherLanguageContent',
75 'after' => [
76 'localizationStateSelector'
77 ],
78 ],
79 'defaultLanguageDifferences' => [
80 'renderType' => 'defaultLanguageDifferences',
81 'after' => [
82 'otherLanguageContent',
83 ],
84 ],
85 ];
86
87 /**
88 * Render side by side element.
89 *
90 * @return array As defined in initializeResultArray() of AbstractNode
91 */
92 public function render()
93 {
94 $languageService = $this->getLanguageService();
95 $resultArray = $this->initializeResultArray();
96
97 $parameterArray = $this->data['parameterArray'];
98 $config = $parameterArray['fieldConf']['config'];
99 $elementName = $parameterArray['itemFormElName'];
100
101 if ($config['readOnly']) {
102 // Early return for the relatively simple read only case
103 return $this->renderReadOnly();
104 }
105
106 $possibleItems = $config['items'];
107 $selectedItems = $parameterArray['itemFormElValue'] ?: [];
108 $selectedItemsCount = count($selectedItems);
109
110 $maxItems = $config['maxitems'];
111 $autoSizeMax = MathUtility::forceIntegerInRange($config['autoSizeMax'], 0);
112 $size = 2;
113 if (isset($config['size'])) {
114 $size = (int)$config['size'];
115 }
116 if ($autoSizeMax >= 1) {
117 $size = MathUtility::forceIntegerInRange($selectedItemsCount + 1, MathUtility::forceIntegerInRange($size, 1), $autoSizeMax);
118 }
119 $itemCanBeSelectedMoreThanOnce = !empty($config['multiple']);
120
121 $listOfSelectedValues = [];
122 $selectedItemsHtml = [];
123 foreach ($selectedItems as $itemValue) {
124 foreach ($possibleItems as $possibleItem) {
125 if ($possibleItem[1] == $itemValue) {
126 $title = $possibleItem[0];
127 $listOfSelectedValues[] = $itemValue;
128 $selectedItemsHtml[] = '<option value="' . htmlspecialchars($itemValue) . '" title="' . htmlspecialchars($title) . '">' . htmlspecialchars($this->appendValueToLabelInDebugMode($title, $itemValue)) . '</option>';
129 break;
130 }
131 }
132 }
133
134 $selectableItemsHtml = [];
135 foreach ($possibleItems as $possibleItem) {
136 $disabledAttr = '';
137 $classAttr = '';
138 if (!$itemCanBeSelectedMoreThanOnce && in_array((string)$possibleItem[1], $selectedItems, true)) {
139 $disabledAttr = ' disabled="disabled"';
140 $classAttr = ' class="hidden"';
141 }
142 $selectableItemsHtml[] =
143 '<option value="'
144 . htmlspecialchars($possibleItem[1])
145 . '" title="' . htmlspecialchars($possibleItem[0]) . '"'
146 . $classAttr . $disabledAttr
147 . '>'
148 . htmlspecialchars($this->appendValueToLabelInDebugMode($possibleItem[0], $possibleItem[1])) .
149 '</option>';
150 }
151
152 // Html stuff for filter and select filter on top of right side of multi select boxes
153 $filterTextfield = [];
154 if ($config['enableMultiSelectFilterTextfield']) {
155 $filterTextfield[] = '<span class="input-group input-group-sm">';
156 $filterTextfield[] = '<span class="input-group-addon">';
157 $filterTextfield[] = '<span class="fa fa-filter"></span>';
158 $filterTextfield[] = '</span>';
159 $filterTextfield[] = '<input class="t3js-formengine-multiselect-filter-textfield form-control" value="">';
160 $filterTextfield[] = '</span>';
161 }
162 $filterDropDownOptions = [];
163 if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) {
164 foreach ($config['multiSelectFilterItems'] as $optionElement) {
165 $value = $languageService->sL($optionElement[0]);
166 $label = $value;
167 if (isset($optionElement[1]) && trim($optionElement[1]) !== '') {
168 $label = $languageService->sL($optionElement[1]);
169 }
170 $filterDropDownOptions[] = '<option value="' . htmlspecialchars($value) . '">' . htmlspecialchars($label) . '</option>';
171 }
172 }
173 $filterHtml = [];
174 if (!empty($filterTextfield) || !empty($filterDropDownOptions)) {
175 $filterHtml[] = '<div class="form-multigroup-item-wizard">';
176 if (!empty($filterTextfield) && !empty($filterDropDownOptions)) {
177 $filterHtml[] = '<div class="t3js-formengine-multiselect-filter-container form-multigroup-wrap">';
178 $filterHtml[] = '<div class="form-multigroup-item form-multigroup-element">';
179 $filterHtml[] = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">';
180 $filterHtml[] = implode(LF, $filterDropDownOptions);
181 $filterHtml[] = '</select>';
182 $filterHtml[] = '</div>';
183 $filterHtml[] = '<div class="form-multigroup-item form-multigroup-element">';
184 $filterHtml[] = implode(LF, $filterTextfield);
185 $filterHtml[] = '</div>';
186 $filterHtml[] = '</div>';
187 } elseif (!empty($filterTextfield)) {
188 $filterHtml[] = implode(LF, $filterTextfield);
189 } else {
190 $filterHtml[] = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">';
191 $filterHtml[] = implode(LF, $filterDropDownOptions);
192 $filterHtml[] = '</select>';
193 }
194 $filterHtml[] = '</div>';
195 }
196
197 $classes = [];
198 $classes[] = 'form-control';
199 $classes[] = 'tceforms-multiselect';
200 if ($maxItems === 1) {
201 $classes[] = 'form-select-no-siblings';
202 }
203 $multipleAttribute = '';
204 if ($maxItems !== 1 && $size !== 1) {
205 $multipleAttribute = ' multiple="multiple"';
206 }
207
208 $fieldInformationResult = $this->renderFieldInformation();
209 $fieldInformationHtml = $fieldInformationResult['html'];
210 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
211
212 $fieldControlResult = $this->renderFieldControl();
213 $fieldControlHtml = $fieldControlResult['html'];
214 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
215
216 $fieldWizardResult = $this->renderFieldWizard();
217 $fieldWizardHtml = $fieldWizardResult['html'];
218 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
219
220 $html = [];
221 $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
222 $html[] = $fieldInformationHtml;
223 $html[] = '<div class="form-wizards-wrap">';
224 $html[] = '<div class="form-wizards-element">';
225 $html[] = '<input type="hidden" data-formengine-input-name="' . htmlspecialchars($elementName) . '" value="' . (int)$itemCanBeSelectedMoreThanOnce . '" />';
226 $html[] = '<div class="form-multigroup-wrap t3js-formengine-field-group">';
227 $html[] = '<div class="form-multigroup-item form-multigroup-element">';
228 $html[] = '<label>';
229 $html[] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selected'));
230 $html[] = '</label>';
231 $html[] = '<div class="form-wizards-wrap form-wizards-aside">';
232 $html[] = '<div class="form-wizards-element">';
233 $html[] = '<select';
234 $html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
235 $html[] = ' size="' . $size . '"';
236 $html[] = ' class="' . implode(' ', $classes) . '"';
237 $html[] = $multipleAttribute;
238 $html[] = ' data-formengine-input-name="' . htmlspecialchars($elementName) . '"';
239 $html[] = '>';
240 $html[] = implode(LF, $selectedItemsHtml);
241 $html[] = '</select>';
242 $html[] = '</div>';
243 $html[] = '<div class="form-wizards-items-aside">';
244 $html[] = '<div class="btn-group-vertical">';
245 if ($maxItems > 1 && $size >= 5) {
246 $html[] = '<a href="#"';
247 $html[] = ' class="btn btn-default t3js-btn-moveoption-top"';
248 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
249 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_to_top')) . '"';
250 $html[] = '>';
251 $html[] = $this->iconFactory->getIcon('actions-move-to-top', Icon::SIZE_SMALL)->render();
252 $html[] = '</a>';
253 }
254 if ($maxItems > 1) {
255 $html[] = '<a href="#"';
256 $html[] = ' class="btn btn-default t3js-btn-moveoption-up"';
257 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
258 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_up')) . '"';
259 $html[] = '>';
260 $html[] = $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render();
261 $html[] = '</a>';
262 $html[] = '<a href="#"';
263 $html[] = ' class="btn btn-default t3js-btn-moveoption-down"';
264 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
265 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_down')) . '"';
266 $html[] = '>';
267 $html[] = $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render();
268 $html[] = '</a>';
269 }
270 if ($maxItems > 1 && $size >= 5) {
271 $html[] = '<a href="#"';
272 $html[] = ' class="btn btn-default t3js-btn-moveoption-bottom"';
273 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
274 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move_to_bottom')) . '"';
275 $html[] = '>';
276 $html[] = $this->iconFactory->getIcon('actions-move-to-bottom', Icon::SIZE_SMALL)->render();
277 $html[] = '</a>';
278 }
279 $html[] = '<a href="#"';
280 $html[] = ' class="btn btn-default t3js-btn-removeoption"';
281 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
282 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.remove_selected')) . '"';
283 $html[] = '>';
284 $html[] = $this->iconFactory->getIcon('actions-selection-delete', Icon::SIZE_SMALL)->render();
285 $html[] = '</a>';
286 $html[] = '</div>';
287 $html[] = '</div>';
288 $html[] = '</div>';
289 $html[] = '</div>';
290 $html[] = '<div class="form-multigroup-item form-multigroup-element">';
291 $html[] = '<label>';
292 $html[] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.items'));
293 $html[] = '</label>';
294 $html[] = '<div class="form-wizards-wrap form-wizards-aside">';
295 $html[] = '<div class="form-wizards-element">';
296 $html[] = implode(LF, $filterHtml);
297 $html[] = '<select';
298 $html[] = ' data-relatedfieldname="' . htmlspecialchars($elementName) . '"';
299 $html[] = ' data-exclusivevalues="' . htmlspecialchars($config['exclusiveKeys']) . '"';
300 $html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
301 $html[] = ' data-formengine-input-name="' . htmlspecialchars($elementName) . '"';
302 $html[] = ' class="form-control t3js-formengine-select-itemstoselect"';
303 $html[] = ' size="' . $size . '"';
304 $html[] = ' onchange="' . htmlspecialchars(implode('', $parameterArray['fieldChangeFunc'])) . '"';
305 $html[] = ' data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '"';
306 $html[] = '>';
307 $html[] = implode(LF, $selectableItemsHtml);
308 $html[] = '</select>';
309 $html[] = '</div>';
310 if (!empty($fieldControlHtml)) {
311 $html[] = '<div class="form-wizards-items-aside">';
312 $html[] = '<div class="btn-group-vertical">';
313 $html[] = $fieldControlHtml;
314 $html[] = '</div>';
315 $html[] = '</div>';
316 }
317 $html[] = '</div>';
318 $html[] = '</div>';
319 $html[] = '</div>';
320 $html[] = '<input type="hidden" name="' . htmlspecialchars($elementName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
321 $html[] = '</div>';
322 if (!empty($fieldWizardHtml)) {
323 $html[] = '<div class="form-wizards-items-bottom">';
324 $html[] = $fieldWizardHtml;
325 $html[] = '</div>';
326 }
327 $html[] = '</div>';
328 $html[] = '</div>';
329
330 $resultArray['html'] = implode(LF, $html);
331 return $resultArray;
332 }
333
334 /**
335 * Create HTML of a read only multi select. Right side is not
336 * rendered, but just the left side with the selected items.
337 *
338 * @return array
339 */
340 protected function renderReadOnly()
341 {
342 $languageService = $this->getLanguageService();
343 $resultArray = $this->initializeResultArray();
344
345 $parameterArray = $this->data['parameterArray'];
346 $config = $parameterArray['fieldConf']['config'];
347 $fieldName = $parameterArray['itemFormElName'];
348
349 $possibleItems = $config['items'];
350 $selectedItems = $parameterArray['itemFormElValue'] ?: [];
351 if (!is_array($selectedItems)) {
352 $selectedItems = GeneralUtility::trimExplode(',', $selectedItems, true);
353 }
354 $selectedItemsCount = count($selectedItems);
355
356 $autoSizeMax = MathUtility::forceIntegerInRange($config['autoSizeMax'], 0);
357 $size = 2;
358 if (isset($config['size'])) {
359 $size = (int)$config['size'];
360 }
361 if ($autoSizeMax >= 1) {
362 $size = MathUtility::forceIntegerInRange($selectedItemsCount + 1, MathUtility::forceIntegerInRange($size, 1), $autoSizeMax);
363 }
364 $multiple = '';
365 if ($size !== 1) {
366 $multiple = ' multiple="multiple"';
367 }
368
369 $listOfSelectedValues = [];
370 $optionsHtml = [];
371 foreach ($selectedItems as $itemValue) {
372 foreach ($possibleItems as $possibleItem) {
373 if ($possibleItem[1] == $itemValue) {
374 $title = $possibleItem[0];
375 $listOfSelectedValues[] = $itemValue;
376 $optionsHtml[] = '<option value="' . htmlspecialchars($itemValue) . '" title="' . htmlspecialchars($title) . '">' . htmlspecialchars($title) . '</option>';
377 break;
378 }
379 }
380 }
381
382 $fieldInformationResult = $this->renderFieldInformation();
383 $fieldInformationHtml = $fieldInformationResult['html'];
384 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
385
386 $html = [];
387 $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
388 $html[] = $fieldInformationHtml;
389 $html[] = '<div class="form-wizards-wrap">';
390 $html[] = '<div class="form-wizards-element">';
391 $html[] = '<label>';
392 $html[] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selected'));
393 $html[] = '</label>';
394 $html[] = '<div class="form-wizards-wrap form-wizards-aside">';
395 $html[] = '<div class="form-wizards-element">';
396 $html[] = '<select';
397 $html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
398 $html[] = ' size="' . $size . '"';
399 $html[] = ' class="form-control tceforms-multiselect"';
400 $html[] = $multiple;
401 $html[] = ' data-formengine-input-name="' . htmlspecialchars($fieldName) . '"';
402 $html[] = ' disabled="disabled">';
403 $html[] = '/>';
404 $html[] = implode(LF, $optionsHtml);
405 $html[] = '</select>';
406 $html[] = '</div>';
407 $html[] = '</div>';
408 $html[] = '<input type="hidden" name="' . htmlspecialchars($fieldName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
409 $html[] = '</div>';
410 $html[] = '</div>';
411 $html[] = '</div>';
412
413 $resultArray['html'] = implode(LF, $html);
414 return $resultArray;
415 }
416
417 /**
418 * @return LanguageService
419 */
420 protected function getLanguageService()
421 {
422 return $GLOBALS['LANG'];
423 }
424
425 /**
426 * @return BackendUserAuthentication
427 */
428 protected function getBackendUserAuthentication()
429 {
430 return $GLOBALS['BE_USER'];
431 }
432 }