‪TYPO3CMS  11.5
RichTextElement.php
Go to the documentation of this file.
1 <?php
2 
3 declare(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 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
33 
39 {
45  protected ‪$defaultFieldInformation = [
46  'tcaDescription' => [
47  'renderType' => 'tcaDescription',
48  ],
49  ];
50 
56  protected ‪$defaultFieldWizard = [
57  'localizationStateSelector' => [
58  'renderType' => 'localizationStateSelector',
59  ],
60  'otherLanguageContent' => [
61  'renderType' => 'otherLanguageContent',
62  'after' => [
63  'localizationStateSelector',
64  ],
65  ],
66  'defaultLanguageDifferences' => [
67  'renderType' => 'defaultLanguageDifferences',
68  'after' => [
69  'otherLanguageContent',
70  ],
71  ],
72  ];
73 
80  protected ‪$rteConfiguration = [];
81 
85  protected ‪$eventDispatcher;
86 
94  public function ‪__construct(‪NodeFactory ‪$nodeFactory, array ‪$data, EventDispatcherInterface ‪$eventDispatcher = null)
95  {
96  parent::__construct(‪$nodeFactory, ‪$data);
97  $this->eventDispatcher = ‪$eventDispatcher ?? GeneralUtility::makeInstance(EventDispatcherInterface::class);
98  }
99 
106  public function ‪render(): array
107  {
108  $resultArray = $this->‪initializeResultArray();
109  $parameterArray = $this->data['parameterArray'];
110  $config = $parameterArray['fieldConf']['config'];
111 
112  $fieldId = $this->‪sanitizeFieldId($parameterArray['itemFormElName']);
113  $itemFormElementName = $this->data['parameterArray']['itemFormElName'];
114 
115  $value = $this->data['parameterArray']['itemFormElValue'] ?? '';
116 
117  $fieldInformationResult = $this->‪renderFieldInformation();
118  $fieldInformationHtml = $fieldInformationResult['html'];
119  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
120 
121  $fieldControlResult = $this->‪renderFieldControl();
122  $fieldControlHtml = $fieldControlResult['html'];
123  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
124 
125  $fieldWizardResult = $this->‪renderFieldWizard();
126  $fieldWizardHtml = $fieldWizardResult['html'];
127  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
128 
129  $attributes = [
130  'style' => 'display:none',
131  'data-formengine-validation-rules' => $this->‪getValidationDataAsJsonString($config),
132  'id' => $fieldId,
133  'name' => htmlspecialchars($itemFormElementName),
134  ];
135 
136  $html = [];
137  $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
138  $html[] = $fieldInformationHtml;
139  $html[] = '<div class="form-control-wrap">';
140  $html[] = '<div class="form-wizards-wrap">';
141  $html[] = '<div class="form-wizards-element">';
142  $html[] = '<textarea ' . GeneralUtility::implodeAttributes($attributes, true) . '>';
143  $html[] = htmlspecialchars($value);
144  $html[] = '</textarea>';
145  $html[] = '</div>';
146  if (!empty($fieldControlHtml)) {
147  $html[] = '<div class="form-wizards-items-aside form-wizards-items-aside--field-control">';
148  $html[] = '<div class="btn-group">';
149  $html[] = $fieldControlHtml;
150  $html[] = '</div>';
151  $html[] = '</div>';
152  }
153  if (!empty($fieldWizardHtml)) {
154  $html[] = '<div class="form-wizards-items-bottom">';
155  $html[] = $fieldWizardHtml;
156  $html[] = '</div>';
157  }
158  $html[] = '</div>';
159  $html[] = '</div>';
160  $html[] = '</div>';
161 
162  $resultArray['html'] = implode(LF, $html);
163 
164  $this->rteConfiguration = $config['richtextConfiguration']['editor'] ?? [];
165  $resultArray['requireJsModules'][] = $this->‪loadCkEditorRequireJsModule($fieldId);
166 
167  return $resultArray;
168  }
169 
175  protected function ‪getLanguageIsoCodeOfContent(): string
176  {
177  $currentLanguageUid = ($this->data['databaseRow']['sys_language_uid'] ?? 0);
178  if (is_array($currentLanguageUid)) {
179  $currentLanguageUid = $currentLanguageUid[0];
180  }
181  $contentLanguageUid = (int)max($currentLanguageUid, 0);
182  if ($contentLanguageUid) {
183  // the language rows might not be fully inialized, so we fallback to en_US in this case
184  $contentLanguage = $this->data['systemLanguageRows'][$currentLanguageUid]['iso'] ?? 'en_US';
185  } else {
186  $contentLanguage = $this->rteConfiguration['config']['defaultContentLanguage'] ?? 'en_US';
187  }
188  $languageCodeParts = explode('_', $contentLanguage);
189  $contentLanguage = strtolower($languageCodeParts[0]) . (!empty($languageCodeParts[1]) ? '_' . strtoupper($languageCodeParts[1]) : '');
190  // Find the configured language in the list of localization locales
191  $locales = GeneralUtility::makeInstance(Locales::class);
192  // If not found, default to 'en'
193  if (!in_array($contentLanguage, $locales->getLocales(), true)) {
194  $contentLanguage = 'en';
195  }
196  return $contentLanguage;
197  }
198 
206  protected function ‪loadCkEditorRequireJsModule(string $fieldId): JavaScriptModuleInstruction
207  {
208  $configuration = $this->‪prepareConfigurationForEditor();
209 
210  $externalPlugins = [];
211  foreach ($this->‪getExtraPlugins() as $extraPluginName => $extraPluginConfig) {
212  $configName = $extraPluginConfig['configName'] ?? $extraPluginName;
213  if (!empty($extraPluginConfig['config']) && is_array($extraPluginConfig['config'])) {
214  if (empty($configuration[$configName])) {
215  $configuration[$configName] = $extraPluginConfig['config'];
216  } elseif (is_array($configuration[$configName])) {
217  $configuration[$configName] = array_replace_recursive($extraPluginConfig['config'], $configuration[$configName]);
218  }
219  }
220  $configuration['extraPlugins'] = ($configuration['extraPlugins'] ?? '') . ',' . $extraPluginName;
221  if (isset($this->data['parameterArray']['fieldConf']['config']['placeholder'])) {
222  $configuration['editorplaceholder'] = (string)$this->data['parameterArray']['fieldConf']['config']['placeholder'];
223  }
224 
225  $externalPlugins[] = [
226  'name' => $extraPluginName,
227  'resource' => $extraPluginConfig['resource'],
228  ];
229  }
230 
231  // Make a hash of the configuration and append it to CKEDITOR.timestamp
232  // This will mitigate browser caching issue when plugins are updated
233  $configurationHash = md5((string)json_encode($configuration));
234  return ‪JavaScriptModuleInstruction::forRequireJS('TYPO3/CMS/RteCkeditor/FormEngineInitializer', 'FormEngineInitializer')
235  ->invoke('initializeCKEditor', [
236  'fieldId' => $fieldId,
237  'configuration' => $configuration,
238  'configurationHash' => $configurationHash,
239  'externalPlugins' => $externalPlugins,
240  ]);
241  }
242 
248  protected function ‪getExtraPlugins(): array
249  {
250  $externalPlugins = $this->rteConfiguration['externalPlugins'] ?? [];
251  $externalPlugins = $this->eventDispatcher
252  ->dispatch(new BeforeGetExternalPluginsEvent($externalPlugins, $this->data))
253  ->getConfiguration();
254 
255  $urlParameters = [
256  'P' => [
257  'table' => $this->data['tableName'],
258  'uid' => $this->data['databaseRow']['uid'],
259  'fieldName' => $this->data['fieldName'],
260  'recordType' => $this->data['recordTypeValue'],
261  'pid' => $this->data['effectivePid'],
262  'richtextConfigurationName' => $this->data['parameterArray']['fieldConf']['config']['richtextConfigurationName'],
263  ],
264  ];
265 
266  $pluginConfiguration = [];
267  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
268  foreach ($externalPlugins as $pluginName => $configuration) {
269  $pluginConfiguration[$pluginName] = [
270  'configName' => $configuration['configName'] ?? $pluginName,
271  'resource' => $this->‪resolveUrlPath($configuration['resource']),
272  ];
273  unset($configuration['configName']);
274  unset($configuration['resource']);
275 
276  if ($configuration['route'] ?? null) {
277  $configuration['routeUrl'] = (string)$uriBuilder->buildUriFromRoute($configuration['route'], $urlParameters);
278  }
279 
280  $pluginConfiguration[$pluginName]['config'] = $configuration;
281  }
282 
283  $pluginConfiguration = $this->eventDispatcher
284  ->dispatch(new AfterGetExternalPluginsEvent($pluginConfiguration, $this->data))
285  ->getConfiguration();
286  return $pluginConfiguration;
287  }
288 
295  protected function ‪replaceLanguageFileReferences(array $configuration): array
296  {
297  foreach ($configuration as $key => $value) {
298  if (is_array($value)) {
299  $configuration[$key] = $this->‪replaceLanguageFileReferences($value);
300  } elseif (is_string($value) && stripos($value, 'LLL:') === 0) {
301  $configuration[$key] = $this->‪getLanguageService()->‪sL($value);
302  }
303  }
304  return $configuration;
305  }
306 
313  protected function ‪replaceAbsolutePathsToRelativeResourcesPath(array $configuration): array
314  {
315  foreach ($configuration as $key => $value) {
316  if (is_array($value)) {
317  $configuration[$key] = $this->‪replaceAbsolutePathsToRelativeResourcesPath($value);
318  } elseif (is_string($value) && ‪PathUtility::isExtensionPath(strtoupper($value))) {
319  $configuration[$key] = $this->‪resolveUrlPath($value);
320  }
321  }
322  return $configuration;
323  }
324 
331  protected function ‪resolveUrlPath(string $value): string
332  {
334  }
335 
342  protected function ‪prepareConfigurationForEditor(): array
343  {
344  // Ensure custom config is empty so nothing additional is loaded
345  // Of course this can be overridden by the editor configuration below
346  $configuration = [
347  'customConfig' => '',
348  ];
349 
350  if ($this->data['parameterArray']['fieldConf']['config']['readOnly'] ?? false) {
351  $configuration['readOnly'] = true;
352  }
353 
354  if (is_array($this->rteConfiguration['config'] ?? null)) {
355  $configuration = array_replace_recursive($configuration, $this->rteConfiguration['config']);
356  }
357 
358  $configuration = $this->eventDispatcher
359  ->dispatch(new BeforePrepareConfigurationForEditorEvent($configuration, $this->data))
360  ->getConfiguration();
361 
362  // Set the UI language of the editor if not hard-coded by the existing configuration
363  if (empty($configuration['language'])) {
364  $userLang = (string)($this->‪getBackendUser()->user['lang'] ?: 'en');
365  $configuration['language'] = $userLang === 'default' ? 'en' : $userLang;
366  }
367  $configuration['contentsLanguage'] = $this->‪getLanguageIsoCodeOfContent();
368 
369  // Replace all label references
370  $configuration = $this->‪replaceLanguageFileReferences($configuration);
371  // Replace all paths
372  $configuration = $this->‪replaceAbsolutePathsToRelativeResourcesPath($configuration);
373 
374  // there are some places where we define an array, but it needs to be a list in order to work
375  if (is_array($configuration['extraPlugins'] ?? null)) {
376  $configuration['extraPlugins'] = implode(',', $configuration['extraPlugins']);
377  }
378  if (is_array($configuration['removePlugins'] ?? null)) {
379  $configuration['removePlugins'] = implode(',', $configuration['removePlugins']);
380  }
381  if (is_array($configuration['removeButtons'] ?? null)) {
382  $configuration['removeButtons'] = implode(',', $configuration['removeButtons']);
383  }
384 
385  $configuration = $this->eventDispatcher
386  ->dispatch(new AfterPrepareConfigurationForEditorEvent($configuration, $this->data))
387  ->getConfiguration();
388 
389  return $configuration;
390  }
391 
396  protected function ‪sanitizeFieldId(string $itemFormElementName): string
397  {
398  $fieldId = (string)preg_replace('/[^a-zA-Z0-9_:.-]/', '_', $itemFormElementName);
399  return htmlspecialchars((string)preg_replace('/^[^a-zA-Z]/', 'x', $fieldId));
400  }
401 
405  protected function ‪getBackendUser()
406  {
407  return ‪$GLOBALS['BE_USER'];
408  }
409 }
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldInformation
‪array renderFieldInformation()
Definition: AbstractFormElement.php:76
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:25
‪TYPO3\CMS\RteCKEditor\Form\Element
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\replaceLanguageFileReferences
‪array replaceLanguageFileReferences(array $configuration)
Definition: RichTextElement.php:291
‪TYPO3\CMS\Backend\Form\AbstractNode\mergeChildReturnIntoExistingResult
‪array mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
Definition: AbstractNode.php:120
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪array initializeResultArray()
Definition: AbstractNode.php:91
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement
Definition: RichTextElement.php:39
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\replaceAbsolutePathsToRelativeResourcesPath
‪array replaceAbsolutePathsToRelativeResourcesPath(array $configuration)
Definition: RichTextElement.php:309
‪TYPO3\CMS\Core\Utility\PathUtility\isExtensionPath
‪static bool isExtensionPath(string $path)
Definition: PathUtility.php:121
‪TYPO3\CMS\Backend\Form\AbstractNode\$nodeFactory
‪NodeFactory $nodeFactory
Definition: AbstractNode.php:36
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\$rteConfiguration
‪array $rteConfiguration
Definition: RichTextElement.php:77
‪TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforeGetExternalPluginsEvent
Definition: BeforeGetExternalPluginsEvent.php:24
‪TYPO3\CMS\Core\Utility\PathUtility\getPublicResourceWebPath
‪static string getPublicResourceWebPath(string $resourcePath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:98
‪TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterPrepareConfigurationForEditorEvent
Definition: AfterPrepareConfigurationForEditorEvent.php:24
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement
Definition: AbstractFormElement.php:35
‪TYPO3\CMS\Core\Localization\Locales
Definition: Locales.php:30
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction
Definition: JavaScriptModuleInstruction.php:23
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:161
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\resolveUrlPath
‪string resolveUrlPath(string $value)
Definition: RichTextElement.php:327
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\render
‪array render()
Definition: RichTextElement.php:102
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction\forRequireJS
‪static self forRequireJS(string $name, string $exportName=null)
Definition: JavaScriptModuleInstruction.php:49
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\sanitizeFieldId
‪string sanitizeFieldId(string $itemFormElementName)
Definition: RichTextElement.php:392
‪TYPO3\CMS\Backend\Form\AbstractNode\getValidationDataAsJsonString
‪string getValidationDataAsJsonString(array $config)
Definition: AbstractNode.php:156
‪TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterGetExternalPluginsEvent
Definition: AfterGetExternalPluginsEvent.php:24
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldControl
‪array renderFieldControl()
Definition: AbstractFormElement.php:92
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\getLanguageIsoCodeOfContent
‪string getLanguageIsoCodeOfContent()
Definition: RichTextElement.php:171
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:40
‪TYPO3\CMS\Backend\Form\AbstractNode\$data
‪array $data
Definition: AbstractNode.php:43
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Backend\Form\NodeFactory
Definition: NodeFactory.php:37
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\$defaultFieldInformation
‪array $defaultFieldInformation
Definition: RichTextElement.php:44
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\$eventDispatcher
‪EventDispatcherInterface $eventDispatcher
Definition: RichTextElement.php:81
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\$defaultFieldWizard
‪array $defaultFieldWizard
Definition: RichTextElement.php:54
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\loadCkEditorRequireJsModule
‪JavaScriptModuleInstruction loadCkEditorRequireJsModule(string $fieldId)
Definition: RichTextElement.php:202
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\getExtraPlugins
‪array getExtraPlugins()
Definition: RichTextElement.php:244
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\getLanguageService
‪LanguageService getLanguageService()
Definition: AbstractFormElement.php:448
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\prepareConfigurationForEditor
‪array prepareConfigurationForEditor()
Definition: RichTextElement.php:338
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\__construct
‪__construct(NodeFactory $nodeFactory, array $data, EventDispatcherInterface $eventDispatcher=null)
Definition: RichTextElement.php:90
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\RteCKEditor\Form\Element\Event\BeforePrepareConfigurationForEditorEvent
Definition: BeforePrepareConfigurationForEditorEvent.php:24
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldWizard
‪array renderFieldWizard()
Definition: AbstractFormElement.php:108
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\getBackendUser
‪BackendUserAuthentication getBackendUser()
Definition: RichTextElement.php:401