feat: PresetBook nennt Presets mit Verweisen auf Farben/Variablen und ersetzt Variablen-Verweise durch ihren Wert
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
a4ea487174
commit
653169a133
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace LIT\MCP\Integrations\Divi\Design;
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
||||||
use LIT\MCP\Abilities\Rev;
|
use LIT\MCP\Abilities\Rev;
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
@@ -21,6 +22,9 @@ final class PresetBook
|
|||||||
|
|
||||||
private const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
private const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||||
|
|
||||||
|
/** The keys of a preset that hold its settings (formats §3). */
|
||||||
|
private const SETTINGS = ['attrs', 'styleAttrs', 'renderAttrs'];
|
||||||
|
|
||||||
/** @param array{module: array<string, array{default: string, items: array<string, array<string, mixed>>}>,
|
/** @param array{module: array<string, array{default: string, items: array<string, array<string, mixed>>}>,
|
||||||
* group: array<string, array{default: string, items: array<string, array<string, mixed>>}>} $data */
|
* group: array<string, array{default: string, items: array<string, array<string, mixed>>}>} $data */
|
||||||
private function __construct(private array $data)
|
private function __construct(private array $data)
|
||||||
@@ -226,6 +230,59 @@ final class PresetBook
|
|||||||
return new self($data);
|
return new self($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presets whose settings (attrs, styleAttrs, renderAttrs) name the global color or variable $id – as a whole
|
||||||
|
* reference or inside a longer value (ruling V8: warnings of lit/divi-colors-set and lit/divi-variables-set).
|
||||||
|
*
|
||||||
|
* @return list<array{id: string, name: string, scope: string}> scope = module or group name
|
||||||
|
*/
|
||||||
|
public function referencing(string $id): array
|
||||||
|
{
|
||||||
|
$users = [];
|
||||||
|
foreach ($this->data as $records) {
|
||||||
|
foreach ($records as $scope => $record) {
|
||||||
|
foreach ($record['items'] as $presetId => $item) {
|
||||||
|
if (ReferenceUsageQuery::mentions(array_intersect_key($item, array_flip(self::SETTINGS)), [$id])) {
|
||||||
|
$users[] = [
|
||||||
|
'id' => (string) $presetId,
|
||||||
|
'name' => is_string($item['name'] ?? null) ? $item['name'] : (string) $presetId,
|
||||||
|
'scope' => (string) $scope,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $users;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inventory with every whole reference to global variable $id in the presets' settings replaced by $value
|
||||||
|
* (ruling V9, lit/divi-design-detach with variable). With $affixes the reference's own before/after settings frame
|
||||||
|
* the value (variables of type strings). References inside a longer value stay. Presets without a reference stay
|
||||||
|
* exactly as they are.
|
||||||
|
*/
|
||||||
|
public function withVariableResolved(string $id, string $value, bool $affixes = false): self
|
||||||
|
{
|
||||||
|
$data = $this->data;
|
||||||
|
foreach ($this->data as $kind => $records) {
|
||||||
|
foreach ($records as $name => $record) {
|
||||||
|
foreach ($record['items'] as $presetId => $item) {
|
||||||
|
foreach (self::SETTINGS as $key) {
|
||||||
|
if (! is_array($item[$key] ?? null)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$resolved = self::resolved($item[$key], $id, $value, $affixes);
|
||||||
|
if ($resolved !== $item[$key]) {
|
||||||
|
$data[$kind][$name]['items'][$presetId][$key] = $resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new self($data);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* IDs of the presets outside $touchedIds whose settings (attrs, styleAttrs, renderAttrs) hold a string with "<"
|
* IDs of the presets outside $touchedIds whose settings (attrs, styleAttrs, renderAttrs) hold a string with "<"
|
||||||
* or ">" – HTML or CSS that Divi's prepare_data() strips when a user without unfiltered_html saves the inventory
|
* or ">" – HTML or CSS that Divi's prepare_data() strips when a user without unfiltered_html saves the inventory
|
||||||
@@ -302,6 +359,28 @@ final class PresetBook
|
|||||||
return $nested;
|
return $nested;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<array-key, mixed> $settings
|
||||||
|
* @return array<array-key, mixed>
|
||||||
|
*/
|
||||||
|
private static function resolved(array $settings, string $id, string $value, bool $affixes): array
|
||||||
|
{
|
||||||
|
foreach ($settings as $key => $inner) {
|
||||||
|
if (is_array($inner)) {
|
||||||
|
$settings[$key] = self::resolved($inner, $id, $value, $affixes);
|
||||||
|
} elseif (is_string($inner) && VariableRef::nameOf($inner) === $id) {
|
||||||
|
$own = $affixes ? VariableRef::settingsOf($inner) : [];
|
||||||
|
$settings[$key] = self::text($own['before'] ?? '') . $value . self::text($own['after'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function text(mixed $value): string
|
||||||
|
{
|
||||||
|
return is_string($value) ? $value : '';
|
||||||
|
}
|
||||||
private static function hasMarkup(mixed $value): bool
|
private static function hasMarkup(mixed $value): bool
|
||||||
{
|
{
|
||||||
if (is_string($value)) {
|
if (is_string($value)) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PresetBook;
|
use LIT\MCP\Integrations\Divi\Design\PresetBook;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PresetDiff;
|
use LIT\MCP\Integrations\Divi\Design\PresetDiff;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PresetScope;
|
use LIT\MCP\Integrations\Divi\Design\PresetScope;
|
||||||
@@ -57,6 +58,101 @@ final class PresetBookTest extends TestCase
|
|||||||
return $stored;
|
return $stored;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stored() with references: CTA primär uses color gcid-holz000001, Doppelt variable gvid-slogan0001 (whole with
|
||||||
|
* before/after, and embedded in a longer value), Lesetext variable gvid-radius0001 in its styleAttrs.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public static function storedWithReferences(): array
|
||||||
|
{
|
||||||
|
$stored = self::stored();
|
||||||
|
$stored['module']['divi/button']['items']['aaaaaaaaaa']['attrs'] = [
|
||||||
|
'button' => ['decoration' => ['background' => ['desktop' => ['value' => [
|
||||||
|
'color' => VariableRef::serialize('color', 'gcid-holz000001', []),
|
||||||
|
]]]]],
|
||||||
|
];
|
||||||
|
$stored['module']['divi/button']['items']['bbbbbbbbbb']['attrs'] = [
|
||||||
|
'button' => ['innerContent' => ['desktop' => ['value' => [
|
||||||
|
'text' => VariableRef::serialize('content', 'gvid-slogan0001', ['before' => 'Ab ', 'after' => '!']),
|
||||||
|
]]]],
|
||||||
|
'module' => ['meta' => ['adminLabel' => ['desktop' => [
|
||||||
|
'value' => 'Vorher ' . VariableRef::serialize('content', 'gvid-slogan0001', []),
|
||||||
|
]]]],
|
||||||
|
];
|
||||||
|
$stored['group']['divi/font-body']['items']['gggggggggg']['styleAttrs'] = [
|
||||||
|
'font' => ['size' => VariableRef::serialize('content', 'gvid-radius0001', [])],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $stored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReferencingListsThePresetsThatNameAColorOrVariable(): void
|
||||||
|
{
|
||||||
|
$book = PresetBook::fromDivi(self::storedWithReferences());
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
[['id' => 'aaaaaaaaaa', 'name' => 'CTA primär', 'scope' => 'divi/button']],
|
||||||
|
$book->referencing('gcid-holz000001')
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
[['id' => 'bbbbbbbbbb', 'name' => 'Doppelt', 'scope' => 'divi/button']],
|
||||||
|
$book->referencing('gvid-slogan0001')
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
[['id' => 'gggggggggg', 'name' => 'Lesetext', 'scope' => 'divi/font-body']],
|
||||||
|
$book->referencing('gvid-radius0001')
|
||||||
|
);
|
||||||
|
self::assertSame([], $book->referencing('gvid-fehlt00001'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithVariableResolvedReplacesWholeReferencesOnly(): void
|
||||||
|
{
|
||||||
|
$stored = self::storedWithReferences();
|
||||||
|
$button = new PresetScope('module', 'divi/button');
|
||||||
|
$book = PresetBook::fromDivi($stored)->withVariableResolved('gvid-slogan0001', 'Holz mit Herz');
|
||||||
|
$attrs = $book->items($button)['bbbbbbbbbb']['attrs'];
|
||||||
|
|
||||||
|
self::assertSame('Holz mit Herz', $attrs['button']['innerContent']['desktop']['value']['text']);
|
||||||
|
self::assertSame(
|
||||||
|
$stored['module']['divi/button']['items']['bbbbbbbbbb']['attrs']['module'],
|
||||||
|
$attrs['module'],
|
||||||
|
'an embedded reference stays'
|
||||||
|
);
|
||||||
|
self::assertSame($stored['module']['divi/button']['items']['aaaaaaaaaa'], $book->items($button)['aaaaaaaaaa']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ruling V9: a strings variable keeps the before/after of the reference. */
|
||||||
|
public function testWithVariableResolvedFramesTheValueWithBeforeAndAfter(): void
|
||||||
|
{
|
||||||
|
$book = PresetBook::fromDivi(self::storedWithReferences())
|
||||||
|
->withVariableResolved('gvid-slogan0001', 'Holz mit Herz', true);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
'Ab Holz mit Herz!',
|
||||||
|
$book->items(new PresetScope('module', 'divi/button'))['bbbbbbbbbb']['attrs']['button']['innerContent']
|
||||||
|
['desktop']['value']['text']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithVariableResolvedReachesStyleAttrsAndLeavesOtherPresetsAlone(): void
|
||||||
|
{
|
||||||
|
$stored = self::storedWithReferences();
|
||||||
|
$book = PresetBook::fromDivi($stored)->withVariableResolved('gvid-radius0001', '8px');
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
['font' => ['size' => '8px']],
|
||||||
|
$book->items(new PresetScope('group', 'divi/font-body'))['gggggggggg']['styleAttrs']
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
PresetBook::fromDivi($stored)->items(new PresetScope('module', 'divi/button')),
|
||||||
|
$book->items(new PresetScope('module', 'divi/button'))
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
PresetBook::fromDivi($stored)->data(),
|
||||||
|
PresetBook::fromDivi($stored)->withVariableResolved('gvid-fehlt00001', 'x')->data()
|
||||||
|
);
|
||||||
|
}
|
||||||
/** Final review I-2: presets whose HTML/CSS Divi's prepare_data() would strip for editors. */
|
/** Final review I-2: presets whose HTML/CSS Divi's prepare_data() would strip for editors. */
|
||||||
public function testMarkupOutsideListsUntouchedPresetsWithHtmlOrCss(): void
|
public function testMarkupOutsideListsUntouchedPresetsWithHtmlOrCss(): void
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user