SelectSingleElement.php 10.8 KB
Newer Older
1
<?php
2

3
4
5
6
7
8
9
10
11
12
13
14
15
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

16
17
namespace TYPO3\CMS\Backend\Form\Element;

18
19
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Core\Utility\StringUtility;
22
23
24
25
26

/**
 * Creates a widget where only one item can be selected.
 * This is either a select drop-down if no size config is given or set to 1, or a select box.
 *
27
 * This is rendered for type=select, renderType=selectSingle
28
 */
29
30
class SelectSingleElement extends AbstractFormElement
{
31
32
33
34
35
36
37
38
39
40
41
    /**
     * Default field information enabled for this element.
     *
     * @var array
     */
    protected $defaultFieldInformation = [
        'tcaDescription' => [
            'renderType' => 'tcaDescription',
        ],
    ];

42
43
44
45
46
47
48
49
50
51
    /**
     * Default field wizards enabled for this element.
     *
     * @var array
     */
    protected $defaultFieldWizard = [
        'selectIcons' => [
            'renderType' => 'selectIcons',
            'disabled' => true,
        ],
52
53
54
55
56
57
        'localizationStateSelector' => [
            'renderType' => 'localizationStateSelector',
            'after' => [
                'selectIcons',
            ],
        ],
58
59
        'otherLanguageContent' => [
            'renderType' => 'otherLanguageContent',
60
            'after' => [ 'localizationStateSelector' ],
61
62
63
64
65
66
67
        ],
        'defaultLanguageDifferences' => [
            'renderType' => 'defaultLanguageDifferences',
            'after' => [ 'otherLanguageContent' ],
        ],
    ];

68
69
70
71
72
73
74
    /**
     * Render single element
     *
     * @return array As defined in initializeResultArray() of AbstractNode
     */
    public function render()
    {
75
76
        $resultArray = $this->initializeResultArray();

77
78
79
80
81
82
        $table = $this->data['tableName'];
        $field = $this->data['fieldName'];
        $parameterArray = $this->data['parameterArray'];
        $config = $parameterArray['fieldConf']['config'];

        $selectItems = $parameterArray['fieldConf']['config']['items'];
83
        $classList = ['form-select', 'form-control-adapt'];
84
85
86
87
88
89

        // Check against inline uniqueness
        /** @var InlineStackProcessor $inlineStackProcessor */
        $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
        $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
        $uniqueIds = null;
90
        if ($this->data['isInlineChild'] && $this->data['inlineParentUid']) {
91
92
93
            // @todo: At least parts of this if is dead and/or broken: $uniqueIds is filled but never used.
            // See InlineControlContainer where 'inlineData' 'unique' 'used' is set. What exactly is
            // this if supposed to do and when should it kick in and what for?
94
            $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
95
96
97
            if ($this->data['inlineParentConfig']['foreign_table'] === $table
                && $this->data['inlineParentConfig']['foreign_unique'] === $field
            ) {
98
                $classList[] = 't3js-inline-unique';
99
100
101
                $uniqueIds = $this->data['inlineData']['unique'][$inlineObjectName . '-' . $table]['used'];
            }
            // hide uid of parent record for symmetric relations
102
103
104
105
106
            if ($this->data['inlineParentConfig']['foreign_table'] === $table
                && (
                    $this->data['inlineParentConfig']['foreign_field'] === $field
                    || $this->data['inlineParentConfig']['symmetric_field'] === $field
                )
107
            ) {
108
                $uniqueIds[] = $this->data['inlineParentUid'];
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
            }
        }

        // Initialization:
        $selectId = StringUtility::getUniqueId('tceforms-select-');
        $selectedIcon = '';
        $size = (int)$config['size'];

        // Style set on <select/>
        $options = '';
        $disabled = false;
        if (!empty($config['readOnly'])) {
            $disabled = true;
        }

        // Prepare groups
        $selectItemCounter = 0;
        $selectItemGroupCount = 0;
127
        $selectItemGroups = [];
128
129
130
        $selectedValue = '';
        $hasIcons = false;

131
        // In case e.g. "l10n_display" is set to "defaultAsReadonly" only one value (as string) could be handed in
132
        if (!empty($parameterArray['itemFormElValue'])) {
133
134
135
136
137
            if (is_array($parameterArray['itemFormElValue'])) {
                $selectedValue = (string)$parameterArray['itemFormElValue'][0];
            } else {
                $selectedValue = (string)$parameterArray['itemFormElValue'];
            }
138
139
140
141
142
143
144
145
        }

        foreach ($selectItems as $item) {
            if ($item[1] === '--div--') {
                // IS OPTGROUP
                if ($selectItemCounter !== 0) {
                    $selectItemGroupCount++;
                }
146
                $selectItemGroups[$selectItemGroupCount]['header'] = [
147
                    'title' => $item[0],
148
                ];
149
150
            } else {
                // IS ITEM
151
                $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $item[0], $item[0]) : '';
152
153
154
155
156
157
                $selected = $selectedValue === (string)$item[1];

                if ($selected) {
                    $selectedIcon = $icon;
                }

158
                $selectItemGroups[$selectItemGroupCount]['items'][] = [
159
                    'title' => $this->appendValueToLabelInDebugMode($item[0], $item[1]),
160
161
162
                    'value' => $item[1],
                    'icon' => $icon,
                    'selected' => $selected,
163
                ];
164
165
166
167
                $selectItemCounter++;
            }
        }

168
169
170
        // Fallback icon
        // @todo: assign a special icon for non matching values?
        if (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
            $selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
        }

        // Process groups
        foreach ($selectItemGroups as $selectItemGroup) {
            // suppress groups without items
            if (empty($selectItemGroup['items'])) {
                continue;
            }

            $optionGroup = is_array($selectItemGroup['header']);
            $options .= ($optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', false) . '">' : '');

            if (is_array($selectItemGroup['items'])) {
                foreach ($selectItemGroup['items'] as $item) {
                    $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' .
                        htmlspecialchars($item['icon']) . '"'
188
                        . ($item['selected'] ? ' selected="selected"' : '') . '>' . htmlspecialchars((string)($item['title'] ?? ''), ENT_COMPAT, 'UTF-8', false) . '</option>';
189
190
191
192
193
194
195
                }
                $hasIcons = !empty($item['icon']);
            }

            $options .= ($optionGroup ? '</optgroup>' : '');
        }

196
197
        $selectAttributes = [
            'id' => $selectId,
198
            'name' => (string)($parameterArray['itemFormElName'] ?? ''),
199
            'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
200
            'class' => implode(' ', $classList),
201
202
        ];
        if ($size) {
203
            $selectAttributes['size'] = (string)$size;
204
        }
205
206
        if ($disabled) {
            $selectAttributes['disabled'] = 'disabled';
207
208
        }

209
210
211
        $fieldInformationResult = $this->renderFieldInformation();
        $fieldInformationHtml = $fieldInformationResult['html'];
        $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
212

213
214
215
216
        $fieldControlResult = $this->renderFieldControl();
        $fieldControlHtml = $fieldControlResult['html'];
        $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);

217
        $fieldWizardResult = $this->renderFieldWizard();
218
        $fieldWizardHtml = $fieldWizardResult['html'];
219
        $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
220

221
        $html = [];
222
        $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
223
        $html[] = $fieldInformationHtml;
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
        $html[] =   '<div class="form-control-wrap">';
        $html[] =       '<div class="form-wizards-wrap">';
        $html[] =           '<div class="form-wizards-element">';
        if ($hasIcons) {
            $html[] =           '<div class="input-group">';
            $html[] =               '<span class="input-group-addon input-group-icon">';
            $html[] =                   $selectedIcon;
            $html[] =               '</span>';
        }
        $html[] =                   '<select ' . GeneralUtility::implodeAttributes($selectAttributes, true) . '>';
        $html[] =                       $options;
        $html[] =                   '</select>';
        if ($hasIcons) {
            $html[] =           '</div>';
        }
        $html[] =           '</div>';
240
241
242
243
244
245
246
        if (!$disabled && !empty($fieldControlHtml)) {
            $html[] =      '<div class="form-wizards-items-aside">';
            $html[] =          '<div class="btn-group">';
            $html[] =              $fieldControlHtml;
            $html[] =          '</div>';
            $html[] =      '</div>';
        }
247
        if (!$disabled && !empty($fieldWizardHtml)) {
248
249
250
            $html[] =       '<div class="form-wizards-items-bottom">';
            $html[] =           $fieldWizardHtml;
            $html[] =       '</div>';
251
        }
252
253
254
        $html[] =       '</div>';
        $html[] =   '</div>';
        $html[] = '</div>';
255
256
257

        $resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SelectSingleElement' => implode(LF, [
            'function(SelectSingleElement) {',
258
259
                'require([\'TYPO3/CMS/Core/DocumentService\'], function(DocumentService) {',
                    'DocumentService.ready().then(function() {',
260
261
262
263
264
265
266
267
268
269
                        'SelectSingleElement.initialize(',
                            GeneralUtility::quoteJSvalue('#' . $selectId) . ',',
                            '{',
                                'onChange: function() {',
                                    implode('', $parameterArray['fieldChangeFunc']),
                                '}',
                            '}',
                        ');',
                    '});',
                '});',
270
271
272
            '}',
        ])];

273
        $resultArray['html'] = implode(LF, $html);
274
275
        return $resultArray;
    }
276
}