‪TYPO3CMS  9.5
RichTextElement.php
Go to the documentation of this file.
1 <?php
2 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 
24 
30 {
36  protected ‪$defaultFieldInformation = [
37  'tcaDescription' => [
38  'renderType' => 'tcaDescription',
39  ],
40  ];
41 
47  protected ‪$defaultFieldWizard = [
48  'localizationStateSelector' => [
49  'renderType' => 'localizationStateSelector',
50  ],
51  'otherLanguageContent' => [
52  'renderType' => 'otherLanguageContent',
53  'after' => [
54  'localizationStateSelector'
55  ],
56  ],
57  'defaultLanguageDifferences' => [
58  'renderType' => 'defaultLanguageDifferences',
59  'after' => [
60  'otherLanguageContent',
61  ],
62  ],
63  ];
64 
71  protected ‪$rteConfiguration = [];
72 
79  public function ‪render(): array
80  {
81  $resultArray = $this->‪initializeResultArray();
82  $parameterArray = $this->data['parameterArray'];
83  $config = $parameterArray['fieldConf']['config'];
84 
85  $fieldId = $this->‪sanitizeFieldId($parameterArray['itemFormElName']);
86  $itemFormElementName = $this->data['parameterArray']['itemFormElName'];
87 
88  $value = $this->data['parameterArray']['itemFormElValue'] ?? '';
89 
90  $fieldInformationResult = $this->‪renderFieldInformation();
91  $fieldInformationHtml = $fieldInformationResult['html'];
92  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
93 
94  $fieldControlResult = $this->‪renderFieldControl();
95  $fieldControlHtml = $fieldControlResult['html'];
96  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
97 
98  $fieldWizardResult = $this->‪renderFieldWizard();
99  $fieldWizardHtml = $fieldWizardResult['html'];
100  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
101 
102  $attributes = [
103  'style' => 'display:none',
104  'data-formengine-validation-rules' => $this->‪getValidationDataAsJsonString($config),
105  'id' => $fieldId,
106  'name' => htmlspecialchars($itemFormElementName),
107  ];
108 
109  $html = [];
110  $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
111  $html[] = $fieldInformationHtml;
112  $html[] = '<div class="form-control-wrap">';
113  $html[] = '<div class="form-wizards-wrap">';
114  $html[] = '<div class="form-wizards-element">';
115  $html[] = '<textarea ' . GeneralUtility::implodeAttributes($attributes, true) . '>';
116  $html[] = htmlspecialchars($value);
117  $html[] = '</textarea>';
118  $html[] = '</div>';
119  if (!empty($fieldControlHtml)) {
120  $html[] = '<div class="form-wizards-items-aside">';
121  $html[] = '<div class="btn-group">';
122  $html[] = $fieldControlHtml;
123  $html[] = '</div>';
124  $html[] = '</div>';
125  }
126  if (!empty($fieldWizardHtml)) {
127  $html[] = '<div class="form-wizards-items-bottom">';
128  $html[] = $fieldWizardHtml;
129  $html[] = '</div>';
130  }
131  $html[] = '</div>';
132  $html[] = '</div>';
133  $html[] = '</div>';
134 
135  $resultArray['html'] = implode(LF, $html);
136 
137  $this->rteConfiguration = $config['richtextConfiguration']['editor'];
138  $resultArray['requireJsModules'][] = [
139  'ckeditor' => $this->‪getCkEditorRequireJsModuleCode($fieldId)
140  ];
141 
142  return $resultArray;
143  }
144 
150  protected function ‪getLanguageIsoCodeOfContent(): string
151  {
152  $currentLanguageUid = $this->data['databaseRow']['sys_language_uid'];
153  if (is_array($currentLanguageUid)) {
154  $currentLanguageUid = $currentLanguageUid[0];
155  }
156  $contentLanguageUid = (int)max($currentLanguageUid, 0);
157  if ($contentLanguageUid) {
158  $contentLanguage = $this->data['systemLanguageRows'][$currentLanguageUid]['iso'];
159  } else {
160  $contentLanguage = $this->rteConfiguration['config']['defaultContentLanguage'] ?? 'en_US';
161  $languageCodeParts = explode('_', $contentLanguage);
162  $contentLanguage = strtolower($languageCodeParts[0]) . ($languageCodeParts[1] ? '_' . strtoupper($languageCodeParts[1]) : '');
163  // Find the configured language in the list of localization locales
164  ‪$locales = GeneralUtility::makeInstance(Locales::class);
165  // If not found, default to 'en'
166  if (!in_array($contentLanguage, ‪$locales->getLocales(), true)) {
167  $contentLanguage = 'en';
168  }
169  }
170  return $contentLanguage;
171  }
172 
180  protected function ‪getCkEditorRequireJsModuleCode(string $fieldId): string
181  {
182  $configuration = $this->‪prepareConfigurationForEditor();
183 
184  $externalPlugins = '';
185  foreach ($this->‪getExtraPlugins() as $extraPluginName => $extraPluginConfig) {
186  $configName = $extraPluginConfig['configName'] ?? $extraPluginName;
187  if (!empty($extraPluginConfig['config']) && is_array($extraPluginConfig['config'])) {
188  if (empty($configuration[$configName])) {
189  $configuration[$configName] = $extraPluginConfig['config'];
190  } elseif (is_array($configuration[$configName])) {
191  $configuration[$configName] = array_replace_recursive($extraPluginConfig['config'], $configuration[$configName]);
192  }
193  }
194  $configuration['extraPlugins'] .= ',' . $extraPluginName;
195 
196  $externalPlugins .= 'CKEDITOR.plugins.addExternal(';
197  $externalPlugins .= GeneralUtility::quoteJSvalue($extraPluginName) . ',';
198  $externalPlugins .= GeneralUtility::quoteJSvalue($extraPluginConfig['resource']) . ',';
199  $externalPlugins .= '\'\');';
200  }
201 
202  $jsonConfiguration = json_encode($configuration);
203 
204  // Make a hash of the configuration and append it to CKEDITOR.timestamp
205  // This will mitigate browser caching issue when plugins are updated
206  $configurationHash = GeneralUtility::shortMD5($jsonConfiguration);
207 
208  return 'function(CKEDITOR) {
209  CKEDITOR.timestamp += "-' . $configurationHash . '";
210  ' . $externalPlugins . '
211  require([\'jquery\', \'TYPO3/CMS/Backend/FormEngine\'], function($, FormEngine) {
212  $(function(){
213  var escapedFieldSelector = \'#\' + $.escapeSelector(\'' . $fieldId . '\');
214  CKEDITOR.replace("' . $fieldId . '", ' . $jsonConfiguration . ');
215  CKEDITOR.instances["' . $fieldId . '"].on(\'change\', function(e) {
216  var commands = e.sender.commands;
217  CKEDITOR.instances["' . $fieldId . '"].updateElement();
218  FormEngine.Validation.validate();
219  FormEngine.Validation.markFieldAsChanged($(escapedFieldSelector));
220 
221  // remember changes done in maximized state and mark field as changed, once minimized again
222  if (typeof commands.maximize !== \'undefined\' && commands.maximize.state === 1) {
223  CKEDITOR.instances["' . $fieldId . '"].on(\'maximize\', function(e) {
224  $(this).off(\'maximize\');
225  FormEngine.Validation.markFieldAsChanged($(escapedFieldSelector));
226  });
227  }
228  });
229  CKEDITOR.instances["' . $fieldId . '"].on(\'mode\', function() {
230  // detect field changes in source mode
231  if (this.mode === \'source\') {
232  var sourceArea = CKEDITOR.instances["' . $fieldId . '"].editable();
233  sourceArea.attachListener(sourceArea, \'change\', function() {
234  FormEngine.Validation.markFieldAsChanged($(escapedFieldSelector));
235  });
236  }
237  });
238  $(document).on(\'inline:sorting-changed\', function() {
239  CKEDITOR.instances["' . $fieldId . '"].destroy();
240  CKEDITOR.replace("' . $fieldId . '", ' . $jsonConfiguration . ');
241  });
242  $(document).on(\'flexform:sorting-changed\', function() {
243  CKEDITOR.instances["' . $fieldId . '"].destroy();
244  CKEDITOR.replace("' . $fieldId . '", ' . $jsonConfiguration . ');
245  });
246  });
247  });
248  }';
249  }
250 
256  protected function ‪getExtraPlugins(): array
257  {
258  $urlParameters = [
259  'P' => [
260  'table' => $this->data['tableName'],
261  'uid' => $this->data['databaseRow']['uid'],
262  'fieldName' => $this->data['fieldName'],
263  'recordType' => $this->data['recordTypeValue'],
264  'pid' => $this->data['effectivePid'],
265  'richtextConfigurationName' => $this->data['parameterArray']['fieldConf']['config']['richtextConfigurationName']
266  ]
267  ];
268 
269  $pluginConfiguration = [];
270  if (isset($this->rteConfiguration['externalPlugins']) && is_array($this->rteConfiguration['externalPlugins'])) {
271  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
272  foreach ($this->rteConfiguration['externalPlugins'] as $pluginName => $configuration) {
273  $pluginConfiguration[$pluginName] = [
274  'configName' => $configuration['configName'] ?? $pluginName,
275  'resource' => $this->‪resolveUrlPath($configuration['resource'])
276  ];
277  unset($configuration['configName'], $configuration['resource']);
278 
279  if ($configuration['route']) {
280  $configuration['routeUrl'] = (string)$uriBuilder->buildUriFromRoute($configuration['route'], $urlParameters);
281  }
282 
283  $pluginConfiguration[$pluginName]['config'] = $configuration;
284  }
285  }
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) && stripos($value, 'EXT:') === 0) {
319  $configuration[$key] = $this->‪resolveUrlPath($value);
320  }
321  }
322  return $configuration;
323  }
324 
331  protected function ‪resolveUrlPath(string $value): string
332  {
333  $value = GeneralUtility::getFileAbsFileName($value);
334  return ‪PathUtility::getAbsoluteWebPath($value);
335  }
336 
343  protected function ‪prepareConfigurationForEditor(): array
344  {
345  // Ensure custom config is empty so nothing additional is loaded
346  // Of course this can be overridden by the editor configuration below
347  $configuration = [
348  'customConfig' => '',
349  ];
350 
351  if (is_array($this->rteConfiguration['config'])) {
352  $configuration = array_replace_recursive($configuration, $this->rteConfiguration['config']);
353  }
354  // Set the UI language of the editor if not hard-coded by the existing configuration
355  if (empty($configuration['language'])) {
356  $configuration['language'] = $this->‪getBackendUser()->uc['lang'] ?: ($this->‪getBackendUser()->user['lang'] ?: 'en');
357  }
358  $configuration['contentsLanguage'] = $this->‪getLanguageIsoCodeOfContent();
359 
360  // Replace all label references
361  $configuration = $this->‪replaceLanguageFileReferences($configuration);
362  // Replace all paths
363  $configuration = $this->‪replaceAbsolutePathsToRelativeResourcesPath($configuration);
364 
365  // there are some places where we define an array, but it needs to be a list in order to work
366  if (is_array($configuration['extraPlugins'])) {
367  $configuration['extraPlugins'] = implode(',', $configuration['extraPlugins']);
368  }
369  if (is_array($configuration['removePlugins'])) {
370  $configuration['removePlugins'] = implode(',', $configuration['removePlugins']);
371  }
372  if (is_array($configuration['removeButtons'])) {
373  $configuration['removeButtons'] = implode(',', $configuration['removeButtons']);
374  }
375 
376  return $configuration;
377  }
378 
383  protected function ‪sanitizeFieldId(string $itemFormElementName): string
384  {
385  $fieldId = preg_replace('/[^a-zA-Z0-9_:.-]/', '_', $itemFormElementName);
386  return htmlspecialchars(preg_replace('/^[^a-zA-Z]/', 'x', $fieldId));
387  }
388 
392  protected function ‪getBackendUser()
393  {
394  return ‪$GLOBALS['BE_USER'];
395  }
396 }
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldInformation
‪array renderFieldInformation()
Definition: AbstractFormElement.php:71
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:23
‪TYPO3\CMS\RteCKEditor\Form\Element
Definition: RichTextElement.php:3
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\replaceLanguageFileReferences
‪array replaceLanguageFileReferences(array $configuration)
Definition: RichTextElement.php:292
‪TYPO3\CMS\Backend\Form\AbstractNode\mergeChildReturnIntoExistingResult
‪array mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
Definition: AbstractNode.php:115
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪array initializeResultArray()
Definition: AbstractNode.php:88
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement
Definition: RichTextElement.php:30
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\replaceAbsolutePathsToRelativeResourcesPath
‪array replaceAbsolutePathsToRelativeResourcesPath(array $configuration)
Definition: RichTextElement.php:310
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\$rteConfiguration
‪array $rteConfiguration
Definition: RichTextElement.php:68
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement
Definition: AbstractFormElement.php:31
‪TYPO3\CMS\Core\Localization\Locales
Definition: Locales.php:29
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:158
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\resolveUrlPath
‪string resolveUrlPath(string $value)
Definition: RichTextElement.php:328
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\render
‪array render()
Definition: RichTextElement.php:76
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\sanitizeFieldId
‪string sanitizeFieldId(string $itemFormElementName)
Definition: RichTextElement.php:380
‪TYPO3\CMS\Backend\Form\AbstractNode\getValidationDataAsJsonString
‪string getValidationDataAsJsonString(array $config)
Definition: AbstractNode.php:153
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldControl
‪array renderFieldControl()
Definition: AbstractFormElement.php:87
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\getLanguageIsoCodeOfContent
‪string getLanguageIsoCodeOfContent()
Definition: RichTextElement.php:147
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:35
‪$locales
‪$locales
Definition: be_users.php:6
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:45
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\$defaultFieldInformation
‪array $defaultFieldInformation
Definition: RichTextElement.php:35
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\$defaultFieldWizard
‪array $defaultFieldWizard
Definition: RichTextElement.php:45
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\getCkEditorRequireJsModuleCode
‪string getCkEditorRequireJsModuleCode(string $fieldId)
Definition: RichTextElement.php:177
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\getExtraPlugins
‪array getExtraPlugins()
Definition: RichTextElement.php:253
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\getLanguageService
‪LanguageService getLanguageService()
Definition: AbstractFormElement.php:389
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\prepareConfigurationForEditor
‪array prepareConfigurationForEditor()
Definition: RichTextElement.php:340
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsoluteWebPath
‪static string getAbsoluteWebPath($targetPath)
Definition: PathUtility.php:42
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldWizard
‪array renderFieldWizard()
Definition: AbstractFormElement.php:103
‪TYPO3\CMS\RteCKEditor\Form\Element\RichTextElement\getBackendUser
‪BackendUserAuthentication getBackendUser()
Definition: RichTextElement.php:389