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