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