]> git.mxchange.org Git - friendica.git/blob - src/Core/Config/Util/ConfigFileManager.php
Merge pull request #12593 from nupplaphil/feat/node.config.php
[friendica.git] / src / Core / Config / Util / ConfigFileManager.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2023, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Core\Config\Util;
23
24 use Friendica\Core\Addon;
25 use Friendica\Core\Config\Exception\ConfigFileException;
26 use Friendica\Core\Config\ValueObject\Cache;
27
28 /**
29  * The ConfigFileLoader loads and saves config-files and stores them in a ConfigCache ( @see Cache )
30  *
31  * It is capable of loading the following config files:
32  * - *.config.php   (current)
33  * - *.ini.php      (deprecated)
34  * - *.htconfig.php (deprecated)
35  */
36 class ConfigFileManager
37 {
38         /**
39          * The default name of the user defined legacy config file
40          *
41          * @var string
42          */
43         const CONFIG_HTCONFIG = 'htconfig';
44
45         /**
46          * The config file, where overrides per admin page/console are saved at
47          *
48          * @var string
49          */
50         const CONFIG_DATA_FILE = 'node.config.php';
51
52         /**
53          * The sample string inside the configs, which shouldn't get loaded
54          *
55          * @var string
56          */
57         const SAMPLE_END = '-sample';
58
59         /**
60          * @var string
61          */
62         private $baseDir;
63         /**
64          * @var string
65          */
66         private $configDir;
67         /**
68          * @var string
69          */
70         private $staticDir;
71
72         /**
73          * @param string $baseDir   The base
74          * @param string $configDir
75          * @param string $staticDir
76          */
77         public function __construct(string $baseDir, string $configDir, string $staticDir)
78         {
79                 $this->baseDir   = $baseDir;
80                 $this->configDir = $configDir;
81                 $this->staticDir = $staticDir;
82         }
83
84         /**
85          * Load the configuration files into an configuration cache
86          *
87          * First loads the default value for all the configuration keys, then the legacy configuration files, then the
88          * expected local.config.php
89          *
90          * @param Cache $config The config cache to load to
91          * @param array $server The $_SERVER array
92          * @param bool  $raw    Set up the raw config format
93          *
94          * @throws ConfigFileException
95          */
96         public function setupCache(Cache $config, array $server = [], bool $raw = false)
97         {
98                 // Load static config files first, the order is important
99                 $config->load($this->loadStaticConfig('defaults'), Cache::SOURCE_STATIC);
100                 $config->load($this->loadStaticConfig('settings'), Cache::SOURCE_STATIC);
101
102                 // try to load the legacy config first
103                 $config->load($this->loadLegacyConfig('htpreconfig'), Cache::SOURCE_FILE);
104                 $config->load($this->loadLegacyConfig('htconfig'), Cache::SOURCE_FILE);
105
106                 // Now load every other config you find inside the 'config/' directory
107                 $this->loadCoreConfig($config);
108
109                 // Now load the node.config.php file with the node specific config values (based on admin gui/console actions)
110                 $this->loadDataConfig($config);
111
112                 $config->load($this->loadEnvConfig($server), Cache::SOURCE_ENV);
113
114                 // In case of install mode, add the found basepath (because there isn't a basepath set yet
115                 if (!$raw && empty($config->get('system', 'basepath'))) {
116                         // Setting at least the basepath we know
117                         $config->set('system', 'basepath', $this->baseDir, Cache::SOURCE_FILE);
118                 }
119         }
120
121         /**
122          * Tries to load the static core-configuration and returns the config array.
123          *
124          * @param string $name The name of the configuration
125          *
126          * @return array The config array (empty if no config found)
127          *
128          * @throws ConfigFileException if the configuration file isn't readable
129          */
130         private function loadStaticConfig(string $name): array
131         {
132                 $configName = $this->staticDir . DIRECTORY_SEPARATOR . $name . '.config.php';
133                 $iniName    = $this->staticDir . DIRECTORY_SEPARATOR . $name . '.ini.php';
134
135                 if (file_exists($configName)) {
136                         return $this->loadConfigFile($configName);
137                 } elseif (file_exists($iniName)) {
138                         return $this->loadINIConfigFile($iniName);
139                 } else {
140                         return [];
141                 }
142         }
143
144         /**
145          * Tries to load the specified core-configuration into the config cache.
146          *
147          * @param Cache $config The Config cache
148          *
149          * @throws ConfigFileException if the configuration file isn't readable
150          */
151         private function loadCoreConfig(Cache $config)
152         {
153                 // try to load legacy ini-files first
154                 foreach ($this->getConfigFiles(true) as $configFile) {
155                         $config->load($this->loadINIConfigFile($configFile), Cache::SOURCE_FILE);
156                 }
157
158                 // try to load supported config at last to overwrite it
159                 foreach ($this->getConfigFiles() as $configFile) {
160                         $config->load($this->loadConfigFile($configFile), Cache::SOURCE_FILE);
161                 }
162         }
163
164         /**
165          * Tries to load the data config file with the overridden data
166          *
167          * @param Cache $config The Config cache
168          *
169          * @throws ConfigFileException In case the config file isn't loadable
170          */
171         private function loadDataConfig(Cache $config)
172         {
173                 $filename = $this->configDir . '/' . self::CONFIG_DATA_FILE;
174
175                 if (file_exists($filename)) {
176                         $dataArray = include $filename;
177
178                         if (!is_array($dataArray)) {
179                                 throw new ConfigFileException(sprintf('Error loading config file %s', $filename));
180                         }
181
182                         $config->load($dataArray, Cache::SOURCE_DATA);
183                 }
184         }
185
186         /**
187          * Saves overridden config entries back into the data.config.phpR
188          *
189          * @param Cache $config The config cache
190          *
191          * @throws ConfigFileException In case the config file isn't writeable or the data is invalid
192          */
193         public function saveData(Cache $config)
194         {
195                 $data = $config->getDataBySource(Cache::SOURCE_DATA);
196
197                 $encodedData = ConfigFileTransformer::encode($data);
198
199                 if (!$encodedData) {
200                         throw new ConfigFileException('config source cannot get encoded');
201                 }
202
203                 if (!file_put_contents($this->configDir . '/' . self::CONFIG_DATA_FILE, $encodedData)) {
204                         throw new ConfigFileException(sprintf('Cannot save data to file %s/%s', $this->configDir, self::CONFIG_DATA_FILE));
205                 }
206         }
207
208         /**
209          * Tries to load the specified addon-configuration and returns the config array.
210          *
211          * @param string $name The name of the configuration
212          *
213          * @return array The config array (empty if no config found)
214          *
215          * @throws ConfigFileException if the configuration file isn't readable
216          */
217         public function loadAddonConfig(string $name): array
218         {
219                 $filepath = $this->baseDir . DIRECTORY_SEPARATOR .   // /var/www/html/
220                                         Addon::DIRECTORY . DIRECTORY_SEPARATOR . // addon/
221                                         $name . DIRECTORY_SEPARATOR .            // openstreetmap/
222                                         'config'. DIRECTORY_SEPARATOR .                  // config/
223                                         $name . ".config.php";                   // openstreetmap.config.php
224
225                 if (file_exists($filepath)) {
226                         return $this->loadConfigFile($filepath);
227                 } else {
228                         return [];
229                 }
230         }
231
232         /**
233          * Tries to load environment specific variables, based on the `env.config.php` mapping table
234          *
235          * @param array $server The $_SERVER variable
236          *
237          * @return array The config array (empty if no config was found)
238          *
239          * @throws ConfigFileException if the configuration file isn't readable
240          */
241         public function loadEnvConfig(array $server): array
242         {
243                 $filepath = $this->staticDir . DIRECTORY_SEPARATOR .   // /var/www/html/static/
244                                         "env.config.php";                          // env.config.php
245
246                 if (!file_exists($filepath)) {
247                         return [];
248                 }
249
250                 $envConfig = $this->loadConfigFile($filepath);
251
252                 $return = [];
253
254                 foreach ($envConfig as $envKey => $configStructure) {
255                         if (isset($server[$envKey])) {
256                                 $return[$configStructure[0]][$configStructure[1]] = $server[$envKey];
257                         }
258                 }
259
260                 return $return;
261         }
262
263         /**
264          * Get the config files of the config-directory
265          *
266          * @param bool $ini True, if scan for ini-files instead of config files
267          *
268          * @return array
269          */
270         private function getConfigFiles(bool $ini = false): array
271         {
272                 $files = scandir($this->configDir);
273                 $found = [];
274
275                 $filePattern = ($ini ? '*.ini.php' : '*.config.php');
276
277                 // Don't load sample files
278                 $sampleEnd = self::SAMPLE_END . ($ini ? '.ini.php' : '.config.php');
279
280                 foreach ($files as $filename) {
281                         if (fnmatch($filePattern, $filename) && substr_compare($filename, $sampleEnd, -strlen($sampleEnd))) {
282                                 $found[] = $this->configDir . '/' . $filename;
283                         }
284                 }
285
286                 return $found;
287         }
288
289         /**
290          * Tries to load the legacy config files (.htconfig.php, .htpreconfig.php) and returns the config array.
291          *
292          * @param string $name The name of the config file (default is empty, which means .htconfig.php)
293          *
294          * @return array The configuration array (empty if no config found)
295          *
296          * @deprecated since version 2018.09
297          */
298         private function loadLegacyConfig(string $name = ''): array
299         {
300                 $name     = !empty($name) ? $name : self::CONFIG_HTCONFIG;
301                 $fullName = $this->baseDir . DIRECTORY_SEPARATOR . '.' . $name . '.php';
302
303                 $config = [];
304                 if (file_exists($fullName)) {
305                         $a         = new \stdClass();
306                         $a->config = [];
307                         include $fullName;
308
309                         $htConfigCategories = array_keys($a->config);
310
311                         // map the legacy configuration structure to the current structure
312                         foreach ($htConfigCategories as $htConfigCategory) {
313                                 if (is_array($a->config[$htConfigCategory])) {
314                                         $keys = array_keys($a->config[$htConfigCategory]);
315
316                                         foreach ($keys as $key) {
317                                                 $config[$htConfigCategory][$key] = $a->config[$htConfigCategory][$key];
318                                         }
319                                 } else {
320                                         $config['config'][$htConfigCategory] = $a->config[$htConfigCategory];
321                                 }
322                         }
323
324                         unset($a);
325
326                         if (isset($db_host)) {
327                                 $config['database']['hostname'] = $db_host;
328                                 unset($db_host);
329                         }
330                         if (isset($db_user)) {
331                                 $config['database']['username'] = $db_user;
332                                 unset($db_user);
333                         }
334                         if (isset($db_pass)) {
335                                 $config['database']['password'] = $db_pass;
336                                 unset($db_pass);
337                         }
338                         if (isset($db_data)) {
339                                 $config['database']['database'] = $db_data;
340                                 unset($db_data);
341                         }
342                         if (isset($config['system']['db_charset'])) {
343                                 $config['database']['charset'] = $config['system']['db_charset'];
344                         }
345                         if (isset($pidfile)) {
346                                 $config['system']['pidfile'] = $pidfile;
347                                 unset($pidfile);
348                         }
349                         if (isset($default_timezone)) {
350                                 $config['system']['default_timezone'] = $default_timezone;
351                                 unset($default_timezone);
352                         }
353                         if (isset($lang)) {
354                                 $config['system']['language'] = $lang;
355                                 unset($lang);
356                         }
357                 }
358
359                 return $config;
360         }
361
362         /**
363          * Tries to load the specified legacy configuration file and returns the config array.
364          *
365          * @param string $filepath
366          *
367          * @return array The configuration array
368          * @throws ConfigFileException
369          * @deprecated since version 2018.12
370          */
371         private function loadINIConfigFile(string $filepath): array
372         {
373                 $contents = include($filepath);
374
375                 $config = parse_ini_string($contents, true, INI_SCANNER_TYPED);
376
377                 if ($config === false) {
378                         throw new ConfigFileException('Error parsing INI config file ' . $filepath);
379                 }
380
381                 return $config;
382         }
383
384         /**
385          * Tries to load the specified configuration file and returns the config array.
386          *
387          * The config format is PHP array and the template for configuration files is the following:
388          *
389          * <?php return [
390          *      'section' => [
391          *          'key' => 'value',
392          *      ],
393          * ];
394          *
395          * @param string $filepath The filepath of the
396          *
397          * @return array The config array0
398          *
399          * @throws ConfigFileException if the config cannot get loaded.
400          */
401         private function loadConfigFile(string $filepath): array
402         {
403                 if (file_exists($filepath)) {
404                         $config = include $filepath;
405
406                         if (!is_array($config)) {
407                                 throw new ConfigFileException('Error loading config file ' . $filepath);
408                         }
409
410                         return $config;
411                 } else {
412                         return [];
413                 }
414         }
415 }