fix(audit L4): Restore-Planer für Farben, Variablen und Presets berechnen die Fundstellen (affected, P8a blocked_ids) für geänderte, entfernte und deaktivierte Einträge

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-20 00:03:28 +02:00
co-authored by Claude Sonnet 5.5
parent 4a8a889f93
commit bd80ab1394
10 changed files with 274 additions and 26 deletions
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use WP_Error;
@@ -19,8 +20,11 @@ final class ColorRestorePlanner implements RestorePlanner
private const REMOVED = 'Farben, die es im Snapshot nicht gibt, entfallen (%s); Seiten und Presets, die sie '
. 'verwenden, behalten den Verweis ohne Wert.';
public function __construct(private DesignSystemWriter $writer, private GlobalColorsAdapter $colors)
{
public function __construct(
private DesignSystemWriter $writer,
private GlobalColorsAdapter $colors,
private GlobalUsages $usages
) {
}
public function area(): string
@@ -64,6 +68,13 @@ final class ColorRestorePlanner implements RestorePlanner
);
}
return new DesignPlan($after->data(), $diff, $summary, null, $warnings, true);
return new DesignPlan(
$after->data(),
$diff,
$summary,
RestoreUsages::ofReferences($this->usages, $diff),
$warnings,
true
);
}
}
@@ -5,6 +5,8 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
use Closure;
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
use LIT\MCP\Integrations\Divi\Library\UsageReport;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use WP_Error;
@@ -21,8 +23,11 @@ final class PresetRestorePlanner implements RestorePlanner
. 'ohne das Recht unfiltered_html verloren ginge; nichts wurde gespeichert.';
/** @param (Closure(): bool)|null $unfiltered Whether the user may save unfiltered HTML (DesignRights). */
public function __construct(private DesignSystemWriter $writer, private ?Closure $unfiltered = null)
{
public function __construct(
private DesignSystemWriter $writer,
private GlobalUsages $usages,
private ?Closure $unfiltered = null
) {
$this->unfiltered ??= DesignRights::unfilteredHtml();
}
@@ -65,10 +70,32 @@ final class PresetRestorePlanner implements RestorePlanner
$after->data(),
$diff,
sprintf('Snapshot %s wiederherstellen – %s', $snapshotId, PresetDiff::summary($diff)),
null,
$this->usagesOf($before, $diff),
['Seiten, die Presets verwenden, die es im Snapshot nicht gibt, behalten den Verweis; das Modul fällt '
. 'dann auf das Standard-Preset zurück.'],
true
);
}
/**
* Audit L4: the pages using a preset the restore changes or removes (spec §4 step 4, ruling P8a); a default
* preset also counts its implicit users (like lit/divi-presets-delete). The orphan warning stays (spec §5).
*
* @param list<array<string, mixed>> $diff
*/
private function usagesOf(PresetBook $before, array $diff): ?UsageReport
{
$targets = [];
foreach ($diff as $row) {
if (in_array($row['action'], RestoreUsages::TOUCHING, true)) {
$scope = new PresetScope((string) $row['kind'], (string) $row['scope']);
$targets[] = ['scope' => $scope, 'id' => (string) $row['id'],
'default' => $before->defaultId($scope) === $row['id']];
}
}
return $targets === []
? null
: $this->usages->reportFor(PresetUsageQuery::withGroups($targets, $this->writer->groupsOf(...)));
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
use LIT\MCP\Integrations\Divi\Library\UsageReport;
defined('ABSPATH') || exit;
/**
* The usages a snapshot restore has to report (audit L4, spec §4 step 4): the pages that use an entry the restore
* changes, removes or deactivates – DesignChange then blocks the confirmed restore for pages the caller may not edit
* (ruling P8a) and the dry run lists them as "affected".
*/
final class RestoreUsages
{
/** Diff actions that change what a page shows (colors: deactivated, variables: archived). */
public const TOUCHING = ['changed', 'removed', 'deactivated', 'archived'];
/**
* @param list<array<string, mixed>> $diff Rows with "id" (gcid-/gvid-) and "action"
*/
public static function ofReferences(GlobalUsages $usages, array $diff): ?UsageReport
{
$ids = [];
foreach ($diff as $row) {
if (in_array($row['action'], self::TOUCHING, true)) {
$ids[] = (string) $row['id'];
}
}
return $ids === [] ? null : $usages->reportFor(new ReferenceUsageQuery(array_values(array_unique($ids))));
}
}
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use WP_Error;
@@ -19,8 +20,11 @@ final class VariableRestorePlanner implements RestorePlanner
private const KEPT = 'Variable „%s“ gibt es im Snapshot nicht; sie bleibt archiviert erhalten (Divi löscht keine '
. 'Variablen).';
public function __construct(private DesignSystemWriter $writer, private GlobalVariablesAdapter $variables)
{
public function __construct(
private DesignSystemWriter $writer,
private GlobalVariablesAdapter $variables,
private GlobalUsages $usages
) {
}
public function area(): string
@@ -55,7 +59,7 @@ final class VariableRestorePlanner implements RestorePlanner
$after->data(),
$diff,
sprintf('Snapshot %s wiederherstellen – %s', $snapshotId, VariableDiff::summary($diff)),
null,
RestoreUsages::ofReferences($this->usages, $diff),
array_map(static fn (array $row): string => sprintf(self::KEPT, $row['label']), $before->onlyHere($older))
);
}
+3 -3
View File
@@ -301,9 +301,9 @@ final class DiviIntegration implements Integration, Bootable
new DiviDesignRestoreAbility(
$this->designChange(),
new SnapshotStore(),
new PresetRestorePlanner($this->designWriter, DesignRights::unfilteredHtml()),
new ColorRestorePlanner($this->designWriter, $this->colorsAdapter),
new VariableRestorePlanner($this->designWriter, $this->variablesAdapter),
new PresetRestorePlanner($this->designWriter, $this->globalUsages(), DesignRights::unfilteredHtml()),
new ColorRestorePlanner($this->designWriter, $this->colorsAdapter, $this->globalUsages()),
new VariableRestorePlanner($this->designWriter, $this->variablesAdapter, $this->globalUsages()),
new ThemeOptionsRestorePlanner($this->themeOptionsAdapter)
),
new DiviPresetsAssignAbility(