1<?php
2declare(strict_types = 1);
3namespace TYPO3\CMS\Backend\Form\Element;
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18use TYPO3\CMS\Backend\Form\NodeFactory;
19use TYPO3\CMS\Core\Imaging\Icon;
20use TYPO3\CMS\Core\Imaging\IconRegistry;
21use TYPO3\CMS\Core\Utility\GeneralUtility;
22use TYPO3\CMS\Core\Utility\StringUtility;
23
24/**
25 * Generation of TCEform elements of the type "check"
26 */
27class CheckboxElement extends AbstractFormElement
28{
29    /**
30     * @var IconRegistry
31     */
32    private $iconRegistry;
33
34    /**
35     * Default field information enabled for this element.
36     *
37     * @var array
38     */
39    protected $defaultFieldInformation = [
40        'tcaDescription' => [
41            'renderType' => 'tcaDescription',
42        ],
43    ];
44
45    /**
46     * Default field wizards enabled for this element.
47     *
48     * @var array
49     */
50    protected $defaultFieldWizard = [
51        'localizationStateSelector' => [
52            'renderType' => 'localizationStateSelector',
53        ],
54        'otherLanguageContent' => [
55            'renderType' => 'otherLanguageContent',
56            'after' => [
57                'localizationStateSelector'
58            ],
59        ],
60        'defaultLanguageDifferences' => [
61            'renderType' => 'defaultLanguageDifferences',
62            'after' => [
63                'otherLanguageContent',
64            ],
65        ],
66    ];
67
68    /**
69     * @param NodeFactory $nodeFactory
70     * @param array $data
71     */
72    public function __construct(NodeFactory $nodeFactory, array $data)
73    {
74        parent::__construct($nodeFactory, $data);
75        $this->iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
76    }
77
78    /**
79     * This will render a checkbox or an array of checkboxes
80     *
81     * @return array As defined in initializeResultArray() of AbstractNode
82     */
83    public function render(): array
84    {
85        $resultArray = $this->initializeResultArray();
86
87        $elementHtml = '';
88        $disabled = false;
89        if ($this->data['parameterArray']['fieldConf']['config']['readOnly']) {
90            $disabled = true;
91        }
92        // Traversing the array of items
93        $items = $this->data['parameterArray']['fieldConf']['config']['items'];
94
95        $numberOfItems = \count($items);
96        if ($numberOfItems === 0) {
97            $items[] = ['', ''];
98            $numberOfItems = 1;
99        }
100        $formElementValue = (int)$this->data['parameterArray']['itemFormElValue'];
101        $cols = (int)$this->data['parameterArray']['fieldConf']['config']['cols'];
102        if ($cols > 1) {
103            [$colClass, $colClear] = $this->calculateColumnMarkup($cols);
104            $elementHtml .= '<div class="checkbox-row row">';
105            $counter = 0;
106            // $itemKey is important here, because items could have been removed via TSConfig
107            foreach ($items as $itemKey => $itemDefinition) {
108                $label = $itemDefinition[0];
109                $elementHtml .=
110                    '<div class="checkbox-column ' . $colClass . '">'
111                    . $this->renderSingleCheckboxElement($label, $itemKey, $formElementValue, $numberOfItems, $this->data['parameterArray'], $disabled) .
112                    '</div>';
113                ++$counter;
114                if ($counter < $numberOfItems && !empty($colClear)) {
115                    foreach ($colClear as $rowBreakAfter => $clearClass) {
116                        if ($counter % $rowBreakAfter === 0) {
117                            $elementHtml .= '<div class="clearfix ' . $clearClass . '"></div>';
118                        }
119                    }
120                }
121            }
122            $elementHtml .= '</div>';
123        } else {
124            $counter = 0;
125            foreach ($items as $itemKey => $itemDefinition) {
126                $label = $itemDefinition[0];
127                $elementHtml .= $this->renderSingleCheckboxElement($label, $counter, $formElementValue, $numberOfItems, $this->data['parameterArray'], $disabled);
128                ++$counter;
129            }
130        }
131        if (!$disabled) {
132            $elementHtml .= '<input type="hidden" name="' . htmlspecialchars($this->data['parameterArray']['itemFormElName']) . '" value="' . htmlspecialchars((string)$formElementValue) . '" />';
133        }
134
135        $fieldInformationResult = $this->renderFieldInformation();
136        $fieldInformationHtml = $fieldInformationResult['html'];
137        $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
138
139        $fieldWizardResult = $this->renderFieldWizard();
140        $fieldWizardHtml = $fieldWizardResult['html'];
141        $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
142
143        $html = [];
144        $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
145        $html[] = $fieldInformationHtml;
146        $html[] =   '<div class="form-wizards-wrap">';
147        $html[] =       '<div class="form-wizards-element">';
148        $html[] =           $elementHtml;
149        $html[] =       '</div>';
150        if (!$disabled && !empty($fieldWizardHtml)) {
151            $html[] =   '<div class="form-wizards-items-bottom">';
152            $html[] =       $fieldWizardHtml;
153            $html[] =   '</div>';
154        }
155        $html[] =   '</div>';
156        $html[] = '</div>';
157
158        $resultArray['html'] = implode(LF, $html);
159        return $resultArray;
160    }
161
162    /**
163     * This functions builds the HTML output for the checkbox
164     *
165     * @param string $label Label of this item
166     * @param int $itemCounter Number of this element in the list of all elements
167     * @param int $formElementValue Value of this element
168     * @param int $numberOfItems Full number of items
169     * @param array $additionalInformation Information with additional configuration options.
170     * @param bool $disabled TRUE if form element is disabled
171     * @return string Single element HTML
172     */
173    protected function renderSingleCheckboxElement($label, $itemCounter, $formElementValue, $numberOfItems, $additionalInformation, $disabled): string
174    {
175        $config = $additionalInformation['fieldConf']['config'];
176        $inline = !empty($config['cols']) && $config['cols'] === 'inline';
177        $invert = isset($config['items'][$itemCounter]['invertStateDisplay']) && $config['items'][$itemCounter]['invertStateDisplay'] === true;
178        $checkboxParameters = $this->checkBoxParams(
179            $additionalInformation['itemFormElName'],
180            $formElementValue,
181            $itemCounter,
182            $numberOfItems,
183            implode('', $additionalInformation['fieldChangeFunc'])
184        );
185        $uniqueId = StringUtility::getUniqueId('_');
186        $checkboxId = $additionalInformation['itemFormElID'] . '_' . $itemCounter . $uniqueId;
187
188        $iconIdentifierChecked = !empty($config['items'][$itemCounter]['iconIdentifierChecked']) ? $config['items'][$itemCounter]['iconIdentifierChecked'] : 'actions-check';
189        if (!$this->iconRegistry->isRegistered($iconIdentifierChecked)) {
190            $iconIdentifierChecked = 'actions-check';
191        }
192        $iconIdentifierUnchecked = !empty($config['items'][$itemCounter]['iconIdentifierUnchecked']) ? $config['items'][$itemCounter]['iconIdentifierUnchecked'] : 'empty-empty';
193        if (!$this->iconRegistry->isRegistered($iconIdentifierUnchecked)) {
194            $iconIdentifierUnchecked = 'empty-empty';
195        }
196        $iconChecked = $this->iconFactory->getIcon($iconIdentifierChecked, Icon::SIZE_SMALL)->render('inline');
197        $iconUnchecked = $this->iconFactory->getIcon($iconIdentifierUnchecked, Icon::SIZE_SMALL)->render('inline');
198
199        return '
200            <div class="checkbox checkbox-type-icon-toggle' . ($invert ? ' checkbox-invert' : '') . ($inline ? ' checkbox-inline' : '') . (!$disabled ? '' : ' disabled') . '">
201                <input type="checkbox"
202                    class="checkbox-input"
203                    value="1"
204                    data-formengine-input-name="' . htmlspecialchars($additionalInformation['itemFormElName']) . '"
205                    ' . $checkboxParameters . '
206                    ' . ($disabled ? ' disabled="disabled"' : '') . '
207                    id="' . $checkboxId . '" />
208                <label class="checkbox-label" for="' . $checkboxId . '">
209                    <span class="checkbox-label-icon">
210                        <span class="checkbox-label-icon-checked">' . ($invert ? $iconUnchecked : $iconChecked) . '</span>
211                        <span class="checkbox-label-icon-unchecked">' . ($invert ? $iconChecked : $iconUnchecked) . '</span>
212                    </span>
213                    <span class="checkbox-label-text">' . $this->appendValueToLabelInDebugMode(($label ? htmlspecialchars($label) : '&nbsp;'), $formElementValue) . '</span>
214                </label>
215            </div>';
216    }
217}
218