]> git.mxchange.org Git - friendica.git/blob - src/Core/Config/ValueObject/Cache.php
Merge pull request #12653 from nupplaphil/bug/config_revert_null
[friendica.git] / src / Core / Config / ValueObject / Cache.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\ValueObject;
23
24 use Friendica\Core\Config\Util\ConfigFileManager;
25 use ParagonIE\HiddenString\HiddenString;
26
27 /**
28  * The Friendica config cache for the application
29  * Initial, all *.config.php files are loaded into this cache with the
30  * ConfigFileManager ( @see ConfigFileManager )
31  */
32 class Cache
33 {
34         /** @var int Indicates that the cache entry is a default value - Lowest Priority */
35         const SOURCE_STATIC = 0;
36         /** @var int Indicates that the cache entry is set by file - Low Priority */
37         const SOURCE_FILE = 1;
38         /** @var int Indicates that the cache entry is manually set by the application (per admin page/console) - Middle Priority */
39         const SOURCE_DATA = 2;
40         /** @var int Indicates that the cache entry is set by a server environment variable - High Priority */
41         const SOURCE_ENV = 3;
42         /** @var int Indicates that the cache entry is fixed and must not be changed */
43         const SOURCE_FIX = 5;
44
45         /** @var int Default value for a config source */
46         const SOURCE_DEFAULT = self::SOURCE_FILE;
47
48         /**
49          * @var array
50          */
51         private $config = [];
52
53         /**
54          * @var int[][]
55          */
56         private $source = [];
57
58         /**
59          * @var bool[][]
60          */
61         private $delConfig = [];
62
63         /**
64          * @var bool
65          */
66         private $hidePasswordOutput;
67
68         /**
69          * @param array $config             A initial config array
70          * @param bool  $hidePasswordOutput True, if cache variables should take extra care of password values
71          * @param int   $source             Sets a source of the initial config values
72          */
73         public function __construct(array $config = [], bool $hidePasswordOutput = true, int $source = self::SOURCE_DEFAULT)
74         {
75                 $this->hidePasswordOutput = $hidePasswordOutput;
76                 $this->load($config, $source);
77         }
78
79         /**
80          * Tries to load the specified configuration array into the config array.
81          * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
82          *
83          * @param array $config
84          * @param int   $source Indicates the source of the config entry
85          */
86         public function load(array $config, int $source = self::SOURCE_DEFAULT)
87         {
88                 $categories = array_keys($config);
89
90                 foreach ($categories as $category) {
91                         if (is_array($config[$category])) {
92                                 $keys = array_keys($config[$category]);
93
94                                 foreach ($keys as $key) {
95                                         $value = $config[$category][$key];
96                                         if (isset($value)) {
97                                                 $this->set($category, $key, $value, $source);
98                                         }
99                                 }
100                         }
101                 }
102         }
103
104         /**
105          * Gets a value from the config cache.
106          *
107          * @param string      $cat Config category
108          * @param string|null $key Config key
109          *
110          * @return null|mixed Returns the value of the Config entry or null if not set
111          */
112         public function get(string $cat, ?string $key = null)
113         {
114                 if (isset($this->config[$cat][$key])) {
115                         return $this->config[$cat][$key];
116                 } elseif (!isset($key) && isset($this->config[$cat])) {
117                         return $this->config[$cat];
118                 } else {
119                         return null;
120                 }
121         }
122
123         /**
124          * Returns the source value of the current, cached config value
125          *
126          * @param string $cat Config category
127          * @param string $key Config key
128          *
129          * @return int
130          */
131         public function getSource(string $cat, string $key): int
132         {
133                 return $this->source[$cat][$key] ?? -1;
134         }
135
136         /**
137          * Returns the whole config array based on the given source type
138          *
139          * @param int $source Indicates the source of the config entry
140          *
141          * @return array The config array part of the given source
142          */
143         public function getDataBySource(int $source): array
144         {
145                 $data = [];
146
147                 $categories = array_keys($this->source);
148
149                 foreach ($categories as $category) {
150                         if (is_array($this->source[$category])) {
151                                 $keys = array_keys($this->source[$category]);
152
153                                 foreach ($keys as $key) {
154                                         if ($this->source[$category][$key] === $source) {
155                                                 $data[$category][$key] = $this->config[$category][$key];
156                                         }
157                                 }
158                         }
159                 }
160
161                 return $data;
162         }
163
164         /**
165          * Sets a value in the config cache. Accepts raw output from the config table
166          *
167          * @param string $cat    Config category
168          * @param string $key    Config key
169          * @param mixed  $value  Value to set
170          * @param int    $source The source of the current config key
171          *
172          * @return bool True, if the value is set
173          */
174         public function set(string $cat, string $key, $value, int $source = self::SOURCE_DEFAULT): bool
175         {
176                 if (!isset($this->config[$cat])) {
177                         $this->config[$cat] = [];
178                         $this->source[$cat] = [];
179                 }
180
181                 if (isset($this->source[$cat][$key]) &&
182                         $source < $this->source[$cat][$key]) {
183                         return false;
184                 }
185
186                 if ($this->hidePasswordOutput &&
187                         $key == 'password' &&
188                         is_string($value)) {
189                         $this->config[$cat][$key] = new HiddenString((string)$value);
190                 } else if (is_string($value)) {
191                         $this->config[$cat][$key] = self::toConfigValue($value);
192                 } else {
193                         $this->config[$cat][$key] = $value;
194                 }
195
196                 $this->source[$cat][$key] = $source;
197
198                 return true;
199         }
200
201         /**
202          * Formats a DB value to a config value
203          * - null   = The db-value isn't set
204          * - bool   = The db-value is either '0' or '1'
205          * - array  = The db-value is a serialized array
206          * - string = The db-value is a string
207          *
208          * Keep in mind that there aren't any numeric/integer config values in the database
209          *
210          * @param string|null $value
211          *
212          * @return mixed
213          */
214         public static function toConfigValue(?string $value)
215         {
216                 if (!isset($value)) {
217                         return null;
218                 }
219
220                 if (preg_match("|^a:[0-9]+:{.*}$|s", $value)) {
221                         return unserialize($value);
222                 } else {
223                         return $value;
224                 }
225         }
226
227         /**
228          * Deletes a value from the config cache.
229          *
230          * @param string $cat Config category
231          * @param string $key Config key
232          *
233          * @return bool true, if deleted
234          */
235         public function delete(string $cat, string $key): bool
236         {
237                 if (isset($this->config[$cat][$key])) {
238                         unset($this->config[$cat][$key]);
239                         unset($this->source[$cat][$key]);
240                         $this->delConfig[$cat][$key] = true;
241                         if (count($this->config[$cat]) == 0) {
242                                 unset($this->config[$cat]);
243                                 unset($this->source[$cat]);
244                         }
245                         return true;
246                 } else {
247                         return false;
248                 }
249         }
250
251         /**
252          * Returns the whole configuration
253          *
254          * @return string[][] The configuration
255          */
256         public function getAll(): array
257         {
258                 return $this->config;
259         }
260
261         /**
262          * Returns an array with missing categories/Keys
263          *
264          * @param string[][] $config The array to check
265          *
266          * @return string[][]
267          */
268         public function keyDiff(array $config): array
269         {
270                 $return = [];
271
272                 $categories = array_keys($config);
273
274                 foreach ($categories as $category) {
275                         if (is_array($config[$category])) {
276                                 $keys = array_keys($config[$category]);
277
278                                 foreach ($keys as $key) {
279                                         if (!isset($this->config[$category][$key])) {
280                                                 $return[$category][$key] = $config[$category][$key];
281                                         }
282                                 }
283                         }
284                 }
285
286                 return $return;
287         }
288
289         /**
290          * Merges a new Cache into the existing one and returns the merged Cache
291          *
292          * @param Cache $cache The cache, which should get merged into this Cache
293          *
294          * @return Cache The merged Cache
295          */
296         public function merge(Cache $cache): Cache
297         {
298                 $newConfig = $this->config;
299                 $newSource = $this->source;
300
301                 $categories = array_keys($cache->config);
302
303                 foreach ($categories as $category) {
304                         if (is_array($cache->config[$category])) {
305                                 $keys = array_keys($cache->config[$category]);
306
307                                 if (is_null($newConfig[$category] ?? null)) {
308                                         $newConfig[$category] = [];
309                                         $newSource[$category] = [];
310                                 }
311
312                                 foreach ($keys as $key) {
313                                         $newConfig[$category][$key] = $cache->config[$category][$key];
314                                         $newSource[$category][$key] = $cache->source[$category][$key];
315                                 }
316                         } else {
317                                 $newConfig[$category] = $cache->config[$category];
318                                 $newSource[$category] = $cache->source[$category];
319                         }
320                 }
321
322                 $delCategories = array_keys($cache->delConfig);
323
324                 foreach ($delCategories as $category) {
325                         if (is_array($cache->delConfig[$category])) {
326                                 $keys = array_keys($cache->delConfig[$category]);
327
328                                 foreach ($keys as $key) {
329                                         unset($newConfig[$category][$key]);
330                                         unset($newSource[$category][$key]);
331                                 }
332                         }
333                 }
334
335                 $newCache = new Cache();
336                 $newCache->config = $newConfig;
337                 $newCache->source = $newSource;
338
339                 return $newCache;
340         }
341 }