feat: Fähigkeiten color-write/variable-write; GlobalColorsAdapter und GlobalVariablesAdapter (Manifest, Signatur-Referenzen) und DesignRights::colors()/variables()

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-19 13:55:09 +02:00
co-authored by Claude Sonnet 5.5
parent d73144ab36
commit 9b7c3c1d3b
12 changed files with 537 additions and 19 deletions
@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
use ET\Builder\Packages\GlobalData\GlobalData;
use LIT\MCP\Integrations\Divi\Release\Call;
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\Manifest;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use RuntimeException;
use WP_Error;
defined('ABSPATH') || exit;
/**
* Adapter "Designsystem" (spec §3) for the global variables (formats §2, plan 4c V7): reads them through
* GlobalData::get_global_variables() back into the stored form and writes them through set_global_variables() with
* the inventory the change was planned on (Divi's own stale check). Without edit_theme_options or Divi's role right
* "Variables Manager" Divi silently saves nothing – DesignRights::variables() asks canManage() first. Not final so
* the variable area can be tested with a mock.
*/
class GlobalVariablesAdapter implements DiviAdapter
{
public static function manifest(): Manifest
{
return new Manifest(
Manifest::DESIGN,
calls: [
new Call(GlobalData::class . '::get_global_variables', 0),
new Call(GlobalData::class . '::set_global_variables', 2),
new Call('et_pb_is_allowed', 1),
],
features: [DiviFeature::VariableWrite]
);
}
/**
* The stored form: type => ID => entry as arrays, without allowedActions, without the customizer fonts and the
* group "colors" (both are not stored there), empty groups left out.
*
* @return array<string, array<string, array<string, mixed>>>
*/
public function read(): array
{
$data = [];
foreach (GlobalData::get_global_variables() as $type => $items) {
if ($type === 'colors' || (! is_array($items) && ! is_object($items))) {
continue;
}
foreach ((array) $items as $id => $item) {
// Divi's sanitizing keeps gvid- IDs only; the customizer fonts (--et_global_*_font) are not stored.
if (! str_starts_with((string) $id, 'gvid-') || (! is_array($item) && ! is_object($item))) {
continue;
}
$entry = (array) $item;
unset($entry['allowedActions']);
$data[(string) $type][(string) $id] = $entry;
}
}
return $data;
}
/**
* @param array<string, array<string, array<string, mixed>>> $data The whole new inventory (stored form).
* @param array<string, array<string, array<string, mixed>>> $expected The inventory the change was planned on.
* @throws DiviLocked When variable-write is not released – before anything is written.
* @throws RuntimeException When Divi refuses the inventory (stale, missing IDs, invalid entries).
*/
public function save(array $data, array $expected): void
{
ReleaseGate::assertFeature(DiviFeature::VariableWrite);
$refusal = GlobalData::set_global_variables($data, $expected);
if ($refusal instanceof WP_Error) {
throw new RuntimeException('global variables refused: ' . $refusal->get_error_code() . ' '
. $refusal->get_error_message());
}
}
/** Divi's role right "Variables Manager"; fail closed without Divi. */
public function canManage(): bool
{
return function_exists('et_pb_is_allowed') && (bool) et_pb_is_allowed('variables_manager');
}
}