VariableRef parses, validates and re-serializes every $variable(...)$ value (S1/S2/S12); AttrWriter routes strings by catalog kind incl. per-subName composite kinds and key-name URL fallback (S3/S3a), rejects odd value keys (S13) and Divi HTML/attribute fields without unfiltered_html (S7); ambiguous design-system labels fail with their ids (S13a); R2 finding recorded (S5). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
19 lines
6.1 KiB
Markdown
19 lines
6.1 KiB
Markdown
# Etappe 2 – Entscheidungen der Planung
|
||
|
||
Getroffen bei der Planung von Etappe 2a/2b, 2026-09-26. Kosten, falls falsch, jeweils am Ende.
|
||
|
||
- D1 (Ruling P16): `rev` = `Rev::ofPost` (ganzer Eintrag); Divi-Sperre = `tree_rev` = `Rev::of(post_content)`. Divi-Lese-Tools liefern beide, Divi-Schreib-Tools verlangen `tree_rev` und liefern beide neu. – Kosten: keine.
|
||
- D2: Direkte Tools nach Etappe 2 = 21 (Spec §4.3 ohne `lit/library-insert`, plus `lit/site-status`, `lit/media-download`). `lit/site-status` bleibt direkt (leeres Schema, Anker von mcp-smoke.sh), `lit/media-download` muss direkt bleiben. Indirekt: `lit/divi-duplicate-node`, `lit/divi-replace-text`. – Kosten: etwas mehr Kontext je Unterhaltung; in Etappe 3 neu bewerten.
|
||
- D3: BlockTree mit eigenem Tokenizer (Muster von `WP_Block_Parser`), opake Knoten für globale Module/Layouts und undekodierbare Attribute, unbalanciertes Markup → Ablehnung. – Kosten: globale Module erst ab Etappe 3 bearbeitbar.
|
||
- D4: `ExposurePolicy::DIRECT_ONLY` bleibt eine Liste; Etappe 2 bringt keine Bild-/Ressourcen-Tools, ein `Ability::requiresDirect()` wäre YAGNI. – Kosten: bei weiteren Bild-Tools die Liste ergänzen.
|
||
- D5: `lit/page-render-summary` gehört zur WordPress-Integration (funktioniert für jeden Eintrag) und rendert den Inhaltsbereich im Prozess (`the_content`), ohne HTTP-Loopback. – Kosten: Header/Footer/Theme Builder fehlen im Auszug.
|
||
- D6: `lit/divi-dynamic-sources` und die Warnung „ähnliche Abschnitte“ kommen mit Etappe 3 (Theme Builder, `lit/library-find-similar`). – Kosten: bis dahin keine Quellenliste; `{{field:…}}` nimmt jeden Meta-Schlüssel.
|
||
- D7 (Ruling R1): Divi-Speichern ohne kses für Benutzer ohne `unfiltered_html` (sonst re-serialisiert WordPress jeden Block) ist nur vertretbar, weil keine client-gelieferte Zeichenkette ungeprüft in `post_content` gelangt. Klartext-Werte werden je Feldtyp bereinigt (Text: `sanitize_text_field`; Rich-Text: `wp_kses_post`, abhängig von `unfiltered_html`; URLs: `esc_url_raw`, geroutet über die Attribut-Metadaten des Katalogs, ersatzweise über den Schlüsselnamen). Werte mit `$variable(...)$` – ob aus Platzhaltern erzeugt oder unverändert aus `lit/divi-node-get` zurückgegeben – werden geparst, streng geprüft (zulässiger Typ, existierende gcid-/gvid-/Dynamic-Content-Quellennamen, whitelisted Settings-Schlüssel, String-Settings bereinigt) und vom Plugin neu serialisiert; alles andere wird mit `validation_failed` abgelehnt. Code-Modul ungefiltert nur mit `unfiltered_html`. – Kosten: jeder neue Schreibpfad muss bereinigen, streng validieren und neu serialisieren (Review-Pflicht).
|
||
- R2 (Ruling): Ob Divi das Dynamic-Content-`before`/`after` bei der Ausgabe selbst escaped, wird in Etappe 2b im Divi-Quellcode verifiziert; die Bereinigung aus D7 gilt unabhängig vom Ergebnis (Verteidigung in der Tiefe). – Kosten: keine (Verifikation, keine Abhängigkeit der Sicherheit vom Ergebnis).
|
||
- R2 – Befund (Etappe 2b, Divi 5.2.1, Ruling S5): (1) Dynamic-Content-`before`/`after` wird bei der Ausgabe nur dann mit `wp_kses` gefiltert, wenn der **Autor des Beitrags** kein `unfiltered_html` hat (`DynamicContentElements::get_wrapper_element`); auf Seiten eines Administrators gelangt es ungefiltert ins HTML – ein Redakteur könnte dort Skript platzieren. (2) `enable_html: on` gibt Meta-Werte mit `wp_kses_post` statt `esc_html` aus; Divi entfernt `enable_html` beim Speichern nur für Benutzer ohne `unfiltered_html` (`Security::sanitize_dynamic_content_fields`, `wp_insert_post_data` Priorität 10). (3) `custom_meta_<key>` wird ohne `is_protected_meta`-Prüfung ausgegeben (`DynamicContentOptionPostMetaKey`). (4) Farbverweis-Namen gehen unescaped in `var(--<name>)` (`StyleLibrary\Utils::resolve_dynamic_variable`). (5) Divis eigene `wp_insert_post_data`-Filter sind kein kses und laufen auch bei unserem Speichern ohne kses: `AttributeSecurity` (Priorität 5, immer, sobald `"decoration":{` und `"attributes":{` vorkommen) und `HtmlSecurity` (Priorität 6, bei `htmlBefore`/`htmlAfter`/`elementType`) parsen und re-serialisieren die **ganze Seite**; `DynamicContentFixes` (Priorität 10). Folgen: die Plugin-Prüfung aus D7 ist die eigentliche Garantie (VariableRef: nur ganze, geprüfte, neu serialisierte Verweise; `before`/`after` ohne unzulässiges HTML, kein `enable_html`, keine Meta-Schlüssel mit `_`); Divis Filter bleiben aktiv (Verteidigung in der Tiefe) und werden nie entfernt; Schreiben auf `*.decoration.attributes` und `*.advanced.html` nur mit `unfiltered_html` („nur im Divi-Builder“, Ruling S7), und auf Seiten mit diesen Feldern gilt die Byte-Invarianz nicht (Divi normalisiert beim Speichern). – Kosten: keine (Dokumentation; die Sicherheit hängt nicht von Divis Ausgabe ab).
|
||
- D8: Spalten neuer Zeilen über `module.decoration.sizing.desktop.value.flexType` (`"1_3,2_3"` → `8_24`, `16_24`; Fünftel und 1/7, 1/9, 1/10, 1/11 nativ), wie Divi 5.2 (Flex-Layout ist Standard). – Kosten: falls der Builder anders speichert, nur Composer anpassen.
|
||
- D9: Platzhalter stehen allein im Wert; Überschriften der Kurzform ohne `level` werden `h2`. – Kosten: eingebettete dynamische Texte nur über die Vollform.
|
||
- D10: `lit/divi-replace-text` je Inhaltstyp; Probelauf liefert `tree_rev` je Seite, Ausführung nur auf diesen `targets` mit `confirm: true`; ersetzt nur sichtbaren Text (`…innerContent`, Schlüssel text/title/label/alt/titleText), nie URLs oder Stile. – Kosten: Text mit HTML-Entitäten muss so gesucht werden, wie er gespeichert ist.
|
||
- D11: `lit/content-create` mit `divi` speichert über `wp_insert_post` mit WordPress' üblicher Filterung (nur neuer, selbst erzeugter Inhalt – nichts Unberührtes, das byte-identisch bleiben müsste). – Kosten: für Benutzer ohne `unfiltered_html` kann WordPress Entitäten normalisieren; `tree_rev` in der Antwort beschreibt den gespeicherten Stand.
|
||
- D12: Fixtures: handgeschriebene Grenzfälle (`synthetic-*.html`), mit Divis eigener Konvertierung erzeugte Seiten (`converted-*.html`), echte Modul-Metadaten (`metadata-sample.json`); im Visual Builder gebaute Seiten exportiert Jan-Niclas später mit `tests/Integration/divi-export-page.php` als `builder-*.html` – sie laufen automatisch im Roundtrip-Test mit.
|