fix: Preset-Änderungen ohne unfiltered_html ablehnen, wenn Divi HTML/CSS anderer Presets entfernen würde
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
048cd9353d
commit
75714429be
@@ -46,6 +46,63 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
||||
self::assertSame('presets-3', $result['snapshot_id']);
|
||||
}
|
||||
|
||||
private static function snapshotOf(array $data): array
|
||||
{
|
||||
return ['id' => 'presets-20261016T100000Z-aaaaaa', 'data' => $data, 'created' => 'x', 'user_id' => 1,
|
||||
'ability' => 'lit/divi-presets-set', 'log_id' => 2, 'size' => 1];
|
||||
}
|
||||
|
||||
private static function restoring(array $current, array $snapshot, bool $unfiltered): DiviDesignRestoreAbility
|
||||
{
|
||||
Functions\when('current_user_can')->justReturn(true);
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
$writer->allows('presets')->andReturn($current);
|
||||
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||
$snapshots = Mockery::mock(SnapshotStore::class);
|
||||
$snapshots->allows('get')->andReturn(self::snapshotOf($snapshot));
|
||||
$snapshots->shouldNotReceive('save');
|
||||
|
||||
return new DiviDesignRestoreAbility(
|
||||
$writer,
|
||||
new DesignChange($snapshots, FakeChangeLog::forbidden()),
|
||||
$snapshots,
|
||||
static fn (): bool => $unfiltered
|
||||
);
|
||||
}
|
||||
|
||||
/** Final review I-2: markup in the snapshot or in the current inventory is lost for editors – refused. */
|
||||
public function testEditorIsRefusedWhenTheSnapshotOrTheInventoryCarriesMarkup(): void
|
||||
{
|
||||
$plain = PresetBookTest::stored();
|
||||
$markup = PresetBookTest::storedWithMarkup();
|
||||
$older = $plain;
|
||||
unset($older['module']['divi/button']['items']['cccccccccc']);
|
||||
$input = ['snapshot_id' => 'presets-20261016T100000Z-aaaaaa'];
|
||||
|
||||
$inSnapshot = self::assertToolError(self::restoring($plain, $markup, false)->execute($input), 'forbidden');
|
||||
self::assertStringContainsString('unfiltered_html', $inSnapshot['message']);
|
||||
self::assertToolError(self::restoring($markup, $older, false)->execute($input), 'forbidden');
|
||||
|
||||
self::assertToolError(self::restoring($plain, $markup, true)->execute($input), 'confirm_required');
|
||||
self::assertToolError(self::restoring($plain, $older, false)->execute($input), 'confirm_required');
|
||||
}
|
||||
|
||||
/** T13: restoring needs the preset rights. */
|
||||
public function testNeedsEditThemeOptions(): void
|
||||
{
|
||||
Functions\when('current_user_can')->justReturn(false);
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||
$writer->shouldNotReceive('savePresets');
|
||||
$snapshots = Mockery::mock(SnapshotStore::class);
|
||||
$snapshots->shouldNotReceive('get');
|
||||
$ability = new DiviDesignRestoreAbility($writer, new DesignChange($snapshots, FakeChangeLog::forbidden()), $snapshots);
|
||||
$input = ['snapshot_id' => 'presets-20261016T100000Z-aaaaaa', 'confirm' => true, 'design_rev' => 'x'];
|
||||
|
||||
self::assertToolError($ability->permission($input), 'forbidden');
|
||||
self::assertToolError($ability->execute($input), 'forbidden');
|
||||
}
|
||||
|
||||
public function testUnknownSnapshotAndOtherAreas(): void
|
||||
{
|
||||
Functions\when('current_user_can')->justReturn(true);
|
||||
|
||||
@@ -25,12 +25,17 @@ final class DiviPresetsDeleteAbilityTest extends TestCase
|
||||
Functions\when('delete_option')->justReturn(true);
|
||||
}
|
||||
|
||||
private function ability(UsageReport $report, ?DesignSystemWriter $writer = null, ?SnapshotStore $snapshots = null): DiviPresetsDeleteAbility
|
||||
{
|
||||
private function ability(
|
||||
UsageReport $report,
|
||||
?DesignSystemWriter $writer = null,
|
||||
?SnapshotStore $snapshots = null,
|
||||
bool $unfiltered = true,
|
||||
array $stored = []
|
||||
): DiviPresetsDeleteAbility {
|
||||
Functions\when('current_user_can')->justReturn(true);
|
||||
if ($writer === null) {
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
$writer->allows('presets')->andReturn(PresetBookTest::stored());
|
||||
$writer->allows('presets')->andReturn($stored === [] ? PresetBookTest::stored() : $stored);
|
||||
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||
}
|
||||
$usages = Mockery::mock(GlobalUsages::class);
|
||||
@@ -39,7 +44,8 @@ final class DiviPresetsDeleteAbilityTest extends TestCase
|
||||
return new DiviPresetsDeleteAbility(
|
||||
$writer,
|
||||
new DesignChange($snapshots ?? Mockery::mock(SnapshotStore::class), new FakeChangeLog(3)),
|
||||
$usages
|
||||
$usages,
|
||||
static fn (): bool => $unfiltered
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,6 +119,22 @@ final class DiviPresetsDeleteAbilityTest extends TestCase
|
||||
self::assertSame(['removed', 'default_cleared'], array_column($dry['diff'], 'action'));
|
||||
}
|
||||
|
||||
/** Final review I-2: deleting saves the whole inventory – other presets' HTML/CSS would be stripped. */
|
||||
public function testEditorIsRefusedWhenOtherPresetsCarryMarkup(): void
|
||||
{
|
||||
$input = ['items' => [['module' => 'button', 'preset' => 'cccccccccc']]];
|
||||
$markup = PresetBookTest::storedWithMarkup();
|
||||
|
||||
$editor = self::assertToolError(
|
||||
$this->ability(self::unused(), null, null, false, $markup)->execute($input),
|
||||
'forbidden'
|
||||
);
|
||||
self::assertStringContainsString('unfiltered_html', $editor['message']);
|
||||
|
||||
self::assertToolError($this->ability(self::unused(), null, null, true, $markup)->execute($input), 'confirm_required');
|
||||
self::assertToolError($this->ability(self::unused(), null, null, false)->execute($input), 'confirm_required');
|
||||
}
|
||||
|
||||
public function testUnknownPreset(): void
|
||||
{
|
||||
self::assertToolError(
|
||||
|
||||
@@ -90,7 +90,8 @@ final class DiviPresetsSetAbilityTest extends TestCase
|
||||
new DesignChange($this->snapshots, new FakeChangeLog(5)),
|
||||
$usages,
|
||||
$loader ?? new DiviPageLoader(DiviFixtures::schema()),
|
||||
$composer
|
||||
$composer,
|
||||
static fn (): bool => $unfiltered
|
||||
);
|
||||
}
|
||||
|
||||
@@ -164,6 +165,45 @@ final class DiviPresetsSetAbilityTest extends TestCase
|
||||
self::assertToolError($this->ability(true)->execute($input), 'confirm_required');
|
||||
}
|
||||
|
||||
/** Final review I-2: Divi would strip other presets' HTML/CSS when an editor saves – refused instead. */
|
||||
public function testEditorIsRefusedWhenOtherPresetsCarryMarkup(): void
|
||||
{
|
||||
$this->stored = PresetBookTest::storedWithMarkup();
|
||||
$input = ['items' => [[
|
||||
'module' => 'button', 'preset' => 'CTA primär',
|
||||
'attrs' => ['button.decoration.background' => ['desktop' => ['color' => '#222222']]],
|
||||
]]];
|
||||
|
||||
$editor = self::assertToolError($this->ability(false)->execute($input), 'forbidden');
|
||||
self::assertStringContainsString('unfiltered_html', $editor['message']);
|
||||
self::assertStringContainsString('nichts wurde gespeichert', $editor['message']);
|
||||
self::assertStringContainsString('Administrator', $editor['hint']);
|
||||
|
||||
self::assertToolError($this->ability(true)->execute($input), 'confirm_required');
|
||||
}
|
||||
|
||||
/** The changed preset's own HTML/CSS would be stripped as well (closest safe variant of I-2). */
|
||||
public function testEditorIsRefusedWhenTheChangedPresetCarriesMarkup(): void
|
||||
{
|
||||
$this->stored = PresetBookTest::storedWithMarkup();
|
||||
unset($this->stored['group']['divi/font-body']['items']['gggggggggg']['styleAttrs']);
|
||||
|
||||
$editor = self::assertToolError($this->ability(false)->execute(['items' => [[
|
||||
'module' => 'button', 'preset' => 'bbbbbbbbbb',
|
||||
'attrs' => ['button.decoration.background' => ['desktop' => ['color' => '#222222']]],
|
||||
]]]), 'forbidden');
|
||||
self::assertStringContainsString('unfiltered_html', $editor['message']);
|
||||
}
|
||||
|
||||
public function testEditorMayChangePresetsWhenNoneCarriesMarkup(): void
|
||||
{
|
||||
self::assertToolError($this->ability(false)->execute(['items' => [[
|
||||
'module' => 'button', 'preset' => 'CTA primär',
|
||||
'attrs' => ['button.decoration.background' => ['desktop' => ['color' => '#222222']]],
|
||||
]]]), 'confirm_required');
|
||||
self::assertToolError($this->ability(false)->execute(self::newButton()), 'confirm_required');
|
||||
}
|
||||
|
||||
public function testFromNodeReadsThePage(): void
|
||||
{
|
||||
$content = '<!-- wp:divi/section --><!-- wp:divi/row --><!-- wp:divi/column -->'
|
||||
|
||||
@@ -42,6 +42,31 @@ final class PresetBookTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> stored() with HTML in one preset and CSS with ">" in another. */
|
||||
public static function storedWithMarkup(): array
|
||||
{
|
||||
$stored = self::stored();
|
||||
$stored['module']['divi/button']['items']['bbbbbbbbbb']['attrs'] = [
|
||||
'module' => ['advanced' => ['html' => ['desktop' => ['value' => ['htmlBefore' => '<b>x</b>']]]]],
|
||||
];
|
||||
$stored['group']['divi/font-body']['items']['gggggggggg']['styleAttrs'] = [
|
||||
'css' => ['desktop' => ['value' => ['freeForm' => 'selector > p { color: red; }']]],
|
||||
];
|
||||
|
||||
return $stored;
|
||||
}
|
||||
|
||||
/** Final review I-2: presets whose HTML/CSS Divi's prepare_data() would strip for editors. */
|
||||
public function testMarkupOutsideListsUntouchedPresetsWithHtmlOrCss(): void
|
||||
{
|
||||
$book = PresetBook::fromDivi(self::storedWithMarkup());
|
||||
|
||||
self::assertSame([], PresetBook::fromDivi(self::stored())->markupOutside([]));
|
||||
self::assertSame(['bbbbbbbbbb', 'gggggggggg'], $book->markupOutside([]));
|
||||
self::assertSame(['gggggggggg'], $book->markupOutside(['bbbbbbbbbb', 'aaaaaaaaaa']));
|
||||
self::assertSame([], $book->markupOutside(['bbbbbbbbbb', 'gggggggggg']));
|
||||
}
|
||||
|
||||
public function testFindsByIdOrUniqueName(): void
|
||||
{
|
||||
$book = PresetBook::fromDivi(self::stored());
|
||||
|
||||
Reference in New Issue
Block a user