feat: lit/divi-design-detach entkoppelt Variablen – Verweise in Seiten und Presets werden durch den Wert ersetzt (V9)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
6e9218a2bc
commit
e951cc1353
@@ -21,27 +21,32 @@ use WP_Error;
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* lit/divi-design-detach (spec §6.8, ruling R10 – presets only): removes every explicit reference to one preset –
|
||||
* from the modulePreset/groupPreset stacks of pages, library entries and Theme Builder layouts (DetachRewrite through
|
||||
* the PageRewriter) and, for a group preset without post_ids, from the groupPresets of other presets (DesignChange,
|
||||
* snapshot "presets"). Modules fall back to the next preset of their stack or the default. The dry run is ONE
|
||||
* confirm_required with the pages and, when other presets nest it, the diff and design_rev of the preset step.
|
||||
* Confirmed, the preset step runs first – its plan carries the page usages, so a blocked live post refuses before
|
||||
* anything is written (P8a) – then at most PageRewriter::PAGE_LIMIT pages. A default preset keeps working implicitly
|
||||
* (Divi applies it); only explicit references go. Variables follow with Etappe 4c.
|
||||
* lit/divi-design-detach (spec §6.8, rulings R10, V9): removes every explicit reference to one preset or one global
|
||||
* variable from pages, library entries and Theme Builder layouts (DetachRewrite or VariableDetachRewrite through the
|
||||
* PageRewriter) and, without post_ids, from presets that nest it (DesignChange, snapshot "presets"): a group preset
|
||||
* leaves the groupPresets of other presets, a variable reference in a preset becomes the variable's value. Modules
|
||||
* fall back to the next preset of their stack or the default; a variable reference becomes the value (the variable
|
||||
* itself stays as it is). The dry run is ONE confirm_required with the pages and, when presets nest the target, the
|
||||
* diff and design_rev of the preset step. Confirmed, the preset step runs first – its plan carries the page usages,
|
||||
* so a blocked live post refuses before anything is written (P8a) – then at most PageRewriter::PAGE_LIMIT pages. A
|
||||
* default preset keeps working implicitly (Divi applies it); only explicit references go. Global colors have no
|
||||
* detach (ruling V9: they are only deactivated).
|
||||
*/
|
||||
final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
{
|
||||
private const VARIABLES = 'Variablen folgen mit Etappe 4c.';
|
||||
|
||||
private const DELETE_HINT = 'Jetzt kann lit/divi-presets-delete das Preset löschen.';
|
||||
|
||||
private const DEFAULT_NOTE = '„%s“ ist das Standard-Preset von %s: Divi wendet es weiter auf Module ohne eigenes '
|
||||
. 'Preset an; entfernt werden nur die ausdrücklichen Verweise.';
|
||||
|
||||
private const ARCHIVE_HINT = 'Jetzt kann lit/divi-variables-set die Variable archivieren (status: archived).';
|
||||
|
||||
private const NO_COLORS = 'Globale Farben lassen sich nicht entkoppeln, nur deaktivieren (lit/divi-colors-set).';
|
||||
|
||||
/** @param (Closure(): bool)|null $unfiltered Whether the user may save unfiltered HTML (DesignRights). */
|
||||
public function __construct(
|
||||
private DesignSystemWriter $writer,
|
||||
private GlobalVariablesAdapter $variables,
|
||||
private DesignChange $change,
|
||||
private GlobalUsages $usages,
|
||||
private PageRewriter $pages,
|
||||
@@ -57,23 +62,26 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return 'Divi-Preset-Verweise entfernen';
|
||||
return 'Divi-Preset- und Variablen-Verweise entfernen';
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Entfernt alle Verweise auf ein Preset (preset: module bzw. group + preset als Name oder ID) aus '
|
||||
. 'Seiten, Bibliothekseinträgen und Theme-Builder-Layouts und – bei Gruppen-Presets – aus anderen Presets; '
|
||||
. 'Module fallen auf das nächste Preset im Stapel bzw. das Standard-Preset zurück. post_ids beschränkt auf '
|
||||
. 'diese Seiten (Verweise in Presets bleiben dann). Zuerst Probelauf (pages; bei Verweisen in Presets diff '
|
||||
. 'und design_rev), nach Rückfrage beim Nutzer confirm: true (mit design_rev, wenn der Probelauf eine '
|
||||
. 'nennt); je Aufruf höchstens ' . PageRewriter::PAGE_LIMIT . ' Seiten, remaining nennt den Rest. Mit '
|
||||
. 'post_ids können nur die genannten Seiten das Schreiben blockieren und die Obergrenze für sehr viele '
|
||||
. 'Seiten gilt nicht; eine genannte veröffentlichte, private oder geplante Seite ohne Bearbeitungsrecht '
|
||||
. 'verweigert den ganzen Aufruf (forbidden, blocked_ids), eine genannte andere Seite ohne '
|
||||
. 'Bearbeitungsrecht steht in skipped (bei mehr als 2000 Live-Fundstellen entfällt die Vorabprüfung, '
|
||||
. 'dann auch eine genannte Live-Seite). Danach kann lit/divi-presets-delete das Preset löschen. '
|
||||
. self::VARIABLES;
|
||||
return 'Entfernt alle Verweise auf ein Preset (preset: module bzw. group + preset als Name oder ID) oder auf '
|
||||
. 'eine globale Variable (variable: Name oder ID) aus Seiten, Bibliothekseinträgen und '
|
||||
. 'Theme-Builder-Layouts und – bei Gruppen-Presets und Variablen – aus Presets; Module fallen auf das '
|
||||
. 'nächste Preset im Stapel bzw. das Standard-Preset zurück, ein Variablenverweis wird durch den aktuellen '
|
||||
. 'Wert der Variablen ersetzt (die Variable selbst bleibt; Verweise mitten in einem längeren Text bleiben '
|
||||
. 'stehen und stehen als Hinweis in warnings). post_ids beschränkt auf diese Seiten (Verweise in Presets '
|
||||
. 'bleiben dann). Zuerst Probelauf (pages; bei Verweisen in Presets diff und design_rev), nach Rückfrage '
|
||||
. 'beim Nutzer confirm: true (mit design_rev, wenn der Probelauf eine nennt); je Aufruf höchstens '
|
||||
. PageRewriter::PAGE_LIMIT . ' Seiten, remaining nennt den Rest. Mit post_ids können nur die genannten '
|
||||
. 'Seiten das Schreiben blockieren und die Obergrenze für sehr viele Seiten gilt nicht; eine genannte '
|
||||
. 'veröffentlichte, private oder geplante Seite ohne Bearbeitungsrecht verweigert den ganzen Aufruf '
|
||||
. '(forbidden, blocked_ids), eine genannte andere Seite ohne Bearbeitungsrecht steht in skipped (bei mehr '
|
||||
. 'als 2000 Live-Fundstellen entfällt die Vorabprüfung, dann auch eine genannte Live-Seite). Danach kann '
|
||||
. 'lit/divi-presets-delete das Preset löschen bzw. lit/divi-variables-set die Variable archivieren. '
|
||||
. self::NO_COLORS;
|
||||
}
|
||||
|
||||
public function inputSchema(): array
|
||||
@@ -90,7 +98,10 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
],
|
||||
'required' => ['preset'],
|
||||
],
|
||||
'variable' => ['type' => 'string', 'description' => self::VARIABLES],
|
||||
'variable' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Name oder ID einer globalen Variablen (statt preset). ' . self::NO_COLORS,
|
||||
],
|
||||
'post_ids' => PostIdsInput::schema(),
|
||||
] + DesignChange::inputProperties(),
|
||||
'additionalProperties' => false,
|
||||
@@ -117,11 +128,12 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
|
||||
/**
|
||||
* Like lit/divi-presets-assign (it rewrites every kind of post), plus the preset step that removes references
|
||||
* nested in other presets (PresetWrite).
|
||||
* nested in presets (PresetWrite); a variable is read and its rights checked through GlobalVariablesAdapter
|
||||
* (VariableWrite).
|
||||
*/
|
||||
public function diviFeatures(array $input): array
|
||||
{
|
||||
return [
|
||||
$features = [
|
||||
DiviFeature::PageRead,
|
||||
DiviFeature::PageWrite,
|
||||
DiviFeature::Catalog,
|
||||
@@ -132,26 +144,25 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
DiviFeature::ThemeBuilderWrite,
|
||||
DiviFeature::Roles,
|
||||
];
|
||||
|
||||
return array_key_exists('variable', $input) ? [...$features, DiviFeature::VariableWrite] : $features;
|
||||
}
|
||||
|
||||
public function permission(array $input): bool|WP_Error
|
||||
{
|
||||
return DesignRights::presets($this->writer) ?? true;
|
||||
return $this->rights($input) ?? true;
|
||||
}
|
||||
|
||||
public function execute(array $input): mixed
|
||||
{
|
||||
$refusal = DesignRights::presets($this->writer);
|
||||
$refusal = $this->rights($input);
|
||||
if ($refusal !== null) {
|
||||
return $refusal;
|
||||
}
|
||||
if (array_key_exists('variable', $input)) {
|
||||
return Errors::unsupported(
|
||||
'Verweise auf Variablen entfernt LIT MCP noch nicht; nichts wurde geändert.',
|
||||
self::VARIABLES . ' Bis dahin nur Presets angeben (preset).'
|
||||
);
|
||||
if (array_key_exists('variable', $input) && array_key_exists('preset', $input)) {
|
||||
return self::invalid(['variable' => 'entweder preset oder variable angeben, nicht beides']);
|
||||
}
|
||||
$target = $this->presetTarget($input);
|
||||
$target = array_key_exists('variable', $input) ? $this->variableTarget($input) : $this->presetTarget($input);
|
||||
if ($target instanceof WP_Error) {
|
||||
return $target;
|
||||
}
|
||||
@@ -159,6 +170,88 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
return ($input['confirm'] ?? false) === true ? $this->confirmed($input, $target) : $this->dryRun($target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spec §10: presets need the preset rights; a variable also the variable rights – the preset step writes presets
|
||||
* either way (V9).
|
||||
*
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
private function rights(array $input): ?WP_Error
|
||||
{
|
||||
$refusal = DesignRights::presets($this->writer);
|
||||
if ($refusal !== null || ! array_key_exists('variable', $input)) {
|
||||
return $refusal;
|
||||
}
|
||||
|
||||
return DesignRights::variables($this->writer, $this->variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* The global variable to detach, resolved on the live inventory; its current value replaces the references
|
||||
* (V9: whether it is active or archived does not matter).
|
||||
*
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
private function variableTarget(array $input): DetachTarget|WP_Error
|
||||
{
|
||||
$needle = is_string($input['variable'] ?? null) ? trim($input['variable']) : '';
|
||||
if ($needle === '') {
|
||||
return self::invalid(['variable' => 'Name oder ID der Variablen erwartet']);
|
||||
}
|
||||
try {
|
||||
$postIds = PostIdsInput::parse($input['post_ids'] ?? null);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return self::invalid(['post_ids' => $e->getMessage()]);
|
||||
}
|
||||
if (str_starts_with($needle, 'gcid-')) {
|
||||
return Errors::unsupported(
|
||||
sprintf('„%s“ ist eine globale Farbe; nichts wurde geändert.', $needle),
|
||||
self::NO_COLORS
|
||||
);
|
||||
}
|
||||
|
||||
$book = VariableBook::fromDivi($this->variables->read());
|
||||
try {
|
||||
$found = $book->find($needle);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return self::invalid(['variable' => $e->getMessage()]);
|
||||
}
|
||||
if ($found === null) {
|
||||
return Errors::notFound(
|
||||
sprintf('Unbekannte Variable „%s“; nichts wurde geändert.', $needle),
|
||||
'Vorhandene Variablen mit Namen und IDs liefert lit/divi-design-system (direkt: '
|
||||
. 'lit-divi-design-system). ' . self::NO_COLORS
|
||||
);
|
||||
}
|
||||
|
||||
$id = $found['id'];
|
||||
$type = $found['type'];
|
||||
$entry = $book->entry($type, $id) ?? [];
|
||||
$label = is_string($entry['label'] ?? null) && $entry['label'] !== '' ? $entry['label'] : $id;
|
||||
$value = $entry['value'] ?? null;
|
||||
if (! is_string($value)) {
|
||||
return Errors::unsupported(
|
||||
sprintf('Variable „%s“ (Typ %s) hat keinen Textwert; nichts wurde geändert.', $label, $type),
|
||||
'Verweise auf diese Variable im Divi-Builder lösen.'
|
||||
);
|
||||
}
|
||||
$nested = $postIds === null && PresetBook::fromDivi($this->writer->presets())->referencing($id) !== [];
|
||||
$affixes = $type === 'strings';
|
||||
|
||||
return new DetachTarget(
|
||||
sprintf('Variable „%s“', $label),
|
||||
$postIds,
|
||||
new ReferenceUsageQuery([$id]),
|
||||
static fn (): NodeRewrite => new VariableDetachRewrite($id, $value, $type),
|
||||
$nested
|
||||
? static fn (PresetBook $presets): PresetBook => $presets->withVariableResolved($id, $value, $affixes)
|
||||
: null,
|
||||
'Presets',
|
||||
[],
|
||||
self::ARCHIVE_HINT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The preset to detach, resolved on the live inventory.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,7 @@ use LIT\MCP\Integrations\Divi\Design\DiviPresetGetAbility;
|
||||
use LIT\MCP\Integrations\Divi\Design\DiviDesignRestoreAbility;
|
||||
use LIT\MCP\Integrations\Divi\Design\DiviDesignSnapshotsAbility;
|
||||
use LIT\MCP\Integrations\Divi\Design\DiviDesignDetachAbility;
|
||||
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Design\DiviPresetsAssignAbility;
|
||||
use LIT\MCP\Integrations\Divi\Design\DiviPresetsDeleteAbility;
|
||||
use LIT\MCP\Integrations\Divi\Design\DiviPresetsSetAbility;
|
||||
@@ -274,6 +275,7 @@ final class DiviIntegration implements Integration, Bootable
|
||||
),
|
||||
new DiviDesignDetachAbility(
|
||||
$this->designWriter,
|
||||
new GlobalVariablesAdapter(),
|
||||
$this->designChange(),
|
||||
$this->globalUsages(),
|
||||
$this->pageRewriter($loader),
|
||||
|
||||
Reference in New Issue
Block a user