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