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