Zurück: edwiki
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.
Options-Zeilen in .htaccess müssen dort auskommentiert werden.phpinfo() verifizieren.?do=check) und hier eintragen; bis dahin auf die aktuelle Version „Mort“ (2026-07-14) hin entwickeln, aber alte API-Pfade nicht blind entfernen.phpmin) erst nach Prüfung beider Hoster festlegen.en + de.?do=check für Umgebung/Rechte, allowdebug + Logger::debug() (Ausgabe im LogViewer unter Admin), notfalls temporäre msg()-Ausgaben – vor dem Commit wieder entfernen.<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.
Pflicht
if (!defined('DOKU_INC')) die(); auf allen Logik-Dateien (action/syntax/helper/admin …), nicht auf conf/ und lang/.protected statt private bevorzugen.?>. Code-Stil PSR-12.@param, @return, Zweck) auf public/protected-Methoden.$INPUT->post->str()/int()/arr(), $INPUT->get->str() – nie $_REQUEST/$_POST/$_GET.cleanID() säubern.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_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()).
fetch() mit JSINFO.sectok. jQuery nur als jQuery(), nie $().defer. Inline-JS nur über tpl_inlineScript() (setzt CSP-Nonce), nie handgeschriebenes <script>.DOKUWIKI_STARTED unter einem plugin_<name>-Unterschlüssel setzen.__background__, __text__, __border__, __link__ bzw. @ini_…), Klassen/IDs mit Plugin-Namen präfixen, IDs als <name>__id (doppelter Unterstrich).TPL_METAHEADER_OUTPUT bedingt einbinden.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).Syntax-Plugins
getType: container, baseonly, formatting, substition (Tippfehler ist korrekt!), protected, disabled, paragraphs. getPType: normal / block / stack. getSort: niedrigste gewinnt.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).$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.DOKUWIKI_STARTED, TPL_METAHEADER_OUTPUT, TPL_ACT_RENDER, RENDERER_CONTENT_POSTPROCESS, PARSER_CACHE_USE, ACTION_ACT_PREPROCESS, AJAX_CALL_UNKNOWN, MENU_ITEMS_ASSEMBLY.
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
PARSER_CACHE_USE ergänzen. ~~NOCACHE~~ erzwingt Neu-Rendern.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.
hsc() für HTML, rawurlencode() für URL-Werte; nutzergelieferte URLs nur mit ^https?:// erlauben (javascript: blocken).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.p_render() laufen lassen.RENDERER_CONTENT_POSTPROCESS): nur Strukturen entfernen, nie Nutzerdaten unescaped wieder einsetzen.JSINFO-Script: json_encode(..., JSON_HEX_TAG) verwenden. JSON_UNESCAPED_SLASHES allein macht </script>-Ausbruch möglich → gespeichertes XSS.textContent/.text() einsetzen, nie innerHTML/.html().<!-- … --> ausgeben – Browser beenden Kommentare auch bei --!>.checkSecurityToken() und auth_quickaclcheck($id) gegen die nötige Stufe (AUTH_EDIT, AUTH_DELETE, AUTH_ADMIN). Benutzer-ID über $INPUT->server->str('REMOTE_USER').inc/init.php laden und Anfrage-Daten ausgeben – die umgehen ACL und CSRF. Immer über action.php / AJAX_CALL_UNKNOWN.& bei jedem Speichern zu &.protected bevorzugt, kein ?>$_REQUEST/$_POST/$_GET, kein @io_saveFile() extra locken)hsc()), Attribut-Werte per Allowlist-Regex?? [])conf/metadata.php ohne require_once; default.php ↔ metadata.php Schlüssel synchronrawurlencode(), nur http(s)plugin.info.txt Datum/Version aktuellen + de vollständig; hartkodierte Texte nach lang/en/lang.php auslagernscript.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.~~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.<form> (HTML5 form=-Attribut nutzen), kein Feld namens page.date = heutiges Datum (YYYY-MM-DD), phpmin X.Y falls nötig, keine Kommentare in der Datei.github.com/<user>/dokuwiki-<name>, Branch main.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).PUBLISHING.md, PLUGIN_REPO_REFERENCE.md, PROMPT_NEW_PLUGIN_PAGE.md im Quell-Repo.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.
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.