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