ConfigurationManager.php

Implementation of case 2 - M. Stichweh, 2016-09-02 17:14

Download (18.5 KB)

 
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 pattern to context specific local overload TYPO3_CONF_VARS file, relative to PATH_site
50
         */
51
        protected $contextLocalConfigurationOverridesFilePattern = 'typo3conf/LocalConfigurationOverrides.%s.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 pathes is always allowed,
75
         * even if the requested sub path does not exist yet.
76
         *
77
         * @var array
78
         */
79
        protected $whiteListedLocalConfigurationPaths = array(
80
                        'EXT/extConf',
81
                        'EXTCONF',
82
                        'INSTALL/wizardDone',
83
                        'DB',
84
                        'SYS/caching/cacheConfigurations',
85
        );
86

    
87
        /**
88
         * Return default configuration array
89
         *
90
         * @return array
91
         */
92
        public function getDefaultConfiguration()
93
        {
94
                return require $this->getDefaultConfigurationFileLocation();
95
        }
96

    
97
        /**
98
         * Get the file location of the default configuration file,
99
         * currently the path and filename.
100
         *
101
         * @return string
102
         * @access private
103
         */
104
        public function getDefaultConfigurationFileLocation()
105
        {
106
                return PATH_site . $this->defaultConfigurationFile;
107
        }
108

    
109
        /**
110
         * Return local configuration array typo3conf/LocalConfiguration.php
111
         *
112
         * @param \TYPO3\CMS\Core\Core\ApplicationContext $context An optional application context to get only the overridden values for that context
113
         * @param boolean $disableContextConfigMerge Disable merging with context specific configurations to fetch just the config from LocalConfiguration.php
114
         * @return array Content array of local configuration file
115
         */
116
        public function getLocalConfiguration($context = null, $disableContextConfigMerge = false)
117
        {
118
                if(!$disableContextConfigMerge && !$context) {
119
                        $config = require $this->getLocalConfigurationFileLocation();
120
                        // Merge with context specific configurations if exists
121
                        if(GeneralUtility::getApplicationContext()->getParent() != null) {
122
                                $contextConfigFilePath = $this->getLocalConfigurationFileLocation(GeneralUtility::getApplicationContext()->getParent());
123
                                if( @file_exists($contextConfigFilePath)) {
124
                                        ArrayUtility::mergeRecursiveWithOverrule($config, require $contextConfigFilePath);
125
                                }
126
                        }
127
                        $contextConfigFilePath = $this->getLocalConfigurationFileLocation(GeneralUtility::getApplicationContext());
128
                        if( @file_exists($contextConfigFilePath)) {
129
                                ArrayUtility::mergeRecursiveWithOverrule($config, require $contextConfigFilePath);
130
                        }
131
                        return $config;
132
                }
133
                elseif($context) {
134
                        $config = array();
135
                        $contextConfigFilePath = $this->getLocalConfigurationFileLocation($context);
136
                        if( @file_exists($contextConfigFilePath)) {
137
                                $config = require $contextConfigFilePath;
138
                        }
139
                        return $config;
140
                }
141
                return require $this->getLocalConfigurationFileLocation();
142
        }
143

    
144
        /**
145
         * Get the file location of the local configuration file,
146
         * currently the path and filename.
147
         *
148
         * @param \TYPO3\CMS\Core\Core\ApplicationContext $context An optional application context as postfix for the configuration file
149
         * @return string
150
         * @access private
151
         */
152
        public function getLocalConfigurationFileLocation($context = '')
153
        {
154
                $localConfigurationFile = PATH_site . $this->localConfigurationFile;                
155
                if($context) {
156
                        $ontextName = array($context->__toString());
157
                        if($context->getParent() != null) {
158
                                array_unshift($ontextName, $context->getParent()->__toString());
159
                        }
160
                        $localConfigurationFile = PATH_site . sprintf($this->contextLocalConfigurationOverridesFilePattern, implode('.', $ontextName));
161
                }                
162
                return $localConfigurationFile;
163
        }
164

    
165
        /**
166
         * Get the file location of the additional configuration file,
167
         * currently the path and filename.
168
         *
169
         * @return string
170
         * @access private
171
         */
172
        public function getAdditionalConfigurationFileLocation()
173
        {
174
                return PATH_site . $this->additionalConfigurationFile;
175
        }
176

    
177
        /**
178
         * Get absolute file location of factory configuration file
179
         *
180
         * @return string
181
         */
182
        protected function getFactoryConfigurationFileLocation()
183
        {
184
                return PATH_site . $this->factoryConfigurationFile;
185
        }
186

    
187
        /**
188
         * Get absolute file location of factory configuration file
189
         *
190
         * @return string
191
         */
192
        protected function getAdditionalFactoryConfigurationFileLocation()
193
        {
194
                return PATH_site . $this->additionalFactoryConfigurationFile;
195
        }
196

    
197
        /**
198
         * Override local configuration with new values.
199
         *
200
         * @param array $configurationToMerge Override configuration array
201
         * @return void
202
         */
203
        public function updateLocalConfiguration(array $configurationToMerge)
204
        {
205
                $newLocalConfiguration = $this->getLocalConfiguration();
206
                ArrayUtility::mergeRecursiveWithOverrule($newLocalConfiguration, $configurationToMerge);
207
                $this->writeLocalConfiguration($newLocalConfiguration);
208
        }
209

    
210
        /**
211
         * Get a value at given path from default configuration
212
         *
213
         * @param string $path Path to search for
214
         * @return mixed Value at path
215
         */
216
        public function getDefaultConfigurationValueByPath($path)
217
        {
218
                return ArrayUtility::getValueByPath($this->getDefaultConfiguration(), $path);
219
        }
220

    
221
        /**
222
         * Get a value at given path from local configuration
223
         *
224
         * @param string $path Path to search for
225
         * @return mixed Value at path
226
         */
227
        public function getLocalConfigurationValueByPath($path)
228
        {
229
                return ArrayUtility::getValueByPath($this->getLocalConfiguration(), $path);
230
        }
231

    
232
        /**
233
         * Get a value from configuration, this is default configuration
234
         * merged with local configuration
235
         *
236
         * @param string $path Path to search for
237
         * @return mixed
238
         */
239
        public function getConfigurationValueByPath($path)
240
        {
241
                $defaultConfiguration = $this->getDefaultConfiguration();
242
                ArrayUtility::mergeRecursiveWithOverrule($defaultConfiguration, $this->getLocalConfiguration());
243
                return ArrayUtility::getValueByPath($defaultConfiguration, $path);
244
        }
245

    
246
        /**
247
         * Update a given path in local configuration to a new value.
248
         *
249
         * @param string $path Path to update
250
         * @param mixed $value Value to set
251
         * @return bool TRUE on success
252
         */
253
        public function setLocalConfigurationValueByPath($path, $value)
254
        {
255
                $result = false;
256
                if ($this->isValidLocalConfigurationPath($path)) {
257
                        // Check if the value is overridden in context specific configuration and store it there if this is the case
258
                        $localConfiguration = array();
259
                        $context = $this->getContextOfConfigurationPath($path, $localConfiguration);
260
                        if(!$context) {
261
                                // Get config from LocalConfiguration.php only - without context specific merging
262
                                $localConfiguration = $this->getLocalConfiguration(null, true);
263
                        }
264
                        $localConfiguration = ArrayUtility::setValueByPath($localConfiguration, $path, $value);
265
                        $result = $this->writeLocalConfiguration($localConfiguration, $context);
266
                }
267
                return $result;
268
        }
269
        
270
        /**
271
         * Searches in context specific configurations for the specified config value path and returns the context where the value path is
272
         * found. NULL is returned if the value path is not found in any context specific configuration.
273
         * 
274
         * @param string $path The configuration value path
275
         * @return \TYPO3\CMS\Core\Core\ApplicationContext The context where the path was found or null otherwise
276
         */
277
        protected function getContextOfConfigurationPath($path, &$localConfiguration)
278
        {
279
                $localConfiguration = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
280
                if(ArrayUtility::isValidPath($localConfiguration, $path))
281
                {
282
                        return GeneralUtility::getApplicationContext();
283
                }
284
                if(GeneralUtility::getApplicationContext()->getParent())
285
                {
286
                        $localConfiguration = $this->getLocalConfiguration(GeneralUtility::getApplicationContext()->getParent());
287
                        if(ArrayUtility::isValidPath($localConfiguration, $path))
288
                        {
289
                                return GeneralUtility::getApplicationContext()->getParent();
290
                        }
291
                }
292
                
293
                return null;
294
        }
295

    
296
        /**
297
         * Update / set a list of path and value pairs in local configuration file
298
         *
299
         * @param array $pairs Key is path, value is value to set
300
         * @return bool TRUE on success
301
         */
302
        public function setLocalConfigurationValuesByPathValuePairs(array $pairs)
303
        {
304
                $localConfigurationModified = false;
305
                $localConfigurationContextModified = false;
306
                $localConfigurationSubContextModified = false;
307
                
308
                $localConfiguration = $this->getLocalConfiguration(null, true);
309
                if(GeneralUtility::getApplicationContext()->getParent()) {
310
                        $localConfigurationContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext()->getParent());
311
                        $localConfigurationSubContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
312
                }
313
                else {
314
                        $localConfigurationContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
315
                        $localConfigurationSubContext = array();
316
                }
317
                foreach ($pairs as $path => $value) {
318
                        if ($this->isValidLocalConfigurationPath($path)) {
319
                                if(ArrayUtility::isValidPath($localConfigurationSubContext, $path))        {
320
                                        $localConfigurationSubContext = ArrayUtility::setValueByPath($localConfigurationSubContext, $path, $value);
321
                                        $localConfigurationSubContextModified = true;
322
                                }
323
                                elseif(ArrayUtility::isValidPath($localConfigurationContext, $path)) {
324
                                        $localConfigurationContext = ArrayUtility::setValueByPath($localConfigurationContext, $path, $value);
325
                                        $localConfigurationContextModified = true;
326
                                }
327
                                else {
328
                                        $localConfiguration = ArrayUtility::setValueByPath($localConfiguration, $path, $value);
329
                                        $localConfigurationModified = true;
330
                                }
331
                        }
332
                }
333
                if($localConfigurationSubContextModified) {
334
                        $this->writeLocalConfiguration($localConfigurationSubContext, GeneralUtility::getApplicationContext());
335
                }
336
                if($localConfigurationContextModified) {
337
                        $context = GeneralUtility::getApplicationContext()->getParent()? GeneralUtility::getApplicationContext()->getParent() : GeneralUtility::getApplicationContext();
338
                        $this->writeLocalConfiguration($localConfigurationContext, $context);
339
                }
340
                if($localConfigurationModified) {
341
                        $this->writeLocalConfiguration($localConfiguration);
342
                }
343
                return $this->getLocalConfiguration();
344
        }
345

    
346
        /**
347
         * Remove keys from LocalConfiguration
348
         *
349
         * @param array $keys Array with key paths to remove from LocalConfiguration
350
         * @return bool TRUE if something was removed
351
         */
352
        public function removeLocalConfigurationKeysByPath(array $keys)
353
        {
354
                $result = false;
355
                $localConfigurationModified = false;
356
                $localConfigurationContextModified = false;
357
                $localConfigurationSubContextModified = false;
358
                
359
                $localConfiguration = $this->getLocalConfiguration(null, true);
360
                if(GeneralUtility::getApplicationContext()->getParent()) {
361
                        $localConfigurationContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext()->getParent());
362
                        $localConfigurationSubContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
363
                }
364
                else {
365
                        $localConfigurationContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
366
                        $localConfigurationSubContext = array();
367
                }
368
                foreach ($keys as $path) {
369
                        // Remove key if path is within LocalConfiguration
370
                        if (ArrayUtility::isValidPath($localConfiguration, $path)) {
371
                                $result = true;
372
                                if(ArrayUtility::isValidPath($localConfigurationSubContext, $path))        {
373
                                        $localConfigurationSubContext = ArrayUtility::removeByPath($localConfigurationSubContext, $path);
374
                                        $localConfigurationSubContextModified = true;
375
                                }
376
                                elseif(ArrayUtility::isValidPath($localConfigurationContext, $path)) {
377
                                        $localConfigurationContext = ArrayUtility::removeByPath($localConfigurationContext, $path);
378
                                        $localConfigurationContextModified = true;
379
                                }
380
                                else {
381
                                        $localConfiguration = ArrayUtility::removeByPath($localConfiguration, $path);
382
                                        $localConfigurationModified = true;
383
                                }
384
                        }
385
                }
386
                if ($result) {
387
                        if($localConfigurationSubContextModified) {
388
                                $this->writeLocalConfiguration($localConfigurationSubContext, GeneralUtility::getApplicationContext());
389
                        }
390
                        if($localConfigurationContextModified) {
391
                                $context = GeneralUtility::getApplicationContext()->getParent()? GeneralUtility::getApplicationContext()->getParent() : GeneralUtility::getApplicationContext();
392
                                $this->writeLocalConfiguration($localConfigurationContext, $context);
393
                        }
394
                        if($localConfigurationModified)        {
395
                                $this->writeLocalConfiguration($localConfiguration);
396
                        }
397
                }
398
                return $result;
399
        }
400

    
401
        /**
402
         * Checks if the configuration can be written.
403
         *
404
         * @param string $context An optional environment context as postfix of the configuration file
405
         * @return bool
406
         * @access private
407
         */
408
        public function canWriteConfiguration($context = '')
409
        {
410
                $fileLocation = $this->getLocalConfigurationFileLocation($context);
411
                return @is_writable($this->pathTypo3Conf) && (!file_exists($fileLocation) || @is_writable($fileLocation));
412
        }
413

    
414
        /**
415
         * Reads the configuration array and exports it to the global variable
416
         *
417
         * @access private
418
         * @throws \UnexpectedValueException
419
         * @return void
420
         */
421
        public function exportConfiguration()
422
        {
423
                if (@is_file($this->getLocalConfigurationFileLocation())) {
424
                        $localConfiguration = $this->getLocalConfiguration();
425
                        if (is_array($localConfiguration)) {
426
                                $defaultConfiguration = $this->getDefaultConfiguration();
427
                                ArrayUtility::mergeRecursiveWithOverrule($defaultConfiguration, $localConfiguration);
428
                                $GLOBALS['TYPO3_CONF_VARS'] = $defaultConfiguration;
429
                        } else {
430
                                throw new \UnexpectedValueException('LocalConfiguration invalid.', 1349272276);
431
                        }
432
                        if (@is_file($this->getAdditionalConfigurationFileLocation())) {
433
                                require $this->getAdditionalConfigurationFileLocation();
434
                        }
435
                } else {
436
                        // No LocalConfiguration (yet), load DefaultConfiguration only
437
                        $GLOBALS['TYPO3_CONF_VARS'] = $this->getDefaultConfiguration();
438
                }
439
        }
440

    
441
        /**
442
         * Write local configuration array to typo3conf/LocalConfiguration.php
443
         *
444
         * @param array $configuration The local configuration to be written
445
         * \TYPO3\CMS\Core\Core\ApplicationContext $context An optional application context to write a context specific file
446
         * @throws \RuntimeException
447
         * @return bool TRUE on success
448
         * @access private
449
         */
450
        public function writeLocalConfiguration(array $configuration, $context = '')
451
        {
452
                $localConfigurationFile = $this->getLocalConfigurationFileLocation($context);
453
                if (!$this->canWriteConfiguration($context)) {
454
                        throw new \RuntimeException(
455
                                        $localConfigurationFile . ' is not writable.', 1346323822
456
                                        );
457
                }
458
                $configuration = ArrayUtility::sortByKeyRecursive($configuration);
459
                $result = GeneralUtility::writeFile(
460
                                $localConfigurationFile,
461
                                '<?php' . LF .
462
                                'return ' .
463
                                ArrayUtility::arrayExport(
464
                                                ArrayUtility::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($configuration)
465
                                                ) .
466
                                ';' . LF,
467
                                true
468
                                );
469

    
470
                GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($localConfigurationFile);
471

    
472
                return $result;
473
        }
474

    
475
        /**
476
         * Write additional configuration array to typo3conf/AdditionalConfiguration.php
477
         *
478
         * @param array $additionalConfigurationLines The configuration lines to be written
479
         * @throws \RuntimeException
480
         * @return bool TRUE on success
481
         * @access private
482
         */
483
        public function writeAdditionalConfiguration(array $additionalConfigurationLines)
484
        {
485
                return GeneralUtility::writeFile(
486
                                PATH_site . $this->additionalConfigurationFile,
487
                                '<?php' . LF .
488
                                implode(LF, $additionalConfigurationLines) . LF
489
                                );
490
        }
491

    
492
        /**
493
         * Uses FactoryConfiguration file and a possible AdditionalFactoryConfiguration
494
         * file in typo3conf to create a basic LocalConfiguration.php. This is used
495
         * by the install tool in an early step.
496
         *
497
         * @throws \RuntimeException
498
         * @return void
499
         * @access private
500
         */
501
        public function createLocalConfigurationFromFactoryConfiguration()
502
        {
503
                if (file_exists($this->getLocalConfigurationFileLocation())) {
504
                        throw new \RuntimeException(
505
                                        'LocalConfiguration.php exists already',
506
                                        1364836026
507
                                        );
508
                }
509
                $localConfigurationArray = require $this->getFactoryConfigurationFileLocation();
510
                $additionalFactoryConfigurationFileLocation = $this->getAdditionalFactoryConfigurationFileLocation();
511
                if (file_exists($additionalFactoryConfigurationFileLocation)) {
512
                        $additionalFactoryConfigurationArray = require $additionalFactoryConfigurationFileLocation;
513
                        ArrayUtility::mergeRecursiveWithOverrule(
514
                                        $localConfigurationArray,
515
                                        $additionalFactoryConfigurationArray
516
                                        );
517
                }
518
                $this->writeLocalConfiguration($localConfigurationArray);
519
        }
520

    
521
        /**
522
         * Check if access / write to given path in local configuration is allowed.
523
         *
524
         * @param string $path Path to search for
525
         * @return bool TRUE if access is allowed
526
         */
527
        protected function isValidLocalConfigurationPath($path)
528
        {
529
                // Early return for white listed paths
530
                foreach ($this->whiteListedLocalConfigurationPaths as $whiteListedPath) {
531
                        if (GeneralUtility::isFirstPartOfStr($path, $whiteListedPath)) {
532
                                return true;
533
                        }
534
                }
535
                return ArrayUtility::isValidPath($this->getDefaultConfiguration(), $path);
536
        }
537
}
538