[TASK] Call explicit render() on icon objects
[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, maxitems > 1, renderMode=singlebox
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 $parameterArray = $this->data['parameterArray'];
37 // Field configuration from TCA:
38 $config = $parameterArray['fieldConf']['config'];
39 $selectItems = $parameterArray['fieldConf']['config']['items'];
40
41 // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
42 $itemArray = array_flip($parameterArray['itemFormElValue']);
43 $optionElements = [];
44 $initiallySelectedIndices = [];
45
46 foreach ($selectItems as $i => $item) {
47 $value = $item[1];
48 $attributes = [];
49
50 // Selected or not by default
51 if (isset($itemArray[$value])) {
52 $attributes['selected'] = 'selected';
53 $initiallySelectedIndices[] = $i;
54 unset($itemArray[$value]);
55 }
56
57 // Non-selectable element
58 if ((string)$value === '--div--') {
59 $attributes['disabled'] = 'disabled';
60 $attributes['class'] = 'formcontrol-select-divider';
61 }
62
63 $optionElements[] = $this->renderOptionElement($value, $item[0], $attributes);
64 }
65
66 // Remaining values:
67 if (!empty($itemArray)
68 && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement']
69 && !$config['disableNoMatchingValueElement']) {
70
71 // Creating the label for the "No Matching Value" entry.
72 $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
73 ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
74 : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
75
76 foreach ($itemArray as $unmatchedValue => $temp) {
77 // Compile <option> tag:
78 array_unshift($optionElements, $this->renderOptionElement(
79 $unmatchedValue,
80 // @todo Check if we can get rid of "@" here (catches missing %s in $noMatchingLabel)
81 @sprintf($noMatchingLabel, $unmatchedValue),
82 ['selected' => 'selected']
83 ));
84 }
85 }
86
87 $selectElement = $this->renderSelectElement($optionElements, $parameterArray, $config);
88 $resetButtonElement = $this->renderResetButtonElement($parameterArray['itemFormElName'] . '[]', $initiallySelectedIndices);
89 $html = [];
90
91 // Add an empty hidden field which will send a blank value if all items are unselected.
92 if (empty($config['readOnly'])) {
93 $html[] = '<input type="hidden" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">';
94 }
95
96 // Put it all together
97 $width = $this->formMaxWidth($this->defaultInputWidth);
98 $html = array_merge($html, [
99 '<div class="form-control-wrap" ' . ($width ? ' style="max-width: ' . $width . 'px"' : '') . '>',
100 '<div class="form-wizards-wrap form-wizards-aside">',
101 '<div class="form-wizards-element">',
102 $selectElement,
103 '</div>',
104 '<div class="form-wizards-items">',
105 $resetButtonElement,
106 '</div>',
107 '</div>',
108 '</div>',
109 '<p>',
110 '<em>' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.holdDownCTRL', TRUE) . '</em>',
111 '</p>',
112 ]);
113 $html = implode(LF, $html);
114
115 // Wizards:
116 if (empty($config['readOnly'])) {
117 $html = $this->renderWizards(
118 [$html],
119 $config['wizards'],
120 $this->data['tableName'],
121 $this->data['databaseRow'],
122 $this->data['fieldName'],
123 $parameterArray,
124 $parameterArray['itemFormElName'],
125 BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
126 );
127 }
128
129 $resultArray = $this->initializeResultArray();
130 $resultArray['html'] = $html;
131
132 return $resultArray;
133 }
134
135 /**
136 * Renders a <select> element
137 *
138 * @param array $optionElements List of rendered <option> elements
139 * @param array $parameterArray
140 * @param array $config Field configuration
141 * @return string
142 */
143 protected function renderSelectElement(array $optionElements, array $parameterArray, array $config) {
144 $selectItems = $parameterArray['fieldConf']['config']['items'];
145 $size = (int)$config['size'];
146 $cssPrefix = $size === 1 ? 'tceforms-select' : 'tceforms-multiselect';
147
148 if ($config['autoSizeMax']) {
149 $size = MathUtility::forceIntegerInRange(
150 count($selectItems) + 1,
151 MathUtility::forceIntegerInRange($size, 1),
152 $config['autoSizeMax']
153 );
154 }
155
156 $attributes = [
157 'name' => $parameterArray['itemFormElName'] . '[]',
158 'multiple' => 'multiple',
159 'onchange' => implode('', $parameterArray['fieldChangeFunc']),
160 'id' => StringUtility::getUniqueId($cssPrefix),
161 'class' => 'form-control ' . $cssPrefix,
162 ];
163
164 if ($size) {
165 $attributes['size'] = $size;
166 }
167
168 if ($config['readOnly']) {
169 $attributes['disabled'] = 'disabled';
170 }
171
172 if (isset($config['itemListStyle'])) {
173 $attributes['style'] = $config['itemListStyle'];
174 }
175
176 $html = [
177 '<select ' . $this->implodeAttributes($attributes) . ' ' . $parameterArray['onFocus'] . ' ' . $this->getValidationDataAsDataAttribute($config) . '>',
178 implode(LF, $optionElements),
179 '</select>',
180 ];
181
182 return implode(LF, $html);
183 }
184
185 /**
186 * Renders a single <option> element
187 *
188 * @param string $value The option value
189 * @param string $label The option label
190 * @param array $attributes Map of attribute names and values
191 * @return string
192 */
193 protected function renderOptionElement($value, $label, array $attributes = []) {
194 $html = [
195 '<option value="' . htmlspecialchars($value) . '" ' . $this->implodeAttributes($attributes) . '>',
196 htmlspecialchars($label, ENT_COMPAT, 'UTF-8', FALSE),
197 '</option>'
198
199 ];
200
201 return implode('', $html);
202 }
203
204 /**
205 * Renders a button for resetting to the selection on initial load
206 *
207 * @param string $formElementName Form element name
208 * @param array $initiallySelectedIndices List of initially selected option indices
209 * @return string
210 */
211 protected function renderResetButtonElement($formElementName, array $initiallySelectedIndices) {
212 $formElementName = GeneralUtility::quoteJSvalue($formElementName);
213 $resetCode = [
214 'document.editform[' . $formElementName . '].selectedIndex=-1'
215 ];
216 foreach ($initiallySelectedIndices as $index) {
217 $resetCode[] = 'document.editform[' . $formElementName . '].options[' . $index . '].selected=1';
218 }
219 $resetCode[] = 'return false';
220
221 $attributes = [
222 'href' => '#',
223 'class' => 'btn btn-default',
224 'onclick' => htmlspecialchars(implode(';', $resetCode)),
225 // htmlspecialchars() is done by $this->implodeAttributes()
226 'title' => $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection')
227 ];
228
229 $html = [
230 '<a ' . $this->implodeAttributes($attributes) . '>',
231 $this->iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL)->render(),
232 '</a>',
233 ];
234
235 return implode('', $html);
236 }
237
238 /**
239 * Build an HTML attributes string from a map of attributes
240 *
241 * All attribute values are passed through htmlspecialchars()
242 *
243 * @param array $attributes Map of attribute names and values
244 * @return string
245 */
246 protected function implodeAttributes(array $attributes = []) {
247 $html = [];
248 foreach ($attributes as $name => $value) {
249 $html[] = $name . '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8', FALSE) . '"';
250 }
251 return implode(' ', $html);
252 }
253
254 }