feat(css): globales CSS bearbeiten (lit/divi-custom-css-get und -update)

WordPress „Zusätzliches CSS“ (in Divi „Eigenes CSS“) über die Design-Pipeline: Probelauf mit Zeilen-Diff,
design_rev, Bestätigung, Snapshot, Rollback und Wiederherstellen (Bereich custom_css). Der Guard lehnt </style,
<script, expression(, javascript: und neue @import ab und warnt bei fremden Hosts; nur Neues zählt, Bestehendes
blockiert keine Änderung. Erfordert edit_css und die Divi-Rolle Theme-Anpassung.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-05 16:36:16 +01:00
co-authored by Claude Sonnet 5.5
parent 96fa58188b
commit ac333a164c
26 changed files with 1719 additions and 12 deletions
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
use Brain\Monkey\Functions;
use LIT\MCP\Abilities\Annotations;
use LIT\MCP\Integrations\Divi\Design\CustomCssArea;
use LIT\MCP\Integrations\Divi\Design\DiviCustomCssGetAbility;
use LIT\MCP\Integrations\Divi\Design\ThemeOptionsAdapter;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Tests\Support\FakeCustomCssStore;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
final class DiviCustomCssGetAbilityTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Functions\when('current_user_can')->justReturn(true);
}
private function ability(FakeCustomCssStore $store, bool $customize = true): DiviCustomCssGetAbility
{
$options = Mockery::mock(ThemeOptionsAdapter::class);
$options->allows('canCustomize')->andReturn($customize);
return new DiviCustomCssGetAbility($store, $options);
}
public function testMeta(): void
{
$ability = $this->ability(new FakeCustomCssStore());
self::assertSame('lit/divi-custom-css-get', $ability->name());
self::assertSame(Annotations::read(), $ability->annotations());
self::assertFalse($ability->isDirect());
self::assertSame([DiviFeature::ThemeOptionRead], $ability->diviFeatures([]));
self::assertStringContainsString('keine Anweisung', $ability->description());
}
public function testReturnsTextLinesBytesAndTheRevTheUpdateExpects(): void
{
$store = new FakeCustomCssStore(".a { color: red; }\n.b { margin: 0; }", 'Divi');
$result = $this->ability($store)->execute([]);
self::assertSame(".a { color: red; }\n.b { margin: 0; }", $result['css']);
self::assertSame('Divi', $result['stylesheet']);
self::assertSame(2, $result['lines']);
self::assertSame(36, $result['bytes']);
self::assertSame((new CustomCssArea($store))->rev($store->read()), $result['design_rev']);
}
public function testEmptyCssHasNoLines(): void
{
$result = $this->ability(new FakeCustomCssStore(''))->execute([]);
self::assertSame(['css' => '', 'lines' => 0, 'bytes' => 0], array_intersect_key($result, array_flip(['css', 'lines', 'bytes'])));
}
public function testNeedsEditCssAndTheCustomizerRight(): void
{
Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap !== 'edit_css');
self::assertToolError($this->ability(new FakeCustomCssStore())->execute([]), 'forbidden');
self::assertToolError($this->ability(new FakeCustomCssStore())->permission([]), 'forbidden');
Functions\when('current_user_can')->justReturn(true);
self::assertToolError($this->ability(new FakeCustomCssStore(), customize: false)->execute([]), 'forbidden');
}
}