feat: lit/divi-theme-options-get liest Wert, wirksamen Wert, Standard und Bereich je Theme-Option

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-19 17:51:18 +02:00
co-authored by Claude Sonnet 5.5
parent 1bc8ce65b7
commit 122548db12
2 changed files with 306 additions and 0 deletions
@@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
use LIT\MCP\Abilities\Ability;
use LIT\MCP\Abilities\Annotations;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\RequiresDiviFeatures;
use WP_Error;
defined('ABSPATH') || exit;
/**
* lit/divi-theme-options-get (spec §8.1, rulings W2, W3): the 20 whitelisted Divi theme options – per key the stored
* value (null = unset), the value in effect (an unset tablet/phone size inherits the larger size), Divi's default,
* type, range or choices – and the design_rev of the area. Reading needs what lit/divi-design-system needs.
*/
final class DiviThemeOptionsGetAbility implements Ability, RequiresDiviFeatures
{
/** Fields of ThemeOptionSpec::definition() passed through as they are. */
private const RULES = ['min', 'max', 'step', 'choices', 'inherits'];
private const NOTES = [
'Ändern mit lit/divi-theme-options-update: set {Schlüssel: Wert}; null setzt auf den Divi-Standard zurück.',
'Ohne eigenen Wert erben Tablet- und Smartphone-Größen die nächstgrößere Größe (inherits).',
'Die fünf Hauptfarben ändert lit/divi-colors-set; Buttons, Logo und SEO bleiben im Divi-Customizer.',
];
public function __construct(private ThemeOptionsAdapter $options)
{
}
public function name(): string
{
return 'lit/divi-theme-options-get';
}
public function label(): string
{
return 'Divi-Theme-Optionen lesen';
}
public function description(): string
{
return 'Liest Divis Theme-Optionen für Schriften, Schriftgrößen und Layout: je Schlüssel den gespeicherten '
. 'Wert (null = nicht gesetzt), den wirksamen Wert, den Divi-Standard, Typ und Bereich oder Auswahl, dazu '
. 'die design_rev. keys grenzt auf einzelne Schlüssel ein. Ändern mit lit/divi-theme-options-update.';
}
public function inputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'keys' => [
'type' => 'array',
'items' => ['type' => 'string', 'enum' => ThemeOptionSpec::KEYS],
'description' => 'Nur diese Theme-Optionen liefern (Standard: alle).',
],
],
'additionalProperties' => false,
];
}
public function outputSchema(): ?array
{
return [
'type' => 'object',
'properties' => [
'options' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'key' => ['type' => 'string'],
'label' => ['type' => 'string'],
'type' => ['type' => 'string'],
'is_default' => ['type' => 'boolean'],
],
'required' => ['key', 'label', 'type', 'value', 'effective', 'default', 'is_default'],
],
],
'design_rev' => ['type' => 'string'],
'notes' => ['type' => 'array', 'items' => ['type' => 'string']],
],
'required' => ['options', 'design_rev', 'notes'],
];
}
public function annotations(): array
{
return Annotations::read();
}
public function isDirect(): bool
{
return false;
}
/** Reads et_divi and the Customizer table (ThemeOptionsAdapter, ThemeOptionSpec mirror). */
public function diviFeatures(array $input): array
{
return [DiviFeature::ThemeOptionRead];
}
public function permission(array $input): bool|WP_Error
{
return self::rights() ?? true;
}
public function execute(array $input): mixed
{
$refusal = self::rights();
if ($refusal !== null) {
return $refusal;
}
$keys = self::keys($input);
if ($keys instanceof WP_Error) {
return $keys;
}
$book = ThemeOptionBook::fromDivi($this->options->read());
$rows = [];
foreach ($keys as $key) {
$definition = ThemeOptionSpec::definition($key);
$rows[] = [
'key' => $key,
'label' => $definition['label'],
'type' => $definition['type'],
'value' => $book->get($key),
'effective' => ThemeOptionSpec::effective($key, $book->data()),
'default' => ThemeOptionSpec::effective($key, $book->without($key)->data()),
'is_default' => ! $book->has($key),
] + array_intersect_key($definition, array_flip(self::RULES));
}
return ['options' => $rows, 'design_rev' => $book->rev(), 'notes' => self::NOTES];
}
/** Like lit/divi-design-system: whoever edits content may read the design. */
private static function rights(): ?WP_Error
{
return current_user_can('edit_posts') ? null : Errors::forbidden('Keine Berechtigung, Inhalte zu bearbeiten.');
}
/**
* @param array<string, mixed> $input
* @return list<string>|WP_Error The requested keys in ThemeOptionSpec::KEYS order (all without "keys").
*/
private static function keys(array $input): array|WP_Error
{
$keys = $input['keys'] ?? ThemeOptionSpec::KEYS;
if (! is_array($keys) || ! array_is_list($keys)) {
return Errors::invalidInput('keys muss eine Liste sein.', ['keys' => 'Liste von Theme-Optionen']);
}
$errors = [];
foreach ($keys as $index => $key) {
if (! is_string($key) || ! ThemeOptionSpec::isKey($key)) {
$errors[sprintf('keys[%d]', $index)] = 'unbekannte Theme-Option; erlaubt sind die Schlüssel aus '
. 'lit/divi-theme-options-get ohne keys';
}
}
if ($errors !== []) {
return Errors::invalidInput('Unbekannte Theme-Optionen.', $errors);
}
return array_values(array_intersect(ThemeOptionSpec::KEYS, $keys));
}
}