[BUGFIX] FormEngine: Size equals to 1 if none set on multi-selects
[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\Utility\ArrayUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Utility\MathUtility;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
22 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
23
24 /**
25 * Render a widget with two boxes side by side.
26 *
27 * This is rendered for config type=select, maxitems > 1, no other renderMode set
28 */
29 class SelectMultipleSideBySideElement 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 side by side 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 // Field configuration from TCA:
47 $config = $parameterArray['fieldConf']['config'];
48 $disabled = '';
49 if ($this->isGlobalReadonly() || $config['readOnly']) {
50 $disabled = ' disabled="disabled"';
51 }
52 $this->resultArray = $this->initializeResultArray();
53 // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
54 $specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
55 $selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
56
57 // Creating the label for the "No Matching Value" entry.
58 $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
59 ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
60 : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
61
62 $html = $this->getSingleField_typeSelect_multiple($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel);
63
64 // Wizards:
65 if (!$disabled) {
66 $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
67 $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf);
68 }
69 $this->resultArray['html'] = $html;
70 return $this->resultArray;
71 }
72
73 /**
74 * Creates a multiple-selector box (two boxes, side-by-side)
75 *
76 * @param string $table See getSingleField_typeSelect()
77 * @param string $field See getSingleField_typeSelect()
78 * @param array $row See getSingleField_typeSelect()
79 * @param array $parameterArray See getSingleField_typeSelect()
80 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
81 * @param array $selItems Items available for selection
82 * @param string $noMatchingLabel Label for no-matching-value
83 * @return string The HTML code for the item
84 */
85 protected function getSingleField_typeSelect_multiple($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel) {
86 $languageService = $this->getLanguageService();
87 $item = '';
88 $disabled = '';
89 if ($this->isGlobalReadonly() || $config['readOnly']) {
90 $disabled = ' disabled="disabled"';
91 }
92 // Setting this hidden field (as a flag that JavaScript can read out)
93 if (!$disabled) {
94 $item .= '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '_mul" value="' . ($config['multiple'] ? 1 : 0) . '" />';
95 }
96 // Set max and min items:
97 $maxitems = MathUtility::forceIntegerInRange($config['maxitems'], 0);
98 if (!$maxitems) {
99 $maxitems = 100000;
100 }
101 $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
102 // Register the required number of elements:
103 $this->resultArray['requiredElements'][$parameterArray['itemFormElName']] = array(
104 $minitems,
105 $maxitems,
106 'imgName' => $table . '_' . $row['uid'] . '_' . $field
107 );
108 $tabAndInlineStack = $this->globalOptions['tabAndInlineStack'];
109 if (!empty($tabAndInlineStack) && preg_match('/^(.+\\])\\[(\\w+)\\]$/', $parameterArray['itemFormElName'], $match)) {
110 array_shift($match);
111 $this->resultArray['requiredNested'][$parameterArray['itemFormElName']] = array(
112 'parts' => $match,
113 'level' => $tabAndInlineStack,
114 );
115 }
116 // Get "removeItems":
117 $removeItems = GeneralUtility::trimExplode(',', $parameterArray['fieldTSConfig']['removeItems'], TRUE);
118 // Get the array with selected items:
119 $itemArray = GeneralUtility::trimExplode(',', $parameterArray['itemFormElValue'], TRUE);
120
121 // Possibly filter some items:
122 $itemArray = ArrayUtility::keepItemsInArray(
123 $itemArray,
124 $parameterArray['fieldTSConfig']['keepItems'],
125 function ($value) {
126 $parts = explode('|', $value, 2);
127 return rawurldecode($parts[0]);
128 }
129 );
130
131 // Perform modification of the selected items array:
132 foreach ($itemArray as $tk => $tv) {
133 $tvP = explode('|', $tv, 2);
134 $evalValue = $tvP[0];
135 $isRemoved = in_array($evalValue, $removeItems)
136 || $config['type'] == 'select' && $config['authMode']
137 && !$this->getBackendUserAuthentication()->checkAuthMode($table, $field, $evalValue, $config['authMode']);
138 if ($isRemoved && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
139 $tvP[1] = rawurlencode(@sprintf($noMatchingLabel, $evalValue));
140 } else {
141 if (isset($parameterArray['fieldTSConfig']['altLabels.'][$evalValue])) {
142 $tvP[1] = rawurlencode($languageService->sL($parameterArray['fieldTSConfig']['altLabels.'][$evalValue]));
143 }
144 if (isset($parameterArray['fieldTSConfig']['altIcons.'][$evalValue])) {
145 $tvP[2] = $parameterArray['fieldTSConfig']['altIcons.'][$evalValue];
146 }
147 }
148 if ($tvP[1] == '') {
149 // Case: flexform, default values supplied, no label provided (bug #9795)
150 foreach ($selItems as $selItem) {
151 if ($selItem[1] == $tvP[0]) {
152 $tvP[1] = html_entity_decode($selItem[0]);
153 break;
154 }
155 }
156 }
157 $itemArray[$tk] = implode('|', $tvP);
158 }
159
160 // size must be at least two, as there are always maxitems > 1 (see parent function)
161 if (isset($config['size'])) {
162 $size = (int)$config['size'];
163 } else {
164 $size = 2;
165 }
166 $size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($itemArray) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size;
167
168 $itemsToSelect = '';
169 $filterTextfield = '';
170 $filterSelectbox = '';
171 if (!$disabled) {
172 // Create option tags:
173 $opt = array();
174 $styleAttrValue = '';
175 foreach ($selItems as $p) {
176 if ($config['iconsInOptionTags']) {
177 $styleAttrValue = FormEngineUtility::optionTagStyle($p[2]);
178 }
179 $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"'
180 . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '')
181 . ' title="' . $p[0] . '">' . $p[0] . '</option>';
182 }
183 // Put together the selector box:
184 $selector_itemListStyle = isset($config['itemListStyle'])
185 ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"'
186 : '';
187 $sOnChange = implode('', $parameterArray['fieldChangeFunc']);
188
189 $multiSelectId = str_replace('.', '', uniqid('tceforms-multiselect-', TRUE));
190 $itemsToSelect = '
191 <select data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '" data-exclusivevalues="'
192 . htmlspecialchars($config['exclusiveKeys']) . '" id="' . $multiSelectId . '" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '_sel" '
193 . ' class="form-control t3js-formengine-select-itemstoselect" '
194 . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"'
195 . $parameterArray['onFocus'] . $selector_itemListStyle . '>
196 ' . implode('
197 ', $opt) . '
198 </select>';
199
200 // enable filter functionality via a text field
201 if ($config['enableMultiSelectFilterTextfield']) {
202 $filterTextfield = '
203 <span class="input-group input-group-sm">
204 <span class="input-group-addon">
205 <span class="fa fa-filter"></span>
206 </span>
207 <input class="t3js-formengine-multiselect-filter-textfield form-control" value="" />
208 </span>';
209 }
210
211 // enable filter functionality via a select
212 if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) {
213 $filterDropDownOptions = array();
214 foreach ($config['multiSelectFilterItems'] as $optionElement) {
215 $optionValue = $languageService->sL(isset($optionElement[1]) && $optionElement[1] != '' ? $optionElement[1]
216 : $optionElement[0]);
217 $filterDropDownOptions[] = '<option value="' . htmlspecialchars($languageService->sL($optionElement[0])) . '">'
218 . htmlspecialchars($optionValue) . '</option>';
219 }
220 $filterSelectbox = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">
221 ' . implode('
222 ', $filterDropDownOptions) . '
223 </select>';
224 }
225 }
226
227 if (!empty(trim($filterSelectbox)) && !empty(trim($filterTextfield))) {
228 $filterSelectbox = '<div class="form-multigroup-item form-multigroup-element">' . $filterSelectbox . '</div>';
229 $filterTextfield = '<div class="form-multigroup-item form-multigroup-element">' . $filterTextfield . '</div>';
230 $selectBoxFilterContents = '<div class="t3js-formengine-multiselect-filter-container form-multigroup-wrap">' . $filterSelectbox . $filterTextfield . '</div>';
231 } else {
232 $selectBoxFilterContents = trim($filterSelectbox . ' ' . $filterTextfield);
233 }
234
235 // Pass to "dbFileIcons" function:
236 $params = array(
237 'size' => $size,
238 'autoSizeMax' => MathUtility::forceIntegerInRange($config['autoSizeMax'], 0),
239 'style' => isset($config['selectedListStyle'])
240 ? ' style="' . htmlspecialchars($config['selectedListStyle']) . '"'
241 : '',
242 'dontShowMoveIcons' => $maxitems <= 1,
243 'maxitems' => $maxitems,
244 'info' => '',
245 'headers' => array(
246 'selector' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.selected'),
247 'items' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.items'),
248 'selectorbox' => $selectBoxFilterContents,
249 ),
250 'noBrowser' => 1,
251 'rightbox' => $itemsToSelect,
252 'readOnly' => $disabled
253 );
254 $item .= $this->dbFileIcons($parameterArray['itemFormElName'], '', '', $itemArray, '', $params, $parameterArray['onFocus']);
255 return $item;
256 }
257
258 /**
259 * @return BackendUserAuthentication
260 */
261 protected function getBackendUserAuthentication() {
262 return $GLOBALS['BE_USER'];
263 }
264
265 }