Benutzer-Werkzeuge

Webseiten-Werkzeuge


ki:edwiki:plugin:leitfaden

Zurück: 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

<name>/
├── 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
├── <typ>/<komp>.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/<iso>/lang.php       # UI-Texte ($lang[...]; keine Wiki-Syntax)
├── lang/<iso>/settings.php   # Labels für den Konfigurationsmanager
└── deleted.files             # optional: beim Update zu löschende Dateien

Benennung: <typ>.php → Klasse <typ>_plugin_<name>. Mehrere Komponenten: <typ>/<komp>.php → <typ>_plugin_<name>_<komp> (Syntax-Modus dann plugin_<name>_<komp>). 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 <script>.
  • JSINFO-Werte im Action-Plugin bei DOKUWIKI_STARTED unter einem plugin_<name>-Unterschlüssel setzen.
  • CSS: DokuWiki-Platzhalter (__background__, __text__, __border__, __link__ bzw. @ini_…), Klassen/IDs mit Plugin-Namen präfixen, IDs als <name>__id (doppelter Unterstrich).
  • Plugin-CSS/JS lädt auf jeder Seite. Feature-spezifisches per TPL_METAHEADER_OUTPUT bedingt einbinden.
  • Stolperfalle LESS: Alles CSS läuft durch lesserphp. rgba(var(--x), 0.3) wird zu Schwarz kompiliert → so schreiben: background: ~"rgba(var(--x), 0.3)"; (relevant fürs Schwarz/Orange-Theme mit CSS-Variablen).

6. DokuWiki-API kompakt

Syntax-Plugins

  • getType: container, baseonly, formatting, substition (Tippfehler ist korrekt!), protected, disabled, paragraphs. getPType: normal / block / stack. getSort: niedrigste gewinnt.
  • Patterns: addSpecialPattern (einmalig), addEntryPattern+addExitPattern (Paar). Keine Delimiter, (?:…) für Alternativen, keine Rückverweise, nur (?i).
  • handle() macht die schwere Arbeit – Ergebnis wird gecacht; Daten nur über das Rückgabe-Array an render().
  • render(): auf $format == 'xhtml' prüfen, an $renderer->doc anhängen, mit hsc() escapen. $format == 'metadata' behandeln und Links via $renderer->internallink() registrieren (Backlinks).
  • Seiten-Flags (z. B. „diese Seite hat eine Infobox“) im aktuellen Metadaten-Zweig setzen: $renderer->meta['plugin']['<name>'] = true; in render() bei metadata. Dann verschwindet das Flag von selbst, wenn die Syntax entfernt wird. Persistent gesetzte Flags bleiben für immer kleben.
  • p_render()/render() (Wikitext parsen) ist teuer – sparsam einsetzen und Ergebnis cachen.

Events

  • $controller->register_hook($name, 'BEFORE'|'AFTER', $this, 'methode', $param, $seq); Handler müssen public sein.
  • $seq: −3999…−1 früher, 0 Standard, 1…3999 später.
  • $e->preventDefault() (nur BEFORE) überspringt die Standardaktion; $e->stopPropagation() überspringt spätere Handler.
  • Häufige Events: DOKUWIKI_STARTED, TPL_METAHEADER_OUTPUT, TPL_ACT_RENDER, RENDERER_CONTENT_POSTPROCESS, PARSER_CACHE_USE, ACTION_ACT_PREPROCESS, AJAX_CALL_UNKNOWN, MENU_ITEMS_ASSEMBLY.
  • Externe Bibliotheken im Konstruktor oder bei Bedarf laden, nie am Dateianfang. Konstruktoren von Syntax-Plugins laufen bei jedem Parse – keine Seiteneffekte dort.

Konfiguration (conf/metadata.php) $meta['k'] = ['<klasse>', '_param' => …]; – Klassen: string, numeric/numericopt, onoff (Default als int 0/1, nicht bool), multichoice (_choices), multicheckbox, regex, fieldset … Labels in lang/<iso>/settings.php.

Lokalisierung: getLang('k'), locale_xhtml('datei'); JS-Texte über $lang['js']['k'] → LANG.plugins.<name>.k. Fehlende Schlüssel fallen auf Englisch zurück.

Caching & Metadaten

  • Cache nicht pauschal abschalten – Abhängigkeiten per PARSER_CACHE_USE ergänzen. ~~NOCACHE~~ erzwingt Neu-Rendern.
  • Wichtig für structauto: Wenn eine Seite Daten anderer Seiten/Struct-Tabellen anzeigt, muss der Cache wissen, wann er ungültig wird (sonst veraltete Infoboxen).
  • p_get_metadata($id, $key, $render) / p_set_metadata(...); Plugin-Daten unter plugin.<name>.

Globale & Konstanten: $ID, $INFO, $ACT (kann ein Array sein → !is_array() prüfen), $conf, $INPUT, $USERINFO, $JSINFO. DOKU_INC, DOKU_PLUGIN, DOKU_BASE, DOKU_URL.

Formulare: dokuwiki\Form\Form benutzen – Sicherheitstoken wird automatisch eingefügt.

7. Sicherheit (die teuersten Lektionen)

  1. Escapen: hsc() für HTML, rawurlencode() für URL-Werte; nutzergelieferte URLs nur mit ^https?:// erlauben (javascript: blocken).
  2. Attribut-Injection – häufigste XSS-Ursache in Syntax-Plugins: Ein Wert aus dem Wiki-Markup landet ungeprüft in class=, style=, id=, title= usw. Ein " bricht aus dem Attribut aus. Regel: (a) Wert per Regex-Capture-Group mit strengem Zeichensatz holen, beide Enden verankern (^…$), keine Quantoren, die leer matchen können (*, ?, \d*); nie nur trim(); (b) bei der Ausgabe zusätzlich hsc(). Redakteure gelten als nicht vertrauenswürdig.
  3. Struct-Werte sind Nutzereingaben: Alles, was aus Struct-Feldern in HTML (Infobox) geschrieben wird, escapen – oder, wie bei structauto, als Wikitext über p_render() laufen lassen.
  4. Regex-Nachbearbeitung von HTML (z. B. RENDERER_CONTENT_POSTPROCESS): nur Strukturen entfernen, nie Nutzerdaten unescaped wieder einsetzen.
  5. Daten ins Inline-JSINFO-Script: json_encode(..., JSON_HEX_TAG) verwenden. JSON_UNESCAPED_SLASHES allein macht </script>-Ausbruch möglich → gespeichertes XSS.
  6. JS: Fremddaten (AJAX-Antworten) mit textContent/.text() einsetzen, nie innerHTML/.html().
  7. HTML-Kommentare: Nutzerdaten nie in <!-- … --> ausgeben – Browser beenden Kommentare auch bei --!>.
  8. 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').
  9. 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.
  10. Keine Stacktraces/Serverpfade an normale Nutzer ausgeben.
  11. 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 &amp;.

8. Review-Checkliste

  1. Fatale Fehler auf aktueller DokuWiki-Version (Namespace-Änderungen)
  2. DOKU_INC-Guard auf Logik-Dateien (nicht conf/lang)
  3. Sichtbarkeit auf allen Methoden, protected bevorzugt, kein ?>
  4. Docblocks auf public/protected
  5. Keine $_REQUEST/$_POST/$_GET, kein @
  6. Globales JS/CSS nur wenn nötig, sonst bedingt laden
  7. Datei-Schreibzugriffe korrekt gelockt (nie io_saveFile() extra locken)
  8. Alle Ausgaben escaped (hsc()), Attribut-Werte per Allowlist-Regex
  9. Null-Sicherheit bei Array-Zugriffen (?? [])
  10. conf/metadata.php ohne require_once; default.php ↔ metadata.php Schlüssel synchron
  11. Keine Debug-Reste, TODO/FIXME markieren
  12. CSRF-Token und ACL-Prüfung bei zustandsändernden Aktionen
  13. URLs rawurlencode(), nur http(s)
  14. plugin.info.txt Datum/Version aktuell
  15. Übersetzungen en + de vollständig; hartkodierte Texte nach lang/en/lang.php auslagern
  16. 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 <form> (HTML5 form=-Attribut nutzen), kein Feld namens page.

10. plugin.info.txt & Veröffentlichung

  • Eigene Plugins: date = heutiges Datum (YYYY-MM-DD), phpmin X.Y falls nötig, keine Kommentare in der Datei.
  • Repo-Schema: github.com/<user>/dokuwiki-<name>, Branch main.
  • Plugin-Seite auf dokuwiki.org: Seiten-ID exakt plugin:<name>; downloadurl → …/zipball/main; Typ-Bitmaske: 1 Syntax, 2 Admin, 4 Action, 8 Render, 16 Helper, 64 Remote, 128 Auth, 256 CLI (structauto = Syntax + Action = Syntax, Action).
  • Vorlagen dafür: PUBLISHING.md, PLUGIN_REPO_REFERENCE.md, PROMPT_NEW_PLUGIN_PAGE.md im Quell-Repo.
  • WYSIWYG (ProseMirror-Editor)-Unterstützung bei Bedarf: PROSEMIRROR_PLUGIN_SUPPORT.md im Quell-Repo. Eigenständige Knoten gehen über die öffentliche API; Inline-Formatierungen mit Attributen erfordern Änderungen am ProseMirror-Plugin selbst.

11. Tests (optional)

Nur wenn beim Review ohnehin nötig: <plugin>/_test/, Namespace \dokuwiki\plugin\<name>\test\, erbt DokuWikiTest, @group plugin_<name>, protected $pluginsEnabled = ['<name>'];. Ein GeneralTest.php prüft plugin.info.txt und conf-Schlüssel-Sync. Läuft nur in einer lokalen DokuWiki-Installation, nicht auf lima-city.

ki/edwiki/plugin/leitfaden.txt · Zuletzt geändert: von nemwar