mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
Merge pull request #55139 from nextcloud/fix/noid/index-settings-mail-on-upgrade
fix(userconfig): set 'mail' as indexed
This commit is contained in:
commit
22c0e76e23
11 changed files with 238 additions and 29 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
Normal file
38
apps/settings/lib/ConfigLexicon.php
Normal file
|
|
@ -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 OCP\Config\IUserConfig;
|
||||
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_EMAIL = 'email';
|
||||
|
||||
public function getStrictness(): Strictness {
|
||||
return Strictness::IGNORE;
|
||||
}
|
||||
|
||||
public function getAppConfigs(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getUserConfigs(): array {
|
||||
return [
|
||||
new Entry(key: self::USER_SETTINGS_EMAIL, type: ValueType::STRING, defaultRaw: '', definition: 'account mail address', flags: IUserConfig::FLAG_INDEXED),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(IUserConfig::FLAG_INDEXED));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* config services cannot be load at __construct() or install will fail
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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 config key ' . $app . '/' . $key . ' which is 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))
|
||||
)
|
||||
);
|
||||
$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(
|
||||
$where,
|
||||
$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()->orX(
|
||||
$qb->expr()->eq($qb->func()->lower('indexed'), $qb->createNamedParameter(strtolower($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($qb->func()->lower($configValueColumn), $qb->createNamedParameter(strtolower($value)))
|
||||
)
|
||||
);
|
||||
$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,14 +1423,33 @@ 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
|
||||
}
|
||||
$update = $this->connection->getQueryBuilder();
|
||||
$update->update('preferences')
|
||||
->where(
|
||||
$update->expr()->eq('appid', $update->createNamedParameter($app)),
|
||||
$update->expr()->eq('configkey', $update->createNamedParameter($key))
|
||||
);
|
||||
|
||||
// switching flags 'indexed' on and off is about adding/removing the bit value on the correct entries
|
||||
if ($indexed) {
|
||||
$update->set('indexed', $update->func()->substring('configvalue', $update->createNamedParameter(1, IQueryBuilder::PARAM_INT), $update->createNamedParameter(64, IQueryBuilder::PARAM_INT)));
|
||||
$update->set('flags', $update->func()->add('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)));
|
||||
$update->andWhere(
|
||||
$update->expr()->neq($update->expr()->castColumn(
|
||||
$update->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), IQueryBuilder::PARAM_INT), $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)
|
||||
));
|
||||
} else {
|
||||
// emptying field 'indexed' if key is not set as indexed anymore
|
||||
$update->set('indexed', $update->createNamedParameter(''));
|
||||
$update->set('flags', $update->func()->subtract('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)));
|
||||
$update->andWhere(
|
||||
$update->expr()->eq($update->expr()->castColumn(
|
||||
$update->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), IQueryBuilder::PARAM_INT), $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)
|
||||
));
|
||||
}
|
||||
|
||||
$update->executeStatement();
|
||||
|
||||
// we clear all cache
|
||||
$this->clearCacheAll();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,5 +25,6 @@ class ConfigKeyMigration implements IRepairStep {
|
|||
|
||||
public function run(IOutput $output) {
|
||||
$this->configManager->migrateConfigLexiconKeys();
|
||||
$this->configManager->updateLexiconEntries('core');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,11 +60,15 @@ class LexiconTest extends TestCase {
|
|||
$this->appConfig->deleteApp(TestLexicon_N::APPID);
|
||||
$this->appConfig->deleteApp(TestLexicon_W::APPID);
|
||||
$this->appConfig->deleteApp(TestLexicon_E::APPID);
|
||||
$this->appConfig->deleteApp(TestLexicon_UserIndexed::APPID);
|
||||
$this->appConfig->deleteApp(TestLexicon_UserIndexedRemove::APPID);
|
||||
|
||||
$this->userConfig->deleteApp(TestConfigLexicon_I::APPID);
|
||||
$this->userConfig->deleteApp(TestLexicon_N::APPID);
|
||||
$this->userConfig->deleteApp(TestLexicon_W::APPID);
|
||||
$this->userConfig->deleteApp(TestLexicon_E::APPID);
|
||||
$this->userConfig->deleteApp(TestLexicon_UserIndexed::APPID);
|
||||
$this->userConfig->deleteApp(TestLexicon_UserIndexedRemove::APPID);
|
||||
}
|
||||
|
||||
public function testAppLexiconSetCorrect() {
|
||||
|
|
@ -234,4 +238,50 @@ class LexiconTest extends TestCase {
|
|||
$this->presetManager->setLexiconPreset(Preset::FAMILY);
|
||||
$this->assertSame('family', $this->userConfig->getValueString('user1', TestLexicon_E::APPID, 'key3'));
|
||||
}
|
||||
|
||||
public function testLexiconIndexedUpdate() {
|
||||
$this->userConfig->setValueString('user1', TestLexicon_UserIndexed::APPID, 'key1', 'abcd');
|
||||
$this->userConfig->setValueString('user2', TestLexicon_UserIndexed::APPID, 'key1', '1234', flags: 64);
|
||||
$this->userConfig->setValueString('user3', TestLexicon_UserIndexed::APPID, 'key1', 'qwer', flags: IUserConfig::FLAG_INDEXED);
|
||||
$this->userConfig->setValueString('user4', TestLexicon_UserIndexed::APPID, 'key1', 'uiop', flags: 64 | IUserConfig::FLAG_INDEXED);
|
||||
|
||||
$bootstrapCoordinator = Server::get(Coordinator::class);
|
||||
$bootstrapCoordinator->getRegistrationContext()?->registerConfigLexicon(TestLexicon_UserIndexed::APPID, TestLexicon_UserIndexed::class);
|
||||
$this->userConfig->clearCacheAll();
|
||||
|
||||
$this->configManager->updateLexiconEntries(TestLexicon_UserIndexed::APPID);
|
||||
|
||||
$this->assertTrue($this->userConfig->isIndexed('user1', TestLexicon_UserIndexed::APPID, 'key1'));
|
||||
$this->assertTrue($this->userConfig->isIndexed('user2', TestLexicon_UserIndexed::APPID, 'key1'));
|
||||
$this->assertTrue($this->userConfig->isIndexed('user3', TestLexicon_UserIndexed::APPID, 'key1'));
|
||||
$this->assertTrue($this->userConfig->isIndexed('user4', TestLexicon_UserIndexed::APPID, 'key1'));
|
||||
|
||||
$this->assertSame(2, $this->userConfig->getValueFlags('user1', TestLexicon_UserIndexed::APPID, 'key1'));
|
||||
$this->assertSame(66, $this->userConfig->getValueFlags('user2', TestLexicon_UserIndexed::APPID, 'key1'));
|
||||
$this->assertSame(2, $this->userConfig->getValueFlags('user3', TestLexicon_UserIndexed::APPID, 'key1'));
|
||||
$this->assertSame(66, $this->userConfig->getValueFlags('user4', TestLexicon_UserIndexed::APPID, 'key1'));
|
||||
}
|
||||
|
||||
public function testLexiconIndexedUpdateRemove() {
|
||||
$this->userConfig->setValueString('user1', TestLexicon_UserIndexedRemove::APPID, 'key1', 'abcd');
|
||||
$this->userConfig->setValueString('user2', TestLexicon_UserIndexedRemove::APPID, 'key1', '1234', flags: 64);
|
||||
$this->userConfig->setValueString('user3', TestLexicon_UserIndexedRemove::APPID, 'key1', 'qwer', flags: IUserConfig::FLAG_INDEXED);
|
||||
$this->userConfig->setValueString('user4', TestLexicon_UserIndexedRemove::APPID, 'key1', 'uiop', flags: 64 | IUserConfig::FLAG_INDEXED);
|
||||
|
||||
$bootstrapCoordinator = Server::get(Coordinator::class);
|
||||
$bootstrapCoordinator->getRegistrationContext()?->registerConfigLexicon(TestLexicon_UserIndexedRemove::APPID, TestLexicon_UserIndexedRemove::class);
|
||||
$this->userConfig->clearCacheAll();
|
||||
|
||||
$this->configManager->updateLexiconEntries(TestLexicon_UserIndexedRemove::APPID);
|
||||
|
||||
$this->assertFalse($this->userConfig->isIndexed('user1', TestLexicon_UserIndexedRemove::APPID, 'key1'));
|
||||
$this->assertFalse($this->userConfig->isIndexed('user2', TestLexicon_UserIndexedRemove::APPID, 'key1'));
|
||||
$this->assertFalse($this->userConfig->isIndexed('user3', TestLexicon_UserIndexedRemove::APPID, 'key1'));
|
||||
$this->assertFalse($this->userConfig->isIndexed('user4', TestLexicon_UserIndexedRemove::APPID, 'key1'));
|
||||
|
||||
$this->assertSame(0, $this->userConfig->getValueFlags('user1', TestLexicon_UserIndexedRemove::APPID, 'key1'));
|
||||
$this->assertSame(64, $this->userConfig->getValueFlags('user2', TestLexicon_UserIndexedRemove::APPID, 'key1'));
|
||||
$this->assertSame(0, $this->userConfig->getValueFlags('user3', TestLexicon_UserIndexedRemove::APPID, 'key1'));
|
||||
$this->assertSame(64, $this->userConfig->getValueFlags('user4', TestLexicon_UserIndexedRemove::APPID, 'key1'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
33
tests/lib/Config/TestLexicon_UserIndexed.php
Normal file
33
tests/lib/Config/TestLexicon_UserIndexed.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace Tests\lib\Config;
|
||||
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\Config\Lexicon\Entry;
|
||||
use OCP\Config\Lexicon\ILexicon;
|
||||
use OCP\Config\Lexicon\Strictness;
|
||||
use OCP\Config\ValueType;
|
||||
|
||||
class TestLexicon_UserIndexed implements ILexicon {
|
||||
public const APPID = 'lexicon_user_indexed';
|
||||
public function getStrictness(): Strictness {
|
||||
return Strictness::EXCEPTION;
|
||||
}
|
||||
|
||||
public function getAppConfigs(): array {
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
public function getUserConfigs(): array {
|
||||
return [
|
||||
new Entry(key: 'key1', type: ValueType::STRING, defaultRaw: '', definition: 'test key', flags: IUserConfig::FLAG_INDEXED),
|
||||
];
|
||||
}
|
||||
}
|
||||
32
tests/lib/Config/TestLexicon_UserIndexedRemove.php
Normal file
32
tests/lib/Config/TestLexicon_UserIndexedRemove.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace Tests\lib\Config;
|
||||
|
||||
use OCP\Config\Lexicon\Entry;
|
||||
use OCP\Config\Lexicon\ILexicon;
|
||||
use OCP\Config\Lexicon\Strictness;
|
||||
use OCP\Config\ValueType;
|
||||
|
||||
class TestLexicon_UserIndexedRemove implements ILexicon {
|
||||
public const APPID = 'lexicon_user_not_indexed';
|
||||
public function getStrictness(): Strictness {
|
||||
return Strictness::EXCEPTION;
|
||||
}
|
||||
|
||||
public function getAppConfigs(): array {
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
public function getUserConfigs(): array {
|
||||
return [
|
||||
new Entry(key: 'key1', type: ValueType::STRING, defaultRaw: '', definition: 'test key'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue