Skip to content

Commit d52b218

Browse files
committed
feat(lexicon): validate value on set
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
1 parent 85ba4e0 commit d52b218

File tree

3 files changed

+44
-10
lines changed

3 files changed

+44
-10
lines changed

lib/private/AppConfig.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -795,11 +795,19 @@ private function setTypedValue(
795795
int $type,
796796
): bool {
797797
$this->assertParams($app, $key);
798-
if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
798+
/** @var ?Entry $lexiconEntry */
799+
$lexiconEntry = null;
800+
if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, lexiconEntry: $lexiconEntry)) {
799801
return false; // returns false as database is not updated
800802
}
801803
$this->loadConfig(null, $lazy ?? true);
802804

805+
// lexicon entry might have requested a check on the value
806+
$confirmationClosure = $lexiconEntry?->onSetConfirmation();
807+
if ($confirmationClosure !== null && !$confirmationClosure($value)) {
808+
return false;
809+
}
810+
803811
$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
804812
$inserted = $refreshCache = false;
805813

lib/private/Config/UserConfig.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,12 +1100,20 @@ private function setTypedValue(
11001100
}
11011101

11021102
$this->assertParams($userId, $app, $key);
1103-
if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, $flags)) {
1103+
/** @var ?Entry $lexiconEntry */
1104+
$lexiconEntry = null;
1105+
if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, $flags, lexiconEntry: $lexiconEntry)) {
11041106
// returns false as database is not updated
11051107
return false;
11061108
}
11071109
$this->loadConfig($userId, $lazy);
11081110

1111+
// lexicon entry might have requested a check on the value
1112+
$confirmationClosure = $lexiconEntry?->onSetConfirmation();
1113+
if ($confirmationClosure !== null && !$confirmationClosure($value)) {
1114+
return false;
1115+
}
1116+
11091117
$inserted = $refreshCache = false;
11101118
$origValue = $value;
11111119
$sensitive = $this->isFlagged(self::FLAG_SENSITIVE, $flags);
@@ -1937,6 +1945,7 @@ private function matchAndApplyLexiconDefinition(
19371945
ValueType &$type = ValueType::MIXED,
19381946
int &$flags = 0,
19391947
?string &$default = null,
1948+
?Entry &$lexiconEntry = null,
19401949
): bool {
19411950
$configDetails = $this->getConfigDetailsFromLexicon($app);
19421951
if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) {
@@ -1953,18 +1962,18 @@ private function matchAndApplyLexiconDefinition(
19531962
return true;
19541963
}
19551964

1956-
/** @var Entry $configValue */
1957-
$configValue = $configDetails['entries'][$key];
1965+
/** @var Entry $lexiconEntry */
1966+
$lexiconEntry = $configDetails['entries'][$key];
19581967
if ($type === ValueType::MIXED) {
19591968
// we overwrite if value was requested as mixed
1960-
$type = $configValue->getValueType();
1961-
} elseif ($configValue->getValueType() !== $type) {
1969+
$type = $lexiconEntry->getValueType();
1970+
} elseif ($lexiconEntry->getValueType() !== $type) {
19621971
throw new TypeConflictException('The user config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
19631972
}
19641973

1965-
$lazy = $configValue->isLazy();
1966-
$flags = $configValue->getFlags();
1967-
if ($configValue->isDeprecated()) {
1974+
$lazy = $lexiconEntry->isLazy();
1975+
$flags = $lexiconEntry->getFlags();
1976+
if ($lexiconEntry->isDeprecated()) {
19681977
$this->logger->notice('User config key ' . $app . '/' . $key . ' is set as deprecated.');
19691978
}
19701979

@@ -1976,7 +1985,7 @@ private function matchAndApplyLexiconDefinition(
19761985

19771986
// only look for default if needed, default from Lexicon got priority if not overwritten by admin
19781987
if ($default !== null) {
1979-
$default = $this->getSystemDefault($app, $configValue) ?? $configValue->getDefault($this->presetManager->getLexiconPreset()) ?? $default;
1988+
$default = $this->getSystemDefault($app, $lexiconEntry) ?? $lexiconEntry->getDefault($this->presetManager->getLexiconPreset()) ?? $default;
19801989
}
19811990

19821991
// returning false will make get() returning $default and set() not changing value in database

lib/public/Config/Lexicon/Entry.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ class Entry {
3535
* @param string|null $rename source in case of a rename of a config key.
3636
* @param int $options additional bitflag options {@see self::RENAME_INVERT_BOOLEAN}
3737
* @param string $note additional note and warning related to the use of the config key.
38+
* @param Closure|null $onSetConfirm callback to be called when a config value is set. {@see onSetConfirmation()} for more details.
3839
*
3940
* @since 32.0.0
41+
* @since 34.0.0 added $onSetConfirm
4042
* @psalm-suppress PossiblyInvalidCast
4143
* @psalm-suppress RiskyCast
4244
*/
@@ -51,6 +53,7 @@ public function __construct(
5153
private readonly ?string $rename = null,
5254
private readonly int $options = 0,
5355
private readonly string $note = '',
56+
private readonly ?Closure $onSetConfirm = null,
5457
) {
5558
// key can only contain alphanumeric chars and underscore "_"
5659
if (preg_match('/[^[:alnum:]_]/', $key)) {
@@ -195,6 +198,20 @@ public function getNote(): string {
195198
return $this->note;
196199
}
197200

201+
/**
202+
* Returns an optional callback to be called when a config value is set.
203+
* If not null, the callback will be called before the config value is set.
204+
* Callable must accept a string-typed parameter containing the new value.
205+
* String-typed parameter can be referenced and modified to a new value from the Callable.
206+
* Callback must return a boolean indicating if the set operation should be allowed.
207+
*
208+
* @return (Closure(string $value): bool)|null
209+
* @since 34.0.0
210+
*/
211+
public function onSetConfirmation(): ?Closure {
212+
return $this->onSetConfirm;
213+
}
214+
198215
/**
199216
* returns if config key is set as lazy
200217
*

0 commit comments

Comments
 (0)