3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
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.
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.
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/>.
22 namespace Friendica\Core\Config\ValueObject;
24 use Friendica\Core\Config\Util\ConfigFileManager;
25 use ParagonIE\HiddenString\HiddenString;
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 )
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 */
42 /** @var int Indicates that the cache entry is fixed and must not be changed */
45 /** @var int Default value for a config source */
46 const SOURCE_DEFAULT = self::SOURCE_FILE;
61 private $delConfig = [];
66 private $hidePasswordOutput;
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
73 public function __construct(array $config = [], bool $hidePasswordOutput = true, int $source = self::SOURCE_DEFAULT)
75 $this->hidePasswordOutput = $hidePasswordOutput;
76 $this->load($config, $source);
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.
83 * @param array $config
84 * @param int $source Indicates the source of the config entry
86 public function load(array $config, int $source = self::SOURCE_DEFAULT)
88 $categories = array_keys($config);
90 foreach ($categories as $category) {
91 if (is_array($config[$category])) {
92 $keys = array_keys($config[$category]);
94 foreach ($keys as $key) {
95 $value = $config[$category][$key];
97 $this->set($category, $key, $value, $source);
105 * Gets a value from the config cache.
107 * @param string $cat Config category
108 * @param string|null $key Config key
110 * @return null|mixed Returns the value of the Config entry or null if not set
112 public function get(string $cat, ?string $key = null)
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];
124 * Returns the source value of the current, cached config value
126 * @param string $cat Config category
127 * @param string $key Config key
131 public function getSource(string $cat, string $key): int
133 return $this->source[$cat][$key] ?? -1;
137 * Returns the whole config array based on the given source type
139 * @param int $source Indicates the source of the config entry
141 * @return array The config array part of the given source
143 public function getDataBySource(int $source): array
147 $categories = array_keys($this->source);
149 foreach ($categories as $category) {
150 if (is_array($this->source[$category])) {
151 $keys = array_keys($this->source[$category]);
153 foreach ($keys as $key) {
154 if ($this->source[$category][$key] === $source) {
155 $data[$category][$key] = $this->config[$category][$key];
165 * Sets a value in the config cache. Accepts raw output from the config table
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
172 * @return bool True, if the value is set
174 public function set(string $cat, string $key, $value, int $source = self::SOURCE_DEFAULT): bool
176 if (!isset($this->config[$cat])) {
177 $this->config[$cat] = [];
178 $this->source[$cat] = [];
181 if (isset($this->source[$cat][$key]) &&
182 $source < $this->source[$cat][$key]) {
186 if ($this->hidePasswordOutput &&
187 $key == 'password' &&
189 $this->config[$cat][$key] = new HiddenString((string)$value);
190 } else if (is_string($value)) {
191 $this->config[$cat][$key] = self::toConfigValue($value);
193 $this->config[$cat][$key] = $value;
196 $this->source[$cat][$key] = $source;
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
208 * Keep in mind that there aren't any numeric/integer config values in the database
210 * @param string|null $value
214 public static function toConfigValue(?string $value)
216 if (!isset($value)) {
220 if (preg_match("|^a:[0-9]+:{.*}$|s", $value)) {
221 return unserialize($value);
228 * Deletes a value from the config cache.
230 * @param string $cat Config category
231 * @param string $key Config key
233 * @return bool true, if deleted
235 public function delete(string $cat, string $key): bool
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]);
252 * Returns the whole configuration
254 * @return string[][] The configuration
256 public function getAll(): array
258 return $this->config;
262 * Returns an array with missing categories/Keys
264 * @param string[][] $config The array to check
268 public function keyDiff(array $config): array
272 $categories = array_keys($config);
274 foreach ($categories as $category) {
275 if (is_array($config[$category])) {
276 $keys = array_keys($config[$category]);
278 foreach ($keys as $key) {
279 if (!isset($this->config[$category][$key])) {
280 $return[$category][$key] = $config[$category][$key];
290 * Merges a new Cache into the existing one and returns the merged Cache
292 * @param Cache $cache The cache, which should get merged into this Cache
294 * @return Cache The merged Cache
296 public function merge(Cache $cache): Cache
298 $newConfig = $this->config;
299 $newSource = $this->source;
301 $categories = array_keys($cache->config);
303 foreach ($categories as $category) {
304 if (is_array($cache->config[$category])) {
305 $keys = array_keys($cache->config[$category]);
307 if (is_null($newConfig[$category] ?? null)) {
308 $newConfig[$category] = [];
309 $newSource[$category] = [];
312 foreach ($keys as $key) {
313 $newConfig[$category][$key] = $cache->config[$category][$key];
314 $newSource[$category][$key] = $cache->source[$category][$key];
317 $newConfig[$category] = $cache->config[$category];
318 $newSource[$category] = $cache->source[$category];
322 $delCategories = array_keys($cache->delConfig);
324 foreach ($delCategories as $category) {
325 if (is_array($cache->delConfig[$category])) {
326 $keys = array_keys($cache->delConfig[$category]);
328 foreach ($keys as $key) {
329 unset($newConfig[$category][$key]);
330 unset($newSource[$category][$key]);
335 $newCache = new Cache();
336 $newCache->config = $newConfig;
337 $newCache->source = $newSource;