2011-03-01 17:20:16 -05:00
< ? php
2024-01-11 11:32:58 -05:00
declare ( strict_types = 1 );
2011-03-12 04:28:10 -05:00
/**
2024-05-23 03:26:56 -04:00
* SPDX - FileCopyrightText : 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2015-02-26 05:37:37 -05:00
*/
2024-01-11 11:32:58 -05:00
2013-03-02 11:17:11 -05:00
namespace OC ;
2024-01-11 11:32:58 -05:00
use InvalidArgumentException ;
use JsonException ;
2024-12-06 05:35:00 -05:00
use OC\AppFramework\Bootstrap\Coordinator ;
2025-06-04 09:23:36 -04:00
use OC\Config\ConfigManager ;
2025-07-29 10:47:23 -04:00
use OC\Config\PresetManager ;
2025-08-12 16:20:15 -04:00
use OC\Memcache\Factory as CacheFactory ;
2025-07-16 09:46:45 -04:00
use OCP\Config\Lexicon\Entry ;
use OCP\Config\Lexicon\Strictness ;
2025-07-23 12:22:51 -04:00
use OCP\Config\ValueType ;
2024-01-11 11:32:58 -05:00
use OCP\DB\Exception as DBException ;
2023-02-20 08:46:00 -05:00
use OCP\DB\QueryBuilder\IQueryBuilder ;
2024-01-11 11:32:58 -05:00
use OCP\Exceptions\AppConfigIncorrectTypeException ;
use OCP\Exceptions\AppConfigTypeConflictException ;
use OCP\Exceptions\AppConfigUnknownKeyException ;
2015-05-11 06:37:14 -04:00
use OCP\IAppConfig ;
2025-08-12 08:57:07 -04:00
use OCP\ICache ;
use OCP\ICacheFactory ;
2017-01-11 05:42:36 -05:00
use OCP\IConfig ;
2024-01-11 11:32:58 -05:00
use OCP\IDBConnection ;
2024-01-25 08:27:51 -05:00
use OCP\Security\ICrypto ;
2024-01-11 11:32:58 -05:00
use Psr\Log\LoggerInterface ;
2013-03-02 11:17:11 -05:00
2011-03-12 04:28:10 -05:00
/**
* This class provides an easy way for apps to store config values in the
* database .
2024-01-11 11:32:58 -05:00
*
2024-01-15 09:50:44 -05:00
* ** Note :** since 29.0 . 0 , it supports ** lazy loading **
2024-01-11 11:32:58 -05:00
*
2024-01-15 09:50:44 -05:00
* ### What is lazy loading ?
* In order to avoid loading useless config values into memory for each request ,
* only non - lazy values are now loaded .
2024-01-11 11:32:58 -05:00
*
2024-01-15 09:50:44 -05:00
* Once a value that is lazy is requested , all lazy values will be loaded .
2024-01-11 11:32:58 -05:00
*
2024-01-15 09:50:44 -05:00
* Similarly , some methods from this class are marked with a warning about ignoring
* lazy loading . Use them wisely and only on parts of the code that are called
* during specific requests or actions to avoid loading the lazy values all the time .
2024-01-11 11:32:58 -05:00
*
* @ since 7.0 . 0
2024-01-15 09:50:44 -05:00
* @ since 29.0 . 0 - Supporting types and lazy loading
2011-03-12 04:28:10 -05:00
*/
2015-05-11 06:37:14 -04:00
class AppConfig implements IAppConfig {
2024-01-11 11:32:58 -05:00
private const APP_MAX_LENGTH = 32 ;
private const KEY_MAX_LENGTH = 64 ;
2024-01-25 08:27:51 -05:00
private const ENCRYPTION_PREFIX = '$AppConfigEncryption$' ;
private const ENCRYPTION_PREFIX_LENGTH = 21 ; // strlen(self::ENCRYPTION_PREFIX)
2025-08-12 08:57:07 -04:00
private const LOCAL_CACHE_KEY = 'OC\\AppConfig' ;
private const LOCAL_CACHE_TTL = 3 ;
2024-01-11 11:32:58 -05:00
2025-08-12 08:57:07 -04:00
/** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
2024-01-11 11:32:58 -05:00
private array $fastCache = []; // cache for normal config keys
2025-08-12 08:57:07 -04:00
/** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
2024-01-11 11:32:58 -05:00
private array $lazyCache = []; // cache for lazy config keys
/** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
private array $valueTypes = []; // type for all config values
private bool $fastLoaded = false ;
private bool $lazyLoaded = false ;
2025-07-16 09:46:45 -04:00
/** @var array<string, array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
2024-12-06 05:35:00 -05:00
private array $configLexiconDetails = [];
2025-05-14 14:10:34 -04:00
private bool $ignoreLexiconAliases = false ;
2025-08-29 08:41:08 -04:00
private array $strictnessApplied = [];
2025-03-25 11:18:51 -04:00
/** @var ?array<string, string> */
private ? array $appVersionsCache = null ;
2025-08-12 08:57:07 -04:00
private ? ICache $localCache = null ;
2025-03-25 11:18:51 -04:00
2024-01-11 11:32:58 -05:00
public function __construct (
protected IDBConnection $connection ,
2025-06-11 14:18:44 -04:00
protected IConfig $config ,
2025-07-29 10:47:23 -04:00
private readonly ConfigManager $configManager ,
private readonly PresetManager $presetManager ,
2024-01-25 08:27:51 -05:00
protected LoggerInterface $logger ,
protected ICrypto $crypto ,
2025-08-12 16:20:15 -04:00
readonly CacheFactory $cacheFactory ,
2024-01-11 11:32:58 -05:00
) {
2025-08-12 08:57:07 -04:00
if ( $config -> getSystemValueBool ( 'cache_app_config' , true ) && $cacheFactory -> isLocalCacheAvailable ()) {
2025-08-12 16:20:15 -04:00
$cacheFactory -> withServerVersionPrefix ( function ( ICacheFactory $factory ) {
$this -> localCache = $factory -> createLocal ();
});
2025-08-12 08:57:07 -04:00
}
2013-03-02 11:17:11 -05:00
}
2014-02-07 08:03:39 -05:00
/**
2024-01-11 11:32:58 -05:00
* @ inheritDoc
*
2024-09-24 09:53:13 -04:00
* @ return list < string > list of app ids
2024-01-11 11:32:58 -05:00
* @ since 7.0 . 0
*/
public function getApps () : array {
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( lazy : true );
2024-01-11 11:32:58 -05:00
$apps = array_merge ( array_keys ( $this -> fastCache ), array_keys ( $this -> lazyCache ));
sort ( $apps );
return array_values ( array_unique ( $apps ));
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
2024-09-24 09:53:13 -04:00
* @ return list < string > list of stored config keys
2025-07-18 10:16:39 -04:00
* @ see searchKeys to not load lazy config keys
*
2024-01-11 11:32:58 -05:00
* @ since 29.0 . 0
2014-02-19 03:31:54 -05:00
*/
2024-01-11 11:32:58 -05:00
public function getKeys ( string $app ) : array {
$this -> assertParams ( $app );
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( $app , true );
2024-01-11 11:32:58 -05:00
$keys = array_merge ( array_keys ( $this -> fastCache [ $app ] ? ? []), array_keys ( $this -> lazyCache [ $app ] ? ? []));
sort ( $keys );
return array_values ( array_unique ( $keys ));
}
2015-09-02 12:56:17 -04:00
2025-07-18 10:16:39 -04:00
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $prefix returns only keys starting with this value
* @ param bool $lazy TRUE to search in lazy config keys
* @ return list < string > list of stored config keys
* @ since 32.0 . 0
*/
public function searchKeys ( string $app , string $prefix = '' , bool $lazy = false ) : array {
$this -> assertParams ( $app );
$this -> loadConfig ( $app , $lazy );
if ( $lazy ) {
$keys = array_keys ( $this -> lazyCache [ $app ] ? ? []);
} else {
$keys = array_keys ( $this -> fastCache [ $app ] ? ? []);
}
if ( $prefix !== '' ) {
$keys = array_filter ( $keys , static fn ( string $key ) : bool => str_starts_with ( $key , $prefix ));
}
sort ( $keys );
return array_values ( array_unique ( $keys ));
}
2024-01-11 11:32:58 -05:00
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param bool | null $lazy TRUE to search within lazy loaded config , NULL to search within all config
*
* @ return bool TRUE if key exists
* @ since 7.0 . 0
* @ since 29.0 . 0 Added the $lazy argument
*/
public function hasKey ( string $app , string $key , ? bool $lazy = false ) : bool {
$this -> assertParams ( $app , $key );
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( $app , $lazy ? ? true );
2025-05-14 14:10:34 -04:00
$this -> matchAndApplyLexiconDefinition ( $app , $key );
2024-01-11 11:32:58 -05:00
2025-08-12 08:57:07 -04:00
$hasLazy = isset ( $this -> lazyCache [ $app ][ $key ]);
$hasFast = isset ( $this -> fastCache [ $app ][ $key ]);
2024-01-11 11:32:58 -05:00
if ( $lazy === null ) {
2025-08-12 08:57:07 -04:00
return $hasLazy || $hasFast ;
} else {
return $lazy ? $hasLazy : $hasFast ;
2014-02-07 08:03:39 -05:00
}
2024-01-11 11:32:58 -05:00
}
/**
* @ param string $app id of the app
* @ param string $key config key
* @ param bool | null $lazy TRUE to search within lazy loaded config , NULL to search within all config
*
* @ return bool
* @ throws AppConfigUnknownKeyException if config key is not known
* @ since 29.0 . 0
*/
public function isSensitive ( string $app , string $key , ? bool $lazy = false ) : bool {
$this -> assertParams ( $app , $key );
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( null , $lazy ? ? true );
2025-05-14 14:10:34 -04:00
$this -> matchAndApplyLexiconDefinition ( $app , $key );
2024-01-11 11:32:58 -05:00
2024-01-16 08:48:27 -05:00
if ( ! isset ( $this -> valueTypes [ $app ][ $key ])) {
throw new AppConfigUnknownKeyException ( 'unknown config key' );
}
return $this -> isTyped ( self :: VALUE_SENSITIVE , $this -> valueTypes [ $app ][ $key ]);
2014-02-07 08:03:39 -05:00
}
2013-12-18 09:28:32 -05:00
2011-03-01 17:20:16 -05:00
/**
2024-01-11 11:32:58 -05:00
* @ inheritDoc
2014-06-01 08:04:17 -04:00
*
2024-01-11 11:32:58 -05:00
* @ param string $app if of the app
* @ param string $key config key
2011-03-12 04:28:10 -05:00
*
2024-01-11 11:32:58 -05:00
* @ return bool TRUE if config is lazy loaded
* @ throws AppConfigUnknownKeyException if config key is not known
* @ see IAppConfig for details about lazy loading
* @ since 29.0 . 0
2011-03-01 17:20:16 -05:00
*/
2024-01-11 11:32:58 -05:00
public function isLazy ( string $app , string $key ) : bool {
2025-05-14 14:10:34 -04:00
$this -> assertParams ( $app , $key );
$this -> matchAndApplyLexiconDefinition ( $app , $key );
2024-01-11 11:32:58 -05:00
// there is a huge probability the non-lazy config are already loaded
if ( $this -> hasKey ( $app , $key , false )) {
return false ;
}
// key not found, we search in the lazy config
if ( $this -> hasKey ( $app , $key , true )) {
return true ;
}
2011-04-08 10:54:12 -04:00
2024-01-11 11:32:58 -05:00
throw new AppConfigUnknownKeyException ( 'unknown config key' );
2011-03-01 17:20:16 -05:00
}
2024-01-11 11:32:58 -05:00
2011-03-01 17:20:16 -05:00
/**
2024-01-11 11:32:58 -05:00
* @ inheritDoc
2014-06-01 08:04:17 -04:00
*
2024-01-11 11:32:58 -05:00
* @ param string $app id of the app
2024-02-12 09:23:36 -05:00
* @ param string $prefix config keys prefix to search
2024-01-11 11:32:58 -05:00
* @ param bool $filtered TRUE to hide sensitive config values . Value are replaced by { @ see IConfig :: SENSITIVE_VALUE }
2011-03-12 04:28:10 -05:00
*
2024-04-04 14:40:31 -04:00
* @ return array < string , string | int | float | bool | array > [ configKey => configValue ]
2024-01-11 11:32:58 -05:00
* @ since 29.0 . 0
2011-03-01 17:20:16 -05:00
*/
2024-02-07 08:22:02 -05:00
public function getAllValues ( string $app , string $prefix = '' , bool $filtered = false ) : array {
$this -> assertParams ( $app , $prefix );
2024-01-11 11:32:58 -05:00
// if we want to filter values, we need to get sensitivity
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( $app , true );
2024-01-11 11:32:58 -05:00
// array_merge() will remove numeric keys (here config keys), so addition arrays instead
2024-04-03 21:20:35 -04:00
$values = $this -> formatAppValues ( $app , ( $this -> fastCache [ $app ] ? ? []) + ( $this -> lazyCache [ $app ] ? ? []));
2024-02-07 08:22:02 -05:00
$values = array_filter (
2024-04-03 21:20:35 -04:00
$values ,
2024-02-07 08:22:02 -05:00
function ( string $key ) use ( $prefix ) : bool {
return str_starts_with ( $key , $prefix ); // filter values based on $prefix
}, ARRAY_FILTER_USE_KEY
);
2024-01-11 11:32:58 -05:00
if ( ! $filtered ) {
return $values ;
}
/**
* Using the old ( deprecated ) list of sensitive values .
*/
foreach ( $this -> getSensitiveKeys ( $app ) as $sensitiveKeyExp ) {
$sensitiveKeys = preg_grep ( $sensitiveKeyExp , array_keys ( $values ));
foreach ( $sensitiveKeys as $sensitiveKey ) {
$this -> valueTypes [ $app ][ $sensitiveKey ] = ( $this -> valueTypes [ $app ][ $sensitiveKey ] ? ? 0 ) | self :: VALUE_SENSITIVE ;
}
}
2015-09-02 12:56:17 -04:00
2024-01-11 11:32:58 -05:00
$result = [];
foreach ( $values as $key => $value ) {
$result [ $key ] = $this -> isTyped ( self :: VALUE_SENSITIVE , $this -> valueTypes [ $app ][ $key ] ? ? 0 ) ? IConfig :: SENSITIVE_VALUE : $value ;
2015-09-02 12:56:17 -04:00
}
2024-01-11 11:32:58 -05:00
return $result ;
2015-09-02 12:56:17 -04:00
}
2024-01-11 11:32:58 -05:00
/**
* @ inheritDoc
*
* @ param string $key config key
* @ param bool $lazy search within lazy loaded config
2024-04-04 14:40:31 -04:00
* @ param int | null $typedAs enforce type for the returned values ({ @ see self :: VALUE_STRING } and others )
2024-01-11 11:32:58 -05:00
*
2024-04-04 14:40:31 -04:00
* @ return array < string , string | int | float | bool | array > [ appId => configValue ]
2024-01-11 11:32:58 -05:00
* @ since 29.0 . 0
*/
2024-04-04 14:40:31 -04:00
public function searchValues ( string $key , bool $lazy = false , ? int $typedAs = null ) : array {
2024-01-11 11:32:58 -05:00
$this -> assertParams ( '' , $key , true );
2024-10-20 15:04:14 -04:00
$this -> loadConfig ( null , $lazy );
2024-01-11 11:32:58 -05:00
/** @var array<array-key, array<array-key, mixed>> $cache */
if ( $lazy ) {
2024-01-15 09:50:44 -05:00
$cache = $this -> lazyCache ;
2024-01-11 11:32:58 -05:00
} else {
2024-01-15 09:50:44 -05:00
$cache = $this -> fastCache ;
2024-01-11 11:32:58 -05:00
}
2024-04-04 14:40:31 -04:00
$values = [];
2024-01-11 11:32:58 -05:00
foreach ( array_keys ( $cache ) as $app ) {
if ( isset ( $cache [ $app ][ $key ])) {
2024-04-04 14:40:31 -04:00
$values [ $app ] = $this -> convertTypedValue ( $cache [ $app ][ $key ], $typedAs ? ? $this -> getValueType (( string ) $app , $key , $lazy ));
2024-01-11 11:32:58 -05:00
}
}
return $values ;
2011-03-12 04:28:10 -05:00
}
2024-01-11 11:32:58 -05:00
2011-03-12 04:28:10 -05:00
/**
2024-01-15 09:50:44 -05:00
* Get the config value as string .
* If the value does not exist the given default will be returned .
2024-01-11 11:32:58 -05:00
*
2024-01-15 09:50:44 -05:00
* Set lazy to `null` to ignore it and get the value from either source .
2024-01-11 11:32:58 -05:00
*
2024-01-15 09:50:44 -05:00
* ** WARNING :** Method is internal and ** SHOULD ** not be used , as it is better to get the value with a type .
2024-01-11 11:32:58 -05:00
*
* @ param string $app id of the app
* @ param string $key config key
* @ param string $default config value
* @ param null | bool $lazy get config as lazy loaded or not . can be NULL
2014-06-01 08:04:17 -04:00
*
2012-09-22 20:39:11 -04:00
* @ return string the value or $default
2024-01-11 11:32:58 -05:00
* @ internal
* @ since 29.0 . 0
2024-01-15 09:50:44 -05:00
* @ see IAppConfig for explanation about lazy loading
2024-01-11 11:32:58 -05:00
* @ see getValueString ()
* @ see getValueInt ()
* @ see getValueFloat ()
* @ see getValueBool ()
2024-01-15 09:50:44 -05:00
* @ see getValueArray ()
2024-01-11 11:32:58 -05:00
*/
public function getValueMixed (
string $app ,
string $key ,
string $default = '' ,
2024-09-19 05:10:31 -04:00
? bool $lazy = false ,
2024-01-11 11:32:58 -05:00
) : string {
2024-01-16 08:35:05 -05:00
try {
$lazy = ( $lazy === null ) ? $this -> isLazy ( $app , $key ) : $lazy ;
2025-05-14 14:10:34 -04:00
} catch ( AppConfigUnknownKeyException ) {
2024-01-16 08:35:05 -05:00
return $default ;
}
2024-01-11 11:32:58 -05:00
return $this -> getTypedValue (
$app ,
$key ,
$default ,
2024-01-16 08:35:05 -05:00
$lazy ,
2024-01-11 11:32:58 -05:00
self :: VALUE_MIXED
);
}
/**
* @ inheritDoc
2011-03-12 04:28:10 -05:00
*
2024-01-11 11:32:58 -05:00
* @ param string $app id of the app
* @ param string $key config key
* @ param string $default default value
* @ param bool $lazy search within lazy loaded config
*
* @ return string stored config value or $default if not set in database
* @ throws InvalidArgumentException if one of the argument format is invalid
* @ throws AppConfigTypeConflictException in case of conflict with the value type set in database
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
2011-03-12 04:28:10 -05:00
*/
2024-01-11 11:32:58 -05:00
public function getValueString (
string $app ,
string $key ,
string $default = '' ,
2024-09-19 05:10:31 -04:00
bool $lazy = false ,
2024-01-11 11:32:58 -05:00
) : string {
return $this -> getTypedValue ( $app , $key , $default , $lazy , self :: VALUE_STRING );
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param int $default default value
* @ param bool $lazy search within lazy loaded config
*
* @ return int stored config value or $default if not set in database
* @ throws InvalidArgumentException if one of the argument format is invalid
* @ throws AppConfigTypeConflictException in case of conflict with the value type set in database
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
*/
public function getValueInt (
string $app ,
string $key ,
int $default = 0 ,
2024-09-19 05:10:31 -04:00
bool $lazy = false ,
2024-01-11 11:32:58 -05:00
) : int {
return ( int ) $this -> getTypedValue ( $app , $key , ( string ) $default , $lazy , self :: VALUE_INT );
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param float $default default value
* @ param bool $lazy search within lazy loaded config
*
* @ return float stored config value or $default if not set in database
* @ throws InvalidArgumentException if one of the argument format is invalid
* @ throws AppConfigTypeConflictException in case of conflict with the value type set in database
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
*/
public function getValueFloat ( string $app , string $key , float $default = 0 , bool $lazy = false ) : float {
return ( float ) $this -> getTypedValue ( $app , $key , ( string ) $default , $lazy , self :: VALUE_FLOAT );
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param bool $default default value
* @ param bool $lazy search within lazy loaded config
*
* @ return bool stored config value or $default if not set in database
* @ throws InvalidArgumentException if one of the argument format is invalid
* @ throws AppConfigTypeConflictException in case of conflict with the value type set in database
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
*/
public function getValueBool ( string $app , string $key , bool $default = false , bool $lazy = false ) : bool {
$b = strtolower ( $this -> getTypedValue ( $app , $key , $default ? 'true' : 'false' , $lazy , self :: VALUE_BOOL ));
return in_array ( $b , [ '1' , 'true' , 'yes' , 'on' ]);
}
2015-09-02 12:56:17 -04:00
2024-01-11 11:32:58 -05:00
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param array $default default value
* @ param bool $lazy search within lazy loaded config
*
* @ return array stored config value or $default if not set in database
* @ throws InvalidArgumentException if one of the argument format is invalid
* @ throws AppConfigTypeConflictException in case of conflict with the value type set in database
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
*/
public function getValueArray (
string $app ,
string $key ,
array $default = [],
2024-09-19 05:10:31 -04:00
bool $lazy = false ,
2024-01-11 11:32:58 -05:00
) : array {
try {
$defaultJson = json_encode ( $default , JSON_THROW_ON_ERROR );
$value = json_decode ( $this -> getTypedValue ( $app , $key , $defaultJson , $lazy , self :: VALUE_ARRAY ), true , flags : JSON_THROW_ON_ERROR );
return is_array ( $value ) ? $value : [];
} catch ( JsonException ) {
return [];
2011-04-08 10:54:12 -04:00
}
2024-01-11 11:32:58 -05:00
}
2015-09-02 12:56:17 -04:00
2024-01-11 11:32:58 -05:00
/**
* @ param string $app id of the app
* @ param string $key config key
* @ param string $default default value
* @ param bool $lazy search within lazy loaded config
* @ param int $type value type { @ see VALUE_STRING } { @ see VALUE_INT }{ @ see VALUE_FLOAT } { @ see VALUE_BOOL } { @ see VALUE_ARRAY }
*
* @ return string
* @ throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
* @ throws InvalidArgumentException
*/
private function getTypedValue (
string $app ,
string $key ,
string $default ,
bool $lazy ,
2024-09-19 05:10:31 -04:00
int $type ,
2024-01-11 11:32:58 -05:00
) : string {
$this -> assertParams ( $app , $key , valueType : $type );
2025-05-14 14:10:34 -04:00
$origKey = $key ;
2025-06-11 14:18:44 -04:00
$matched = $this -> matchAndApplyLexiconDefinition ( $app , $key , $lazy , $type , $default );
if ( $default === null ) {
// there is no logical reason for it to be null
throw new \Exception ( 'default cannot be null' );
}
// returns default if strictness of lexicon is set to WARNING (block and report)
if ( ! $matched ) {
return $default ;
2024-12-06 05:35:00 -05:00
}
2025-06-11 14:18:44 -04:00
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( $app , $lazy ? ? true );
2024-01-11 11:32:58 -05:00
/**
* We ignore check if mixed type is requested .
* If type of stored value is set as mixed , we don ' t filter .
* If type of stored value is defined , we compare with the one requested .
*/
$knownType = $this -> valueTypes [ $app ][ $key ] ? ? 0 ;
if ( ! $this -> isTyped ( self :: VALUE_MIXED , $type )
&& $knownType > 0
&& ! $this -> isTyped ( self :: VALUE_MIXED , $knownType )
&& ! $this -> isTyped ( $type , $knownType )) {
$this -> logger -> warning ( 'conflict with value type from database' , [ 'app' => $app , 'key' => $key , 'type' => $type , 'knownType' => $knownType ]);
throw new AppConfigTypeConflictException ( 'conflict with value type from database' );
}
2024-01-31 18:03:27 -05:00
/**
* - the pair $app / $key cannot exist in both array ,
2024-01-25 08:27:51 -05:00
* - we should still return an existing non - lazy value even if current method
2024-01-31 18:03:27 -05:00
* is called with $lazy is true
*
* This way , lazyCache will be empty until the load for lazy config value is requested .
*/
2024-01-25 08:27:51 -05:00
if ( isset ( $this -> lazyCache [ $app ][ $key ])) {
$value = $this -> lazyCache [ $app ][ $key ];
} elseif ( isset ( $this -> fastCache [ $app ][ $key ])) {
$value = $this -> fastCache [ $app ][ $key ];
} else {
return $default ;
}
$sensitive = $this -> isTyped ( self :: VALUE_SENSITIVE , $knownType );
if ( $sensitive && str_starts_with ( $value , self :: ENCRYPTION_PREFIX )) {
// Only decrypt values that are stored encrypted
$value = $this -> crypto -> decrypt ( substr ( $value , self :: ENCRYPTION_PREFIX_LENGTH ));
}
2025-05-14 14:10:34 -04:00
// in case the key was modified while running matchAndApplyLexiconDefinition() we are
// interested to check options in case a modification of the value is needed
2025-06-17 13:02:08 -04:00
// ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
2025-06-04 09:23:36 -04:00
if ( $origKey !== $key && $type === self :: VALUE_BOOL ) {
2025-07-29 10:47:23 -04:00
$value = ( $this -> configManager -> convertToBool ( $value , $this -> getLexiconEntry ( $app , $key ))) ? '1' : '0' ;
2025-05-14 14:10:34 -04:00
}
2024-01-25 08:27:51 -05:00
return $value ;
2011-03-01 17:20:16 -05:00
}
2012-08-29 02:38:33 -04:00
2011-10-02 08:30:51 -04:00
/**
2024-01-11 11:32:58 -05:00
* @ inheritDoc
2014-06-01 08:04:17 -04:00
*
2024-01-11 11:32:58 -05:00
* @ param string $app id of the app
* @ param string $key config key
*
* @ return int type of the value
* @ throws AppConfigUnknownKeyException if config key is not known
* @ since 29.0 . 0
* @ see VALUE_STRING
* @ see VALUE_INT
* @ see VALUE_FLOAT
* @ see VALUE_BOOL
* @ see VALUE_ARRAY
2011-10-02 08:30:51 -04:00
*/
2024-04-03 21:20:35 -04:00
public function getValueType ( string $app , string $key , ? bool $lazy = null ) : int {
2025-05-05 07:16:53 -04:00
$type = self :: VALUE_MIXED ;
$ignorable = $lazy ? ? false ;
$this -> matchAndApplyLexiconDefinition ( $app , $key , $ignorable , $type );
if ( $type !== self :: VALUE_MIXED ) {
// a modified $type means config key is set in Lexicon
return $type ;
}
2024-01-11 11:32:58 -05:00
$this -> assertParams ( $app , $key );
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( $app , $lazy ? ? true );
2015-09-02 12:56:17 -04:00
2024-01-15 09:50:44 -05:00
if ( ! isset ( $this -> valueTypes [ $app ][ $key ])) {
throw new AppConfigUnknownKeyException ( 'unknown config key' );
}
$type = $this -> valueTypes [ $app ][ $key ];
2024-01-11 11:32:58 -05:00
$type &= ~ self :: VALUE_SENSITIVE ;
return $type ;
2011-10-02 08:30:51 -04:00
}
2012-08-29 02:38:33 -04:00
2024-01-11 11:32:58 -05:00
2011-03-01 17:20:16 -05:00
/**
2024-01-11 11:32:58 -05:00
* Store a config key and its value in database as VALUE_MIXED
2014-06-01 08:04:17 -04:00
*
2024-01-11 11:32:58 -05:00
* ** WARNING :** Method is internal and ** MUST ** not be used as it is best to set a real value type
*
* @ param string $app id of the app
* @ param string $key config key
* @ param string $value config value
* @ param bool $lazy set config as lazy loaded
* @ param bool $sensitive if TRUE value will be hidden when listing config values .
*
* @ return bool TRUE if value was different , therefor updated in database
* @ throws AppConfigTypeConflictException if type from database is not VALUE_MIXED
* @ internal
* @ since 29.0 . 0
2024-01-15 09:50:44 -05:00
* @ see IAppConfig for explanation about lazy loading
2024-01-11 11:32:58 -05:00
* @ see setValueString ()
* @ see setValueInt ()
* @ see setValueFloat ()
* @ see setValueBool ()
* @ see setValueArray ()
2011-03-01 17:20:16 -05:00
*/
2024-01-11 11:32:58 -05:00
public function setValueMixed (
string $app ,
string $key ,
string $value ,
bool $lazy = false ,
2024-09-19 05:10:31 -04:00
bool $sensitive = false ,
2024-01-11 11:32:58 -05:00
) : bool {
return $this -> setTypedValue (
$app ,
$key ,
$value ,
$lazy ,
self :: VALUE_MIXED | ( $sensitive ? self :: VALUE_SENSITIVE : 0 )
);
}
2015-09-03 09:41:30 -04:00
2024-01-11 11:32:58 -05:00
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param string $value config value
* @ param bool $lazy set config as lazy loaded
* @ param bool $sensitive if TRUE value will be hidden when listing config values .
*
* @ return bool TRUE if value was different , therefor updated in database
* @ throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
*/
public function setValueString (
string $app ,
string $key ,
string $value ,
bool $lazy = false ,
2024-09-19 05:10:31 -04:00
bool $sensitive = false ,
2024-01-11 11:32:58 -05:00
) : bool {
return $this -> setTypedValue (
$app ,
$key ,
$value ,
$lazy ,
self :: VALUE_STRING | ( $sensitive ? self :: VALUE_SENSITIVE : 0 )
);
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param int $value config value
* @ param bool $lazy set config as lazy loaded
* @ param bool $sensitive if TRUE value will be hidden when listing config values .
*
* @ return bool TRUE if value was different , therefor updated in database
* @ throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
*/
public function setValueInt (
string $app ,
string $key ,
int $value ,
bool $lazy = false ,
2024-09-19 05:10:31 -04:00
bool $sensitive = false ,
2024-01-11 11:32:58 -05:00
) : bool {
2024-01-15 11:44:49 -05:00
if ( $value > 2000000000 ) {
$this -> logger -> debug ( 'You are trying to store an integer value around/above 2,147,483,647. This is a reminder that reaching this theoretical limit on 32 bits system will throw an exception.' );
2024-01-15 09:50:44 -05:00
}
2024-01-11 11:32:58 -05:00
return $this -> setTypedValue (
$app ,
$key ,
( string ) $value ,
$lazy ,
self :: VALUE_INT | ( $sensitive ? self :: VALUE_SENSITIVE : 0 )
);
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param float $value config value
* @ param bool $lazy set config as lazy loaded
* @ param bool $sensitive if TRUE value will be hidden when listing config values .
*
* @ return bool TRUE if value was different , therefor updated in database
* @ throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
*/
public function setValueFloat (
string $app ,
string $key ,
float $value ,
bool $lazy = false ,
2024-09-19 05:10:31 -04:00
bool $sensitive = false ,
2024-01-11 11:32:58 -05:00
) : bool {
return $this -> setTypedValue (
$app ,
$key ,
( string ) $value ,
$lazy ,
self :: VALUE_FLOAT | ( $sensitive ? self :: VALUE_SENSITIVE : 0 )
);
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param bool $value config value
* @ param bool $lazy set config as lazy loaded
*
* @ return bool TRUE if value was different , therefor updated in database
* @ throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
*/
public function setValueBool (
string $app ,
string $key ,
bool $value ,
2024-09-19 05:10:31 -04:00
bool $lazy = false ,
2024-01-11 11:32:58 -05:00
) : bool {
return $this -> setTypedValue (
$app ,
$key ,
( $value ) ? '1' : '0' ,
$lazy ,
self :: VALUE_BOOL
);
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
* @ param array $value config value
* @ param bool $lazy set config as lazy loaded
* @ param bool $sensitive if TRUE value will be hidden when listing config values .
*
* @ return bool TRUE if value was different , therefor updated in database
* @ throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
* @ throws JsonException
* @ since 29.0 . 0
* @ see IAppConfig for explanation about lazy loading
*/
public function setValueArray (
string $app ,
string $key ,
array $value ,
bool $lazy = false ,
2024-09-19 05:10:31 -04:00
bool $sensitive = false ,
2024-01-11 11:32:58 -05:00
) : bool {
try {
return $this -> setTypedValue (
$app ,
$key ,
json_encode ( $value , JSON_THROW_ON_ERROR ),
$lazy ,
self :: VALUE_ARRAY | ( $sensitive ? self :: VALUE_SENSITIVE : 0 )
);
} catch ( JsonException $e ) {
$this -> logger -> warning ( 'could not setValueArray' , [ 'app' => $app , 'key' => $key , 'exception' => $e ]);
throw $e ;
2014-06-01 08:04:17 -04:00
}
2024-01-11 11:32:58 -05:00
}
/**
* Store a config key and its value in database
*
* If config key is already known with the exact same config value and same sensitive / lazy status , the
* database is not updated . If config value was previously stored as sensitive , status will not be
* altered .
*
* @ param string $app id of the app
* @ param string $key config key
* @ param string $value config value
* @ param bool $lazy config set as lazy loaded
* @ param int $type value type { @ see VALUE_STRING } { @ see VALUE_INT } { @ see VALUE_FLOAT } { @ see VALUE_BOOL } { @ see VALUE_ARRAY }
*
* @ return bool TRUE if value was updated in database
* @ throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
* @ see IAppConfig for explanation about lazy loading
*/
private function setTypedValue (
string $app ,
string $key ,
string $value ,
bool $lazy ,
2024-09-19 05:10:31 -04:00
int $type ,
2024-01-11 11:32:58 -05:00
) : bool {
$this -> assertParams ( $app , $key );
2024-12-12 13:37:14 -05:00
if ( ! $this -> matchAndApplyLexiconDefinition ( $app , $key , $lazy , $type )) {
2024-12-06 05:35:00 -05:00
return false ; // returns false as database is not updated
}
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( null , $lazy ? ? true );
2015-09-02 12:56:17 -04:00
2024-01-11 11:32:58 -05:00
$sensitive = $this -> isTyped ( self :: VALUE_SENSITIVE , $type );
2024-02-12 14:33:44 -05:00
$inserted = $refreshCache = false ;
2024-01-11 11:32:58 -05:00
2024-04-03 13:56:19 -04:00
$origValue = $value ;
2024-03-06 16:42:20 -05:00
if ( $sensitive || ( $this -> hasKey ( $app , $key , $lazy ) && $this -> isSensitive ( $app , $key , $lazy ))) {
2024-01-25 08:27:51 -05:00
$value = self :: ENCRYPTION_PREFIX . $this -> crypto -> encrypt ( $value );
}
2024-02-12 14:33:44 -05:00
if ( $this -> hasKey ( $app , $key , $lazy )) {
/**
* no update if key is already known with set lazy status and value is
* not different , unless sensitivity is switched from false to true .
*/
2025-08-12 08:57:07 -04:00
if ( $origValue === $this -> getTypedValue ( $app , $key , $value , $lazy ? ? true , $type )
2024-02-12 14:33:44 -05:00
&& ( ! $sensitive || $this -> isSensitive ( $app , $key , $lazy ))) {
return false ;
2024-01-11 11:32:58 -05:00
}
2024-02-12 14:33:44 -05:00
} else {
/**
* if key is not known yet , we try to insert .
* It might fail if the key exists with a different lazy flag .
*/
try {
$insert = $this -> connection -> getQueryBuilder ();
$insert -> insert ( 'appconfig' )
2024-08-23 09:10:27 -04:00
-> setValue ( 'appid' , $insert -> createNamedParameter ( $app ))
-> setValue ( 'lazy' , $insert -> createNamedParameter (( $lazy ) ? 1 : 0 , IQueryBuilder :: PARAM_INT ))
-> setValue ( 'type' , $insert -> createNamedParameter ( $type , IQueryBuilder :: PARAM_INT ))
-> setValue ( 'configkey' , $insert -> createNamedParameter ( $key ))
-> setValue ( 'configvalue' , $insert -> createNamedParameter ( $value ));
2024-02-12 14:33:44 -05:00
$insert -> executeStatement ();
$inserted = true ;
} catch ( DBException $e ) {
if ( $e -> getReason () !== DBException :: REASON_UNIQUE_CONSTRAINT_VIOLATION ) {
throw $e ; // TODO: throw exception or just log and returns false !?
}
}
}
2024-01-11 11:32:58 -05:00
2024-02-12 14:33:44 -05:00
/**
* We cannot insert a new row , meaning we need to update an already existing one
*/
if ( ! $inserted ) {
2024-01-11 11:32:58 -05:00
$currType = $this -> valueTypes [ $app ][ $key ] ? ? 0 ;
if ( $currType === 0 ) { // this might happen when switching lazy loading status
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( lazy : true );
2024-01-11 11:32:58 -05:00
$currType = $this -> valueTypes [ $app ][ $key ] ? ? 0 ;
}
2024-01-15 09:50:44 -05:00
/**
* This should only happen during the upgrade process from 28 to 29.
* We only log a warning and set it to VALUE_MIXED .
*/
if ( $currType === 0 ) {
$this -> logger -> warning ( 'Value type is set to zero (0) in database. This is fine only during the upgrade process from 28 to 29.' , [ 'app' => $app , 'key' => $key ]);
$currType = self :: VALUE_MIXED ;
}
2024-01-11 11:32:58 -05:00
/**
* we only accept a different type from the one stored in database
* if the one stored in database is not - defined ( VALUE_MIXED )
2020-10-21 04:04:06 -04:00
*/
2025-06-30 09:04:05 -04:00
if ( ! $this -> isTyped ( self :: VALUE_MIXED , $currType )
&& ( $type | self :: VALUE_SENSITIVE ) !== ( $currType | self :: VALUE_SENSITIVE )) {
2024-01-11 11:32:58 -05:00
try {
$currType = $this -> convertTypeToString ( $currType );
2025-08-18 12:35:54 -04:00
$type = $this -> convertTypeToString ( $type );
2024-01-11 11:32:58 -05:00
} catch ( AppConfigIncorrectTypeException ) {
2024-01-15 09:50:44 -05:00
// can be ignored, this was just needed for a better exception message.
2024-01-11 11:32:58 -05:00
}
throw new AppConfigTypeConflictException ( 'conflict between new type (' . $type . ') and old type (' . $currType . ')' );
}
2020-10-21 04:04:06 -04:00
2024-01-11 11:32:58 -05:00
// we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
if ( $sensitive || $this -> isTyped ( self :: VALUE_SENSITIVE , $currType )) {
2024-03-06 16:42:20 -05:00
$type |= self :: VALUE_SENSITIVE ;
2020-10-21 04:04:06 -04:00
}
2024-01-11 11:32:58 -05:00
2025-08-25 08:06:29 -04:00
try {
if ( $lazy !== $this -> isLazy ( $app , $key )) {
$refreshCache = true ;
}
} catch ( AppConfigUnknownKeyException ) {
// pass
2024-01-11 11:32:58 -05:00
}
$update = $this -> connection -> getQueryBuilder ();
$update -> update ( 'appconfig' )
2024-08-23 09:10:27 -04:00
-> set ( 'configvalue' , $update -> createNamedParameter ( $value ))
-> set ( 'lazy' , $update -> createNamedParameter (( $lazy ) ? 1 : 0 , IQueryBuilder :: PARAM_INT ))
-> set ( 'type' , $update -> createNamedParameter ( $type , IQueryBuilder :: PARAM_INT ))
-> where ( $update -> expr () -> eq ( 'appid' , $update -> createNamedParameter ( $app )))
-> andWhere ( $update -> expr () -> eq ( 'configkey' , $update -> createNamedParameter ( $key )));
2024-01-11 11:32:58 -05:00
$update -> executeStatement ();
2015-09-11 06:11:40 -04:00
}
2024-01-11 11:32:58 -05:00
if ( $refreshCache ) {
$this -> clearCache ();
return true ;
}
2015-09-02 12:56:17 -04:00
2024-01-11 11:32:58 -05:00
// update local cache
if ( $lazy ) {
2024-04-29 11:15:45 -04:00
$this -> lazyCache [ $app ][ $key ] = $value ;
2024-01-11 11:32:58 -05:00
} else {
2024-04-29 11:15:45 -04:00
$this -> fastCache [ $app ][ $key ] = $value ;
2024-01-11 11:32:58 -05:00
}
$this -> valueTypes [ $app ][ $key ] = $type ;
2025-08-12 08:57:07 -04:00
$this -> clearLocalCache ();
2015-09-02 12:56:17 -04:00
2024-01-11 11:32:58 -05:00
return true ;
2011-03-12 04:28:10 -05:00
}
/**
2024-01-11 11:32:58 -05:00
* Change the type of config value .
2014-06-01 08:04:17 -04:00
*
2024-01-11 11:32:58 -05:00
* ** WARNING :** Method is internal and ** MUST ** not be used as it may break things .
*
* @ param string $app id of the app
* @ param string $key config key
* @ param int $type value type { @ see VALUE_STRING } { @ see VALUE_INT } { @ see VALUE_FLOAT } { @ see VALUE_BOOL } { @ see VALUE_ARRAY }
*
* @ return bool TRUE if database update were necessary
* @ throws AppConfigUnknownKeyException if $key is now known in database
* @ throws AppConfigIncorrectTypeException if $type is not valid
* @ internal
* @ since 29.0 . 0
2011-03-12 04:28:10 -05:00
*/
2024-01-11 11:32:58 -05:00
public function updateType ( string $app , string $key , int $type = self :: VALUE_MIXED ) : bool {
$this -> assertParams ( $app , $key );
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( lazy : true );
2025-05-14 14:10:34 -04:00
$this -> matchAndApplyLexiconDefinition ( $app , $key );
$this -> isLazy ( $app , $key ); // confirm key exists
2024-01-11 11:32:58 -05:00
// type can only be one type
if ( ! in_array ( $type , [ self :: VALUE_MIXED , self :: VALUE_STRING , self :: VALUE_INT , self :: VALUE_FLOAT , self :: VALUE_BOOL , self :: VALUE_ARRAY ])) {
throw new AppConfigIncorrectTypeException ( 'Unknown value type' );
}
$currType = $this -> valueTypes [ $app ][ $key ];
if (( $type | self :: VALUE_SENSITIVE ) === ( $currType | self :: VALUE_SENSITIVE )) {
return false ;
}
// we complete with sensitive flag if the stored value is set as sensitive
if ( $this -> isTyped ( self :: VALUE_SENSITIVE , $currType )) {
$type = $type | self :: VALUE_SENSITIVE ;
}
2015-09-02 12:56:17 -04:00
2024-01-11 11:32:58 -05:00
$update = $this -> connection -> getQueryBuilder ();
$update -> update ( 'appconfig' )
2024-08-23 09:10:27 -04:00
-> set ( 'type' , $update -> createNamedParameter ( $type , IQueryBuilder :: PARAM_INT ))
-> where ( $update -> expr () -> eq ( 'appid' , $update -> createNamedParameter ( $app )))
-> andWhere ( $update -> expr () -> eq ( 'configkey' , $update -> createNamedParameter ( $key )));
2024-01-11 11:32:58 -05:00
$update -> executeStatement ();
$this -> valueTypes [ $app ][ $key ] = $type ;
return true ;
2011-03-12 04:28:10 -05:00
}
2024-01-11 11:32:58 -05:00
2011-03-12 04:28:10 -05:00
/**
2024-01-11 11:32:58 -05:00
* @ inheritDoc
2014-06-01 08:04:17 -04:00
*
2024-01-11 11:32:58 -05:00
* @ param string $app id of the app
* @ param string $key config key
* @ param bool $sensitive TRUE to set as sensitive , FALSE to unset
2011-03-12 04:28:10 -05:00
*
2024-02-01 15:48:40 -05:00
* @ return bool TRUE if entry was found in database and an update was necessary
2024-01-11 11:32:58 -05:00
* @ since 29.0 . 0
2011-03-12 04:28:10 -05:00
*/
2024-01-11 11:32:58 -05:00
public function updateSensitive ( string $app , string $key , bool $sensitive ) : bool {
$this -> assertParams ( $app , $key );
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( lazy : true );
2025-05-14 14:10:34 -04:00
$this -> matchAndApplyLexiconDefinition ( $app , $key );
2024-01-11 11:32:58 -05:00
2024-02-01 15:48:40 -05:00
try {
if ( $sensitive === $this -> isSensitive ( $app , $key , null )) {
return false ;
}
} catch ( AppConfigUnknownKeyException $e ) {
2024-01-11 11:32:58 -05:00
return false ;
}
2015-09-02 12:56:17 -04:00
2024-01-25 08:27:51 -05:00
$lazy = $this -> isLazy ( $app , $key );
if ( $lazy ) {
$cache = $this -> lazyCache ;
} else {
$cache = $this -> fastCache ;
}
if ( ! isset ( $cache [ $app ][ $key ])) {
throw new AppConfigUnknownKeyException ( 'unknown config key' );
}
2024-01-11 11:32:58 -05:00
/**
* type returned by getValueType () is already cleaned from sensitive flag
* we just need to update it based on $sensitive and store it in database
*/
$type = $this -> getValueType ( $app , $key );
2024-01-25 08:27:51 -05:00
$value = $cache [ $app ][ $key ];
2024-01-11 11:32:58 -05:00
if ( $sensitive ) {
2024-01-25 08:27:51 -05:00
$type |= self :: VALUE_SENSITIVE ;
$value = self :: ENCRYPTION_PREFIX . $this -> crypto -> encrypt ( $value );
} else {
$value = $this -> crypto -> decrypt ( substr ( $value , self :: ENCRYPTION_PREFIX_LENGTH ));
2024-01-11 11:32:58 -05:00
}
$update = $this -> connection -> getQueryBuilder ();
$update -> update ( 'appconfig' )
2024-08-23 09:10:27 -04:00
-> set ( 'type' , $update -> createNamedParameter ( $type , IQueryBuilder :: PARAM_INT ))
-> set ( 'configvalue' , $update -> createNamedParameter ( $value ))
-> where ( $update -> expr () -> eq ( 'appid' , $update -> createNamedParameter ( $app )))
-> andWhere ( $update -> expr () -> eq ( 'configkey' , $update -> createNamedParameter ( $key )));
2024-01-11 11:32:58 -05:00
$update -> executeStatement ();
2015-09-02 12:56:17 -04:00
2024-01-11 11:32:58 -05:00
$this -> valueTypes [ $app ][ $key ] = $type ;
return true ;
2011-03-01 17:20:16 -05:00
}
2012-08-29 02:38:33 -04:00
2012-04-14 11:53:02 -04:00
/**
2024-01-11 11:32:58 -05:00
* @ inheritDoc
2013-12-18 09:10:12 -05:00
*
2024-01-11 11:32:58 -05:00
* @ param string $app id of the app
* @ param string $key config key
* @ param bool $lazy TRUE to set as lazy loaded , FALSE to unset
*
2024-02-01 15:48:40 -05:00
* @ return bool TRUE if entry was found in database and an update was necessary
2024-01-11 11:32:58 -05:00
* @ since 29.0 . 0
2012-04-14 11:53:02 -04:00
*/
2024-01-11 11:32:58 -05:00
public function updateLazy ( string $app , string $key , bool $lazy ) : bool {
$this -> assertParams ( $app , $key );
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( lazy : true );
2025-05-14 14:10:34 -04:00
$this -> matchAndApplyLexiconDefinition ( $app , $key );
2024-01-11 11:32:58 -05:00
2024-02-01 15:48:40 -05:00
try {
if ( $lazy === $this -> isLazy ( $app , $key )) {
return false ;
}
} catch ( AppConfigUnknownKeyException $e ) {
2012-04-14 11:53:02 -04:00
return false ;
}
2013-03-02 11:17:11 -05:00
2024-01-11 11:32:58 -05:00
$update = $this -> connection -> getQueryBuilder ();
$update -> update ( 'appconfig' )
2024-08-23 09:10:27 -04:00
-> set ( 'lazy' , $update -> createNamedParameter ( $lazy ? 1 : 0 , IQueryBuilder :: PARAM_INT ))
-> where ( $update -> expr () -> eq ( 'appid' , $update -> createNamedParameter ( $app )))
-> andWhere ( $update -> expr () -> eq ( 'configkey' , $update -> createNamedParameter ( $key )));
2024-01-11 11:32:58 -05:00
$update -> executeStatement ();
// At this point, it is a lot safer to clean cache
$this -> clearCache ();
return true ;
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
*
* @ return array
* @ throws AppConfigUnknownKeyException if config key is not known in database
* @ since 29.0 . 0
*/
public function getDetails ( string $app , string $key ) : array {
$this -> assertParams ( $app , $key );
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( lazy : true );
2025-05-14 14:10:34 -04:00
$this -> matchAndApplyLexiconDefinition ( $app , $key );
2024-01-11 11:32:58 -05:00
$lazy = $this -> isLazy ( $app , $key );
if ( $lazy ) {
2024-01-15 09:50:44 -05:00
$cache = $this -> lazyCache ;
2013-12-18 09:10:12 -05:00
} else {
2024-01-15 09:50:44 -05:00
$cache = $this -> fastCache ;
2024-01-11 11:32:58 -05:00
}
2013-03-02 11:17:11 -05:00
2024-01-11 11:32:58 -05:00
$type = $this -> getValueType ( $app , $key );
try {
$typeString = $this -> convertTypeToString ( $type );
} catch ( AppConfigIncorrectTypeException $e ) {
$this -> logger -> warning ( 'type stored in database is not correct' , [ 'exception' => $e , 'type' => $type ]);
$typeString = ( string ) $type ;
2014-06-01 08:14:30 -04:00
}
2024-01-11 11:32:58 -05:00
2024-01-16 08:48:27 -05:00
if ( ! isset ( $cache [ $app ][ $key ])) {
throw new AppConfigUnknownKeyException ( 'unknown config key' );
}
2024-04-17 13:27:21 -04:00
$value = $cache [ $app ][ $key ];
$sensitive = $this -> isSensitive ( $app , $key , null );
if ( $sensitive && str_starts_with ( $value , self :: ENCRYPTION_PREFIX )) {
$value = $this -> crypto -> decrypt ( substr ( $value , self :: ENCRYPTION_PREFIX_LENGTH ));
}
2024-01-11 11:32:58 -05:00
return [
'app' => $app ,
'key' => $key ,
2024-04-17 13:27:21 -04:00
'value' => $value ,
2024-01-11 11:32:58 -05:00
'type' => $type ,
2024-01-15 09:50:44 -05:00
'lazy' => $lazy ,
2024-01-11 11:32:58 -05:00
'typeString' => $typeString ,
2024-04-17 13:27:21 -04:00
'sensitive' => $sensitive
2024-01-11 11:32:58 -05:00
];
2012-04-14 11:53:02 -04:00
}
2015-09-02 12:56:17 -04:00
2025-07-23 12:22:51 -04:00
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
*
* @ return array { app : string , key : string , lazy ? : bool , valueType ? : ValueType , valueTypeName ? : string , sensitive ? : bool , default ? : string , definition ? : string , note ? : string }
* @ since 32.0 . 0
*/
public function getKeyDetails ( string $app , string $key ) : array {
$this -> assertParams ( $app , $key );
try {
$details = $this -> getDetails ( $app , $key );
2025-07-29 10:47:23 -04:00
} catch ( AppConfigUnknownKeyException ) {
2025-07-23 12:22:51 -04:00
$details = [
'app' => $app ,
'key' => $key
];
}
/** @var Entry $lexiconEntry */
try {
$lazy = false ;
$this -> matchAndApplyLexiconDefinition ( $app , $key , $lazy , lexiconEntry : $lexiconEntry );
} catch ( AppConfigTypeConflictException | AppConfigUnknownKeyException ) {
// can be ignored
}
if ( $lexiconEntry !== null ) {
$details = array_merge ( $details , [
'lazy' => $lexiconEntry -> isLazy (),
'valueType' => $lexiconEntry -> getValueType (),
'valueTypeName' => $lexiconEntry -> getValueType () -> name ,
'sensitive' => $lexiconEntry -> isFlagged ( self :: FLAG_SENSITIVE ),
2025-07-29 10:47:23 -04:00
'default' => $lexiconEntry -> getDefault ( $this -> presetManager -> getLexiconPreset ()),
2025-07-23 12:22:51 -04:00
'definition' => $lexiconEntry -> getDefinition (),
'note' => $lexiconEntry -> getNote (),
]);
}
2025-07-30 07:36:49 -04:00
return array_filter ( $details , static fn ( $v ) : bool => ( $v !== null ));
2025-07-23 12:22:51 -04:00
}
2017-01-11 05:42:36 -05:00
/**
2024-01-11 11:32:58 -05:00
* @ param string $type
*
* @ return int
* @ throws AppConfigIncorrectTypeException
* @ since 29.0 . 0
*/
public function convertTypeToInt ( string $type ) : int {
return match ( strtolower ( $type )) {
'mixed' => IAppConfig :: VALUE_MIXED ,
'string' => IAppConfig :: VALUE_STRING ,
'integer' => IAppConfig :: VALUE_INT ,
'float' => IAppConfig :: VALUE_FLOAT ,
'boolean' => IAppConfig :: VALUE_BOOL ,
'array' => IAppConfig :: VALUE_ARRAY ,
default => throw new AppConfigIncorrectTypeException ( 'Unknown type ' . $type )
};
}
/**
* @ param int $type
*
* @ return string
* @ throws AppConfigIncorrectTypeException
* @ since 29.0 . 0
*/
public function convertTypeToString ( int $type ) : string {
$type &= ~ self :: VALUE_SENSITIVE ;
return match ( $type ) {
IAppConfig :: VALUE_MIXED => 'mixed' ,
IAppConfig :: VALUE_STRING => 'string' ,
IAppConfig :: VALUE_INT => 'integer' ,
IAppConfig :: VALUE_FLOAT => 'float' ,
IAppConfig :: VALUE_BOOL => 'boolean' ,
IAppConfig :: VALUE_ARRAY => 'array' ,
default => throw new AppConfigIncorrectTypeException ( 'Unknown numeric type ' . $type )
};
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
* @ param string $key config key
*
* @ since 29.0 . 0
*/
public function deleteKey ( string $app , string $key ) : void {
$this -> assertParams ( $app , $key );
2025-05-14 14:10:34 -04:00
$this -> matchAndApplyLexiconDefinition ( $app , $key );
2024-01-11 11:32:58 -05:00
$qb = $this -> connection -> getQueryBuilder ();
$qb -> delete ( 'appconfig' )
2024-08-23 09:10:27 -04:00
-> where ( $qb -> expr () -> eq ( 'appid' , $qb -> createNamedParameter ( $app )))
-> andWhere ( $qb -> expr () -> eq ( 'configkey' , $qb -> createNamedParameter ( $key )));
2024-01-11 11:32:58 -05:00
$qb -> executeStatement ();
unset ( $this -> lazyCache [ $app ][ $key ]);
unset ( $this -> fastCache [ $app ][ $key ]);
2024-12-04 08:25:49 -05:00
unset ( $this -> valueTypes [ $app ][ $key ]);
2025-08-12 08:57:07 -04:00
$this -> clearLocalCache ();
2024-01-11 11:32:58 -05:00
}
/**
* @ inheritDoc
*
* @ param string $app id of the app
*
* @ since 29.0 . 0
*/
public function deleteApp ( string $app ) : void {
$this -> assertParams ( $app );
$qb = $this -> connection -> getQueryBuilder ();
$qb -> delete ( 'appconfig' )
2024-08-23 09:10:27 -04:00
-> where ( $qb -> expr () -> eq ( 'appid' , $qb -> createNamedParameter ( $app )));
2024-01-11 11:32:58 -05:00
$qb -> executeStatement ();
$this -> clearCache ();
}
/**
* @ inheritDoc
*
* @ param bool $reload set to TRUE to refill cache instantly after clearing it
*
2025-08-19 11:32:37 -04:00
* @ internal
2024-01-11 11:32:58 -05:00
* @ since 29.0 . 0
*/
public function clearCache ( bool $reload = false ) : void {
$this -> lazyLoaded = $this -> fastLoaded = false ;
2025-06-11 14:18:44 -04:00
$this -> lazyCache = $this -> fastCache = $this -> valueTypes = $this -> configLexiconDetails = [];
2025-08-12 08:57:07 -04:00
$this -> localCache ? -> remove ( self :: LOCAL_CACHE_KEY );
2024-01-11 11:32:58 -05:00
if ( ! $reload ) {
return ;
}
2025-08-12 08:57:07 -04:00
$this -> loadConfig ( lazy : true );
2024-01-11 11:32:58 -05:00
}
/**
* For debug purpose .
* Returns the cached data .
2017-01-11 05:42:36 -05:00
*
* @ return array
2024-01-11 11:32:58 -05:00
* @ since 29.0 . 0
* @ internal
2017-01-11 05:42:36 -05:00
*/
2024-01-11 11:32:58 -05:00
public function statusCache () : array {
return [
'fastLoaded' => $this -> fastLoaded ,
'fastCache' => $this -> fastCache ,
'lazyLoaded' => $this -> lazyLoaded ,
'lazyCache' => $this -> lazyCache ,
];
}
2017-01-11 05:42:36 -05:00
2024-01-11 11:32:58 -05:00
/**
* @ param int $needle bitflag to search
* @ param int $type known value
*
* @ return bool TRUE if bitflag $needle is set in $type
*/
private function isTyped ( int $needle , int $type ) : bool {
return (( $needle & $type ) !== 0 );
}
/**
2024-01-15 09:50:44 -05:00
* Confirm the string set for app and key fit the database description
2024-01-11 11:32:58 -05:00
*
* @ param string $app assert $app fit in database
* @ param string $configKey assert config key fit in database
* @ param bool $allowEmptyApp $app can be empty string
* @ param int $valueType assert value type is only one type
*
* @ throws InvalidArgumentException
*/
private function assertParams ( string $app = '' , string $configKey = '' , bool $allowEmptyApp = false , int $valueType = - 1 ) : void {
if ( ! $allowEmptyApp && $app === '' ) {
throw new InvalidArgumentException ( 'app cannot be an empty string' );
}
if ( strlen ( $app ) > self :: APP_MAX_LENGTH ) {
throw new InvalidArgumentException (
'Value (' . $app . ') for app is too long (' . self :: APP_MAX_LENGTH . ')'
);
}
if ( strlen ( $configKey ) > self :: KEY_MAX_LENGTH ) {
throw new InvalidArgumentException ( 'Value (' . $configKey . ') for key is too long (' . self :: KEY_MAX_LENGTH . ')' );
}
if ( $valueType > - 1 ) {
$valueType &= ~ self :: VALUE_SENSITIVE ;
if ( ! in_array ( $valueType , [ self :: VALUE_MIXED , self :: VALUE_STRING , self :: VALUE_INT , self :: VALUE_FLOAT , self :: VALUE_BOOL , self :: VALUE_ARRAY ])) {
throw new InvalidArgumentException ( 'Unknown value type' );
2017-01-11 05:42:36 -05:00
}
}
2024-01-11 11:32:58 -05:00
}
2017-01-11 05:42:36 -05:00
2015-09-02 12:56:17 -04:00
/**
2024-01-11 11:32:58 -05:00
* Load normal config or config set as lazy loaded
*
2025-08-12 08:57:07 -04:00
* @ param bool $lazy set to TRUE to also load config values set as lazy loaded
2015-09-02 12:56:17 -04:00
*/
2025-08-12 08:57:07 -04:00
private function loadConfig ( ? string $app = null , bool $lazy = false ) : void {
2024-01-11 11:32:58 -05:00
if ( $this -> isLoaded ( $lazy )) {
2017-01-11 05:42:36 -05:00
return ;
}
2015-09-02 12:56:17 -04:00
2024-10-20 15:04:14 -04:00
// if lazy is null or true, we debug log
2025-08-12 08:57:07 -04:00
if ( $lazy === true && $app !== null ) {
2024-10-20 15:04:14 -04:00
$exception = new \RuntimeException ( 'The loading of lazy AppConfig values have been triggered by app "' . $app . '"' );
$this -> logger -> debug ( $exception -> getMessage (), [ 'exception' => $exception , 'app' => $app ]);
2024-02-07 04:35:00 -05:00
}
2025-08-12 08:57:07 -04:00
$loadLazyOnly = $lazy && $this -> isLoaded ();
2024-01-11 11:32:58 -05:00
2025-08-12 08:57:07 -04:00
/** @var array<mixed> */
$cacheContent = $this -> localCache ? -> get ( self :: LOCAL_CACHE_KEY ) ? ? [];
$includesLazyValues = ! empty ( $cacheContent ) && ! empty ( $cacheContent [ 'lazyCache' ]);
if ( ! empty ( $cacheContent ) && ( ! $lazy || $includesLazyValues )) {
$this -> valueTypes = $cacheContent [ 'valueTypes' ];
$this -> fastCache = $cacheContent [ 'fastCache' ];
$this -> fastLoaded = ! empty ( $this -> fastCache );
if ( $includesLazyValues ) {
$this -> lazyCache = $cacheContent [ 'lazyCache' ];
$this -> lazyLoaded = ! empty ( $this -> lazyCache );
}
return ;
}
2024-01-24 16:00:18 -05:00
2025-08-12 08:57:07 -04:00
// Otherwise no cache available and we need to fetch from database
$qb = $this -> connection -> getQueryBuilder ();
$qb -> from ( 'appconfig' )
-> select ( 'appid' , 'configkey' , 'configvalue' , 'type' );
if ( $lazy === false ) {
$qb -> where ( $qb -> expr () -> eq ( 'lazy' , $qb -> createNamedParameter ( 0 , IQueryBuilder :: PARAM_INT )));
2025-03-04 06:28:14 -05:00
} else {
2025-08-12 08:57:07 -04:00
if ( $loadLazyOnly ) {
$qb -> where ( $qb -> expr () -> eq ( 'lazy' , $qb -> createNamedParameter ( 1 , IQueryBuilder :: PARAM_INT )));
}
2025-03-04 06:28:14 -05:00
$qb -> addSelect ( 'lazy' );
2024-01-11 11:32:58 -05:00
}
2015-09-02 12:56:17 -04:00
2025-03-04 06:28:14 -05:00
$result = $qb -> executeQuery ();
2016-06-10 09:20:22 -04:00
$rows = $result -> fetchAll ();
foreach ( $rows as $row ) {
2024-01-24 16:00:18 -05:00
// most of the time, 'lazy' is not in the select because its value is already known
2025-08-12 08:57:07 -04:00
if ( $lazy && (( int ) $row [ 'lazy' ]) === 1 ) {
2024-04-29 11:15:45 -04:00
$this -> lazyCache [ $row [ 'appid' ]][ $row [ 'configkey' ]] = $row [ 'configvalue' ] ? ? '' ;
2024-01-11 11:32:58 -05:00
} else {
2024-04-29 11:15:45 -04:00
$this -> fastCache [ $row [ 'appid' ]][ $row [ 'configkey' ]] = $row [ 'configvalue' ] ? ? '' ;
2015-09-03 09:41:30 -04:00
}
2024-01-11 11:32:58 -05:00
$this -> valueTypes [ $row [ 'appid' ]][ $row [ 'configkey' ]] = ( int )( $row [ 'type' ] ? ? 0 );
2015-09-02 12:56:17 -04:00
}
2024-01-11 11:32:58 -05:00
2025-08-12 08:57:07 -04:00
$result -> closeCursor ();
$this -> localCache ? -> set (
self :: LOCAL_CACHE_KEY ,
[
'fastCache' => $this -> fastCache ,
'lazyCache' => $this -> lazyCache ,
'valueTypes' => $this -> valueTypes ,
],
self :: LOCAL_CACHE_TTL ,
);
2015-09-02 12:56:17 -04:00
2025-08-12 08:57:07 -04:00
$this -> fastLoaded = true ;
$this -> lazyLoaded = $lazy ;
2015-09-02 12:56:17 -04:00
}
2022-11-15 07:08:20 -05:00
2024-01-11 11:32:58 -05:00
/**
2025-08-12 08:57:07 -04:00
* @ param bool $lazy - If set to true then also check if lazy values are loaded
2024-01-11 11:32:58 -05:00
*/
2025-08-12 08:57:07 -04:00
private function isLoaded ( bool $lazy = false ) : bool {
return $this -> fastLoaded && ( ! $lazy || $this -> lazyLoaded );
2024-01-11 11:32:58 -05:00
}
/**
* Gets the config value
*
* @ param string $app app
* @ param string $key key
2025-08-12 16:21:38 -04:00
* @ param string $default - Default value if the key does not exist
2024-01-11 11:32:58 -05:00
*
2025-08-12 16:21:38 -04:00
* @ return string the value or $default
2024-09-18 17:51:06 -04:00
* @ deprecated 29.0 . 0 use getValue * ()
2024-01-11 11:32:58 -05:00
*
* This function gets a value from the appconfig table . If the key does
* not exist the default value will be returned
*/
2025-08-12 16:21:38 -04:00
public function getValue ( $app , $key , $default = '' ) {
2024-10-20 15:04:14 -04:00
$this -> loadConfig ( $app );
2025-05-14 14:10:34 -04:00
$this -> matchAndApplyLexiconDefinition ( $app , $key );
2024-01-11 11:32:58 -05:00
return $this -> fastCache [ $app ][ $key ] ? ? $default ;
}
/**
* Sets a value . If the key did not exist before it will be created .
*
* @ param string $app app
* @ param string $key key
* @ param string | float | int $value value
*
* @ return bool True if the value was inserted or updated , false if the value was the same
* @ throws AppConfigTypeConflictException
* @ throws AppConfigUnknownKeyException
2024-09-18 17:51:06 -04:00
* @ deprecated 29.0 . 0
2024-01-11 11:32:58 -05:00
*/
public function setValue ( $app , $key , $value ) {
2024-01-24 16:00:18 -05:00
/**
* TODO : would it be overkill , or decently improve performance , to catch
* call to this method with $key = 'enabled' and 'hide' config value related
* to $app when the app is disabled ( by modifying entry in database : lazy = lazy + 2 )
* or enabled ( lazy = lazy - 2 )
*
* this solution would remove the loading of config values from disabled app
2025-08-12 08:57:07 -04:00
* unless calling the method .
2024-01-24 16:00:18 -05:00
*/
2024-01-11 11:32:58 -05:00
return $this -> setTypedValue ( $app , $key , ( string ) $value , false , self :: VALUE_MIXED );
}
/**
* get multiple values , either the app or key can be used as wildcard by setting it to false
*
* @ param string | false $app
* @ param string | false $key
*
* @ return array | false
2024-02-12 09:23:36 -05:00
* @ deprecated 29.0 . 0 use { @ see getAllValues ()}
2024-01-11 11:32:58 -05:00
*/
public function getValues ( $app , $key ) {
if (( $app !== false ) === ( $key !== false )) {
return false ;
}
$key = ( $key === false ) ? '' : $key ;
if ( ! $app ) {
2024-04-04 14:40:31 -04:00
return $this -> searchValues ( $key , false , self :: VALUE_MIXED );
2024-01-11 11:32:58 -05:00
} else {
return $this -> getAllValues ( $app , $key );
}
}
/**
* get all values of the app or and filters out sensitive data
*
* @ param string $app
*
* @ return array
2024-02-12 09:23:36 -05:00
* @ deprecated 29.0 . 0 use { @ see getAllValues ()}
2024-01-11 11:32:58 -05:00
*/
public function getFilteredValues ( $app ) {
return $this -> getAllValues ( $app , filtered : true );
}
2024-04-03 21:20:35 -04:00
/**
* ** Warning :** avoid default NULL value for $lazy as this will
* load all lazy values from the database
*
* @ param string $app
2024-04-04 14:40:31 -04:00
* @ param array < string , string > $values [ 'key' => 'value' ]
2024-04-03 21:20:35 -04:00
* @ param bool | null $lazy
*
2024-04-04 14:40:31 -04:00
* @ return array < string , string | int | float | bool | array >
2024-04-03 21:20:35 -04:00
*/
private function formatAppValues ( string $app , array $values , ? bool $lazy = null ) : array {
2024-09-05 15:23:38 -04:00
foreach ( $values as $key => $value ) {
2024-04-03 21:20:35 -04:00
try {
$type = $this -> getValueType ( $app , $key , $lazy );
2025-05-14 14:10:34 -04:00
} catch ( AppConfigUnknownKeyException ) {
2024-04-03 21:20:35 -04:00
continue ;
}
2024-04-04 14:40:31 -04:00
$values [ $key ] = $this -> convertTypedValue ( $value , $type );
2024-04-03 21:20:35 -04:00
}
return $values ;
}
2024-04-04 14:40:31 -04:00
/**
* convert string value to the expected type
*
* @ param string $value
* @ param int $type
*
* @ return string | int | float | bool | array
*/
private function convertTypedValue ( string $value , int $type ) : string | int | float | bool | array {
switch ( $type ) {
case self :: VALUE_INT :
return ( int ) $value ;
case self :: VALUE_FLOAT :
return ( float ) $value ;
case self :: VALUE_BOOL :
return in_array ( strtolower ( $value ), [ '1' , 'true' , 'yes' , 'on' ]);
case self :: VALUE_ARRAY :
try {
return json_decode ( $value , true , flags : JSON_THROW_ON_ERROR );
} catch ( JsonException $e ) {
// ignoreable
}
break ;
}
return $value ;
}
2024-01-11 11:32:58 -05:00
/**
* @ param string $app
*
* @ return string []
2024-09-18 17:51:06 -04:00
* @ deprecated 29.0 . 0 data sensitivity should be set when calling setValue * ()
2024-01-11 11:32:58 -05:00
*/
private function getSensitiveKeys ( string $app ) : array {
$sensitiveValues = [
'circles' => [
'/^key_pairs$/' ,
'/^local_gskey$/' ,
],
2024-10-28 09:56:19 -04:00
'call_summary_bot' => [
'/^secret_(.*)$/' ,
],
2024-01-11 11:32:58 -05:00
'external' => [
'/^sites$/' ,
2024-10-14 04:42:06 -04:00
'/^jwt_token_privkey_(.*)$/' ,
2024-01-11 11:32:58 -05:00
],
2024-10-28 09:56:19 -04:00
'globalsiteselector' => [
'/^gss\.jwt\.key$/' ,
],
2025-04-25 02:43:36 -04:00
'gpgmailer' => [
'/^GpgServerKey$/' ,
],
2024-01-11 11:32:58 -05:00
'integration_discourse' => [
'/^private_key$/' ,
'/^public_key$/' ,
],
'integration_dropbox' => [
'/^client_id$/' ,
'/^client_secret$/' ,
],
'integration_github' => [
'/^client_id$/' ,
'/^client_secret$/' ,
],
'integration_gitlab' => [
'/^client_id$/' ,
'/^client_secret$/' ,
'/^oauth_instance_url$/' ,
],
'integration_google' => [
'/^client_id$/' ,
'/^client_secret$/' ,
],
'integration_jira' => [
'/^client_id$/' ,
'/^client_secret$/' ,
'/^forced_instance_url$/' ,
],
'integration_onedrive' => [
'/^client_id$/' ,
'/^client_secret$/' ,
],
'integration_openproject' => [
'/^client_id$/' ,
'/^client_secret$/' ,
'/^oauth_instance_url$/' ,
],
'integration_reddit' => [
'/^client_id$/' ,
'/^client_secret$/' ,
],
'integration_suitecrm' => [
'/^client_id$/' ,
'/^client_secret$/' ,
'/^oauth_instance_url$/' ,
],
'integration_twitter' => [
'/^consumer_key$/' ,
'/^consumer_secret$/' ,
'/^followed_user$/' ,
],
'integration_zammad' => [
'/^client_id$/' ,
'/^client_secret$/' ,
'/^oauth_instance_url$/' ,
],
2025-04-25 02:43:36 -04:00
'maps' => [
'/^mapboxAPIKEY$/' ,
],
2024-01-11 11:32:58 -05:00
'notify_push' => [
'/^cookie$/' ,
],
2024-10-07 04:58:03 -04:00
'onlyoffice' => [
'/^jwt_secret$/' ,
],
'passwords' => [
'/^SSEv1ServerKey$/' ,
],
2024-01-11 11:32:58 -05:00
'serverinfo' => [
'/^token$/' ,
],
'spreed' => [
'/^bridge_bot_password$/' ,
'/^hosted-signaling-server-(.*)$/' ,
'/^recording_servers$/' ,
'/^signaling_servers$/' ,
'/^signaling_ticket_secret$/' ,
'/^signaling_token_privkey_(.*)$/' ,
'/^signaling_token_pubkey_(.*)$/' ,
'/^sip_bridge_dialin_info$/' ,
'/^sip_bridge_shared_secret$/' ,
'/^stun_servers$/' ,
'/^turn_servers$/' ,
'/^turn_server_secret$/' ,
],
'support' => [
'/^last_response$/' ,
'/^potential_subscription_key$/' ,
'/^subscription_key$/' ,
],
'theming' => [
'/^imprintUrl$/' ,
'/^privacyUrl$/' ,
'/^slogan$/' ,
'/^url$/' ,
],
2024-10-07 04:58:03 -04:00
'twofactor_gateway' => [
'/^.*token$/' ,
],
2025-04-25 02:43:36 -04:00
'user_ldap' => [
'/^(s..)?ldap_agent_password$/' ,
],
2024-01-11 11:32:58 -05:00
'user_saml' => [
'/^idp-x509cert$/' ,
],
2024-10-28 09:56:19 -04:00
'whiteboard' => [
'/^jwt_secret_key$/' ,
],
2024-01-11 11:32:58 -05:00
];
return $sensitiveValues [ $app ] ? ? [];
}
2022-11-15 07:08:20 -05:00
/**
* Clear all the cached app config values
* New cache will be generated next time a config value is retrieved
2024-01-11 11:32:58 -05:00
*
2024-09-18 17:51:06 -04:00
* @ deprecated 29.0 . 0 use { @ see clearCache ()}
2022-11-15 07:08:20 -05:00
*/
public function clearCachedConfig () : void {
2024-01-11 11:32:58 -05:00
$this -> clearCache ();
2022-11-15 07:08:20 -05:00
}
2024-12-06 05:35:00 -05:00
/**
2025-05-14 14:10:34 -04:00
* Match and apply current use of config values with defined lexicon .
* Set $lazy to NULL only if only interested into checking that $key is alias .
2024-12-06 05:35:00 -05:00
*
* @ throws AppConfigUnknownKeyException
* @ throws AppConfigTypeConflictException
* @ return bool TRUE if everything is fine compared to lexicon or lexicon does not exist
*/
2024-12-12 13:37:14 -05:00
private function matchAndApplyLexiconDefinition (
2024-12-06 05:35:00 -05:00
string $app ,
2025-05-14 14:10:34 -04:00
string & $key ,
? bool & $lazy = null ,
int & $type = self :: VALUE_MIXED ,
2025-06-11 14:18:44 -04:00
? string & $default = null ,
2025-07-23 12:22:51 -04:00
? Entry & $lexiconEntry = null ,
2024-12-06 05:35:00 -05:00
) : bool {
if ( in_array ( $key ,
[
'enabled' ,
'installed_version' ,
'types' ,
])) {
return true ; // we don't break stuff for this list of config keys.
}
$configDetails = $this -> getConfigDetailsFromLexicon ( $app );
2025-05-14 14:10:34 -04:00
if ( array_key_exists ( $key , $configDetails [ 'aliases' ]) && ! $this -> ignoreLexiconAliases ) {
// in case '$rename' is set in ConfigLexiconEntry, we use the new config key
$key = $configDetails [ 'aliases' ][ $key ];
}
2024-12-06 05:35:00 -05:00
if ( ! array_key_exists ( $key , $configDetails [ 'entries' ])) {
2025-08-29 08:41:08 -04:00
return $this -> applyLexiconStrictness ( $configDetails [ 'strictness' ], $app . '/' . $key );
2025-05-14 14:10:34 -04:00
}
// if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon
if ( $lazy === null ) {
return true ;
2024-12-06 05:35:00 -05:00
}
2025-07-23 12:22:51 -04:00
/** @var Entry $lexiconEntry */
$lexiconEntry = $configDetails [ 'entries' ][ $key ];
2024-12-06 05:35:00 -05:00
$type &= ~ self :: VALUE_SENSITIVE ;
2025-07-23 12:22:51 -04:00
$appConfigValueType = $lexiconEntry -> getValueType () -> toAppConfigFlag ();
2024-12-06 05:35:00 -05:00
if ( $type === self :: VALUE_MIXED ) {
$type = $appConfigValueType ; // we overwrite if value was requested as mixed
} elseif ( $appConfigValueType !== $type ) {
throw new AppConfigTypeConflictException ( 'The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon' );
}
2025-07-23 12:22:51 -04:00
$lazy = $lexiconEntry -> isLazy ();
2025-06-11 14:18:44 -04:00
// only look for default if needed, default from Lexicon got priority
if ( $default !== null ) {
2025-07-29 10:47:23 -04:00
$default = $lexiconEntry -> getDefault ( $this -> presetManager -> getLexiconPreset ()) ? ? $default ;
2025-06-11 14:18:44 -04:00
}
2025-07-23 12:22:51 -04:00
if ( $lexiconEntry -> isFlagged ( self :: FLAG_SENSITIVE )) {
2024-12-06 05:35:00 -05:00
$type |= self :: VALUE_SENSITIVE ;
}
2025-07-23 12:22:51 -04:00
if ( $lexiconEntry -> isDeprecated ()) {
2024-12-06 05:35:00 -05:00
$this -> logger -> notice ( 'App config key ' . $app . '/' . $key . ' is set as deprecated.' );
}
return true ;
}
/**
* manage ConfigLexicon behavior based on strictness set in IConfigLexicon
*
2025-07-16 09:46:45 -04:00
* @ param Strictness | null $strictness
2024-12-06 05:35:00 -05:00
* @ param string $line
*
* @ return bool TRUE if conflict can be fully ignored , FALSE if action should be not performed
* @ throws AppConfigUnknownKeyException if strictness implies exception
2025-08-12 08:57:07 -04:00
* @ see \OCP\Config\Lexicon\ILexicon :: getStrictness ()
2024-12-06 05:35:00 -05:00
*/
2025-08-29 08:41:08 -04:00
private function applyLexiconStrictness ( ? Strictness $strictness , string $configAppKey ) : bool {
2024-12-06 05:35:00 -05:00
if ( $strictness === null ) {
return true ;
}
2025-08-29 08:41:08 -04:00
$line = 'The app config key ' . $configAppKey . ' is not defined in the config lexicon' ;
2024-12-06 05:35:00 -05:00
switch ( $strictness ) {
2025-07-16 09:46:45 -04:00
case Strictness :: IGNORE :
2024-12-06 05:35:00 -05:00
return true ;
2025-07-16 09:46:45 -04:00
case Strictness :: NOTICE :
2025-08-29 08:41:08 -04:00
if ( ! in_array ( $configAppKey , $this -> strictnessApplied , true )) {
$this -> strictnessApplied [] = $configAppKey ;
$this -> logger -> notice ( $line );
}
2024-12-06 05:35:00 -05:00
return true ;
2025-07-16 09:46:45 -04:00
case Strictness :: WARNING :
2025-08-29 08:41:08 -04:00
if ( ! in_array ( $configAppKey , $this -> strictnessApplied , true )) {
$this -> strictnessApplied [] = $configAppKey ;
$this -> logger -> warning ( $line );
}
2024-12-06 05:35:00 -05:00
return false ;
}
throw new AppConfigUnknownKeyException ( $line );
}
/**
* extract details from registered $appId ' s config lexicon
*
* @ param string $appId
2025-05-14 14:10:34 -04:00
* @ internal
2024-12-06 05:35:00 -05:00
*
2025-07-16 09:46:45 -04:00
* @ return array { entries : array < string , Entry > , aliases : array < string , string > , strictness : Strictness }
2024-12-06 05:35:00 -05:00
*/
2025-05-14 14:10:34 -04:00
public function getConfigDetailsFromLexicon ( string $appId ) : array {
2024-12-06 05:35:00 -05:00
if ( ! array_key_exists ( $appId , $this -> configLexiconDetails )) {
2025-05-14 14:10:34 -04:00
$entries = $aliases = [];
2024-12-06 05:35:00 -05:00
$bootstrapCoordinator = \OCP\Server :: get ( Coordinator :: class );
$configLexicon = $bootstrapCoordinator -> getRegistrationContext () ? -> getConfigLexicon ( $appId );
foreach ( $configLexicon ? -> getAppConfigs () ? ? [] as $configEntry ) {
$entries [ $configEntry -> getKey ()] = $configEntry ;
2025-08-12 08:57:07 -04:00
$newName = $configEntry -> getRename ();
if ( $newName !== null ) {
$aliases [ $newName ] = $configEntry -> getKey ();
2025-05-14 14:10:34 -04:00
}
2024-12-06 05:35:00 -05:00
}
$this -> configLexiconDetails [ $appId ] = [
'entries' => $entries ,
2025-05-14 14:10:34 -04:00
'aliases' => $aliases ,
2025-07-16 09:46:45 -04:00
'strictness' => $configLexicon ? -> getStrictness () ? ? Strictness :: IGNORE
2024-12-06 05:35:00 -05:00
];
}
return $this -> configLexiconDetails [ $appId ];
}
2025-03-25 11:18:51 -04:00
2025-08-13 14:31:15 -04:00
/**
* get Lexicon Entry using appId and config key entry
*
* @ return Entry | null NULL if entry does not exist in app ' s Lexicon
* @ internal
*/
public function getLexiconEntry ( string $appId , string $key ) : ? Entry {
2025-05-14 14:10:34 -04:00
return $this -> getConfigDetailsFromLexicon ( $appId )[ 'entries' ][ $key ] ? ? null ;
}
/**
* if set to TRUE , ignore aliases defined in Config Lexicon during the use of the methods of this class
*
* @ internal
*/
public function ignoreLexiconAliases ( bool $ignore ) : void {
$this -> ignoreLexiconAliases = $ignore ;
}
2025-03-25 11:18:51 -04:00
/**
* Returns the installed versions of all apps
*
* @ return array < string , string >
*/
2025-06-02 11:45:35 -04:00
public function getAppInstalledVersions ( bool $onlyEnabled = false ) : array {
2025-03-25 11:18:51 -04:00
if ( $this -> appVersionsCache === null ) {
/** @var array<string, string> */
$this -> appVersionsCache = $this -> searchValues ( 'installed_version' , false , IAppConfig :: VALUE_STRING );
}
2025-06-02 11:45:35 -04:00
if ( $onlyEnabled ) {
return array_filter (
$this -> appVersionsCache ,
fn ( string $app ) : bool => $this -> getValueString ( $app , 'enabled' , 'no' ) !== 'no' ,
ARRAY_FILTER_USE_KEY
);
}
2025-03-25 11:18:51 -04:00
return $this -> appVersionsCache ;
}
2025-08-12 08:57:07 -04:00
private function clearLocalCache () : void {
$this -> localCache ? -> remove ( self :: LOCAL_CACHE_KEY );
}
2011-03-01 17:20:16 -05:00
}