fix: verify option writes before treating update_option() false as failure
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -138,7 +138,7 @@ final class WpSettingsUpdateAbility implements Ability
|
||||
try {
|
||||
foreach ($settings as $key => $value) {
|
||||
$key = (string) $key;
|
||||
if ($previous[$key] === $value) {
|
||||
if ($this->isStored($key, $this->whitelist->normalize($key, $value))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -168,17 +168,34 @@ final class WpSettingsUpdateAbility implements Ability
|
||||
|
||||
private function writeOne(string $key, mixed $value): void
|
||||
{
|
||||
$normalized = $this->whitelist->normalize($key, $value);
|
||||
|
||||
if ($key === 'permalink_structure') {
|
||||
$this->setPermalinkStructure((string) $value);
|
||||
$this->setPermalinkStructure((string) $normalized);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (update_option($key, $this->whitelist->normalize($key, $value)) === false) {
|
||||
throw new RuntimeException(sprintf('update_option(%s) returned false', $key));
|
||||
// update_option() returns false both for a genuine failure and for a value
|
||||
// that already matches the stored option (WordPress core short-circuits the
|
||||
// write in that case, see wp-includes/option.php) – get_option() afterwards
|
||||
// tells the two apart, the same way MetaFieldSource::holds() does for meta.
|
||||
if (update_option($key, $normalized) === false && ! $this->isStored($key, $normalized)) {
|
||||
throw new RuntimeException(sprintf('update_option(%s) failed', $key));
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether option $key already holds $normalized, compared like WordPress compares scalars. */
|
||||
private function isStored(string $key, mixed $normalized): bool
|
||||
{
|
||||
$stored = get_option($key);
|
||||
if (is_scalar($stored) && is_scalar($normalized)) {
|
||||
return (string) $stored === (string) $normalized;
|
||||
}
|
||||
|
||||
return $stored == $normalized; // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
|
||||
}
|
||||
|
||||
/**
|
||||
* A write failed; restores the keys already written to their previous values and
|
||||
* reports write_failed (fully restored) or partial_write (some keys could not be
|
||||
@@ -223,5 +240,11 @@ final class WpSettingsUpdateAbility implements Ability
|
||||
global $wp_rewrite;
|
||||
$wp_rewrite->set_permalink_structure($structure);
|
||||
flush_rewrite_rules();
|
||||
|
||||
// set_permalink_structure() has no return value to check; verify the option
|
||||
// itself so a failure here is caught and rolled back like any other setting.
|
||||
if (! $this->isStored('permalink_structure', $structure)) {
|
||||
throw new RuntimeException('set_permalink_structure() did not update the stored value.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user