[BUGFIX] Ensure pageTS is overriding on top-level RTE syntax
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Configuration / Richtext.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Configuration;
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\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
21 use TYPO3\CMS\Core\Utility\ArrayUtility;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23
24 /**
25 * Prepare richtext configuration. Used in DataHandler and FormEngine
26 *
27 * @internal Internal class for the time being - may change / vanish any time
28 * @todo When I grow up, I want to become a data provider
29 */
30 class Richtext
31 {
32 /**
33 * This is an intermediate class / method to retrieve RTE
34 * configuration until all core places use data providers to do that.
35 *
36 * @param string $table The table the field is in
37 * @param string $field Field name
38 * @param int $pid Real page id
39 * @param string $recordType Record type value
40 * @param array $tcaFieldConf ['config'] section of TCA field
41 * @return array
42 */
43 public function getConfiguration(string $table, string $field, int $pid, string $recordType, array $tcaFieldConf): array
44 {
45 // create instance of NodeFactory, ask for "text" element
46 //
47 // As soon an the Data handler starts using FormDataProviders, this class can vanish again, and the hack to
48 // test for specific rich text instances can be dropped: Split the "TcaText" data provider into multiple parts, each
49 // RTE should register and own data provider that does the transformation / configuration providing. This way,
50 // the explicit check for different RTE classes is removed from core and "hooked in" by the RTE's.
51
52 // The main problem here is that all parameters that the processing needs is handed over to as TSconfig
53 // "dotted array" syntax. We convert at least the processing information available under "processing"
54 // together with pageTS, this way it can be overridden and understood in RteHtmlParser.
55 // However, all other parts of the core will depend on the non-dotted syntax (coming from Yaml directly)
56
57 if (!isset($tcaFieldConf['richtextConfiguration'])) {
58 $tcaFieldConf['richtextConfiguration'] = 'default';
59 }
60 $usePreset = $tcaFieldConf['richtextConfiguration'];
61 $configuration = $this->loadConfigurationFromPreset($tcaFieldConf['richtextConfiguration']);
62
63 // Overload with PageTSconfig configuration
64 // First use RTE.*
65 // Then overload with RTE.default
66 // Then overload with RTE.config.tt_content.bodytext
67 // Then overload with RTE.config.tt_content.bodytext.types.textmedia
68 $fullPageTsConfig = $this->getRtePageTsConfigOfPid($pid);
69 $fullPageTsConfig = !empty($fullPageTsConfig['properties']) ? $fullPageTsConfig['properties'] : [];
70 $defaultPageTsConfigOverrides = isset($fullPageTsConfig['default.']) ? $fullPageTsConfig['default.'] : null;
71 $fieldSpecificPageTsConfigOverrides = isset($fullPageTsConfig['config.'][$table . '.'][$field . '.']) ? $fullPageTsConfig['config.'][$table . '.'][$field . '.'] : null;
72 unset($fullPageTsConfig['default.'], $fullPageTsConfig['config.']);
73 // RTE.* (used for RTE.classesAnchor or similar in RTEHtmlArea)
74 if (!empty($fullPageTsConfig)) {
75 ArrayUtility::mergeRecursiveWithOverrule($configuration, $fullPageTsConfig);
76 }
77 // RTE.default.*
78 if (is_array($defaultPageTsConfigOverrides)) {
79 ArrayUtility::mergeRecursiveWithOverrule($configuration, $defaultPageTsConfigOverrides);
80 }
81 // RTE.config.tt_content.bodytext and based on type as well
82 if (is_array($fieldSpecificPageTsConfigOverrides)) {
83 $fieldSpecificPageTsConfigOverridesWithoutType = $fieldSpecificPageTsConfigOverrides;
84 unset($fieldSpecificPageTsConfigOverridesWithoutType['types.']);
85 ArrayUtility::mergeRecursiveWithOverrule($configuration, $fieldSpecificPageTsConfigOverridesWithoutType);
86 if ($recordType
87 && isset($fieldSpecificPageTsConfigOverrides['types.'][$recordType . '.'])
88 && is_array($fieldSpecificPageTsConfigOverrides['types.'][$recordType . '.'])) {
89 ArrayUtility::mergeRecursiveWithOverrule(
90 $configuration,
91 $fieldSpecificPageTsConfigOverrides['types.'][$recordType . '.']
92 );
93 }
94 }
95
96 // Reload the base configuration, if overridden via PageTS "RTE.default.preset = Minimal" for instance
97 // However, if a preset is chosen via TSconfig, then it is not possible to override anything else again
98 // via TSconfig (endless loop).
99 if (isset($configuration['preset']) && $usePreset !== $configuration['preset']) {
100 $configuration = $this->loadConfigurationFromPreset($configuration['preset']);
101 }
102
103 // Handle "mode" / "transformation" config when overridden
104 if (isset($configuration['proc.']['overruleMode']) && $configuration['proc.']['overruleMode'] === 'ts_css') {
105 // Change legacy 'ts_css' to 'default'
106 $configuration['proc.']['overruleMode'] = 'default';
107 } elseif (!isset($configuration['proc.']['mode']) && !isset($configuration['proc.']['overruleMode'])) {
108 $configuration['proc.']['overruleMode'] = 'default';
109 }
110
111 return $configuration;
112 }
113
114 /**
115 * Load a configuration preset from an external resource (currently only YAML is supported).
116 * This is the default behaviour and can be overridden by pageTSconfig.
117 *
118 * @param string $presetName
119 * @return array the parsed configuration
120 */
121 protected function loadConfigurationFromPreset(string $presetName = ''): array
122 {
123 $configuration = [];
124 if (!empty($presetName) && isset($GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets'][$presetName])) {
125 $fileLoader = GeneralUtility::makeInstance(YamlFileLoader::class);
126 $configuration = $fileLoader->load($GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets'][$presetName]);
127 // For future versions, you should however rely on the "processing" key and not the "proc" key.
128 if (is_array($configuration['processing'])) {
129 $configuration['proc.'] = $this->convertPlainArrayToTypoScriptArray($configuration['processing']);
130 }
131 }
132 return $configuration;
133 }
134
135 /**
136 * Return RTE section of page TS
137 *
138 * @param int $pid Page ts of given pid
139 * @return array RTE section of pageTs of given pid
140 */
141 protected function getRtePageTsConfigOfPid(int $pid): array
142 {
143 // Override with pageTs if needed
144 $backendUser = $this->getBackendUser();
145 return $backendUser->getTSConfig('RTE', BackendUtility::getPagesTSconfig($pid));
146 }
147
148 /**
149 * Returns an array with Typoscript the old way (with dot)
150 * Since the functionality in Yaml is without the dots, but the new configuration is used without the dots
151 * this functionality adds also an explicit = 1 to the arrays
152 *
153 * @param array $plainArray An array
154 * @return array array with TypoScript as usual (with dot)
155 */
156 protected function convertPlainArrayToTypoScriptArray(array $plainArray)
157 {
158 $typoScriptArray = [];
159 foreach ($plainArray as $key => $value) {
160 if (is_array($value)) {
161 if (!isset($typoScriptArray[$key])) {
162 $typoScriptArray[$key] = 1;
163 }
164 $typoScriptArray[$key . '.'] = $this->convertPlainArrayToTypoScriptArray($value);
165 } else {
166 $typoScriptArray[$key] = is_null($value) ? '' : $value;
167 }
168 }
169 return $typoScriptArray;
170 }
171
172 /**
173 * @return BackendUserAuthentication
174 */
175 protected function getBackendUser() : BackendUserAuthentication
176 {
177 return $GLOBALS['BE_USER'];
178 }
179 }