37f58a7d001f2dbe62e854a4f80418b8cfd60495
[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\ArrayUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Utility\MathUtility;
20 use TYPO3\CMS\Backend\Utility\IconUtility;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
22 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
23
24 /**
25 * Generation of TCEform elements of the type "select"
26 */
27 class SelectElement extends AbstractFormElement {
28
29 /**
30 * If this value is set during traversal and the traversal chain can
31 * not be walked to the end this value will be returned instead.
32 *
33 * @var string
34 */
35 protected $alternativeFieldValue;
36
37 /**
38 * If this is TRUE the alternative field value will be used even if
39 * the detected field value is not empty.
40 *
41 * @var bool
42 */
43 protected $forceAlternativeFieldValueUse = FALSE;
44
45 /**
46 * The row data of the record that is currently traversed.
47 *
48 * @var array
49 */
50 protected $currentRow;
51
52 /**
53 * Name of the table that is currently traversed.
54 *
55 * @var string
56 */
57 protected $currentTable;
58
59 /**
60 * This will render a selector box element, or possibly a special construction with two selector boxes.
61 * That depends on configuration.
62 *
63 * @param string $table The table name of the record
64 * @param string $field The field name which this element is supposed to edit
65 * @param array $row The record data array where the value(s) for the field can be found
66 * @param array $additionalInformation An array with additional configuration options.
67 * @return string The HTML code for the TCEform field
68 */
69 public function render($table, $field, $row, &$additionalInformation) {
70 // Field configuration from TCA:
71 $config = $additionalInformation['fieldConf']['config'];
72 $disabled = '';
73 if ($this->isRenderReadonly() || $config['readOnly']) {
74 $disabled = ' disabled="disabled"';
75 }
76 // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
77 $specConf = BackendUtility::getSpecConfParts($additionalInformation['extra'], $additionalInformation['fieldConf']['defaultExtras']);
78 $selItems = $this->getSelectItems($table, $field, $row, $additionalInformation);
79
80 // Creating the label for the "No Matching Value" entry.
81 $nMV_label = isset($additionalInformation['fieldTSConfig']['noMatchingValue_label'])
82 ? $this->getLanguageService()->sL($additionalInformation['fieldTSConfig']['noMatchingValue_label'])
83 : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
84 // Prepare some values:
85 $maxitems = (int)$config['maxitems'];
86 // If a SINGLE selector box...
87 if ($maxitems <= 1 && $config['renderMode'] !== 'tree') {
88 $item = $this->getSingleField_typeSelect_single($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
89 } elseif ($config['renderMode'] === 'checkbox') {
90 // Checkbox renderMode
91 $item = $this->getSingleField_typeSelect_checkbox($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
92 } elseif ($config['renderMode'] === 'singlebox') {
93 // Single selector box renderMode
94 $item = $this->getSingleField_typeSelect_singlebox($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
95 } elseif ($config['renderMode'] === 'tree') {
96 // Tree renderMode
97 $treeClass = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Form\Element\TreeElement::class, $this->formEngine);
98 $item = $treeClass->renderField($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
99 // Register the required number of elements
100 $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
101 $this->formEngine->registerRequiredProperty('range', $additionalInformation['itemFormElName'], array($minitems, $maxitems, 'imgName' => $table . '_' . $row['uid'] . '_' . $field));
102 } else {
103 // Traditional multiple selector box:
104 $item = $this->getSingleField_typeSelect_multiple($table, $field, $row, $additionalInformation, $config, $selItems, $nMV_label);
105 }
106 // Wizards:
107 if (!$disabled) {
108 $altItem = '<input type="hidden" name="' . $additionalInformation['itemFormElName'] . '" value="' . htmlspecialchars($additionalInformation['itemFormElValue']) . '" />';
109 $item = $this->formEngine->renderWizards(array($item, $altItem), $config['wizards'], $table, $row, $field, $additionalInformation, $additionalInformation['itemFormElName'], $specConf);
110 }
111 return $item;
112 }
113
114 /**
115 * Creates a multiple-selector box (two boxes, side-by-side)
116 * (Render function for getSingleField_typeSelect())
117 *
118 * @param string $table See getSingleField_typeSelect()
119 * @param string $field See getSingleField_typeSelect()
120 * @param array $row See getSingleField_typeSelect()
121 * @param array $PA See getSingleField_typeSelect()
122 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
123 * @param array $selItems Items available for selection
124 * @param string $nMV_label Label for no-matching-value
125 * @return string The HTML code for the item
126 * @see getSingleField_typeSelect()
127 */
128 public function getSingleField_typeSelect_multiple($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
129 $languageService = $this->getLanguageService();
130 $item = '';
131 $disabled = '';
132 if ($this->isRenderReadonly() || $config['readOnly']) {
133 $disabled = ' disabled="disabled"';
134 }
135 // Setting this hidden field (as a flag that JavaScript can read out)
136 if (!$disabled) {
137 $item .= '<input type="hidden" name="' . $PA['itemFormElName'] . '_mul" value="' . ($config['multiple'] ? 1 : 0) . '" />';
138 }
139 // Set max and min items:
140 $maxitems = MathUtility::forceIntegerInRange($config['maxitems'], 0);
141 if (!$maxitems) {
142 $maxitems = 100000;
143 }
144 $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
145 // Register the required number of elements:
146 $this->formEngine->registerRequiredProperty('range', $PA['itemFormElName'], array($minitems, $maxitems, 'imgName' => $table . '_' . $row['uid'] . '_' . $field));
147 // Get "removeItems":
148 $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
149 // Get the array with selected items:
150 $itemArray = GeneralUtility::trimExplode(',', $PA['itemFormElValue'], TRUE);
151
152 // Possibly filter some items:
153 $itemArray = ArrayUtility::keepItemsInArray(
154 $itemArray,
155 $PA['fieldTSConfig']['keepItems'],
156 function ($value) {
157 $parts = explode('|', $value, 2);
158 return rawurldecode($parts[0]);
159 }
160 );
161
162 // Perform modification of the selected items array:
163 foreach ($itemArray as $tk => $tv) {
164 $tvP = explode('|', $tv, 2);
165 $evalValue = $tvP[0];
166 $isRemoved = in_array($evalValue, $removeItems)
167 || $config['form_type'] == 'select' && $config['authMode']
168 && !$this->getBackendUserAuthentication()->checkAuthMode($table, $field, $evalValue, $config['authMode']);
169 if ($isRemoved && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
170 $tvP[1] = rawurlencode(@sprintf($nMV_label, $evalValue));
171 } else {
172 if (isset($PA['fieldTSConfig']['altLabels.'][$evalValue])) {
173 $tvP[1] = rawurlencode($languageService->sL($PA['fieldTSConfig']['altLabels.'][$evalValue]));
174 }
175 if (isset($PA['fieldTSConfig']['altIcons.'][$evalValue])) {
176 $tvP[2] = $PA['fieldTSConfig']['altIcons.'][$evalValue];
177 }
178 }
179 if ($tvP[1] == '') {
180 // Case: flexform, default values supplied, no label provided (bug #9795)
181 foreach ($selItems as $selItem) {
182 if ($selItem[1] == $tvP[0]) {
183 $tvP[1] = html_entity_decode($selItem[0]);
184 break;
185 }
186 }
187 }
188 $itemArray[$tk] = implode('|', $tvP);
189 }
190 $itemsToSelect = '';
191 $filterTextfield = '';
192 $filterSelectbox = '';
193 $size = 0;
194 if (!$disabled) {
195 // Create option tags:
196 $opt = array();
197 $styleAttrValue = '';
198 foreach ($selItems as $p) {
199 if ($config['iconsInOptionTags']) {
200 $styleAttrValue = $this->formEngine->optionTagStyle($p[2]);
201 }
202 $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"'
203 . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '')
204 . ' title="' . $p[0] . '">' . $p[0] . '</option>';
205 }
206 // Put together the selector box:
207 $selector_itemListStyle = isset($config['itemListStyle'])
208 ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"'
209 : ' style="' . $this->formEngine->defaultMultipleSelectorStyle . '"';
210 $size = (int)$config['size'];
211 $size = $config['autoSizeMax']
212 ? MathUtility::forceIntegerInRange(count($itemArray) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax'])
213 : $size;
214 $sOnChange = implode('', $PA['fieldChangeFunc']);
215
216 $multiSelectId = str_replace('.', '', uniqid('tceforms-multiselect-', TRUE));
217 $itemsToSelect = '
218 <select data-relatedfieldname="' . htmlspecialchars($PA['itemFormElName']) . '" data-exclusivevalues="'
219 . htmlspecialchars($config['exclusiveKeys']) . '" id="' . $multiSelectId . '" name="' . $PA['itemFormElName'] . '_sel" '
220 . ' class="' . $this->cssClassTypeElementPrefix . 'select tceforms-multiselect tceforms-itemstoselect t3-form-select-itemstoselect" '
221 . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"'
222 . $PA['onFocus'] . $selector_itemListStyle . '>
223 ' . implode('
224 ', $opt) . '
225 </select>';
226
227 // enable filter functionality via a text field
228 if ($config['enableMultiSelectFilterTextfield']) {
229 $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>';
230 }
231
232 // enable filter functionality via a select
233 if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) {
234 $filterDropDownOptions = array();
235 foreach ($config['multiSelectFilterItems'] as $optionElement) {
236 $optionValue = $languageService->sL(isset($optionElement[1]) && $optionElement[1] != '' ? $optionElement[1]
237 : $optionElement[0]);
238 $filterDropDownOptions[] = '<option value="' . htmlspecialchars($languageService->sL($optionElement[0])) . '">'
239 . htmlspecialchars($optionValue) . '</option>';
240 }
241 $filterSelectbox = '<select class="t3-form-multiselect-filter-dropdown form-control">
242 ' . implode('
243 ', $filterDropDownOptions) . '
244 </select>';
245 }
246 }
247
248 $selectBoxFilterContents = trim($filterSelectbox . $filterTextfield);
249 if (!empty($selectBoxFilterContents)) {
250 $selectBoxFilterContents = '<div class="form-inline"><div class="t3-form-multiselect-filter-container form-group-sm pull-right">' . $selectBoxFilterContents . '</div></div>';
251 }
252
253 // Pass to "dbFileIcons" function:
254 $params = array(
255 'size' => $size,
256 'autoSizeMax' => MathUtility::forceIntegerInRange($config['autoSizeMax'], 0),
257 'style' => isset($config['selectedListStyle'])
258 ? ' style="' . htmlspecialchars($config['selectedListStyle']) . '"'
259 : ' style="' . $this->formEngine->defaultMultipleSelectorStyle . '"',
260 'dontShowMoveIcons' => $maxitems <= 1,
261 'maxitems' => $maxitems,
262 'info' => '',
263 'headers' => array(
264 'selector' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.selected') . ':<br />',
265 'items' => '<div class="pull-left">' . $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.items') . ':</div>' . $selectBoxFilterContents
266 ),
267 'noBrowser' => 1,
268 'thumbnails' => $itemsToSelect,
269 'readOnly' => $disabled
270 );
271 $item .= $this->formEngine->dbFileIcons($PA['itemFormElName'], '', '', $itemArray, '', $params, $PA['onFocus']);
272 return $item;
273 }
274
275 /**
276 * Collects the items for a select field by reading the configured
277 * select items from the configuration and / or by collecting them
278 * from a foreign table.
279 *
280 * @param string $table The table name of the record
281 * @param string $fieldName The select field name
282 * @param array $row The record data array where the value(s) for the field can be found
283 * @param array $PA An array with additional configuration options.
284 * @return array
285 */
286 protected function getSelectItems($table, $fieldName, array $row, array $PA) {
287 $config = $PA['fieldConf']['config'];
288
289 // Getting the selector box items from the system
290 $selectItems = $this->formEngine->addSelectOptionsToItemArray(
291 $this->formEngine->initItemArray($PA['fieldConf']),
292 $PA['fieldConf'],
293 $this->formEngine->setTSconfig($table, $row),
294 $fieldName
295 );
296
297 // Possibly filter some items:
298 $selectItems = ArrayUtility::keepItemsInArray(
299 $selectItems,
300 $PA['fieldTSConfig']['keepItems'],
301 function ($value) {
302 return $value[1];
303 }
304 );
305
306 // Possibly add some items:
307 $selectItems = $this->formEngine->addItems($selectItems, $PA['fieldTSConfig']['addItems.']);
308
309 // Process items by a user function:
310 if (isset($config['itemsProcFunc']) && $config['itemsProcFunc']) {
311 $selectItems = $this->formEngine->procItems($selectItems, $PA['fieldTSConfig']['itemsProcFunc.'], $config, $table, $row, $fieldName);
312 }
313
314 // Possibly remove some items:
315 $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
316 foreach ($selectItems as $selectItemIndex => $selectItem) {
317
318 // Checking languages and authMode:
319 $languageDeny = FALSE;
320 $beUserAuth = $this->getBackendUserAuthentication();
321 if (
322 !empty($GLOBALS['TCA'][$table]['ctrl']['languageField'])
323 && $GLOBALS['TCA'][$table]['ctrl']['languageField'] === $fieldName
324 && !$beUserAuth->checkLanguageAccess($selectItem[1])
325 ) {
326 $languageDeny = TRUE;
327 }
328
329 $authModeDeny = FALSE;
330 if (
331 ($config['form_type'] === 'select')
332 && $config['authMode']
333 && !$beUserAuth->checkAuthMode($table, $fieldName, $selectItem[1], $config['authMode'])
334 ) {
335 $authModeDeny = TRUE;
336 }
337
338 if (in_array($selectItem[1], $removeItems) || $languageDeny || $authModeDeny) {
339 unset($selectItems[$selectItemIndex]);
340 } elseif (isset($PA['fieldTSConfig']['altLabels.'][$selectItem[1]])) {
341 $selectItems[$selectItemIndex][0] = htmlspecialchars($this->getLanguageService()->sL($PA['fieldTSConfig']['altLabels.'][$selectItem[1]]));
342 }
343
344 // Removing doktypes with no access:
345 if (($table === 'pages' || $table === 'pages_language_overlay') && $fieldName === 'doktype') {
346 if (!($beUserAuth->isAdmin() || GeneralUtility::inList($beUserAuth->groupData['pagetypes_select'], $selectItem[1]))) {
347 unset($selectItems[$selectItemIndex]);
348 }
349 }
350 }
351
352 return $selectItems;
353 }
354
355 /**
356 * Creates a single-selector box
357 * (Render function for getSingleField_typeSelect())
358 *
359 * @param string $table See getSingleField_typeSelect()
360 * @param string $field See getSingleField_typeSelect()
361 * @param array $row See getSingleField_typeSelect()
362 * @param array $PA See getSingleField_typeSelect()
363 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
364 * @param array $selItems Items available for selection
365 * @param string $nMV_label Label for no-matching-value
366 * @return string The HTML code for the item
367 * @see getSingleField_typeSelect()
368 */
369 public function getSingleField_typeSelect_single($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
370 // check against inline uniqueness
371 $inlineParent = $this->formEngine->inline->getStructureLevel(-1);
372 $uniqueIds = NULL;
373 if (is_array($inlineParent) && $inlineParent['uid']) {
374 if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
375 $uniqueIds = $this->formEngine->inline->inlineData['unique'][$this->formEngine->inline->inlineNames['object']
376 . InlineElement::Structure_Separator . $table]['used'];
377 $PA['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,\'' . $this->formEngine->inline->inlineNames['object']
378 . InlineElement::Structure_Separator . $table . '\',\'' . $this->formEngine->inline->inlineNames['form']
379 . '\',\'' . $row['uid'] . '\');';
380 }
381 // hide uid of parent record for symmetric relations
382 if (
383 $inlineParent['config']['foreign_table'] == $table
384 && ($inlineParent['config']['foreign_field'] == $field || $inlineParent['config']['symmetric_field'] == $field)
385 ) {
386 $uniqueIds[] = $inlineParent['uid'];
387 }
388 }
389 // Initialization:
390 $c = 0;
391 $sI = 0;
392 $noMatchingValue = 1;
393 $opt = array();
394 $selicons = array();
395 $onlySelectedIconShown = 0;
396 $size = (int)$config['size'];
397 // Style set on <select/>
398 $selectedStyle = '';
399 $item = '';
400 $disabled = '';
401 if ($this->isRenderReadonly() || $config['readOnly']) {
402 $disabled = ' disabled="disabled"';
403 $onlySelectedIconShown = 1;
404 }
405 // Register as required if minitems is greater than zero
406 if (($minItems = MathUtility::forceIntegerInRange($config['minitems'], 0)) > 0) {
407 $this->formEngine->registerRequiredProperty('field', $table . '_' . $row['uid'] . '_' . $field, $PA['itemFormElName']);
408 }
409
410 // Icon configuration:
411 if ($config['suppress_icons'] == 'IF_VALUE_FALSE') {
412 $suppressIcons = !$PA['itemFormElValue'] ? 1 : 0;
413 } elseif ($config['suppress_icons'] == 'ONLY_SELECTED') {
414 $suppressIcons = 0;
415 $onlySelectedIconShown = 1;
416 } elseif ($config['suppress_icons']) {
417 $suppressIcons = 1;
418 } else {
419 $suppressIcons = 0;
420 }
421 // Traverse the Array of selector box items:
422 $optGroupStart = array();
423 $optGroupOpen = FALSE;
424 $classesForSelectTag = array();
425 foreach ($selItems as $p) {
426 $sM = (string)$PA['itemFormElValue'] === (string)$p[1] ? ' selected="selected"' : '';
427 if ($sM) {
428 $sI = $c;
429 $noMatchingValue = 0;
430 }
431 // Getting style attribute value (for icons):
432 $styleAttrValue = '';
433 if ($config['iconsInOptionTags']) {
434 $styleAttrValue = $this->formEngine->optionTagStyle($p[2]);
435 if ($sM) {
436 list($selectIconFile, $selectIconInfo) = $this->formEngine->getIcon($p[2]);
437 if (!empty($selectIconInfo)) {
438 $selectedStyle = ' style="background-image:url(' . $selectIconFile . ');"';
439 $classesForSelectTag[] = 'typo3-TCEforms-select-selectedItemWithBackgroundImage';
440 }
441 }
442 }
443 // Compiling the <option> tag:
444 if (!($p[1] != $PA['itemFormElValue'] && is_array($uniqueIds) && in_array($p[1], $uniqueIds))) {
445 if ($p[1] === '--div--') {
446 $optGroupStart[0] = $p[0];
447 if ($config['iconsInOptionTags']) {
448 $optGroupStart[1] = $this->formEngine->optgroupTagStyle($p[2]);
449 } else {
450 $optGroupStart[1] = $styleAttrValue;
451 }
452 } else {
453 if (count($optGroupStart)) {
454 // Closing last optgroup before next one starts
455 if ($optGroupOpen) {
456 $opt[] = '</optgroup>' . LF;
457 }
458 $opt[] = '<optgroup label="' . htmlspecialchars($optGroupStart[0], ENT_COMPAT, 'UTF-8', FALSE)
459 . '"' . ($optGroupStart[1] ? ' style="' . htmlspecialchars($optGroupStart[1]) . '"' : '')
460 . ' class="c-divider">' . LF;
461 $optGroupOpen = TRUE;
462 $c--;
463 $optGroupStart = array();
464 }
465 $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . $sM
466 . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . '>'
467 . htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE) . '</option>' . LF;
468 }
469 }
470 // If there is an icon for the selector box (rendered in selicon-table below)...:
471 // if there is an icon ($p[2]), icons should be shown, and, if only selected are visible, is it selected
472 if ($p[2] && !$suppressIcons && (!$onlySelectedIconShown || $sM)) {
473 list($selIconFile, $selIconInfo) = $this->formEngine->getIcon($p[2]);
474 $iOnClick = $this->formEngine->elName($PA['itemFormElName']) . '.selectedIndex=' . $c . '; ' . $this->formEngine->elName($PA['itemFormElName']);
475 $iOnClickOptions = $this->formEngine->elName($PA['itemFormElName']) . '.options[' . $c . ']';
476 if (empty($selIconInfo)) {
477 $iOnClick .= '.className=' . $iOnClickOptions . '.className; ';
478 } else {
479 $iOnClick .= '.style.backgroundImage=' . $iOnClickOptions . '.style.backgroundImage; ';
480 }
481 $iOnClick .= implode('', $PA['fieldChangeFunc']) . 'this.blur(); return false;';
482 $selicons[] = array(
483 (!$onlySelectedIconShown ? '<a href="#" onclick="' . htmlspecialchars($iOnClick) . '">' : '')
484 . $this->formEngine->getIconHtml($p[2], htmlspecialchars($p[0]), htmlspecialchars($p[0]))
485 . (!$onlySelectedIconShown ? '</a>' : ''),
486 $c,
487 $sM
488 );
489 }
490 $c++;
491 }
492 // Closing optgroup if open
493 if ($optGroupOpen) {
494 $opt[] = '</optgroup>';
495 }
496 // No-matching-value:
497 if ($PA['itemFormElValue'] && $noMatchingValue && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
498 $nMV_label = @sprintf($nMV_label, $PA['itemFormElValue']);
499 $opt[] = '<option value="' . htmlspecialchars($PA['itemFormElValue']) . '" selected="selected">' . htmlspecialchars($nMV_label) . '</option>';
500 }
501 // Create item form fields:
502 $sOnChange = 'if (this.options[this.selectedIndex].value==\'--div--\') {this.selectedIndex=' . $sI . ';} ' . implode('', $PA['fieldChangeFunc']);
503 if (!$disabled) {
504 // MUST be inserted before the selector - else is the value of the hiddenfield here mysteriously submitted...
505 $item .= '<input type="hidden" name="' . $PA['itemFormElName'] . '_selIconVal" value="' . htmlspecialchars($sI) . '" />';
506 }
507 if ($config['iconsInOptionTags']) {
508 $classesForSelectTag[] = 'icon-select';
509 }
510 $item .= '<select' . $selectedStyle . ' id="' . str_replace('.', '', uniqid('tceforms-select-', TRUE)) . '" name="' . $PA['itemFormElName'] . '" class="' . $this->cssClassTypeElementPrefix . 'select ' . implode(' ', $classesForSelectTag) . '"' . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"' . $PA['onFocus'] . $disabled . '>';
511 $item .= implode('', $opt);
512 $item .= '</select>';
513 // Create icon table:
514 if (count($selicons) && !$config['noIconsBelowSelect']) {
515 $item .= '<div class="typo3-TCEforms-selectIcons">';
516 $selicon_cols = (int)$config['selicon_cols'];
517 if (!$selicon_cols) {
518 $selicon_cols = count($selicons);
519 }
520 $sR = ceil(count($selicons) / $selicon_cols);
521 $selicons = array_pad($selicons, $sR * $selicon_cols, '');
522 for ($sa = 0; $sa < $sR; $sa++) {
523 $item .= '<div>';
524 for ($sb = 0; $sb < $selicon_cols; $sb++) {
525 $sk = $sa * $selicon_cols + $sb;
526 $imgN = 'selIcon_' . $table . '_' . $row['uid'] . '_' . $field . '_' . $selicons[$sk][1];
527 $imgS = $selicons[$sk][2] ? $this->formEngine->backPath . 'gfx/content_selected.gif' : 'clear.gif';
528 $item .= '<span><img name="' . htmlspecialchars($imgN) . '" src="' . htmlspecialchars($imgS) . '" width="7" height="10" alt="" /></span>';
529 $item .= '<span>' . $selicons[$sk][0] . '</span>';
530 }
531 $item .= '</div>';
532 }
533 $item .= '</div>';
534 }
535 return $item;
536 }
537
538 /**
539 * Creates a checkbox list (renderMode = "checkbox")
540 * (Render function for getSingleField_typeSelect())
541 *
542 * @param string $table See getSingleField_typeSelect()
543 * @param string $field See getSingleField_typeSelect()
544 * @param array $row See getSingleField_typeSelect()
545 * @param array $PA See getSingleField_typeSelect()
546 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
547 * @param array $selItems Items available for selection
548 * @param string $nMV_label Label for no-matching-value
549 * @return string The HTML code for the item
550 * @see getSingleField_typeSelect()
551 */
552 public function getSingleField_typeSelect_checkbox($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
553 if (empty($selItems)) {
554 return '';
555 }
556 // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
557 $itemArray = array_flip($this->formEngine->extractValuesOnlyFromValueLabelList($PA['itemFormElValue']));
558 $output = '';
559
560 // Disabled
561 $disabled = 0;
562 if ($this->isRenderReadonly() || $config['readOnly']) {
563 $disabled = 1;
564 }
565 // Traverse the Array of selector box items:
566 $groups = array();
567 $currentGroup = 0;
568 $c = 0;
569 $sOnChange = '';
570 if (!$disabled) {
571 $sOnChange = implode('', $PA['fieldChangeFunc']);
572 // Used to accumulate the JS needed to restore the original selection.
573 foreach ($selItems as $p) {
574 // Non-selectable element:
575 if ($p[1] === '--div--') {
576 $selIcon = '';
577 if (isset($p[2]) && $p[2] != 'empty-emtpy') {
578 $selIcon = $this->formEngine->getIconHtml($p[2]);
579 }
580 $currentGroup++;
581 $groups[$currentGroup]['header'] = array(
582 'icon' => $selIcon,
583 'title' => htmlspecialchars($p[0])
584 );
585 } else {
586
587 // Check if some help text is available
588 // Since TYPO3 4.5 help text is expected to be an associative array
589 // with two key, "title" and "description"
590 // For the sake of backwards compatibility, we test if the help text
591 // is a string and use it as a description (this could happen if items
592 // are modified with an itemProcFunc)
593 $hasHelp = FALSE;
594 $help = '';
595 $helpArray = array();
596 if (is_array($p[3]) && count($p[3]) > 0 || !empty($p[3])) {
597 $hasHelp = TRUE;
598 if (is_array($p[3])) {
599 $helpArray = $p[3];
600 } else {
601 $helpArray['description'] = $p[3];
602 }
603 }
604 if ($hasHelp) {
605 $help = BackendUtility::wrapInHelp('', '', '', $helpArray);
606 }
607
608 // Selected or not by default:
609 $checked = 0;
610 if (isset($itemArray[$p[1]])) {
611 $checked = 1;
612 unset($itemArray[$p[1]]);
613 }
614
615 // Build item array
616 $groups[$currentGroup]['items'][] = array(
617 'id' => str_replace('.', '', uniqid('select_checkbox_row_', TRUE)),
618 'name' => $PA['itemFormElName'] . '[' . $c . ']',
619 'value' => $p[1],
620 'checked' => $checked,
621 'disabled' => $disabled,
622 'class' => '',
623 'icon' => (!empty($p[2]) ? $this->formEngine->getIconHtml($p[2]) : IconUtility::getSpriteIcon('empty-empty')),
624 'title' => htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE),
625 'help' => $help
626 );
627 $c++;
628 }
629 }
630 }
631 // Remaining values (invalid):
632 if (count($itemArray) && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
633 $currentGroup++;
634 foreach ($itemArray as $theNoMatchValue => $temp) {
635 // Build item array
636 $groups[$currentGroup]['items'][] = array(
637 'id' => str_replace('.', '', uniqid('select_checkbox_row_', TRUE)),
638 'name' => $PA['itemFormElName'] . '[' . $c . ']',
639 'value' => $theNoMatchValue,
640 'checked' => 1,
641 'disabled' => $disabled,
642 'class' => 'danger',
643 'icon' => '',
644 'title' => htmlspecialchars(@sprintf($nMV_label, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE),
645 'help' => ''
646 );
647 $c++;
648 }
649 }
650 // Add an empty hidden field which will send a blank value if all items are unselected.
651 $output .= '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($PA['itemFormElName']) . '" value="" />';
652
653 // Building the checkboxes
654 foreach($groups as $groupKey => $group){
655 $groupId = htmlspecialchars($PA['itemFormElID']) . '-group-' . $groupKey;
656 $output .= '<div class="panel panel-default">';
657 if(is_array($group['header'])){
658 $output .= '
659 <div class="panel-heading">
660 <a data-toggle="collapse" href="#' . $groupId . '" aria-expanded="true" aria-controls="' . $groupId . '">
661 ' . $group['header']['icon'] . '
662 ' . $group['header']['title'] . '
663 </a>
664 </div>
665 ';
666 }
667 if(is_array($group['items']) && count($group['items']) >= 1){
668 $tableRows = '';
669 $checkGroup = array();
670 $uncheckGroup = array();
671 $resetGroup = array();
672
673 // Render rows
674 foreach($group['items'] as $item){
675 $tableRows .= '
676 <tr class="' . $item['class'] . '">
677 <td class="col-checkbox">
678 <input type="checkbox"
679 id="' . $item['id'] . '"
680 name="' . htmlspecialchars($item['name']) . '"
681 value="' . htmlspecialchars($item['value']) . '"
682 onclick="' . htmlspecialchars($sOnChange) . '"
683 ' . ($item['checked'] ? ' checked=checked' : '') . '
684 ' . ($item['disabled'] ? ' disabled=disabled' : '') . '
685 ' . $PA['onFocus'] . ' />
686 </td>
687 <td class="col-icon">
688 <label class="label-block" for="' . $item['id'] . '">' . $item['icon'] . '</label>
689 </td>
690 <td class="col-title">
691 <label class="label-block" for="' . $item['id'] . '">' . $item['title'] . '</label>
692 </td>
693 <td>' . $item['help'] . '</td>
694 </tr>
695 ';
696 $checkGroup[] = $this->formEngine->elName($item['name']) . '.checked=1;';
697 $uncheckGroup[] = $this->formEngine->elName($item['name']) . '.checked=0;';
698 $resetGroup[] = $this->formEngine->elName($item['name']) . '.checked='.$item['checked'] . ';';
699 }
700
701 // Build toggle group checkbox
702 $toggleGroupCheckbox = '';
703 if(count($resetGroup)){
704 $toggleGroupCheckbox = '
705 <input type="checkbox" class="checkbox" onclick="if (checked) {' . htmlspecialchars(implode('', $checkGroup) . '} else {' . implode('', $uncheckGroup)) . '}">
706 ';
707 }
708
709 // Build reset group button
710 $resetGroupBtn = '';
711 if(count($resetGroup)){
712 $resetGroupBtn = '
713 <a href="#" class="btn btn-default" onclick="' . implode('', $resetGroup) . ' return false;' . '">
714 ' . IconUtility::getSpriteIcon('actions-edit-undo', array('title' => htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection')))) . '
715 ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection') . '
716 </a>
717 ';
718 }
719
720 $output .= '
721 <div id="' . $groupId . '" class="panel-collapse collapse in" role="tabpanel">
722 <div class="table-fit">
723 <table class="table table-transparent table-hover">
724 <thead>
725 <tr>
726 <th class="col-checkbox">' . $toggleGroupCheckbox . '</th>
727 <th class="col-icon"></th>
728 <th class="text-right" colspan="2">' . $resetGroupBtn . '</th>
729 </tr>
730 </thead>
731 <tbody>' . $tableRows . '</tbody>
732 </table>
733 </div>
734 </div>
735 ';
736 }
737 $output .= '</div>';
738 }
739
740 return $output;
741 }
742
743 /**
744 * Creates a selectorbox list (renderMode = "singlebox")
745 * (Render function for getSingleField_typeSelect())
746 *
747 * @param string $table See getSingleField_typeSelect()
748 * @param string $field See getSingleField_typeSelect()
749 * @param array $row See getSingleField_typeSelect()
750 * @param array $PA See getSingleField_typeSelect()
751 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
752 * @param array $selItems Items available for selection
753 * @param string $nMV_label Label for no-matching-value
754 * @return string The HTML code for the item
755 * @see getSingleField_typeSelect()
756 */
757 public function getSingleField_typeSelect_singlebox($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
758 $languageService = $this->getLanguageService();
759 // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
760 $itemArray = array_flip($this->formEngine->extractValuesOnlyFromValueLabelList($PA['itemFormElValue']));
761 $item = '';
762 $disabled = '';
763 if ($this->isRenderReadonly() || $config['readOnly']) {
764 $disabled = ' disabled="disabled"';
765 }
766 // Traverse the Array of selector box items:
767 $opt = array();
768 // Used to accumulate the JS needed to restore the original selection.
769 $restoreCmd = array();
770 $c = 0;
771 foreach ($selItems as $p) {
772 // Selected or not by default:
773 $sM = '';
774 if (isset($itemArray[$p[1]])) {
775 $sM = ' selected="selected"';
776 $restoreCmd[] = $this->formEngine->elName(($PA['itemFormElName'] . '[]')) . '.options[' . $c . '].selected=1;';
777 unset($itemArray[$p[1]]);
778 }
779 // Non-selectable element:
780 $nonSel = '';
781 if ((string)$p[1] === '--div--') {
782 $nonSel = ' onclick="this.selected=0;" class="c-divider"';
783 }
784 // Icon style for option tag:
785 $styleAttrValue = '';
786 if ($config['iconsInOptionTags']) {
787 $styleAttrValue = $this->formEngine->optionTagStyle($p[2]);
788 }
789 // Compile <option> tag:
790 $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . $sM . $nonSel
791 . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . '>'
792 . htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE) . '</option>';
793 $c++;
794 }
795 // Remaining values:
796 if (count($itemArray) && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
797 foreach ($itemArray as $theNoMatchValue => $temp) {
798 // Compile <option> tag:
799 array_unshift($opt, '<option value="' . htmlspecialchars($theNoMatchValue) . '" selected="selected">'
800 . htmlspecialchars(@sprintf($nMV_label, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE) . '</option>');
801 }
802 }
803 // Compile selector box:
804 $sOnChange = implode('', $PA['fieldChangeFunc']);
805 $selector_itemListStyle = isset($config['itemListStyle'])
806 ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"'
807 : ' style="' . $this->formEngine->defaultMultipleSelectorStyle . '"';
808 $size = (int)$config['size'];
809 $cssPrefix = $size === 1 ? 'tceforms-select' : 'tceforms-multiselect';
810 $size = $config['autoSizeMax']
811 ? MathUtility::forceIntegerInRange(count($selItems) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax'])
812 : $size;
813 $selectBox = '<select id="' . str_replace('.', '', uniqid($cssPrefix, TRUE)) . '" name="' . $PA['itemFormElName'] . '[]" '
814 . 'class="' . $this->cssClassTypeElementPrefix . 'select ' . $cssPrefix . '"' . ($size ? ' size="' . $size . '" ' : '')
815 . ' multiple="multiple" onchange="' . htmlspecialchars($sOnChange) . '"' . $PA['onFocus']
816 . ' ' . $selector_itemListStyle . $disabled . '>
817 ' . implode('
818 ', $opt) . '
819 </select>';
820 // Add an empty hidden field which will send a blank value if all items are unselected.
821 if (!$disabled) {
822 $item .= '<input type="hidden" name="' . htmlspecialchars($PA['itemFormElName']) . '" value="" />';
823 }
824 // Put it all into a table:
825 $onClick = htmlspecialchars($this->formEngine->elName(($PA['itemFormElName'] . '[]')) . '.selectedIndex=-1;' . implode('', $restoreCmd) . ' return false;');
826 $item .= '
827 <table border="0" cellspacing="0" cellpadding="0" width="1" class="typo3-TCEforms-select-singlebox">
828 <tr>
829 <td>
830 ' . $selectBox . '
831 <br/>
832 <em>' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.holdDownCTRL')) . '</em>
833 </td>
834 <td valign="top">
835 <a href="#" onclick="' . $onClick . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection')) . '">'
836 . IconUtility::getSpriteIcon('actions-edit-undo') . '</a>
837 </td>
838 </tr>
839 </table>
840 ';
841 return $item;
842 }
843
844 /**
845 * If the select field is build by a foreign_table the related UIDs
846 * will be returned.
847 *
848 * Otherwise the label of the currently selected value will be written
849 * to the alternativeFieldValue class property.
850 *
851 * @param array $fieldConfig The "config" section of the TCA for the current select field.
852 * @param string $fieldName The name of the select field.
853 * @param string $value The current value in the local record, usually a comma separated list of selected values.
854 * @return array Array of related UIDs.
855 */
856 public function getRelatedSelectFieldUids(array $fieldConfig, $fieldName, $value) {
857 $relatedUids = array();
858
859 $isTraversable = FALSE;
860 if (isset($fieldConfig['foreign_table'])) {
861 $isTraversable = TRUE;
862 // if a foreign_table is used we pre-filter the records for performance
863 $fieldConfig['foreign_table_where'] .= ' AND ' . $fieldConfig['foreign_table'] . '.uid IN (' . $value . ')';
864 }
865
866 $PA = array();
867 $PA['fieldConf']['config'] = $fieldConfig;
868 $PA['fieldConf']['config']['form_type'] = $PA['fieldConf']['config']['form_type'] ? $PA['fieldConf']['config']['form_type'] : $PA['fieldConf']['config']['type'];
869 $PA['fieldTSConfig'] = $this->formEngine->setTSconfig($this->currentTable, $this->currentRow, $fieldName);
870 $PA['fieldConf']['config'] = $this->formEngine->overrideFieldConf($PA['fieldConf']['config'], $PA['fieldTSConfig']);
871 $selectItemArray = $this->getSelectItems($this->currentTable, $fieldName, $this->currentRow, $PA);
872
873 if ($isTraversable && count($selectItemArray)) {
874 $this->currentTable = $fieldConfig['foreign_table'];
875 $relatedUids = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value);
876 } else {
877 $selectedLabels = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value, 1, TRUE);
878 if (count($selectedLabels) === 1) {
879 $this->alternativeFieldValue = $selectedLabels[0];
880 $this->forceAlternativeFieldValueUse = TRUE;
881 }
882 }
883
884 return $relatedUids;
885 }
886
887 /**
888 * Extracts the selected values from a given array of select items.
889 *
890 * @param array $selectItemArray The select item array generated by \TYPO3\CMS\Backend\Form\FormEngine->getSelectItems.
891 * @param string $value The currently selected value(s) as comma separated list.
892 * @param int|NULL $maxItems Optional value, if set processing is skipped and an empty array will be returned when the number of selected values is larger than the provided value.
893 * @param bool $returnLabels If TRUE the select labels will be returned instead of the values.
894 * @return array
895 */
896 protected function getSelectedValuesFromSelectItemArray(array $selectItemArray, $value, $maxItems = NULL, $returnLabels = FALSE) {
897 $values = GeneralUtility::trimExplode(',', $value);
898 $selectedValues = array();
899
900 if ($maxItems !== NULL && (count($values) > (int)$maxItems)) {
901 return $selectedValues;
902 }
903
904 foreach ($selectItemArray as $selectItem) {
905 $selectItemValue = $selectItem[1];
906 if (in_array($selectItemValue, $values)) {
907 if ($returnLabels) {
908 $selectedValues[] = $selectItem[0];
909 } else {
910 $selectedValues[] = $selectItemValue;
911 }
912 }
913 }
914
915 return $selectedValues;
916 }
917
918 /**
919 * @param string $alternativeFieldValue
920 */
921 public function setAlternativeFieldValue($alternativeFieldValue) {
922 $this->alternativeFieldValue = $alternativeFieldValue;
923 }
924
925 /**
926 * @param array $currentRow
927 */
928 public function setCurrentRow($currentRow) {
929 $this->currentRow = $currentRow;
930 }
931
932 /**
933 * @param string $currentTable
934 */
935 public function setCurrentTable($currentTable) {
936 $this->currentTable = $currentTable;
937 }
938
939 /**
940 * @param bool $forceAlternativeFieldValueUse
941 */
942 public function setForceAlternativeFieldValueUse($forceAlternativeFieldValueUse) {
943 $this->forceAlternativeFieldValueUse = $forceAlternativeFieldValueUse;
944 }
945
946 /**
947 * @return string
948 */
949 public function getAlternativeFieldValue() {
950 return $this->alternativeFieldValue;
951 }
952
953 /**
954 * @return array
955 */
956 public function getCurrentRow() {
957 return $this->currentRow;
958 }
959
960 /**
961 * @return string
962 */
963 public function getCurrentTable() {
964 return $this->currentTable;
965 }
966
967 /**
968 * @return boolean
969 */
970 public function isForceAlternativeFieldValueUse() {
971 return $this->forceAlternativeFieldValueUse;
972 }
973
974 }