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