WordPress-Plugin Projektmanager für ökologische Gutachterbüros

Ein Custom WordPress-Plugin ersetzt einen Stapel Word-Vorlagen: Aus losen Berichts-Templates mit Briefkopf wird ein eigenes Projektmanagement-Tool mit Gastzugängen für Mitarbeiter, Vor-Ort-Erfassung im Browser und automatischer PDF-Generierung – inklusive aufgestempeltem Firmen-Briefpapier.

Für ein ökologisches Gutachterbüro habe ich genau das gebaut. Das Büro erstellt artenschutzrechtliche Berichte – Begehungen, FFH-Vorprüfungen, Umsiedlungs-Protokolle, Monitoring-Tagesprotokolle. Vor dem Plugin lief das so: Mitarbeiter waren tagsüber im Feld, machten handschriftliche Notizen und Fotos, abends wurde im Büro abgetippt, eine Word-Vorlage geöffnet, der Briefkopf hoffentlich nicht vergessen, am Ende ein PDF rausgeschickt. Heute öffnet ein Mitarbeiter mit Gastlink die URL auf dem Handy, füllt das Formular direkt vor Ort aus, lädt Bilder mit Kommentar und Orientierung hoch – und der Bürochef gibt am Ende mit einem Klick frei.

In diesem Artikel zeige ich, wie der Workflow technisch funktioniert: das Datenmodell, die Gastzugang-Logik, die PDF-Engine mit Briefpapier-Stamping – und warum für so ein Setup ein WordPress-Plugin sinnvoller war als jede SaaS-Lösung.

Wie wurden Artenschutzberichte vorher erstellt?

Die alte Welt bestand aus neun fachlichen Word- und PDF-Vorlagen, alle mit Firmen-Briefkopf, alle mit eigenen Pflichtfeldern. Tagesprotokolle für Begehungen, Vorlagen für FFH-Vorprüfungen, Relevanzcheck-Artprotokolle, Kartiertermine, Monitoring-Tagesprotokolle, Umsiedlungs-Berichte – jede Vorlage mit eigenem Layout, eigenen Tabellen, eigenen Pflichtangaben.

Der Workflow lief so: Vor-Ort-Begehung mit Notizblock und Kamera, abends Reinschrift im Büro. Die Word-Vorlage wurde aus einem Vorjahres-Projekt kopiert, halbherzig zurückgesetzt, manuell ausgefüllt. Bilder lagen irgendwo auf OneDrive, mussten gesucht, sortiert, ins Dokument gezogen werden. Aktenzeichen wurden manuell vergeben – mit dem klassischen Risiko, dass die nächste Nummer schon doppelt vergeben war oder eine Lücke entstand.

Versendet wurde per E-Mail mit PDF im Anhang. Die finale Version landete in einem Projektordner, eine Kopie ging mehr oder weniger zuverlässig in einen Backup-Ordner. Wenn ein Mitarbeiter krank war oder gewechselt hatte, suchte man die letzten Berichte zusammen wie Ostereier.

Die typischen Fehlerquellen

Falscher Briefkopf, vergessenes Aktenzeichen, doppelte Lauf­nummer, vertauschte Bilder, alte Vorlagenversion aus dem Vorjahr – jede Stelle, an der ein Mensch manuell etwas einträgt, ist eine Stelle, an der etwas falsch sein kann.

Was musste das System leisten?

Das Plugin musste die neun Berichtstypen sauber abbilden, einen klaren Status pro Bericht haben (Entwurf vs. abgeschlossen) und externe Mitarbeiter ins System lassen können – ohne dafür WordPress-Accounts anlegen zu müssen. Dazu kamen ein konsistenter Briefkopf auf allen PDFs, automatische Nummernvergabe pro Projekt und ein Cloud-Backup für den Fall, dass die WordPress-Installation mal ausfällt.

Anforderungen im Überblick
  • 9 fachliche Berichtstypen mit individuellem PDF-Layout
  • Projekt-Hierarchie: Kunde → Projekt → Berichte
  • Gastzugänge für externe Mitarbeiter ohne WordPress-Account
  • Vor-Ort-Erfassung im Browser (Handy, Tablet, Laptop)
  • Bilder mit Kommentar und Orientierung pro Beobachtung
  • Konsistenter Firmen-Briefkopf auf allen PDFs
  • Automatische Aktenzeichen-Vergabe pro Projekt
  • Cloud-Backup der finalen Berichte

Wichtig war außerdem, dass das Büro das System selbst betreiben kann. Keine Vendor-Abhängigkeit, keine monatlichen Lizenzkosten pro Mitarbeiter, keine Datenexport-Klauseln in irgendeinem AGB-Kleingedruckten. Die Daten gehören dem Büro – das war von Anfang an gesetzt.

Warum ein WordPress-Plugin und keine SaaS-Lösung?

Drei Gründe haben gegen eine SaaS-Lösung wie Jotform Enterprise, Formstack Documents oder 123FormBuilder gesprochen:

Erstens: Der bestehende WordPress-Stack. Hosting, Backup, User-Management, SSL, Updates – das alles läuft seit Jahren. Eine SaaS-Lösung obendrauf hätte einen zweiten Stack mit eigener Datenpflege, eigenen Logins und eigenen Audit-Logs bedeutet.

Zweitens: Die domänenspezifischen Felder. Artkürzel, Schutzgebietskürzel, Brutverdacht-Kategorien, Eingriffsflächen – das sind Datenstrukturen, die kein generischer Form-Builder out-of-the-box abbildet. Im SaaS müsste man jedes Feld einzeln nachbauen, oft ohne saubere Validierung. Beim eigenen Plugin werden die Listen aus einer importierten arten_import.csv gespeist, ergänzt um eigene Stammdaten.

Drittens: Datensouveränität. Artenschutzberichte enthalten Standortdaten gefährdeter Arten, Auftraggeber-Informationen, manchmal Eingriffsplanungen für sensible Bauvorhaben. Diese Daten verlassen mit dem Plugin den eigenen Server nicht – außer als verschlüsseltes Backup zur kontrollierten Cloud-Instanz (LuckyCloud auf Seafile-Basis).

Kriterium SaaS (Jotform/Formstack) Eigenes Plugin
Setup-Kosten Niedrig Einmalig vierstellig
Laufende Kosten ~3.000–10.000 €/Jahr Hosting + Wartung
Domänen-Felder Generisch Maßgeschneidert
Datenhoheit Beim Anbieter Auf eigenem Server
Anpassbarkeit Begrenzt Vollständig
Ausstieg Datenexport-Aufwand Daten gehören dem Büro

Datenmodell: 8 Tabellen statt CPT-Salat

Bei WordPress-Plugins ist die Versuchung groß, alles über Custom Post Types und postmeta zu lösen. Bei diesem Plugin habe ich bewusst den anderen Weg gewählt: acht eigene MySQL-Tabellen, sauber per dbDelta beim Aktivieren angelegt.

Der Grund: Beziehungen sauber abbilden. Ein Projekt hat n Mitarbeiter, m Berichts-Templates, k Berichts-Instanzen, jede Instanz hat j Bilder und null oder einen Gastzugang. Mit postmeta wird das schnell zum N+1-Query-Massaker. Mit eigenen Tabellen sind das saubere JOINs mit Indizes – und das DB-Schema ist auf einen Blick verständlich.

  • fm_projects – Projekt mit Kundendaten und Aktenzeichen
  • fm_project_users – Mitarbeiter-Zuordnung (n:m)
  • fm_form_templates – die 9 Berichtstypen mit Feldstruktur (JSON)
  • fm_project_forms – welcher Bericht ist welchem Projekt zugeordnet
  • fm_form_instances – konkrete Ausfüllungen (Status: entwurf / abgeschlossen)
  • fm_form_images – Bilder mit Kommentar und Orientierung
  • fm_form_guest_access – Token + Passwort + Status pro Instanz
  • fm_settings – globale Plugin-Einstellungen

Der Workflow vom Auftrag bis zum verschickten PDF läuft komplett über diese acht Tabellen. Kein Posttyp-Geflecht, keine versteckten Performance-Fallen.

Gastzugänge für externe Mitarbeiter

Das Kern-Feature des Plugins: Mitarbeiter können Berichte ausfüllen, ohne einen WordPress-Account zu haben. Praktisch für saisonale Kräfte, freie Mitarbeiter oder kurzfristig eingebundene Praktikanten. Technisch ist das ein Token-plus-Passwort-System mit kontrolliertem Lebenszyklus.

Pro Berichts-Instanz wird genau ein Gastzugang angelegt: ein 40-stelliger zufälliger Token (per wp_generate_password(40, false, false)) und ein vom Verwalter vergebenes Passwort. Beides wird in fm_form_guest_access gespeichert – das Passwort als wp_hash_password-Hash, der Token im Klartext (er ersetzt den Benutzernamen).

PHP
// Gastzugang für eine Berichts-Instanz erstellen
public function create_for_instance($instance_id, $password, $created_by = 0) {
    global $wpdb;
    $instance = $wpdb->get_row($wpdb->prepare(
        "SELECT id, status FROM {$this->instances_table} WHERE id = %d",
        $instance_id), ARRAY_A);

    // Nur Entwürfe können einen Gastzugang bekommen
    if (!$instance || $instance['status'] !== 'entwurf') return false;

    $token         = wp_generate_password(40, false, false);
    $password_hash = wp_hash_password($password);

    // Pro Instanz nur ein aktiver Zugang
    $wpdb->delete($this->table, ['instance_id' => $instance_id], ['%d']);
    $wpdb->insert($this->table, [
        'instance_id'   => $instance_id,
        'access_token'  => $token,
        'password_hash' => $password_hash,
        'status'        => 'active',
        'created_by'    => (int) $created_by,
    ]);
    return ['instance_id' => $instance_id, 'access_token' => $token];
}

Der Verwalter bekommt nach dem Erstellen die URL und das Passwort und schickt beides extern an den Mitarbeiter – per E-Mail, Signal, wie er möchte. Die URL hat das Format ?fm_project_slug=…&fm_guest_token=…. Beim Aufruf prüft das Plugin Token plus Passwort und gibt nur dann Zugriff auf genau diese eine Instanz frei. Keine Projektliste, keine anderen Berichte, kein Zugriff auf abgeschlossene Berichte.

Das Eleganteste an der Lösung ist der Self-Revoking-Mechanismus: Sobald der Bericht abgeschlossen wird, wird der Gastzugang automatisch gelöscht. Kein Cron-Job, der nach Ablaufzeiten sucht. Keine vergessenen Tokens, die monatelang gültig bleiben. Der Gastzugang lebt genau so lange wie er gebraucht wird – und keinen Tag länger.

Self-revoking ohne Cron

Beim Form-Abschluss wird der zugehörige Gastzugang in derselben Transaktion gelöscht. Kein Hintergrundprozess, kein TTL-Counter, keine vergessenen Token. Der Lifecycle ist an die Domain-Logik gekoppelt, nicht an einen Timer.

PDF-Generierung mit Briefpapier-Stamping

Die PDF-Engine ist mit Abstand der größte Brocken im Plugin: class-fm-pdf.php mit knapp 5.000 Zeilen. Kein Twig, kein Blade – pures PHP-HTML mit einer eigenen Build-Triade pro Berichtstyp: is_*_protocol(), build_*_definition(), render_*_intro(). Das ist nicht hübsch, aber es bildet die neun unterschiedlichen Layouts sauber ab und bleibt beim Debuggen lesbar.

Unter der Haube läuft Dompdf für die HTML-zu-PDF-Konvertierung. Mehrseitige Berichte werden über CSS @page-Regeln gesteuert: Kopf- und Fußzeile als @page { @top-center { … } @bottom-center { … } }, fortlaufende Seitennummerierung über counter(page).

Der Trick mit dem Firmen-Briefpapier: Statt das Layout in CSS nachzubauen (was bei Logos, Wasserzeichen und Fonts immer eine Quelle für Inkonsistenzen ist), wird das vorhandene PDF-Briefpapier per FPDI über jede Seite der Dompdf-Ausgabe gestempelt. Das Briefpapier-PDF kommt aus der Grafikabteilung, das Plugin muss es nur korrekt überlagern – Pixel-perfekt, mit allen Schriftarten, Farbprofilen und Rändern, die die Druckerei vorgegeben hat.

PHP
// PDF generieren und ablegen
public function generate($save_to_file = false) {
    if (!$save_to_file) return $this->generate_html(true);

    $loader = new FM_DomPDF_Loader();
    $html   = $this->generate_html_for_dompdf();
    $result = $loader->generate_pdf($html, $this->build_filename());
    if (!$result['success']) return false;

    $upload_dir = wp_upload_dir();
    $temp_dir   = $upload_dir['basedir'] . '/fm-temp/';
    if (!file_exists($temp_dir)) wp_mkdir_p($temp_dir);

    // Zeit-Präfix verhindert kollidierende Dateinamen + erschwert Brute-Force-URLs
    $filepath = $temp_dir . time() . '_' . $this->build_filename();
    file_put_contents($filepath, $result['pdf']);
    return $filepath;
}

Die fertigen PDFs liegen in wp-uploads/fm-temp/ mit Zeit-Präfix im Dateinamen. Die Auslieferung läuft nicht über direkte URLs auf die Datei, sondern über einen template_redirect-Hook, der erst die Berechtigung prüft und dann den Datei-Stream zurückschickt – ein klassischer "gerouteter Download".

Vor-Ort-Erfassung: Bilder mit Kommentar und Orientierung

Der eigentliche Effizienzgewinn entsteht draußen im Feld. Mitarbeiter öffnen den Gastlink auf dem Handy, geben Beobachtungen direkt während der Begehung ein und laden Fotos hoch. Pro Bild speichert das Plugin nicht nur die Datei selbst, sondern auch einen Kommentar ("Brutverdacht Buntspecht in alter Eiche") und eine Orientierung ("Blickrichtung Süd").

Im Browser läuft alles über AJAX: Tippt der Mitarbeiter ins Formular, wird der Zwischenstand per Autosave als JSON in data_json der Instanz aktualisiert. Lädt er ein Bild hoch, geht der File-Upload an einen nopriv-AJAX-Endpoint, der den Token bei jedem Call gegenprüft. Server-seitig wird jedes Eingabefeld rekursiv mit sanitize_form_value() gesäubert, plus check_ajax_referer als Nonce-Schutz auf jedem Endpoint.

Formular-Eingabemaske im Browser mit Pflichtfeldern und Bilder-Upload
Eingabemaske im Browser – Felder, Bilder mit Kommentar und Orientierung lassen sich direkt vor Ort erfassen.
Aus dem Foto-Anhang wird ein Bericht

Im finalen PDF erscheinen alle hochgeladenen Bilder als Foto-Appendix mit Beschriftung. Was vorher zwei Stunden Reinschrift im Büro war (Bilder suchen, einsortieren, Bildunterschriften tippen), passiert jetzt schon während der Begehung.

Projektnummerierung & Cloud-Backup

Aktenzeichen sind in der Gutachterwelt heilig. Jeder Bericht braucht eine eindeutige, fortlaufende Nummer pro Projekt – und zwar mit der gleichen Logik wie früher in der Word-Welt. Das Plugin löst das mit einer zentralen Funktion fm_get_instance_project_number(), die das Format Typindex.Laufnummer erzeugt: 3.07 bedeutet "dritter Berichtstyp im Projekt, siebte Instanz davon".

Wichtig dabei: Die Funktion ist zentral. Es gibt nicht in jeder View eine eigene Nummern-Logik. Wenn die Nummerierungsregel sich ändert, ändert sie sich an einer Stelle. Das hatte ich vorher anders versucht – jede View hatte ihre eigene get_number()-Methode – und das endete in zwei verschiedenen Nummern für denselben Bericht, je nachdem ob man ihn in der Übersicht oder im PDF gesehen hat.

Für das Cloud-Backup nutzt das Plugin die LuckyCloud-API – LuckyCloud läuft auf Seafile, einer Open-Source-Cloud-Storage-Lösung mit OAuth-Token-Authentifizierung. Pro Projekt wird ein eigener Cloud-Ordner angelegt, abgeschlossene PDFs werden automatisch dort hochgeladen. Der API-Token wird per set_transient gecacht (eine Woche TTL), neue Tokens werden bei Ablauf transparent geholt.

Zahlen & Architektur

Ein paar Eckdaten, um die Größenordnung des Plugins einzuordnen:

  • 9 fachliche Berichtstypen mit individuellem PDF-Layout
  • 8 Custom MySQL-Tabellen
  • 35+ AJAX-Endpoints (sowohl wp_ajax_* als auch wp_ajax_nopriv_*)
  • Composer-Pakete: Dompdf, FPDF, FPDI
  • ~22.000 Zeilen PHP gesamt
Plugin-Bereich Zeilen Code Verantwortlichkeit
includes/ ~9.200 Domain-Klassen (PDF, Project, Guest-Access, Email, Cloud, Species)
public/ ~5.000 Frontend-Controller + Form-Views + AJAX
admin/ ~3.700 Backend-Views (Projekte, Formulare, Settings)
Bootstrap ~2.800 Plugin-Setup, Hooks, Routing

Der mit Abstand größte Brocken in einer einzelnen Datei ist class-fm-pdf.php mit fast 5.000 Zeilen. Das ist normalerweise ein Code-Smell – aber bei neun unterschiedlichen Berichts-Layouts mit jeweils eigenen Build-Methoden ist die Aufteilung weniger eindeutig, als sie klingt. Eine sinnvolle Refactoring-Richtung wäre, jeden Berichtstyp in eine eigene Renderer-Klasse zu ziehen. Funktioniert hat es so wie es ist – das war hier wichtiger als architektonische Eleganz.

Wann lohnt sich ein eigenes Plugin?

Ein eigenes WordPress-Plugin ist nicht für jeden Fall die richtige Lösung. Bei einem Standard-Kontaktformular wäre WPForms oder Gravity Forms schneller eingerichtet. Bei einer Handvoll PDF-Vorlagen reicht oft Formstack Documents.

Sinnvoll wird die Eigenentwicklung in dieser Konstellation:

  • Mehr als 5–10 unterschiedliche Berichts-Layouts mit individuellen Pflichtfeldern
  • Domänenspezifische Datenstrukturen, die kein generischer Form-Builder sauber abbildet
  • Externe oder saisonale Mitarbeiter, die ohne WordPress-Account ins System sollen
  • Ein bestehender WordPress-Stack, in den das Plugin sich nahtlos einfügt
  • DSGVO-Anforderungen oder Datensouveränitäts-Wünsche, die On-Premise-Speicherung verlangen
  • SaaS-Kosten, die in Summe über mehrere Jahre 5–15.000 € erreichen würden

Der Break-even gegenüber einer SaaS-Alternative liegt bei diesem Setup typischerweise bei 18–24 Monaten. Danach zahlt das Plugin sich Monat für Monat selbst zurück – und das Büro hat den vollen Code-Zugriff plus die Möglichkeit, jederzeit nachzubessern, ohne auf eine Roadmap warten zu müssen.

Was Eigenentwicklung nicht ersetzt

Ein eigenes Plugin braucht Wartung – Updates für PHP-Versionen, Sicherheits-Patches, gelegentliche Anpassungen wenn WordPress oder Plugins darunter sich ändern. Im Schnitt sind das 2–6 Stunden pro Quartal. Wer das nicht einplanen kann, sollte eher zur SaaS-Lösung greifen.

Häufige Fragen zur Digitalisierung von Berichts-Workflows

Wie funktioniert die Vor-Ort-Erfassung von Berichten ohne WordPress-Account?

Über Gastzugänge. Das Plugin generiert pro Bericht einen 40-stelligen Token plus ein vom Verwalter vergebenes Passwort. Der Mitarbeiter öffnet die URL auf dem Handy, gibt das Passwort ein und kann das Formular ausfüllen, Bilder hochladen und kommentieren – alles ohne WordPress-User-Account.

Welche PDF-Library generiert die Berichte mit Firmen-Briefkopf?

Dompdf erzeugt das HTML-zu-PDF, FPDI stempelt anschließend das vorhandene Briefpapier-PDF über jede Seite. So bleibt das Corporate Design konsistent und es muss kein eigenes Layout in CSS nachgebaut werden. Mehrseitige Berichte mit Kopf-/Fußzeilen werden über CSS @page-Regeln gesteuert.

Sind Gastzugänge mit Token und Passwort sicher genug?

Für interne Mitarbeiter mit kontrolliertem Verteilkreis ja. Pro Formular existiert genau ein aktiver Zugang, der beim Abschluss des Berichts automatisch gelöscht wird (Self-Revoking). AJAX-Endpoints prüfen das Token bei jedem Aufruf. Für höhere Anforderungen lassen sich Token-TTL, Audit-Log und 2FA nachrüsten.

Wie viele Berichtstypen sind in einem solchen Plugin sinnvoll?

Im konkreten Fall sind es neun fachliche Vorlagen – von Tagesprotokoll und Begehung bis FFH-Vorprüfung und Monitoring. Jeder Berichtstyp hat ein eigenes PDF-Layout mit Briefkopf, aber alle teilen sich die Projekt-Verwaltung, das Bilderhandling und die Gastzugänge. Mehr als 10–15 Typen würde ich auf einen schemagetriebenen Form-Builder umstellen.

Was kostet eine SaaS-Alternative wie Jotform oder Formstack?

Realistisch 3.000–10.000 Euro pro Jahr für ein Setup mit Document-Generation, Projekt-Hierarchie und Gastzugängen. Jotform Enterprise startet im niedrigen vierstelligen Bereich pro Jahr, Formstack Documents plus Forms summiert sich schnell zu fünfstelligen Jahreskosten. Der domänenspezifische Workflow (Aktenzeichen, Briefkopf, fachspezifische Felder) müsste dort trotzdem nachgebaut werden.

Wann lohnt sich ein eigenes WordPress-Plugin gegenüber einer SaaS-Lösung?

Wenn ein bestehender WordPress-Stack vorhanden ist, die Workflows domänenspezifisch sind, externe Mitarbeiter ohne WordPress-Account ins System sollen und die Daten on-premise bleiben müssen. Bei mehr als 5–10 individuellen Berichts-Layouts ist die einmalige Plugin-Entwicklung typischerweise nach 18–24 Monaten amortisiert – inklusive vollem Code-Eigentum und Anpassungsfreiheit.

Eigene Berichte digitalisieren?

Du planst eine ähnliche Digitalisierung von Formularen oder Berichten – mit Gastzugängen, PDF-Generierung oder Cloud-Backup? Schreib mir, ich schaue mir deinen Workflow an.

Kontakt