feat: lit/divi-variables-set prüft Schrift-Variablen gegen Divis Schriftliste (mit Vorschlägen)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
dab4a9ba2e
commit
449ec5c33f
@@ -22,8 +22,9 @@ defined('ABSPATH') || exit;
|
|||||||
* pipeline (VariableArea). An item names an existing variable in id (ID or name) or creates one from type, label and
|
* pipeline (VariableArea). An item names an existing variable in id (ID or name) or creates one from type, label and
|
||||||
* value. Variables are never deleted – Divi refuses an inventory that lacks a stored ID (V1) –, only archived (order
|
* value. Variables are never deleted – Divi refuses an inventory that lacks a stored ID (V1) –, only archived (order
|
||||||
* "") and activated again (appended at the end of their type); the type of a variable stays, gradients are left to the
|
* "") and activated again (appended at the end of their type); the type of a variable stays, gradients are left to the
|
||||||
* builder. Values are checked per type (VariableValue); an active variable needs one. The dry run lists the posts that
|
* builder. Values are checked per type (VariableValue), font variables also against Divi's font list (FontCheck, ruling
|
||||||
* reference a changed, archived or activated variable (ReferenceUsageQuery) and warns about presets that use it.
|
* W12); an active variable needs one. The dry run lists the posts that reference a changed, archived or activated
|
||||||
|
* variable (ReferenceUsageQuery) and warns about presets that use it.
|
||||||
*/
|
*/
|
||||||
final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures
|
final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures
|
||||||
{
|
{
|
||||||
@@ -52,6 +53,7 @@ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures
|
|||||||
private GlobalVariablesAdapter $variables,
|
private GlobalVariablesAdapter $variables,
|
||||||
private DesignChange $change,
|
private DesignChange $change,
|
||||||
private GlobalUsages $usages,
|
private GlobalUsages $usages,
|
||||||
|
private FontCatalog $fonts,
|
||||||
private ?Closure $random = null
|
private ?Closure $random = null
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -229,7 +231,7 @@ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures
|
|||||||
try {
|
try {
|
||||||
$entry[$key] = match ($key) {
|
$entry[$key] = match ($key) {
|
||||||
'label' => self::cleanLabel($book, $item['label'], $id),
|
'label' => self::cleanLabel($book, $item['label'], $id),
|
||||||
'value' => self::value($type, $item['value']),
|
'value' => $this->value($type, $item['value']),
|
||||||
default => self::status($item['status']),
|
default => self::status($item['status']),
|
||||||
};
|
};
|
||||||
} catch (InvalidArgumentException $e) {
|
} catch (InvalidArgumentException $e) {
|
||||||
@@ -349,7 +351,7 @@ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @throws InvalidArgumentException */
|
/** @throws InvalidArgumentException */
|
||||||
private static function value(string $type, mixed $value): string
|
private function value(string $type, mixed $value): string
|
||||||
{
|
{
|
||||||
if (is_int($value)) {
|
if (is_int($value)) {
|
||||||
$value = (string) $value; // an attachment ID for images
|
$value = (string) $value; // an attachment ID for images
|
||||||
@@ -358,6 +360,9 @@ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures
|
|||||||
throw new InvalidArgumentException('Wert als Text erwartet');
|
throw new InvalidArgumentException('Wert als Text erwartet');
|
||||||
}
|
}
|
||||||
$problem = VariableValue::check($type, $value);
|
$problem = VariableValue::check($type, $value);
|
||||||
|
if ($problem === null && $type === 'fonts') {
|
||||||
|
$problem = FontCheck::problem($this->fonts, $value);
|
||||||
|
}
|
||||||
if ($problem !== null) {
|
if ($problem !== null) {
|
||||||
throw new InvalidArgumentException($problem);
|
throw new InvalidArgumentException($problem);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ defined('ABSPATH') || exit;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the value of a global variable before it goes to Divi (ruling V6, D7 principle). The type "gradients"
|
* Checks the value of a global variable before it goes to Divi (ruling V6, D7 principle). The type "gradients"
|
||||||
* (new in Divi 5.15) is passed through by VariableBook, but the tools never create or change it. The check of fonts
|
* (new in Divi 5.15) is passed through by VariableBook, but the tools never create or change it. Font names are only
|
||||||
* against Divi's font list follows with 4d. Pure PHP.
|
* checked for their form here; DiviVariablesSetAbility checks them against Divi's font list (FontCheck, ruling W12).
|
||||||
|
* Pure PHP.
|
||||||
*/
|
*/
|
||||||
final class VariableValue
|
final class VariableValue
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ use LIT\MCP\Integrations\Divi\Design\DiviPresetsAssignAbility;
|
|||||||
use LIT\MCP\Integrations\Divi\Design\DiviPresetsDeleteAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviPresetsDeleteAbility;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DiviPresetsSetAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviPresetsSetAbility;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DiviVariablesSetAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviVariablesSetAbility;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\FontCatalog;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PageRewriter;
|
use LIT\MCP\Integrations\Divi\Design\PageRewriter;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PresetAssignment;
|
use LIT\MCP\Integrations\Divi\Design\PresetAssignment;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PresetComposer;
|
use LIT\MCP\Integrations\Divi\Design\PresetComposer;
|
||||||
@@ -304,7 +305,8 @@ final class DiviIntegration implements Integration, Bootable
|
|||||||
$this->designWriter,
|
$this->designWriter,
|
||||||
$this->variablesAdapter,
|
$this->variablesAdapter,
|
||||||
$this->designChange(),
|
$this->designChange(),
|
||||||
$this->globalUsages()
|
$this->globalUsages(),
|
||||||
|
new FontCatalog()
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use LIT\MCP\Abilities\WriteFailed;
|
|||||||
use LIT\MCP\Integrations\Divi\Design\DesignChange;
|
use LIT\MCP\Integrations\Divi\Design\DesignChange;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DiviVariablesSetAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviVariablesSetAbility;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\FontCatalog;
|
||||||
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||||
use LIT\MCP\Integrations\Divi\Design\ReferenceUsageQuery;
|
use LIT\MCP\Integrations\Divi\Design\ReferenceUsageQuery;
|
||||||
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
||||||
@@ -48,7 +49,8 @@ final class DiviVariablesSetAbilityTest extends TestCase
|
|||||||
?SnapshotStore $snapshots = null,
|
?SnapshotStore $snapshots = null,
|
||||||
array $presets = [],
|
array $presets = [],
|
||||||
?GlobalVariablesAdapter $variables = null,
|
?GlobalVariablesAdapter $variables = null,
|
||||||
bool $manage = true
|
bool $manage = true,
|
||||||
|
?FontCatalog $fonts = null
|
||||||
): DiviVariablesSetAbility {
|
): DiviVariablesSetAbility {
|
||||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||||
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||||
@@ -60,6 +62,7 @@ final class DiviVariablesSetAbilityTest extends TestCase
|
|||||||
$variables ?? VariableAreaTest::adapter($this->stored, $this->saves, $manage),
|
$variables ?? VariableAreaTest::adapter($this->stored, $this->saves, $manage),
|
||||||
new DesignChange($snapshots ?? Mockery::mock(SnapshotStore::class), new FakeChangeLog(3)),
|
new DesignChange($snapshots ?? Mockery::mock(SnapshotStore::class), new FakeChangeLog(3)),
|
||||||
$usages ?? self::usages(self::unused()),
|
$usages ?? self::usages(self::unused()),
|
||||||
|
$fonts ?? FontCheckTest::catalog(),
|
||||||
static function (int $length) use (&$counter): string {
|
static function (int $length) use (&$counter): string {
|
||||||
return str_pad((string) ++$counter, $length, '0', STR_PAD_LEFT);
|
return str_pad((string) ++$counter, $length, '0', STR_PAD_LEFT);
|
||||||
}
|
}
|
||||||
@@ -377,4 +380,56 @@ final class DiviVariablesSetAbilityTest extends TestCase
|
|||||||
|
|
||||||
self::assertSame([], $dry['warnings']);
|
self::assertSame([], $dry['warnings']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ruling W12: a font variable names a font Divi knows exactly; unknown names get suggestions. */
|
||||||
|
public function testFontVariablesNeedAFontDiviKnows(): void
|
||||||
|
{
|
||||||
|
$error = $this->ability()
|
||||||
|
->execute(['items' => [['type' => 'fonts', 'label' => 'Marke', 'value' => 'robotto']]]);
|
||||||
|
|
||||||
|
self::assertToolError($error, 'validation_failed');
|
||||||
|
$fields = self::errorFields($error);
|
||||||
|
self::assertSame(['items[0].value'], array_keys($fields));
|
||||||
|
self::assertStringStartsWith('unbekannte Schrift „robotto“; ähnlich: ', $fields['items[0].value']);
|
||||||
|
self::assertStringContainsString('Roboto', $fields['items[0].value']);
|
||||||
|
self::assertSame([], $this->saves);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAFontVariableWithAKnownFontIsPlanned(): void
|
||||||
|
{
|
||||||
|
$dry = self::assertToolError(
|
||||||
|
$this->ability()->execute(['items' => [['type' => 'fonts', 'label' => 'Marke', 'value' => 'Roboto Slab']]]),
|
||||||
|
'confirm_required'
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame(['added'], array_column($dry['diff'], 'action'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The font list is loaded only for font variables (it costs 1.3 MB, ruling W5). */
|
||||||
|
public function testOtherTypesDoNotLoadTheFontList(): void
|
||||||
|
{
|
||||||
|
$fonts = Mockery::mock(FontCatalog::class);
|
||||||
|
$fonts->shouldNotReceive('has', 'names');
|
||||||
|
|
||||||
|
self::assertToolError(
|
||||||
|
$this->ability(fonts: $fonts)->execute(['items' => [
|
||||||
|
['type' => 'strings', 'label' => 'Gruß', 'value' => 'Hallo'],
|
||||||
|
['id' => 'Eckenradius', 'value' => '12px'],
|
||||||
|
]]),
|
||||||
|
'confirm_required'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** VariableValue checks first: a malformed name never reaches the font list. */
|
||||||
|
public function testAMalformedFontNameFailsBeforeTheFontList(): void
|
||||||
|
{
|
||||||
|
$fonts = Mockery::mock(FontCatalog::class);
|
||||||
|
$fonts->shouldNotReceive('has', 'names');
|
||||||
|
|
||||||
|
$error = $this->ability(fonts: $fonts)
|
||||||
|
->execute(['items' => [['type' => 'fonts', 'label' => 'Marke', 'value' => 'Roboto; x']]]);
|
||||||
|
|
||||||
|
self::assertToolError($error, 'validation_failed');
|
||||||
|
self::assertStringStartsWith('Schrift:', self::errorFields($error)['items[0].value']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user