diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2025-05-14 20:10:34 +0200 |
---|---|---|
committer | Maxence Lange <maxence@artificial-owl.com> | 2025-05-16 17:20:12 +0200 |
commit | 9fdc9a2b9972bb8f46eab84e887477c6a21cd5f5 (patch) | |
tree | 73604d2d49dd9cc0d06a163946bbf56394b2ae71 | |
parent | b9480f44dc1b652210411ea7b939f462bc9389d4 (diff) | |
download | nextcloud-server-feat/noid/lexicon-migrate-keys.tar.gz nextcloud-server-feat/noid/lexicon-migrate-keys.zip |
feat(lexicon): migrate config key/valuefeat/noid/lexicon-migrate-keys
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
-rw-r--r-- | core/Command/Config/App/Base.php | 2 | ||||
-rw-r--r-- | core/Command/Config/App/SetConfig.php | 25 | ||||
-rw-r--r-- | core/Command/Config/ListConfigs.php | 7 | ||||
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 2 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 2 | ||||
-rw-r--r-- | lib/private/App/AppManager.php | 5 | ||||
-rw-r--r-- | lib/private/AppConfig.php | 79 | ||||
-rw-r--r-- | lib/private/Config/ConfigManager.php | 252 | ||||
-rw-r--r-- | lib/private/Config/UserConfig.php | 86 | ||||
-rw-r--r-- | lib/private/Repair/ConfigKeyMigration.php | 29 | ||||
-rw-r--r-- | lib/private/Server.php | 16 | ||||
-rw-r--r-- | lib/unstable/Config/Lexicon/ConfigLexiconEntry.php | 25 | ||||
-rw-r--r-- | tests/Core/Command/Config/App/DeleteConfigTest.php | 5 | ||||
-rw-r--r-- | tests/Core/Command/Config/App/GetConfigTest.php | 5 | ||||
-rw-r--r-- | tests/Core/Command/Config/App/SetConfigTest.php | 5 | ||||
-rw-r--r-- | tests/Core/Command/Config/ListConfigsTest.php | 10 | ||||
-rw-r--r-- | tests/lib/DB/AdapterTest.php | 2 |
17 files changed, 491 insertions, 66 deletions
diff --git a/core/Command/Config/App/Base.php b/core/Command/Config/App/Base.php index 07341c4faf9..e90a8e78f5b 100644 --- a/core/Command/Config/App/Base.php +++ b/core/Command/Config/App/Base.php @@ -7,12 +7,14 @@ declare(strict_types=1); */ namespace OC\Core\Command\Config\App; +use OC\Config\ConfigManager; use OCP\IAppConfig; use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; abstract class Base extends \OC\Core\Command\Base { public function __construct( protected IAppConfig $appConfig, + protected readonly ConfigManager $configManager, ) { parent::__construct(); } diff --git a/core/Command/Config/App/SetConfig.php b/core/Command/Config/App/SetConfig.php index 345067cfd45..1f4ab81bf05 100644 --- a/core/Command/Config/App/SetConfig.php +++ b/core/Command/Config/App/SetConfig.php @@ -9,7 +9,6 @@ declare(strict_types=1); namespace OC\Core\Command\Config\App; use OC\AppConfig; -use OCP\Exceptions\AppConfigIncorrectTypeException; use OCP\Exceptions\AppConfigUnknownKeyException; use OCP\IAppConfig; use Symfony\Component\Console\Helper\QuestionHelper; @@ -161,7 +160,6 @@ class SetConfig extends Base { } $value = (string)$input->getOption('value'); - switch ($type) { case IAppConfig::VALUE_MIXED: $updated = $this->appConfig->setValueMixed($appName, $configName, $value, $lazy, $sensitive); @@ -172,34 +170,19 @@ class SetConfig extends Base { break; case IAppConfig::VALUE_INT: - if ($value !== ((string)((int)$value))) { - throw new AppConfigIncorrectTypeException('Value is not an integer'); - } - $updated = $this->appConfig->setValueInt($appName, $configName, (int)$value, $lazy, $sensitive); + $updated = $this->appConfig->setValueInt($appName, $configName, $this->configManager->convertToInt($value), $lazy, $sensitive); break; case IAppConfig::VALUE_FLOAT: - if ($value !== ((string)((float)$value))) { - throw new AppConfigIncorrectTypeException('Value is not a float'); - } - $updated = $this->appConfig->setValueFloat($appName, $configName, (float)$value, $lazy, $sensitive); + $updated = $this->appConfig->setValueFloat($appName, $configName, $this->configManager->convertToFloat($value), $lazy, $sensitive); break; case IAppConfig::VALUE_BOOL: - if (in_array(strtolower($value), ['true', '1', 'on', 'yes'])) { - $valueBool = true; - } elseif (in_array(strtolower($value), ['false', '0', 'off', 'no'])) { - $valueBool = false; - } else { - throw new AppConfigIncorrectTypeException('Value is not a boolean, please use \'true\' or \'false\''); - } - $updated = $this->appConfig->setValueBool($appName, $configName, $valueBool, $lazy); + $updated = $this->appConfig->setValueBool($appName, $configName, $this->configManager->convertToBool($value), $lazy); break; case IAppConfig::VALUE_ARRAY: - $valueArray = json_decode($value, true, flags: JSON_THROW_ON_ERROR); - $valueArray = (is_array($valueArray)) ? $valueArray : throw new AppConfigIncorrectTypeException('Value is not an array'); - $updated = $this->appConfig->setValueArray($appName, $configName, $valueArray, $lazy, $sensitive); + $updated = $this->appConfig->setValueArray($appName, $configName, $this->configManager->convertToArray($value), $lazy, $sensitive); break; } } diff --git a/core/Command/Config/ListConfigs.php b/core/Command/Config/ListConfigs.php index 094348dd9ba..b81bfbf4d18 100644 --- a/core/Command/Config/ListConfigs.php +++ b/core/Command/Config/ListConfigs.php @@ -7,6 +7,7 @@ */ namespace OC\Core\Command\Config; +use OC\Config\ConfigManager; use OC\Core\Command\Base; use OC\SystemConfig; use OCP\IAppConfig; @@ -22,6 +23,7 @@ class ListConfigs extends Base { public function __construct( protected SystemConfig $systemConfig, protected IAppConfig $appConfig, + protected ConfigManager $configManager, ) { parent::__construct(); } @@ -44,6 +46,7 @@ class ListConfigs extends Base { InputOption::VALUE_NONE, 'Use this option when you want to include sensitive configs like passwords, salts, ...' ) + ->addOption('migrate', null, InputOption::VALUE_NONE, 'Rename config keys of all enabled apps, based on ConfigLexicon') ; } @@ -51,6 +54,10 @@ class ListConfigs extends Base { $app = $input->getArgument('app'); $noSensitiveValues = !$input->getOption('private'); + if ($input->getOption('migrate')) { + $this->configManager->migrateConfigLexiconKeys(($app === 'all') ? null : $app); + } + if (!is_string($app)) { $output->writeln('<error>Invalid app value given</error>'); return 1; diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index c0c086e4038..e13e77b00bb 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1184,6 +1184,7 @@ return array( 'OC\\Comments\\Manager' => $baseDir . '/lib/private/Comments/Manager.php', 'OC\\Comments\\ManagerFactory' => $baseDir . '/lib/private/Comments/ManagerFactory.php', 'OC\\Config' => $baseDir . '/lib/private/Config.php', + 'OC\\Config\\ConfigManager' => $baseDir . '/lib/private/Config/ConfigManager.php', 'OC\\Config\\Lexicon\\CoreConfigLexicon' => $baseDir . '/lib/private/Config/Lexicon/CoreConfigLexicon.php', 'OC\\Config\\UserConfig' => $baseDir . '/lib/private/Config/UserConfig.php', 'OC\\Console\\Application' => $baseDir . '/lib/private/Console/Application.php', @@ -1889,6 +1890,7 @@ return array( 'OC\\Repair\\ClearGeneratedAvatarCache' => $baseDir . '/lib/private/Repair/ClearGeneratedAvatarCache.php', 'OC\\Repair\\ClearGeneratedAvatarCacheJob' => $baseDir . '/lib/private/Repair/ClearGeneratedAvatarCacheJob.php', 'OC\\Repair\\Collation' => $baseDir . '/lib/private/Repair/Collation.php', + 'OC\\Repair\\ConfigKeyMigration' => $baseDir . '/lib/private/Repair/ConfigKeyMigration.php', 'OC\\Repair\\Events\\RepairAdvanceEvent' => $baseDir . '/lib/private/Repair/Events/RepairAdvanceEvent.php', 'OC\\Repair\\Events\\RepairErrorEvent' => $baseDir . '/lib/private/Repair/Events/RepairErrorEvent.php', 'OC\\Repair\\Events\\RepairFinishEvent' => $baseDir . '/lib/private/Repair/Events/RepairFinishEvent.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index f2f817877d2..91ecef39161 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1225,6 +1225,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Comments\\Manager' => __DIR__ . '/../../..' . '/lib/private/Comments/Manager.php', 'OC\\Comments\\ManagerFactory' => __DIR__ . '/../../..' . '/lib/private/Comments/ManagerFactory.php', 'OC\\Config' => __DIR__ . '/../../..' . '/lib/private/Config.php', + 'OC\\Config\\ConfigManager' => __DIR__ . '/../../..' . '/lib/private/Config/ConfigManager.php', 'OC\\Config\\Lexicon\\CoreConfigLexicon' => __DIR__ . '/../../..' . '/lib/private/Config/Lexicon/CoreConfigLexicon.php', 'OC\\Config\\UserConfig' => __DIR__ . '/../../..' . '/lib/private/Config/UserConfig.php', 'OC\\Console\\Application' => __DIR__ . '/../../..' . '/lib/private/Console/Application.php', @@ -1930,6 +1931,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Repair\\ClearGeneratedAvatarCache' => __DIR__ . '/../../..' . '/lib/private/Repair/ClearGeneratedAvatarCache.php', 'OC\\Repair\\ClearGeneratedAvatarCacheJob' => __DIR__ . '/../../..' . '/lib/private/Repair/ClearGeneratedAvatarCacheJob.php', 'OC\\Repair\\Collation' => __DIR__ . '/../../..' . '/lib/private/Repair/Collation.php', + 'OC\\Repair\\ConfigKeyMigration' => __DIR__ . '/../../..' . '/lib/private/Repair/ConfigKeyMigration.php', 'OC\\Repair\\Events\\RepairAdvanceEvent' => __DIR__ . '/../../..' . '/lib/private/Repair/Events/RepairAdvanceEvent.php', 'OC\\Repair\\Events\\RepairErrorEvent' => __DIR__ . '/../../..' . '/lib/private/Repair/Events/RepairErrorEvent.php', 'OC\\Repair\\Events\\RepairFinishEvent' => __DIR__ . '/../../..' . '/lib/private/Repair/Events/RepairFinishEvent.php', diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index f6494fa946d..bbc863b321a 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -8,6 +8,7 @@ namespace OC\App; use OC\AppConfig; use OC\AppFramework\Bootstrap\Coordinator; +use OC\Config\ConfigManager; use OCP\Activity\IManager as IActivityManager; use OCP\App\AppPathNotFoundException; use OCP\App\Events\AppDisableEvent; @@ -561,6 +562,8 @@ class AppManager implements IAppManager { ManagerEvent::EVENT_APP_ENABLE, $appId )); $this->clearAppsCache(); + + \OCP\Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId); } /** @@ -615,6 +618,8 @@ class AppManager implements IAppManager { ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups )); $this->clearAppsCache(); + + \OCP\Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId); } /** diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index a8a6f689ffa..f2d4743e98a 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -59,8 +59,9 @@ 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<array-key, array{entries: array<array-key, ConfigLexiconEntry>, aliases: array<array-key, string>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ private array $configLexiconDetails = []; + private bool $ignoreLexiconAliases = false; /** @var ?array<string, string> */ private ?array $appVersionsCache = null; @@ -117,6 +118,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 +144,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 +165,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 +290,7 @@ class AppConfig implements IAppConfig { ): string { try { $lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy; - } catch (AppConfigUnknownKeyException $e) { + } catch (AppConfigUnknownKeyException) { return $default; } @@ -429,6 +435,7 @@ class AppConfig implements IAppConfig { int $type, ): string { $this->assertParams($app, $key, valueType: $type); + $origKey = $key; if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default)) { return $default; // returns default if strictness of lexicon is set to WARNING (block and report) } @@ -469,6 +476,15 @@ 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 + if ($origKey !== $key) { + $lexiconEntry = $this->getLexiconEntry($app, $key); + if ($type === self::VALUE_BOOL && $lexiconEntry?->hasOption(ConfigLexiconEntry::RENAME_INVERT_BOOLEAN)) { + $value = (in_array(strtolower($value), ['1', 'true', 'yes', 'on'])) ? '0' : '1'; + } + } + return $value; } @@ -863,7 +879,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 +922,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 +982,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 +1018,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 +1106,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))) @@ -1293,6 +1315,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 +1395,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 +1579,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,9 +1588,9 @@ class AppConfig implements IAppConfig { */ private function matchAndApplyLexiconDefinition( string $app, - string $key, - bool &$lazy, - int &$type, + string &$key, + ?bool &$lazy = null, + int &$type = self::VALUE_MIXED, string &$default = '', ): bool { if (in_array($key, @@ -1578,11 +1602,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 */ @@ -1644,20 +1675,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<array-key, ConfigLexiconEntry>, aliases: array<array-key, 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,6 +1701,19 @@ 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; + } + /** * Returns the installed versions of all apps * diff --git a/lib/private/Config/ConfigManager.php b/lib/private/Config/ConfigManager.php new file mode 100644 index 00000000000..f31b7426dc0 --- /dev/null +++ b/lib/private/Config/ConfigManager.php @@ -0,0 +1,252 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Config; + +use JsonException; +use NCU\Config\Exceptions\TypeConflictException; +use NCU\Config\IUserConfig; +use NCU\Config\Lexicon\ConfigLexiconEntry; +use NCU\Config\ValueType; +use OC\AppConfig; +use OCP\App\IAppManager; +use OCP\IAppConfig; +use Psr\Log\LoggerInterface; + +class ConfigManager { + public function __construct( + /** @var AppConfig $appConfig */ + private readonly IAppConfig $appConfig, + /** @var UserConfig $appConfig */ + private readonly IUserConfig $userConfig, + private readonly IAppManager $appManager, + private readonly LoggerInterface $logger, + ) { + } + + /** + * Use the rename values from the list of ConfigLexiconEntry defined in each app ConfigLexicon + * to migrate config value to a new config key. + * Migration will only occur if new config key has no value in database. + * The previous value from the key set in rename will be deleted from the database when migration + * is over. + * + * This method should be mainly called during a new upgrade or when a new app is enabled. + * + * @see ConfigLexiconEntry + * @internal + * @param string|null $appId when set to NULL the method will be executed for all enabled apps of the instance + */ + public function migrateConfigLexiconKeys(?string $appId = null): void { + if ($appId === null) { + $this->migrateConfigLexiconKeys('core'); + foreach ($this->appManager->getEnabledApps() as $app) { + $this->migrateConfigLexiconKeys($app); + } + + return; + } + + // it is required to ignore aliases when moving config values + $this->appConfig->ignoreLexiconAliases(true); + $this->userConfig->ignoreLexiconAliases(true); + + $this->migrateAppConfigKeys($appId); + $this->migrateUserConfigKeys($appId); + + // switch back to normal behavior + $this->appConfig->ignoreLexiconAliases(false); + $this->userConfig->ignoreLexiconAliases(false); + } + + /** + * Get details from lexicon related to AppConfig and search for entries with rename to initiate + * a migration to new config key + */ + private function migrateAppConfigKeys(string $appId): void { + $lexicon = $this->appConfig->getConfigDetailsFromLexicon($appId); + + // we store a list of config keys to compare with any 'copyFrom' + $keys = []; + foreach ($lexicon['entries'] as $entry) { + $keys[] = $entry->getKey(); + } + + foreach ($lexicon['entries'] as $entry) { + // only interested in entries with rename set + if ($entry->getRename() === null) { + continue; + } + + if (in_array($entry->getRename(), $keys, true)) { + $this->logger->error('rename value should not exist as a valid config key within Lexicon'); + continue; + } + + // only migrate if rename config key has a value and the new config key hasn't + if ($this->appConfig->hasKey($appId, $entry->getRename()) + && !$this->appConfig->hasKey($appId, $entry->getKey())) { + try { + $this->migrateAppConfigValue($appId, $entry); + } catch (TypeConflictException $e) { + $this->logger->error('could not migrate AppConfig value', ['appId' => $appId, 'entry' => $entry, 'exception' => $e]); + continue; + } + } + + // we only delete previous config value if migration went fine. + $this->appConfig->deleteKey($appId, $entry->getRename()); + } + } + + /** + * Get details from lexicon related to UserConfig and search for entries with rename to initiate + * a migration to new config key + */ + private function migrateUserConfigKeys(string $appId): void { + $lexicon = $this->userConfig->getConfigDetailsFromLexicon($appId); + + // we store a list of set keys to compare with any 'copyFrom' + $keys = []; + foreach ($lexicon['entries'] as $entry) { + $keys[] = $entry->getKey(); + } + + foreach ($lexicon['entries'] as $entry) { + // only interested in keys with rename set + if ($entry->getRename() === null) { + continue; + } + + if (in_array($entry->getRename(), $keys, true)) { + $this->logger->error('rename value should not exist as a valid key within Lexicon'); + continue; + } + + foreach ($this->userConfig->getValuesByUsers($appId, $entry->getRename()) as $userId => $value) { + if ($this->userConfig->hasKey($userId, $appId, $entry->getKey())) { + continue; + } + + try { + $this->migrateUserConfigValue($userId, $appId, $entry); + } catch (TypeConflictException $e) { + $this->logger->error('could not migrate UserConfig value', ['userId' => $userId, 'appId' => $appId, 'entry' => $entry, 'exception' => $e]); + continue; + } + + $this->userConfig->deleteUserConfig($userId, $appId, $entry->getRename()); + } + } + } + + + /** + * converting value from rename to the new key + * + * @throws TypeConflictException if previous value does not fit the expected type + */ + private function migrateAppConfigValue(string $appId, ConfigLexiconEntry $entry): void { + $value = $this->appConfig->getValueMixed($appId, $entry->getRename(), lazy: null); + switch ($entry->getValueType()) { + case ValueType::STRING: + $this->appConfig->setValueString($appId, $entry->getKey(), $value); + return; + + case ValueType::INT: + $this->appConfig->setValueInt($appId, $entry->getKey(), $this->convertToInt($value)); + return; + + case ValueType::FLOAT: + $this->appConfig->setValueFloat($appId, $entry->getKey(), $this->convertToFloat($value)); + return; + + case ValueType::BOOL: + $this->appConfig->setValueBool($appId, $entry->getKey(), $this->convertToBool($value, $entry)); + return; + + case ValueType::ARRAY: + $this->appConfig->setValueArray($appId, $entry->getKey(), $this->convertToArray($value)); + return; + } + } + + /** + * converting value from rename to the new key + * + * @throws TypeConflictException if previous value does not fit the expected type + */ + private function migrateUserConfigValue(string $userId, string $appId, ConfigLexiconEntry $entry): void { + $value = $this->userConfig->getValueMixed($userId, $appId, $entry->getRename(), lazy: null); + switch ($entry->getValueType()) { + case ValueType::STRING: + $this->userConfig->setValueString($userId, $appId, $entry->getKey(), $value); + return; + + case ValueType::INT: + $this->userConfig->setValueInt($userId, $appId, $entry->getKey(), $this->convertToInt($value)); + return; + + case ValueType::FLOAT: + $this->userConfig->setValueFloat($userId, $appId, $entry->getKey(), $this->convertToFloat($value)); + return; + + case ValueType::BOOL: + $this->userConfig->setValueBool($userId, $appId, $entry->getKey(), $this->convertToBool($value, $entry)); + return; + + case ValueType::ARRAY: + $this->userConfig->setValueArray($userId, $appId, $entry->getKey(), $this->convertToArray($value)); + return; + } + } + + public function convertToInt(string $value): int { + if ($value !== ((string)((int)$value))) { + throw new TypeConflictException('Value is not an integer'); + } + + return (int)$value; + } + + public function convertToFloat(string $value): float { + if ($value !== ((string)((float)$value))) { + throw new TypeConflictException('Value is not a float'); + } + + return (float)$value; + } + + public function convertToBool(string $value, ?ConfigLexiconEntry $entry = null): bool { + if (in_array(strtolower($value), ['true', '1', 'on', 'yes'])) { + $valueBool = true; + } elseif (in_array(strtolower($value), ['false', '0', 'off', 'no'])) { + $valueBool = false; + } else { + throw new TypeConflictException('Value cannot be converted to boolean'); + } + if ($entry?->hasOption(ConfigLexiconEntry::RENAME_INVERT_BOOLEAN) === true) { + $valueBool = !$valueBool; + } + + return $valueBool; + } + + public function convertToArray(string $value): array { + try { + $valueArray = json_decode($value, true, flags: JSON_THROW_ON_ERROR); + } catch (JsonException) { + throw new TypeConflictException('Value is not a valid json'); + } + if (!is_array($valueArray)) { + throw new TypeConflictException('Value is not an array'); + } + + return $valueArray; + } +} diff --git a/lib/private/Config/UserConfig.php b/lib/private/Config/UserConfig.php index 77a86a5e1c7..810d15bdbd3 100644 --- a/lib/private/Config/UserConfig.php +++ b/lib/private/Config/UserConfig.php @@ -62,8 +62,9 @@ class UserConfig implements IUserConfig { private array $fastLoaded = []; /** @var array<string, boolean> ['user_id' => bool] */ private array $lazyLoaded = []; - /** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ + /** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, aliases: array<array-key, string>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ private array $configLexiconDetails = []; + private bool $ignoreLexiconAliases = false; public function __construct( protected IDBConnection $connection, @@ -150,6 +151,7 @@ class UserConfig implements IUserConfig { public function hasKey(string $userId, string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if ($lazy === null) { $appCache = $this->getValues($userId, $app); @@ -178,6 +180,7 @@ class UserConfig implements IUserConfig { public function isSensitive(string $userId, string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if (!isset($this->valueDetails[$userId][$app][$key])) { throw new UnknownKeyException('unknown config key'); @@ -201,6 +204,7 @@ class UserConfig implements IUserConfig { public function isIndexed(string $userId, string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if (!isset($this->valueDetails[$userId][$app][$key])) { throw new UnknownKeyException('unknown config key'); @@ -222,6 +226,8 @@ class UserConfig implements IUserConfig { * @since 31.0.0 */ public function isLazy(string $userId, string $app, string $key): bool { + $this->matchAndApplyLexiconDefinition($userId, $app, $key); + // there is a huge probability the non-lazy config are already loaded // meaning that we can start by only checking if a current non-lazy key exists if ($this->hasKey($userId, $app, $key, false)) { @@ -349,6 +355,7 @@ class UserConfig implements IUserConfig { ?array $userIds = null, ): array { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); $qb = $this->connection->getQueryBuilder(); $qb->select('userid', 'configvalue', 'type') @@ -464,6 +471,7 @@ class UserConfig implements IUserConfig { */ private function searchUsersByTypedValue(string $app, string $key, string|array $value, bool $caseInsensitive = false): Generator { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); $qb = $this->connection->getQueryBuilder(); $qb->from('preferences'); @@ -541,6 +549,7 @@ class UserConfig implements IUserConfig { string $default = '', ?bool $lazy = false, ): string { + $this->matchAndApplyLexiconDefinition($userId, $app, $key); try { $lazy ??= $this->isLazy($userId, $app, $key); } catch (UnknownKeyException) { @@ -710,6 +719,7 @@ class UserConfig implements IUserConfig { ValueType $type, ): string { $this->assertParams($userId, $app, $key); + $origKey = $key; if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, default: $default)) { // returns default if strictness of lexicon is set to WARNING (block and report) return $default; @@ -746,6 +756,16 @@ class UserConfig implements IUserConfig { } $this->decryptSensitiveValue($userId, $app, $key, $value); + + // in case the key was modified while running matchAndApplyLexiconDefinition() we are + // interested to check options in case a modification of the value is needed + if ($origKey !== $key) { + $lexiconEntry = $this->getLexiconEntry($app, $key); + if ($type === ValueType::BOOL && $lexiconEntry?->hasOption(ConfigLexiconEntry::RENAME_INVERT_BOOLEAN)) { + $value = (in_array(strtolower($value), ['1', 'true', 'yes', 'on'])) ? '0' : '1'; + } + } + return $value; } @@ -764,6 +784,7 @@ class UserConfig implements IUserConfig { public function getValueType(string $userId, string $app, string $key, ?bool $lazy = null): ValueType { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if (!isset($this->valueDetails[$userId][$app][$key]['type'])) { throw new UnknownKeyException('unknown config key'); @@ -788,6 +809,7 @@ class UserConfig implements IUserConfig { public function getValueFlags(string $userId, string $app, string $key, bool $lazy = false): int { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if (!isset($this->valueDetails[$userId][$app][$key])) { throw new UnknownKeyException('unknown config key'); @@ -1197,8 +1219,8 @@ class UserConfig implements IUserConfig { public function updateType(string $userId, string $app, string $key, ValueType $type = ValueType::MIXED): bool { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); - // confirm key exists - $this->isLazy($userId, $app, $key); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); + $this->isLazy($userId, $app, $key); // confirm key exists $update = $this->connection->getQueryBuilder(); $update->update('preferences') @@ -1227,6 +1249,7 @@ class UserConfig implements IUserConfig { public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); try { if ($sensitive === $this->isSensitive($userId, $app, $key, null)) { @@ -1282,6 +1305,8 @@ class UserConfig implements IUserConfig { */ public function updateGlobalSensitive(string $app, string $key, bool $sensitive): void { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); + foreach (array_keys($this->getValuesByUsers($app, $key)) as $userId) { try { $this->updateSensitive($userId, $app, $key, $sensitive); @@ -1311,6 +1336,7 @@ class UserConfig implements IUserConfig { public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); try { if ($indexed === $this->isIndexed($userId, $app, $key, null)) { @@ -1366,6 +1392,8 @@ class UserConfig implements IUserConfig { */ public function updateGlobalIndexed(string $app, string $key, bool $indexed): void { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); + foreach (array_keys($this->getValuesByUsers($app, $key)) as $userId) { try { $this->updateIndexed($userId, $app, $key, $indexed); @@ -1392,6 +1420,7 @@ class UserConfig implements IUserConfig { public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); try { if ($lazy === $this->isLazy($userId, $app, $key)) { @@ -1426,6 +1455,7 @@ class UserConfig implements IUserConfig { */ public function updateGlobalLazy(string $app, string $key, bool $lazy): void { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); $update = $this->connection->getQueryBuilder(); $update->update('preferences') @@ -1451,6 +1481,8 @@ class UserConfig implements IUserConfig { public function getDetails(string $userId, string $app, string $key): array { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); + $lazy = $this->isLazy($userId, $app, $key); if ($lazy) { @@ -1498,6 +1530,8 @@ class UserConfig implements IUserConfig { */ public function deleteUserConfig(string $userId, string $app, string $key): void { $this->assertParams($userId, $app, $key); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); + $qb = $this->connection->getQueryBuilder(); $qb->delete('preferences') ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId))) @@ -1520,6 +1554,8 @@ class UserConfig implements IUserConfig { */ public function deleteKey(string $app, string $key): void { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); + $qb = $this->connection->getQueryBuilder(); $qb->delete('preferences') ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app))) @@ -1538,6 +1574,7 @@ class UserConfig implements IUserConfig { */ public function deleteApp(string $app): void { $this->assertParams('', $app, allowEmptyUser: true); + $qb = $this->connection->getQueryBuilder(); $qb->delete('preferences') ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app))); @@ -1830,7 +1867,8 @@ class UserConfig implements IUserConfig { } /** - * 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 UnknownKeyException * @throws TypeConflictException @@ -1839,17 +1877,27 @@ class UserConfig implements IUserConfig { private function matchAndApplyLexiconDefinition( string $userId, string $app, - string $key, - bool &$lazy, - ValueType &$type, + string &$key, + ?bool &$lazy = null, + ValueType &$type = ValueType::MIXED, int &$flags = 0, string &$default = '', ): bool { $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 user 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 */ $configValue = $configDetails['entries'][$key]; if ($type === ValueType::MIXED) { @@ -1934,24 +1982,42 @@ class UserConfig implements IUserConfig { * 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<array-key, ConfigLexiconEntry>, aliases: array<array-key, 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?->getUserConfigs() ?? [] 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 ]; } 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; + } } diff --git a/lib/private/Repair/ConfigKeyMigration.php b/lib/private/Repair/ConfigKeyMigration.php new file mode 100644 index 00000000000..38eb994ef3d --- /dev/null +++ b/lib/private/Repair/ConfigKeyMigration.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Repair; + +use OC\Config\ConfigManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class ConfigKeyMigration implements IRepairStep { + public function __construct( + private ConfigManager $configManager, + ) { + } + + public function getName(): string { + return 'Initiate config keys migration'; + } + + public function run(IOutput $output) { + $this->configManager->migrateConfigLexiconKeys(); + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index 545ceacbe81..47c62b15235 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -573,6 +573,7 @@ class Server extends ServerContainer implements IServerContainer { return new \OC\SystemConfig($config); }); + $this->registerAlias(IAppManager::class, AppManager::class); $this->registerAlias(IAppConfig::class, \OC\AppConfig::class); $this->registerAlias(IUserConfig::class, \OC\Config\UserConfig::class); @@ -783,21 +784,6 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerAlias(ITempManager::class, TempManager::class); - - $this->registerService(AppManager::class, function (ContainerInterface $c) { - // TODO: use auto-wiring - return new \OC\App\AppManager( - $c->get(IUserSession::class), - $c->get(\OCP\IConfig::class), - $c->get(IGroupManager::class), - $c->get(ICacheFactory::class), - $c->get(IEventDispatcher::class), - $c->get(LoggerInterface::class), - $c->get(ServerVersion::class), - ); - }); - $this->registerAlias(IAppManager::class, AppManager::class); - $this->registerAlias(IDateTimeZone::class, DateTimeZone::class); $this->registerService(IDateTimeFormatter::class, function (Server $c) { diff --git a/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php b/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php index d7d781d8e26..d0d9b4cbd23 100644 --- a/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php +++ b/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php @@ -17,6 +17,9 @@ use NCU\Config\ValueType; * @experimental 31.0.0 */ class ConfigLexiconEntry { + /** @experimental 32.0.0 */ + public const RENAME_INVERT_BOOLEAN = 1; + private string $definition = ''; private ?string $default = null; @@ -26,6 +29,7 @@ class ConfigLexiconEntry { * @param string $definition optional description of config key available when using occ command * @param bool $lazy set config value as lazy * @param int $flags set flags + * @param string|null $rename previous config key to migrate config value from * @param bool $deprecated set config key as deprecated * * @experimental 31.0.0 @@ -40,6 +44,8 @@ class ConfigLexiconEntry { private readonly bool $lazy = false, private readonly int $flags = 0, private readonly bool $deprecated = false, + private readonly ?string $rename = null, + private readonly int $options = 0, ) { /** @psalm-suppress UndefinedClass */ if (\OC::$CLI) { // only store definition if ran from CLI @@ -199,6 +205,25 @@ class ConfigLexiconEntry { } /** + * should be called/used only during migration/upgrade. + * link to an old config key. + * + * @return string|null not NULL if value can be imported from a previous key + * @experimental 32.0.0 + */ + public function getRename(): ?string { + return $this->rename; + } + + /** + * @experimental 32.0.0 + * @return bool TRUE if $option was set during the creation of the entry. + */ + public function hasOption(int $option): bool { + return (($option & $this->options) !== 0); + } + + /** * returns if config key is set as deprecated * * @return bool TRUE if config si deprecated diff --git a/tests/Core/Command/Config/App/DeleteConfigTest.php b/tests/Core/Command/Config/App/DeleteConfigTest.php index 1e44c2aafe6..6f7bfbf3a7c 100644 --- a/tests/Core/Command/Config/App/DeleteConfigTest.php +++ b/tests/Core/Command/Config/App/DeleteConfigTest.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace Tests\Core\Command\Config\App; +use OC\Config\ConfigManager; use OC\Core\Command\Config\App\DeleteConfig; use OCP\IAppConfig; use PHPUnit\Framework\MockObject\MockObject; @@ -19,6 +20,7 @@ use Test\TestCase; class DeleteConfigTest extends TestCase { protected IAppConfig&MockObject $appConfig; + protected ConfigManager&MockObject $configManager; protected InputInterface&MockObject $consoleInput; protected OutputInterface&MockObject $consoleOutput; protected Command $command; @@ -27,10 +29,11 @@ class DeleteConfigTest extends TestCase { parent::setUp(); $this->appConfig = $this->createMock(IAppConfig::class); + $this->configManager = $this->createMock(ConfigManager::class); $this->consoleInput = $this->createMock(InputInterface::class); $this->consoleOutput = $this->createMock(OutputInterface::class); - $this->command = new DeleteConfig($this->appConfig); + $this->command = new DeleteConfig($this->appConfig, $this->configManager); } diff --git a/tests/Core/Command/Config/App/GetConfigTest.php b/tests/Core/Command/Config/App/GetConfigTest.php index 89a75c0b527..63a02f263d0 100644 --- a/tests/Core/Command/Config/App/GetConfigTest.php +++ b/tests/Core/Command/Config/App/GetConfigTest.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace Tests\Core\Command\Config\App; +use OC\Config\ConfigManager; use OC\Core\Command\Config\App\GetConfig; use OCP\Exceptions\AppConfigUnknownKeyException; use OCP\IAppConfig; @@ -20,6 +21,7 @@ use Test\TestCase; class GetConfigTest extends TestCase { protected IAppConfig&MockObject $appConfig; + protected ConfigManager&MockObject $configManager; protected InputInterface&MockObject $consoleInput; protected OutputInterface&MockObject $consoleOutput; protected Command $command; @@ -28,10 +30,11 @@ class GetConfigTest extends TestCase { parent::setUp(); $this->appConfig = $this->createMock(IAppConfig::class); + $this->configManager = $this->createMock(ConfigManager::class); $this->consoleInput = $this->createMock(InputInterface::class); $this->consoleOutput = $this->createMock(OutputInterface::class); - $this->command = new GetConfig($this->appConfig); + $this->command = new GetConfig($this->appConfig, $this->configManager); } diff --git a/tests/Core/Command/Config/App/SetConfigTest.php b/tests/Core/Command/Config/App/SetConfigTest.php index 099471228b4..596439d85a8 100644 --- a/tests/Core/Command/Config/App/SetConfigTest.php +++ b/tests/Core/Command/Config/App/SetConfigTest.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace Tests\Core\Command\Config\App; use OC\AppConfig; +use OC\Config\ConfigManager; use OC\Core\Command\Config\App\SetConfig; use OCP\Exceptions\AppConfigUnknownKeyException; use OCP\IAppConfig; @@ -21,6 +22,7 @@ use Test\TestCase; class SetConfigTest extends TestCase { protected IAppConfig&MockObject $appConfig; + protected ConfigManager&MockObject $configManager; protected InputInterface&MockObject $consoleInput; protected OutputInterface&MockObject $consoleOutput; protected Command $command; @@ -29,10 +31,11 @@ class SetConfigTest extends TestCase { parent::setUp(); $this->appConfig = $this->createMock(AppConfig::class); + $this->configManager = $this->createMock(ConfigManager::class); $this->consoleInput = $this->createMock(InputInterface::class); $this->consoleOutput = $this->createMock(OutputInterface::class); - $this->command = new SetConfig($this->appConfig); + $this->command = new SetConfig($this->appConfig, $this->configManager); } diff --git a/tests/Core/Command/Config/ListConfigsTest.php b/tests/Core/Command/Config/ListConfigsTest.php index 12a6e6f1cf8..216a6133579 100644 --- a/tests/Core/Command/Config/ListConfigsTest.php +++ b/tests/Core/Command/Config/ListConfigsTest.php @@ -7,6 +7,7 @@ namespace Tests\Core\Command\Config; +use OC\Config\ConfigManager; use OC\Core\Command\Config\ListConfigs; use OC\SystemConfig; use OCP\IAppConfig; @@ -20,6 +21,8 @@ class ListConfigsTest extends TestCase { protected $appConfig; /** @var \PHPUnit\Framework\MockObject\MockObject */ protected $systemConfig; + /** @var \PHPUnit\Framework\MockObject\MockObject */ + protected $configManager; /** @var \PHPUnit\Framework\MockObject\MockObject */ protected $consoleInput; @@ -38,12 +41,17 @@ class ListConfigsTest extends TestCase { $appConfig = $this->appConfig = $this->getMockBuilder(IAppConfig::class) ->disableOriginalConstructor() ->getMock(); + $configManager = $this->configManager = $this->getMockBuilder(ConfigManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMockBuilder(InputInterface::class)->getMock(); $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); /** @var \OC\SystemConfig $systemConfig */ /** @var \OCP\IAppConfig $appConfig */ - $this->command = new ListConfigs($systemConfig, $appConfig); + /** @var ConfigManager $configManager */ + $this->command = new ListConfigs($systemConfig, $appConfig, $configManager); } public static function listData(): array { diff --git a/tests/lib/DB/AdapterTest.php b/tests/lib/DB/AdapterTest.php index 99b7cf4e099..1b8c38c0edc 100644 --- a/tests/lib/DB/AdapterTest.php +++ b/tests/lib/DB/AdapterTest.php @@ -14,7 +14,7 @@ class AdapterTest extends TestCase { public function setUp(): void { $this->connection = \OC::$server->getDatabaseConnection(); - $this->appId = uniqid('test_db_adapter', true); + $this->appId = substr(uniqid('test_db_adapter', true), 0, 32); } public function tearDown(): void { |