refactor: DetachTarget ersetzt das Array in DiviDesignDetachAbility::target(); Verhalten unverändert
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
fef964bb7a
commit
116d866c61
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Design;
|
||||
|
||||
use Closure;
|
||||
use LIT\MCP\Integrations\Divi\Library\UsageQuery;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* What lit/divi-design-detach removes (spec §6.8, rulings R10, V9), resolved on the live inventory: the usage query
|
||||
* and a fresh NodeRewrite per post for the PageRewriter and – when presets nest the target and no post_ids narrow the
|
||||
* call – the preset step, i.e. how the preset inventory changes (DesignChange, snapshot "presets"). Pure PHP.
|
||||
*/
|
||||
final class DetachTarget
|
||||
{
|
||||
/**
|
||||
* @param string $subject The target in messages: 'Variable „Eckenradius“'.
|
||||
* @param list<int>|null $postIds Only these posts; null = every usage.
|
||||
* @param Closure(): NodeRewrite $rewrite A fresh strategy per post.
|
||||
* @param (Closure(PresetBook): PresetBook)|null $presets The preset step; null = none.
|
||||
* @param string $nestedIn Where the preset step removes references (messages).
|
||||
* @param list<string> $notes Warnings of every answer.
|
||||
* @param string $doneHint The next step once nothing references the target.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $subject,
|
||||
public readonly ?array $postIds,
|
||||
public readonly UsageQuery $query,
|
||||
public readonly Closure $rewrite,
|
||||
public readonly ?Closure $presets,
|
||||
public readonly string $nestedIn,
|
||||
public readonly array $notes,
|
||||
public readonly string $doneHint
|
||||
) {
|
||||
}
|
||||
|
||||
/** The change in messages and in the change log of every saved post. */
|
||||
public function change(): string
|
||||
{
|
||||
return 'Entkoppeln von ' . $this->subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether nothing is left to detach: all usages, nothing skipped, and no usage this account may not read.
|
||||
*
|
||||
* @param list<array<string, mixed>> $skipped
|
||||
*/
|
||||
public function finished(array $skipped, int $hidden): bool
|
||||
{
|
||||
return $this->postIds === null && $skipped === [] && $hidden === 0;
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
self::VARIABLES . ' Bis dahin nur Presets angeben (preset).'
|
||||
);
|
||||
}
|
||||
$target = $this->target($input);
|
||||
$target = $this->presetTarget($input);
|
||||
if ($target instanceof WP_Error) {
|
||||
return $target;
|
||||
}
|
||||
@@ -163,10 +163,8 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
* The preset to detach, resolved on the live inventory.
|
||||
*
|
||||
* @param array<string, mixed> $input
|
||||
* @return array{scope: PresetScope, id: string, name: string, default: bool, nested: bool,
|
||||
* post_ids: list<int>|null, query: PresetUsageQuery, rewrite: Closure(): NodeRewrite, change: string}|WP_Error
|
||||
*/
|
||||
private function target(array $input): array|WP_Error
|
||||
private function presetTarget(array $input): DetachTarget|WP_Error
|
||||
{
|
||||
$preset = $input['preset'] ?? null;
|
||||
if (! is_array($preset)) {
|
||||
@@ -204,76 +202,69 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
|
||||
$id = (string) $found['id'];
|
||||
$label = is_string($found['name'] ?? null) ? $found['name'] : $id;
|
||||
$nested = $postIds === null && $book->nestedUsers($id) !== [];
|
||||
|
||||
return [
|
||||
'scope' => $scope,
|
||||
'id' => $id,
|
||||
'name' => $label,
|
||||
'default' => $book->defaultId($scope) === $id,
|
||||
'nested' => $postIds === null && $book->nestedUsers($id) !== [],
|
||||
'post_ids' => $postIds,
|
||||
return new DetachTarget(
|
||||
sprintf('%s „%s“', $scope->label(), $label),
|
||||
$postIds,
|
||||
// Explicit references only (R10), also for a default preset: no withGroups(), implicit uses stay.
|
||||
'query' => new PresetUsageQuery([['scope' => $scope, 'id' => $id, 'default' => false]]),
|
||||
'rewrite' => static fn (): NodeRewrite => new DetachRewrite($scope, $id),
|
||||
'change' => sprintf('Entkoppeln von %s „%s“', $scope->label(), $label),
|
||||
];
|
||||
new PresetUsageQuery([['scope' => $scope, 'id' => $id, 'default' => false]]),
|
||||
static fn (): NodeRewrite => new DetachRewrite($scope, $id),
|
||||
$nested ? static fn (PresetBook $presets): PresetBook => $presets->withoutNestedRef($id) : null,
|
||||
'anderen Presets',
|
||||
$book->defaultId($scope) === $id ? [sprintf(self::DEFAULT_NOTE, $label, $scope->name)] : [],
|
||||
self::DELETE_HINT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* ONE confirm_required with the pages and, when other presets nest the preset, the preset step's diff and
|
||||
* ONE confirm_required with the pages and, when presets nest the target, the preset step's diff and
|
||||
* design_rev; a normal result when nothing references it any more.
|
||||
*
|
||||
* @param array<string, mixed> $target
|
||||
* @return array<string, mixed>|WP_Error
|
||||
*/
|
||||
private function dryRun(array $target): array|WP_Error
|
||||
private function dryRun(DetachTarget $target): array|WP_Error
|
||||
{
|
||||
$preview = $this->pages->preview($target['query'], $target['rewrite'], $target['post_ids']);
|
||||
$preview = $this->pages->preview($target->query, $target->rewrite, $target->postIds);
|
||||
if ($preview instanceof WP_Error) {
|
||||
return $preview;
|
||||
}
|
||||
$presets = $target['nested'] ? $this->presetDryRun($target) : [];
|
||||
$presets = $target->presets !== null ? $this->presetDryRun($target) : [];
|
||||
if ($presets instanceof WP_Error) {
|
||||
return $presets;
|
||||
}
|
||||
|
||||
$notes = self::notes($target);
|
||||
if ($presets === [] && PageRewriteReport::isEmpty($preview)) {
|
||||
$summary = sprintf(
|
||||
'Keine Verweise auf %s „%s“ gefunden – nichts zu tun.',
|
||||
$target['scope']->label(),
|
||||
$target['name']
|
||||
);
|
||||
|
||||
$done = self::finished($target, $preview['skipped'], $preview['hidden_count']);
|
||||
$summary = sprintf('Keine Verweise auf %s gefunden – nichts zu tun.', $target->subject);
|
||||
$done = $target->finished($preview['skipped'], $preview['hidden_count']);
|
||||
|
||||
return PageRewriteReport::nothingToDo(
|
||||
$summary . ($done ? ' ' . self::DELETE_HINT : ''),
|
||||
$summary . ($done ? ' ' . $target->doneHint : ''),
|
||||
$preview,
|
||||
$notes
|
||||
$target->notes
|
||||
);
|
||||
}
|
||||
|
||||
$message = PageRewriteReport::dryRunMessage($preview, $target['change']);
|
||||
$message = PageRewriteReport::dryRunMessage($preview, $target->change());
|
||||
if ($presets !== []) {
|
||||
$message .= sprintf(
|
||||
' Zuerst entfernt der Aufruf die Verweise in %d anderen Presets (diff, mit Snapshot); dafür die '
|
||||
. 'design_rev mit confirm: true mitsenden.',
|
||||
count($presets['diff'])
|
||||
' Zuerst entfernt der Aufruf die Verweise in %d %s (diff, mit Snapshot); dafür die design_rev mit '
|
||||
. 'confirm: true mitsenden.',
|
||||
count($presets['diff']),
|
||||
$target->nestedIn
|
||||
);
|
||||
}
|
||||
|
||||
return Errors::confirmRequired($message, $preview + $presets + ['warnings' => $notes]);
|
||||
return Errors::confirmRequired($message, $preview + $presets + ['warnings' => $target->notes]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The preset step's dry run through DesignChange::run() without confirm: its confirm_required carries diff and
|
||||
* design_rev; a result without error means nothing nests the preset any more.
|
||||
* design_rev; a result without error means no preset nests the target any more.
|
||||
*
|
||||
* @param array<string, mixed> $target
|
||||
* @return array{diff: list<array<string, mixed>>, design_rev: string}|array{}|WP_Error
|
||||
*/
|
||||
private function presetDryRun(array $target): array|WP_Error
|
||||
private function presetDryRun(DetachTarget $target): array|WP_Error
|
||||
{
|
||||
$result = $this->change->run($this->name(), new PresetArea($this->writer), [], $this->nestedPlan($target));
|
||||
if (! $result instanceof WP_Error) {
|
||||
@@ -287,15 +278,12 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
return ['diff' => $data['diff'] ?? [], 'design_rev' => (string) ($data['design_rev'] ?? '')];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $target
|
||||
* @return Closure(array<array-key, mixed>): (DesignPlan|WP_Error)
|
||||
*/
|
||||
private function nestedPlan(array $target): Closure
|
||||
/** @return Closure(array<array-key, mixed>): (DesignPlan|WP_Error) */
|
||||
private function nestedPlan(DetachTarget $target): Closure
|
||||
{
|
||||
return function (array $raw) use ($target): DesignPlan|WP_Error {
|
||||
$before = PresetBook::fromDivi($raw);
|
||||
$after = $before->withoutNestedRef($target['id']);
|
||||
$after = $target->presets === null ? $before : ($target->presets)($before);
|
||||
$diff = PresetDiff::between($before, $after);
|
||||
if ($diff === []) {
|
||||
return new DesignPlan($after->data(), [], PresetDiff::summary([]));
|
||||
@@ -311,7 +299,7 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
$after->data(),
|
||||
$diff,
|
||||
PresetDiff::summary($diff),
|
||||
$this->usages->reportFor($target['query'])
|
||||
$this->usages->reportFor($target->query)
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -320,15 +308,14 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
* Preset step first (stops on any error – nothing written), then at most PAGE_LIMIT pages.
|
||||
*
|
||||
* @param array<string, mixed> $input
|
||||
* @param array<string, mixed> $target
|
||||
* @return array<string, mixed>|WP_Error
|
||||
* @throws WriteFailed When the preset step fails, or the first page fails without a preset step.
|
||||
* @throws DiviLocked Likewise.
|
||||
*/
|
||||
private function confirmed(array $input, array $target): array|WP_Error
|
||||
private function confirmed(array $input, DetachTarget $target): array|WP_Error
|
||||
{
|
||||
$presets = null;
|
||||
if ($target['nested']) {
|
||||
if ($target->presets !== null) {
|
||||
$presets = $this->change->run(
|
||||
$this->name(),
|
||||
new PresetArea($this->writer),
|
||||
@@ -344,10 +331,10 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
try {
|
||||
$pages = $this->pages->apply(
|
||||
$this->name(),
|
||||
$target['query'],
|
||||
$target['rewrite'],
|
||||
$target['post_ids'],
|
||||
$target['change']
|
||||
$target->query,
|
||||
$target->rewrite,
|
||||
$target->postIds,
|
||||
$target->change()
|
||||
);
|
||||
} catch (WriteFailed | DiviLocked $e) {
|
||||
// Thrown when the first page fails: the registrar would answer "nothing was saved" – untrue after the
|
||||
@@ -357,25 +344,24 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
}
|
||||
Log::failure($this->name(), $e);
|
||||
|
||||
return self::afterPresets(self::firstPageError($e), $presets);
|
||||
return self::afterPresets(self::firstPageError($e), $presets, $target);
|
||||
}
|
||||
if ($pages instanceof WP_Error) {
|
||||
return $wrotePresets ? self::afterPresets($pages, $presets) : $pages;
|
||||
return $wrotePresets ? self::afterPresets($pages, $presets, $target) : $pages;
|
||||
}
|
||||
|
||||
$summary = PageRewriteReport::savedSummary($pages, $target['change']);
|
||||
$summary = PageRewriteReport::savedSummary($pages, $target->change());
|
||||
if ($wrotePresets) {
|
||||
$summary = $presets['summary'] . ' ' . $summary;
|
||||
}
|
||||
$done = self::finished($target, $pages['skipped'], $pages['hidden_count']);
|
||||
if ($pages['remaining'] === 0 && $done) {
|
||||
$summary .= ' ' . self::DELETE_HINT;
|
||||
if ($pages['remaining'] === 0 && $target->finished($pages['skipped'], $pages['hidden_count'])) {
|
||||
$summary .= ' ' . $target->doneHint;
|
||||
}
|
||||
|
||||
return ToolResult::ok($summary)
|
||||
->snapshotId($presets['snapshot_id'] ?? null)
|
||||
->logId($presets['log_id'] ?? null)
|
||||
->warnings(array_merge($presets['warnings'] ?? [], $pages['warnings'], self::notes($target)))
|
||||
->warnings(array_merge($presets['warnings'] ?? [], $pages['warnings'], $target->notes))
|
||||
->with('saved', $pages['saved'])
|
||||
->with('remaining', $pages['remaining'])
|
||||
->with('skipped', $pages['skipped'])
|
||||
@@ -407,14 +393,15 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
*
|
||||
* @param array<string, mixed> $presets The preset step's ToolResult.
|
||||
*/
|
||||
private static function afterPresets(WP_Error $error, array $presets): WP_Error
|
||||
private static function afterPresets(WP_Error $error, array $presets, DetachTarget $target): WP_Error
|
||||
{
|
||||
$data = (array) $error->get_error_data();
|
||||
|
||||
return Errors::make(
|
||||
Errors::codeOf($error),
|
||||
Errors::clientMessage($error) . sprintf(
|
||||
' Die Verweise in anderen Presets sind bereits entfernt (Snapshot %s).',
|
||||
' Die Verweise in %s sind bereits entfernt (Snapshot %s).',
|
||||
$target->nestedIn,
|
||||
(string) $presets['snapshot_id']
|
||||
),
|
||||
(string) ($data['hint'] ?? ''),
|
||||
@@ -424,27 +411,6 @@ final class DiviDesignDetachAbility implements Ability, RequiresDiviFeatures
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether nothing is left to detach before lit/divi-presets-delete: all usages, nothing skipped, and no usage
|
||||
* this account may not read (delete would still answer in_use).
|
||||
*
|
||||
* @param array<string, mixed> $target
|
||||
* @param list<array<string, mixed>> $skipped
|
||||
*/
|
||||
private static function finished(array $target, array $skipped, int $hidden): bool
|
||||
{
|
||||
return $target['post_ids'] === null && $skipped === [] && $hidden === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $target
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function notes(array $target): array
|
||||
{
|
||||
return $target['default'] ? [sprintf(self::DEFAULT_NOTE, $target['name'], $target['scope']->name)] : [];
|
||||
}
|
||||
|
||||
/** @param array<string, string> $errors */
|
||||
private static function invalid(array $errors): WP_Error
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user