[BUGFIX] Store extension configuration in key EXTENSIONS
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Classes / Utility / ConfigurationUtility.php
1 <?php
2 namespace TYPO3\CMS\Extensionmanager\Utility;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\TypoScript\ConfigurationForm;
18 use TYPO3\CMS\Core\Utility\ArrayUtility;
19
20 /**
21 * Utility for dealing with ext_emconf and ext_conf_template settings
22 */
23 class ConfigurationUtility implements \TYPO3\CMS\Core\SingletonInterface
24 {
25 /**
26 * @var \TYPO3\CMS\Extbase\Object\ObjectManager
27 */
28 protected $objectManager;
29
30 /**
31 * @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
32 */
33 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
34 {
35 $this->objectManager = $objectManager;
36 }
37
38 /**
39 * Get default configuration from ext_conf_template of an extension
40 * and save as initial configuration to LocalConfiguration ['EXT']['extConf'].
41 *
42 * Used by the InstallUtility to initialize local extension config.
43 *
44 * @param string $extensionKey Extension key
45 */
46 public function saveDefaultConfiguration($extensionKey)
47 {
48 $currentConfiguration = $this->getCurrentConfiguration($extensionKey);
49 $nestedConfiguration = $this->convertValuedToNestedConfiguration($currentConfiguration);
50 $this->writeConfiguration($nestedConfiguration, $extensionKey);
51 }
52
53 /**
54 * Writes extension specific configuration to LocalConfiguration file
55 * in array ['EXT']['extConf'][$extensionKey].
56 *
57 * Removes core cache files afterwards.
58 *
59 * This low level method expects a nested configuration array that
60 * was already merged with default configuration and maybe new form values.
61 *
62 * @param array $configuration Configuration to save
63 * @param string $extensionKey Extension key
64 */
65 public function writeConfiguration(array $configuration = [], $extensionKey)
66 {
67 /** @var $configurationManager \TYPO3\CMS\Core\Configuration\ConfigurationManager */
68 $configurationManager = $this->objectManager->get(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
69 $configurationManager->setLocalConfigurationValueByPath('EXT/extConf/' . $extensionKey, serialize($configuration));
70 $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS/' . $extensionKey, $configuration);
71 }
72
73 /**
74 * Get current configuration of an extension. Will return the configuration as a valued object
75 *
76 * @param string $extensionKey
77 * @return array
78 */
79 public function getCurrentConfiguration(string $extensionKey): array
80 {
81 $mergedConfiguration = $this->getDefaultConfigurationFromExtConfTemplateAsValuedArray($extensionKey);
82
83 // @deprecated loading serialized configuration is deprecated and will be removed in v10 - use EXTENSIONS array instead
84 // No objects allowed in extConf at all - it is safe to deny that during unserialize()
85 $legacyCurrentExtensionConfiguration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$extensionKey], ['allowed_classes' => false]);
86 $legacyCurrentExtensionConfiguration = is_array($legacyCurrentExtensionConfiguration) ? $legacyCurrentExtensionConfiguration : [];
87 $mergedConfiguration = $this->mergeExtensionConfigurations($mergedConfiguration, $legacyCurrentExtensionConfiguration);
88
89 if (isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey]) && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey])) {
90 $currentExtensionConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey];
91 $mergedConfiguration = $this->mergeExtensionConfigurations($mergedConfiguration, $currentExtensionConfiguration);
92 }
93
94 return $mergedConfiguration;
95 }
96
97 /**
98 * Create a flat array of configuration options from
99 * ext_conf_template.txt of an extension using core's typoscript parser.
100 *
101 * Generates an array from the typoscript style constants and
102 * adds meta data like TSConstantEditor comments
103 *
104 * Result is an array, with configuration item as array keys,
105 * and item properties as key-value sub-array:
106 *
107 * array(
108 * 'fooOption' => array(
109 * 'type' => 'string',
110 * 'value' => 'foo',
111 * ...
112 * ),
113 * 'barOption' => array(
114 * 'type' => boolean,
115 * 'default_value' => 0,
116 * ...
117 * ),
118 * ...
119 * )
120 *
121 * @param string $extensionKey Extension key
122 * @return array
123 */
124 public function getDefaultConfigurationFromExtConfTemplateAsValuedArray($extensionKey)
125 {
126 $rawConfigurationString = $this->getDefaultConfigurationRawString($extensionKey);
127
128 $theConstants = [];
129
130 if ((string)$rawConfigurationString !== '') {
131 /** @var ConfigurationForm $tsStyleConfig */
132 $tsStyleConfig = $this->objectManager->get(ConfigurationForm::class);
133 $tsStyleConfig->doNotSortCategoriesBeforeMakingForm = true;
134
135 $theConstants = $tsStyleConfig->ext_initTSstyleConfig($rawConfigurationString);
136
137 // Loop through configuration items, see if it is assigned to a sub category
138 // and add the sub category label to the item property if so.
139 foreach ($theConstants as $configurationOptionName => $configurationOption) {
140 if (
141 array_key_exists('subcat_name', $configurationOption)
142 && isset($tsStyleConfig->subCategories[$configurationOption['subcat_name']])
143 && isset($tsStyleConfig->subCategories[$configurationOption['subcat_name']][0])
144 ) {
145 $theConstants[$configurationOptionName]['subcat_label'] = $tsStyleConfig->subCategories[$configurationOption['subcat_name']][0];
146 }
147 }
148
149 // Set up the additional descriptions
150 if (isset($tsStyleConfig->setup['constants']['TSConstantEditor.'])) {
151 foreach ($tsStyleConfig->setup['constants']['TSConstantEditor.'] as $category => $highlights) {
152 $theConstants['__meta__'][rtrim($category, '.')]['highlightText'] = $highlights['description'];
153 foreach ($highlights as $highlightNumber => $value) {
154 if (rtrim($category, '.') == $theConstants[$value]['cat']) {
155 $theConstants[$value]['highlight'] = $highlightNumber;
156 }
157 }
158 }
159 }
160 }
161
162 return $theConstants;
163 }
164
165 /**
166 * Return content of an extensions ext_conf_template.txt file if
167 * the file exists, empty string if file does not exist.
168 *
169 * @param string $extensionKey Extension key
170 * @return string
171 */
172 protected function getDefaultConfigurationRawString($extensionKey)
173 {
174 $rawString = '';
175 $extConfTemplateFileLocation = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName(
176 'EXT:' . $extensionKey . '/ext_conf_template.txt'
177 );
178 if (file_exists($extConfTemplateFileLocation)) {
179 $rawString = file_get_contents($extConfTemplateFileLocation);
180 }
181 return $rawString;
182 }
183
184 /**
185 * Converts a valued configuration to a nested configuration.
186 *
187 * array('first.second' => array('value' => 1))
188 * will become
189 * array('first.' => array('second' => ))
190 *
191 * @param array $valuedConfiguration
192 * @return array
193 */
194 public function convertValuedToNestedConfiguration(array $valuedConfiguration)
195 {
196 $nestedConfiguration = [];
197 foreach ($valuedConfiguration as $name => $section) {
198 $path = str_replace('.', './', $name);
199 $nestedConfiguration = ArrayUtility::setValueByPath($nestedConfiguration, $path, $section['value'], '/');
200 }
201 return $nestedConfiguration;
202 }
203
204 /**
205 * Convert a nested configuration to a valued configuration
206 *
207 * array('first.' => array('second' => 1))
208 * will become
209 * array('first.second' => array('value' => 1)
210 * @param array $nestedConfiguration
211 * @return array
212 */
213 public function convertNestedToValuedConfiguration(array $nestedConfiguration)
214 {
215 $flatExtensionConfig = ArrayUtility::flatten($nestedConfiguration);
216 $valuedCurrentExtensionConfig = [];
217 foreach ($flatExtensionConfig as $key => $value) {
218 $valuedCurrentExtensionConfig[$key]['value'] = $value;
219 }
220 return $valuedCurrentExtensionConfig;
221 }
222
223 /**
224 * Merges two existing configuration arrays,
225 * expects configuration as valued flat structure
226 * and overrides as nested array
227 *
228 * @see convertNestedToValuedConfiguration
229 *
230 * @param array $configuration
231 * @param array $configurationOverride
232 *
233 * @return array
234 */
235 private function mergeExtensionConfigurations(array $configuration, array $configurationOverride): array
236 {
237 $configurationOverride = $this->convertNestedToValuedConfiguration(
238 $configurationOverride
239 );
240 ArrayUtility::mergeRecursiveWithOverrule(
241 $configuration,
242 $configurationOverride
243 );
244 return $configuration;
245 }
246 }