]> git.mxchange.org Git - friendica.git/blob - src/Core/Config/ValueObject/Cache.php
Merge remote-tracking branch 'upstream/develop' into mod-item
[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 $hidePasswordOutput;
62
63         /**
64          * @param array $config             A initial config array
65          * @param bool  $hidePasswordOutput True, if cache variables should take extra care of password values
66          * @param int   $source             Sets a source of the initial config values
67          */
68         public function __construct(array $config = [], bool $hidePasswordOutput = true, int $source = self::SOURCE_DEFAULT)
69         {
70                 $this->hidePasswordOutput = $hidePasswordOutput;
71                 $this->load($config, $source);
72         }
73
74         /**
75          * Tries to load the specified configuration array into the config array.
76          * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
77          *
78          * @param array $config
79          * @param int   $source Indicates the source of the config entry
80          */
81         public function load(array $config, int $source = self::SOURCE_DEFAULT)
82         {
83                 $categories = array_keys($config);
84
85                 foreach ($categories as $category) {
86                         if (is_array($config[$category])) {
87                                 $keys = array_keys($config[$category]);
88
89                                 foreach ($keys as $key) {
90                                         $this->set($category, $key, $config[$category][$key] ?? null, $source);
91                                 }
92                         } else {
93                                 $this->set($category, null, $config[$category], $source);
94                         }
95                 }
96         }
97
98         /**
99          * Gets a value from the config cache.
100          *
101          * @param string      $cat Config category
102          * @param string|null $key Config key
103          *
104          * @return null|mixed Returns the value of the Config entry or null if not set
105          */
106         public function get(string $cat, ?string $key = null)
107         {
108                 if (isset($this->config[$cat][$key])) {
109                         return $this->config[$cat][$key];
110                 } elseif (!isset($key) && isset($this->config[$cat])) {
111                         return $this->config[$cat];
112                 } else {
113                         return null;
114                 }
115         }
116
117         /**
118          * Returns the source value of the current, cached config value
119          *
120          * @param string $cat Config category
121          * @param string $key Config key
122          *
123          * @return int
124          */
125         public function getSource(string $cat, string $key): int
126         {
127                 return $this->source[$cat][$key] ?? -1;
128         }
129
130         /**
131          * Returns the whole config array based on the given source type
132          *
133          * @param int $source Indicates the source of the config entry
134          *
135          * @return array The config array part of the given source
136          */
137         public function getDataBySource(int $source): array
138         {
139                 $data = [];
140
141                 $categories = array_keys($this->source);
142
143                 foreach ($categories as $category) {
144                         if (is_array($this->source[$category])) {
145                                 $keys = array_keys($this->source[$category]);
146
147                                 foreach ($keys as $key) {
148                                         if ($this->source[$category][$key] === $source) {
149                                                 $data[$category][$key] = $this->config[$category][$key];
150                                         }
151                                 }
152                         } elseif (is_int($this->source[$category])) {
153                                 $data[$category] = null;
154                         }
155                 }
156
157                 return $data;
158         }
159
160         /**
161          * Sets a value in the config cache. Accepts raw output from the config table
162          *
163          * @param string  $cat    Config category
164          * @param ?string $key    Config key
165          * @param ?mixed  $value  Value to set
166          * @param int     $source The source of the current config key
167          *
168          * @return bool True, if the value is set
169          */
170         public function set(string $cat, string $key = null, $value = null, int $source = self::SOURCE_DEFAULT): bool
171         {
172                 if (!isset($this->config[$cat]) && $key !== null) {
173                         $this->config[$cat] = [];
174                         $this->source[$cat] = [];
175                 }
176
177                 if ((isset($this->source[$cat][$key]) && $source < $this->source[$cat][$key]) ||
178                         (is_int($this->source[$cat] ?? null) && $source < $this->source[$cat])) {
179                         return false;
180                 }
181
182                 if ($this->hidePasswordOutput &&
183                         $key == 'password' &&
184                         is_string($value)) {
185                         $this->setCatKeyValueSource($cat, $key, new HiddenString($value), $source);
186                 } else if (is_string($value)) {
187                         $this->setCatKeyValueSource($cat, $key, self::toConfigValue($value), $source);
188                 } else {
189                         $this->setCatKeyValueSource($cat, $key, $value, $source);
190                 }
191
192                 return true;
193         }
194
195         private function setCatKeyValueSource(string $cat, string $key = null, $value = null, int $source = self::SOURCE_DEFAULT)
196         {
197                 if (isset($key)) {
198                         $this->config[$cat][$key] = $value;
199                         $this->source[$cat][$key] = $source;
200                 } else {
201                         $this->config[$cat] = $value;
202                         $this->source[$cat] = $source;
203                 }
204         }
205
206         /**
207          * Formats a DB value to a config value
208          * - null   = The db-value isn't set
209          * - bool   = The db-value is either '0' or '1'
210          * - array  = The db-value is a serialized array
211          * - string = The db-value is a string
212          *
213          * Keep in mind that there aren't any numeric/integer config values in the database
214          *
215          * @param string|null $value
216          *
217          * @return null|array|string
218          */
219         public static function toConfigValue(?string $value)
220         {
221                 if (!isset($value)) {
222                         return null;
223                 }
224
225                 if (preg_match("|^a:[0-9]+:{.*}$|s", $value)) {
226                         return unserialize($value);
227                 } else {
228                         return $value;
229                 }
230         }
231
232         /**
233          * Deletes a value from the config cache.
234          *
235          * @param string  $cat Config category
236          * @param ?string $key Config key (if not set, the whole category will get deleted)
237          * @param int     $source The source of the current config key
238          *
239          * @return bool true, if deleted
240          */
241         public function delete(string $cat, string $key = null, int $source = self::SOURCE_DEFAULT): bool
242         {
243                 if (isset($this->config[$cat][$key])) {
244                         $this->config[$cat][$key] = null;
245                         $this->source[$cat][$key] = $source;
246                         if (empty(array_filter($this->config[$cat], function($value) { return !is_null($value); }))) {
247                                 $this->config[$cat] = null;
248                                 $this->source[$cat] = $source;
249                         }
250                 } elseif (isset($this->config[$cat])) {
251                         $this->config[$cat] = null;
252                         $this->source[$cat] = $source;
253                 }
254
255                 return true;
256         }
257
258         /**
259          * Returns the whole configuration
260          *
261          * @return string[][] The configuration
262          */
263         public function getAll(): array
264         {
265                 return $this->config;
266         }
267
268         /**
269          * Returns an array with missing categories/Keys
270          *
271          * @param string[][] $config The array to check
272          *
273          * @return string[][]
274          */
275         public function keyDiff(array $config): array
276         {
277                 $return = [];
278
279                 $categories = array_keys($config);
280
281                 foreach ($categories as $category) {
282                         if (is_array($config[$category])) {
283                                 $keys = array_keys($config[$category]);
284
285                                 foreach ($keys as $key) {
286                                         if (!isset($this->config[$category][$key])) {
287                                                 $return[$category][$key] = $config[$category][$key];
288                                         }
289                                 }
290                         }
291                 }
292
293                 return $return;
294         }
295
296         /**
297          * Merges a new Cache into the existing one and returns the merged Cache
298          *
299          * @param Cache $cache The cache, which should get merged into this Cache
300          *
301          * @return Cache The merged Cache
302          */
303         public function merge(Cache $cache): Cache
304         {
305                 $newConfig = $this->config;
306                 $newSource = $this->source;
307
308                 $categories = array_keys($cache->config);
309
310                 foreach ($categories as $category) {
311                         if (is_array($cache->config[$category])) {
312                                 $keys = array_keys($cache->config[$category]);
313
314                                 foreach ($keys as $key) {
315                                         $newConfig[$category][$key] = $cache->config[$category][$key];
316                                         $newSource[$category][$key] = $cache->source[$category][$key];
317                                 }
318                         } else {
319                                 $newConfig[$category] = $cache->config[$category];
320                                 $newSource[$category] = $cache->source[$category];
321                         }
322                 }
323
324                 $newCache = new Cache();
325                 $newCache->config = $newConfig;
326                 $newCache->source = $newSource;
327
328                 return $newCache;
329         }
330 }