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[] A list of valid config source */
35 const VALID_SOURCES = [
43 /** @var int Indicates that the cache entry is a default value - Lowest Priority */
44 const SOURCE_STATIC = 0;
45 /** @var int Indicates that the cache entry is set by file - Low Priority */
46 const SOURCE_FILE = 1;
47 /** @var int Indicates that the cache entry is manually set by the application (per admin page/console) - Middle Priority */
48 const SOURCE_DATA = 2;
49 /** @var int Indicates that the cache entry is set by a server environment variable - High Priority */
51 /** @var int Indicates that the cache entry is fixed and must not be changed */
54 /** @var int Default value for a config source */
55 const SOURCE_DEFAULT = self::SOURCE_FILE;
70 private $delConfig = [];
75 private $hidePasswordOutput;
78 * @param array $config A initial config array
79 * @param bool $hidePasswordOutput True, if cache variables should take extra care of password values
80 * @param int $source Sets a source of the initial config values
82 public function __construct(array $config = [], bool $hidePasswordOutput = true, int $source = self::SOURCE_DEFAULT)
84 $this->hidePasswordOutput = $hidePasswordOutput;
85 $this->load($config, $source);
89 * Tries to load the specified configuration array into the config array.
90 * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
92 * @param array $config
93 * @param int $source Indicates the source of the config entry
95 public function load(array $config, int $source = self::SOURCE_DEFAULT)
97 $categories = array_keys($config);
99 foreach ($categories as $category) {
100 if (is_array($config[$category])) {
101 $keys = array_keys($config[$category]);
103 foreach ($keys as $key) {
104 $value = $config[$category][$key];
106 $this->set($category, $key, $value, $source);
114 * Gets a value from the config cache.
116 * @param string $cat Config category
117 * @param string|null $key Config key
119 * @return null|mixed Returns the value of the Config entry or null if not set
121 public function get(string $cat, ?string $key = null)
123 if (isset($this->config[$cat][$key])) {
124 return $this->config[$cat][$key];
125 } elseif (!isset($key) && isset($this->config[$cat])) {
126 return $this->config[$cat];
133 * Returns the source value of the current, cached config value
135 * @param string $cat Config category
136 * @param string $key Config key
140 public function getSource(string $cat, string $key): int
142 return $this->source[$cat][$key] ?? -1;
146 * Returns the whole config array based on the given source type
148 * @param int $source Indicates the source of the config entry
150 * @return array The config array part of the given source
152 public function getDataBySource(int $source): array
156 $categories = array_keys($this->source);
158 foreach ($categories as $category) {
159 if (is_array($this->source[$category])) {
160 $keys = array_keys($this->source[$category]);
162 foreach ($keys as $key) {
163 if ($this->source[$category][$key] === $source) {
164 $data[$category][$key] = $this->config[$category][$key];
174 * Sets a value in the config cache. Accepts raw output from the config table
176 * @param string $cat Config category
177 * @param string $key Config key
178 * @param mixed $value Value to set
179 * @param int $source The source of the current config key
181 * @return bool True, if the value is set
183 public function set(string $cat, string $key, $value, int $source = self::SOURCE_DEFAULT): bool
185 if (!isset($this->config[$cat])) {
186 $this->config[$cat] = [];
187 $this->source[$cat] = [];
190 if (isset($this->source[$cat][$key]) &&
191 $source < $this->source[$cat][$key]) {
195 if ($this->hidePasswordOutput &&
196 $key == 'password' &&
198 $this->config[$cat][$key] = new HiddenString((string)$value);
199 } else if (is_string($value)) {
200 $this->config[$cat][$key] = self::toConfigValue($value);
202 $this->config[$cat][$key] = $value;
205 $this->source[$cat][$key] = $source;
211 * Formats a DB value to a config value
212 * - null = The db-value isn't set
213 * - bool = The db-value is either '0' or '1'
214 * - array = The db-value is a serialized array
215 * - string = The db-value is a string
217 * Keep in mind that there aren't any numeric/integer config values in the database
219 * @param string|null $value
223 public static function toConfigValue(?string $value)
225 if (!isset($value)) {
229 if (preg_match("|^a:[0-9]+:{.*}$|s", $value)) {
230 return unserialize($value);
237 * Deletes a value from the config cache.
239 * @param string $cat Config category
240 * @param string $key Config key
242 * @return bool true, if deleted
244 public function delete(string $cat, string $key): bool
246 if (isset($this->config[$cat][$key])) {
247 unset($this->config[$cat][$key]);
248 unset($this->source[$cat][$key]);
249 $this->delConfig[$cat][$key] = true;
250 if (count($this->config[$cat]) == 0) {
251 unset($this->config[$cat]);
252 unset($this->source[$cat]);
261 * Returns the whole configuration
263 * @return string[][] The configuration
265 public function getAll(): array
267 return $this->config;
271 * Returns an array with missing categories/Keys
273 * @param string[][] $config The array to check
277 public function keyDiff(array $config): array
281 $categories = array_keys($config);
283 foreach ($categories as $category) {
284 if (is_array($config[$category])) {
285 $keys = array_keys($config[$category]);
287 foreach ($keys as $key) {
288 if (!isset($this->config[$category][$key])) {
289 $return[$category][$key] = $config[$category][$key];
299 * Merges a new Cache into the existing one and returns the merged Cache
301 * @param Cache $cache The cache, which should get merged into this Cache
303 * @return Cache The merged Cache
305 public function merge(Cache $cache): Cache
307 $newConfig = $this->config;
308 $newSource = $this->source;
310 $categories = array_keys($cache->config);
312 foreach ($categories as $category) {
313 if (is_array($cache->config[$category])) {
314 $keys = array_keys($cache->config[$category]);
316 if (is_null($newConfig[$category] ?? null)) {
317 $newConfig[$category] = [];
318 $newSource[$category] = [];
321 foreach ($keys as $key) {
322 $newConfig[$category][$key] = $cache->config[$category][$key];
323 $newSource[$category][$key] = $cache->source[$category][$key];
326 $newConfig[$category] = $cache->config[$category];
327 $newSource[$category] = $cache->source[$category];
331 $delCategories = array_keys($cache->delConfig);
333 foreach ($delCategories as $category) {
334 if (is_array($cache->delConfig[$category])) {
335 $keys = array_keys($cache->delConfig[$category]);
337 foreach ($keys as $key) {
338 unset($newConfig[$category][$key]);
339 unset($newSource[$category][$key]);
344 $newCache = new Cache();
345 $newCache->config = $newConfig;
346 $newCache->source = $newSource;