Files
WP-LIT-MCP/docs/superpowers/plans/2026-10-19-etappe-4e-schleifen-guides-audit.md
T

103 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Etappe 4e – Schleifen-Inhalte, Guides, Security-Audit, Version – Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Der KI-Assistent kann `loop_*`-Inhalte (z. B. Datum, Kategorien, Autor) in Modulen setzen, die in einem Divi-Loop-Element liegen oder selbst eines sind – mit Kontextprüfung aus dem Blockbaum, passenden Fehlermeldungen und der Kurzform `{{loop:<option>}}`; `lit/divi-dynamic-sources` zeigt die passenden Loop-Optionen je Knoten. Außerdem: Guides für alle Etappe-4-Werkzeuge, ein Security-Audit der ganzen Etappe mit Fix-Welle und der Versionssprung auf 0.7.0.
**Architecture:** Der Schleifen-Kontext wird **im Blockbaum** bestimmt (`LoopContext`: nächster Vorfahre-oder-selbst mit `module.advanced.loop.desktop.value.enable === "on"`, Typ aus `queryType`, Standard `post_types`) und als reines Wertobjekt `LoopScope` an `VariableRef`/`DynamicContentRules` gegeben; dort erlaubt er nur `loop_*`-Optionen der passenden Art (Post/Term/Benutzer/Menü). Die Einspeisung liegt in den Abilities (Insert, Update, Move), nicht in den unteren Schichten: `DiviComposition::inLoop()` liefert eine Komposition mit Kontext. `DynamicContentMap` behält die `loop_*`-Optionen (heute verworfen) gruppiert nach Art; Divi-Aufrufe ändern sich nicht (`DynamicContentOptions::get_options()` liefert sie schon). Guides, Audit und Version sind Dokumentations-/Prozess-Tasks mit klaren Checklisten.
**Tech Stack:** PHP ≥ 8.1, WordPress 7.x, Divi 5.15.1 (nur über vorhandene Adapter), PHPUnit 9.6 + Brain Monkey + Mockery, phpcs, DDEV + bash/jq-Smokes.
**Spec:** `docs/superpowers/specs/2026-10-16-etappe-4-designsystem-design.md` (§9 Schleifen-Inhalte, §12 Audit, §14 Schritt 8–9, §1.3 Nr. 5–6, A3, A10, A11), Entscheidungen `docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md` (G11, G12), Recherchen 4e: `docs/superpowers/notes/2026-10-19-etappe-4e-recherche-schleifen.md` (Divi-Interna; Task 10 legt sie ab, bis dahin `…/scratchpad/research-4e-loop-divi.md`, `…/research-4e-loop-plugin.md`, `…/research-4e-guides-audit-version.md` – die Task-Autoren haben sie gelesen), Pläne 4c/4d als Vorbild; der Code ist maßgeblich, wo er von den Plänen abweicht.
**Voraussetzung:** Branch `etappe-4` (= `main`), Stand 7678d22 (4a–4d fertig), 3368 Unit-Tests grün (`composer test`), `composer lint` sauber.
**Teil von Etappe 4:** 4a–4d (fertig) → **4e (dieser Plan)** = Abschluss von Etappe 4.
## Global Constraints
- **Dateien nur im Plugin-Verzeichnis** `/home/janniclas/Projekte/WordPress/wp-content/plugins/WP-LIT-MCP` anlegen oder ändern. Integrationsskripte stellen im EXIT-Trap alles wieder her, was sie ändern (Seiten, Optionen).
- PHP ≥ 8.1 ohne 8.2+-Features; WordPress ≥ 6.9; Divi 5.15.1. Namespace `LIT\MCP\` → `src/`, PSR-4, eine Klasse pro Datei, jede Datei mit `defined('ABSPATH') || exit;`, `declare(strict_types=1);`, höchstens 120 Zeichen je Zeile. Nach jedem Task `composer test` und `composer lint` sauber (`composer test` = `php -d memory_limit=512M vendor/bin/phpunit`).
- **Divi-Grenze (Divi-Absicherung §3):** Divi-Aufrufe nur in Adaptern aus `DiviAdapters::CLASSES`. 4e braucht **keinen neuen Divi-Aufruf**: `DynamicContentMap` ruft `DynamicContentOptions::get_options()` schon und behält jetzt nur mehr von dessen Ergebnis. Ergibt sich doch ein neuer Divi-Aufruf, Task anhalten und melden (NEEDS_CONTEXT). `LoopScope`, `LoopContext` und die Kontext-Teile der Regeln sind **reines PHP** (keine WordPress-/Divi-Aufrufe).
- **Sicherheitsgrenze Typabgleich:** Ein `loop_*`-Wert ist nur erlaubt, wenn der Schleifen-Kontext des Knotens (Art) zur Art der Option passt. Das ist **keine Komfortmeldung**: In Divi liest `loop_post_meta_key_*` in einer Term-/Benutzer-/Menü-Schleife Post-Meta des Objekts mit derselben ID (und Benutzer-/Term-Meta in einer Post-Schleife umgekehrt) – ein Fehlabgleich würde fremde Daten ausgeben (Recherche 4e, Befund 1). Ohne Schleifen-Kontext ist jede `loop_*`-Option abgelehnt.
- **Rechte/Sperren unverändert:** Alle Dynamic-Content-Einstellungen laufen weiter durch `ValueCleaner`/`VariableRef`/`DynamicContentRules` mit den Meta-Schlüssel-Regeln S29/S30 (auch für `loop_*_meta_key_`) und `unfiltered_html`-Regeln; die Release-Gate-Fähigkeiten der Tools ändern sich nicht (`dynamic-sources` ist schon überall deklariert, wo Inhalte entstehen).
- **Sprache:** Meldungen, Hinweise, Guides, Notizen auf Deutsch mit echten Umlauten; neutral „KI-Assistent“, „MCP-Client“. Tool-Namen: direkte Tools `lit-<name>`, indirekte `lit/<name>` (ToolNamingTest).
- **Tests:** Abilities über `LIT\MCP\Tests\Unit\TestCase` (Brain Monkey + Mockery, `assertToolError`); vorbelegte Stubs (`wp_json_encode`, `is_wp_error`, `absint`, `wp_slash`, `wp_unslash`, `sanitize_text_field`) nie mit `Functions\expect()` prüfen. Reine Klassen erben von `PHPUnit\Framework\TestCase`. TDD: Test zuerst, RED-Lauf, Code, GREEN; ein Test muss den Codepfad erreichen, den sein Name behauptet (keine vakuösen Assertions – Lehre aus 4c/4d). Echte Loop-Optionsnamen aus Divi 5.15.1 verwenden (die bisherigen Testdaten `loop_post_meta_key` u. ä. sind erfunden und werden in Task 2 korrigiert). Integration: `cd /home/janniclas/Projekte/WordPress && timeout 900 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/<skript>.sh` (läuft nur mit deaktivierten Plugins `postlos` und `asynclistry`; vorher Zustand mit `ddev exec wp plugin list` notieren und danach wiederherstellen).
- **Kein clean-code-review-Skill je Task** (die globale CLAUDE.md-Regel zieht sonst die Antwort des Implementierers an sich); der Gesamtreview folgt am Ende (Task 10).
- Commits: `git commit -m "<Nachricht>" -m "Co-Authored-By: Claude <Modellname> <noreply@anthropic.com>"` mit dem Namen des ausführenden Modells; nur die Dateien des Tasks stagen.
## Entscheidungen für 4e (Rulings, verbindlich)
- **X1 Kontextregel (Divi 5.15.1):** Der Schleifen-Kontext eines Knotens ist der des **nächsten Vorfahren-oder-selbst**, dessen `attrs.module.advanced.loop.desktop.value.enable` genau `"on"` ist (Tablet/Phone/Hover-Varianten und `true` zählen nicht – das liest auch Divi so). `queryType` fehlt → `post_types`. Verschachtelte Schleifen: die innerste gilt für alles darunter, egal welcher Art. Loop-Einstellungen, die nur über ein **Preset** kommen (`LoopPresetAttrsMap`), erkennt die Baum-Prüfung nicht – das ist eine dokumentierte Grenze (fail closed: ohne erkannte Schleife werden `loop_*` abgelehnt).
- **X2 Arten:** `post` (`post_types`, `current_page`), `term` (`post_taxonomies`), `user` (`user_roles`), `menu` (`menus`), `repeater` (`repeater_<acf-name>`, nur falls die Recherche zeigt, dass es eigene Optionen gibt – sonst fällt `repeater_*` unter `post` oder wird abgelehnt; der Task-Autor entscheidet anhand der Recherchetabelle und dokumentiert es). Die Zuordnung Option → Art stammt aus der `group` der Divi-Option („Loop Terms“ → term, „Loop Users“ → user, alle übrigen `loop_*` → post, Menü nach Tabelle); die drei Meta-Schlüssel-Optionen (`loop_post_meta_key_manual_custom_field`, `loop_user_…`, `loop_term_…`) ordnet der Name zu, weil ihre Gruppennamen übersetzt sind. Gespeichert wird der **Array-Schlüssel** der Option (`loop_term_name`), nicht die interne `id`.
- **X3 Einspeisung in den Abilities:** `VariableRef` und `DynamicContentRules` bekommen einen optionalen `?LoopScope` (Standard `LoopScope::none()` → verhält sich wie heute, kein bestehender Aufruf bricht). `DiviComposition::inLoop(LoopScope)` liefert eine Komposition mit Kontext (Vorbild `forPost()`). Insert: Kontext der Zielposition (Eltern-Knoten, alle neuen Knoten eines Aufrufs teilen ihn, solange sie nicht selbst eine Schleife einschalten – ein neuer Knoten mit eigener `loop`-Einstellung im Aufruf schaltet den Kontext für seine Nachfahren um: der Task-Autor löst das über die neuen Attribute des Knotens, wenn nötig fail closed = Schleife zählt erst ab dem gespeicherten Zustand und der Fehler nennt das). Update: Kontext des bestehenden Knotens (Vorfahren aus dem Baum, plus eigene neue `loop`-Attribute desselben Aufrufs); **ein Composer je Kontext** (Ruling S31 „ein VariableRef je Komposition“ wird entsprechend umformuliert). Move: Kontext am neuen Ziel; ein Move, der vorhandene `loop_*`-Inhalte aus einer Schleife herausführt oder in eine Schleife anderer Art, erzeugt eine **Warnung** (Divi lässt das zu), keine Ablehnung (A11). `lit-content-create` und Theme-Builder-Anlage haben nie einen Schleifen-Kontext.
- **X4 Kurzform:** `{{loop:<option>}}` (Option ohne Präfix, z. B. `{{loop:post_date}}` → `loop_post_date`) mit Einstellungen wie `{{post:…}}` (A10); die Tabelle `LOOP` in `Placeholders` ordnet den Kurznamen den Optionen zu; welche Optionen before/after tragen, kommt aus der `DynamicContentMap`.
- **X5 Fehlermeldungen (Spec §9):** ohne Schleife: „Schleifen-Inhalt "%s" braucht eine Divi-Schleife in diesem Modul oder einem übergeordneten Element (Abschnitt, Zeile, Spalte, Gruppe).“; falsche Art: „… passt nicht zur Schleife dieses Elements (Typ: %s).“ (Typ deutsch: Beiträge, Terme, Benutzer, Menü). Die irreführende alte Meldung „nur innerhalb von Divi-Schleifen“ entfällt; Ruling S36 ist abgelöst.
- **X6 `lit/divi-dynamic-sources`:** optional `post_id` + `node` (Pfad-ID): zusätzlich die zum Schleifen-Kontext dieses Knotens passenden `loop_*`-Optionen (Feldtypen wie die übrigen Optionen); lesen mit Leserecht auf die Seite (Muster `lit-divi-node-get`, `DiviPageLoader`); ohne Schleife im Kontext: Hinweis „kein Schleifen-Kontext“ statt Liste. Der Konstruktor bekommt den `DiviPageLoader` (Task 9 verdrahtet; die Tool-Fähigkeiten ändern sich nicht).
- **X7 Grenzen, dokumentiert statt gelöst:** (a) `module.advanced.loop` ist als Attributpfad schreibbar – Schleifen ein-/ausschalten über `attrs` war schon vorher möglich und bleibt (A3 verbietet nur ein *Anlegen/Konfigurieren-Werkzeug*); (b) globale Module werden nicht betreten – `loop_*` in Bibliothekseinträgen hat keinen Schleifen-Kontext und wird abgelehnt; (c) `StoredNodeGuard`/Kopieren: kopierte Schleifen-Container mit `loop_*`-Kindern bleiben für Benutzer ohne `unfiltered_html` abgelehnt (fail closed), solange die Prüfung nur einen Kontext je Unterbaum kennt; (d) Presets, die eine Schleife einschalten, erkennt die Baumprüfung nicht (X1); (e) ACF-Wiederholungsfelder verlangen die Einstellung `acf_type`, die die Einstellungsregeln heute ablehnen – bleibt außen vor, wenn die Task-Autoren keinen sicheren Weg finden. Alle Punkte kommen in die Audit-Notiz (Task 8) und den Guide (Task 7).
- **X8 Guides (G11):** keine Prompts, keine neue Guide-Datei ohne Not; Ablauf „Corporate Design übernehmen“ in `workflows.md`; `divi-design-system.md` bekommt Farben, Variablen, Theme-Optionen, Schriften, Restore je Bereich, Rechte; `overview.md` die vollständige Liste der indirekten Design-Tools und die neuen Fehlercodes (`design_rev`, `rev_mismatch`, `conflict`, `in_use`, `font_*`); `divi-placeholders.md`/`divi-shortform.md` `{{loop:…}}` und die Loop-Regeln. Die Inhalte folgen der Checkliste der Recherche (A) und dem echten Code der Tools.
- **X9 Audit (Task 8):** Ein unabhängiger Audit (Opus, Skill `anthropic-skills:security-audit` wenn verfügbar, sonst eigenes Vorgehen) über den Umfang von Spec §12 (Preset-`attrs`/XSS, Font-Upload, Massen-Schreibrechte, Schleifen-Optionen/Meta-Schlüssel/Typabgleich, Snapshot-Restore) im Format der Audits 3a/3b (`docs/superpowers/notes/2026-09-27-etappe-3a-security-audit.md`); Befunde mit Schwere; kritische/hohe Befunde werden in einer Fix-Welle behoben, mittlere nach Urteil, der Rest steht als Restrisiko in der Notiz. Das DNS-Rebinding-Fenster zwischen `allows()` und cURL (W13) gehört in den Audit.
- **X10 Version:** 0.6.1 → 0.7.0 (`wp-lit-mcp.php:6`, `src/Plugin.php:48`, `readme.txt:7` plus Changelog-Abschnitt in `readme.txt`); kein Git-Tag, kein Update-Server, kein `CHANGELOG`; `DiviReleases::TESTED` bleibt (5.2.1, 5.14.0, 5.15.1). Spec/§9 nennt 5.14.0 – Task 10 berichtigt auf 5.15.1.
## Schnittstellenvertrag (Namen und Signaturen, die spätere Tasks voraussetzen)
Klassen unter `src/Integrations/Divi/Compose/` (Namespace `LIT\MCP\Integrations\Divi\Compose`), Tests unter `tests/Unit/Integrations/Divi/Compose/`, sofern nicht anders genannt. Task-Autoren dürfen Details verfeinern, aber nur mit Eintrag unter `## ABWEICHUNGEN` am Ende ihres Abschnitts.
```php
// LoopScope.php (Task 1) – reines PHP, unveränderlich
public const POST = 'post'; public const TERM = 'term'; public const USER = 'user'; public const MENU = 'menu';
public static function none(): self;
public static function ofQueryType(string $queryType): self; // post_types|current_page → post, post_taxonomies → term, user_roles → user, menus → menu; unbekannt (repeater_… nach X2) → laut Task-Entscheidung
public function active(): bool;
public function kind(): ?string; // null ohne Schleife
public function queryType(): ?string;
public function label(): string; // deutsch für Meldungen: 'Beiträge' | 'Terme' | 'Benutzer' | 'Menü'
// LoopContext.php (Task 1) – reines PHP
/** @param array<string, mixed> $attrs Attribute eines Blocks */
public static function enabled(array $attrs): bool; // desktop.value.enable === 'on' (X1)
public static function ownScope(array $attrs): ?LoopScope; // Schleife an genau diesem Block, sonst null
/** @param list<array<string, mixed>> $attrChain Attribute von der Wurzel bis zum Knoten (inklusive) */
public static function fromChain(array $attrChain): LoopScope; // innerste gewinnt
public static function of(NodeIndex $index, string $nodeId): LoopScope; // Vorfahren-oder-selbst über die Pfad-ID (Namespace BlockTree\NodeIndex)
// DynamicContentMap (Pages/, Task 2) – loop_*-Optionen bleiben erhalten
/** @return array<string, array<string, mixed>> Option => Feldspezifikation, nur loop_*-Optionen der Art $kind */
public function loopFields(string $kind): array; // Name final nach Task 2; Task 3–5 nutzen genau diese Methode
// DynamicContentRules / VariableRef (Task 2) – optionaler Kontext
// VariableRef::__construct(DesignSystem $design, ?array $dynamicContent = null, ?LoopScope $loop = null)
// DynamicContentRules::__construct(DesignSystem $design, ?array $map, ?LoopScope $loop = null)
// DiviComposition (Pages/, Task 4)
public function inLoop(LoopScope $scope): self; // Klon mit Kontext, Vorbild forPost()
// Placeholders (Task 3): {{loop:<kurzname>}} → loop_<name>; Tabelle LOOP wie POST
// DiviDynamicSourcesAbility (Task 5): __construct(…, ?DiviPageLoader $loader = null); Eingabe post_id + node optional
```
## Dateistruktur
**Neu:** `Compose/LoopScope.php`, `Compose/LoopContext.php`, `tests/Integration/divi-loop-smoke.sh`, ggf. Fixture unter `tests/Fixtures/divi/` (echte Loop-Seite), Audit-Notiz `docs/superpowers/notes/2026-10-19-etappe-4e-security-audit.md`, Recherche-Notiz `docs/superpowers/notes/2026-10-19-etappe-4e-recherche-schleifen.md`.
**Geändert:** `Compose/{DynamicContentRules,VariableRef,Placeholders}.php`, `Pages/{DynamicContentMap,DiviComposition,DiviInsertAbility,DiviUpdateAbility,DiviMoveAbility,DiviDynamicSourcesAbility,DiviWriter}.php` (je nach Task), `DiviIntegration.php` (Loader für dynamic-sources), Guides (`docs/guides/*.md`), `wp-lit-mcp.php`, `src/Plugin.php`, `readme.txt`, Spec/Notizen, Tests der genannten Klassen (inkl. `DynamicContentRulesTest`, `VariableRefTest`, `DiviFixtures`).
## Task-Übersicht
| # | Task | Ergebnis |
|---|---|---|
| 1 | `LoopScope`, `LoopContext` | reiner Schleifen-Kontext aus dem Baum |
| 2 | `DynamicContentMap` behält `loop_*`; `DynamicContentRules`/`VariableRef` mit Kontext; Meldungen; Fixtures korrigieren | `loop_*` im passenden Kontext erlaubt, sonst Fehler |
| 3 | `{{loop:…}}` in `Placeholders`/Kurzform | Kurzform |
| 4 | Kontext in Insert, Update, Move; Move-Warnung | Werkzeuge nutzen den Kontext |
| 5 | `lit/divi-dynamic-sources` mit `post_id` + `node` | Optionen je Knoten |
| 6 | Smoke gegen echtes Divi (Loop-Seite, Speichern, Rendern) | `divi-loop-smoke.sh` |
| 7 | Guides | Etappe-4-Werkzeuge und Loops dokumentiert |
| 8 | Security-Audit Etappe 4 + Fix-Welle | Audit-Notiz, Fixes |
| 9 | Version 0.7.0, Spec-/Notiz-Nachträge, Verdrahtung | Release-Stand |
| 10 | Gesamtverifikation, Gesamtreview, Memory | Abschluss Etappe 4 |
---
<!-- TASKS-PLACEHOLDER -->