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