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