fix: Standard-Preset löschen – nur ausdrückliche Verweise blockieren, implizite Nutzung im Probelauf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
75714429be
commit
3d760113ec
@@ -17,8 +17,9 @@ defined('ABSPATH') || exit;
|
|||||||
/**
|
/**
|
||||||
* lit/divi-presets-delete (spec §6.4, G5): deletes presets nothing uses any more – no page, library entry or Theme
|
* lit/divi-presets-delete (spec §6.4, G5): deletes presets nothing uses any more – no page, library entry or Theme
|
||||||
* Builder layout (GlobalUsages with PresetUsageQuery), and no other preset that nests it. A used preset is refused
|
* Builder layout (GlobalUsages with PresetUsageQuery), and no other preset that nests it. A used preset is refused
|
||||||
* with its usages (in_use); lit/divi-design-detach (Etappe 4b) removes the references first. A default preset loses
|
* with its usages (in_use); lit/divi-design-detach (Etappe 4b) removes the references first. Only explicit references
|
||||||
* its default. Through the change pipeline with Divi's delete semantics (legacy option cleaned).
|
* block: a default preset loses its default, and the posts with modules that use it implicitly are listed in the dry
|
||||||
|
* run (spec §6.4). Through the change pipeline with Divi's delete semantics (legacy option cleaned).
|
||||||
*/
|
*/
|
||||||
final class DiviPresetsDeleteAbility implements Ability
|
final class DiviPresetsDeleteAbility implements Ability
|
||||||
{
|
{
|
||||||
@@ -28,6 +29,8 @@ final class DiviPresetsDeleteAbility implements Ability
|
|||||||
. 'Divi-Builder; danach erneut löschen.';
|
. 'Divi-Builder; danach erneut löschen.';
|
||||||
|
|
||||||
/** @param (Closure(): bool)|null $unfiltered Whether the user may save unfiltered HTML (DesignRights). */
|
/** @param (Closure(): bool)|null $unfiltered Whether the user may save unfiltered HTML (DesignRights). */
|
||||||
|
private const DEFAULT_WARNING = 'Module ohne eigenes Preset verwenden danach das Divi-Standardaussehen.';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private DesignSystemWriter $writer,
|
private DesignSystemWriter $writer,
|
||||||
private DesignChange $change,
|
private DesignChange $change,
|
||||||
@@ -158,7 +161,9 @@ final class DiviPresetsDeleteAbility implements Ability
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$report = $this->usages->reportFor(new PresetUsageQuery($targets), false);
|
// Spec §6.4: only explicit references block; a default preset's implicit use is listed in the dry run.
|
||||||
|
$explicit = array_map(static fn (array $target): array => ['default' => false] + $target, $targets);
|
||||||
|
$report = $this->usages->reportFor(new PresetUsageQuery($explicit), false);
|
||||||
if ($nested !== [] || $report->exists()) {
|
if ($nested !== [] || $report->exists()) {
|
||||||
return Errors::inUse(
|
return Errors::inUse(
|
||||||
'Die Presets werden noch verwendet; nichts wurde gelöscht.',
|
'Die Presets werden noch verwendet; nichts wurde gelöscht.',
|
||||||
@@ -173,6 +178,15 @@ final class DiviPresetsDeleteAbility implements Ability
|
|||||||
|
|
||||||
$diff = PresetDiff::between($before, $book);
|
$diff = PresetDiff::between($before, $book);
|
||||||
|
|
||||||
return new DesignPlan($book->data(), $diff, PresetDiff::summary($diff), null, [], true);
|
$defaults = array_values(array_filter($targets, static fn (array $target): bool => $target['default']));
|
||||||
|
|
||||||
|
return new DesignPlan(
|
||||||
|
$book->data(),
|
||||||
|
$diff,
|
||||||
|
PresetDiff::summary($diff),
|
||||||
|
$defaults === [] ? null : $this->usages->reportFor(new PresetUsageQuery($defaults)),
|
||||||
|
$defaults === [] ? [] : [self::DEFAULT_WARNING],
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ use Brain\Monkey\Functions;
|
|||||||
use LIT\MCP\Integrations\Divi\Design\DesignChange;
|
use LIT\MCP\Integrations\Divi\Design\DesignChange;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DiviPresetsDeleteAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviPresetsDeleteAbility;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PresetUsageQuery;
|
||||||
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
||||||
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
|
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
|
||||||
use LIT\MCP\Integrations\Divi\Library\UsageReport;
|
use LIT\MCP\Integrations\Divi\Library\UsageReport;
|
||||||
use LIT\MCP\Tests\Support\FakeChangeLog;
|
use LIT\MCP\Tests\Support\FakeChangeLog;
|
||||||
use LIT\MCP\Tests\Unit\TestCase;
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
|
use ReflectionProperty;
|
||||||
|
|
||||||
final class DiviPresetsDeleteAbilityTest extends TestCase
|
final class DiviPresetsDeleteAbilityTest extends TestCase
|
||||||
{
|
{
|
||||||
@@ -135,6 +137,47 @@ final class DiviPresetsDeleteAbilityTest extends TestCase
|
|||||||
self::assertToolError($this->ability(self::unused(), null, null, false)->execute($input), 'confirm_required');
|
self::assertToolError($this->ability(self::unused(), null, null, false)->execute($input), 'confirm_required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return list<bool> The default flags of a PresetUsageQuery's targets. */
|
||||||
|
private static function defaultFlags(PresetUsageQuery $query): array
|
||||||
|
{
|
||||||
|
$targets = (new ReflectionProperty(PresetUsageQuery::class, 'targets'))->getValue($query);
|
||||||
|
|
||||||
|
return array_column($targets, 'default');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Final review I-6 (spec §6.4): implicit use of a default preset does not block; the dry run lists it. */
|
||||||
|
public function testImplicitUseOfADefaultPresetIsListedNotBlocking(): void
|
||||||
|
{
|
||||||
|
$used = new UsageReport(1, [['post_id' => 3, 'title' => 'Start', 'status' => 'publish', 'type' => 'page']],
|
||||||
|
0, 1, [], 0, false);
|
||||||
|
Functions\when('current_user_can')->justReturn(true);
|
||||||
|
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||||
|
$writer->allows('presets')->andReturn(PresetBookTest::stored());
|
||||||
|
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||||
|
$usages = Mockery::mock(GlobalUsages::class);
|
||||||
|
$usages->expects('reportFor')
|
||||||
|
->with(Mockery::on(static fn (PresetUsageQuery $q): bool => self::defaultFlags($q) === [false]), false)
|
||||||
|
->andReturn(self::unused());
|
||||||
|
$usages->expects('reportFor')
|
||||||
|
->with(Mockery::on(static fn (PresetUsageQuery $q): bool => self::defaultFlags($q) === [true]))
|
||||||
|
->andReturn($used);
|
||||||
|
$ability = new DiviPresetsDeleteAbility(
|
||||||
|
$writer,
|
||||||
|
new DesignChange(Mockery::mock(SnapshotStore::class), FakeChangeLog::forbidden()),
|
||||||
|
$usages,
|
||||||
|
static fn (): bool => true
|
||||||
|
);
|
||||||
|
|
||||||
|
$dry = self::assertToolError(
|
||||||
|
$ability->execute(['items' => [['module' => 'text', 'preset' => 'Fließtext']]]),
|
||||||
|
'confirm_required'
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame(3, $dry['affected'][0]['post_id']);
|
||||||
|
self::assertSame(1, $dry['affected_count']);
|
||||||
|
self::assertContains('Module ohne eigenes Preset verwenden danach das Divi-Standardaussehen.', $dry['warnings']);
|
||||||
|
}
|
||||||
|
|
||||||
public function testUnknownPreset(): void
|
public function testUnknownPreset(): void
|
||||||
{
|
{
|
||||||
self::assertToolError(
|
self::assertToolError(
|
||||||
|
|||||||
Reference in New Issue
Block a user