Browse Source

fix(userconfig): set 'mail' as indexed

Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
pull/55139/head
Maxence Lange 2 months ago
parent
commit
0249e3a2f5
  1. 1
      apps/settings/composer/composer/autoload_classmap.php
  2. 1
      apps/settings/composer/composer/autoload_static.php
  3. 3
      apps/settings/lib/AppInfo/Application.php
  4. 38
      apps/settings/lib/ConfigLexicon.php
  5. 1
      lib/private/App/AppManager.php
  6. 15
      lib/private/Config/ConfigManager.php
  7. 75
      lib/private/Config/UserConfig.php
  8. 1
      lib/private/Repair/ConfigKeyMigration.php

1
apps/settings/composer/composer/autoload_classmap.php

@ -19,6 +19,7 @@ return array(
'OCA\\Settings\\Command\\AdminDelegation\\Add' => $baseDir . '/../lib/Command/AdminDelegation/Add.php',
'OCA\\Settings\\Command\\AdminDelegation\\Remove' => $baseDir . '/../lib/Command/AdminDelegation/Remove.php',
'OCA\\Settings\\Command\\AdminDelegation\\Show' => $baseDir . '/../lib/Command/AdminDelegation/Show.php',
'OCA\\Settings\\ConfigLexicon' => $baseDir . '/../lib/ConfigLexicon.php',
'OCA\\Settings\\Controller\\AISettingsController' => $baseDir . '/../lib/Controller/AISettingsController.php',
'OCA\\Settings\\Controller\\AdminSettingsController' => $baseDir . '/../lib/Controller/AdminSettingsController.php',
'OCA\\Settings\\Controller\\AppSettingsController' => $baseDir . '/../lib/Controller/AppSettingsController.php',

1
apps/settings/composer/composer/autoload_static.php

@ -34,6 +34,7 @@ class ComposerStaticInitSettings
'OCA\\Settings\\Command\\AdminDelegation\\Add' => __DIR__ . '/..' . '/../lib/Command/AdminDelegation/Add.php',
'OCA\\Settings\\Command\\AdminDelegation\\Remove' => __DIR__ . '/..' . '/../lib/Command/AdminDelegation/Remove.php',
'OCA\\Settings\\Command\\AdminDelegation\\Show' => __DIR__ . '/..' . '/../lib/Command/AdminDelegation/Show.php',
'OCA\\Settings\\ConfigLexicon' => __DIR__ . '/..' . '/../lib/ConfigLexicon.php',
'OCA\\Settings\\Controller\\AISettingsController' => __DIR__ . '/..' . '/../lib/Controller/AISettingsController.php',
'OCA\\Settings\\Controller\\AdminSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AdminSettingsController.php',
'OCA\\Settings\\Controller\\AppSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AppSettingsController.php',

3
apps/settings/lib/AppInfo/Application.php

@ -12,6 +12,7 @@ use OC\AppFramework\Utility\TimeFactory;
use OC\Authentication\Events\AppPasswordCreatedEvent;
use OC\Authentication\Token\IProvider;
use OC\Server;
use OCA\Settings\ConfigLexicon;
use OCA\Settings\Hooks;
use OCA\Settings\Listener\AppPasswordCreatedActivityListener;
use OCA\Settings\Listener\GroupRemovedListener;
@ -112,6 +113,8 @@ class Application extends App implements IBootstrap {
$context->registerSearchProvider(AppSearch::class);
$context->registerSearchProvider(UserSearch::class);
$context->registerConfigLexicon(ConfigLexicon::class);
// Register listeners
$context->registerEventListener(AppPasswordCreatedEvent::class, AppPasswordCreatedActivityListener::class);
$context->registerEventListener(UserAddedEvent::class, UserAddedToGroupActivityListener::class);

38
apps/settings/lib/ConfigLexicon.php

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings;
use OC\Config\UserConfig;
use OCP\Config\Lexicon\Entry;
use OCP\Config\Lexicon\ILexicon;
use OCP\Config\Lexicon\Strictness;
use OCP\Config\ValueType;
/**
* Config Lexicon for settings.
*
* Please Add & Manage your Config Keys in that file and keep the Lexicon up to date!
*/
class ConfigLexicon implements ILexicon {
public const USER_SETTINGS_MAIL = 'mail';
public function getStrictness(): Strictness {
return Strictness::IGNORE;
}
public function getAppConfigs(): array {
return [];
}
public function getUserConfigs(): array {
return [
new Entry(key: self::USER_SETTINGS_MAIL, type: ValueType::STRING, defaultRaw: '', definition: 'account mail address', flags: UserConfig::FLAG_INDEXED),
];
}
}

1
lib/private/App/AppManager.php

@ -1089,6 +1089,7 @@ class AppManager implements IAppManager {
// migrate eventual new config keys in the process
/** @psalm-suppress InternalMethod */
$this->configManager->migrateConfigLexiconKeys($appId);
$this->configManager->updateLexiconEntries($appId);
$this->dispatcher->dispatchTyped(new AppUpdateEvent($appId));
$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(

15
lib/private/Config/ConfigManager.php

@ -82,6 +82,21 @@ class ConfigManager {
$this->userConfig->ignoreLexiconAliases(false);
}
/**
* Upgrade stored data in case of changes in the lexicon.
* Heavy process to be executed on core and app upgrade.
*
* - upgrade UserConfig entries if set as indexed
*/
public function updateLexiconEntries(string $appId): void {
$this->loadConfigServices();
$lexicon = $this->userConfig->getConfigDetailsFromLexicon($appId);
foreach ($lexicon['entries'] as $entry) {
// upgrade based on index flag
$this->userConfig->updateGlobalIndexed($appId, $entry->getKey(), $entry->isFlagged(UserConfig::FLAG_INDEXED));
}
}
/**
* config services cannot be load at __construct() or install will fail
*/

75
lib/private/Config/UserConfig.php

@ -477,40 +477,55 @@ class UserConfig implements IUserConfig {
$this->assertParams('', $app, $key, allowEmptyUser: true);
$this->matchAndApplyLexiconDefinition('', $app, $key);
$lexiconEntry = $this->getLexiconEntry($app, $key);
if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) === false) {
$this->logger->notice('UserConfig+Lexicon: using searchUsersByTypedValue on a config key not set as indexed');
}
$qb = $this->connection->getQueryBuilder();
$qb->from('preferences');
$qb->select('userid');
$qb->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
$qb->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
// search within 'indexed' OR 'configvalue' only if 'flags' is set as not indexed
// TODO: when implementing config lexicon remove the searches on 'configvalue' if value is set as indexed
$configValueColumn = ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR) : 'configvalue';
if (is_array($value)) {
$where = $qb->expr()->orX(
$qb->expr()->in('indexed', $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY)),
$qb->expr()->andX(
$qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
$qb->expr()->in($configValueColumn, $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY))
)
);
} else {
if ($caseInsensitive) {
$where = $qb->expr()->in('indexed', $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY));
// in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed
if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) {
$where = $qb->expr()->orX(
$qb->expr()->eq($qb->func()->lower('indexed'), $qb->createNamedParameter(strtolower($value))),
$where,
$qb->expr()->andX(
$qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
$qb->expr()->eq($qb->func()->lower($configValueColumn), $qb->createNamedParameter(strtolower($value)))
$qb->expr()->in($configValueColumn, $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY))
)
);
}
} else {
if ($caseInsensitive) {
$where = $qb->expr()->eq($qb->func()->lower('indexed'), $qb->createNamedParameter(strtolower($value)));
// in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed
if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) {
$where = $qb->expr()->orX(
$where,
$qb->expr()->andX(
$qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
$qb->expr()->eq($qb->func()->lower($configValueColumn), $qb->createNamedParameter(strtolower($value)))
)
);
}
} else {
$where = $qb->expr()->orX(
$qb->expr()->eq('indexed', $qb->createNamedParameter($value)),
$qb->expr()->andX(
$qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
$qb->expr()->eq($configValueColumn, $qb->createNamedParameter($value))
)
);
$where = $qb->expr()->eq('indexed', $qb->createNamedParameter($value));
// in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed
if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) {
$where = $qb->expr()->orX(
$where,
$qb->expr()->andX(
$qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)),
$qb->expr()->eq($configValueColumn, $qb->createNamedParameter($value))
)
);
}
}
}
@ -1408,13 +1423,19 @@ class UserConfig implements IUserConfig {
$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);
} catch (UnknownKeyException) {
// should not happen and can be ignored
}
}
$bitPosition = log(self::FLAG_INDEXED) / log(2); // emulate base-2 logarithm (log2)
$bitOperation = ($indexed) ? '`flags` | (1 << ' . $bitPosition . ')' : '`flags` & ~(1 << ' . $bitPosition . ')';
$update = $this->connection->getQueryBuilder();
$update->update('preferences')
->set('flags', $update->createFunction($bitOperation))
->set('indexed', ($indexed) ? 'configvalue' : $update->createNamedParameter(''))
->where(
$update->expr()->eq('appid', $update->createNamedParameter($app)),
$update->expr()->eq('configkey', $update->createNamedParameter($key))
);
$update->executeStatement();
// we clear all cache
$this->clearCacheAll();

1
lib/private/Repair/ConfigKeyMigration.php

@ -25,5 +25,6 @@ class ConfigKeyMigration implements IRepairStep {
public function run(IOutput $output) {
$this->configManager->migrateConfigLexiconKeys();
$this->configManager->updateLexiconEntries('core');
}
}
Loading…
Cancel
Save