Skip to content

Commit 106ba14

Browse files
committed
feat(lexicon): events onSet() and initialize()
Signed-off-by: Maxence Lange <[email protected]> d Signed-off-by: Maxence Lange <[email protected]>
1 parent 1b42dad commit 106ba14

File tree

7 files changed

+143
-19
lines changed

7 files changed

+143
-19
lines changed

apps/settings/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
'OCA\\Settings\\Command\\AdminDelegation\\Add' => $baseDir . '/../lib/Command/AdminDelegation/Add.php',
2020
'OCA\\Settings\\Command\\AdminDelegation\\Remove' => $baseDir . '/../lib/Command/AdminDelegation/Remove.php',
2121
'OCA\\Settings\\Command\\AdminDelegation\\Show' => $baseDir . '/../lib/Command/AdminDelegation/Show.php',
22+
'OCA\\Settings\\ConfigLexicon' => $baseDir . '/../lib/ConfigLexicon.php',
2223
'OCA\\Settings\\Controller\\AISettingsController' => $baseDir . '/../lib/Controller/AISettingsController.php',
2324
'OCA\\Settings\\Controller\\AdminSettingsController' => $baseDir . '/../lib/Controller/AdminSettingsController.php',
2425
'OCA\\Settings\\Controller\\AppSettingsController' => $baseDir . '/../lib/Controller/AppSettingsController.php',

apps/settings/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class ComposerStaticInitSettings
3434
'OCA\\Settings\\Command\\AdminDelegation\\Add' => __DIR__ . '/..' . '/../lib/Command/AdminDelegation/Add.php',
3535
'OCA\\Settings\\Command\\AdminDelegation\\Remove' => __DIR__ . '/..' . '/../lib/Command/AdminDelegation/Remove.php',
3636
'OCA\\Settings\\Command\\AdminDelegation\\Show' => __DIR__ . '/..' . '/../lib/Command/AdminDelegation/Show.php',
37+
'OCA\\Settings\\ConfigLexicon' => __DIR__ . '/..' . '/../lib/ConfigLexicon.php',
3738
'OCA\\Settings\\Controller\\AISettingsController' => __DIR__ . '/..' . '/../lib/Controller/AISettingsController.php',
3839
'OCA\\Settings\\Controller\\AdminSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AdminSettingsController.php',
3940
'OCA\\Settings\\Controller\\AppSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AppSettingsController.php',

apps/settings/lib/AppInfo/Application.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OC\Authentication\Events\AppPasswordCreatedEvent;
1313
use OC\Authentication\Token\IProvider;
1414
use OC\Server;
15+
use OCA\Settings\ConfigLexicon;
1516
use OCA\Settings\Hooks;
1617
use OCA\Settings\Listener\AppPasswordCreatedActivityListener;
1718
use OCA\Settings\Listener\GroupRemovedListener;
@@ -129,6 +130,9 @@ public function register(IRegistrationContext $context): void {
129130
// Register Settings Form(s)
130131
$context->registerDeclarativeSettings(MailProvider::class);
131132

133+
// Register Config Lexicon
134+
$context->registerConfigLexicon(ConfigLexicon::class);
135+
132136
/**
133137
* Core class wrappers
134138
*/
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Settings;
10+
11+
use NCU\Config\Lexicon\ConfigLexiconEntry;
12+
use NCU\Config\Lexicon\ConfigLexiconStrictness;
13+
use NCU\Config\Lexicon\IConfigLexicon;
14+
use NCU\Config\ValueType;
15+
16+
/**
17+
* ConfigLexicon for 'settings' app/user configs
18+
*/
19+
class ConfigLexicon implements IConfigLexicon {
20+
public function getStrictness(): ConfigLexiconStrictness {
21+
return ConfigLexiconStrictness::IGNORE;
22+
}
23+
24+
/**
25+
* @inheritDoc
26+
* @return ConfigLexiconEntry[]
27+
*/
28+
public function getAppConfigs(): array {
29+
return [
30+
(new ConfigLexiconEntry('email', ValueType::STRING, '', 'email'))->onSet(function (string &$value): void { $value = strtolower($value); }),
31+
];
32+
}
33+
34+
/**
35+
* @inheritDoc
36+
* @return ConfigLexiconEntry[]
37+
*/
38+
public function getUserConfigs(): array {
39+
return [
40+
];
41+
}
42+
}

lib/private/AppConfig.php

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,8 @@ private function getTypedValue(
438438
): string {
439439
$this->assertParams($app, $key, valueType: $type);
440440
$origKey = $key;
441-
if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default)) {
441+
/** @var ConfigLexiconEntry $lexiconEntry */
442+
if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default, lexiconEntry: $lexiconEntry)) {
442443
return $default; // returns default if strictness of lexicon is set to WARNING (block and report)
443444
}
444445
$this->loadConfig($app, $lazy);
@@ -469,6 +470,13 @@ private function getTypedValue(
469470
} elseif (isset($this->fastCache[$app][$key])) {
470471
$value = $this->fastCache[$app][$key];
471472
} else {
473+
// unknown value, might want to execute something instead of just returning default.
474+
// default value will be stored in database, unless false is returned
475+
if (($lexiconEntry?->executeOnInit() !== null)
476+
&& $lexiconEntry->executeOnInit()($default) !== false) {
477+
$this->setTypedValue($app, $key, $default, $lazy, $type);
478+
}
479+
472480
return $default;
473481
}
474482

@@ -748,11 +756,18 @@ private function setTypedValue(
748756
int $type,
749757
): bool {
750758
$this->assertParams($app, $key);
751-
if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
759+
/** @var ConfigLexiconEntry $lexiconEntry */
760+
if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, lexiconEntry: $lexiconEntry)) {
752761
return false; // returns false as database is not updated
753762
}
754763
$this->loadConfig(null, $lazy);
755764

765+
// might want to execute something before storing new value
766+
// will cancel storing of new value in database if false is returned
767+
if ($lexiconEntry?->executeOnSet() !== null && $lexiconEntry?->executeOnSet()($value) === false) {
768+
return false;
769+
}
770+
756771
$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
757772
$inserted = $refreshCache = false;
758773

@@ -1593,6 +1608,7 @@ private function matchAndApplyLexiconDefinition(
15931608
?bool &$lazy = null,
15941609
int &$type = self::VALUE_MIXED,
15951610
string &$default = '',
1611+
?ConfigLexiconEntry &$lexiconEntry = null,
15961612
): bool {
15971613
if (in_array($key,
15981614
[
@@ -1617,23 +1633,23 @@ private function matchAndApplyLexiconDefinition(
16171633
return true;
16181634
}
16191635

1620-
/** @var ConfigLexiconEntry $configValue */
1621-
$configValue = $configDetails['entries'][$key];
1636+
/** @var ConfigLexiconEntry $lexiconEntry */
1637+
$lexiconEntry = $configDetails['entries'][$key];
16221638
$type &= ~self::VALUE_SENSITIVE;
16231639

1624-
$appConfigValueType = $configValue->getValueType()->toAppConfigFlag();
1640+
$appConfigValueType = $lexiconEntry->getValueType()->toAppConfigFlag();
16251641
if ($type === self::VALUE_MIXED) {
16261642
$type = $appConfigValueType; // we overwrite if value was requested as mixed
16271643
} elseif ($appConfigValueType !== $type) {
16281644
throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
16291645
}
16301646

1631-
$lazy = $configValue->isLazy();
1632-
$default = $configValue->getDefault() ?? $default; // default from Lexicon got priority
1633-
if ($configValue->isFlagged(self::FLAG_SENSITIVE)) {
1647+
$lazy = $lexiconEntry->isLazy();
1648+
$default = $lexiconEntry->getDefault() ?? $default; // default from Lexicon got priority
1649+
if ($lexiconEntry->isFlagged(self::FLAG_SENSITIVE)) {
16341650
$type |= self::VALUE_SENSITIVE;
16351651
}
1636-
if ($configValue->isDeprecated()) {
1652+
if ($lexiconEntry->isDeprecated()) {
16371653
$this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
16381654
}
16391655

lib/private/Config/UserConfig.php

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ private function getTypedValue(
721721
): string {
722722
$this->assertParams($userId, $app, $key);
723723
$origKey = $key;
724-
if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, default: $default)) {
724+
if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, default: $default, lexiconEntry: $lexiconEntry)) {
725725
// returns default if strictness of lexicon is set to WARNING (block and report)
726726
return $default;
727727
}
@@ -753,6 +753,13 @@ private function getTypedValue(
753753
} elseif (isset($this->fastCache[$userId][$app][$key])) {
754754
$value = $this->fastCache[$userId][$app][$key];
755755
} else {
756+
// unknown value, might want to execute something instead of just returning default.
757+
// default value will be stored in database, unless false is returned
758+
if (($lexiconEntry?->executeOnInit() !== null)
759+
&& $lexiconEntry->executeOnInit()($default) !== false) {
760+
$this->setTypedValue($userId, $app, $key, $default, $lazy, $type);
761+
}
762+
756763
return $default;
757764
}
758765

@@ -1068,12 +1075,19 @@ private function setTypedValue(
10681075
ValueType $type,
10691076
): bool {
10701077
$this->assertParams($userId, $app, $key);
1071-
if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, $flags)) {
1078+
/** @var ConfigLexiconEntry $lexiconEntry */
1079+
if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, $flags, lexiconEntry: $lexiconEntry)) {
10721080
// returns false as database is not updated
10731081
return false;
10741082
}
10751083
$this->loadConfig($userId, $lazy);
10761084

1085+
// might want to execute something before storing new value
1086+
// will cancel storing of new value in database if false is returned
1087+
if ($lexiconEntry?->executeOnSet() !== null && $lexiconEntry?->executeOnSet()($value) === false) {
1088+
return false;
1089+
}
1090+
10771091
$inserted = $refreshCache = false;
10781092
$origValue = $value;
10791093
$sensitive = $this->isFlagged(self::FLAG_SENSITIVE, $flags);
@@ -1882,6 +1896,7 @@ private function matchAndApplyLexiconDefinition(
18821896
ValueType &$type = ValueType::MIXED,
18831897
int &$flags = 0,
18841898
string &$default = '',
1899+
?ConfigLexiconEntry &$lexiconEntry = null,
18851900
): bool {
18861901
$configDetails = $this->getConfigDetailsFromLexicon($app);
18871902
if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) {
@@ -1898,18 +1913,18 @@ private function matchAndApplyLexiconDefinition(
18981913
return true;
18991914
}
19001915

1901-
/** @var ConfigLexiconEntry $configValue */
1902-
$configValue = $configDetails['entries'][$key];
1916+
/** @var ConfigLexiconEntry $lexiconEntry */
1917+
$lexiconEntry = $configDetails['entries'][$key];
19031918
if ($type === ValueType::MIXED) {
19041919
// we overwrite if value was requested as mixed
1905-
$type = $configValue->getValueType();
1906-
} elseif ($configValue->getValueType() !== $type) {
1920+
$type = $lexiconEntry->getValueType();
1921+
} elseif ($lexiconEntry->getValueType() !== $type) {
19071922
throw new TypeConflictException('The user config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
19081923
}
19091924

1910-
$lazy = $configValue->isLazy();
1911-
$flags = $configValue->getFlags();
1912-
if ($configValue->isDeprecated()) {
1925+
$lazy = $lexiconEntry->isLazy();
1926+
$flags = $lexiconEntry->getFlags();
1927+
if ($lexiconEntry->isDeprecated()) {
19131928
$this->logger->notice('User config key ' . $app . '/' . $key . ' is set as deprecated.');
19141929
}
19151930

@@ -1920,7 +1935,7 @@ private function matchAndApplyLexiconDefinition(
19201935
}
19211936

19221937
// default from Lexicon got priority but it can still be overwritten by admin
1923-
$default = $this->getSystemDefault($app, $configValue) ?? $configValue->getDefault() ?? $default;
1938+
$default = $this->getSystemDefault($app, $lexiconEntry) ?? $lexiconEntry->getDefault() ?? $default;
19241939

19251940
// returning false will make get() returning $default and set() not changing value in database
19261941
return !$enforcedValue;

lib/unstable/Config/Lexicon/ConfigLexiconEntry.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace NCU\Config\Lexicon;
1010

11+
use Closure;
1112
use NCU\Config\ValueType;
1213

1314
/**
@@ -22,6 +23,8 @@ class ConfigLexiconEntry {
2223

2324
private string $definition = '';
2425
private ?string $default = null;
26+
private ?Closure $initialize = null;
27+
private ?Closure $onSet = null;
2528

2629
/**
2730
* @param string $key config key
@@ -136,6 +139,48 @@ public function getDefault(): ?string {
136139
return $this->default;
137140
}
138141

142+
/**
143+
* set a closure to be executed when reading a non-set config value
144+
* first param of the closure is the default value of the config key.
145+
*
146+
* @experimental 32.0.0
147+
*/
148+
public function initialize(Closure $closure): self {
149+
$this->initialize = $closure;
150+
return $this;
151+
}
152+
153+
/**
154+
* returns if something needs to be executed when reading a non-set config value
155+
*
156+
* @return Closure|null NULL if nothing is supposed to happens
157+
* @experimental 32.0.0
158+
*/
159+
public function executeOnInit(): ?Closure {
160+
return $this->initialize;
161+
}
162+
163+
/**
164+
* set a closure to be executed when setting a value to this config key
165+
* first param of the closure is future value.
166+
*
167+
* @experimental 32.0.0
168+
*/
169+
public function onSet(Closure $closure): self {
170+
$this->onSet = $closure;
171+
return $this;
172+
}
173+
174+
/**
175+
* returns if something needs to be executed when writing a new value
176+
*
177+
* @return Closure|null NULL if nothing is supposed to happens
178+
* @experimental 32.0.0
179+
*/
180+
public function executeOnSet(): ?Closure {
181+
return $this->onSet;
182+
}
183+
139184
/**
140185
* convert $entry into string, based on the expected type for config value
141186
*

0 commit comments

Comments
 (0)