[BUGFIX] Fix several typos in php comments
[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[] = '<span class="input-group input-group-sm">';
154 $filterTextfield[] = '<span class="input-group-addon">';
155 $filterTextfield[] = '<span class="fa fa-filter"></span>';
156 $filterTextfield[] = '</span>';
157 $filterTextfield[] = '<input class="t3js-formengine-multiselect-filter-textfield form-control" value="">';
158 $filterTextfield[] = '</span>';
159
160 $filterDropDownOptions = [];
161 if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) {
162 foreach ($config['multiSelectFilterItems'] as $optionElement) {
163 $value = $languageService->sL($optionElement[0]);
164 $label = $value;
165 if (isset($optionElement[1]) && trim($optionElement[1]) !== '') {
166 $label = $languageService->sL($optionElement[1]);
167 }
168 $filterDropDownOptions[] = '<option value="' . htmlspecialchars($value) . '">' . htmlspecialchars($label) . '</option>';
169 }
170 }
171 $filterHtml = [];
172 $filterHtml[] = '<div class="form-multigroup-item-wizard">';
173 if (!empty($filterDropDownOptions)) {
174 $filterHtml[] = '<div class="t3js-formengine-multiselect-filter-container form-multigroup-wrap">';
175 $filterHtml[] = '<div class="form-multigroup-item form-multigroup-element">';
176 $filterHtml[] = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">';
177 $filterHtml[] = implode(LF, $filterDropDownOptions);
178 $filterHtml[] = '</select>';
179 $filterHtml[] = '</div>';
180 $filterHtml[] = '<div class="form-multigroup-item form-multigroup-element">';
181 $filterHtml[] = implode(LF, $filterTextfield);
182 $filterHtml[] = '</div>';
183 $filterHtml[] = '</div>';
184 } else {
185 $filterHtml[] = implode(LF, $filterTextfield);
186 }
187 $filterHtml[] = '</div>';
188
189 $classes = [];
190 $classes[] = 'form-control';
191 $classes[] = 'tceforms-multiselect';
192 if ($maxItems === 1) {
193 $classes[] = 'form-select-no-siblings';
194 }
195 $multipleAttribute = '';
196 if ($maxItems !== 1 && $size !== 1) {
197 $multipleAttribute = ' multiple="multiple"';
198 }
199
200 $fieldInformationResult = $this->renderFieldInformation();
201 $fieldInformationHtml = $fieldInformationResult['html'];
202 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
203
204 $fieldControlResult = $this->renderFieldControl();
205 $fieldControlHtml = $fieldControlResult['html'];
206 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
207
208 $fieldWizardResult = $this->renderFieldWizard();
209 $fieldWizardHtml = $fieldWizardResult['html'];
210 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
211
212 $selectedOptionsFieldId = StringUtility::getUniqueId('tceforms-multiselect-');
213 $availableOptionsFieldId = StringUtility::getUniqueId('tceforms-multiselect-');
214
215 $html = [];
216 $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
217 $html[] = $fieldInformationHtml;
218 $html[] = '<div class="form-wizards-wrap">';
219 $html[] = '<div class="form-wizards-element">';
220 $html[] = '<input type="hidden" data-formengine-input-name="' . htmlspecialchars($elementName) . '" value="' . (int)$itemCanBeSelectedMoreThanOnce . '" />';
221 $html[] = '<div class="form-multigroup-wrap t3js-formengine-field-group">';
222 $html[] = '<div class="form-multigroup-item form-multigroup-element">';
223 $html[] = '<label>';
224 $html[] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selected'));
225 $html[] = '</label>';
226 $html[] = '<div class="form-wizards-wrap form-wizards-aside">';
227 $html[] = '<div class="form-wizards-element">';
228 $html[] = '<select';
229 $html[] = ' id="' . $selectedOptionsFieldId . '"';
230 $html[] = ' size="' . $size . '"';
231 $html[] = ' class="' . implode(' ', $classes) . '"';
232 $html[] = $multipleAttribute;
233 $html[] = ' data-formengine-input-name="' . htmlspecialchars($elementName) . '"';
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-option t3js-btn-moveoption-top"';
243 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
244 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/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-option t3js-btn-moveoption-up"';
252 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
253 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/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-option t3js-btn-moveoption-down"';
259 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
260 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/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-option t3js-btn-moveoption-bottom"';
268 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
269 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/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-option t3js-btn-removeoption"';
276 $html[] = ' data-fieldname="' . htmlspecialchars($elementName) . '"';
277 $html[] = ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/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:core/Resources/Private/Language/locallang_core.xlf:labels.items'));
288 $html[] = '</label>';
289 $html[] = '<div class="form-wizards-wrap form-wizards-aside">';
290 $html[] = '<div class="form-wizards-element">';
291 $html[] = implode(LF, $filterHtml);
292 $html[] = '<select';
293 $html[] = ' data-relatedfieldname="' . htmlspecialchars($elementName) . '"';
294 $html[] = ' data-exclusivevalues="' . htmlspecialchars($config['exclusiveKeys']) . '"';
295 $html[] = ' id="' . $availableOptionsFieldId . '"';
296 $html[] = ' data-formengine-input-name="' . htmlspecialchars($elementName) . '"';
297 $html[] = ' class="form-control t3js-formengine-select-itemstoselect"';
298 $html[] = ' size="' . $size . '"';
299 $html[] = ' onchange="' . htmlspecialchars(implode('', $parameterArray['fieldChangeFunc'])) . '"';
300 $html[] = ' data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '"';
301 $html[] = '>';
302 $html[] = implode(LF, $selectableItemsHtml);
303 $html[] = '</select>';
304 $html[] = '</div>';
305 if (!empty($fieldControlHtml)) {
306 $html[] = '<div class="form-wizards-items-aside">';
307 $html[] = '<div class="btn-group-vertical">';
308 $html[] = $fieldControlHtml;
309 $html[] = '</div>';
310 $html[] = '</div>';
311 }
312 $html[] = '</div>';
313 $html[] = '</div>';
314 $html[] = '</div>';
315 $html[] = '<input type="hidden" name="' . htmlspecialchars($elementName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
316 $html[] = '</div>';
317 if (!empty($fieldWizardHtml)) {
318 $html[] = '<div class="form-wizards-items-bottom">';
319 $html[] = $fieldWizardHtml;
320 $html[] = '</div>';
321 }
322 $html[] = '</div>';
323 $html[] = '</div>';
324
325 $resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SelectMultipleSideBySideElement' => '
326 function(SelectMultipleSideBySideElement) {
327 new SelectMultipleSideBySideElement(' . GeneralUtility::quoteJSvalue($selectedOptionsFieldId) . ', ' . GeneralUtility::quoteJSvalue($availableOptionsFieldId) . ');
328 }'
329 ];
330
331 $resultArray['html'] = implode(LF, $html);
332 return $resultArray;
333 }
334
335 /**
336 * Create HTML of a read only multi select. Right side is not
337 * rendered, but just the left side with the selected items.
338 *
339 * @return array
340 */
341 protected function renderReadOnly()
342 {
343 $languageService = $this->getLanguageService();
344 $resultArray = $this->initializeResultArray();
345
346 $parameterArray = $this->data['parameterArray'];
347 $config = $parameterArray['fieldConf']['config'];
348 $fieldName = $parameterArray['itemFormElName'];
349
350 $possibleItems = $config['items'];
351 $selectedItems = $parameterArray['itemFormElValue'] ?: [];
352 if (!is_array($selectedItems)) {
353 $selectedItems = GeneralUtility::trimExplode(',', $selectedItems, true);
354 }
355 $selectedItemsCount = count($selectedItems);
356
357 $autoSizeMax = MathUtility::forceIntegerInRange($config['autoSizeMax'], 0);
358 $size = 2;
359 if (isset($config['size'])) {
360 $size = (int)$config['size'];
361 }
362 if ($autoSizeMax >= 1) {
363 $size = MathUtility::forceIntegerInRange($selectedItemsCount + 1, MathUtility::forceIntegerInRange($size, 1), $autoSizeMax);
364 }
365 $multiple = '';
366 if ($size !== 1) {
367 $multiple = ' multiple="multiple"';
368 }
369
370 $listOfSelectedValues = [];
371 $optionsHtml = [];
372 foreach ($selectedItems as $itemValue) {
373 foreach ($possibleItems as $possibleItem) {
374 if ($possibleItem[1] == $itemValue) {
375 $title = $possibleItem[0];
376 $listOfSelectedValues[] = $itemValue;
377 $optionsHtml[] = '<option value="' . htmlspecialchars($itemValue) . '" title="' . htmlspecialchars($title) . '">' . htmlspecialchars($title) . '</option>';
378 break;
379 }
380 }
381 }
382
383 $fieldInformationResult = $this->renderFieldInformation();
384 $fieldInformationHtml = $fieldInformationResult['html'];
385 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
386
387 $html = [];
388 $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
389 $html[] = $fieldInformationHtml;
390 $html[] = '<div class="form-wizards-wrap">';
391 $html[] = '<div class="form-wizards-element">';
392 $html[] = '<label>';
393 $html[] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selected'));
394 $html[] = '</label>';
395 $html[] = '<div class="form-wizards-wrap form-wizards-aside">';
396 $html[] = '<div class="form-wizards-element">';
397 $html[] = '<select';
398 $html[] = ' id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '"';
399 $html[] = ' size="' . $size . '"';
400 $html[] = ' class="form-control tceforms-multiselect"';
401 $html[] = $multiple;
402 $html[] = ' data-formengine-input-name="' . htmlspecialchars($fieldName) . '"';
403 $html[] = ' disabled="disabled">';
404 $html[] = '/>';
405 $html[] = implode(LF, $optionsHtml);
406 $html[] = '</select>';
407 $html[] = '</div>';
408 $html[] = '</div>';
409 $html[] = '<input type="hidden" name="' . htmlspecialchars($fieldName) . '" value="' . htmlspecialchars(implode(',', $listOfSelectedValues)) . '" />';
410 $html[] = '</div>';
411 $html[] = '</div>';
412 $html[] = '</div>';
413
414 $resultArray['html'] = implode(LF, $html);
415 return $resultArray;
416 }
417
418 /**
419 * @return LanguageService
420 */
421 protected function getLanguageService()
422 {
423 return $GLOBALS['LANG'];
424 }
425
426 /**
427 * @return BackendUserAuthentication
428 */
429 protected function getBackendUserAuthentication()
430 {
431 return $GLOBALS['BE_USER'];
432 }
433 }