[BUGFIX] Store extension configuration in key EXTENSIONS
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Configuration / ConfigurationManager.php
1 <?php
2 namespace TYPO3\CMS\Core\Configuration;
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\Service\OpcodeCacheService;
18 use TYPO3\CMS\Core\Utility\ArrayUtility;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * Handle loading and writing of global and local (instance specific)
23 * configuration.
24 *
25 * This class handles the access to the files
26 * - EXT:core/Configuration/DefaultConfiguration.php (default TYPO3_CONF_VARS)
27 * - typo3conf/LocalConfiguration.php (overrides of TYPO3_CONF_VARS)
28 * - typo3conf/AdditionalConfiguration.php (optional additional local code blocks)
29 *
30 * IMPORTANT:
31 * This class is intended for internal core use ONLY.
32 * Extensions should usually use the resulting $GLOBALS['TYPO3_CONF_VARS'] array,
33 * do not try to modify settings in LocalConfiguration.php with an extension.
34 * @internal
35 */
36 class ConfigurationManager
37 {
38 /**
39 * @var string Path to default TYPO3_CONF_VARS file, relative to PATH_site
40 */
41 protected $defaultConfigurationFile = 'typo3/sysext/core/Configuration/DefaultConfiguration.php';
42
43 /**
44 * @var string Path to description file for TYPO3_CONF_VARS, relative to PATH_site
45 */
46 protected $defaultConfigurationDescriptionFile = 'typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml';
47
48 /**
49 * @var string Path to local overload TYPO3_CONF_VARS file, relative to PATH_site
50 */
51 protected $localConfigurationFile = 'typo3conf/LocalConfiguration.php';
52
53 /**
54 * @var string Path to additional local file, relative to PATH_site
55 */
56 protected $additionalConfigurationFile = 'typo3conf/AdditionalConfiguration.php';
57
58 /**
59 * @var string Path to factory configuration file used during installation as LocalConfiguration boilerplate
60 */
61 protected $factoryConfigurationFile = 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
62
63 /**
64 * @var string Path to possible additional factory configuration file delivered by packages
65 */
66 protected $additionalFactoryConfigurationFile = 'typo3conf/AdditionalFactoryConfiguration.php';
67
68 /**
69 * Writing to these configuration paths is always allowed,
70 * even if the requested sub path does not exist yet.
71 *
72 * @var array
73 */
74 protected $whiteListedLocalConfigurationPaths = [
75 'EXT/extConf',
76 'EXTCONF',
77 'DB',
78 'SYS/caching/cacheConfigurations',
79 'EXTENSIONS',
80 ];
81
82 /**
83 * Return default configuration array
84 *
85 * @return array
86 */
87 public function getDefaultConfiguration()
88 {
89 return require $this->getDefaultConfigurationFileLocation();
90 }
91
92 /**
93 * Get the file location of the default configuration file,
94 * currently the path and filename.
95 *
96 * @return string
97 * @access private
98 */
99 public function getDefaultConfigurationFileLocation()
100 {
101 return PATH_site . $this->defaultConfigurationFile;
102 }
103
104 /**
105 * Get the file location of the default configuration description file,
106 * currently the path and filename.
107 *
108 * @return string
109 * @access private
110 */
111 public function getDefaultConfigurationDescriptionFileLocation()
112 {
113 return PATH_site . $this->defaultConfigurationDescriptionFile;
114 }
115
116 /**
117 * Return local configuration array typo3conf/LocalConfiguration.php
118 *
119 * @return array Content array of local configuration file
120 */
121 public function getLocalConfiguration()
122 {
123 return require $this->getLocalConfigurationFileLocation();
124 }
125
126 /**
127 * Get the file location of the local configuration file,
128 * currently the path and filename.
129 *
130 * @return string
131 * @access private
132 */
133 public function getLocalConfigurationFileLocation()
134 {
135 return PATH_site . $this->localConfigurationFile;
136 }
137
138 /**
139 * Get the file location of the additional configuration file,
140 * currently the path and filename.
141 *
142 * @return string
143 * @access private
144 */
145 public function getAdditionalConfigurationFileLocation()
146 {
147 return PATH_site . $this->additionalConfigurationFile;
148 }
149
150 /**
151 * Get absolute file location of factory configuration file
152 *
153 * @return string
154 */
155 protected function getFactoryConfigurationFileLocation()
156 {
157 return PATH_site . $this->factoryConfigurationFile;
158 }
159
160 /**
161 * Get absolute file location of factory configuration file
162 *
163 * @return string
164 */
165 protected function getAdditionalFactoryConfigurationFileLocation()
166 {
167 return PATH_site . $this->additionalFactoryConfigurationFile;
168 }
169
170 /**
171 * Override local configuration with new values.
172 *
173 * @param array $configurationToMerge Override configuration array
174 */
175 public function updateLocalConfiguration(array $configurationToMerge)
176 {
177 $newLocalConfiguration = $this->getLocalConfiguration();
178 ArrayUtility::mergeRecursiveWithOverrule($newLocalConfiguration, $configurationToMerge);
179 $this->writeLocalConfiguration($newLocalConfiguration);
180 }
181
182 /**
183 * Get a value at given path from default configuration
184 *
185 * @param string $path Path to search for
186 * @return mixed Value at path
187 */
188 public function getDefaultConfigurationValueByPath($path)
189 {
190 return ArrayUtility::getValueByPath($this->getDefaultConfiguration(), $path);
191 }
192
193 /**
194 * Get a value at given path from local configuration
195 *
196 * @param string $path Path to search for
197 * @return mixed Value at path
198 */
199 public function getLocalConfigurationValueByPath($path)
200 {
201 return ArrayUtility::getValueByPath($this->getLocalConfiguration(), $path);
202 }
203
204 /**
205 * Get a value from configuration, this is default configuration
206 * merged with local configuration
207 *
208 * @param string $path Path to search for
209 * @return mixed
210 */
211 public function getConfigurationValueByPath($path)
212 {
213 $defaultConfiguration = $this->getDefaultConfiguration();
214 ArrayUtility::mergeRecursiveWithOverrule($defaultConfiguration, $this->getLocalConfiguration());
215 return ArrayUtility::getValueByPath($defaultConfiguration, $path);
216 }
217
218 /**
219 * Update a given path in local configuration to a new value.
220 *
221 * @param string $path Path to update
222 * @param mixed $value Value to set
223 * @return bool TRUE on success
224 */
225 public function setLocalConfigurationValueByPath($path, $value)
226 {
227 $result = false;
228 if ($this->isValidLocalConfigurationPath($path)) {
229 $localConfiguration = $this->getLocalConfiguration();
230 $localConfiguration = ArrayUtility::setValueByPath($localConfiguration, $path, $value);
231 $result = $this->writeLocalConfiguration($localConfiguration);
232 }
233 return $result;
234 }
235
236 /**
237 * Update / set a list of path and value pairs in local configuration file
238 *
239 * @param array $pairs Key is path, value is value to set
240 * @return bool TRUE on success
241 */
242 public function setLocalConfigurationValuesByPathValuePairs(array $pairs)
243 {
244 $localConfiguration = $this->getLocalConfiguration();
245 foreach ($pairs as $path => $value) {
246 if ($this->isValidLocalConfigurationPath($path)) {
247 $localConfiguration = ArrayUtility::setValueByPath($localConfiguration, $path, $value);
248 }
249 }
250 return $this->writeLocalConfiguration($localConfiguration);
251 }
252
253 /**
254 * Remove keys from LocalConfiguration
255 *
256 * @param array $keys Array with key paths to remove from LocalConfiguration
257 * @return bool TRUE if something was removed
258 */
259 public function removeLocalConfigurationKeysByPath(array $keys)
260 {
261 $result = false;
262 $localConfiguration = $this->getLocalConfiguration();
263 foreach ($keys as $path) {
264 // Remove key if path is within LocalConfiguration
265 if (ArrayUtility::isValidPath($localConfiguration, $path)) {
266 $result = true;
267 $localConfiguration = ArrayUtility::removeByPath($localConfiguration, $path);
268 }
269 }
270 if ($result) {
271 $this->writeLocalConfiguration($localConfiguration);
272 }
273 return $result;
274 }
275
276 /**
277 * Checks if the configuration can be written.
278 *
279 * @return bool
280 * @access private
281 */
282 public function canWriteConfiguration()
283 {
284 $fileLocation = $this->getLocalConfigurationFileLocation();
285 return @is_writable(file_exists($fileLocation) ? $fileLocation : PATH_site . 'typo3conf/');
286 }
287
288 /**
289 * Reads the configuration array and exports it to the global variable
290 *
291 * @access private
292 * @throws \UnexpectedValueException
293 */
294 public function exportConfiguration()
295 {
296 if (@is_file($this->getLocalConfigurationFileLocation())) {
297 $localConfiguration = $this->getLocalConfiguration();
298 if (is_array($localConfiguration)) {
299 $defaultConfiguration = $this->getDefaultConfiguration();
300 ArrayUtility::mergeRecursiveWithOverrule($defaultConfiguration, $localConfiguration);
301 $GLOBALS['TYPO3_CONF_VARS'] = $defaultConfiguration;
302 } else {
303 throw new \UnexpectedValueException('LocalConfiguration invalid.', 1349272276);
304 }
305 if (@is_file($this->getAdditionalConfigurationFileLocation())) {
306 require $this->getAdditionalConfigurationFileLocation();
307 }
308 } else {
309 // No LocalConfiguration (yet), load DefaultConfiguration only
310 $GLOBALS['TYPO3_CONF_VARS'] = $this->getDefaultConfiguration();
311 }
312 }
313
314 /**
315 * Write local configuration array to typo3conf/LocalConfiguration.php
316 *
317 * @param array $configuration The local configuration to be written
318 * @throws \RuntimeException
319 * @return bool TRUE on success
320 * @access private
321 */
322 public function writeLocalConfiguration(array $configuration)
323 {
324 $localConfigurationFile = $this->getLocalConfigurationFileLocation();
325 if (!$this->canWriteConfiguration()) {
326 throw new \RuntimeException(
327 $localConfigurationFile . ' is not writable.',
328 1346323822
329 );
330 }
331 $configuration = ArrayUtility::sortByKeyRecursive($configuration);
332 $result = GeneralUtility::writeFile(
333 $localConfigurationFile,
334 '<?php' . LF .
335 'return ' .
336 ArrayUtility::arrayExport(
337 ArrayUtility::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($configuration)
338 ) .
339 ';' . LF,
340 true
341 );
342
343 GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($localConfigurationFile);
344
345 return $result;
346 }
347
348 /**
349 * Write additional configuration array to typo3conf/AdditionalConfiguration.php
350 *
351 * @param array $additionalConfigurationLines The configuration lines to be written
352 * @throws \RuntimeException
353 * @return bool TRUE on success
354 * @access private
355 */
356 public function writeAdditionalConfiguration(array $additionalConfigurationLines)
357 {
358 return GeneralUtility::writeFile(
359 PATH_site . $this->additionalConfigurationFile,
360 '<?php' . LF .
361 implode(LF, $additionalConfigurationLines) . LF
362 );
363 }
364
365 /**
366 * Uses FactoryConfiguration file and a possible AdditionalFactoryConfiguration
367 * file in typo3conf to create a basic LocalConfiguration.php. This is used
368 * by the install tool in an early step.
369 *
370 * @throws \RuntimeException
371 * @access private
372 */
373 public function createLocalConfigurationFromFactoryConfiguration()
374 {
375 if (file_exists($this->getLocalConfigurationFileLocation())) {
376 throw new \RuntimeException(
377 'LocalConfiguration.php exists already',
378 1364836026
379 );
380 }
381 $localConfigurationArray = require $this->getFactoryConfigurationFileLocation();
382 $additionalFactoryConfigurationFileLocation = $this->getAdditionalFactoryConfigurationFileLocation();
383 if (file_exists($additionalFactoryConfigurationFileLocation)) {
384 $additionalFactoryConfigurationArray = require $additionalFactoryConfigurationFileLocation;
385 ArrayUtility::mergeRecursiveWithOverrule(
386 $localConfigurationArray,
387 $additionalFactoryConfigurationArray
388 );
389 }
390 $this->writeLocalConfiguration($localConfigurationArray);
391 }
392
393 /**
394 * Check if access / write to given path in local configuration is allowed.
395 *
396 * @param string $path Path to search for
397 * @return bool TRUE if access is allowed
398 */
399 protected function isValidLocalConfigurationPath($path)
400 {
401 // Early return for white listed paths
402 foreach ($this->whiteListedLocalConfigurationPaths as $whiteListedPath) {
403 if (GeneralUtility::isFirstPartOfStr($path, $whiteListedPath)) {
404 return true;
405 }
406 }
407 return ArrayUtility::isValidPath($this->getDefaultConfiguration(), $path);
408 }
409 }