[!!!][TASK] Improve flex and TCA handling in FormEngine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / SelectSingleBoxElement.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\Imaging\Icon;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Core\Utility\MathUtility;
21 use TYPO3\CMS\Core\Utility\StringUtility;
22
23 /**
24 * Create a widget with a select box where multiple items can be selected
25 *
26 * This is rendered for config type=select, renderType=selectSingleBox
27 */
28 class SelectSingleBoxElement extends AbstractFormElement
29 {
30 /**
31 * This will render a selector box element, or possibly a special construction with two selector boxes.
32 *
33 * @return array As defined in initializeResultArray() of AbstractNode
34 */
35 public function render()
36 {
37 $parameterArray = $this->data['parameterArray'];
38 // Field configuration from TCA:
39 $config = $parameterArray['fieldConf']['config'];
40 $selectItems = $parameterArray['fieldConf']['config']['items'];
41
42 // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
43 $itemArray = array_flip($parameterArray['itemFormElValue']);
44 $optionElements = [];
45 $initiallySelectedIndices = [];
46
47 foreach ($selectItems as $i => $item) {
48 $value = $item[1];
49 $attributes = [];
50
51 // Selected or not by default
52 if (isset($itemArray[$value])) {
53 $attributes['selected'] = 'selected';
54 $initiallySelectedIndices[] = $i;
55 unset($itemArray[$value]);
56 }
57
58 // Non-selectable element
59 if ((string)$value === '--div--') {
60 $attributes['disabled'] = 'disabled';
61 $attributes['class'] = 'formcontrol-select-divider';
62 }
63
64 $optionElements[] = $this->renderOptionElement($value, $item[0], $attributes);
65 }
66
67 $selectElement = $this->renderSelectElement($optionElements, $parameterArray, $config);
68 $resetButtonElement = $this->renderResetButtonElement($parameterArray['itemFormElName'] . '[]', $initiallySelectedIndices);
69 $html = [];
70
71 // Add an empty hidden field which will send a blank value if all items are unselected.
72 if (empty($config['readOnly'])) {
73 $html[] = '<input type="hidden" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">';
74 }
75
76 // Put it all together
77 $width = $this->formMaxWidth($this->defaultInputWidth);
78 $html = array_merge($html, [
79 '<div class="form-control-wrap" ' . ($width ? ' style="max-width: ' . $width . 'px"' : '') . '>',
80 '<div class="form-wizards-wrap form-wizards-aside">',
81 '<div class="form-wizards-element">',
82 $selectElement,
83 '</div>',
84 '<div class="form-wizards-items">',
85 $resetButtonElement,
86 '</div>',
87 '</div>',
88 '</div>',
89 '<p>',
90 '<em>' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.holdDownCTRL')) . '</em>',
91 '</p>',
92 ]);
93 $html = implode(LF, $html);
94
95 // Wizards:
96 if (empty($config['readOnly'])) {
97 $html = $this->renderWizards(
98 [$html],
99 $config['wizards'],
100 $this->data['tableName'],
101 $this->data['databaseRow'],
102 $this->data['fieldName'],
103 $parameterArray,
104 $parameterArray['itemFormElName'],
105 BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
106 );
107 }
108
109 $resultArray = $this->initializeResultArray();
110 $resultArray['html'] = $html;
111
112 return $resultArray;
113 }
114
115 /**
116 * Renders a <select> element
117 *
118 * @param array $optionElements List of rendered <option> elements
119 * @param array $parameterArray
120 * @param array $config Field configuration
121 * @return string
122 */
123 protected function renderSelectElement(array $optionElements, array $parameterArray, array $config)
124 {
125 $selectItems = $parameterArray['fieldConf']['config']['items'];
126 $size = (int)$config['size'];
127 $cssPrefix = $size === 1 ? 'tceforms-select' : 'tceforms-multiselect';
128
129 if ($config['autoSizeMax']) {
130 $size = MathUtility::forceIntegerInRange(
131 count($selectItems) + 1,
132 MathUtility::forceIntegerInRange($size, 1),
133 $config['autoSizeMax']
134 );
135 }
136
137 $attributes = [
138 'name' => $parameterArray['itemFormElName'] . '[]',
139 'multiple' => 'multiple',
140 'onchange' => implode('', $parameterArray['fieldChangeFunc']),
141 'id' => StringUtility::getUniqueId($cssPrefix),
142 'class' => 'form-control ' . $cssPrefix,
143 ];
144
145 if ($size) {
146 $attributes['size'] = $size;
147 }
148
149 if ($config['readOnly']) {
150 $attributes['disabled'] = 'disabled';
151 }
152
153 if (isset($config['itemListStyle'])) {
154 $attributes['style'] = $config['itemListStyle'];
155 }
156
157 $html = [
158 '<select ' . $this->implodeAttributes($attributes) . ' ' . $this->getValidationDataAsDataAttribute($config) . '>',
159 implode(LF, $optionElements),
160 '</select>',
161 ];
162
163 return implode(LF, $html);
164 }
165
166 /**
167 * Renders a single <option> element
168 *
169 * @param string $value The option value
170 * @param string $label The option label
171 * @param array $attributes Map of attribute names and values
172 * @return string
173 */
174 protected function renderOptionElement($value, $label, array $attributes = [])
175 {
176 $html = [
177 '<option value="' . htmlspecialchars($value) . '" ' . $this->implodeAttributes($attributes) . '>',
178 htmlspecialchars($label, ENT_COMPAT, 'UTF-8', false),
179 '</option>'
180
181 ];
182
183 return implode('', $html);
184 }
185
186 /**
187 * Renders a button for resetting to the selection on initial load
188 *
189 * @param string $formElementName Form element name
190 * @param array $initiallySelectedIndices List of initially selected option indices
191 * @return string
192 */
193 protected function renderResetButtonElement($formElementName, array $initiallySelectedIndices)
194 {
195 $formElementName = GeneralUtility::quoteJSvalue($formElementName);
196 $resetCode = [
197 'document.editform[' . $formElementName . '].selectedIndex=-1'
198 ];
199 foreach ($initiallySelectedIndices as $index) {
200 $resetCode[] = 'document.editform[' . $formElementName . '].options[' . $index . '].selected=1';
201 }
202 $resetCode[] = 'return false';
203
204 $attributes = [
205 'href' => '#',
206 'class' => 'btn btn-default',
207 'onclick' => htmlspecialchars(implode(';', $resetCode)),
208 // htmlspecialchars() is done by $this->implodeAttributes()
209 'title' => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.revertSelection')
210 ];
211
212 $html = [
213 '<a ' . $this->implodeAttributes($attributes) . '>',
214 $this->iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL)->render(),
215 '</a>',
216 ];
217
218 return implode('', $html);
219 }
220
221 /**
222 * Build an HTML attributes string from a map of attributes
223 *
224 * All attribute values are passed through htmlspecialchars()
225 *
226 * @param array $attributes Map of attribute names and values
227 * @return string
228 */
229 protected function implodeAttributes(array $attributes = [])
230 {
231 $html = [];
232 foreach ($attributes as $name => $value) {
233 $html[] = $name . '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8', false) . '"';
234 }
235 return implode(' ', $html);
236 }
237 }