Fehlerbehebung beim Anbinden eines eigenen Plugins
Symptome, die beim Arbeiten mit den Erweiterungspunkten der Suite auftreten, mit ihrer technischen Ursache und der Behebung. Die Beschreibung der Erweiterungspunkte selbst steht in der API-Referenz.
Mein Beitrag erscheint nirgends
Umgebung: wkfluentui installiert und aktiviert, eigenes Plugin registriert auf WKFLUENTUI_REGISTER_CONTRIBUTIONS.
Ursache: Der Descriptor wurde beim Einlesen verworfen. Fünf Prüfungen verwerfen still, und keine davon protokolliert etwas — ein stiller Verwurf ist das vorgesehene Verhalten, weil ein fehlerhafter Beitrag die Anfrage nicht kosten darf:
| Prüfung | Verworfen, wenn |
|---|---|
| Kennung | owner oder id fehlt oder ist leer |
| Art | kind ist gesetzt, aber keiner von view, action, route |
| Region | kind ist gesetzt, region fehlt oder gehört nicht zum Vokabular |
| Berechtigung | permission ist gesetzt, aber weder null noch aufrufbar |
| Sichtbarkeit | owner ist ein Admin-Plugin, das diese Sitzung nicht erreichen darf |
Behebung:
- Prüfen Sie, ob Ihr Haken überhaupt läuft — setzen Sie testweise eine Protokollzeile an den Anfang der Methode. Läuft er nicht, ist die
action-Komponente nicht geladen: falscher Klassenname (action_plugin_plus Verzeichnisname), falscher Dateiname, oder das Plugin ist im Erweiterungsmanager nicht aktiviert. - Prüfen Sie den Descriptor gegen die Tabelle oben.
- Fragen Sie das Register direkt ab und lassen Sie sich die Kennungen ins Protokoll schreiben. Steht Ihre Kennung dort, ist der Beitrag angenommen und das Problem liegt beim Zeichnen.
$contract = plugin_load('helper', 'wkfluentui_contract'); \dokuwiki\Logger::error('wkexample', implode(', ', array_keys($contract->all())));
Mein Beitrag verschwindet für alle außer Administratoren
Umgebung: Der eigene Descriptor trägt einen owner, der zugleich ein Admin-Plugin ist.
Ursache: Ist owner ein installiertes Admin-Plugin, entscheidet dessen eigenes isAccessibleByCurrentUser() über den Beitrag. Ein Admin-Plugin, dessen forAdminOnly() wahr liefert, schließt damit jede Sitzung ohne Superuser-Recht von allen Beiträgen dieses Eigentümers aus — auch von solchen, die mit dem Verwaltungsbildschirm nichts zu tun haben.
Das ist Absicht und keine Einschränkung, um die man herumbauen sollte. Die Prüfung schließt eine Lücke: Beiträge, die über das Ereignis kamen, hatten zeitweise gar keinen Sichtbarkeitsfilter, weil ein Haken bei jeder Sitzung läuft. Anonym gemessen lieferte das Register damals unter anderem die Anzahl eingebundener Git-Repositorien und die Adressen mehrerer Verwaltungsaktionen. In einen Browser gelangte davon nichts, weil jeder Verbraucher zusätzlich selbst prüft — verloren war nicht die Vertraulichkeit, sondern die Zusicherung.
Behebung: Trennen Sie die Anliegen. Was für alle sichtbar sein soll, gehört in ein Paket ohne eigenen Admin-Bildschirm; dieses hat keine solche Prüfung und bringt stattdessen ein eigenes permission mit, das nachbildet, was das Ziel des Beitrags durchsetzt:
'permission' => static fn(): bool => auth_quickaclcheck('start') >= AUTH_READ,
Ein permission, das weder null noch aufrufbar ist, verweigert — ein fehlerhafter Wert gewährt nie.
Mein Abzeichen erscheint nicht
Umgebung: Descriptor mit dem Feld badge.
Ursache: Eine von drei Bedingungen ist nicht erfüllt.
countist 0 oder kleiner. „Nichts zu melden„ ist keine anzuzeigende Null; das Abzeichen entfällt vollständig.countist weder eine Ganzzahl noch eine reine Ziffernzeichenkette. Der ganze Abzeichenwert wird dann verworfen.ownerist kein Admin-Plugin. Die Aktivitätsleiste zeigt einen Eintrag je Eintrag ausplugin_list('admin'). Ein Abzeichen schmückt diesen Eintrag — gibt es ihn nicht, wird das Abzeichen bei jeder Anfrage berechnet und nirgends gezeichnet.
Behebung: Legen Sie eine admin.php an, wenn Sie eine Zahl an der Aktivitätsleiste brauchen. Die kleinste zulässige Form steht im Tutorial, Schritt 5. Ein unbekannter Ton ist kein Fehler, sondern fällt auf normal zurück.
Mein Befehl ist ausgegraut und sagt nicht, warum
Umgebung: Descriptor mit kind action.
Ursache: enabled hat false geliefert, oder enabled war gesetzt, aber weder null noch aufrufbar — auch das ergibt „nicht ausführbar“, weil die Prüfung zuschließt. Ein Wurf innerhalb von enabled wird nicht protokolliert: die Prüfung läuft für jeden Beitrag bei jedem Registeraufbau, und ein wiederholt werfender Prüfer füllte das Protokoll mit derselben Zeile.
Behebung: Liefern Sie disabledReason mit. Ein untätiges Bedienelement ohne Begründung ist eine Sackgasse; die Zeichner geben den Text als Kurzhinweis und als zugängliche Beschreibung aus.
'enabled' => fn(): bool => $this->pendingCount() > 0, 'disabledReason' => $this->getLang('nothing_pending'),
Denken Sie daran, dass permission und enabled verschiedene Fragen sind: permission entscheidet, ob es den Eintrag für diese Sitzung überhaupt gibt; enabled entscheidet, ob der vorhandene Eintrag betätigt werden kann. Was eine Sitzung nicht haben darf, darf nicht ausgegraut erscheinen — ein ausgegrautes Bedienelement verrät weiterhin, dass der Befehl existiert.
wkcore: unknown service '…'
Umgebung: RuntimeException beim Aufruf von helper_plugin_wkcore::get().
Ursache: Die Kennung ist weder als aufgelöster Dienst noch als schwebende Erzeugerfunktion eingetragen. Die Meldung zählt alle bekannten Kennungen auf — lesen Sie sie, sie beantwortet die Frage meist unmittelbar. Drei Auslöser:
- Das eintragende Paket ist nicht installiert oder nicht aktiviert.
- Es ist installiert, aber seine
action-Komponente ist nicht geladen, sodass sein Haken aufWKCORE_REGISTER_SERVICESnie lief. - Die Kennung ist verschrieben. Es gibt keine unscharfe Auflösung.
Behebung: Prüfen Sie immer vor dem Zugriff, und behandeln Sie „Paket abwesend„ als fehlende Funktion, nicht als Fehler:
$core = plugin_load('helper', 'wkcore'); if ($core === null || !$core->has('vault.secrets')) { return null; // die Funktion fehlt, nichts stürzt ab } $secret = $core->get('vault.secrets')->read($id);
Zum Nachsehen, was tatsächlich eingetragen ist, gibt es den Diagnosebildschirm ?do=wkcore_diag. Er ist an $conf['allowdebug'] gebunden und auf einer Produktivinstallation nicht erreichbar. Werten Sie seine Ausgabe nicht maschinell aus: sie ist ausdrücklich nicht als stabiles Format zugesichert.
Nach dem zweiten Faktor lande ich auf der Startseite
Umgebung: Eigene Step-up-Brücke, helper_plugin_wkidentity_contract::require().
Ursache: Das Rückkehrziel war nicht auflösbar und ist auf den zugesicherten Rückfallwert $conf['start'] gefallen. Zulässig sind ausschließlich zwei Formen:
['type' => 'page', 'id' => $bereinigteSeitenId] // wahlweise + 'act' ['type' => 'workbench', 'id' => $bestehendeBeitragsKennung]
Eine URL wird nicht angenommen — auch keine gültige. Das ist bewusst so gebaut: das Paket, dem die Authentifizierung gehört, soll keine fremde Adresse auswerten und über deren Herkunft entscheiden müssen. Andernfalls könnte ein beeinträchtigter Beitragender steuern, wohin ein Mensch unmittelbar nach dem Nachweis eines zweiten Faktors geleitet wird.
Behebung:
- Für eine Seite: Typ
pagemit der aktuellen Seiten-ID. - Für einen seitengebundenen Bildschirm zusätzlich
act. Das ist ein bloßes Wort aus Kleinbuchstaben, Ziffern und Unterstrichen, höchstens 32 Zeichen; kein Schema, kein Punkt und kein Schrägstrich überleben das Muster. - Für einen Workbench-Bildschirm: Typ
workbenchmit einer derzeit aufzählbaren Beitragskennung. Gehört Ihrem Paket kein Admin-Bildschirm, landet diese Form auf dem leeren Verwaltungsindex; nehmen Sie dann die Seitenform.
Die Umleitung schlägt fehl: „Cannot modify header information"
Umgebung: require() oder send_redirect() aus einem Haken auf TPL_ACT_UNKNOWN.
Ursache: TPL_ACT_UNKNOWN feuert aus tpl_content() heraus — also mitten in einer Seite, die das Template bereits schreibt. Ein send_redirect() von dort leitet nicht um, sondern löst E_WARNING: Cannot modify header information aus und wird übersprungen.
Das trifft ausgerechnet den Pfad, der am meisten zählt: der richtige Code wird angenommen, der Nachweis wird vermerkt, und die Umleitung zum Ziel scheitert still. Zurück bleibt ein Mensch auf einer halb gezeichneten Prüfseite, ohne Anhaltspunkt, dass es funktioniert hat.
Behebung: Teilen Sie die Arbeit auf zwei Phasen auf. ACTION_ACT_PREPROCESS liegt echt vor jeder Ausgabe; dort fällt die Entscheidung samt Umleitung. TPL_ACT_UNKNOWN zeichnet nur noch, was die erste Phase entschieden hat:
public function register(EventHandler $controller) { $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'claimStepUp'); $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'renderStepUp'); }
Vollständig durchgespielt: How-To: eine Aktion hinter einen zweiten Faktor stellen.
Der Antragslink zeigt auf eine leere Seite
Umgebung: helper_plugin_wkrequest::urlFor() oder requestAction().
Ursache: urlFor() liefert eine leere Zeichenkette, wenn der Entwurf keine gültige Antragsart nennt. Ein leeres Ziel in einem Verweis ergibt einen Link auf die aktuelle Adresse — sichtbar, anklickbar, wirkungslos. Vier Umstände machen einen Antrag nicht stellbar:
typeist keine bekannte Antragsart.- Der Speicher fehlt, weil
wkcoreoderwkstoragenicht verfügbar sind. - Es gibt keine Sitzung, und die Art ist keine der beiden ohne Sitzung stellbaren (
MFA_RESET_REQUEST,ACCOUNT_SUPPORT_REQUEST). - Anonyme Anträge sind in der Konfiguration abgeschaltet.
Behebung: Verweisen Sie nie selbst auf das rohe Ergebnis von urlFor(). Benutzen Sie requestAction() und hängen Sie den Eintrag bedingungslos an Ihre Aktionsliste an. Ein nicht stellbarer Antrag ergibt einen Eintrag, den die Tafel verwirft — Sie brauchen keine eigene Verzweigung, und das Bedienelement erscheint nur dort, wo es tatsächlich funktionierte:
$actions[] = $req->requestAction($draft); $actions[] = $req->signInAction(); echo $req->panel(['actions' => $actions]);
Prüfen Sie zusätzlich mit supports('request.anonymous'), wenn Ihr Bildschirm auch ohne Sitzung erreichbar ist: Diese Fähigkeit bildet die Konfiguration ab, nicht nur den Code.
Ein Modul-Tag bleibt leer
Umgebung: Das Tag <module name="…"/> auf einer Wiki-Seite, Beitrag mit der Region adminModule.
Ursache: Zwei verschiedene Fehler mit demselben Erscheinungsbild.
ownerist kein Admin-Plugin. Der Modulverzeichnisdienst löst den tragenden Gegenstand mitplugin_load('admin', $owner)auf. Für einen Nicht-Admin-Eigentümer entsteht der Verzeichniseintrag trotzdem — der Name wird also gefunden — und das Zeichnen liefert dann eine leere Zeichenkette. Das liest sich wie ein kaputtes Modul, nicht wie ein nicht eintragbares.- Der Name im Tag ist der falsche. Geschlüsselt wird auf den Produktnamen, nicht auf den Menütext und nicht auf den Paketnamen. Der Menütext ist übersetzt: auf ihn geschlüsselt hört eine Seite auf zu funktionieren, sobald die Wikisprache wechselt. Der Paketname ist gegen Sprache stabil, aber nicht gegen Umbenennungen. Der Paketname löst als Rückfall zusätzlich auf.
Behebung: Legen Sie eine admin.php an, und benennen Sie das Modul im Tag mit seinem Produktnamen. Ein Modul, das ein abgeschicktes Formular verarbeiten soll, braucht außerdem beide Hälften: die frühe Phase für die Berechtigungsprüfung und die Verarbeitung, die späte fürs Zeichnen. Wird nur gezeichnet, erscheint das Modul richtig und kann nichts entgegennehmen — der Verzeichnisdienst sagt das, statt es zu überdecken.
Mein Bildschirm erscheint in zwei Schalen
Umgebung: Eigener Bildschirm, der die Workbench-Schale selbst zeichnet.
Ursache: Die Erklärung selfHosted ist bei dieser Anfrage nicht angekommen. Der häufigste Grund ist eine Platzierung innerhalb derselben Absicherung wie ein Haken, der für ein Abzeichen eine Datenbank befragt: fällt die Datenbank aus, greift die Fehlerbehandlung, und mit ihr entfällt die Schalenerklärung. Der Bildschirm wird dann ein zweites Mal eingefasst — ein Darstellungsfehler, dessen Ursache eine fehlgeschlagene Zählabfrage in einer anderen Datei ist.
Behebung: Kündigen Sie die Fläche in einem eigenen Descriptor an, außerhalb jeder Absicherung. Sie ist eine feste Eigenschaft des Pakets und keine Messgröße:
public function registerContributions(Event $event) { // Feststehende Tatsache — nie innerhalb eines try. $event->data['contributions'][] = [ 'owner' => 'wkexample', 'id' => 'surface', 'surface' => 'selfHosted', ]; // Alles, was gemessen werden muss, danach und abgesichert. try { $event->data['contributions'][] = [ 'owner' => 'wkexample', 'id' => 'badge', 'badge' => ['count' => $this->store()->countPending(), 'tone' => 'warning'], ]; } catch (\Throwable $e) { \dokuwiki\Logger::error('wkexample: badge count failed', $e->getMessage()); } }
Ein Paket darf die Fläche auf einem Descriptor erklären; alle weiteren Beiträge bleiben davon unberührt.