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