Requirement, Call, ConstantRequirement, Mirror, Problem, FeatureStatus, CompatibilityMatrix, Manifest, ManifestCheck sowie Fingerprints und LineDiff liegen jetzt im divi-freien Kern. Neu: GlobalRequirement, CompatibilityMatrix::fromProblems(), ManifestCheck-Option signatures, DiviManifestCheck::create() und DiviAreas. Divi-Verhalten unverändert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
4.5 KiB
PHP
115 lines
4.5 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Integrations\Divi\Design;
|
||
|
||
use ET\Builder\Packages\GlobalData\GlobalData;
|
||
use LIT\MCP\Compatibility\Call;
|
||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||
use LIT\MCP\Integrations\Divi\Release\DiviAreas;
|
||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||
use LIT\MCP\Compatibility\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(
|
||
DiviAreas::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');
|
||
}
|
||
|
||
/**
|
||
* Audit L5: GlobalData keeps a static copy of the variables that can be older than another writer's finished
|
||
* save (the option cache is dropped by DesignLock). Called under the lock, before reading.
|
||
*/
|
||
public function refresh(): void
|
||
{
|
||
self::forgetCached('variables');
|
||
}
|
||
|
||
/** Resets one entry of GlobalData's static runtime cache (private, so by reflection; gone = nothing to reset). */
|
||
private static function forgetCached(string $key): void
|
||
{
|
||
try {
|
||
$cache = new \ReflectionProperty(GlobalData::class, '_cached_data');
|
||
$data = $cache->getValue();
|
||
if (is_array($data) && array_key_exists($key, $data)) {
|
||
$data[$key] = null;
|
||
$cache->setValue(null, $data);
|
||
}
|
||
} catch (\ReflectionException) {
|
||
// Divi without that property: its reads are not cached this way.
|
||
}
|
||
}
|
||
}
|