[TASK] Use GeneralUtility::quoteJSvalue() where needed part 2
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / SelectSingleElement.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\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\MathUtility;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
21 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
22
23 /**
24 * Creates a widget where only one item can be selected.
25 * This is either a select drop-down if no size config is given or set to 1, or a select box.
26 *
27 * This is rendered for type=select, maxitems=1
28 */
29 class SelectSingleElement extends AbstractFormElement {
30
31 /**
32 * @var array Result array given returned by render() - This property is a helper until class is properly refactored
33 */
34 protected $resultArray = array();
35
36 /**
37 * Render single element
38 *
39 * @return array As defined in initializeResultArray() of AbstractNode
40 */
41 public function render() {
42 $table = $this->globalOptions['table'];
43 $field = $this->globalOptions['fieldName'];
44 $row = $this->globalOptions['databaseRow'];
45 $parameterArray = $this->globalOptions['parameterArray'];
46 $config = $parameterArray['fieldConf']['config'];
47
48 $disabled = '';
49 if ($this->isGlobalReadonly() || $config['readOnly']) {
50 $disabled = ' disabled="disabled"';
51 }
52
53 $this->resultArray = $this->initializeResultArray();
54
55 // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
56 $specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
57 $selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
58
59 // Creating the label for the "No Matching Value" entry.
60 $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
61 ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
62 : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
63
64 $html = $this->getSingleField_typeSelect_single($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel);
65
66 // Wizards:
67 if (!$disabled) {
68 $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
69 $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf);
70 }
71 $this->resultArray['html'] = $html;
72 return $this->resultArray;
73 }
74
75 /**
76 * Creates a single-selector box
77 *
78 * @param string $table See getSingleField_typeSelect()
79 * @param string $field See getSingleField_typeSelect()
80 * @param array $row See getSingleField_typeSelect()
81 * @param array $parameterArray See getSingleField_typeSelect()
82 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
83 * @param array $selectItems Items available for selection
84 * @param string $noMatchingLabel Label for no-matching-value
85 * @return string The HTML code for the item
86 */
87 protected function getSingleField_typeSelect_single($table, $field, $row, $parameterArray, $config, $selectItems, $noMatchingLabel) {
88 // Check against inline uniqueness
89 /** @var InlineStackProcessor $inlineStackProcessor */
90 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
91 $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
92 $inlineParent = $inlineStackProcessor->getStructureLevel(-1);
93 $uniqueIds = NULL;
94 if (is_array($inlineParent) && $inlineParent['uid']) {
95 $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
96 $inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix();
97 if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
98 $uniqueIds = $this->globalOptions['inlineData']['unique'][$inlineObjectName . '-' . $table]['used'];
99 $parameterArray['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,'
100 . GeneralUtility::quoteJSvalue($inlineObjectName . '-' . $table) . ','
101 . GeneralUtility::quoteJSvalue($inlineFormName) . ','
102 . GeneralUtility::quoteJSvalue($row['uid']) . ');';
103 }
104 // hide uid of parent record for symmetric relations
105 if (
106 $inlineParent['config']['foreign_table'] == $table
107 && ($inlineParent['config']['foreign_field'] == $field || $inlineParent['config']['symmetric_field'] == $field)
108 ) {
109 $uniqueIds[] = $inlineParent['uid'];
110 }
111 }
112
113 // Initialization:
114 $selectId = str_replace('.', '', uniqid('tceforms-select-', TRUE));
115 $selectedIndex = 0;
116 $selectedIcon = '';
117 $noMatchingValue = 1;
118 $onlySelectedIconShown = 0;
119 $size = (int)$config['size'];
120
121 // Style set on <select/>
122 $out = '';
123 $options = '';
124 $disabled = FALSE;
125 if ($this->isGlobalReadonly() || $config['readOnly']) {
126 $disabled = TRUE;
127 $onlySelectedIconShown = 1;
128 }
129 // Register as required if minitems is greater than zero
130 if (($minItems = MathUtility::forceIntegerInRange($config['minitems'], 0)) > 0) {
131 $this->resultArray['requiredFields'][$table . '_' . $row['uid'] . '_' . $field] = $parameterArray['itemFormElName'];
132 $tabAndInlineStack = $this->globalOptions['tabAndInlineStack'];
133 if (!empty($tabAndInlineStack) && preg_match('/^(.+\\])\\[(\\w+)\\]$/', $parameterArray['itemFormElName'], $match)) {
134 array_shift($match);
135 $this->resultArray['requiredNested'][$parameterArray['itemFormElName']] = array(
136 'parts' => $match,
137 'level' => $tabAndInlineStack,
138 );
139 }
140 }
141
142 // Icon configuration:
143 if ($config['suppress_icons'] === 'IF_VALUE_FALSE') {
144 $suppressIcons = !$parameterArray['itemFormElValue'] ? 1 : 0;
145 } elseif ($config['suppress_icons'] === 'ONLY_SELECTED') {
146 $suppressIcons = 0;
147 $onlySelectedIconShown = 1;
148 } elseif ($config['suppress_icons']) {
149 $suppressIcons = 1;
150 } else {
151 $suppressIcons = 0;
152 }
153
154 // Prepare groups
155 $selectItemCounter = 0;
156 $selectItemGroupCount = 0;
157 $selectItemGroups = array();
158 $selectIcons = array();
159 foreach ($selectItems as $item) {
160 if ($item[1] === '--div--') {
161 // IS OPTGROUP
162 if ($selectItemCounter !== 0) {
163 $selectItemGroupCount++;
164 }
165 $selectItemGroups[$selectItemGroupCount]['header'] = array(
166 'title' => $item[0],
167 'icon' => (!empty($item[2]) ? FormEngineUtility::getIconHtml($item[2]) : ''),
168 );
169 } else {
170 // IS ITEM
171 $title = htmlspecialchars($item['0'], ENT_COMPAT, 'UTF-8', FALSE);
172 $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $title, $title) : '';
173 $selected = ((string)$parameterArray['itemFormElValue'] === (string)$item[1] ? 1 : 0);
174 if ($selected) {
175 $selectedIndex = $selectItemCounter;
176 $selectedIcon = $icon;
177 $noMatchingValue = 0;
178 }
179 $selectItemGroups[$selectItemGroupCount]['items'][] = array(
180 'title' => $title,
181 'value' => $item[1],
182 'icon' => $icon,
183 'selected' => $selected,
184 'index' => $selectItemCounter
185 );
186 // ICON
187 if ($icon && !$suppressIcons && (!$onlySelectedIconShown || $selected)) {
188 $onClick = 'document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . '].selectedIndex=' . $selectItemCounter . ';';
189 if ($config['iconsInOptionTags']) {
190 $onClick .= 'document.getElementById(' . GeneralUtility::quoteJSvalue($selectId . '_icon') . ').innerHTML = '
191 . 'document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ']'
192 . '.options[' . $selectItemCounter . '].getAttribute(\'data-icon\'); ';
193 }
194 $onClick .= implode('', $parameterArray['fieldChangeFunc']);
195 $onClick .= 'this.blur();return false;';
196 $selectIcons[] = array(
197 'title' => $title,
198 'icon' => $icon,
199 'index' => $selectItemCounter,
200 'onClick' => $onClick
201 );
202 }
203 $selectItemCounter++;
204 }
205
206 }
207
208 // No-matching-value:
209 if ($parameterArray['itemFormElValue'] && $noMatchingValue && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
210 $noMatchingLabel = @sprintf($noMatchingLabel, $parameterArray['itemFormElValue']);
211 $options = '<option value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" selected="selected">' . htmlspecialchars($noMatchingLabel) . '</option>';
212 } elseif (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
213 $selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
214 }
215
216 // Process groups
217 foreach ($selectItemGroups as $selectItemGroup) {
218 $optionGroup = is_array($selectItemGroup['header']);
219 $options .= ($optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', FALSE) . '">' : '');
220 if (is_array($selectItemGroup['items'])) {
221 foreach ($selectItemGroup['items'] as $item) {
222 $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' .
223 htmlspecialchars($item['icon']) . '"'
224 . ($item['selected'] ? ' selected="selected"' : '') . '>' . $item['title'] . '</option>';
225 }
226 }
227 $options .= ($optionGroup ? '</optgroup>' : '');
228 }
229
230 // Create item form fields:
231 $sOnChange = 'if (this.options[this.selectedIndex].value==\'--div--\') {this.selectedIndex=' . $selectedIndex . ';} ';
232 if ($config['iconsInOptionTags']) {
233 $sOnChange .= 'document.getElementById(' . GeneralUtility::quoteJSvalue($selectId . '_icon') . ').innerHTML = this.options[this.selectedIndex].getAttribute(\'data-icon\'); ';
234 }
235 $sOnChange .= implode('', $parameterArray['fieldChangeFunc']);
236
237 // Add icons in option tags
238 $prepend = '';
239 $append = '';
240 if ($config['iconsInOptionTags']) {
241 $prepend = '<div class="input-group"><div id="' . $selectId . '_icon" class="input-group-addon input-group-icon t3js-formengine-select-prepend">' . $selectedIcon . '</div>';
242 $append = '</div>';
243 }
244
245 // Build the element
246 $out .= '
247 <div class="form-control-wrap">
248 ' . $prepend . '
249 <select'
250 . ' id="' . $selectId . '"'
251 . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
252 . ' class="form-control form-control-adapt"'
253 . ($size ? ' size="' . $size . '"' : '')
254 . ' onchange="' . htmlspecialchars($sOnChange) . '"'
255 . $parameterArray['onFocus']
256 . ($disabled ? ' disabled="disabled"' : '')
257 . '>
258 ' . $options . '
259 </select>
260 ' . $append . '
261 </div>';
262
263 // Create icon table:
264 if (count($selectIcons) && !$config['noIconsBelowSelect']) {
265 $selectIconColumns = (int)$config['selicon_cols'];
266 if (!$selectIconColumns) {
267 $selectIconColumns = count($selectIcons);
268 }
269 $selectIconColumns = ($selectIconColumns > 12 ? 12 : $selectIconColumns);
270 $selectIconRows = ceil(count($selectIcons) / $selectIconColumns);
271 $selectIcons = array_pad($selectIcons, $selectIconRows * $selectIconColumns, '');
272 $out .= '<div class="table-fit table-fit-inline-block"><table class="table table-condensed table-white table-center"><tbody><tr>';
273 for ($selectIconCount = 0; $selectIconCount < count($selectIcons); $selectIconCount++) {
274 if ($selectIconCount % $selectIconColumns === 0 && $selectIconCount !== 0) {
275 $out .= '</tr><tr>';
276 }
277 $out .= '<td>';
278 if (is_array($selectIcons[$selectIconCount])) {
279 $out .= (!$onlySelectedIconShown ? '<a href="#" title="' . $selectIcons[$selectIconCount]['title'] . '" onClick="' . htmlspecialchars($selectIcons[$selectIconCount]['onClick']) . '">' : '');
280 $out .= $selectIcons[$selectIconCount]['icon'];
281 $out .= (!$onlySelectedIconShown ? '</a>' : '');
282 }
283 $out .= '</td>';
284 }
285 $out .= '</tr></tbody></table></div>';
286 }
287
288 return $out;
289 }
290
291 }