diff options
Diffstat (limited to 'lib/private/AppConfig.php')
-rw-r--r-- | lib/private/AppConfig.php | 157 |
1 files changed, 132 insertions, 25 deletions
diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index a8a6f689ffa..f050deba1ca 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -14,7 +14,9 @@ use JsonException; use NCU\Config\Lexicon\ConfigLexiconEntry; use NCU\Config\Lexicon\ConfigLexiconStrictness; use NCU\Config\Lexicon\IConfigLexicon; +use NCU\Config\Lexicon\Preset; use OC\AppFramework\Bootstrap\Coordinator; +use OC\Config\ConfigManager; use OCP\DB\Exception as DBException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Exceptions\AppConfigIncorrectTypeException; @@ -24,6 +26,7 @@ use OCP\IAppConfig; use OCP\IConfig; use OCP\IDBConnection; use OCP\Security\ICrypto; +use OCP\Server; use Psr\Log\LoggerInterface; /** @@ -59,14 +62,16 @@ class AppConfig implements IAppConfig { private array $valueTypes = []; // type for all config values private bool $fastLoaded = false; private bool $lazyLoaded = false; - /** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ + /** @var array<string, array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ private array $configLexiconDetails = []; - + private bool $ignoreLexiconAliases = false; + private ?Preset $configLexiconPreset = null; /** @var ?array<string, string> */ private ?array $appVersionsCache = null; public function __construct( protected IDBConnection $connection, + protected IConfig $config, protected LoggerInterface $logger, protected ICrypto $crypto, ) { @@ -90,8 +95,9 @@ class AppConfig implements IAppConfig { * @inheritDoc * * @param string $app id of the app - * * @return list<string> list of stored config keys + * @see searchKeys to not load lazy config keys + * * @since 29.0.0 */ public function getKeys(string $app): array { @@ -107,6 +113,32 @@ class AppConfig implements IAppConfig { * @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)); + } + + /** + * @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 * @@ -117,6 +149,7 @@ class AppConfig implements IAppConfig { public function hasKey(string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($app, $key); $this->loadConfig($app, $lazy); + $this->matchAndApplyLexiconDefinition($app, $key); if ($lazy === null) { $appCache = $this->getAllValues($app); @@ -142,6 +175,7 @@ class AppConfig implements IAppConfig { public function isSensitive(string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($app, $key); $this->loadConfig(null, $lazy); + $this->matchAndApplyLexiconDefinition($app, $key); if (!isset($this->valueTypes[$app][$key])) { throw new AppConfigUnknownKeyException('unknown config key'); @@ -162,6 +196,9 @@ class AppConfig implements IAppConfig { * @since 29.0.0 */ public function isLazy(string $app, string $key): bool { + $this->assertParams($app, $key); + $this->matchAndApplyLexiconDefinition($app, $key); + // there is a huge probability the non-lazy config are already loaded if ($this->hasKey($app, $key, false)) { return false; @@ -284,7 +321,7 @@ class AppConfig implements IAppConfig { ): string { try { $lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy; - } catch (AppConfigUnknownKeyException $e) { + } catch (AppConfigUnknownKeyException) { return $default; } @@ -429,9 +466,18 @@ class AppConfig implements IAppConfig { int $type, ): string { $this->assertParams($app, $key, valueType: $type); - if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default)) { - return $default; // returns default if strictness of lexicon is set to WARNING (block and report) + $origKey = $key; + $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; + } + $this->loadConfig($app, $lazy); /** @@ -469,6 +515,14 @@ class AppConfig implements IAppConfig { $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH)); } + // in case the key was modified while running matchAndApplyLexiconDefinition() we are + // interested to check options in case a modification of the value is needed + // ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN + if ($origKey !== $key && $type === self::VALUE_BOOL) { + $configManager = Server::get(ConfigManager::class); + $value = ($configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0'; + } + return $value; } @@ -798,8 +852,8 @@ class AppConfig implements IAppConfig { * we only accept a different type from the one stored in database * if the one stored in database is not-defined (VALUE_MIXED) */ - if (!$this->isTyped(self::VALUE_MIXED, $currType) && - ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) { + if (!$this->isTyped(self::VALUE_MIXED, $currType) + && ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) { try { $currType = $this->convertTypeToString($currType); $type = $this->convertTypeToString($type); @@ -863,7 +917,8 @@ class AppConfig implements IAppConfig { public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool { $this->assertParams($app, $key); $this->loadConfigAll(); - $lazy = $this->isLazy($app, $key); + $this->matchAndApplyLexiconDefinition($app, $key); + $this->isLazy($app, $key); // confirm key exists // 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])) { @@ -905,6 +960,7 @@ class AppConfig implements IAppConfig { public function updateSensitive(string $app, string $key, bool $sensitive): bool { $this->assertParams($app, $key); $this->loadConfigAll(); + $this->matchAndApplyLexiconDefinition($app, $key); try { if ($sensitive === $this->isSensitive($app, $key, null)) { @@ -964,6 +1020,7 @@ class AppConfig implements IAppConfig { public function updateLazy(string $app, string $key, bool $lazy): bool { $this->assertParams($app, $key); $this->loadConfigAll(); + $this->matchAndApplyLexiconDefinition($app, $key); try { if ($lazy === $this->isLazy($app, $key)) { @@ -999,6 +1056,7 @@ class AppConfig implements IAppConfig { public function getDetails(string $app, string $key): array { $this->assertParams($app, $key); $this->loadConfigAll(); + $this->matchAndApplyLexiconDefinition($app, $key); $lazy = $this->isLazy($app, $key); if ($lazy) { @@ -1086,6 +1144,8 @@ class AppConfig implements IAppConfig { */ public function deleteKey(string $app, string $key): void { $this->assertParams($app, $key); + $this->matchAndApplyLexiconDefinition($app, $key); + $qb = $this->connection->getQueryBuilder(); $qb->delete('appconfig') ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app))) @@ -1123,7 +1183,8 @@ class AppConfig implements IAppConfig { */ public function clearCache(bool $reload = false): void { $this->lazyLoaded = $this->fastLoaded = false; - $this->lazyCache = $this->fastCache = $this->valueTypes = []; + $this->lazyCache = $this->fastCache = $this->valueTypes = $this->configLexiconDetails = []; + $this->configLexiconPreset = null; if (!$reload) { return; @@ -1293,6 +1354,7 @@ class AppConfig implements IAppConfig { */ public function getValue($app, $key, $default = null) { $this->loadConfig($app); + $this->matchAndApplyLexiconDefinition($app, $key); return $this->fastCache[$app][$key] ?? $default; } @@ -1372,7 +1434,7 @@ class AppConfig implements IAppConfig { foreach ($values as $key => $value) { try { $type = $this->getValueType($app, $key, $lazy); - } catch (AppConfigUnknownKeyException $e) { + } catch (AppConfigUnknownKeyException) { continue; } @@ -1556,7 +1618,8 @@ class AppConfig implements IAppConfig { } /** - * match and apply current use of config values with defined lexicon + * 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. * * @throws AppConfigUnknownKeyException * @throws AppConfigTypeConflictException @@ -1564,10 +1627,10 @@ class AppConfig implements IAppConfig { */ private function matchAndApplyLexiconDefinition( string $app, - string $key, - bool &$lazy, - int &$type, - string &$default = '', + string &$key, + ?bool &$lazy = null, + int &$type = self::VALUE_MIXED, + ?string &$default = null, ): bool { if (in_array($key, [ @@ -1578,11 +1641,18 @@ class AppConfig implements IAppConfig { return true; // we don't break stuff for this list of config keys. } $configDetails = $this->getConfigDetailsFromLexicon($app); + 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]; + } + if (!array_key_exists($key, $configDetails['entries'])) { - return $this->applyLexiconStrictness( - $configDetails['strictness'], - 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon' - ); + return $this->applyLexiconStrictness($configDetails['strictness'], 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon'); + } + + // if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon + if ($lazy === null) { + return true; } /** @var ConfigLexiconEntry $configValue */ @@ -1597,7 +1667,11 @@ class AppConfig implements IAppConfig { } $lazy = $configValue->isLazy(); - $default = $configValue->getDefault() ?? $default; // default from Lexicon got priority + // only look for default if needed, default from Lexicon got priority + if ($default !== null) { + $default = $configValue->getDefault($this->getLexiconPreset()) ?? $default; + } + if ($configValue->isFlagged(self::FLAG_SENSITIVE)) { $type |= self::VALUE_SENSITIVE; } @@ -1644,20 +1718,25 @@ class AppConfig implements IAppConfig { * extract details from registered $appId's config lexicon * * @param string $appId + * @internal * - * @return array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness} + * @return array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness} */ - private function getConfigDetailsFromLexicon(string $appId): array { + public function getConfigDetailsFromLexicon(string $appId): array { if (!array_key_exists($appId, $this->configLexiconDetails)) { - $entries = []; + $entries = $aliases = []; $bootstrapCoordinator = \OCP\Server::get(Coordinator::class); $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId); foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) { $entries[$configEntry->getKey()] = $configEntry; + if ($configEntry->getRename() !== null) { + $aliases[$configEntry->getRename()] = $configEntry->getKey(); + } } $this->configLexiconDetails[$appId] = [ 'entries' => $entries, + 'aliases' => $aliases, 'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE ]; } @@ -1665,16 +1744,44 @@ class AppConfig implements IAppConfig { return $this->configLexiconDetails[$appId]; } + private function getLexiconEntry(string $appId, string $key): ?ConfigLexiconEntry { + 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; + } + + private function getLexiconPreset(): Preset { + if ($this->configLexiconPreset === null) { + $this->configLexiconPreset = Preset::tryFrom($this->config->getSystemValueInt(ConfigManager::PRESET_CONFIGKEY, 0)) ?? Preset::NONE; + } + + return $this->configLexiconPreset; + } + /** * Returns the installed versions of all apps * * @return array<string, string> */ - public function getAppInstalledVersions(): array { + public function getAppInstalledVersions(bool $onlyEnabled = false): array { if ($this->appVersionsCache === null) { /** @var array<string, string> */ $this->appVersionsCache = $this->searchValues('installed_version', false, IAppConfig::VALUE_STRING); } + if ($onlyEnabled) { + return array_filter( + $this->appVersionsCache, + fn (string $app): bool => $this->getValueString($app, 'enabled', 'no') !== 'no', + ARRAY_FILTER_USE_KEY + ); + } return $this->appVersionsCache; } } |