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