feat: ThemeOptionBook – unveränderlicher Bestand der Theme-Optionen mit rev() (W6)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-19 17:47:59 +02:00
co-authored by Claude Sonnet 5.5
parent a9edd850c1
commit d9c4bc3751
2 changed files with 195 additions and 0 deletions
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
use InvalidArgumentException;
use LIT\MCP\Abilities\Rev;
defined('ABSPATH') || exit;
/**
* The managed theme options as stored in get_option('et_divi') (ruling W6): only the whitelist keys that are
* set, in the fixed order of ThemeOptionSpec::KEYS, with the raw value and type (int, float, string or false –
* Divi stores its default font as false). A key that is not set is missing. Takes a snapshot or the whole et_divi
* array alike. Immutable – with() and without() return a new book, and a key is either set or missing, never both.
* rev() is the design_rev of the area "theme_options". Pure PHP.
*/
final class ThemeOptionBook
{
/** 1.0 and 1 are different stored values, so the hash must tell them apart (zero fraction kept). */
private const JSON_FLAGS = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION;
/** @param array<string, int|float|string|false> $data */
private function __construct(private array $data)
{
}
/** @param array<array-key, mixed> $raw Other keys, null and values of other types are dropped. */
public static function fromDivi(array $raw): self
{
$data = [];
foreach (ThemeOptionSpec::KEYS as $key) {
$value = $raw[$key] ?? null;
if ($value === false || is_int($value) || is_float($value) || is_string($value)) {
$data[$key] = $value;
}
}
return new self($data);
}
/** @return array<string, int|float|string|false> */
public function data(): array
{
return $this->data;
}
/** Hash of the canonical JSON; the key order is fixed, and 1, 1.0 and "1" differ. */
public function rev(): string
{
// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- pure PHP (spec §4.4).
$json = (string) json_encode($this->data, self::JSON_FLAGS);
return Rev::of('theme_options' . "\0" . $json);
}
public function has(string $key): bool
{
return array_key_exists($key, $this->data);
}
public function get(string $key): int|float|string|false|null
{
return $this->data[$key] ?? null;
}
/** @throws InvalidArgumentException For a key outside the whitelist. */
public function with(string $key, int|float|string|false $value): self
{
ThemeOptionSpec::definition($key);
return self::fromDivi([...$this->data, $key => $value]);
}
public function without(string $key): self
{
return self::fromDivi(array_diff_key($this->data, [$key => true]));
}
}