feat: lit/divi-variables-set warnt vor Verwendungen in Presets und beim Archivieren verwendeter Variablen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-19 14:10:57 +02:00
co-authored by Claude Sonnet 5.5
parent d7a8b85f8d
commit fef964bb7a
2 changed files with 98 additions and 2 deletions
@@ -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<array<string, mixed>> $diff
* @return list<string>
*/
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<string> $names */
private static function names(array $names): string
{
return implode(', ', array_slice($names, 0, self::LISTED)) . (count($names) > self::LISTED ? ', …' : '');
}
}
@@ -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']);
}
}