1<?php
2declare(strict_types = 1);
3namespace TYPO3\CMS\T3editor\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\Element\AbstractFormElement;
19use TYPO3\CMS\Core\Utility\GeneralUtility;
20use TYPO3\CMS\Core\Utility\MathUtility;
21use TYPO3\CMS\T3editor\Exception\InvalidModeException;
22use TYPO3\CMS\T3editor\Mode;
23use TYPO3\CMS\T3editor\Registry\AddonRegistry;
24use TYPO3\CMS\T3editor\Registry\ModeRegistry;
25use TYPO3\CMS\T3editor\T3editor;
26
27/**
28 * t3editor FormEngine widget
29 * @internal
30 */
31class T3editorElement extends AbstractFormElement
32{
33    /**
34     * @var array
35     */
36    protected $resultArray;
37
38    /**
39     * @var string
40     */
41    protected $mode = '';
42
43    /**
44     * Default field information enabled for this element.
45     *
46     * @var array
47     */
48    protected $defaultFieldInformation = [
49        'tcaDescription' => [
50            'renderType' => 'tcaDescription',
51        ],
52    ];
53
54    /**
55     * Default field wizards enabled for this element.
56     *
57     * @var array
58     */
59    protected $defaultFieldWizard = [
60        'localizationStateSelector' => [
61            'renderType' => 'localizationStateSelector',
62        ],
63        'otherLanguageContent' => [
64            'renderType' => 'otherLanguageContent',
65            'after' => [
66                'localizationStateSelector',
67            ],
68        ],
69        'defaultLanguageDifferences' => [
70            'renderType' => 'defaultLanguageDifferences',
71            'after' => [
72                'otherLanguageContent',
73            ],
74        ],
75    ];
76
77    /**
78     * Render t3editor element
79     *
80     * @return array As defined in initializeResultArray() of AbstractNode
81     * @throws \TYPO3\CMS\T3editor\Exception\InvalidModeException
82     * @throws \InvalidArgumentException
83     * @throws \BadFunctionCallException
84     */
85    public function render(): array
86    {
87        $this->resultArray = $this->initializeResultArray();
88        $this->resultArray['stylesheetFiles'][] = 'EXT:t3editor/Resources/Public/JavaScript/Contrib/cm/lib/codemirror.css';
89        $this->resultArray['stylesheetFiles'][] = 'EXT:t3editor/Resources/Public/Css/t3editor.css';
90        $this->resultArray['requireJsModules'][] = [
91            'TYPO3/CMS/T3editor/T3editor' => 'function(T3editor) {T3editor.observeEditorCandidates()}'
92        ];
93
94        // Compile and register t3editor configuration
95        GeneralUtility::makeInstance(T3editor::class)->registerConfiguration();
96
97        $registeredAddons = AddonRegistry::getInstance()->getForMode($this->getMode()->getFormatCode());
98        foreach ($registeredAddons as $addon) {
99            foreach ($addon->getCssFiles() as $cssFile) {
100                $this->resultArray['stylesheetFiles'][] = $cssFile;
101            }
102        }
103
104        $parameterArray = $this->data['parameterArray'];
105
106        $attributes = [];
107        if (isset($parameterArray['fieldConf']['config']['rows']) && MathUtility::canBeInterpretedAsInteger($parameterArray['fieldConf']['config']['rows'])) {
108            $attributes['rows'] = $parameterArray['fieldConf']['config']['rows'];
109        }
110
111        $attributes['wrap'] = 'off';
112        $attributes['style'] = 'width:100%;';
113        $attributes['onchange'] = GeneralUtility::quoteJSvalue($parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged']);
114
115        $attributeString = GeneralUtility::implodeAttributes($attributes, true);
116
117        $editorHtml = $this->getHTMLCodeForEditor(
118            $parameterArray['itemFormElName'],
119            'text-monospace enable-tab',
120            $parameterArray['itemFormElValue'],
121            $attributeString,
122            $this->data['tableName'] . ' > ' . $this->data['fieldName'],
123            [
124                'target' => 0,
125                'effectivePid' => $this->data['effectivePid']
126            ]
127        );
128
129        $fieldInformationResult = $this->renderFieldInformation();
130        $fieldInformationHtml = $fieldInformationResult['html'];
131        $this->resultArray = $this->mergeChildReturnIntoExistingResult($this->resultArray, $fieldInformationResult, false);
132
133        $fieldControlResult = $this->renderFieldControl();
134        $fieldControlHtml = $fieldControlResult['html'];
135        $this->resultArray = $this->mergeChildReturnIntoExistingResult($this->resultArray, $fieldControlResult, false);
136
137        $fieldWizardResult = $this->renderFieldWizard();
138        $fieldWizardHtml = $fieldWizardResult['html'];
139        $this->resultArray = $this->mergeChildReturnIntoExistingResult($this->resultArray, $fieldWizardResult, false);
140
141        $html = [];
142        $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
143        $html[] =   $fieldInformationHtml;
144        $html[] =   '<div class="form-control-wrap">';
145        $html[] =       '<div class="form-wizards-wrap">';
146        $html[] =           '<div class="form-wizards-element">';
147        $html[] =               '<div class="t3editor-wrapper">';
148        $html[] =                   $editorHtml;
149        $html[] =               '</div>';
150        $html[] =           '</div>';
151        if (!empty($fieldControlHtml)) {
152            $html[] =           '<div class="form-wizards-items-aside">';
153            $html[] =               '<div class="btn-group">';
154            $html[] =                   $fieldControlHtml;
155            $html[] =               '</div>';
156            $html[] =           '</div>';
157        }
158        if (!empty($fieldWizardHtml)) {
159            $html[] = '<div class="form-wizards-items-bottom">';
160            $html[] = $fieldWizardHtml;
161            $html[] = '</div>';
162        }
163        $html[] =       '</div>';
164        $html[] =   '</div>';
165        $html[] = '</div>';
166
167        $this->resultArray['html'] = implode(LF, $html);
168
169        return $this->resultArray;
170    }
171
172    /**
173     * Generates HTML with code editor
174     *
175     * @param string $name Name attribute of HTML tag
176     * @param string $class Class attribute of HTML tag
177     * @param string $content Content of the editor
178     * @param string $additionalParams Any additional editor parameters
179     * @param string $alt Alt attribute
180     * @param array $hiddenfields
181     *
182     * @return string Generated HTML code for editor
183     * @throws \TYPO3\CMS\T3editor\Exception\InvalidModeException
184     */
185    protected function getHTMLCodeForEditor(
186        string $name,
187        string $class = '',
188        string $content = '',
189        string $additionalParams = '',
190        string $alt = '',
191        array $hiddenfields = []
192    ): string {
193        $code = [];
194        $attributes = [];
195        $mode = $this->getMode();
196        $registeredAddons = AddonRegistry::getInstance()->getForMode($mode->getFormatCode());
197
198        $attributes['class'] = $class . ' t3editor';
199        $attributes['alt'] = $alt;
200        $attributes['id'] = 't3editor_' . md5($name);
201        $attributes['name'] = $name;
202
203        $settings = AddonRegistry::getInstance()->compileSettings($registeredAddons);
204        $addons = [];
205        foreach ($registeredAddons as $addon) {
206            $addons[] = $addon->getIdentifier();
207        }
208
209        $attributes['data-codemirror-config'] = json_encode([
210            'mode' => $mode->getIdentifier(),
211            'addons' => json_encode($addons),
212            'options' => json_encode($settings)
213        ]);
214
215        $attributesString = '';
216        foreach ($attributes as $attribute => $value) {
217            $attributesString .= $attribute . '="' . htmlspecialchars((string)$value) . '" ';
218        }
219        $attributesString .= $additionalParams;
220
221        $code[] = '<textarea ' . $attributesString . '>' . htmlspecialchars($content) . '</textarea>';
222
223        if (!empty($hiddenfields)) {
224            foreach ($hiddenfields as $attributeName => $value) {
225                $code[] = '<input type="hidden" name="' . htmlspecialchars($attributeName) . '" value="' . htmlspecialchars((string)$value) . '" />';
226            }
227        }
228        return implode(LF, $code);
229    }
230
231    /**
232     * @return Mode
233     * @throws InvalidModeException
234     */
235    protected function getMode(): Mode
236    {
237        $config = $this->data['parameterArray']['fieldConf']['config'];
238
239        if (!isset($config['format'])) {
240            return ModeRegistry::getInstance()->getDefaultMode();
241        }
242
243        $identifier = $config['format'];
244        if (strpos($config['format'], '/') !== false) {
245            $parts = explode('/', $config['format']);
246            $identifier = end($parts);
247        }
248
249        return ModeRegistry::getInstance()->getByFormatCode($identifier);
250    }
251}
252