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