area => planner */ private array $planners = []; public function __construct( private DesignChange $change, private SnapshotStore $snapshots, RestorePlanner ...$planners ) { foreach ($planners as $planner) { $this->planners[$planner->area()] = $planner; } } public function name(): string { return 'lit/divi-design-restore'; } public function label(): string { return 'Designsystem wiederherstellen'; } public function description(): string { return 'Stellt einen Snapshot des Designsystems wieder her (snapshot_id aus lit/divi-design-snapshots oder ' . 'einer Änderungsantwort). Zuerst Probelauf (Unterschied zum aktuellen Stand, design_rev), nach Rückfrage ' . 'beim Nutzer confirm: true; vorher wird der aktuelle Stand selbst gesichert. Bereiche: presets, colors ' . '(Farben, die es im Snapshot nicht gibt, entfallen), variables (Variablen, die erst nach dem Snapshot ' . 'entstanden, bleiben archiviert erhalten) und theme_options (Theme-Optionen, die im Snapshot nicht ' . 'gesetzt sind, kehren zum Divi-Standard zurück) und custom_css (das ganze eigene CSS, nur für dasselbe ' . 'Theme).'; } public function inputSchema(): array { return [ 'type' => 'object', 'properties' => ['snapshot_id' => ['type' => 'string']] + DesignChange::inputProperties(), 'required' => ['snapshot_id'], 'additionalProperties' => false, ]; } public function outputSchema(): ?array { return DesignChange::outputSchema(); } public function annotations(): array { return Annotations::destructive(false); } public function isDirect(): bool { return false; } /** The features of the snapshot's area; without a restorable area those of every area (fail closed). */ public function diviFeatures(array $input): array { return FeatureSets::merge(...array_map( static fn (RestorePlanner $planner): array => $planner->features(), $this->plannersFor($input) )); } public function permission(array $input): bool|WP_Error { return $this->rights($input) ?? true; } public function execute(array $input): mixed { $refusal = $this->rights($input); if ($refusal !== null) { return $refusal; } $id = is_string($input['snapshot_id'] ?? null) ? $input['snapshot_id'] : ''; $area = self::areaOf($input); if ($area === null) { return Errors::invalidInput( 'Ungültige Snapshot-ID.', ['snapshot_id' => 'z. B. presets-20261016T101500Z-a1b2c3'] ); } $planner = $this->planners[$area] ?? null; if ($planner === null) { return Errors::unsupported( 'Snapshots dieses Bereichs lassen sich hier nicht wiederherstellen.', 'Den Stand des Snapshots im Divi-Builder bzw. -Customizer von Hand herstellen.' ); } $snapshot = $this->snapshots->get($area, $id); if ($snapshot === null) { return Errors::notFound( sprintf('Snapshot %s gibt es nicht (mehr).', $id), 'Vorhandene Snapshots mit lit/divi-design-snapshots auflisten; je Bereich bleiben die letzten 20.' ); } return $this->change->run( $this->name(), $planner->designArea(), $input, static fn (array $raw): DesignPlan|WP_Error => $planner->plan($raw, $snapshot['data'], $id) ); } /** * Spec §10: the rights of the snapshot's area; without a restorable area those of every area (fail closed). * * @param array $input */ private function rights(array $input): ?WP_Error { foreach ($this->plannersFor($input) as $planner) { $refusal = $planner->rights(); if ($refusal !== null) { return $refusal; } } return null; } /** * @param array $input * @return list The planner of the snapshot's area, or every planner. */ private function plannersFor(array $input): array { $planner = $this->planners[self::areaOf($input) ?? ''] ?? null; return $planner !== null ? [$planner] : array_values($this->planners); } /** @param array $input */ private static function areaOf(array $input): ?string { $id = $input['snapshot_id'] ?? null; return is_string($id) && preg_match(self::ID, $id, $match) === 1 ? $match[1] : null; } }