From fef964bb7af1ff8e321b4bb331b1d7bd841b4c54 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Mon, 19 Oct 2026 14:10:57 +0200 Subject: [PATCH] feat: lit/divi-variables-set warnt vor Verwendungen in Presets und beim Archivieren verwendeter Variablen Co-Authored-By: Claude Sonnet 5.5 --- .../Divi/Design/DiviVariablesSetAbility.php | 57 ++++++++++++++++++- .../Design/DiviVariablesSetAbilityTest.php | 43 ++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/Integrations/Divi/Design/DiviVariablesSetAbility.php b/src/Integrations/Divi/Design/DiviVariablesSetAbility.php index 158e3b8..0ed7d36 100644 --- a/src/Integrations/Divi/Design/DiviVariablesSetAbility.php +++ b/src/Integrations/Divi/Design/DiviVariablesSetAbility.php @@ -10,6 +10,7 @@ use LIT\MCP\Abilities\Ability; use LIT\MCP\Abilities\Annotations; use LIT\MCP\Abilities\Errors; 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; @@ -22,7 +23,7 @@ defined('ABSPATH') || exit; * 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 * builder. Values are checked per type (VariableValue); an active variable needs one. The dry run lists the posts that - * reference a changed, archived or activated variable (ReferenceUsageQuery). + * reference a changed, archived or activated variable (ReferenceUsageQuery) and warns about presets that use it. */ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures { @@ -38,6 +39,13 @@ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures private const NEW_ENTRY = ['id' => '', 'label' => '', 'value' => '', 'order' => '', 'status' => 'active', 'lastUpdated' => '', 'variableType' => '']; + /** 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 archivierte Variablen; ' + . 'archivierte Variablen bleiben dort als Verweis stehen. lit/divi-design-detach mit variable ersetzt die ' + . 'Verweise vorher durch den Wert.'; + /** @param (Closure(int): string)|null $random Random characters for new IDs (VariableBook::newId(); tests). */ public function __construct( private DesignSystemWriter $writer, @@ -173,12 +181,14 @@ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures } } $touched = array_values(array_unique($touched)); + $report = $touched === [] ? null : $this->usages->reportFor(new ReferenceUsageQuery($touched)); return new DesignPlan( $book->data(), $diff, VariableDiff::summary($diff), - $touched === [] ? null : $this->usages->reportFor(new ReferenceUsageQuery($touched)) + $report, + $this->warnings($diff, $report) ); } @@ -370,4 +380,47 @@ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures return $id; } + + /** + * Ruling V8: presets that use a changed, archived or activated variable are warnings, never a block; archiving a + * variable that posts still use is allowed (V1a) and points to lit/divi-design-detach. + * + * @param list> $diff + * @return list + */ + private function warnings(array $diff, ?UsageReport $report): array + { + $presets = null; + $warnings = []; + $archives = false; + foreach ($diff as $row) { + if ($row['action'] === 'added') { + continue; + } + $archived = $row['action'] === 'archived'; + $archives = $archives || $archived; + $presets ??= PresetBook::fromDivi($this->writer->presets()); + $users = array_column($presets->referencing((string) $row['id']), 'name'); + if ($users !== []) { + $warnings[] = sprintf( + $archived + ? 'Variable „%s“ wird archiviert, aber noch von Presets verwendet: %s.' + : 'Die Änderung an Variable „%s“ wirkt auch auf die Presets %s.', + $row['name'], + self::names($users) + ); + } + } + if ($archives && $report !== null && $report->exists()) { + $warnings[] = self::IN_USE; + } + + return $warnings; + } + + /** @param list $names */ + private static function names(array $names): string + { + return implode(', ', array_slice($names, 0, self::LISTED)) . (count($names) > self::LISTED ? ', …' : ''); + } } diff --git a/tests/Unit/Integrations/Divi/Design/DiviVariablesSetAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviVariablesSetAbilityTest.php index c64ccd4..f2494a7 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviVariablesSetAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviVariablesSetAbilityTest.php @@ -316,4 +316,47 @@ final class DiviVariablesSetAbilityTest extends TestCase self::assertCount(2, $calls); self::assertSame(VariableBook::fromDivi(VariableAreaTest::stored())->data(), $calls[1], 'the snapshot again'); } + + /** Ruling V1a/V8: archiving a used variable is allowed; presets and posts are warnings, the dry run lists posts. */ + public function testArchivingAUsedVariableWarnsAndListsTheUsages(): void + { + $dry = self::assertToolError( + $this->ability(self::usages(self::used()), null, PresetBookTest::storedWithReferences()) + ->execute(['items' => [['id' => 'Slogan', 'status' => 'archived']]]), + 'confirm_required' + ); + + self::assertSame(7, $dry['affected'][0]['post_id']); + self::assertSame([ + 'Variable „Slogan“ wird archiviert, aber noch von Presets verwendet: Doppelt.', + 'Die unter affected genannten Inhalte verweisen auf geänderte oder archivierte Variablen; archivierte ' + . 'Variablen bleiben dort als Verweis stehen. lit/divi-design-detach mit variable ersetzt die Verweise ' + . 'vorher durch den Wert.', + ], $dry['warnings']); + } + + public function testChangingAVariableWarnsAboutThePresetsThatUseIt(): void + { + $dry = self::assertToolError( + $this->ability(null, null, PresetBookTest::storedWithReferences()) + ->execute(['items' => [['id' => 'Eckenradius', 'value' => '12px']]]), + 'confirm_required' + ); + + self::assertSame( + ['Die Änderung an Variable „Eckenradius“ wirkt auch auf die Presets Lesetext.'], + $dry['warnings'] + ); + } + + public function testNewVariablesHaveNoWarnings(): void + { + $dry = self::assertToolError( + $this->ability(null, null, PresetBookTest::storedWithReferences()) + ->execute(['items' => [['type' => 'strings', 'label' => 'Gruß', 'value' => 'Hallo']]]), + 'confirm_required' + ); + + self::assertSame([], $dry['warnings']); + } }