[!!!][TASK] Prepare richtext configuration
[Packages/TYPO3.CMS.git] / typo3 / sysext / rte_ckeditor / Classes / Form / Element / RichTextElement.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\RteCKEditor\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
18 use TYPO3\CMS\Backend\Form\Element\AbstractFormElement;
19 use TYPO3\CMS\Backend\Routing\UriBuilder;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22 use TYPO3\CMS\Core\Database\ConnectionPool;
23 use TYPO3\CMS\Core\Localization\Locales;
24 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Core\Utility\PathUtility;
27 use TYPO3\CMS\Lang\LanguageService;
28
29 /**
30 * Render rich text editor in FormEngine
31 */
32 class RichTextElement extends AbstractFormElement
33 {
34
35 /**
36 * pid of fixed versioned record.
37 * This is the pid of the record in normal cases, but is changed to the pid
38 * of the "mother" record in case the handled record is a versioned overlay
39 * and "mother" is located at a different pid.
40 *
41 * @var int
42 */
43 protected $pidOfVersionedMotherRecord;
44
45 /**
46 * RTE configuration
47 * This property contains "processed" configuration
48 * where table and type specific RTE setup is merged into 'default.' array.
49 *
50 * @var array
51 */
52 protected $rteConfiguration = [];
53
54 /**
55 * Renders the ckeditor element
56 *
57 * @return array
58 * @throws \InvalidArgumentException
59 */
60 public function render() : array
61 {
62 $resultArray = $this->initializeResultArray();
63 $row = $this->data['databaseRow'];
64 BackendUtility::fixVersioningPid($this->data['tableName'], $row);
65 $this->pidOfVersionedMotherRecord = (int)$row['pid'];
66
67 $resourcesPath = PathUtility::getAbsoluteWebPath(
68 ExtensionManagementUtility::extPath('rte_ckeditor', 'Resources/Public/')
69 );
70 $table = $this->data['tableName'];
71 $row = $this->data['databaseRow'];
72 $parameterArray = $this->data['parameterArray'];
73 $defaultExtras = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
74 BackendUtility::fixVersioningPid($table, $row);
75
76 $fieldId = $this->sanitizeFieldId($parameterArray['itemFormElName']);
77 $resultArray['html'] = $this->renderWizards(
78 [$this->getHtml($fieldId)],
79 $parameterArray['fieldConf']['config']['wizards'],
80 $table,
81 $row,
82 $this->data['fieldName'],
83 $parameterArray,
84 $parameterArray['itemFormElName'],
85 $defaultExtras,
86 true
87 );
88
89 $this->rteConfiguration = $parameterArray['fieldConf']['config']['richtextConfiguration'];
90
91 $resultArray['requireJsModules'] = [];
92 $resultArray['requireJsModules'][] =[
93 'ckeditor' => $this->getCkEditorRequireJsModuleCode($resourcesPath, $fieldId)
94 ];
95
96 return $resultArray;
97 }
98
99 /**
100 * Determine the contents language iso code
101 *
102 * @return string
103 */
104 protected function getContentsLanguage()
105 {
106 $language = $this->getLanguageService()->lang;
107 if ($language === 'default' || !$language) {
108 $language = 'en';
109 }
110 $currentLanguageUid = $this->data['databaseRow']['sys_language_uid'];
111 if (is_array($currentLanguageUid)) {
112 $currentLanguageUid = $currentLanguageUid[0];
113 }
114 $contentLanguageUid = (int)max($currentLanguageUid, 0);
115 if ($contentLanguageUid) {
116 $contentISOLanguage = $language;
117 if (ExtensionManagementUtility::isLoaded('static_info_tables')) {
118 $tableA = 'sys_language';
119 $tableB = 'static_languages';
120
121 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
122 ->getQueryBuilderForTable($tableA);
123
124 $result = $queryBuilder
125 ->select('a.uid', 'b.lg_iso_2', 'b.lg_country_iso_2')
126 ->from($tableA, 'a')
127 ->where('a.uid', (int)$contentLanguageUid)
128 ->leftJoin(
129 'a',
130 $tableB,
131 'b',
132 $queryBuilder->expr()->eq('a.static_lang_isocode', $queryBuilder->quoteIdentifier('b.uid'))
133 )
134 ->execute();
135
136 while ($languageRow = $result->fetch()) {
137 $contentISOLanguage = strtolower(trim($languageRow['lg_iso_2']) . (trim($languageRow['lg_country_iso_2']) ? '_' . trim($languageRow['lg_country_iso_2']) : ''));
138 }
139 }
140 } else {
141 $contentISOLanguage = trim($this->rteConfiguration['defaultContentLanguage'] ?? '') ?: 'en';
142 $languageCodeParts = explode('_', $contentISOLanguage);
143 $contentISOLanguage = strtolower($languageCodeParts[0]) . ($languageCodeParts[1] ? '_' . strtoupper($languageCodeParts[1]) : '');
144 // Find the configured language in the list of localization locales
145 /** @var $locales Locales */
146 $locales = GeneralUtility::makeInstance(Locales::class);
147 // If not found, default to 'en'
148 if (!in_array($contentISOLanguage, $locales->getLocales(), true)) {
149 $contentISOLanguage = 'en';
150 }
151 }
152 return $contentISOLanguage;
153 }
154
155 /**
156 * Gets the JavaScript code for CKEditor module
157 *
158 * @param string $resourcesPath
159 * @param string $fieldId
160 * @return string
161 */
162 protected function getCkEditorRequireJsModuleCode(string $resourcesPath, string $fieldId) : string
163 {
164 $customConfig = [
165 'contentsCss' => $resourcesPath . 'Css/contents.css',
166 'customConfig' => $resourcesPath . 'JavaScript/defaultconfig.js',
167 'toolbar' => 'Basic',
168 'uiColor' => '#F8F8F8',
169 'stylesSet' => 'default',
170 'extraPlugins' => '',
171 'RTEtsConfigParams' => $this->getRTEtsConfigParams(),
172 'contentsLanguage' => $this->getContentsLanguage(),
173 ];
174
175 $externalPlugins = '';
176 foreach ($this->getExternalPlugins() as $pluginName => $config) {
177 $customConfig[$pluginName] = $config['config'];
178 $customConfig['extraPlugins'] .= ',' . $pluginName;
179
180 $externalPlugins .= 'CKEDITOR.plugins.addExternal(';
181 $externalPlugins .= GeneralUtility::quoteJSvalue($pluginName) . ',';
182 $externalPlugins .= GeneralUtility::quoteJSvalue($config['path']) . ',';
183 $externalPlugins .= '\'\');';
184 }
185
186 return 'function(CKEDITOR) {
187 CKEDITOR.config.height = 400;
188 CKEDITOR.contentsCss = "' . $resourcesPath . 'Css/contents.css";
189 CKEDITOR.config.width = "auto";
190 ' . $externalPlugins . '
191 CKEDITOR.replace("' . $fieldId . '", ' . json_encode($customConfig) . ');
192 }';
193 }
194
195 /**
196 * Create <textarea> element
197 *
198 * @param string $fieldId
199 * @return string Main HTML to render
200 */
201 protected function getHtml(string $fieldId) : string
202 {
203 $itemFormElementName = $this->data['parameterArray']['itemFormElName'];
204 $value = $this->data['parameterArray']['itemFormElValue'] ?? '';
205
206 return '<textarea style="display:none" ' . $this->getValidationDataAsDataAttribute($this->data['parameterArray']['fieldConf']['config']) . ' id="' . $fieldId . '" name="' . htmlspecialchars($itemFormElementName) . '">' . htmlspecialchars($value) . '</textarea>';
207 }
208
209 /**
210 * A list of parameters that is mostly given as GET/POST to other RTE controllers.
211 *
212 * @return string
213 */
214 protected function getRTEtsConfigParams() : string
215 {
216 $result = [
217 $this->data['tableName'],
218 $this->data['databaseRow']['uid'],
219 $this->data['fieldName'],
220 $this->pidOfVersionedMotherRecord,
221 $this->data['recordTypeValue'],
222 $this->data['effectivePid'],
223 ];
224 return implode(':', $result);
225 }
226
227 /**
228 * Get configuration of external/additional plugins
229 *
230 * @return array
231 */
232 protected function getExternalPlugins() : array
233 {
234 // todo: find new name for this option (do we still need this?)
235 // Initializing additional attributes
236 $additionalAttributes = [];
237 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['rte_ckeditor']['plugins']['TYPO3Link']['additionalAttributes']) {
238 $additionalAttributes = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['rte_ckeditor']['plugins']['TYPO3Link']['additionalAttributes'], true);
239 }
240
241 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
242 // todo: add api for this https://forge.typo3.org/issues/78929
243 $pluginPath = PathUtility::getAbsoluteWebPath(
244 ExtensionManagementUtility::extPath('rte_ckeditor', 'Resources/Public/JavaScript/Plugins/typo3link.js')
245 );
246 $externalPlugins = [
247 'typo3link' => [
248 'path' => $pluginPath,
249 'config' => [
250 'routeUrl' => (string)$uriBuilder->buildUriFromRoute('rteckeditor_wizard_browse_links'),
251 'additionalAttributes' => $additionalAttributes
252 ]
253 ]
254 ];
255
256 return $externalPlugins;
257 }
258
259 /**
260 * @return LanguageService
261 */
262 protected function getLanguageService() : LanguageService
263 {
264 return $GLOBALS['LANG'];
265 }
266
267 /**
268 * @return BackendUserAuthentication
269 */
270 protected function getBackendUserAuthentication() : BackendUserAuthentication
271 {
272 return $GLOBALS['BE_USER'];
273 }
274
275 /**
276 * @param string $itemFormElementName
277 * @return string
278 */
279 protected function sanitizeFieldId(string $itemFormElementName) : string
280 {
281 $fieldId = preg_replace('/[^a-zA-Z0-9_:.-]/', '_', $itemFormElementName);
282 return htmlspecialchars(preg_replace('/^[^a-zA-Z]/', 'x', $fieldId));
283 }
284 }