Zurück: [[ki:stand:edwiki]]
====== DokuWiki-Plugin-Leitfaden (edwiki 2.0) ======
> Arbeitsgrundlage für Claude bei jeder DokuWiki-Plugin-Arbeit in diesem Projekt (structauto u. a.). Angepasst aus **tracker-user/dokuwiki-claude** (GPL-2.0, https://github.com/tracker-user/dokuwiki-claude) – Umgebung, Browser-Vorgaben und Übersetzungen auf Olivers Setup umgestellt, Windows/Docker-Teile entfernt.
===== 1. Umgebung (Oliver) =====
* **Entwicklung/Test:** lima-city-Webspace (Freehosting) – **kein Shell-Zugang, keine Server-Logs**; Dateien per Upload/FTP. Einige ''%%Options%%''-Zeilen in ''%%.htaccess%%'' müssen dort auskommentiert werden.
* **Produktiv:** edwiki.de auf UD MEDIA (Klassik-Hosting) – PHP-Version noch per ''%%phpinfo()%%'' verifizieren.
* **DokuWiki-Version:** auf dem Testwiki prüfen (''%%?do=check%%'') und hier eintragen; bis dahin auf die aktuelle Version „Mort“ (2026-07-14) hin entwickeln, aber alte API-Pfade nicht blind entfernen.
* **PHP:** Ziel 8.x – exakte Untergrenze (''%%phpmin%%'') erst nach Prüfung beider Hoster festlegen.
* **Lokale Dev-Maschine:** CachyOS (Linux). Optional lokales Test-DokuWiki per Docker, ist aber kein Muss.
* **Browser-Untergrenze:** moderne Browser (aktuelles Firefox/Chrome) – **keine** Altbrowser-Einschränkungen nötig.
* **Sprachen:** jedes Plugin bekommt **''%%en%%'' + ''%%de%%''**.
* **Debugging ohne Logs:** ''%%?do=check%%'' für Umgebung/Rechte, ''%%allowdebug%%'' + ''%%Logger::debug()%%'' (Ausgabe im LogViewer unter Admin), notfalls temporäre ''%%msg()%%''-Ausgaben – vor dem Commit wieder entfernen.
===== 2. Arbeitsweise =====
* **Erst planen, dann coden:** Idee/Plan besprechen, Code erst nach ausdrücklicher Freigabe („leg los“).
* **Nicht raten, nachschlagen:** DokuWiki-APIs, Hooks und Klassen gegen den echten Quellcode prüfen (github.com/dokuwiki/dokuwiki bzw. struct-Plugin-Quellcode), nicht aus dem Gedächtnis.
* **Nach Änderungen an JS/CSS/lang** Cache brechen (siehe Abschnitt 9).
* **Zeilenenden:** LF in allen Dateien.
===== 3. Plugin-Aufbau =====
/
├── plugin.info.txt # Manifest (Pflicht)
├── syntax.php # Wiki-Syntax
├── action.php # Event-Hooks, AJAX, UI-Einbindung
├── admin.php # Admin-Seite
├── helper.php # reine Logik/API, von anderen Plugins nutzbar
├── renderer.php / remote.php / auth.php / cli.php
├── /.php # mehrere Komponenten eines Typs
├── script.js / style.css # werden GLOBAL auf jeder Seite gebündelt (sparsam!)
├── conf/default.php # Standardwerte ($conf[...])
├── conf/metadata.php # ACP-Metadaten ($meta[...]; kein require_once!)
├── lang//lang.php # UI-Texte ($lang[...]; keine Wiki-Syntax)
├── lang//settings.php # Labels für den Konfigurationsmanager
└── deleted.files # optional: beim Update zu löschende Dateien
**Benennung:** ''%%.php%%'' → Klasse ''%%_plugin_%%''. Mehrere Komponenten: ''%%/.php%%'' → ''%%_plugin__%%'' (Syntax-Modus dann ''%%plugin__%%''). Plugin-Name nur ''%%a-z0-9%%'', **kein Unterstrich**.
**Basisklassen** (''%%\dokuwiki\Extension\%%''): ''%%SyntaxPlugin%%'', ''%%ActionPlugin%%'', ''%%AdminPlugin%%'', ''%%Plugin%%'' (für Helper), ''%%RemotePlugin%%'', ''%%AuthPlugin%%'', ''%%CLIPlugin%%'', ''%%EventHandler%%''. Renderer erben von ''%%Doku_Renderer%%''.
===== 4. PHP-Konventionen =====
**Pflicht**
* ''%%if (!defined('DOKU_INC')) die();%%'' auf allen Logik-Dateien (action/syntax/helper/admin …), **nicht** auf ''%%conf/%%'' und ''%%lang/%%''.
* Sichtbarkeit immer explizit; ''%%protected%%'' statt ''%%private%%'' bevorzugen.
* Kein schließendes ''%%?>%%''. Code-Stil PSR-12.
* Docblocks (''%%@param%%'', ''%%@return%%'', Zweck) auf public/protected-Methoden.
* Eingaben nur über ''%%$INPUT->post->str()/int()/arr()%%'', ''%%$INPUT->get->str()%%'' – nie ''%%$_REQUEST/$_POST/$_GET%%''.
* Seiten-IDs mit ''%%cleanID()%%'' säubern.
* Dateien: ''%%io_saveFile()%%'' ist bereits atomar – **nicht** zusätzlich locken (Deadlock). Für Lesen-Ändern-Schreiben eigenes ''%%io_lock()%%''/''%%io_unlock()%%'' um die ganze Sequenz.
* JSON speichern mit ''%%JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE%%''.
**Stil:** ''%%[]%%'' statt ''%%array()%%'', ''%%??%%'' für Fallbacks, kein ''%%@%%'', ''%%in_array(..., true)%%'', ''%%str_starts_with()%%'', keine ''%%var_dump%%''/''%%print_r%%''/''%%error_log%%''-Reste.
**Rechte-Muster:** Helper sind reine Logik und bekommen Fakten als Parameter; die Action-Schicht sammelt sie (''%%$INPUT->server->str('REMOTE_USER')%%'', ''%%auth_isadmin()%%'').
===== 5. JS & CSS =====
* Vanilla JS, kein Framework. AJAX per ''%%fetch()%%'' mit ''%%JSINFO.sectok%%''. jQuery nur als ''%%jQuery()%%'', nie ''%%$()%%''.
* Alle Bundles laden ''%%defer%%''. Inline-JS nur über ''%%tpl_inlineScript()%%'' (setzt CSP-Nonce), nie handgeschriebenes ''%%%%''-Ausbruch möglich → gespeichertes XSS.
- **JS:** Fremddaten (AJAX-Antworten) mit ''%%textContent%%''/''%%.text()%%'' einsetzen, nie ''%%innerHTML%%''/''%%.html()%%''.
- **HTML-Kommentare:** Nutzerdaten nie in ''%%%%'' ausgeben – Browser beenden Kommentare auch bei ''%%--!>%%''.
- **CSRF ≠ Berechtigung:** Zustandsändernde Aktionen brauchen **beides**: ''%%checkSecurityToken()%%'' **und** ''%%auth_quickaclcheck($id)%%'' gegen die nötige Stufe (''%%AUTH_EDIT%%'', ''%%AUTH_DELETE%%'', ''%%AUTH_ADMIN%%''). Benutzer-ID über ''%%$INPUT->server->str('REMOTE_USER')%%''.
- **Keine eigenständigen PHP-Skripte**, die selbst ''%%inc/init.php%%'' laden und Anfrage-Daten ausgeben – die umgehen ACL und CSRF. Immer über ''%%action.php%%'' / ''%%AJAX_CALL_UNKNOWN%%''.
- **Keine Stacktraces/Serverpfade** an normale Nutzer ausgeben.
- **Ausnahme Editor-Round-Trip:** In Daten, die zurück ins Markup geschrieben werden (WYSIWYG), nicht escapen – nur an der HTML-Grenze, sonst wird ''%%&%%'' bei jedem Speichern zu ''%%&%%''.
===== 8. Review-Checkliste =====
- Fatale Fehler auf aktueller DokuWiki-Version (Namespace-Änderungen)
- DOKU_INC-Guard auf Logik-Dateien (nicht conf/lang)
- Sichtbarkeit auf allen Methoden, ''%%protected%%'' bevorzugt, kein ''%%?>%%''
- Docblocks auf public/protected
- Keine ''%%$_REQUEST/$_POST/$_GET%%'', kein ''%%@%%''
- Globales JS/CSS nur wenn nötig, sonst bedingt laden
- Datei-Schreibzugriffe korrekt gelockt (nie ''%%io_saveFile()%%'' extra locken)
- Alle Ausgaben escaped (''%%hsc()%%''), Attribut-Werte per Allowlist-Regex
- Null-Sicherheit bei Array-Zugriffen (''%%?? []%%'')
- ''%%conf/metadata.php%%'' ohne ''%%require_once%%''; ''%%default.php%%'' ↔ ''%%metadata.php%%'' Schlüssel synchron
- Keine Debug-Reste, TODO/FIXME markieren
- CSRF-Token **und** ACL-Prüfung bei zustandsändernden Aktionen
- URLs ''%%rawurlencode()%%'', nur ''%%http(s)%%''
- ''%%plugin.info.txt%%'' Datum/Version aktuell
- Übersetzungen ''%%en%%'' + ''%%de%%'' vollständig; hartkodierte Texte nach ''%%lang/en/lang.php%%'' auslagern
- README stimmt mit Code überein (Code hat Vorrang)
===== 9. Häufige Stolperfallen =====
* **Cache-Buster:** Nach Änderungen an ''%%script.js%%''/''%%style.css%%''/''%%lang%%'' liefern Browser weiter das alte Bündel, weil Plugin-Dateien nicht in die Bündel-URL einfließen. Lösung: im Konfigurationsmanager irgendeine Einstellung speichern (oder ''%%conf/local.php%%'' neu hochladen/anfassen). Das bricht auch den Metadaten-Render-Cache.
* **Metadaten-Cache:** Code-Änderungen am Plugin invalidieren gerenderte Metadaten nicht – gleicher Trick wie oben.
* **''%%~~MARKER~~%%'' für den Rest der Seite:** ''%%addSpecialPattern('~~MARKER~~[\s\S]*', …)%%'' fängt alles bis Seitenende als ein ungeparstes Token (Lexer läuft mit DOTALL).
* **''%%class_exists()%%''** löst Autoload aus; ''%%class_exists($c, false)%%'' nur, wenn man das explizit nicht will.
* **''%%metaFN($id, '.myplugin')%%''** liefert einen Pfad in ''%%data/meta/%%'' passend zur Seite.
* **Admin-Tabellen mit Zeilen-Buttons:** keine verschachtelten ''%%