TYPO3 CMS  TYPO3_8-7
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 
29 {
35  protected $defaultFieldWizard = [
36  'localizationStateSelector' => [
37  'renderType' => 'localizationStateSelector',
38  ],
39  'otherLanguageContent' => [
40  'renderType' => 'otherLanguageContent',
41  'after' => [
42  'localizationStateSelector'
43  ],
44  ],
45  'defaultLanguageDifferences' => [
46  'renderType' => 'defaultLanguageDifferences',
47  'after' => [
48  'otherLanguageContent',
49  ],
50  ],
51  ];
52 
59  protected $rteConfiguration = [];
60 
67  public function render(): array
68  {
69  $resultArray = $this->initializeResultArray();
70  $parameterArray = $this->data['parameterArray'];
71  $config = $parameterArray['fieldConf']['config'];
72 
73  $fieldId = $this->sanitizeFieldId($parameterArray['itemFormElName']);
74  $itemFormElementName = $this->data['parameterArray']['itemFormElName'];
75 
76  $value = $this->data['parameterArray']['itemFormElValue'] ?? '';
77 
78  $legacyWizards = $this->renderWizards();
79  $legacyFieldControlHtml = implode(LF, $legacyWizards['fieldControl']);
80  $legacyFieldWizardHtml = implode(LF, $legacyWizards['fieldWizard']);
81 
82  $fieldInformationResult = $this->renderFieldInformation();
83  $fieldInformationHtml = $fieldInformationResult['html'];
84  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
85 
86  $fieldControlResult = $this->renderFieldControl();
87  $fieldControlHtml = $legacyFieldControlHtml . $fieldControlResult['html'];
88  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
89 
90  $fieldWizardResult = $this->renderFieldWizard();
91  $fieldWizardHtml = $legacyFieldWizardHtml . $fieldWizardResult['html'];
92  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
93 
94  $attributes = [
95  'style' => 'display:none',
96  'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
97  'id' => $fieldId,
98  'name' => htmlspecialchars($itemFormElementName),
99  ];
100 
101  $html = [];
102  $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
103  $html[] = $fieldInformationHtml;
104  $html[] = '<div class="form-control-wrap">';
105  $html[] = '<div class="form-wizards-wrap">';
106  $html[] = '<div class="form-wizards-element">';
107  $html[] = '<textarea ' . GeneralUtility::implodeAttributes($attributes, true) . '>';
108  $html[] = htmlspecialchars($value);
109  $html[] = '</textarea>';
110  $html[] = '</div>';
111  $html[] = '<div class="form-wizards-items-aside">';
112  $html[] = '<div class="btn-group">';
113  $html[] = $fieldControlHtml;
114  $html[] = '</div>';
115  $html[] = '</div>';
116  $html[] = '<div class="form-wizards-items-bottom">';
117  $html[] = $fieldWizardHtml;
118  $html[] = '</div>';
119  $html[] = '</div>';
120  $html[] = '</div>';
121  $html[] = '</div>';
122 
123  $resultArray['html'] = implode(LF, $html);
124 
125  $this->rteConfiguration = $config['richtextConfiguration']['editor'];
126  $resultArray['requireJsModules'][] = [
127  'ckeditor' => $this->getCkEditorRequireJsModuleCode($fieldId)
128  ];
129 
130  return $resultArray;
131  }
132 
138  protected function getLanguageIsoCodeOfContent(): string
139  {
140  $currentLanguageUid = $this->data['databaseRow']['sys_language_uid'];
141  if (is_array($currentLanguageUid)) {
142  $currentLanguageUid = $currentLanguageUid[0];
143  }
144  $contentLanguageUid = (int)max($currentLanguageUid, 0);
145  if ($contentLanguageUid) {
146  $contentLanguage = $this->data['systemLanguageRows'][$currentLanguageUid]['iso'];
147  } else {
148  $contentLanguage = $this->rteConfiguration['config']['defaultContentLanguage'] ?? 'en_US';
149  $languageCodeParts = explode('_', $contentLanguage);
150  $contentLanguage = strtolower($languageCodeParts[0]) . ($languageCodeParts[1] ? '_' . strtoupper($languageCodeParts[1]) : '');
151  // Find the configured language in the list of localization locales
152  $locales = GeneralUtility::makeInstance(Locales::class);
153  // If not found, default to 'en'
154  if (!in_array($contentLanguage, $locales->getLocales(), true)) {
155  $contentLanguage = 'en';
156  }
157  }
158  return $contentLanguage;
159  }
160 
168  protected function getCkEditorRequireJsModuleCode(string $fieldId): string
169  {
170  $configuration = $this->prepareConfigurationForEditor();
171 
172  $externalPlugins = '';
173  foreach ($this->getExtraPlugins() as $pluginName => $config) {
174  if (!empty($config['config']) && !empty($configuration[$pluginName])) {
175  $config['config'] = array_replace_recursive($config['config'], $configuration[$pluginName]);
176  }
177  $configuration[$pluginName] = $config['config'];
178  $configuration['extraPlugins'] .= ',' . $pluginName;
179 
180  $externalPlugins .= 'CKEDITOR.plugins.addExternal(';
181  $externalPlugins .= GeneralUtility::quoteJSvalue($pluginName) . ',';
182  $externalPlugins .= GeneralUtility::quoteJSvalue($config['resource']) . ',';
183  $externalPlugins .= '\'\');';
184  }
185 
186  $jsonConfiguration = json_encode($configuration);
187 
188  // Make a hash of the configuration and append it to CKEDITOR.timestamp
189  // This will mitigate browser caching issue when plugins are updated
190  $configurationHash = GeneralUtility::shortMD5($jsonConfiguration);
191 
192  return 'function(CKEDITOR) {
193  CKEDITOR.timestamp += "-' . $configurationHash . '";
194  ' . $externalPlugins . '
195  require([\'jquery\', \'TYPO3/CMS/Backend/FormEngine\'], function($, FormEngine) {
196  $(function(){
197  CKEDITOR.replace("' . $fieldId . '", ' . $jsonConfiguration . ');
198  CKEDITOR.instances["' . $fieldId . '"].on(\'change\', function() {
199  CKEDITOR.instances["' . $fieldId . '"].updateElement();
200  FormEngine.Validation.validate();
201  FormEngine.Validation.markFieldAsChanged($(\'#' . $fieldId . '\'));
202  });
203  $(document).on(\'inline:sorting-changed\', function() {
204  CKEDITOR.instances["' . $fieldId . '"].destroy();
205  CKEDITOR.replace("' . $fieldId . '", ' . $jsonConfiguration . ');
206  });
207  $(document).on(\'flexform:sorting-changed\', function() {
208  CKEDITOR.instances["' . $fieldId . '"].destroy();
209  CKEDITOR.replace("' . $fieldId . '", ' . $jsonConfiguration . ');
210  });
211  });
212  });
213  }';
214  }
215 
221  protected function getExtraPlugins(): array
222  {
223  $urlParameters = [
224  'P' => [
225  'table' => $this->data['tableName'],
226  'uid' => $this->data['databaseRow']['uid'],
227  'fieldName' => $this->data['fieldName'],
228  'recordType' => $this->data['recordTypeValue'],
229  'pid' => $this->data['effectivePid'],
230  'richtextConfigurationName' => $this->data['parameterArray']['fieldConf']['config']['richtextConfigurationName']
231  ]
232  ];
233 
234  $pluginConfiguration = [];
235  if (isset($this->rteConfiguration['externalPlugins']) && is_array($this->rteConfiguration['externalPlugins'])) {
236  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
237  foreach ($this->rteConfiguration['externalPlugins'] as $pluginName => $configuration) {
238  $pluginConfiguration[$pluginName] = [
239  'resource' => $this->resolveUrlPath($configuration['resource'])
240  ];
241  unset($configuration['resource']);
242 
243  if ($configuration['route']) {
244  $configuration['routeUrl'] = (string)$uriBuilder->buildUriFromRoute($configuration['route'], $urlParameters);
245  }
246 
247  $pluginConfiguration[$pluginName]['config'] = $configuration;
248  }
249  }
250  return $pluginConfiguration;
251  }
252 
259  protected function replaceLanguageFileReferences(array $configuration): array
260  {
261  foreach ($configuration as $key => $value) {
262  if (is_array($value)) {
263  $configuration[$key] = $this->replaceLanguageFileReferences($value);
264  } elseif (is_string($value) && stripos($value, 'LLL:') === 0) {
265  $configuration[$key] = $this->getLanguageService()->sL($value);
266  }
267  }
268  return $configuration;
269  }
270 
277  protected function replaceAbsolutePathsToRelativeResourcesPath(array $configuration): array
278  {
279  foreach ($configuration as $key => $value) {
280  if (is_array($value)) {
281  $configuration[$key] = $this->replaceAbsolutePathsToRelativeResourcesPath($value);
282  } elseif (is_string($value) && stripos($value, 'EXT:') === 0) {
283  $configuration[$key] = $this->resolveUrlPath($value);
284  }
285  }
286  return $configuration;
287  }
288 
295  protected function resolveUrlPath(string $value): string
296  {
297  $value = GeneralUtility::getFileAbsFileName($value);
298  return PathUtility::getAbsoluteWebPath($value);
299  }
300 
307  protected function prepareConfigurationForEditor(): array
308  {
309  // Ensure custom config is empty so nothing additional is loaded
310  // Of course this can be overridden by the editor configuration below
311  $configuration = [
312  'customConfig' => '',
313  ];
314 
315  if (is_array($this->rteConfiguration['config'])) {
316  $configuration = array_replace_recursive($configuration, $this->rteConfiguration['config']);
317  }
318  // Set the UI language of the editor if not hard-coded by the existing configuration
319  if (empty($configuration['language'])) {
320  $configuration['language'] = $this->getBackendUser()->uc['lang'] ?: ($this->getBackendUser()->user['lang'] ?: 'en');
321  }
322  $configuration['contentsLanguage'] = $this->getLanguageIsoCodeOfContent();
323 
324  // Replace all label references
325  $configuration = $this->replaceLanguageFileReferences($configuration);
326  // Replace all paths
327  $configuration = $this->replaceAbsolutePathsToRelativeResourcesPath($configuration);
328 
329  // there are some places where we define an array, but it needs to be a list in order to work
330  if (is_array($configuration['extraPlugins'])) {
331  $configuration['extraPlugins'] = implode(',', $configuration['extraPlugins']);
332  }
333  if (is_array($configuration['removePlugins'])) {
334  $configuration['removePlugins'] = implode(',', $configuration['removePlugins']);
335  }
336  if (is_array($configuration['removeButtons'])) {
337  $configuration['removeButtons'] = implode(',', $configuration['removeButtons']);
338  }
339 
340  return $configuration;
341  }
342 
347  protected function sanitizeFieldId(string $itemFormElementName): string
348  {
349  $fieldId = preg_replace('/[^a-zA-Z0-9_:.-]/', '_', $itemFormElementName);
350  return htmlspecialchars(preg_replace('/^[^a-zA-Z]/', 'x', $fieldId));
351  }
352 
356  protected function getBackendUser()
357  {
358  return $GLOBALS['BE_USER'];
359  }
360 }
static implodeAttributes(array $arr, $xhtmlSafe=false, $dontOmitBlankAttribs=false)
static getAbsoluteWebPath($targetPath)
Definition: PathUtility.php:40
static getFileAbsFileName($filename, $_=null, $_2=null)
renderWizards( $itemKinds=null, $wizConf=null, $table=null, $row=null, $fieldName=null, $PA=null, $itemName=null, $specConf=null, $RTE=null)
static makeInstance($className,... $constructorArguments)
mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
$locales
Definition: be_users.php:6