2011-03-01 22:20:16 +00:00
< ? php
2024-01-11 16:32:58 +00:00
declare ( strict_types = 1 );
2011-03-12 09:28:10 +00:00
/**
2017-01-17 10:28:47 +00:00
* @ copyright Copyright ( c ) 2017 , Joas Schilling < coding @ schilljs . com >
2016-07-21 15:07:57 +00:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2016-05-26 17:56:05 +00:00
* @ author Arthur Schiwon < blizzz @ arthur - schiwon . de >
2015-03-26 10:44:34 +00:00
* @ author Bart Visscher < bartv @ thisnet . nl >
2020-04-29 09:57:22 +00:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2015-03-26 10:44:34 +00:00
* @ author Jakob Sack < mail @ jakobsack . de >
2016-07-21 15:07:57 +00:00
* @ author Joas Schilling < coding @ schilljs . com >
2015-03-26 10:44:34 +00:00
* @ author Jörn Friedrich Dreyer < jfd @ butonic . de >
2024-01-11 16:32:58 +00:00
* @ author Maxence Lange < maxence @ artificial - owl . com >
2019-12-03 18:57:53 +00:00
* @ author michaelletzgus < michaelletzgus @ users . noreply . github . com >
2015-03-26 10:44:34 +00:00
* @ author Morris Jobke < hey @ morrisjobke . de >
2016-07-21 16:13:36 +00:00
* @ author Robin Appelman < robin @ icewind . nl >
2016-01-12 14:02:16 +00:00
* @ author Robin McCorkell < robin @ mccorkell . me . uk >
2020-12-16 13:54:15 +00:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2015-03-26 10:44:34 +00:00
*
* @ license AGPL - 3.0
*
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
2011-03-12 09:28:10 +00:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
2015-03-26 10:44:34 +00:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
2015-02-26 10:37:37 +00:00
*
2015-03-26 10:44:34 +00:00
* You should have received a copy of the GNU Affero General Public License , version 3 ,
2019-12-03 18:57:53 +00:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
2011-03-12 09:28:10 +00:00
*
2015-02-26 10:37:37 +00:00
*/
2024-01-11 16:32:58 +00:00
2013-03-02 16:17:11 +00:00
namespace OC ;
2024-01-11 16:32:58 +00:00
use InvalidArgumentException ;
use JsonException ;
use OCP\DB\Exception as DBException ;
2023-02-20 13:46:00 +00:00
use OCP\DB\QueryBuilder\IQueryBuilder ;
2024-01-11 16:32:58 +00:00
use OCP\Exceptions\AppConfigIncorrectTypeException ;
use OCP\Exceptions\AppConfigTypeConflictException ;
use OCP\Exceptions\AppConfigUnknownKeyException ;
2015-05-11 10:37:14 +00:00
use OCP\IAppConfig ;
2017-01-11 10:42:36 +00:00
use OCP\IConfig ;
2024-01-11 16:32:58 +00:00
use OCP\IDBConnection ;
2024-01-25 13:27:51 +00:00
use OCP\Security\ICrypto ;
2024-01-11 16:32:58 +00:00
use Psr\Log\LoggerInterface ;
2013-03-02 16:17:11 +00:00
2011-03-12 09:28:10 +00:00
/**
* This class provides an easy way for apps to store config values in the
* database .
2024-01-11 16:32:58 +00:00
*
2024-01-15 14:50:44 +00:00
* ** Note :** since 29.0 . 0 , it supports ** lazy loading **
2024-01-11 16:32:58 +00:00
*
2024-01-15 14:50:44 +00: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 16:32:58 +00:00
*
2024-01-15 14:50:44 +00:00
* Once a value that is lazy is requested , all lazy values will be loaded .
2024-01-11 16:32:58 +00:00
*
2024-01-15 14:50:44 +00: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 16:32:58 +00:00
*
* @ since 7.0 . 0
2024-01-15 14:50:44 +00:00
* @ since 29.0 . 0 - Supporting types and lazy loading
2011-03-12 09:28:10 +00:00
*/
2015-05-11 10:37:14 +00:00
class AppConfig implements IAppConfig {
2024-01-11 16:32:58 +00:00
private const APP_MAX_LENGTH = 32 ;
private const KEY_MAX_LENGTH = 64 ;
2024-01-25 13:27:51 +00:00
private const ENCRYPTION_PREFIX = '$AppConfigEncryption$' ;
private const ENCRYPTION_PREFIX_LENGTH = 21 ; // strlen(self::ENCRYPTION_PREFIX)
2024-01-11 16:32:58 +00:00
/** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
private array $fastCache = []; // cache for normal config keys
/** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
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 ;
/**
* $migrationCompleted is only needed to manage the previous structure
* of the database during the upgrading process to nc29 .
2024-01-24 21:00:18 +00:00
*
* only when upgrading from a version prior 28.0 . 2
*
2024-01-11 16:32:58 +00:00
* @ TODO : remove this value in Nextcloud 30 +
*/
private bool $migrationCompleted = true ;
public function __construct (
protected IDBConnection $connection ,
2024-01-25 13:27:51 +00:00
protected LoggerInterface $logger ,
protected ICrypto $crypto ,
2024-01-11 16:32:58 +00:00
) {
2013-03-02 16:17:11 +00:00
}
2014-02-07 13:03:39 +00:00
/**
2024-01-11 16:32:58 +00:00
* @ inheritDoc
*
* @ return string [] list of app ids
* @ since 7.0 . 0
*/
public function getApps () : array {
$this -> loadConfigAll ();
$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
*
* @ return string [] list of stored config keys
* @ since 29.0 . 0
2014-02-19 08:31:54 +00:00
*/
2024-01-11 16:32:58 +00:00
public function getKeys ( string $app ) : array {
$this -> assertParams ( $app );
$this -> loadConfigAll ();
$keys = array_merge ( array_keys ( $this -> fastCache [ $app ] ? ? []), array_keys ( $this -> lazyCache [ $app ] ? ? []));
sort ( $keys );
return array_values ( array_unique ( $keys ));
}
2015-09-02 16:56:17 +00:00
2024-01-11 16:32:58 +00: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 );
$this -> loadConfig ( $lazy );
if ( $lazy === null ) {
$appCache = $this -> getAllValues ( $app );
return isset ( $appCache [ $key ]);
}
if ( $lazy ) {
2024-01-15 14:50:44 +00:00
return isset ( $this -> lazyCache [ $app ][ $key ]);
2014-02-07 13:03:39 +00:00
}
2015-09-02 16:56:17 +00:00
2024-01-15 14:50:44 +00:00
return isset ( $this -> fastCache [ $app ][ $key ]);
2024-01-11 16:32:58 +00: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 );
$this -> loadConfig ( $lazy );
2024-01-16 13:48:27 +00: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 13:03:39 +00:00
}
2013-12-18 14:28:32 +00:00
2011-03-01 22:20:16 +00:00
/**
2024-01-11 16:32:58 +00:00
* @ inheritDoc
2014-06-01 12:04:17 +00:00
*
2024-01-11 16:32:58 +00:00
* @ param string $app if of the app
* @ param string $key config key
2011-03-12 09:28:10 +00:00
*
2024-01-11 16:32:58 +00: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 22:20:16 +00:00
*/
2024-01-11 16:32:58 +00:00
public function isLazy ( string $app , string $key ) : bool {
// 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 14:54:12 +00:00
2024-01-11 16:32:58 +00:00
throw new AppConfigUnknownKeyException ( 'unknown config key' );
2011-03-01 22:20:16 +00:00
}
2024-01-11 16:32:58 +00:00
2011-03-01 22:20:16 +00:00
/**
2024-01-11 16:32:58 +00:00
* @ inheritDoc
2014-06-01 12:04:17 +00:00
*
2024-01-11 16:32:58 +00:00
* @ param string $app id of the app
2024-02-12 14:23:36 +00:00
* @ param string $prefix config keys prefix to search
2024-01-11 16:32:58 +00:00
* @ param bool $filtered TRUE to hide sensitive config values . Value are replaced by { @ see IConfig :: SENSITIVE_VALUE }
2011-03-12 09:28:10 +00:00
*
2024-04-04 18:40:31 +00:00
* @ return array < string , string | int | float | bool | array > [ configKey => configValue ]
2024-01-11 16:32:58 +00:00
* @ since 29.0 . 0
2011-03-01 22:20:16 +00:00
*/
2024-02-07 13:22:02 +00:00
public function getAllValues ( string $app , string $prefix = '' , bool $filtered = false ) : array {
$this -> assertParams ( $app , $prefix );
2024-01-11 16:32:58 +00:00
// if we want to filter values, we need to get sensitivity
$this -> loadConfigAll ();
// array_merge() will remove numeric keys (here config keys), so addition arrays instead
2024-04-04 01:20:35 +00:00
$values = $this -> formatAppValues ( $app , ( $this -> fastCache [ $app ] ? ? []) + ( $this -> lazyCache [ $app ] ? ? []));
2024-02-07 13:22:02 +00:00
$values = array_filter (
2024-04-04 01:20:35 +00:00
$values ,
2024-02-07 13:22:02 +00: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 16:32:58 +00: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 16:56:17 +00:00
2024-01-11 16:32:58 +00: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 16:56:17 +00:00
}
2024-01-11 16:32:58 +00:00
return $result ;
2015-09-02 16:56:17 +00:00
}
2024-01-11 16:32:58 +00:00
/**
* @ inheritDoc
*
* @ param string $key config key
* @ param bool $lazy search within lazy loaded config
2024-04-04 18:40:31 +00:00
* @ param int | null $typedAs enforce type for the returned values ({ @ see self :: VALUE_STRING } and others )
2024-01-11 16:32:58 +00:00
*
2024-04-04 18:40:31 +00:00
* @ return array < string , string | int | float | bool | array > [ appId => configValue ]
2024-01-11 16:32:58 +00:00
* @ since 29.0 . 0
*/
2024-04-04 18:40:31 +00:00
public function searchValues ( string $key , bool $lazy = false , ? int $typedAs = null ) : array {
2024-01-11 16:32:58 +00:00
$this -> assertParams ( '' , $key , true );
$this -> loadConfig ( $lazy );
/** @var array<array-key, array<array-key, mixed>> $cache */
if ( $lazy ) {
2024-01-15 14:50:44 +00:00
$cache = $this -> lazyCache ;
2024-01-11 16:32:58 +00:00
} else {
2024-01-15 14:50:44 +00:00
$cache = $this -> fastCache ;
2024-01-11 16:32:58 +00:00
}
2024-04-04 18:40:31 +00:00
$values = [];
2024-01-11 16:32:58 +00:00
foreach ( array_keys ( $cache ) as $app ) {
if ( isset ( $cache [ $app ][ $key ])) {
2024-04-04 18:40:31 +00:00
$values [ $app ] = $this -> convertTypedValue ( $cache [ $app ][ $key ], $typedAs ? ? $this -> getValueType (( string ) $app , $key , $lazy ));
2024-01-11 16:32:58 +00:00
}
}
return $values ;
2011-03-12 09:28:10 +00:00
}
2024-01-11 16:32:58 +00:00
2011-03-12 09:28:10 +00:00
/**
2024-01-15 14:50:44 +00:00
* Get the config value as string .
* If the value does not exist the given default will be returned .
2024-01-11 16:32:58 +00:00
*
2024-01-15 14:50:44 +00:00
* Set lazy to `null` to ignore it and get the value from either source .
2024-01-11 16:32:58 +00:00
*
2024-01-15 14:50:44 +00:00
* ** WARNING :** Method is internal and ** SHOULD ** not be used , as it is better to get the value with a type .
2024-01-11 16:32:58 +00: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 12:04:17 +00:00
*
2012-09-23 00:39:11 +00:00
* @ return string the value or $default
2024-01-11 16:32:58 +00:00
* @ internal
* @ since 29.0 . 0
2024-01-15 14:50:44 +00:00
* @ see IAppConfig for explanation about lazy loading
2024-01-11 16:32:58 +00:00
* @ see getValueString ()
* @ see getValueInt ()
* @ see getValueFloat ()
* @ see getValueBool ()
2024-01-15 14:50:44 +00:00
* @ see getValueArray ()
2024-01-11 16:32:58 +00:00
*/
public function getValueMixed (
string $app ,
string $key ,
string $default = '' ,
? bool $lazy = false
) : string {
2024-01-16 13:35:05 +00:00
try {
$lazy = ( $lazy === null ) ? $this -> isLazy ( $app , $key ) : $lazy ;
} catch ( AppConfigUnknownKeyException $e ) {
return $default ;
}
2024-01-11 16:32:58 +00:00
return $this -> getTypedValue (
$app ,
$key ,
$default ,
2024-01-16 13:35:05 +00:00
$lazy ,
2024-01-11 16:32:58 +00:00
self :: VALUE_MIXED
);
}
/**
* @ inheritDoc
2011-03-12 09:28:10 +00:00
*
2024-01-11 16:32:58 +00: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 09:28:10 +00:00
*/
2024-01-11 16:32:58 +00:00
public function getValueString (
string $app ,
string $key ,
string $default = '' ,
bool $lazy = false
) : 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 ,
bool $lazy = false
) : 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 16:56:17 +00:00
2024-01-11 16:32:58 +00: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 = [],
bool $lazy = false
) : 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 14:54:12 +00:00
}
2024-01-11 16:32:58 +00:00
}
2015-09-02 16:56:17 +00:00
2024-01-11 16:32:58 +00: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 ,
int $type
) : string {
$this -> assertParams ( $app , $key , valueType : $type );
$this -> loadConfig ( $lazy );
/**
* 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 23:03:27 +00:00
/**
* - the pair $app / $key cannot exist in both array ,
2024-01-25 13:27:51 +00:00
* - we should still return an existing non - lazy value even if current method
2024-01-31 23:03:27 +00: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 13:27:51 +00: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 ));
}
return $value ;
2011-03-01 22:20:16 +00:00
}
2012-08-29 06:38:33 +00:00
2011-10-02 12:30:51 +00:00
/**
2024-01-11 16:32:58 +00:00
* @ inheritDoc
2014-06-01 12:04:17 +00:00
*
2024-01-11 16:32:58 +00: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 12:30:51 +00:00
*/
2024-04-04 01:20:35 +00:00
public function getValueType ( string $app , string $key , ? bool $lazy = null ) : int {
2024-01-11 16:32:58 +00:00
$this -> assertParams ( $app , $key );
2024-04-04 01:20:35 +00:00
$this -> loadConfig ( $lazy );
2015-09-02 16:56:17 +00:00
2024-01-15 14:50:44 +00:00
if ( ! isset ( $this -> valueTypes [ $app ][ $key ])) {
throw new AppConfigUnknownKeyException ( 'unknown config key' );
}
$type = $this -> valueTypes [ $app ][ $key ];
2024-01-11 16:32:58 +00:00
$type &= ~ self :: VALUE_SENSITIVE ;
return $type ;
2011-10-02 12:30:51 +00:00
}
2012-08-29 06:38:33 +00:00
2024-01-11 16:32:58 +00:00
2011-03-01 22:20:16 +00:00
/**
2024-01-11 16:32:58 +00:00
* Store a config key and its value in database as VALUE_MIXED
2014-06-01 12:04:17 +00:00
*
2024-01-11 16:32:58 +00: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 14:50:44 +00:00
* @ see IAppConfig for explanation about lazy loading
2024-01-11 16:32:58 +00:00
* @ see setValueString ()
* @ see setValueInt ()
* @ see setValueFloat ()
* @ see setValueBool ()
* @ see setValueArray ()
2011-03-01 22:20:16 +00:00
*/
2024-01-11 16:32:58 +00:00
public function setValueMixed (
string $app ,
string $key ,
string $value ,
bool $lazy = false ,
bool $sensitive = false
) : bool {
return $this -> setTypedValue (
$app ,
$key ,
$value ,
$lazy ,
self :: VALUE_MIXED | ( $sensitive ? self :: VALUE_SENSITIVE : 0 )
);
}
2015-09-03 13:41:30 +00:00
2024-01-11 16:32:58 +00: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 ,
bool $sensitive = false
) : 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 ,
bool $sensitive = false
) : bool {
2024-01-15 16:44:49 +00: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 14:50:44 +00:00
}
2024-01-11 16:32:58 +00: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 ,
bool $sensitive = false
) : 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 ,
bool $lazy = false
) : 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 ,
bool $sensitive = false
) : 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 12:04:17 +00:00
}
2024-01-11 16:32:58 +00: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 ,
int $type
) : bool {
$this -> assertParams ( $app , $key );
$this -> loadConfig ( $lazy );
2015-09-02 16:56:17 +00:00
2024-01-11 16:32:58 +00:00
$sensitive = $this -> isTyped ( self :: VALUE_SENSITIVE , $type );
2024-02-12 19:33:44 +00:00
$inserted = $refreshCache = false ;
2024-01-11 16:32:58 +00:00
2024-04-03 17:56:19 +00:00
$origValue = $value ;
2024-03-06 21:42:20 +00:00
if ( $sensitive || ( $this -> hasKey ( $app , $key , $lazy ) && $this -> isSensitive ( $app , $key , $lazy ))) {
2024-01-25 13:27:51 +00:00
$value = self :: ENCRYPTION_PREFIX . $this -> crypto -> encrypt ( $value );
}
2024-02-12 19:33:44 +00: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 .
*/
2024-04-03 17:56:19 +00:00
if ( $origValue === $this -> getTypedValue ( $app , $key , $value , $lazy , $type )
2024-02-12 19:33:44 +00:00
&& ( ! $sensitive || $this -> isSensitive ( $app , $key , $lazy ))) {
return false ;
2024-01-11 16:32:58 +00:00
}
2024-02-12 19:33:44 +00: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' )
-> 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 ));
$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 16:32:58 +00:00
2024-02-12 19:33:44 +00:00
/**
* We cannot insert a new row , meaning we need to update an already existing one
*/
if ( ! $inserted ) {
2024-01-11 16:32:58 +00:00
$currType = $this -> valueTypes [ $app ][ $key ] ? ? 0 ;
if ( $currType === 0 ) { // this might happen when switching lazy loading status
$this -> loadConfigAll ();
$currType = $this -> valueTypes [ $app ][ $key ] ? ? 0 ;
}
2024-01-15 14:50:44 +00: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 16:32:58 +00: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 08:04:06 +00:00
*/
2024-01-11 16:32:58 +00:00
if ( ! $this -> isTyped ( self :: VALUE_MIXED , $currType ) &&
( $type | self :: VALUE_SENSITIVE ) !== ( $currType | self :: VALUE_SENSITIVE )) {
try {
$currType = $this -> convertTypeToString ( $currType );
$type = $this -> convertTypeToString ( $type );
} catch ( AppConfigIncorrectTypeException ) {
2024-01-15 14:50:44 +00:00
// can be ignored, this was just needed for a better exception message.
2024-01-11 16:32:58 +00:00
}
throw new AppConfigTypeConflictException ( 'conflict between new type (' . $type . ') and old type (' . $currType . ')' );
}
2020-10-21 08:04:06 +00:00
2024-01-11 16:32:58 +00: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 21:42:20 +00:00
$type |= self :: VALUE_SENSITIVE ;
2020-10-21 08:04:06 +00:00
}
2024-01-11 16:32:58 +00:00
if ( $lazy !== $this -> isLazy ( $app , $key )) {
$refreshCache = true ;
}
$update = $this -> connection -> getQueryBuilder ();
$update -> update ( 'appconfig' )
-> set ( 'configvalue' , $update -> createNamedParameter ( $value ))
2024-01-24 21:00:18 +00:00
-> set ( 'lazy' , $update -> createNamedParameter (( $lazy ) ? 1 : 0 , IQueryBuilder :: PARAM_INT ))
2024-01-11 16:32:58 +00: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 )));
$update -> executeStatement ();
2015-09-11 10:11:40 +00:00
}
2024-01-11 16:32:58 +00:00
if ( $refreshCache ) {
$this -> clearCache ();
return true ;
}
2015-09-02 16:56:17 +00:00
2024-01-11 16:32:58 +00:00
// update local cache
if ( $lazy ) {
$cache = & $this -> lazyCache ;
} else {
$cache = & $this -> fastCache ;
}
$cache [ $app ][ $key ] = $value ;
$this -> valueTypes [ $app ][ $key ] = $type ;
2015-09-02 16:56:17 +00:00
2024-01-11 16:32:58 +00:00
return true ;
2011-03-12 09:28:10 +00:00
}
/**
2024-01-11 16:32:58 +00:00
* Change the type of config value .
2014-06-01 12:04:17 +00:00
*
2024-01-11 16:32:58 +00: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 09:28:10 +00:00
*/
2024-01-11 16:32:58 +00:00
public function updateType ( string $app , string $key , int $type = self :: VALUE_MIXED ) : bool {
$this -> assertParams ( $app , $key );
$this -> loadConfigAll ();
$lazy = $this -> isLazy ( $app , $key );
// 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 16:56:17 +00:00
2024-01-11 16:32:58 +00:00
$update = $this -> connection -> getQueryBuilder ();
$update -> update ( 'appconfig' )
-> set ( 'type' , $update -> createNamedParameter ( $type , IQueryBuilder :: PARAM_INT ))
-> where ( $update -> expr () -> eq ( 'appid' , $update -> createNamedParameter ( $app )))
-> andWhere ( $update -> expr () -> eq ( 'configkey' , $update -> createNamedParameter ( $key )));
$update -> executeStatement ();
$this -> valueTypes [ $app ][ $key ] = $type ;
return true ;
2011-03-12 09:28:10 +00:00
}
2024-01-11 16:32:58 +00:00
2011-03-12 09:28:10 +00:00
/**
2024-01-11 16:32:58 +00:00
* @ inheritDoc
2014-06-01 12:04:17 +00:00
*
2024-01-11 16:32:58 +00: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 09:28:10 +00:00
*
2024-02-01 20:48:40 +00:00
* @ return bool TRUE if entry was found in database and an update was necessary
2024-01-11 16:32:58 +00:00
* @ since 29.0 . 0
2011-03-12 09:28:10 +00:00
*/
2024-01-11 16:32:58 +00:00
public function updateSensitive ( string $app , string $key , bool $sensitive ) : bool {
$this -> assertParams ( $app , $key );
$this -> loadConfigAll ();
2024-02-01 20:48:40 +00:00
try {
if ( $sensitive === $this -> isSensitive ( $app , $key , null )) {
return false ;
}
} catch ( AppConfigUnknownKeyException $e ) {
2024-01-11 16:32:58 +00:00
return false ;
}
2015-09-02 16:56:17 +00:00
2024-01-25 13:27:51 +00: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 16:32:58 +00: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 13:27:51 +00:00
$value = $cache [ $app ][ $key ];
2024-01-11 16:32:58 +00:00
if ( $sensitive ) {
2024-01-25 13:27:51 +00: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 16:32:58 +00:00
}
$update = $this -> connection -> getQueryBuilder ();
$update -> update ( 'appconfig' )
-> set ( 'type' , $update -> createNamedParameter ( $type , IQueryBuilder :: PARAM_INT ))
2024-01-25 13:27:51 +00:00
-> set ( 'configvalue' , $update -> createNamedParameter ( $value ))
2024-01-11 16:32:58 +00:00
-> where ( $update -> expr () -> eq ( 'appid' , $update -> createNamedParameter ( $app )))
-> andWhere ( $update -> expr () -> eq ( 'configkey' , $update -> createNamedParameter ( $key )));
$update -> executeStatement ();
2015-09-02 16:56:17 +00:00
2024-01-11 16:32:58 +00:00
$this -> valueTypes [ $app ][ $key ] = $type ;
return true ;
2011-03-01 22:20:16 +00:00
}
2012-08-29 06:38:33 +00:00
2012-04-14 15:53:02 +00:00
/**
2024-01-11 16:32:58 +00:00
* @ inheritDoc
2013-12-18 14:10:12 +00:00
*
2024-01-11 16:32:58 +00: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 20:48:40 +00:00
* @ return bool TRUE if entry was found in database and an update was necessary
2024-01-11 16:32:58 +00:00
* @ since 29.0 . 0
2012-04-14 15:53:02 +00:00
*/
2024-01-11 16:32:58 +00:00
public function updateLazy ( string $app , string $key , bool $lazy ) : bool {
$this -> assertParams ( $app , $key );
$this -> loadConfigAll ();
2024-02-01 20:48:40 +00:00
try {
if ( $lazy === $this -> isLazy ( $app , $key )) {
return false ;
}
} catch ( AppConfigUnknownKeyException $e ) {
2012-04-14 15:53:02 +00:00
return false ;
}
2013-03-02 16:17:11 +00:00
2024-01-11 16:32:58 +00:00
$update = $this -> connection -> getQueryBuilder ();
$update -> update ( 'appconfig' )
2024-01-24 21:00:18 +00:00
-> set ( 'lazy' , $update -> createNamedParameter ( $lazy ? 1 : 0 , IQueryBuilder :: PARAM_INT ))
2024-01-11 16:32:58 +00:00
-> where ( $update -> expr () -> eq ( 'appid' , $update -> createNamedParameter ( $app )))
-> andWhere ( $update -> expr () -> eq ( 'configkey' , $update -> createNamedParameter ( $key )));
$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 );
$this -> loadConfigAll ();
$lazy = $this -> isLazy ( $app , $key );
if ( $lazy ) {
2024-01-15 14:50:44 +00:00
$cache = $this -> lazyCache ;
2013-12-18 14:10:12 +00:00
} else {
2024-01-15 14:50:44 +00:00
$cache = $this -> fastCache ;
2024-01-11 16:32:58 +00:00
}
2013-03-02 16:17:11 +00:00
2024-01-11 16:32:58 +00: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 12:14:30 +00:00
}
2024-01-11 16:32:58 +00:00
2024-01-16 13:48:27 +00:00
if ( ! isset ( $cache [ $app ][ $key ])) {
throw new AppConfigUnknownKeyException ( 'unknown config key' );
}
2024-04-17 23:01:47 +00: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 16:32:58 +00:00
return [
'app' => $app ,
'key' => $key ,
2024-04-17 23:01:47 +00:00
'value' => $value ,
2024-01-11 16:32:58 +00:00
'type' => $type ,
2024-01-15 14:50:44 +00:00
'lazy' => $lazy ,
2024-01-11 16:32:58 +00:00
'typeString' => $typeString ,
2024-04-17 23:01:47 +00:00
'sensitive' => $sensitive
2024-01-11 16:32:58 +00:00
];
2012-04-14 15:53:02 +00:00
}
2015-09-02 16:56:17 +00:00
2017-01-11 10:42:36 +00:00
/**
2024-01-11 16:32:58 +00: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 );
$qb = $this -> connection -> getQueryBuilder ();
$qb -> delete ( 'appconfig' )
-> where ( $qb -> expr () -> eq ( 'appid' , $qb -> createNamedParameter ( $app )))
-> andWhere ( $qb -> expr () -> eq ( 'configkey' , $qb -> createNamedParameter ( $key )));
$qb -> executeStatement ();
unset ( $this -> lazyCache [ $app ][ $key ]);
unset ( $this -> fastCache [ $app ][ $key ]);
}
/**
* @ 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' )
-> where ( $qb -> expr () -> eq ( 'appid' , $qb -> createNamedParameter ( $app )));
$qb -> executeStatement ();
$this -> clearCache ();
}
/**
* @ inheritDoc
*
* @ param bool $reload set to TRUE to refill cache instantly after clearing it
*
* @ since 29.0 . 0
*/
public function clearCache ( bool $reload = false ) : void {
$this -> lazyLoaded = $this -> fastLoaded = false ;
$this -> lazyCache = $this -> fastCache = $this -> valueTypes = [];
if ( ! $reload ) {
return ;
}
$this -> loadConfigAll ();
}
/**
* For debug purpose .
* Returns the cached data .
2017-01-11 10:42:36 +00:00
*
* @ return array
2024-01-11 16:32:58 +00:00
* @ since 29.0 . 0
* @ internal
2017-01-11 10:42:36 +00:00
*/
2024-01-11 16:32:58 +00:00
public function statusCache () : array {
return [
'fastLoaded' => $this -> fastLoaded ,
'fastCache' => $this -> fastCache ,
'lazyLoaded' => $this -> lazyLoaded ,
'lazyCache' => $this -> lazyCache ,
];
}
2017-01-11 10:42:36 +00:00
2024-01-11 16:32:58 +00: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 14:50:44 +00:00
* Confirm the string set for app and key fit the database description
2024-01-11 16:32:58 +00: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 10:42:36 +00:00
}
}
2024-01-11 16:32:58 +00:00
}
2017-01-11 10:42:36 +00:00
2024-01-11 16:32:58 +00:00
private function loadConfigAll () : void {
$this -> loadConfig ( null );
2017-01-11 10:42:36 +00:00
}
2015-09-02 16:56:17 +00:00
/**
2024-01-11 16:32:58 +00:00
* Load normal config or config set as lazy loaded
*
* @ param bool | null $lazy set to TRUE to load config set as lazy loaded , set to NULL to load all config
2015-09-02 16:56:17 +00:00
*/
2024-01-11 16:32:58 +00:00
private function loadConfig ( ? bool $lazy = false ) : void {
if ( $this -> isLoaded ( $lazy )) {
2017-01-11 10:42:36 +00:00
return ;
}
2015-09-02 16:56:17 +00:00
2024-02-07 09:35:00 +00:00
if (( $lazy ? ? true ) !== false ) { // if lazy is null or true, we debug log
2024-02-12 13:53:46 +00:00
$this -> logger -> debug ( 'The loading of lazy AppConfig values have been requested' , [ 'exception' => new \RuntimeException ( 'ignorable exception' )]);
2024-02-07 09:35:00 +00:00
}
2024-01-11 16:32:58 +00:00
$qb = $this -> connection -> getQueryBuilder ();
$qb -> from ( 'appconfig' );
/**
* The use of $this ->> migrationCompleted is only needed to manage the
* database during the upgrading process to nc29 .
*/
if ( ! $this -> migrationCompleted ) {
$qb -> select ( 'appid' , 'configkey' , 'configvalue' );
} else {
2024-01-24 21:00:18 +00:00
// we only need value from lazy when loadConfig does not specify it
$qb -> select ( 'appid' , 'configkey' , 'configvalue' , 'type' );
2024-01-11 16:32:58 +00:00
if ( $lazy !== null ) {
2024-01-24 21:00:18 +00:00
$qb -> where ( $qb -> expr () -> eq ( 'lazy' , $qb -> createNamedParameter ( $lazy ? 1 : 0 , IQueryBuilder :: PARAM_INT )));
} else {
$qb -> addSelect ( 'lazy' );
2024-01-11 16:32:58 +00:00
}
}
2015-09-02 16:56:17 +00:00
2024-01-11 16:32:58 +00:00
try {
$result = $qb -> executeQuery ();
} catch ( DBException $e ) {
/**
* in case of issue with field name , it means that migration is not completed .
* Falling back to a request without select on lazy .
* This whole try / catch and the migrationCompleted variable can be removed in NC30 .
*/
if ( $e -> getReason () !== DBException :: REASON_INVALID_FIELD_NAME ) {
throw $e ;
}
$this -> migrationCompleted = false ;
$this -> loadConfig ( $lazy );
return ;
}
2015-09-02 16:56:17 +00:00
2016-06-10 13:20:22 +00:00
$rows = $result -> fetchAll ();
foreach ( $rows as $row ) {
2024-01-24 21:00:18 +00:00
// most of the time, 'lazy' is not in the select because its value is already known
if (( $row [ 'lazy' ] ? ? ( $lazy ? ? 0 ) ? 1 : 0 ) === 1 ) {
2024-01-11 16:32:58 +00:00
$cache = & $this -> lazyCache ;
} else {
$cache = & $this -> fastCache ;
2015-09-03 13:41:30 +00:00
}
2024-01-11 16:32:58 +00:00
$cache [ $row [ 'appid' ]][ $row [ 'configkey' ]] = $row [ 'configvalue' ] ? ? '' ;
$this -> valueTypes [ $row [ 'appid' ]][ $row [ 'configkey' ]] = ( int )( $row [ 'type' ] ? ? 0 );
2015-09-02 16:56:17 +00:00
}
$result -> closeCursor ();
2024-01-11 16:32:58 +00:00
$this -> setAsLoaded ( $lazy );
}
/**
* if $lazy is :
* - false : will returns true if fast config is loaded
* - true : will returns true if lazy config is loaded
* - null : will returns true if both config are loaded
*
* @ param bool $lazy
*
* @ return bool
*/
private function isLoaded ( ? bool $lazy ) : bool {
if ( $lazy === null ) {
return $this -> lazyLoaded && $this -> fastLoaded ;
}
2015-09-02 16:56:17 +00:00
2024-01-11 16:32:58 +00:00
return $lazy ? $this -> lazyLoaded : $this -> fastLoaded ;
2015-09-02 16:56:17 +00:00
}
2022-11-15 12:08:20 +00:00
2024-01-11 16:32:58 +00:00
/**
* if $lazy is :
* - false : set fast config as loaded
* - true : set lazy config as loaded
* - null : set both config as loaded
*
* @ param bool $lazy
*/
private function setAsLoaded ( ? bool $lazy ) : void {
if ( $lazy === null ) {
$this -> fastLoaded = true ;
$this -> lazyLoaded = true ;
return ;
}
if ( $lazy ) {
$this -> lazyLoaded = true ;
} else {
$this -> fastLoaded = true ;
}
}
/**
* Gets the config value
*
* @ param string $app app
* @ param string $key key
* @ param string $default = null , default value if the key does not exist
*
* @ return string the value or $default
* @ deprecated - use getValue * ()
*
* This function gets a value from the appconfig table . If the key does
* not exist the default value will be returned
*/
public function getValue ( $app , $key , $default = null ) {
$this -> loadConfig ();
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
* @ deprecated
*/
public function setValue ( $app , $key , $value ) {
2024-01-24 21:00:18 +00: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
* unless calling the method { @ see loadConfigAll ()}
*/
2024-01-11 16:32:58 +00: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 14:23:36 +00:00
* @ deprecated 29.0 . 0 use { @ see getAllValues ()}
2024-01-11 16:32:58 +00:00
*/
public function getValues ( $app , $key ) {
if (( $app !== false ) === ( $key !== false )) {
return false ;
}
$key = ( $key === false ) ? '' : $key ;
if ( ! $app ) {
2024-04-04 18:40:31 +00:00
return $this -> searchValues ( $key , false , self :: VALUE_MIXED );
2024-01-11 16:32:58 +00: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 14:23:36 +00:00
* @ deprecated 29.0 . 0 use { @ see getAllValues ()}
2024-01-11 16:32:58 +00:00
*/
public function getFilteredValues ( $app ) {
return $this -> getAllValues ( $app , filtered : true );
}
2024-04-04 01:20:35 +00:00
/**
* ** Warning :** avoid default NULL value for $lazy as this will
* load all lazy values from the database
*
* @ param string $app
2024-04-04 18:40:31 +00:00
* @ param array < string , string > $values [ 'key' => 'value' ]
2024-04-04 01:20:35 +00:00
* @ param bool | null $lazy
*
2024-04-04 18:40:31 +00:00
* @ return array < string , string | int | float | bool | array >
2024-04-04 01:20:35 +00:00
*/
private function formatAppValues ( string $app , array $values , ? bool $lazy = null ) : array {
foreach ( $values as $key => $value ) {
try {
$type = $this -> getValueType ( $app , $key , $lazy );
} catch ( AppConfigUnknownKeyException $e ) {
continue ;
}
2024-04-04 18:40:31 +00:00
$values [ $key ] = $this -> convertTypedValue ( $value , $type );
2024-04-04 01:20:35 +00:00
}
return $values ;
}
2024-04-04 18:40:31 +00: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 16:32:58 +00:00
/**
* @ param string $app
*
* @ return string []
* @ deprecated data sensitivity should be set when calling setValue * ()
*/
private function getSensitiveKeys ( string $app ) : array {
$sensitiveValues = [
'circles' => [
'/^key_pairs$/' ,
'/^local_gskey$/' ,
],
2024-10-28 13:56:19 +00:00
'call_summary_bot' => [
'/^secret_(.*)$/' ,
],
2024-01-11 16:32:58 +00:00
'external' => [
'/^sites$/' ,
2024-10-14 08:42:06 +00:00
'/^jwt_token_privkey_(.*)$/' ,
2024-01-11 16:32:58 +00:00
],
2024-10-28 13:56:19 +00:00
'globalsiteselector' => [
'/^gss\.jwt\.key$/' ,
],
2024-01-11 16:32:58 +00: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$/' ,
],
'notify_push' => [
'/^cookie$/' ,
],
2024-10-07 08:58:03 +00:00
'onlyoffice' => [
'/^jwt_secret$/' ,
],
'passwords' => [
'/^SSEv1ServerKey$/' ,
],
2024-01-11 16:32:58 +00: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$/' ,
],
'user_ldap' => [
'/^(s..)?ldap_agent_password$/' ,
],
2024-10-07 08:58:03 +00:00
'twofactor_gateway' => [
'/^.*token$/' ,
],
2024-01-11 16:32:58 +00:00
'user_saml' => [
'/^idp-x509cert$/' ,
],
2024-10-28 13:56:19 +00:00
'whiteboard' => [
'/^jwt_secret_key$/' ,
],
2024-01-11 16:32:58 +00:00
];
return $sensitiveValues [ $app ] ? ? [];
}
2022-11-15 12:08:20 +00:00
/**
* Clear all the cached app config values
* New cache will be generated next time a config value is retrieved
2024-01-11 16:32:58 +00:00
*
* @ deprecated use { @ see clearCache ()}
2022-11-15 12:08:20 +00:00
*/
public function clearCachedConfig () : void {
2024-01-11 16:32:58 +00:00
$this -> clearCache ();
2022-11-15 12:08:20 +00:00
}
2011-03-01 22:20:16 +00:00
}