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