cbd325accd641a8f7f17f01218692fa65a667078
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / SelectElement.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\IconUtility;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22
23 /**
24 * Generation of TCEform elements of the type "select"
25 */
26 class SelectElement extends AbstractFormElement {
27
28 /**
29 * This will render a selector box element, or possibly a special construction with two selector boxes.
30 * That depends on configuration.
31 *
32 * @param string $table The table name of the record
33 * @param string $field The field name which this element is supposed to edit
34 * @param array $row The record data array where the value(s) for the field can be found
35 * @param array $additionalInformation An array with additional configuration options.
36 * @return string The HTML code for the TCEform field
37 */
38 public function render($table, $field, $row, &$additionalInformation) {
39 // Field configuration from TCA:
40 $config = $additionalInformation['fieldConf']['config'];
41 $disabled = '';
42 if ($this->formEngine->renderReadonly || $config['readOnly']) {
43 $disabled = ' disabled="disabled"';
44 }
45 // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
46 $specConf = $this->formEngine->getSpecConfFromString($additionalInformation['extra'], $additionalInformation['fieldConf']['defaultExtras']);
47 $selItems = $this->getSelectItems($table, $field, $row, $additionalInformation);
48
49 // Creating the label for the "No Matching Value" entry.
50 $nMV_label = isset($additionalInformation['fieldTSConfig']['noMatchingValue_label'])
51 ? $this->formEngine->sL($additionalInformation['fieldTSConfig']['noMatchingValue_label'])
52 : '[ ' . $this->formEngine->getLL('l_noMatchingValue') . ' ]';
53 // Prepare some values:
54 $maxitems = (int)$config['maxitems'];
55 // If a SINGLE selector box...
56 if ($maxitems <= 1 && $config['renderMode'] !== 'tree') {
57 $item = $this->getSingleField_typeSelect_single($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
58 } elseif ($config['renderMode'] === 'checkbox') {
59 // Checkbox renderMode
60 $item = $this->getSingleField_typeSelect_checkbox($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
61 } elseif ($config['renderMode'] === 'singlebox') {
62 // Single selector box renderMode
63 $item = $this->getSingleField_typeSelect_singlebox($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
64 } elseif ($config['renderMode'] === 'tree') {
65 // Tree renderMode
66 $treeClass = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Form\Element\TreeElement::class, $this->formEngine);
67 $item = $treeClass->renderField($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
68 // Register the required number of elements
69 $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
70 $this->formEngine->registerRequiredProperty('range', $additionalInformation['itemFormElName'], array($minitems, $maxitems, 'imgName' => $table . '_' . $row['uid'] . '_' . $field));
71 } else {
72 // Traditional multiple selector box:
73 $item = $this->getSingleField_typeSelect_multiple($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
74 }
75 // Wizards:
76 if (!$disabled) {
77 $altItem = '<input type="hidden" name="' . $additionalInformation['itemFormElName'] . '" value="' . htmlspecialchars($additionalInformation['itemFormElValue']) . '" />';
78 $item = $this->formEngine->renderWizards(array($item, $altItem), $config['wizards'], $table, $row, $field, $additionalInformation, $additionalInformation['itemFormElName'], $specConf);
79 }
80 return $item;
81 }
82
83 /**
84 * Creates a multiple-selector box (two boxes, side-by-side)
85 * (Render function for getSingleField_typeSelect())
86 *
87 * @param string $table See getSingleField_typeSelect()
88 * @param string $field See getSingleField_typeSelect()
89 * @param array $row See getSingleField_typeSelect()
90 * @param array $PA See getSingleField_typeSelect()
91 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
92 * @param array $selItems Items available for selection
93 * @param string $nMV_label Label for no-matching-value
94 * @return string The HTML code for the item
95 * @see getSingleField_typeSelect()
96 */
97 public function getSingleField_typeSelect_multiple($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
98 $item = '';
99 $disabled = '';
100 if ($this->formEngine->renderReadonly || $config['readOnly']) {
101 $disabled = ' disabled="disabled"';
102 }
103 // Setting this hidden field (as a flag that JavaScript can read out)
104 if (!$disabled) {
105 $item .= '<input type="hidden" name="' . $PA['itemFormElName'] . '_mul" value="' . ($config['multiple'] ? 1 : 0) . '" />';
106 }
107 // Set max and min items:
108 $maxitems = MathUtility::forceIntegerInRange($config['maxitems'], 0);
109 if (!$maxitems) {
110 $maxitems = 100000;
111 }
112 $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
113 // Register the required number of elements:
114 $this->formEngine->registerRequiredProperty('range', $PA['itemFormElName'], array($minitems, $maxitems, 'imgName' => $table . '_' . $row['uid'] . '_' . $field));
115 // Get "removeItems":
116 $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
117 // Get the array with selected items:
118 $itemArray = GeneralUtility::trimExplode(',', $PA['itemFormElValue'], TRUE);
119
120 // Possibly filter some items:
121 $itemArray = GeneralUtility::keepItemsInArray(
122 $itemArray,
123 $PA['fieldTSConfig']['keepItems'],
124 function ($value) {
125 $parts = explode('|', $value, 2);
126 return rawurldecode($parts[0]);
127 }
128 );
129
130 // Perform modification of the selected items array:
131 foreach ($itemArray as $tk => $tv) {
132 $tvP = explode('|', $tv, 2);
133 $evalValue = $tvP[0];
134 $isRemoved = in_array($evalValue, $removeItems)
135 || $config['form_type'] == 'select' && $config['authMode']
136 && !$this->getBackendUserAuthentication()->checkAuthMode($table, $field, $evalValue, $config['authMode']);
137 if ($isRemoved && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
138 $tvP[1] = rawurlencode(@sprintf($nMV_label, $evalValue));
139 } elseif (isset($PA['fieldTSConfig']['altLabels.'][$evalValue])) {
140 $tvP[1] = rawurlencode($this->formEngine->sL($PA['fieldTSConfig']['altLabels.'][$evalValue]));
141 }
142 if ($tvP[1] == '') {
143 // Case: flexform, default values supplied, no label provided (bug #9795)
144 foreach ($selItems as $selItem) {
145 if ($selItem[1] == $tvP[0]) {
146 $tvP[1] = html_entity_decode($selItem[0]);
147 break;
148 }
149 }
150 }
151 $itemArray[$tk] = implode('|', $tvP);
152 }
153 $itemsToSelect = '';
154 $filterTextfield = '';
155 $filterSelectbox = '';
156 $size = 0;
157 if (!$disabled) {
158 // Create option tags:
159 $opt = array();
160 $styleAttrValue = '';
161 foreach ($selItems as $p) {
162 if ($config['iconsInOptionTags']) {
163 $styleAttrValue = $this->formEngine->optionTagStyle($p[2]);
164 }
165 $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"'
166 . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '')
167 . ' title="' . $p[0] . '">' . $p[0] . '</option>';
168 }
169 // Put together the selector box:
170 $selector_itemListStyle = isset($config['itemListStyle'])
171 ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"'
172 : ' style="' . $this->formEngine->defaultMultipleSelectorStyle . '"';
173 $size = (int)$config['size'];
174 $size = $config['autoSizeMax']
175 ? MathUtility::forceIntegerInRange(count($itemArray) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax'])
176 : $size;
177 $sOnChange = implode('', $PA['fieldChangeFunc']);
178
179 $multiSelectId = str_replace('.', '', uniqid('tceforms-multiselect-', TRUE));
180 $itemsToSelect = '
181 <select data-relatedfieldname="' . htmlspecialchars($PA['itemFormElName']) . '" data-exclusivevalues="'
182 . htmlspecialchars($config['exclusiveKeys']) . '" id="' . $multiSelectId . '" name="' . $PA['itemFormElName'] . '_sel" '
183 . $this->formEngine->insertDefStyle('select', 'tceforms-multiselect tceforms-itemstoselect t3-form-select-itemstoselect')
184 . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"'
185 . $PA['onFocus'] . $selector_itemListStyle . '>
186 ' . implode('
187 ', $opt) . '
188 </select>';
189
190 if ($config['enableMultiSelectFilterTextfield'] || $config['multiSelectFilterItems']) {
191 $this->formEngine->multiSelectFilterCount++;
192 $jsSelectBoxFilterName = str_replace(' ', '', ucwords(str_replace('-', ' ', GeneralUtility::strtolower($multiSelectId))));
193 $this->formEngine->additionalJS_post[] = '
194 var ' . $jsSelectBoxFilterName . ' = new TCEForms.SelectBoxFilter("' . $multiSelectId . '");
195 ';
196 }
197
198 if ($config['enableMultiSelectFilterTextfield']) {
199 // add input field for filter
200 $filterTextfield = '<input class="typo3-TCEforms-suggest-search typo3-TCEforms-multiselect-filter" id="'
201 . $multiSelectId . '_filtertextfield" value="" style="width: 104px;" />';
202 }
203
204 if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) {
205 $filterDropDownOptions = array();
206 foreach ($config['multiSelectFilterItems'] as $optionElement) {
207 $optionValue = $this->formEngine->sL(isset($optionElement[1]) && $optionElement[1] != '' ? $optionElement[1]
208 : $optionElement[0]);
209 $filterDropDownOptions[] = '<option value="' . htmlspecialchars($this->formEngine->sL($optionElement[0])) . '">'
210 . htmlspecialchars($optionValue) . '</option>';
211 }
212 $filterSelectbox = '
213 <select id="' . $multiSelectId . '_filterdropdown">
214 ' . implode('
215 ', $filterDropDownOptions) . '
216 </select>';
217 }
218 }
219 // Pass to "dbFileIcons" function:
220 $params = array(
221 'size' => $size,
222 'autoSizeMax' => MathUtility::forceIntegerInRange($config['autoSizeMax'], 0),
223 'style' => isset($config['selectedListStyle'])
224 ? ' style="' . htmlspecialchars($config['selectedListStyle']) . '"'
225 : ' style="' . $this->formEngine->defaultMultipleSelectorStyle . '"',
226 'dontShowMoveIcons' => $maxitems <= 1,
227 'maxitems' => $maxitems,
228 'info' => '',
229 'headers' => array(
230 'selector' => $this->formEngine->getLL('l_selected') . ':<br />',
231 'items' => $this->formEngine->getLL('l_items') . ': ' . $filterSelectbox . $filterTextfield . '<br />'
232 ),
233 'noBrowser' => 1,
234 'thumbnails' => $itemsToSelect,
235 'readOnly' => $disabled
236 );
237 $item .= $this->formEngine->dbFileIcons($PA['itemFormElName'], '', '', $itemArray, '', $params, $PA['onFocus']);
238 return $item;
239 }
240
241 /**
242 * Collects the items for a select field by reading the configured
243 * select items from the configuration and / or by collecting them
244 * from a foreign table.
245 *
246 * @param string $table The table name of the record
247 * @param string $fieldName The select field name
248 * @param array $row The record data array where the value(s) for the field can be found
249 * @param array $PA An array with additional configuration options.
250 * @return array
251 */
252 protected function getSelectItems($table, $fieldName, array $row, array $PA) {
253 $config = $PA['fieldConf']['config'];
254
255 // Getting the selector box items from the system
256 $selectItems = $this->formEngine->addSelectOptionsToItemArray(
257 $this->formEngine->initItemArray($PA['fieldConf']),
258 $PA['fieldConf'],
259 $this->formEngine->setTSconfig($table, $row),
260 $fieldName
261 );
262
263 // Possibly filter some items:
264 $selectItems = GeneralUtility::keepItemsInArray(
265 $selectItems,
266 $PA['fieldTSConfig']['keepItems'],
267 function ($value) {
268 return $value[1];
269 }
270 );
271
272 // Possibly add some items:
273 $selectItems = $this->formEngine->addItems($selectItems, $PA['fieldTSConfig']['addItems.']);
274
275 // Process items by a user function:
276 if (isset($config['itemsProcFunc']) && $config['itemsProcFunc']) {
277 $selectItems = $this->formEngine->procItems($selectItems, $PA['fieldTSConfig']['itemsProcFunc.'], $config, $table, $row, $fieldName);
278 }
279
280 // Possibly remove some items:
281 $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
282 foreach ($selectItems as $selectItemIndex => $selectItem) {
283
284 // Checking languages and authMode:
285 $languageDeny = FALSE;
286 $beUserAuth = $this->getBackendUserAuthentication();
287 if (
288 !empty($GLOBALS['TCA'][$table]['ctrl']['languageField'])
289 && $GLOBALS['TCA'][$table]['ctrl']['languageField'] === $fieldName
290 && !$beUserAuth->checkLanguageAccess($selectItem[1])
291 ) {
292 $languageDeny = TRUE;
293 }
294
295 $authModeDeny = FALSE;
296 if (
297 ($config['form_type'] === 'select')
298 && $config['authMode']
299 && !$beUserAuth->checkAuthMode($table, $fieldName, $selectItem[1], $config['authMode'])
300 ) {
301 $authModeDeny = TRUE;
302 }
303
304 if (in_array($selectItem[1], $removeItems) || $languageDeny || $authModeDeny) {
305 unset($selectItems[$selectItemIndex]);
306 } elseif (isset($PA['fieldTSConfig']['altLabels.'][$selectItem[1]])) {
307 $selectItems[$selectItemIndex][0] = htmlspecialchars($this->formEngine->sL($PA['fieldTSConfig']['altLabels.'][$selectItem[1]]));
308 }
309
310 // Removing doktypes with no access:
311 if (($table === 'pages' || $table === 'pages_language_overlay') && $fieldName === 'doktype') {
312 if (!($beUserAuth->isAdmin() || GeneralUtility::inList($beUserAuth->groupData['pagetypes_select'], $selectItem[1]))) {
313 unset($selectItems[$selectItemIndex]);
314 }
315 }
316 }
317
318 return $selectItems;
319 }
320
321 /**
322 * Creates a single-selector box
323 * (Render function for getSingleField_typeSelect())
324 *
325 * @param string $table See getSingleField_typeSelect()
326 * @param string $field See getSingleField_typeSelect()
327 * @param array $row See getSingleField_typeSelect()
328 * @param array $PA See getSingleField_typeSelect()
329 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
330 * @param array $selItems Items available for selection
331 * @param string $nMV_label Label for no-matching-value
332 * @return string The HTML code for the item
333 * @see getSingleField_typeSelect()
334 */
335 public function getSingleField_typeSelect_single($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
336 // check against inline uniqueness
337 $inlineParent = $this->formEngine->inline->getStructureLevel(-1);
338 $uniqueIds = NULL;
339 if (is_array($inlineParent) && $inlineParent['uid']) {
340 if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
341 $uniqueIds = $this->formEngine->inline->inlineData['unique'][$this->formEngine->inline->inlineNames['object']
342 . InlineElement::Structure_Separator . $table]['used'];
343 $PA['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,\'' . $this->formEngine->inline->inlineNames['object']
344 . InlineElement::Structure_Separator . $table . '\',\'' . $this->formEngine->inline->inlineNames['form']
345 . '\',\'' . $row['uid'] . '\');';
346 }
347 // hide uid of parent record for symmetric relations
348 if (
349 $inlineParent['config']['foreign_table'] == $table
350 && ($inlineParent['config']['foreign_field'] == $field || $inlineParent['config']['symmetric_field'] == $field)
351 ) {
352 $uniqueIds[] = $inlineParent['uid'];
353 }
354 }
355 // Initialization:
356 $c = 0;
357 $sI = 0;
358 $noMatchingValue = 1;
359 $opt = array();
360 $selicons = array();
361 $onlySelectedIconShown = 0;
362 $size = (int)$config['size'];
363 // Style set on <select/>
364 $selectedStyle = '';
365 $item = '';
366 $disabled = '';
367 if ($this->formEngine->renderReadonly || $config['readOnly']) {
368 $disabled = ' disabled="disabled"';
369 $onlySelectedIconShown = 1;
370 }
371 // Register as required if minitems is greater than zero
372 if (($minItems = MathUtility::forceIntegerInRange($config['minitems'], 0)) > 0) {
373 $this->formEngine->registerRequiredProperty('field', $table . '_' . $row['uid'] . '_' . $field, $PA['itemFormElName']);
374 }
375
376 // Icon configuration:
377 if ($config['suppress_icons'] == 'IF_VALUE_FALSE') {
378 $suppressIcons = !$PA['itemFormElValue'] ? 1 : 0;
379 } elseif ($config['suppress_icons'] == 'ONLY_SELECTED') {
380 $suppressIcons = 0;
381 $onlySelectedIconShown = 1;
382 } elseif ($config['suppress_icons']) {
383 $suppressIcons = 1;
384 } else {
385 $suppressIcons = 0;
386 }
387 // Traverse the Array of selector box items:
388 $optGroupStart = array();
389 $optGroupOpen = FALSE;
390 $classesForSelectTag = array();
391 foreach ($selItems as $p) {
392 $sM = (string)$PA['itemFormElValue'] === (string)$p[1] ? ' selected="selected"' : '';
393 if ($sM) {
394 $sI = $c;
395 $noMatchingValue = 0;
396 }
397 // Getting style attribute value (for icons):
398 $styleAttrValue = '';
399 if ($config['iconsInOptionTags']) {
400 $styleAttrValue = $this->formEngine->optionTagStyle($p[2]);
401 if ($sM) {
402 list($selectIconFile, $selectIconInfo) = $this->formEngine->getIcon($p[2]);
403 if (!empty($selectIconInfo)) {
404 $selectedStyle = ' style="background-image:url(' . $selectIconFile . ');"';
405 $classesForSelectTag[] = 'typo3-TCEforms-select-selectedItemWithBackgroundImage';
406 }
407 }
408 }
409 // Compiling the <option> tag:
410 if (!($p[1] != $PA['itemFormElValue'] && is_array($uniqueIds) && in_array($p[1], $uniqueIds))) {
411 if ($p[1] === '--div--') {
412 $optGroupStart[0] = $p[0];
413 if ($config['iconsInOptionTags']) {
414 $optGroupStart[1] = $this->formEngine->optgroupTagStyle($p[2]);
415 } else {
416 $optGroupStart[1] = $styleAttrValue;
417 }
418 } else {
419 if (count($optGroupStart)) {
420 // Closing last optgroup before next one starts
421 if ($optGroupOpen) {
422 $opt[] = '</optgroup>' . LF;
423 }
424 $opt[] = '<optgroup label="' . htmlspecialchars($optGroupStart[0], ENT_COMPAT, 'UTF-8', FALSE)
425 . '"' . ($optGroupStart[1] ? ' style="' . htmlspecialchars($optGroupStart[1]) . '"' : '')
426 . ' class="c-divider">' . LF;
427 $optGroupOpen = TRUE;
428 $c--;
429 $optGroupStart = array();
430 }
431 $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . $sM
432 . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . '>'
433 . htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE) . '</option>' . LF;
434 }
435 }
436 // If there is an icon for the selector box (rendered in selicon-table below)...:
437 // if there is an icon ($p[2]), icons should be shown, and, if only selected are visible, is it selected
438 if ($p[2] && !$suppressIcons && (!$onlySelectedIconShown || $sM)) {
439 list($selIconFile, $selIconInfo) = $this->formEngine->getIcon($p[2]);
440 $iOnClick = $this->formEngine->elName($PA['itemFormElName']) . '.selectedIndex=' . $c . '; ' . $this->formEngine->elName($PA['itemFormElName']);
441 $iOnClickOptions = $this->formEngine->elName($PA['itemFormElName']) . '.options[' . $c . ']';
442 if (empty($selIconInfo)) {
443 $iOnClick .= '.className=' . $iOnClickOptions . '.className; ';
444 } else {
445 $iOnClick .= '.style.backgroundImage=' . $iOnClickOptions . '.style.backgroundImage; ';
446 }
447 $iOnClick .= implode('', $PA['fieldChangeFunc']) . 'this.blur(); return false;';
448 $selicons[] = array(
449 (!$onlySelectedIconShown ? '<a href="#" onclick="' . htmlspecialchars($iOnClick) . '">' : '')
450 . $this->formEngine->getIconHtml($p[2], htmlspecialchars($p[0]), htmlspecialchars($p[0]))
451 . (!$onlySelectedIconShown ? '</a>' : ''),
452 $c,
453 $sM
454 );
455 }
456 $c++;
457 }
458 // Closing optgroup if open
459 if ($optGroupOpen) {
460 $opt[] = '</optgroup>';
461 }
462 // No-matching-value:
463 if ($PA['itemFormElValue'] && $noMatchingValue && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
464 $nMV_label = @sprintf($nMV_label, $PA['itemFormElValue']);
465 $opt[] = '<option value="' . htmlspecialchars($PA['itemFormElValue']) . '" selected="selected">' . htmlspecialchars($nMV_label) . '</option>';
466 }
467 // Create item form fields:
468 $sOnChange = 'if (this.options[this.selectedIndex].value==\'--div--\') {this.selectedIndex=' . $sI . ';} ' . implode('', $PA['fieldChangeFunc']);
469 if (!$disabled) {
470 // MUST be inserted before the selector - else is the value of the hiddenfield here mysteriously submitted...
471 $item .= '<input type="hidden" name="' . $PA['itemFormElName'] . '_selIconVal" value="' . htmlspecialchars($sI) . '" />';
472 }
473 if ($config['iconsInOptionTags']) {
474 $classesForSelectTag[] = 'icon-select';
475 }
476 $item .= '<select' . $selectedStyle . ' id="' . str_replace('.', '', uniqid('tceforms-select-', TRUE)) . '" name="' . $PA['itemFormElName'] . '" ' . $this->formEngine->insertDefStyle('select', implode(' ', $classesForSelectTag)) . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"' . $PA['onFocus'] . $disabled . '>';
477 $item .= implode('', $opt);
478 $item .= '</select>';
479 // Create icon table:
480 if (count($selicons) && !$config['noIconsBelowSelect']) {
481 $item .= '<div class="typo3-TCEforms-selectIcons">';
482 $selicon_cols = (int)$config['selicon_cols'];
483 if (!$selicon_cols) {
484 $selicon_cols = count($selicons);
485 }
486 $sR = ceil(count($selicons) / $selicon_cols);
487 $selicons = array_pad($selicons, $sR * $selicon_cols, '');
488 for ($sa = 0; $sa < $sR; $sa++) {
489 $item .= '<div>';
490 for ($sb = 0; $sb < $selicon_cols; $sb++) {
491 $sk = $sa * $selicon_cols + $sb;
492 $imgN = 'selIcon_' . $table . '_' . $row['uid'] . '_' . $field . '_' . $selicons[$sk][1];
493 $imgS = $selicons[$sk][2] ? $this->formEngine->backPath . 'gfx/content_selected.gif' : 'clear.gif';
494 $item .= '<span><img name="' . htmlspecialchars($imgN) . '" src="' . htmlspecialchars($imgS) . '" width="7" height="10" alt="" /></span>';
495 $item .= '<span>' . $selicons[$sk][0] . '</span>';
496 }
497 $item .= '</div>';
498 }
499 $item .= '</div>';
500 }
501 return $item;
502 }
503
504 /**
505 * Creates a checkbox list (renderMode = "checkbox")
506 * (Render function for getSingleField_typeSelect())
507 *
508 * @param string $table See getSingleField_typeSelect()
509 * @param string $field See getSingleField_typeSelect()
510 * @param array $row See getSingleField_typeSelect()
511 * @param array $PA See getSingleField_typeSelect()
512 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
513 * @param array $selItems Items available for selection
514 * @param string $nMV_label Label for no-matching-value
515 * @return string The HTML code for the item
516 * @see getSingleField_typeSelect()
517 */
518 public function getSingleField_typeSelect_checkbox($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
519 if (empty($selItems)) {
520 return '';
521 }
522 // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
523 $itemArray = array_flip($this->formEngine->extractValuesOnlyFromValueLabelList($PA['itemFormElValue']));
524 $item = '';
525 $disabled = '';
526 if ($this->formEngine->renderReadonly || $config['readOnly']) {
527 $disabled = ' disabled="disabled"';
528 }
529 // Traverse the Array of selector box items:
530 $tRows = array();
531 $c = 0;
532 $setAll = array();
533 $unSetAll = array();
534 $restoreCmd = array();
535 $sOnChange = '';
536 if (!$disabled) {
537 $sOnChange = implode('', $PA['fieldChangeFunc']);
538 // Used to accumulate the JS needed to restore the original selection.
539 foreach ($selItems as $p) {
540 // Non-selectable element:
541 if ($p[1] === '--div--') {
542 $selIcon = '';
543 if (isset($p[2]) && $p[2] != 'empty-emtpy') {
544 $selIcon = $this->formEngine->getIconHtml($p[2]);
545 }
546 $tRows[] = '
547 <tr class="c-header">
548 <td colspan="3">' . $selIcon . htmlspecialchars($p[0]) . '</td>
549 </tr>';
550 } else {
551 // Selected or not by default:
552 $sM = '';
553 if (isset($itemArray[$p[1]])) {
554 $sM = ' checked="checked"';
555 unset($itemArray[$p[1]]);
556 }
557 // Icon:
558 if (!empty($p[2])) {
559 $selIcon = $this->formEngine->getIconHtml($p[2]);
560 } else {
561 $selIcon = IconUtility::getSpriteIcon('empty-empty');
562 }
563 // Compile row:
564 $rowId = str_replace('.', '', uniqid('select_checkbox_row_', TRUE));
565 $onClickCell = $this->formEngine->elName(($PA['itemFormElName'] . '[' . $c . ']')) . '.checked=!' . $this->formEngine->elName(($PA['itemFormElName'] . '[' . $c . ']')) . '.checked;';
566 $onClick = 'this.attributes.getNamedItem("class").nodeValue = ' . $this->formEngine->elName(($PA['itemFormElName'] . '[' . $c . ']')) . '.checked ? "c-selectedItem" : "c-unselectedItem";';
567 $setAll[] = $this->formEngine->elName(($PA['itemFormElName'] . '[' . $c . ']')) . '.checked=1;';
568 $setAll[] = '$(\'' . $rowId . '\').removeClassName(\'c-unselectedItem\');$(\'' . $rowId . '\').addClassName(\'c-selectedItem\');';
569 $unSetAll[] = $this->formEngine->elName(($PA['itemFormElName'] . '[' . $c . ']')) . '.checked=0;';
570 $unSetAll[] = '$(\'' . $rowId . '\').removeClassName(\'c-selectedItem\');$(\'' . $rowId . '\').addClassName(\'c-unselectedItem\');';
571 $restoreCmd[] = $this->formEngine->elName(($PA['itemFormElName'] . '[' . $c . ']')) . '.checked=' . ($sM ? 1 : 0) . ';' . '$(\'' . $rowId . '\').removeClassName(\'c-selectedItem\');$(\'' . $rowId . '\').removeClassName(\'c-unselectedItem\');' . '$(\'' . $rowId . '\').addClassName(\'c-' . ($sM ? '' : 'un') . 'selectedItem\');';
572 // Check if some help text is available
573 // Since TYPO3 4.5 help text is expected to be an associative array
574 // with two key, "title" and "description"
575 // For the sake of backwards compatibility, we test if the help text
576 // is a string and use it as a description (this could happen if items
577 // are modified with an itemProcFunc)
578 $hasHelp = FALSE;
579 $help = '';
580 $helpArray = array();
581 if (is_array($p[3]) && count($p[3]) > 0 || !empty($p[3])) {
582 $hasHelp = TRUE;
583 if (is_array($p[3])) {
584 $helpArray = $p[3];
585 } else {
586 $helpArray['description'] = $p[3];
587 }
588 }
589 $label = htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE);
590 if ($hasHelp) {
591 $help = BackendUtility::wrapInHelp('', '', '', $helpArray);
592 }
593 $tRows[] = '
594 <tr id="' . $rowId . '" class="' . ($sM ? 'c-selectedItem' : 'c-unselectedItem')
595 . '" onclick="' . htmlspecialchars($onClick) . '" style="cursor: pointer;">
596 <td class="c-checkbox"><input type="checkbox" ' . $this->formEngine->insertDefStyle('check')
597 . ' name="' . htmlspecialchars(($PA['itemFormElName'] . '[' . $c . ']'))
598 . '" value="' . htmlspecialchars($p[1]) . '"' . $sM . ' onclick="' . htmlspecialchars($sOnChange)
599 . '"' . $PA['onFocus'] . ' /></td>
600 <td class="c-labelCell" onclick="' . htmlspecialchars($onClickCell) . '">' . $selIcon . $label . '</td>
601 <td class="c-descr" onclick="' . htmlspecialchars($onClickCell) . '">' . (empty($help) ? '' : $help) . '</td>
602 </tr>';
603 $c++;
604 }
605 }
606 }
607 // Remaining values (invalid):
608 if (count($itemArray) && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
609 foreach ($itemArray as $theNoMatchValue => $temp) {
610 // Compile <checkboxes> tag:
611 array_unshift($tRows, '
612 <tr class="c-invalidItem">
613 <td class="c-checkbox"><input type="checkbox" ' . $this->formEngine->insertDefStyle('check')
614 . ' name="' . htmlspecialchars(($PA['itemFormElName'] . '[' . $c . ']'))
615 . '" value="' . htmlspecialchars($theNoMatchValue) . '" checked="checked" onclick="' . htmlspecialchars($sOnChange) . '"'
616 . $PA['onFocus'] . $disabled . ' /></td>
617 <td class="c-labelCell">' . htmlspecialchars(@sprintf($nMV_label, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE) . '</td><td>&nbsp;</td>
618 </tr>');
619 $c++;
620 }
621 }
622 // Add an empty hidden field which will send a blank value if all items are unselected.
623 $item .= '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($PA['itemFormElName']) . '" value="" />';
624 // Remaining checkboxes will get their set-all link:
625 $tableHead = '';
626 if (count($setAll)) {
627 $tableHead = '<thead>
628 <tr class="c-header-checkbox-controls t3-row-header">
629 <td class="c-checkbox">
630 <input type="checkbox" class="checkbox" onclick="if (checked) {' . htmlspecialchars(implode('', $setAll) . '} else {' . implode('', $unSetAll)) . '}">
631 </td>
632 <td colspan="2">
633 </td>
634 </tr></thead>';
635 }
636 // Implode rows in table:
637 $item .= '
638 <table border="0" cellpadding="0" cellspacing="0" class="typo3-TCEforms-select-checkbox">' . $tableHead . '<tbody>' . implode('', $tRows) . '</tbody>
639 </table>
640 ';
641 // Add revert icon
642 if (!empty($restoreCmd)) {
643 $item .= '<a href="#" onclick="' . implode('', $restoreCmd) . ' return false;' . '">'
644 . IconUtility::getSpriteIcon('actions-edit-undo', array('title' => htmlspecialchars($this->formEngine->getLL('l_revertSelection')))) . '</a>';
645 }
646 return $item;
647 }
648
649 /**
650 * Creates a selectorbox list (renderMode = "singlebox")
651 * (Render function for getSingleField_typeSelect())
652 *
653 * @param string $table See getSingleField_typeSelect()
654 * @param string $field See getSingleField_typeSelect()
655 * @param array $row See getSingleField_typeSelect()
656 * @param array $PA See getSingleField_typeSelect()
657 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
658 * @param array $selItems Items available for selection
659 * @param string $nMV_label Label for no-matching-value
660 * @return string The HTML code for the item
661 * @see getSingleField_typeSelect()
662 */
663 public function getSingleField_typeSelect_singlebox($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
664 // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
665 $itemArray = array_flip($this->formEngine->extractValuesOnlyFromValueLabelList($PA['itemFormElValue']));
666 $item = '';
667 $disabled = '';
668 if ($this->formEngine->renderReadonly || $config['readOnly']) {
669 $disabled = ' disabled="disabled"';
670 }
671 // Traverse the Array of selector box items:
672 $opt = array();
673 // Used to accumulate the JS needed to restore the original selection.
674 $restoreCmd = array();
675 $c = 0;
676 foreach ($selItems as $p) {
677 // Selected or not by default:
678 $sM = '';
679 if (isset($itemArray[$p[1]])) {
680 $sM = ' selected="selected"';
681 $restoreCmd[] = $this->formEngine->elName(($PA['itemFormElName'] . '[]')) . '.options[' . $c . '].selected=1;';
682 unset($itemArray[$p[1]]);
683 }
684 // Non-selectable element:
685 $nonSel = '';
686 if ((string)$p[1] === '--div--') {
687 $nonSel = ' onclick="this.selected=0;" class="c-divider"';
688 }
689 // Icon style for option tag:
690 $styleAttrValue = '';
691 if ($config['iconsInOptionTags']) {
692 $styleAttrValue = $this->formEngine->optionTagStyle($p[2]);
693 }
694 // Compile <option> tag:
695 $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . $sM . $nonSel
696 . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . '>'
697 . htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE) . '</option>';
698 $c++;
699 }
700 // Remaining values:
701 if (count($itemArray) && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
702 foreach ($itemArray as $theNoMatchValue => $temp) {
703 // Compile <option> tag:
704 array_unshift($opt, '<option value="' . htmlspecialchars($theNoMatchValue) . '" selected="selected">'
705 . htmlspecialchars(@sprintf($nMV_label, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE) . '</option>');
706 }
707 }
708 // Compile selector box:
709 $sOnChange = implode('', $PA['fieldChangeFunc']);
710 $selector_itemListStyle = isset($config['itemListStyle'])
711 ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"'
712 : ' style="' . $this->formEngine->defaultMultipleSelectorStyle . '"';
713 $size = (int)$config['size'];
714 $cssPrefix = $size === 1 ? 'tceforms-select' : 'tceforms-multiselect';
715 $size = $config['autoSizeMax']
716 ? MathUtility::forceIntegerInRange(count($selItems) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax'])
717 : $size;
718 $selectBox = '<select id="' . str_replace('.', '', uniqid($cssPrefix, TRUE)) . '" name="' . $PA['itemFormElName'] . '[]" '
719 . $this->formEngine->insertDefStyle('select', $cssPrefix) . ($size ? ' size="' . $size . '" ' : '')
720 . ' multiple="multiple" onchange="' . htmlspecialchars($sOnChange) . '"' . $PA['onFocus']
721 . ' ' . $selector_itemListStyle . $disabled . '>
722 ' . implode('
723 ', $opt) . '
724 </select>';
725 // Add an empty hidden field which will send a blank value if all items are unselected.
726 if (!$disabled) {
727 $item .= '<input type="hidden" name="' . htmlspecialchars($PA['itemFormElName']) . '" value="" />';
728 }
729 // Put it all into a table:
730 $onClick = htmlspecialchars($this->formEngine->elName(($PA['itemFormElName'] . '[]')) . '.selectedIndex=-1;' . implode('', $restoreCmd) . ' return false;');
731 $item .= '
732 <table border="0" cellspacing="0" cellpadding="0" width="1" class="typo3-TCEforms-select-singlebox">
733 <tr>
734 <td>
735 ' . $selectBox . '
736 <br/>
737 <em>' . htmlspecialchars($this->formEngine->getLL('l_holdDownCTRL')) . '</em>
738 </td>
739 <td valign="top">
740 <a href="#" onclick="' . $onClick . '" title="' . htmlspecialchars($this->formEngine->getLL('l_revertSelection')) . '">'
741 . IconUtility::getSpriteIcon('actions-edit-undo') . '</a>
742 </td>
743 </tr>
744 </table>
745 ';
746 return $item;
747 }
748 }