feat: lit/divi-colors-set löst {{color:…}} mit relative gegen den geplanten Bestand auf, lehnt Zyklen ab und warnt vor Verwendungen in Presets und relativen Farben
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
337b87b572
commit
2fd03f3900
@@ -9,7 +9,9 @@ use InvalidArgumentException;
|
||||
use LIT\MCP\Abilities\Ability;
|
||||
use LIT\MCP\Abilities\Annotations;
|
||||
use LIT\MCP\Abilities\Errors;
|
||||
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
||||
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
|
||||
use LIT\MCP\Integrations\Divi\Library\UsageReport;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||
use LIT\MCP\Integrations\Divi\Release\RequiresDiviFeatures;
|
||||
use WP_Error;
|
||||
@@ -20,8 +22,10 @@ defined('ABSPATH') || exit;
|
||||
* lit/divi-colors-set (spec §7.1, rulings V3–V5, V8): creates and changes global colors through the change pipeline
|
||||
* (ColorArea). An item names an existing color in id (ID, label, alias primary … or option name accent_color …) or
|
||||
* creates one from label and color. Colors are never deleted, only deactivated; the five main colors only change
|
||||
* their value. The dry run lists the posts that reference a changed, deactivated or activated color
|
||||
* (ReferenceUsageQuery).
|
||||
* their value, never to a reference. {{color:…}} with relative is resolved against the planned inventory – also
|
||||
* colors of the same call, in item order – and stored as Divi reference (VariableRef); cycles and chains deeper than
|
||||
* 10 are refused. The dry run lists the posts that reference a changed, deactivated or activated color
|
||||
* (ReferenceUsageQuery) and warns about presets and relative colors built on it.
|
||||
*/
|
||||
final class DiviColorsSetAbility implements Ability, RequiresDiviFeatures
|
||||
{
|
||||
@@ -39,6 +43,17 @@ final class DiviColorsSetAbility implements Ability, RequiresDiviFeatures
|
||||
private const NEW_ENTRY = ['color' => '', 'folder' => '', 'label' => '', 'lastUpdated' => '', 'status' => 'active',
|
||||
'usedInPosts' => []];
|
||||
|
||||
private const PLACEHOLDER = '/^\{\{\s*color\s*:\s*([^{}]+?)\s*\}\}$/D';
|
||||
|
||||
/** Exception code of value(): the problem belongs to the item's relative, not to its color. */
|
||||
private const RELATIVE = 1;
|
||||
|
||||
/** At most this many names per warning. */
|
||||
private const LISTED = 10;
|
||||
|
||||
private const IN_USE = 'Die unter affected genannten Inhalte verweisen auf geänderte oder deaktivierte Farben; '
|
||||
. 'deaktivierte Farben bleiben dort als Verweis stehen.';
|
||||
|
||||
/** @param (Closure(int): string)|null $random Random characters for new IDs (ColorBook::newId(); tests). */
|
||||
public function __construct(
|
||||
private DesignSystemWriter $writer,
|
||||
@@ -172,6 +187,13 @@ final class DiviColorsSetAbility implements Ability, RequiresDiviFeatures
|
||||
}
|
||||
$book = $this->apply($book, $item, $field, $errors);
|
||||
}
|
||||
$cycle = $errors === [] ? $book->cycle() : null;
|
||||
if ($cycle !== null) {
|
||||
$errors['items'] = sprintf(
|
||||
'Relative Farben bauen im Kreis aufeinander auf oder sind tiefer als 10 Stufen verschachtelt: %s',
|
||||
implode(' → ', array_map($book->label(...), $cycle))
|
||||
);
|
||||
}
|
||||
if ($errors !== []) {
|
||||
return Errors::invalidInput('Ungültige Farb-Angaben; nichts wurde gespeichert.', $errors);
|
||||
}
|
||||
@@ -184,12 +206,14 @@ final class DiviColorsSetAbility implements Ability, RequiresDiviFeatures
|
||||
}
|
||||
}
|
||||
$touched = array_values(array_unique($touched));
|
||||
$report = $touched === [] ? null : $this->usages->reportFor(new ReferenceUsageQuery($touched));
|
||||
|
||||
return new DesignPlan(
|
||||
$book->data(),
|
||||
$diff,
|
||||
ColorDiff::summary($diff),
|
||||
$touched === [] ? null : $this->usages->reportFor(new ReferenceUsageQuery($touched))
|
||||
$report,
|
||||
$this->warnings($book, $diff, $report)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -225,12 +249,15 @@ final class DiviColorsSetAbility implements Ability, RequiresDiviFeatures
|
||||
'label' => self::cleanLabel($book, $item['label'], $id),
|
||||
'folder' => self::folder($item['folder']),
|
||||
'status' => self::status($item['status']),
|
||||
default => self::value($item),
|
||||
default => self::value($book, $item, $main),
|
||||
};
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$errors[$field . '.' . $key] = $e->getMessage();
|
||||
$errors[$field . '.' . ($e->getCode() === self::RELATIVE ? 'relative' : $key)] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
if (array_key_exists('relative', $item) && ! array_key_exists('color', $item)) {
|
||||
$errors[$field . '.relative'] = 'nur zusammen mit color als Platzhalter {{color:…}}';
|
||||
}
|
||||
if (count($errors) > $known || ($id !== null && $entry === $book->entry($id))) {
|
||||
return $book;
|
||||
}
|
||||
@@ -313,22 +340,52 @@ final class DiviColorsSetAbility implements Ability, RequiresDiviFeatures
|
||||
}
|
||||
|
||||
/**
|
||||
* The stored value of $item's color (ruling V5): a checked fixed value, or {{color:…}} with relative resolved
|
||||
* against $book (the planned inventory) into a Divi reference. A main color only takes a fixed value – Divi keeps
|
||||
* it in et_divi, where a reference would break the Customizer.
|
||||
*
|
||||
* @param array<array-key, mixed> $item
|
||||
* @throws InvalidArgumentException
|
||||
* @throws InvalidArgumentException Code RELATIVE when the problem is in relative.
|
||||
*/
|
||||
private static function value(array $item): string
|
||||
private static function value(ColorBook $book, array $item, bool $main): string
|
||||
{
|
||||
$color = $item['color'];
|
||||
if (! is_string($color)) {
|
||||
throw new InvalidArgumentException('Farbwert als Text erwartet');
|
||||
}
|
||||
$color = trim($color);
|
||||
$problem = ColorValue::check($color);
|
||||
if ($problem !== null) {
|
||||
throw new InvalidArgumentException($problem);
|
||||
if (preg_match(self::PLACEHOLDER, $color, $match) !== 1) {
|
||||
if (array_key_exists('relative', $item)) {
|
||||
throw new InvalidArgumentException('nur zusammen mit einem Platzhalter {{color:…}}', self::RELATIVE);
|
||||
}
|
||||
$problem = ColorValue::check($color);
|
||||
if ($problem !== null) {
|
||||
throw new InvalidArgumentException($problem);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
return $color;
|
||||
if ($main) {
|
||||
throw new InvalidArgumentException('Hauptfarben brauchen einen festen Farbwert, keinen Platzhalter');
|
||||
}
|
||||
$base = $book->find($match[1]);
|
||||
if ($base === null) {
|
||||
throw new InvalidArgumentException(sprintf('unbekannte Farbe „%s“ im Platzhalter', $match[1]));
|
||||
}
|
||||
$relative = $item['relative'] ?? null;
|
||||
if (! is_array($relative)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Pflicht mit einem Platzhalter: {hue, saturation, lightness, opacity}',
|
||||
self::RELATIVE
|
||||
);
|
||||
}
|
||||
$settings = ColorValue::relative($relative);
|
||||
if (is_string($settings)) {
|
||||
throw new InvalidArgumentException($settings, self::RELATIVE);
|
||||
}
|
||||
|
||||
return VariableRef::serialize('color', $base, $settings);
|
||||
}
|
||||
|
||||
private function newId(ColorBook $book): string
|
||||
@@ -339,4 +396,57 @@ final class DiviColorsSetAbility implements Ability, RequiresDiviFeatures
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruling V8: presets and relative colors built on a changed, deactivated or activated color are warnings, never
|
||||
* a block.
|
||||
*
|
||||
* @param list<array<string, mixed>> $diff
|
||||
* @return list<string>
|
||||
*/
|
||||
private function warnings(ColorBook $book, array $diff, ?UsageReport $report): array
|
||||
{
|
||||
$presets = null;
|
||||
$warnings = [];
|
||||
$deactivates = false;
|
||||
foreach ($diff as $row) {
|
||||
if ($row['action'] === 'added') {
|
||||
continue;
|
||||
}
|
||||
$deactivated = $row['action'] === 'deactivated';
|
||||
$deactivates = $deactivates || $deactivated;
|
||||
$presets ??= PresetBook::fromDivi($this->writer->presets());
|
||||
$users = array_column($presets->referencing((string) $row['id']), 'name');
|
||||
if ($users !== []) {
|
||||
$warnings[] = sprintf(
|
||||
$deactivated
|
||||
? 'Farbe „%s“ wird deaktiviert, aber noch von Presets verwendet: %s.'
|
||||
: 'Die Änderung an Farbe „%s“ wirkt auch auf die Presets %s.',
|
||||
$row['name'],
|
||||
self::names($users)
|
||||
);
|
||||
}
|
||||
$derived = array_map($book->label(...), $book->referencing((string) $row['id']));
|
||||
if ($derived !== []) {
|
||||
$warnings[] = sprintf(
|
||||
$deactivated
|
||||
? 'Farbe „%1$s“ wird deaktiviert, aber die relativen Farben %2$s bauen auf ihr auf.'
|
||||
: 'Die relativen Farben %2$s bauen auf Farbe „%1$s“ auf und ändern sich mit.',
|
||||
$row['name'],
|
||||
self::names($derived)
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($deactivates && $report !== null && $report->exists()) {
|
||||
$warnings[] = self::IN_USE;
|
||||
}
|
||||
|
||||
return $warnings;
|
||||
}
|
||||
|
||||
/** @param list<string> $names */
|
||||
private static function names(array $names): string
|
||||
{
|
||||
return implode(', ', array_slice($names, 0, self::LISTED)) . (count($names) > self::LISTED ? ', …' : '');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user