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