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