mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 08:44:07 -04:00
Merge pull request #52832 from nextcloud/feat/noid/lexicon-migrate-keys
feat(lexicon): migrate config key/value
This commit is contained in:
commit
d161e07cf7
23 changed files with 605 additions and 126 deletions
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1190,6 +1190,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',
|
||||
|
|
@ -1901,6 +1902,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',
|
||||
|
|
|
|||
|
|
@ -1231,6 +1231,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',
|
||||
|
|
@ -1942,6 +1943,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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -27,6 +28,7 @@ use OCP\INavigationManager;
|
|||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Server;
|
||||
use OCP\ServerVersion;
|
||||
use OCP\Settings\IManager as ISettingsManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -82,12 +84,13 @@ class AppManager implements IAppManager {
|
|||
private IEventDispatcher $dispatcher,
|
||||
private LoggerInterface $logger,
|
||||
private ServerVersion $serverVersion,
|
||||
private ConfigManager $configManager,
|
||||
) {
|
||||
}
|
||||
|
||||
private function getNavigationManager(): INavigationManager {
|
||||
if ($this->navigationManager === null) {
|
||||
$this->navigationManager = \OCP\Server::get(INavigationManager::class);
|
||||
$this->navigationManager = Server::get(INavigationManager::class);
|
||||
}
|
||||
return $this->navigationManager;
|
||||
}
|
||||
|
|
@ -113,7 +116,7 @@ class AppManager implements IAppManager {
|
|||
if (!$this->config->getSystemValueBool('installed', false)) {
|
||||
throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
|
||||
}
|
||||
$this->appConfig = \OCP\Server::get(AppConfig::class);
|
||||
$this->appConfig = Server::get(AppConfig::class);
|
||||
return $this->appConfig;
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +127,7 @@ class AppManager implements IAppManager {
|
|||
if (!$this->config->getSystemValueBool('installed', false)) {
|
||||
throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
|
||||
}
|
||||
$this->urlGenerator = \OCP\Server::get(IURLGenerator::class);
|
||||
$this->urlGenerator = Server::get(IURLGenerator::class);
|
||||
return $this->urlGenerator;
|
||||
}
|
||||
|
||||
|
|
@ -459,7 +462,7 @@ class AppManager implements IAppManager {
|
|||
]);
|
||||
}
|
||||
|
||||
$coordinator = \OCP\Server::get(Coordinator::class);
|
||||
$coordinator = Server::get(Coordinator::class);
|
||||
$coordinator->bootApp($app);
|
||||
|
||||
$eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it");
|
||||
|
|
@ -568,6 +571,8 @@ class AppManager implements IAppManager {
|
|||
ManagerEvent::EVENT_APP_ENABLE, $appId
|
||||
));
|
||||
$this->clearAppsCache();
|
||||
|
||||
$this->configManager->migrateConfigLexiconKeys($appId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -626,6 +631,8 @@ class AppManager implements IAppManager {
|
|||
ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
|
||||
));
|
||||
$this->clearAppsCache();
|
||||
|
||||
$this->configManager->migrateConfigLexiconKeys($appId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use NCU\Config\Lexicon\ConfigLexiconEntry;
|
|||
use NCU\Config\Lexicon\ConfigLexiconStrictness;
|
||||
use NCU\Config\Lexicon\IConfigLexicon;
|
||||
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 +25,7 @@ use OCP\IAppConfig;
|
|||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Server;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -59,8 +61,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<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;
|
||||
|
||||
/** @var ?array<string, string> */
|
||||
private ?array $appVersionsCache = null;
|
||||
|
|
@ -117,6 +120,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 +146,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 +167,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 +292,7 @@ class AppConfig implements IAppConfig {
|
|||
): string {
|
||||
try {
|
||||
$lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
|
||||
} catch (AppConfigUnknownKeyException $e) {
|
||||
} catch (AppConfigUnknownKeyException) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
|
|
@ -429,6 +437,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 +478,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;
|
||||
}
|
||||
|
||||
|
|
@ -863,7 +880,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 +923,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 +983,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 +1019,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 +1107,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 +1316,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 +1396,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 +1580,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 +1589,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 +1603,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 +1676,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,6 +1702,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
|
||||
*
|
||||
|
|
|
|||
250
lib/private/Config/ConfigManager.php
Normal file
250
lib/private/Config/ConfigManager.php
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
<?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 OCP\Server;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* tools to maintains configurations
|
||||
*
|
||||
* @since 32.0.0
|
||||
*/
|
||||
class ConfigManager {
|
||||
/** @var AppConfig|null $appConfig */
|
||||
private ?IAppConfig $appConfig = null;
|
||||
/** @var UserConfig|null $userConfig */
|
||||
private ?IUserConfig $userConfig = null;
|
||||
|
||||
public function __construct(
|
||||
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
|
||||
* @since 32.0.0
|
||||
* @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');
|
||||
$appManager = Server::get(IAppManager::class);
|
||||
foreach ($appManager->getEnabledApps() as $app) {
|
||||
$this->migrateConfigLexiconKeys($app);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loadConfigServices();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* config services cannot be load at __construct() or install will fail
|
||||
*/
|
||||
private function loadConfigServices(): void {
|
||||
if ($this->appConfig === null) {
|
||||
$this->appConfig = Server::get(IAppConfig::class);
|
||||
}
|
||||
if ($this->userConfig === null) {
|
||||
$this->userConfig = Server::get(IUserConfig::class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
foreach ($lexicon['entries'] as $entry) {
|
||||
// only interested in entries with rename set
|
||||
if ($entry->getRename() === null) {
|
||||
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);
|
||||
foreach ($lexicon['entries'] as $entry) {
|
||||
// only interested in keys with rename set
|
||||
if ($entry->getRename() === null) {
|
||||
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 (!is_numeric($value) || (float)$value <> (int)$value) {
|
||||
throw new TypeConflictException('Value is not an integer');
|
||||
}
|
||||
|
||||
return (int)$value;
|
||||
}
|
||||
|
||||
public function convertToFloat(string $value): float {
|
||||
if (!is_numeric($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;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
|
|||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Server;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -62,8 +63,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<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;
|
||||
|
||||
public function __construct(
|
||||
protected IDBConnection $connection,
|
||||
|
|
@ -150,6 +152,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 +181,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 +205,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 +227,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 +356,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 +472,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 +550,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 +720,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 +757,15 @@ 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
|
||||
// ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
|
||||
if ($origKey !== $key && $type === ValueType::BOOL) {
|
||||
$configManager = Server::get(ConfigManager::class);
|
||||
$value = ($configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
|
||||
}
|
||||
|
||||
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');
|
||||
|
|
@ -1202,8 +1224,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')
|
||||
|
|
@ -1232,6 +1254,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)) {
|
||||
|
|
@ -1287,6 +1310,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);
|
||||
|
|
@ -1316,6 +1341,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)) {
|
||||
|
|
@ -1371,6 +1397,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);
|
||||
|
|
@ -1397,6 +1425,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)) {
|
||||
|
|
@ -1431,6 +1460,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')
|
||||
|
|
@ -1456,6 +1486,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) {
|
||||
|
|
@ -1503,6 +1535,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)))
|
||||
|
|
@ -1525,6 +1559,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)))
|
||||
|
|
@ -1543,6 +1579,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)));
|
||||
|
|
@ -1835,7 +1872,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
|
||||
|
|
@ -1844,17 +1882,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) {
|
||||
|
|
@ -1939,24 +1987,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<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?->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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
lib/private/Repair/ConfigKeyMigration.php
Normal file
29
lib/private/Repair/ConfigKeyMigration.php
Normal file
|
|
@ -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 'Migrate config keys';
|
||||
}
|
||||
|
||||
public function run(IOutput $output) {
|
||||
$this->configManager->migrateConfigLexiconKeys();
|
||||
}
|
||||
}
|
||||
|
|
@ -572,6 +572,7 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
|
||||
$this->registerAlias(IAppConfig::class, \OC\AppConfig::class);
|
||||
$this->registerAlias(IUserConfig::class, \OC\Config\UserConfig::class);
|
||||
$this->registerAlias(IAppManager::class, AppManager::class);
|
||||
|
||||
$this->registerService(IFactory::class, function (Server $c) {
|
||||
return new \OC\L10N\Factory(
|
||||
|
|
@ -780,21 +781,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) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
use OC\App\DependencyAnalyzer;
|
||||
use OC\App\Platform;
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\Config\ConfigManager;
|
||||
use OC\DB\MigrationService;
|
||||
use OC\Installer;
|
||||
use OC\Repair;
|
||||
|
|
@ -211,7 +212,7 @@ class OC_App {
|
|||
array $groups = []) {
|
||||
// Check if app is already downloaded
|
||||
/** @var Installer $installer */
|
||||
$installer = \OCP\Server::get(Installer::class);
|
||||
$installer = Server::get(Installer::class);
|
||||
$isDownloaded = $installer->isDownloaded($appId);
|
||||
|
||||
if (!$isDownloaded) {
|
||||
|
|
@ -246,7 +247,7 @@ class OC_App {
|
|||
}
|
||||
}
|
||||
|
||||
\OCP\Server::get(LoggerInterface::class)->error('No application directories are marked as writable.', ['app' => 'core']);
|
||||
Server::get(LoggerInterface::class)->error('No application directories are marked as writable.', ['app' => 'core']);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -310,7 +311,7 @@ class OC_App {
|
|||
* @param string $appId
|
||||
* @param bool $refreshAppPath should be set to true only during install/upgrade
|
||||
* @return string|false
|
||||
* @deprecated 11.0.0 use \OCP\Server::get(IAppManager)->getAppPath()
|
||||
* @deprecated 11.0.0 use Server::get(IAppManager)->getAppPath()
|
||||
*/
|
||||
public static function getAppPath(string $appId, bool $refreshAppPath = false) {
|
||||
$appId = self::cleanAppId($appId);
|
||||
|
|
@ -349,7 +350,7 @@ class OC_App {
|
|||
*/
|
||||
public static function getAppVersionByPath(string $path): string {
|
||||
$infoFile = $path . '/appinfo/info.xml';
|
||||
$appData = \OCP\Server::get(IAppManager::class)->getAppInfoByPath($infoFile);
|
||||
$appData = Server::get(IAppManager::class)->getAppInfoByPath($infoFile);
|
||||
return $appData['version'] ?? '';
|
||||
}
|
||||
|
||||
|
|
@ -391,7 +392,7 @@ class OC_App {
|
|||
* @deprecated 20.0.0 Please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface
|
||||
*/
|
||||
public static function registerLogIn(array $entry) {
|
||||
\OCP\Server::get(LoggerInterface::class)->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface');
|
||||
Server::get(LoggerInterface::class)->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface');
|
||||
self::$altLogin[] = $entry;
|
||||
}
|
||||
|
||||
|
|
@ -400,11 +401,11 @@ class OC_App {
|
|||
*/
|
||||
public static function getAlternativeLogIns(): array {
|
||||
/** @var Coordinator $bootstrapCoordinator */
|
||||
$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
|
||||
$bootstrapCoordinator = Server::get(Coordinator::class);
|
||||
|
||||
foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
|
||||
if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
|
||||
\OCP\Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
|
||||
Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
|
||||
'option' => $registration->getService(),
|
||||
'interface' => IAlternativeLogin::class,
|
||||
'app' => $registration->getAppId(),
|
||||
|
|
@ -414,9 +415,9 @@ class OC_App {
|
|||
|
||||
try {
|
||||
/** @var IAlternativeLogin $provider */
|
||||
$provider = \OCP\Server::get($registration->getService());
|
||||
$provider = Server::get($registration->getService());
|
||||
} catch (ContainerExceptionInterface $e) {
|
||||
\OCP\Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.',
|
||||
Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.',
|
||||
[
|
||||
'exception' => $e,
|
||||
'option' => $registration->getService(),
|
||||
|
|
@ -433,7 +434,7 @@ class OC_App {
|
|||
'class' => $provider->getClass(),
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
\OCP\Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.',
|
||||
Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.',
|
||||
[
|
||||
'exception' => $e,
|
||||
'option' => $registration->getService(),
|
||||
|
|
@ -452,7 +453,7 @@ class OC_App {
|
|||
* @deprecated 31.0.0 Use IAppManager::getAllAppsInAppsFolders instead
|
||||
*/
|
||||
public static function getAllApps(): array {
|
||||
return \OCP\Server::get(IAppManager::class)->getAllAppsInAppsFolders();
|
||||
return Server::get(IAppManager::class)->getAllAppsInAppsFolders();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -461,7 +462,7 @@ class OC_App {
|
|||
* @deprecated 32.0.0 Use \OCP\Support\Subscription\IRegistry::delegateGetSupportedApps instead
|
||||
*/
|
||||
public function getSupportedApps(): array {
|
||||
$subscriptionRegistry = \OCP\Server::get(\OCP\Support\Subscription\IRegistry::class);
|
||||
$subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
|
||||
$supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
|
||||
return $supportedApps;
|
||||
}
|
||||
|
|
@ -486,12 +487,12 @@ class OC_App {
|
|||
if (!in_array($app, $blacklist)) {
|
||||
$info = $appManager->getAppInfo($app, false, $langCode);
|
||||
if (!is_array($info)) {
|
||||
\OCP\Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
|
||||
Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($info['name'])) {
|
||||
\OCP\Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
|
||||
Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -558,7 +559,7 @@ class OC_App {
|
|||
|
||||
public static function shouldUpgrade(string $app): bool {
|
||||
$versions = self::getAppVersions();
|
||||
$currentVersion = \OCP\Server::get(\OCP\App\IAppManager::class)->getAppVersion($app);
|
||||
$currentVersion = Server::get(\OCP\App\IAppManager::class)->getAppVersion($app);
|
||||
if ($currentVersion && isset($versions[$app])) {
|
||||
$installedVersion = $versions[$app];
|
||||
if (!version_compare($currentVersion, $installedVersion, '=')) {
|
||||
|
|
@ -647,7 +648,7 @@ class OC_App {
|
|||
* @deprecated 32.0.0 Use IAppManager::getAppInstalledVersions or IAppConfig::getAppInstalledVersions instead
|
||||
*/
|
||||
public static function getAppVersions(): array {
|
||||
return \OCP\Server::get(IAppConfig::class)->getAppInstalledVersions();
|
||||
return Server::get(IAppConfig::class)->getAppInstalledVersions();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -665,13 +666,13 @@ class OC_App {
|
|||
}
|
||||
|
||||
if (is_file($appPath . '/appinfo/database.xml')) {
|
||||
\OCP\Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
|
||||
Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
|
||||
return false;
|
||||
}
|
||||
|
||||
\OC::$server->getAppManager()->clearAppsCache();
|
||||
$l = \OC::$server->getL10N('core');
|
||||
$appData = \OCP\Server::get(\OCP\App\IAppManager::class)->getAppInfo($appId, false, $l->getLanguageCode());
|
||||
$appData = Server::get(\OCP\App\IAppManager::class)->getAppInfo($appId, false, $l->getLanguageCode());
|
||||
|
||||
$ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
|
||||
$ignoreMax = in_array($appId, $ignoreMaxApps, true);
|
||||
|
|
@ -711,9 +712,13 @@ class OC_App {
|
|||
|
||||
self::setAppTypes($appId);
|
||||
|
||||
$version = \OCP\Server::get(\OCP\App\IAppManager::class)->getAppVersion($appId);
|
||||
$version = Server::get(\OCP\App\IAppManager::class)->getAppVersion($appId);
|
||||
\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
|
||||
|
||||
// migrate eventual new config keys in the process
|
||||
/** @psalm-suppress InternalMethod */
|
||||
Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId);
|
||||
|
||||
\OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
|
||||
\OC::$server->get(IEventDispatcher::class)->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
|
||||
ManagerEvent::EVENT_APP_UPDATE, $appId
|
||||
|
|
|
|||
|
|
@ -16,15 +16,13 @@ use OCP\EventDispatcher\Event;
|
|||
* @since 27.0.0
|
||||
*/
|
||||
class AppUpdateEvent extends Event {
|
||||
private string $appId;
|
||||
|
||||
/**
|
||||
* @since 27.0.0
|
||||
*/
|
||||
public function __construct(string $appId) {
|
||||
public function __construct(
|
||||
private readonly string $appId,
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->appId = $appId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -198,6 +204,25 @@ class ConfigLexiconEntry {
|
|||
return (($flag & $this->getFlags()) === $flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ namespace Test\App;
|
|||
|
||||
use OC\App\AppManager;
|
||||
use OC\AppConfig;
|
||||
use OC\Config\ConfigManager;
|
||||
use OCP\App\AppPathNotFoundException;
|
||||
use OCP\App\Events\AppDisableEvent;
|
||||
use OCP\App\Events\AppEnableEvent;
|
||||
|
|
@ -36,10 +37,7 @@ use Test\TestCase;
|
|||
* @package Test\App
|
||||
*/
|
||||
class AppManagerTest extends TestCase {
|
||||
/**
|
||||
* @return AppConfig|MockObject
|
||||
*/
|
||||
protected function getAppConfig() {
|
||||
protected function getAppConfig(): AppConfig&MockObject {
|
||||
$appConfig = [];
|
||||
$config = $this->createMock(AppConfig::class);
|
||||
|
||||
|
|
@ -86,33 +84,17 @@ class AppManagerTest extends TestCase {
|
|||
return $config;
|
||||
}
|
||||
|
||||
/** @var IUserSession|MockObject */
|
||||
protected $userSession;
|
||||
|
||||
/** @var IConfig|MockObject */
|
||||
private $config;
|
||||
|
||||
/** @var IGroupManager|MockObject */
|
||||
protected $groupManager;
|
||||
|
||||
/** @var AppConfig|MockObject */
|
||||
protected $appConfig;
|
||||
|
||||
/** @var ICache|MockObject */
|
||||
protected $cache;
|
||||
|
||||
/** @var ICacheFactory|MockObject */
|
||||
protected $cacheFactory;
|
||||
|
||||
/** @var IEventDispatcher|MockObject */
|
||||
protected $eventDispatcher;
|
||||
|
||||
/** @var LoggerInterface|MockObject */
|
||||
protected $logger;
|
||||
|
||||
protected IUserSession&MockObject $userSession;
|
||||
private IConfig&MockObject $config;
|
||||
protected IGroupManager&MockObject $groupManager;
|
||||
protected AppConfig&MockObject $appConfig;
|
||||
protected ICache&MockObject $cache;
|
||||
protected ICacheFactory&MockObject $cacheFactory;
|
||||
protected IEventDispatcher&MockObject $eventDispatcher;
|
||||
protected LoggerInterface&MockObject $logger;
|
||||
protected IURLGenerator&MockObject $urlGenerator;
|
||||
|
||||
protected ServerVersion&MockObject $serverVersion;
|
||||
protected ConfigManager&MockObject $configManager;
|
||||
|
||||
/** @var IAppManager */
|
||||
protected $manager;
|
||||
|
|
@ -130,6 +112,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->serverVersion = $this->createMock(ServerVersion::class);
|
||||
$this->configManager = $this->createMock(ConfigManager::class);
|
||||
|
||||
$this->overwriteService(AppConfig::class, $this->appConfig);
|
||||
$this->overwriteService(IURLGenerator::class, $this->urlGenerator);
|
||||
|
|
@ -152,6 +135,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->serverVersion,
|
||||
$this->configManager,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -295,6 +279,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->serverVersion,
|
||||
$this->configManager,
|
||||
])
|
||||
->onlyMethods([
|
||||
'getAppPath',
|
||||
|
|
@ -349,6 +334,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->serverVersion,
|
||||
$this->configManager,
|
||||
])
|
||||
->onlyMethods([
|
||||
'getAppPath',
|
||||
|
|
@ -411,6 +397,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->serverVersion,
|
||||
$this->configManager,
|
||||
])
|
||||
->onlyMethods([
|
||||
'getAppPath',
|
||||
|
|
@ -616,6 +603,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->serverVersion,
|
||||
$this->configManager,
|
||||
])
|
||||
->onlyMethods(['getAppInfo'])
|
||||
->getMock();
|
||||
|
|
@ -676,6 +664,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->serverVersion,
|
||||
$this->configManager,
|
||||
])
|
||||
->onlyMethods(['getAppInfo'])
|
||||
->getMock();
|
||||
|
|
@ -817,6 +806,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->serverVersion,
|
||||
$this->configManager,
|
||||
])
|
||||
->onlyMethods([
|
||||
'getAppInfo',
|
||||
|
|
@ -848,6 +838,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->serverVersion,
|
||||
$this->configManager,
|
||||
])
|
||||
->onlyMethods([
|
||||
'getAppInfo',
|
||||
|
|
@ -878,6 +869,7 @@ class AppManagerTest extends TestCase {
|
|||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->serverVersion,
|
||||
$this->configManager,
|
||||
])
|
||||
->onlyMethods([
|
||||
'getAppInfo',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Test;
|
|||
use OC\App\AppManager;
|
||||
use OC\App\InfoParser;
|
||||
use OC\AppConfig;
|
||||
use OC\Config\ConfigManager;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\ICacheFactory;
|
||||
|
|
@ -573,6 +574,7 @@ class AppTest extends \Test\TestCase {
|
|||
Server::get(IEventDispatcher::class),
|
||||
Server::get(LoggerInterface::class),
|
||||
Server::get(ServerVersion::class),
|
||||
\OCP\Server::get(ConfigManager::class),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ namespace Tests\lib\Config;
|
|||
use NCU\Config\Exceptions\TypeConflictException;
|
||||
use NCU\Config\Exceptions\UnknownKeyException;
|
||||
use NCU\Config\IUserConfig;
|
||||
use OC\AppConfig;
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\Config\ConfigManager;
|
||||
use OCP\Exceptions\AppConfigTypeConflictException;
|
||||
use OCP\Exceptions\AppConfigUnknownKeyException;
|
||||
use OCP\IAppConfig;
|
||||
|
|
@ -25,8 +27,10 @@ use Test\TestCase;
|
|||
* @package Test
|
||||
*/
|
||||
class LexiconTest extends TestCase {
|
||||
/** @var AppConfig */
|
||||
private IAppConfig $appConfig;
|
||||
private IUserConfig $userConfig;
|
||||
private ConfigManager $configManager;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
|
@ -39,6 +43,7 @@ class LexiconTest extends TestCase {
|
|||
|
||||
$this->appConfig = Server::get(IAppConfig::class);
|
||||
$this->userConfig = Server::get(IUserConfig::class);
|
||||
$this->configManager = Server::get(ConfigManager::class);
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
|
|
@ -141,11 +146,61 @@ class LexiconTest extends TestCase {
|
|||
public function testUserLexiconSetException() {
|
||||
$this->expectException(UnknownKeyException::class);
|
||||
$this->userConfig->setValueString('user1', TestConfigLexicon_E::APPID, 'key_exception', 'new_value');
|
||||
$this->assertSame('', $this->userConfig->getValueString('user1', TestConfigLexicon_E::APPID, 'key3', ''));
|
||||
$this->assertSame('', $this->userConfig->getValueString('user1', TestConfigLexicon_E::APPID, 'key5', ''));
|
||||
}
|
||||
|
||||
public function testUserLexiconGetException() {
|
||||
$this->expectException(UnknownKeyException::class);
|
||||
$this->userConfig->getValueString('user1', TestConfigLexicon_E::APPID, 'key_exception');
|
||||
}
|
||||
|
||||
public function testAppConfigLexiconRenameSetNewValue() {
|
||||
$this->assertSame(12345, $this->appConfig->getValueInt(TestConfigLexicon_I::APPID, 'key3', 123));
|
||||
$this->appConfig->setValueInt(TestConfigLexicon_I::APPID, 'old_key3', 994);
|
||||
$this->assertSame(994, $this->appConfig->getValueInt(TestConfigLexicon_I::APPID, 'key3', 123));
|
||||
}
|
||||
|
||||
public function testAppConfigLexiconRenameSetOldValuePreMigration() {
|
||||
$this->appConfig->ignoreLexiconAliases(true);
|
||||
$this->appConfig->setValueInt(TestConfigLexicon_I::APPID, 'old_key3', 993);
|
||||
$this->appConfig->ignoreLexiconAliases(false);
|
||||
$this->assertSame(12345, $this->appConfig->getValueInt(TestConfigLexicon_I::APPID, 'key3', 123));
|
||||
}
|
||||
|
||||
public function testAppConfigLexiconRenameSetOldValuePostMigration() {
|
||||
$this->appConfig->ignoreLexiconAliases(true);
|
||||
$this->appConfig->setValueInt(TestConfigLexicon_I::APPID, 'old_key3', 994);
|
||||
$this->appConfig->ignoreLexiconAliases(false);
|
||||
$this->configManager->migrateConfigLexiconKeys(TestConfigLexicon_I::APPID);
|
||||
$this->assertSame(994, $this->appConfig->getValueInt(TestConfigLexicon_I::APPID, 'key3', 123));
|
||||
}
|
||||
|
||||
public function testAppConfigLexiconRenameGetNewValue() {
|
||||
$this->appConfig->setValueInt(TestConfigLexicon_I::APPID, 'key3', 981);
|
||||
$this->assertSame(981, $this->appConfig->getValueInt(TestConfigLexicon_I::APPID, 'old_key3', 123));
|
||||
}
|
||||
|
||||
public function testAppConfigLexiconRenameGetOldValuePreMigration() {
|
||||
$this->appConfig->ignoreLexiconAliases(true);
|
||||
$this->appConfig->setValueInt(TestConfigLexicon_I::APPID, 'key3', 984);
|
||||
$this->assertSame(123, $this->appConfig->getValueInt(TestConfigLexicon_I::APPID, 'old_key3', 123));
|
||||
$this->appConfig->ignoreLexiconAliases(false);
|
||||
}
|
||||
|
||||
public function testAppConfigLexiconRenameGetOldValuePostMigration() {
|
||||
$this->appConfig->ignoreLexiconAliases(true);
|
||||
$this->appConfig->setValueInt(TestConfigLexicon_I::APPID, 'key3', 987);
|
||||
$this->appConfig->ignoreLexiconAliases(false);
|
||||
$this->configManager->migrateConfigLexiconKeys(TestConfigLexicon_I::APPID);
|
||||
$this->assertSame(987, $this->appConfig->getValueInt(TestConfigLexicon_I::APPID, 'old_key3', 123));
|
||||
}
|
||||
|
||||
public function testAppConfigLexiconRenameInvertBoolean() {
|
||||
$this->appConfig->ignoreLexiconAliases(true);
|
||||
$this->appConfig->setValueBool(TestConfigLexicon_I::APPID, 'old_key4', true);
|
||||
$this->appConfig->ignoreLexiconAliases(false);
|
||||
$this->assertSame(true, $this->appConfig->getValueBool(TestConfigLexicon_I::APPID, 'key4'));
|
||||
$this->configManager->migrateConfigLexiconKeys(TestConfigLexicon_I::APPID);
|
||||
$this->assertSame(false, $this->appConfig->getValueBool(TestConfigLexicon_I::APPID, 'key4'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ class TestConfigLexicon_I implements IConfigLexicon {
|
|||
public function getAppConfigs(): array {
|
||||
return [
|
||||
new ConfigLexiconEntry('key1', ValueType::STRING, 'abcde', 'test key', true, IAppConfig::FLAG_SENSITIVE),
|
||||
new ConfigLexiconEntry('key2', ValueType::INT, 12345, 'test key', false)
|
||||
|
||||
new ConfigLexiconEntry('key2', ValueType::INT, 12345, 'test key', false),
|
||||
new ConfigLexiconEntry('key3', ValueType::INT, 12345, 'test key', true, rename: 'old_key3'),
|
||||
new ConfigLexiconEntry('key4', ValueType::BOOL, 12345, 'test key', true, rename: 'old_key4', options: ConfigLexiconEntry::RENAME_INVERT_BOOLEAN),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class AdapterTest extends TestCase {
|
|||
|
||||
public function setUp(): void {
|
||||
$this->connection = Server::get(IDBConnection::class);
|
||||
$this->appId = uniqid('test_db_adapter', true);
|
||||
$this->appId = substr(uniqid('test_db_adapter', true), 0, 32);
|
||||
}
|
||||
|
||||
public function tearDown(): void {
|
||||
|
|
|
|||
Loading…
Reference in a new issue