feature/707-komponenten-soc-helper-rewiring — der Template-master lädt aktuell noch keinen wkfluentui-Helper; Merge-Entscheidung liegt beim Nutzer.
Status: Tier 1 — beschreibt tatsächlich umgesetzten Code, verifiziert gegen
wkfluentui@1366902.
Diese Seite ist die Quelle der Wahrheit für die öffentliche PHP-API von
wkfluentui: sechs eigenständige Helper-Klassen mit zusammen
36 öffentlichen Methoden, die framework-freie HTML-Bausteine (Baum,
Tabs, Drawer/Modal, Formular-Raster, Datentabelle, Tree List,
Eingabefelder-Familie, NavBar, Card, CountBadge, Listen-Filter,
Editor-Tab-Strip, Command-Palette, Kontextmenü-Quelle,
Code-Editor-Aktivierung, Toolbar, CSV-Export), Schema-Formular-Seiten
(formpage) sowie ACL-geprüfte
Datenermittlung (Admin-Leiste, Bildergalerie, Cross-Plugin-Navigation,
Avatar-Initialen) bereitstellen. Zielgruppe: jedes DokuWiki-Plugin oder
-Template, das diese Bausteine wiederverwenden will — die API ist bewusst
nicht an premium-navy-ivory gebunden.
Die meisten Methoden haben zusätzlich eine eigene, vertiefte
Komponentenseite mit Demo-Skizze und Feature-Unterseiten; diese Seite
verweist dort hin, statt den Vertrag zu duplizieren (Zuordnung: Tabelle in
Abschnitt 6). Nur drawer(), modal(), rowActions() sowie je eine
Methode in adminrail/gallery/nav/user haben keine andere
Seite — hier steht ihr vollständiger Vertrag.
wkfluentui (lib/plugins/wkfluentui/).hsc(), wl(), cleanID(), auth_quickaclcheck() — kein Aufruf außerhalb einer DokuWiki-Instanz möglich).listImages(), getAdminRailItems()): ein bereits initialisierter Nutzer-/ACL-Kontext (Standard in jedem regulären DokuWiki-Request).
Sechs getrennte Helper-Klassen, kein gemeinsames helper.php.
wkfluentui hat bewusst kein eigenes Wurzel-helper.php —
plugin_load('helper', 'wkfluentui') liefert deshalb immer null,
lautlos, ohne Fehler oder Warnung. Jede Komponente liegt unter
helper/<component>.php und wird einzeln geladen (Details siehe
Abschnitt 11, Troubleshooting, weiter unten auf dieser Seite) — dieser
Namensfehler blieb in wk-dw-msqlite-plugin mehrere Features lang
unbemerkt, weil DokuWiki selbst keine Warnung für einen unbekannten
Helper-Namen ausgibt.
Escaping-Konvention (gilt für alle Methoden einheitlich): jede
Methode escaped die ihr übergebenen Klartext-Werte (Label, Titel, Href)
selbst über hsc() (schützt gegen CWE-79/XSS). Parameter, deren Name auf
Html endet (controlHtml, bodyHtml, element), sind dagegen
bereits vom Aufrufer erzeugtes, vertrauenswürdiges HTML — der Aufrufer
trägt dort die Escaping-Verantwortung. Eine Ausnahme von der ersten Regel ist
dokumentiert in Abschnitt
„gallery" unten.
CSS-Gegenstück: die Sektion WIDGETS in wkfluentui/style.css
(site-weit automatisch eingebunden über DokuWikis css_pluginstyles()).
Minimalbeispiel: Toolbar-Helfer laden und eine einzeilige Aktionsleiste rendern.
/** @var helper_plugin_wkfluentui_widgets $widgets */ $widgets = plugin_load('helper', 'wkfluentui_widgets'); echo $widgets->toolbar([ ['label' => 'Neu', 'element' => '<button type="submit" name="do" value="new">Neu</button>'], ]);
Falsch: plugin_load('helper', 'wkfluentui') (ohne Suffix) — liefert
null, jeder nachfolgende Methodenaufruf löst einen Fatal Error „Call to a
member function … on null„ aus.
| Helper | Konsument | Verwendete Methoden |
|---|---|---|
adminrail | lib/tpl/wkbizway/inc/wkbizway-render.php | getAdminRailItems() 1) |
gallery | lib/tpl/wkbizway/inc/wkbizway-render.php | listImages() 2) |
nav | lib/tpl/wkbizway/inc/nav.php | getBlogCategoryNav(), getAcmenuFlyout() 3) |
user | lib/tpl/wkbizway/main.php, inc/wiki-shell.php | avatarInitials() 4) |
widgets | lib/plugins/wksqliteds/dbadmin/*.php | die neun Bestandsmethoden (Table Designer, Object Explorer, „Connect to…“-Dialog); die neu hinzugekommenen Methoden (Eingabefelder-Familie, treeList(), navBar() u. a.) warten auf ihren ersten Konsumenten |
Weitere WvdS-Plugins sind eingeladen, dieselben Helper zu nutzen (siehe FluentUI (Design-System-Bibliothek), Abschnitt „Rolle„).
| Klasse | Datei | plugin_load()-Aufruf | Methode(n) | Vertrag |
|---|---|---|---|---|
helper_plugin_wkfluentui_adminrail | helper/adminrail.php | plugin_load('helper', 'wkfluentui_adminrail') | getAdminRailItems() | unten, Abschnitt „adminrail" |
helper_plugin_wkfluentui_gallery | helper/gallery.php | plugin_load('helper', 'wkfluentui_gallery') | listImages() | unten, Abschnitt „gallery" |
helper_plugin_wkfluentui_nav | helper/nav.php | plugin_load('helper', 'wkfluentui_nav') | getBlogCategoryNav(), getAcmenuFlyout() | unten, Abschnitt „nav" |
helper_plugin_wkfluentui_user | helper/user.php | plugin_load('helper', 'wkfluentui_user') | avatarInitials() | unten, Abschnitt „user" |
helper_plugin_wkfluentui_formpage | helper/formpage.php | plugin_load('helper', 'wkfluentui_formpage') | loadSchema(), validate(), serialize(), parse(), formFields() | formpage → Schema-Formular-Seiten |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | tree() | treeview → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | tabs() | tabcontrol → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | drawer() | unten, Abschnitt „drawer" |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | modal() | unten, Abschnitt „modal" |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | propertyGrid() | property-grid → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | dataGrid() | datagrid → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | rowActions() | unten, Abschnitt „rowActions" |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | codeEditor() | editors → codeEditor()-Baustein |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | toolbar() | toolbar → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | treeList() | treelist → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | navBar() | navbar → Grundlagen |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | button() | form-fields → Button |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | calendar() | form-fields → Calendar |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | checkbox() | form-fields → Checkbox |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | comboBox() | form-fields → ComboBox |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | dateTime() | form-fields → DateTime |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | dropdown() | form-fields → Dropdown Editor |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | listEditor() | form-fields → List Editor |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | progressBar() | form-fields → ProgressBar |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | exportRows() | datagrid → Data Export |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | countBadge() | widgets → Katalog (CountBadge) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | card() | widgets → Katalog (Card/Tile) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | declarativeTable() | widgets → Katalog (DeclarativeTable) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | listFilter() | widgets → Katalog (InputBox-Filter) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | editorTabs() | widgets → Katalog (Editor-Tab-Strip) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | commandPalette() | widgets → Katalog (Command-Palette) |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | workbench() | unten, Abschnitt „workbench" |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | statusbar() | unten, Abschnitt „statusbar" |
helper_plugin_wkfluentui_widgets | helper/widgets.php | plugin_load('helper', 'wkfluentui_widgets') | accordion() | unten, Abschnitt „accordion" |
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_adminrail |
| Datei | lib/plugins/wkfluentui/helper/adminrail.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
getAdminRailItems(string $currentPage): array — liefert die
ACL-gefilterte, in die drei festen Buckets admin/manager/other
gruppierte und alphabetisch (Fallback: Menü-Sortierindex) sortierte Liste
aller für den angemeldeten Nutzer sichtbaren Admin-Plugins, im selben Format
wie DokuWikis eigene do=admin-Kachelansicht (\dokuwiki\Ui\Admin),
aber flach statt gruppiert und um ein Inline-SVG-icon-Feld sowie ein
active-Flag für $currentPage ergänzt.
| Parameter | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
$currentPage | string | Ja | der page-Request-Parameter des aktuell angezeigten Admin-Screens ($INPUT->str('page')); leerer String auf dem nackten Admin-Index |
Rückgabewert: Array von ['id' => string, 'label' => string, 'href' => string, 'icon' => string, 'active' => bool] je sichtbarem Admin-Plugin. Reihenfolge: Bucket admin → manager → other, innerhalb jedes Buckets alphabetisch nach Label. Leeres Array, wenn der Nutzer kein einziges Admin-Plugin sehen darf — kein Fehler.
Sicherheit: Die Bucket-Zugehörigkeit (welche Plugin-IDs zu admin/
manager zählen) ist ein wörtliches Duplikat der Liste in DokuWikis
eigenem \dokuwiki\Ui\Admin — bewusst, damit dieselbe Installation in
beiden Ansichten dieselbe Gruppierung zeigt; ein Core-Update, das diese Liste
ändert, erfordert einen synchronen Abgleich hier. ACL-Prüfung über
isAccessibleByCurrentUser() je Plugin (CWE-284/Broken Access Control) —
der Aufrufer muss nicht zusätzlich filtern.
Nebenwirkungen: keine (reine Lese-/Enumerationsmethode).
Fehlerverhalten: wirft keine Exceptions. Ein Plugin, dessen
Menü-Icon-Datei fehlt/zu groß/unlesbar ist, erhält icon' ⇒ ''`` — dieselbe
stille Degradation wie in DokuWikis eigenem Ui\Admin::showMenuItem()
(kein Platzhalter-Icon).
$rail = plugin_load('helper', 'wkfluentui_adminrail'); $items = $rail->getAdminRailItems($INPUT->str('page')); foreach ($items as $item) { echo '<a href="' . $item['href'] . '"' . ($item['active'] ? ' class="active"' : '') . '>' . $item['icon'] . hsc($item['label']) . '</a>'; }
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_gallery |
| Datei | lib/plugins/wkfluentui/helper/gallery.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
listImages(string $ns, int $thumb = 200): array — liefert alle
lesbaren Bilddateien direkt in $ns (nicht rekursiv, keine
Unter-Namespaces).
| Parameter | Typ | Erforderlich | Standardwert | Beschreibung |
|---|---|---|---|---|
$ns | string | Ja | – | Medien-Namespace; wird intern über cleanID() normalisiert |
$thumb | int | Nein | 200 | Thumbnail-Breite in Pixeln, intern auf 32–1024 begrenzt |
Rückgabewert: Array von
['id' => string, 'full' => string (ml()-URL), 'thumb' => string (ml()-URL), 'name' => string]
je Bild. Leeres Array bei ungültigem/leerem $ns oder fehlendem Leserecht.
Sicherheit — abweichende Escaping-Regel (Ausnahme zur allgemeinen
Konvention aus Abschnitt 3): $ns wird über cleanID() normalisiert
(CWE-22, Path Traversal) und vor dem Dateisystemzugriff per
auth_quickaclcheck("$ns:*") < AUTH_READ geprüft (CWE-284). Das
name-Feld im Rückgabewert ist roh (noNS($id), nicht
hsc()-escaped) — anders als bei jeder anderen Methode dieser API. Der
Aufrufer muss name vor der Ausgabe selbst escapen; full/thumb
sind bereits fertige, in sich sichere URLs (ml()).
Nebenwirkungen: ein Dateisystem-Scan (search() über
$conf['mediadir'], Tiefe 1) je Aufruf — kein Cache.
Fehlerverhalten: wirft keine Exceptions; alle Fehlerfälle (ungültiger
Namespace, fehlendes Leserecht, leerer Namespace) liefern ein leeres Array,
nicht null und nicht false — ein Aufrufer kann immer direkt iterieren.
$gallery = plugin_load('helper', 'wkfluentui_gallery'); foreach ($gallery->listImages('de:blog:media', 300) as $img) { echo '<img src="' . $img['thumb'] . '" alt="' . hsc($img['name']) . '">'; }
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_nav |
| Datei | lib/plugins/wkfluentui/helper/nav.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
Zwei Methoden, beide null-tolerant: ist das jeweilige Ziel-Plugin deaktiviert/nicht installiert, liefern beide den leeren String statt eines Fehlers.
getBlogCategoryNav(string $rootNs): string — Blog-Kategorie-Flyout-Fragment für einen Blog-Root-Namespace (z. B. de:blog), delegiert an wkblog_tags::getCategoryTree().getAcmenuFlyout(string $rootNs): string — Namespace-Baum-Flyout-Fragment für einen Wiki-Root-Namespace (z. B. de:wiki), delegiert an wkacmenu::renderNavFlyout().
Rückgabewert (beide): HTML-Fragment (eine <li>-Liste), leerer
String wenn das jeweilige Ziel-Plugin fehlt oder keine Daten liefert.
Nebenwirkungen: beide Methoden cachen intern (DokuWikis
\dokuwiki\Cache\Cache, 1800 Sekunden Lebensdauer, CACHE_AGE-Konstante)
— je Namespace UND je Gruppenmitgliedschaft-Set des aktuell angemeldeten
Nutzers (Sicherheitsgrund, s. u.), nicht global. getBlogCategoryNav()
hängt seinen Cache-Eintrag zusätzlich an wkblog_tagss eigene
Speicherdatei (cacheDependencyFile()), damit eine reine Tag-Änderung
(die die Blogseite selbst nicht berührt) den Cache sofort invalidiert statt
bis zu 1800 Sekunden veraltet zu bleiben. getAcmenuFlyout() hängt seinen
Cache-Eintrag zusätzlich an DokuWikis Seitenindex
(data/index/page.idx): der wächst, sobald eine neu angelegte Seite
erstindiziert wird (Taskrunner beim nächsten Seitenaufruf) — neue Seiten
erscheinen damit ohne 30-Minuten-Wartezeit im Flyout. Dokumentierte Grenze:
Löschungen berühren page.idx nicht (die PID-Zeilen bleiben stabil),
eine gelöschte Seite kann daher bis zum Ablauf der Altersgrenze im Flyout
verbleiben.
Sicherheit: Der Cache-Schlüssel enthält bewusst die
Gruppenmitgliedschaft des Nutzers (sortierte @Gruppe-Liste) — ohne diesen
Bestandteil würde ein ACL-gefilterter Navigationsbaum, den ein
privilegierter Nutzer zuerst aufruft, im Cache landen und danach
fälschlicherweise auch einem nicht-privilegierten Nutzer ausgeliefert
(Cache-basiertes ACL-Leck, CWE-284).
Fehlerverhalten: keine Exceptions; fehlendes Ziel-Plugin → leerer String (kein Log-Eintrag, kein Fehler).
$nav = plugin_load('helper', 'wkfluentui_nav'); echo $nav->getBlogCategoryNav('de:blog'); echo $nav->getAcmenuFlyout('de:wiki');
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_user |
| Datei | lib/plugins/wkfluentui/helper/user.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
avatarInitials(string $realName, string $login): string — liefert
ein bis zwei Großbuchstaben-Initialen für einen Avatar-Platzhalter, wenn
keine hochgeladene Avatar-Datei existiert (die Datei-Lookup-Logik selbst
liegt bewusst nicht hier, s. Abgrenzung unten).
| Parameter | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
$realName | string | Ja (darf leer sein) | Anzeigename des Nutzers |
$login | string | Ja | Login-Name, nur als Fallback genutzt |
Algorithmus: erstes + letztes Wort von $realName bei 2+ Wörtern,
erste zwei Zeichen von $realName bei genau einem Wort, erste zwei
Zeichen von $login als Fallback wenn $realName leer ist. Multibyte-
sicher (mb_substr/mb_strtoupper).
Rückgabewert: string, immer nicht-leer, sofern $login nicht
ebenfalls leer ist.
Sicherheit: Die Methode escaped ihre Ausgabe nicht selbst (anders
als die allgemeine Konvention aus Abschnitt 3, die nur für widgets.php
gilt) — $realName stammt aus dem Nutzerprofil und kann theoretisch
beliebige Zeichen enthalten. Der Aufrufer muss den Rückgabewert vor der
Ausgabe selbst hsc()-escapen.
Abgrenzung: bewusst getrennt von der Avatar-Datei-Lookup-Logik in
wvdsavatar/action.php::currentAvatarUrl() — kleine, stabile
String-Logik bleibt hier dupliziert statt eine harte Cross-Plugin-
Laufzeitabhängigkeit für wenige Zeilen einzuführen (dokumentierte,
bewusste Entscheidung, kein Versehen).
$user = plugin_load('helper', 'wkfluentui_user'); $initials = $user->avatarInitials($INFO['userinfo']['name'] ?? '', $_SERVER['REMOTE_USER'] ?? ''); echo '<span class="avatar-placeholder">' . hsc($initials) . '</span>';
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_widgets |
| Datei | lib/plugins/wkfluentui/helper/widgets.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
drawer(string $title, string $bodyHtml, string $cancelHref): string
— rechtsseitiges Slide-in-Panel: ein Vollflächen-Scrim (Klick-/Escape-Ziel =
$cancelHref, ein normaler Link, funktioniert dadurch vollständig ohne
JavaScript) plus ein Panel fester Breite. Generalisiert die zuvor in
DbAdmin::renderNewTableDrawer()/renderAddColumnDrawer()/
renderEditDrawer() duplizierte Struktur.
| Parameter | Typ | Beschreibung |
|---|---|---|
$title | string | Drawer-Überschrift, hsc()-escaped |
$bodyHtml | string | vertrauenswürdiges HTML (z. B. ein Formular) — Aufrufer-escaped |
$cancelHref | string | Scrim-/Abbrechen-Ziel-URL, hsc()-escaped |
Erzeugtes Markup: <div class="wk-drawer"> →
<a class="wk-scrim" href="…"> → <div class="wk-drawer__panel" role="dialog" aria-modal="true">
mit <h3>-Titel und $bodyHtml.
Nebenwirkungen: keine (reine Render-Methode, kein State/Request-Zugriff).
Fehlerverhalten: keines — alle drei Parameter sind Pflicht-Strings, ein leerer String ist für jeden Parameter ein gültiger, wenn auch funktional sinnloser Wert (leerer Titel/Body/Scrim-Ziel).
echo $widgets->drawer( 'Neue Tabelle', $widgets->propertyGrid($spaltenFelder), wl($ID, ['ns' => 'tabellen']) );
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_widgets |
| Datei | lib/plugins/wkfluentui/helper/widgets.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
modal(string $title, string $bodyHtml, string $cancelHref, array $opts = []): string
— zentriertes Dialogfenster, gleiches Scrim-Prinzip wie drawer(), mittig
statt seitlich verankert.
| Parameter | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
$title | string | Ja | Dialog-Überschrift, hsc()-escaped |
$bodyHtml | string | Ja | vertrauenswürdiges HTML — Aufrufer-escaped |
$cancelHref | string | Ja | Scrim-/Abbrechen-Ziel-URL, hsc()-escaped |
$opts['size'] | string | Nein | normal (Default, 640px) oder workbench (1024×768, resize:both, Viewport-Clamp, <1024px vollflächig) |
$opts['context'] | string | Nein | setzt data-wk-dialog-context; scripts/dialog.js merkt die zuletzt gewählte Größe je Kontext in localStorage unter wk-dialog-size:<context> |
Rückwärtskompatibilität: die 3-Parameter-Aufrufform (ohne $opts)
rendert byte-identisch zur Vorversion bis auf ein zusätzliches
tabindex=“-1„ am Panel (programmatisches Fokusziel der Fokusfalle —
ohne JavaScript wirkungslos, kein sichtbarer Unterschied).
Abgrenzung: das Laden eines fremden Admin-Moduls in einen Dialog
läuft nicht über modal(), sondern über den
a[data-wk-dialog-src]-Trigger-Link-Vertrag (der Link navigiert
ohne JavaScript normal und wird nur progressiv zum Inline-Workbench-Dialog)
— Details: admin-layout →
„Fremd-Modul-Aufrufe als Inline-Dialog".
Nebenwirkungen: keine serverseitig; clientseitig (mit
scripts/dialog.js): Fokusfalle, Escape-Handler, localStorage-
Schreibzugriff bei workbench-Größe mit gesetztem context.
Fehlerverhalten: unbekannter $opts['size']-Wert degradiert
still auf normal (kein Fehler, keine Warnung).
echo $widgets->modal( 'Verbindung herstellen', $widgets->propertyGrid($verbindungsFelder), wl($ID), ['size' => 'workbench', 'context' => 'connect'] );
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_widgets |
| Datei | lib/plugins/wkfluentui/helper/widgets.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
rowActions(array $items): string — verpackt bereits vorhandene,
sichtbare Aktions-Elemente einer Baum-/Grid-Zeile als versteckte
<ul class="wk-row-actions" hidden> (Kontextmenü-Quelle für
scripts/contextmenu.js, s. u.).
| Parameter | Typ | Beschreibung |
|---|---|---|
$items | array | Liste ['label' => string, 'element' => string] — element ist das echte, bereits sichtbar gerenderte <a>-/<button>-HTML |
Rückgabewert: HTML-String; leerer String bei leerem $items (keine
Menü-Anzeige für eine Zeile ohne Aktionen — kein leeres, verwirrendes
Kontextmenü).
Wichtige Invariante: rowActions() dupliziert keine Logik — jedes
element muss identisch mit dem Element sein, das bereits sichtbar in der
Zeile steht. Das Kontextmenü triggert bei Klick .click() auf das echte
Original-Element, es öffnet keinen zweiten Code-Pfad.
Client-Gegenstück scripts/contextmenu.js (progressive Erweiterung):
bei Rechtsklick auf ein Element mit data-wk-menu wird das native
Kontextmenü unterdrückt und ein Popup aus dem benachbarten
.wk-row-actions-Fragment gerendert. Ohne JavaScript bleibt jede
Aktion über die sichtbaren Buttons erreichbar. Einzige Zusatzfunktion ohne
No-JS-Äquivalent: „Copy“/„Copy with Headers„ über
data-wk-copy/data-wk-copy-headers-Attribute
(navigator.clipboard.writeText(…)).
Tastatur (WAI-ARIA-Menu-Muster): Die Einträge tragen
role="menuitem"; beim Öffnen erhält der erste Eintrag den Fokus,
Pfeil hoch/runter wandern zyklisch, Pos1/Ende springen zum
ersten/letzten Eintrag, Escape schließt; beim Schließen kehrt der Fokus
zum nächstliegenden fokussierbaren Element der Ursprungszeile zurück
(dieselbe Mechanik wie die Menüs von scripts/admin-rail.js). Hinweis:
derzeit rendert kein Konsument im lib/-Baum das
data-wk-menu-Attribut — der Vertrag ist gegen das ausgelieferte
Skript (scripts/contextmenu.js) verifiziert, der frühere Erstkonsument
(SQL-Workbench-Ergebniszeilen) setzt es seit seiner Umstrukturierung nicht
mehr.
Nebenwirkungen: keine serverseitig.
Fehlerverhalten: keines — ein element-Wert, der kein valides HTML
ist, wird unverändert ausgegeben (der Aufrufer trägt die Verantwortung für
valides, sicheres HTML in element, s. Escaping-Konvention Abschnitt 3).
$actions = $widgets->rowActions([ ['label' => 'Bearbeiten', 'element' => '<a href="' . wl($ID, ['do' => 'edit']) . '">Bearbeiten</a>'], ['label' => 'Löschen', 'element' => '<button type="submit" name="do" value="delete">Löschen</button>'], ]);
workbench(array $regions, array $opts = []): string — komponiert die Shell-Regionen in das generische Workbench-Grid .wk-workbench (Layout-Schicht; die Region-Optik bleibt bei den .wk-shell-*-Primitives, die Innenleben bleiben Aufrufer-HTML).
$regions: Schlüssel commandbar · sidebar · content (Pflicht) · auxbar · rail · statusbar. Jeder Wert ist ein vertrauenswürdiges Aufrufer-HTML-Fragment (Aufrufer escapet — derselbe Vertrag wie drawer()/modal()). Fehlende/leere Regionen emittieren kein Element (die Grid-Variante lässt den Track kollabieren statt eine tote Lücke zu lassen). Trägt das äußerste Element des Fragments bereits die Region-Klasse, wird es unverändert durchgereicht (Attribute wie is-collapsed oder data-wk-auxbar-src bleiben erhalten); nackte Fragmente werden in das Standard-Element der Region gehüllt.$opts['variant']: '''' (4-spaltig) · flat · no-sidebar · no-auxbar · auxbar-collapsed · split — muss einem CSS-Archetyp entsprechen; unbekannte Werte fallen auf das volle Grid zurück. split stellt Content und Auxbar als zwei gleich breite Hälften nebeneinander (minmax(0,1fr) minmax(0,1fr)): der Archetyp für eine zweite, eigenständige Arbeitsfläche neben der ersten — im Unterschied zu no-sidebar, dessen feste 280px-Spalte für „Eigenschaften der Auswahl“ gedacht ist und für eine Arbeitsfläche zu schmal. Unter 1024px stapeln die Hälften; die zweite bleibt sichtbar (sie ist offen, weil der Benutzer sie geöffnet hat — anders als die generische Auxbar, die dort ausgeblendet wird).$opts['class']: zusätzliche Container-Klassen (Klartext, escaped) — der plugin-lokale Scope-Hook (z. B. wkq-shell).$opts['context']: stabiler Flächen-Bezeichner (Klartext, escaped) → data-wk-workbench-context, der Persistenz-Schlüssel für Client-Präferenzen (Sash-Breiten). Ohne Kontext keine Persistenz.
Verhaltens-Schicht: scripts/workbench.js (Overlay/Scrim/Region-Toggles) und scripts/sash.js (Resize) docken über die Container-Klasse bzw. das Kontext-Attribut an — Regeln in FluentUI: Admin-Bereich (vertieft).
statusbar(array $left, array $right = [], array $opts = []): string — Statusbar-Region mit Gruppen-/Item-Anatomie und Prioritäts-Vertrag.
['text' => …] (Klartext, escaped) oder ['html' => …] (vertrauenswürdiges Aufrufer-HTML, gewinnt über text); optional 'priority' => int (bereits die Anwesenheit ist das Signal — Items ohne Priorität verschwinden unter 1024px) und 'title' => … (Klartext-Tooltip, escaped).$opts['class']: zusätzliche Container-Klassen (Klartext, escaped).Warnung: eine Statusbar, deren Items alle keine Priorität tragen, ist unter 1024px leer — die überlebenswichtigen Items immer markieren.
Seit 2026-07-17 (Design-System-Vereinheitlichung, WDX-WdxAccordion-inspiriert,
s. widgets → Katalog (Accordion)).
accordion(array $items, array $opts = []): string — Progressive-Disclosure-Akkordeon
aus nativen, exklusiven <details name="…">-Gruppen (Öffnen eines Abschnitts
schließt die anderen derselben Gruppe automatisch) — kein JavaScript für die Kernfunktion
nötig.
$items: je Eintrag ['summary' => string (Klartext, escaped), 'bodyHtml' => string (vertrauenswürdiges Aufrufer-HTML), 'id' => string (optional, HTML-''id'' des ''<details>''), 'open' => bool (optional, Standard-offener Abschnitt)].$opts['context']: Exklusivitäts-Gruppenname, validiert gegen ^[a-z][a-z0-9-]*$ (ungültig/fehlend → default). Zwei accordion()-Aufrufe mit demselben Kontext auf derselben Seite bilden eine Exklusivitätsgruppe über beide Aufrufe hinweg (der Wert wandert 1:1 in <details name="wk-accordion-<context>">; HTML gruppiert per name unabhängig von der DOM-Position) — nützlich, wenn ein Aufrufer mehrere accordion()-Blöcke rendert, die trotzdem gemeinsam exklusiv bleiben sollen (Erstkonsument: wkfluentui/action/admintoc.php, ein Akkordeon-Block je getTOC()-Abschnitt, gemeinsamer Kontext admintoc).
Rückgabewert: string — <div class="wk-accordion">…</div>. Leerer String bei leerem $items.
Kompatibilität: <details name>-Exklusivität landete in Chrome/Edge 120, Firefox 122,
Safari 17.2 (~2023/24). Ältere Browser ignorieren die Gruppierung und lassen jeden Abschnitt
unabhängig offen — identisch zu einer einfachen <details>-Liste ohne name, kein
Funktionsverlust, nur ohne die Exklusivitäts-Politur.
$widgets = plugin_load('helper', 'wkfluentui_widgets'); echo $widgets->accordion([ ['summary' => 'Sprache', 'bodyHtml' => $langFieldsetHtml, 'id' => 'lang', 'open' => true], ['summary' => 'Titel', 'bodyHtml' => $titleFieldsetHtml, 'id' => 'title'], ], ['context' => 'admintoc']);
wkToast(type, text, opts) (scripts/toast.js): type ∈ info/success/warning/error (unbekannt → info); text ist Klartext und wird nie als HTML interpretiert; opts.timeout in ms (Default 6000; error ignoriert ihn und bleibt bis zum Schließen); Rückgabe ist eine Dismiss-Funktion. Nur für asynchrone JS-Rückmeldungen — msg() bleibt der Kanal für Vollseiten-POSTs.scripts/sash.js): injiziert je vorhandener Sidebar/Auxbar eine role="separator"-Leiste; schreibt --wk-workbench-<region>-width inline auf den Container; Persistenz unter wk-workbench-size:<context>:<region> (re-geklemmt: Sidebar 180–480px, Auxbar 200–520px).body[data-wk-density="compact"] ist der einzige Schalter der Wirkschicht (Sektion DENSITY in style.css); wer den Umschalter stellt (Template-User-Menü) und wie persistiert wird (wk-density:<login>), gehört dem Template.Admin-Rail plus Avatar-Initialen in einer Template-Kopfzeile kombiniert (realistischer Ausschnitt, wie ihn ein Template nach Merge des Refactor-Branches einsetzen würde):
<?php /** @var helper_plugin_wkfluentui_adminrail $rail */ $rail = plugin_load('helper', 'wkfluentui_adminrail'); /** @var helper_plugin_wkfluentui_user $userHelper */ $userHelper = plugin_load('helper', 'wkfluentui_user'); global $INFO, $INPUT; ?> <nav class="wk-shell-rail"> <?php foreach ($rail->getAdminRailItems($INPUT->str('page')) as $item): ?> <a href="<?php echo $item['href']; ?>"<?php echo $item['active'] ? ' class="active"' : ''; ?> aria-current="<?php echo $item['active'] ? 'page' : 'false'; ?>"> <?php echo $item['icon']; ?> <span class="a11y"><?php echo hsc($item['label']); ?></span> </a> <?php endforeach; ?> </nav> <div class="wk-avatar-placeholder"> <?php echo hsc($userHelper->avatarInitials($INFO['userinfo']['name'] ?? '', $INFO['userinfo']['login'] ?? '')); ?> </div>
$_REQUEST/$_POST zu oder verändert Zustand — reine Render-/Enumerations-Methoden. Formular-Verarbeitung (POST-Handling, sectok-Prüfung) bleibt vollständig Sache des Aufrufers.navs Cache-Lebensdauer (1800s) ist fest verdrahtet (CACHE_AGE-Konstante), nicht konfigurierbar über $opts — eine Änderung erfordert einen Code-Eingriff.gallery::listImages() ist nicht rekursiv — Bilder in Unter-Namespaces von $ns werden nicht gefunden, das ist bewusst so (keine versehentliche Tiefenexplosion bei großen Medienbäumen).scripts/*.js-Dateien sind ausnahmslos progressive Erweiterungen, nie Voraussetzung.tree(), tabs(), propertyGrid(), dataGrid(), treeList(), toolbar(), navBar(), codeEditor() und die Eingabefelder-Familie) — auf deren dedizierten Seiten (Verweise: Abschnitt 6).
Symptom: Fatal error: Call to a member function toolbar() on null
(oder jede andere Methode dieser API) direkt nach plugin_load('helper',
'wkfluentui').
Ursache: wkfluentui hat kein Wurzel-helper.php — der Aufruf
ohne Komponenten-Suffix liefert null, lautlos, ohne eigenen Fehler
(s. Abschnitt 3). Dieser Namensfehler blieb in wk-dw-msqlite-plugin
längere Zeit unbemerkt, weil DokuWiki selbst keine Warnung dafür ausgibt.
Lösung: plugin_load('helper', 'wkfluentui_<component>') mit dem
korrekten Suffix aufrufen — widgets, adminrail, gallery, nav,
user oder formpage (Tabelle in Abschnitt 6).
scripts/dialog.js/scripts/auxbar.js/scripts/admin-rail.js
Verifiziert gegen: wkfluentui@274a7d9 (Branch feature/454-workbench-grid: workbench()/statusbar()/wkToast()/Sash/Dichte), wk-dw-sqliteds-plugin@24b83d2