[!!!][TASK] Improve flex and TCA handling in FormEngine
[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\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
19 use TYPO3\CMS\Core\Imaging\Icon;
20 use TYPO3\CMS\Core\Utility\MathUtility;
21 use TYPO3\CMS\Core\Utility\StringUtility;
22 use TYPO3\CMS\Lang\LanguageService;
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 * Render side by side element.
33 *
34 * @return array As defined in initializeResultArray() of AbstractNode
35 */
36 public function render()
37 {
38 $languageService = $this->getLanguageService();
39
40 $parameterArray = $this->data['parameterArray'];
41 $config = $parameterArray['fieldConf']['config'];
42 $elementName = $parameterArray['itemFormElName'];
43
44 if ($config['readOnly']) {
45 // Early return for the relatively simple read only case
46 return $this->renderReadOnly();
47 }
48
49 $possibleItems = $config['items'];
50 $selectedItems = $parameterArray['itemFormElValue'] ?: [];
51 $selectedItemsCount = count($selectedItems);
52
53 $maxItems = $config['maxitems'];
54 $autoSizeMax = MathUtility::forceIntegerInRange($config['autoSizeMax'], 0);
55 $size = 2;
56 if (isset($config['size'])) {
57 $size = (int)$config['size'];
58 }
59 if ($autoSizeMax >= 1) {
60 $size = MathUtility::forceIntegerInRange($selectedItemsCount + 1, MathUtility::forceIntegerInRange($size, 1), $autoSizeMax);
61 }
62 $itemCanBeSelectedMoreThanOnce = !empty($config['multiple']);
63
64 $listOfSelectedValues = [];
65 $selectedItemsHtml = [];
66 foreach ($selectedItems as $itemValue) {
67 foreach ($possibleItems as $possibleItem) {
68 if ($possibleItem[1] == $itemValue) {
69 $title = $possibleItem[0];
70 $listOfSelectedValues[] = $itemValue;
71 $selectedItemsHtml[] = '<option value="' . htmlspecialchars($itemValue) . '" title="' . htmlspecialchars($title) . '">' . htmlspecialchars($title) . '</option>';
72 break;
73 }
74 }
75 }
76
77 $selectableItemsHtml = [];
78 foreach ($possibleItems as $possibleItem) {
79 $disabledAttr = '';
80 $classAttr = '';
81 if (!$itemCanBeSelectedMoreThanOnce && in_array((string)$possibleItem[1], $selectedItems, true)) {
82 $disabledAttr = ' disabled="disabled"';
83 $classAttr = ' class="hidden"';
84 }
85 $selectableItemsHtml[] =
86 '<option value="'
87 . htmlspecialchars($possibleItem[1])
88 . '" title="' . htmlspecialchars($possibleItem[0]) . '"'
89 . $classAttr . $disabledAttr
90 . '>'
91 . htmlspecialchars($possibleItem[0]) .
92 '</option>';
93 }
94
95 // Html stuff for filter and select filter on top of right side of multi select boxes
96 $filterTextfield = [];
97 if ($config['enableMultiSelectFilterTextfield']) {
98 $filterTextfield[] = '<span class="input-group input-group-sm">';
99 $filterTextfield[] = '<span class="input-group-addon">';
100 $filterTextfield[] = '<span class="fa fa-filter"></span>';
101 $filterTextfield[] = '</span>';
102 $filterTextfield[] = '<input class="t3js-formengine-multiselect-filter-textfield form-control" value="">';
103 $filterTextfield[] = '</span>';
104 }
105 $filterDropDownOptions = [];
106 if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) {
107 foreach ($config['multiSelectFilterItems'] as $optionElement) {
108 $value = $languageService->sL($optionElement[0]);
109 $label = $value;
110 if (isset($optionElement[1]) && trim($optionElement[1]) !== '') {
111 $label = $languageService->sL($optionElement[1]);
112 }
113 $filterDropDownOptions[] = '<option value="' . htmlspecialchars($value) . '">' . htmlspecialchars($label) . '</option>';
114 }
115 }
116 $filterHtml = [];
117 if (!empty($filterTextfield) || !empty($filterDropDownOptions)) {
118 $filterHtml[] = '<div class="form-multigroup-item-wizard">';
119 if (!empty($filterTextfield) && !empty($filterDropDownOptions)) {
120 $filterHtml[] = '<div class="t3js-formengine-multiselect-filter-container form-multigroup-wrap">';
121 $filterHtml[] = '<div class="form-multigroup-item form-multigroup-element">';
122 $filterHtml[] = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">';
123 $filterHtml[] = implode(LF, $filterDropDownOptions);
124 $filterHtml[] = '</select>';
125 $filterHtml[] = '</div>';
126 $filterHtml[] = '<div class="form-multigroup-item form-multigroup-element">';
127 $filterHtml[] = implode(LF, $filterTextfield);
128 $filterHtml[] = '</div>';
129 $filterHtml[] = '</div>';
130 } elseif (!empty($filterTextfield)) {
131 $filterHtml[] = implode(LF, $filterTextfield);
132 } else {
133 $filterHtml[] = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">';
134 $filterHtml[] = implode(LF, $filterDropDownOptions);
135 $filterHtml[] = '</select>';
136 }
137 $filterHtml[] = '</div>';
138 }
139
140 $classes = [];
141 $classes[] = 'form-control';
142 $classes[] = 'tceforms-multiselect';
143 if ($maxItems === 1) {
144 $classes[] = 'form-select-no-siblings';
145 }
146 $multipleAttribute = '';
147 if ($maxItems !== 1 && $size !== 1) {
148 $multipleAttribute = ' multiple="multiple"';
149 }
150 $selectedListStyle = '';
151 if (isset($config['selectedListStyle'])) {
152 $selectedListStyle = ' style="' . htmlspecialchars($config['selectedListStyle']) . '"';
153 }
154 $selectableListStyle = '';
155 if (isset($config['itemListStyle'])) {
156 $selectableListStyle = ' style="' . htmlspecialchars($config['itemListStyle']) . '"';
157 }
158
159 $html = [];
160 $html[] = '<input type="hidden" data-formengine-input-name="' . htmlspecialchars($elementName) . '" value="' . (int)$itemCanBeSelectedMoreThanOnce . '" />';
161 $html[] = '<div class="form-multigroup-wrap t3js-formengine-field-group">';
162 $html[] = '<div class="form-multigroup-item form-multigroup-element">';
163 $html[] = '<label>';
164 $html[] = htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.selected'));
165 $html[] = '</label>';
166 $html[] = '<div class="form-wizards-wrap form-wizards-aside">';
167 $html[] = '<div class="form-wizards-element">';
168 $html[] = '<select';
169 $html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
170 $html[] = ' size="' . $size . '"';
171 $html[] = ' class="' . implode(' ', $classes) . '"';
172 $html[] = $multipleAttribute;
173 $html[] = ' data-formengine-input-name="' . htmlspecialchars($elementName) . '"';
174 $html[] = $selectedListStyle;
175 $html[] = '>';
176 $html[] = implode(LF, $selectedItemsHtml);
177 $html[] = '</select>';
178 $html[] = '</div>';
179 $html[] = '<div class="form-wizards-items">';
180 $html[] = '<div class="btn-group-vertical">';
181 if ($maxItems > 1 && $size >= 5) {
182 $html[] = '<a href="#"';
183 $html[] = ' class="btn btn-default t3js-btn-moveoption-top"';
184 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
185 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.move_to_top')) . '"';
186 $html[] = '>';
187 $html[] = $this->iconFactory->getIcon('actions-move-to-top', Icon::SIZE_SMALL)->render();
188 $html[] = '</a>';
189 }
190 if ($maxItems > 1) {
191 $html[] = '<a href="#"';
192 $html[] = ' class="btn btn-default t3js-btn-moveoption-up"';
193 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
194 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.move_up')) . '"';
195 $html[] = '>';
196 $html[] = $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render();
197 $html[] = '</a>';
198 $html[] = '<a href="#"';
199 $html[] = ' class="btn btn-default t3js-btn-moveoption-down"';
200 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
201 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.move_down')) . '"';
202 $html[] = '>';
203 $html[] = $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render();
204 $html[] = '</a>';
205 }
206 if ($maxItems > 1 && $size >= 5) {
207 $html[] = '<a href="#"';
208 $html[] = ' class="btn btn-default t3js-btn-moveoption-bottom"';
209 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
210 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.move_to_bottom')) . '"';
211 $html[] = '>';
212 $html[] = $this->iconFactory->getIcon('actions-move-to-bottom', Icon::SIZE_SMALL)->render();
213 $html[] = '</a>';
214 }
215 $html[] = '<a href="#"';
216 $html[] = ' class="btn btn-default t3js-btn-removeoption"';
217 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
218 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.remove_selected')) . '"';
219 $html[] = '>';
220 $html[] = $this->iconFactory->getIcon('actions-selection-delete', Icon::SIZE_SMALL)->render();
221 $html[] = '</a>';
222 $html[] = '</div>';
223 $html[] = '</div>';
224 $html[] = '</div>';
225 $html[] = '</div>';
226 $html[] = '<div class="form-multigroup-item form-multigroup-element">';
227 $html[] = '<label>';
228 $html[] = htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.items'));
229 $html[] = '</label>';
230 $html[] = implode(LF, $filterHtml);
231 $html[] = '<select';
232 $html[] = ' data-relatedfieldname="' . htmlspecialchars($elementName) . '"';
233 $html[] = ' data-exclusivevalues="' . htmlspecialchars($config['exclusiveKeys']) . '"';
234 $html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
235 $html[] = ' data-formengine-input-name="' . htmlspecialchars($elementName) . '"';
236 $html[] = ' class="form-control t3js-formengine-select-itemstoselect"';
237 $html[] = ' size="' . $size . '"';
238 $html[] = ' onchange="' . htmlspecialchars(implode('', $parameterArray['fieldChangeFunc'])) . '"';
239 $html[] = $this->getValidationDataAsDataAttribute($config);
240 $html[] = $selectableListStyle;
241 $html[] = '>';
242 $html[] = implode(LF, $selectableItemsHtml);
243 $html[] = '</select>';
244 $html[] = '</div>';
245 $html[] = '</div>';
246 $html[] = '<input type="hidden" name="' . htmlspecialchars($elementName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
247
248 $html = $this->renderWizards(
249 [ implode(LF, $html) ],
250 $config['wizards'],
251 $this->data['tableName'],
252 $this->data['databaseRow'],
253 $this->data['fieldName'],
254 $parameterArray,
255 $elementName,
256 BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
257 );
258
259 $resultArray = $this->initializeResultArray();
260 $resultArray['html'] = $html;
261 return $resultArray;
262 }
263
264 /**
265 * Create HTML of a read only multi select. Right side is not
266 * rendered, but just the left side with the selected items.
267 *
268 * @return array
269 */
270 protected function renderReadOnly()
271 {
272 $languageService = $this->getLanguageService();
273
274 $parameterArray = $this->data['parameterArray'];
275 $config = $parameterArray['fieldConf']['config'];
276 $fieldName = $parameterArray['itemFormElName'];
277
278 $possibleItems = $config['items'];
279 $selectedItems = $parameterArray['itemFormElValue'] ?: [];
280 $selectedItemsCount = count($selectedItems);
281
282 $autoSizeMax = MathUtility::forceIntegerInRange($config['autoSizeMax'], 0);
283 $size = 2;
284 if (isset($config['size'])) {
285 $size = (int)$config['size'];
286 }
287 if ($autoSizeMax >= 1) {
288 $size = MathUtility::forceIntegerInRange($selectedItemsCount + 1, MathUtility::forceIntegerInRange($size, 1), $autoSizeMax);
289 }
290 $multiple = '';
291 if ($size !== 1) {
292 $multiple = ' multiple="multiple"';
293 }
294 $style = '';
295 if (isset($config['selectedListStyle'])) {
296 $style = ' style="' . htmlspecialchars($config['selectedListStyle']) . '"';
297 }
298
299 $listOfSelectedValues = [];
300 $optionsHtml = [];
301 foreach ($selectedItems as $itemValue) {
302 foreach ($possibleItems as $possibleItem) {
303 if ($possibleItem[1] == $itemValue) {
304 $title = $possibleItem[0];
305 $listOfSelectedValues[] = $itemValue;
306 $optionsHtml[] = '<option value="' . htmlspecialchars($itemValue) . '" title="' . htmlspecialchars($title) . '">' . htmlspecialchars($title) . '</option>';
307 break;
308 }
309 }
310 }
311
312 $html = [];
313 $html[] = '<label>';
314 $html[] = htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.selected'));
315 $html[] = '</label>';
316 $html[] = '<div class="form-wizards-wrap form-wizards-aside">';
317 $html[] = '<div class="form-wizards-element">';
318 $html[] = '<select';
319 $html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
320 $html[] = ' size="' . $size . '"';
321 $html[] = ' class="form-control tceforms-multiselect"';
322 $html[] = $multiple;
323 $html[] = ' data-formengine-input-name="' . htmlspecialchars($fieldName) . '"';
324 $html[] = $style;
325 $html[] = ' disabled="disabled">';
326 $html[] = '/>';
327 $html[] = implode(LF, $optionsHtml);
328 $html[] = '</select>';
329 $html[] = '</div>';
330 $html[] = '</div>';
331 $html[] = '<input type="hidden" name="' . htmlspecialchars($fieldName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
332
333 $resultArray = $this->initializeResultArray();
334 $resultArray['html'] = implode(LF, $html);
335 return $resultArray;
336 }
337
338 /**
339 * @return LanguageService
340 */
341 protected function getLanguageService()
342 {
343 return $GLOBALS['LANG'];
344 }
345
346 /**
347 * @return BackendUserAuthentication
348 */
349 protected function getBackendUserAuthentication()
350 {
351 return $GLOBALS['BE_USER'];
352 }
353 }