Sie befinden sich hier: start » de » Interne Dokumentation » DokuWiki-Erweiterungen (WvdS) » Erweiterungspunkte der WvdS-DW-Suite » Fehlerbehebung beim Anbinden eines eigenen Plugins

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:

  1. 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.
  2. Prüfen Sie den Descriptor gegen die Tabelle oben.
  3. 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.

  1. count ist 0 oder kleiner. „Nichts zu melden„ ist keine anzuzeigende Null; das Abzeichen entfällt vollständig.
  2. count ist weder eine Ganzzahl noch eine reine Ziffernzeichenkette. Der ganze Abzeichenwert wird dann verworfen.
  3. owner ist kein Admin-Plugin. Die Aktivitätsleiste zeigt einen Eintrag je Eintrag aus plugin_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:

  1. Das eintragende Paket ist nicht installiert oder nicht aktiviert.
  2. Es ist installiert, aber seine action-Komponente ist nicht geladen, sodass sein Haken auf WKCORE_REGISTER_SERVICES nie lief.
  3. 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 page mit 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 workbench mit 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.

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:

  1. type ist keine bekannte Antragsart.
  2. Der Speicher fehlt, weil wkcore oder wkstorage nicht verfügbar sind.
  3. Es gibt keine Sitzung, und die Art ist keine der beiden ohne Sitzung stellbaren (MFA_RESET_REQUEST, ACCOUNT_SUPPORT_REQUEST).
  4. 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.

  1. owner ist kein Admin-Plugin. Der Modulverzeichnisdienst löst den tragenden Gegenstand mit plugin_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.
  2. 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.

de/wiki/dwe/sdk/troubleshooting.txt · Zuletzt geändert: von 0.0.0.0