Search by

ithilbert / kontaktformular

ithilbert

Mein Kontaktformular mit Verarbeitungscode

Package info

github.com/ITHilbert/kontaktformular

Homepage

pkg:composer/ithilbert/kontaktformular

Statistics

Installs: 184

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

2.2.1 2026-11-13 19:34 UTC

This package is auto-updated.

Last update: 2026-11-13 21:03:55 UTC


README

Beschreibung: Kontaktformular mit Spam-Schutz, optionalem Datei-Upload und Mail-Versand.

Features

  • Felder: Name, E-Mail, Telefon (optional), Nachricht, Datei-Anhang (optional)
  • Spam-Schutz: Honeypot-Feld + Mindest-Nachrichtenlänge + Rate-Limit (throttle:6,1)
  • Datei-Upload mit Whitelist (pdf,jpg,png,zip,doc,docx,xls,xlsx), automatisches Zippen
  • Token-basierter Download-Link mit Hash + Name + ID
  • Cleanup-Command für alte Dateien (kontaktformular:delete_old_files)

Installation

composer require ithilbert/kontaktformular
php artisan vendor:publish --tag=kontaktformular-config
php artisan migrate

Konfiguration

// config/kontaktformular.php
return [
    'mailFrom'        => env('KONTAKTFORMULAR_MAIL_FROM', env('MAIL_FROM_ADDRESS')),
    'mailTo'          => env('KONTAKTFORMULAR_MAIL_TO',   env('MAIL_TO_ADDRESS')),
    'mailBcc'         => env('KONTAKTFORMULAR_MAIL_BCC', ''),
    'subject'         => env('KONTAKTFORMULAR_SUBJECT', 'Anfrage von ' . env('APP_NAME')),
    'fileDownloadUrl' => env('APP_URL') . '/kontaktformular/file/',
    'dateien_loeschen_nach_tagen'  => env('KONTAKTFORMULAR_DATEIEN_LOESCHEN_NACH_TAGEN', 30),
    'anfragen_loeschen_nach_tagen' => env('KONTAKTFORMULAR_ANFRAGEN_LOESCHEN_NACH_TAGEN'),
    'disk'                         => env('KONTAKTFORMULAR_DISK', 'local'),
    'loeschen_einplanen'           => env('KONTAKTFORMULAR_LOESCHEN_EINPLANEN', true),
];

Schlägt der Mailversand an mailTo/mailBcc fehl (z. B. SMTP-Fehler, ungültige BCC-Adresse), hält das den Besucher nicht auf: Er sieht die Danke-Seite, der Fehler wird gemeldet (Log, mit id und Nummer der Anfrage) und die Anfrage bleibt gespeichert.

Kopie an das Lead-Postfach (mailBcc)

mailBcc (env KONTAKTFORMULAR_MAIL_BCC) schickt jede Anfrage zusätzlich als BCC-Kopie an ein Lead-Postfach, z. B. marketing@it-entwickler-hilbert.de für die Marketing-Auswertung. mailTo, Absender, Betreff und Inhalt bleiben unverändert.

  • Eine Adresse: KONTAKTFORMULAR_MAIL_BCC=marketing@it-entwickler-hilbert.de
  • Mehrere Adressen: durch Komma getrennt (a@x.de, b@y.de); Leerzeichen und leere Teile werden ignoriert. In der veröffentlichten Config ist auch ein Array möglich (['a@x.de', 'b@y.de']).
  • Leer oder nicht gesetzt: keine Kopie.

Herkunft der Anfrage (Google Ads / UTM)

Das Paket merkt sich, woher ein Besucher kam, und schickt diese Herkunft mit der Anfrage mit – auch wenn er vor dem Formular weitere Seiten aufruft.

Update eines bestehenden Projekts

  • php artisan migrate ist beim Update Pflicht (neue Spalte herkunft); ohne sie schlägt das Speichern fehl, sobald eine Herkunft erfasst wurde.
  • Wer die Mail-Views veröffentlicht hat (php artisan vendor:publish --tag=kontaktformular-views), muss sie neu veröffentlichen (--force) und eigene Anpassungen übernehmen – sonst fehlen die neuen Zeilen in der Mail.

Middleware

Die Middleware ITHilbert\Kontaktformular\Middleware\HerkunftErfassen wird vom ServiceProvider automatisch in die Middleware-Gruppe web eingehängt, und zwar direkt nach StartSession (in der Gruppe und in der Middleware-Priorität). So wird die Herkunft auch erfasst, wenn CSRF-Prüfung, auth oder throttle den Request danach abbrechen. Middlewares, die andere Pakete in web einhängen (z. B. userauth), bleiben erhalten. Im Projekt ist nichts einzutragen. Routen außerhalb von web (z. B. api, ohne Session) bleiben unberührt. Erfasst werden nur Seitenaufrufe: Requests mit einer anderen Methode als GET/HEAD (z. B. POST /anfrage), Requests, die JSON erwarten (expectsJson()), und der Token-Abruf kontaktformular.csrf_token setzen keine Herkunft; der nächste Seitenaufruf der Session wird als erster Kontakt erfasst.

Erfasst wird nur beim ersten Request einer Session (First Touch); spätere Requests ändern die gespeicherten Werte nicht. Ist der erste Request schon POST /anfrage, zählt dieser.

Erfasste Werte

Wert Quelle
gclid, gbraid, wbraid Query-Parameter (Google-Ads-Klick-IDs)
utm_source, utm_medium, utm_campaign, utm_term Query-Parameter
einstieg Pfad der ersten Seite ohne Query, URL-dekodiert, z. B. /excel-programmierer, /über-uns (statt /%C3%BCber-uns)
referrer nur der Host aus dem Referer-Header, z. B. google.com
  • Nur vorhandene Werte werden gespeichert. Leere Parameter und Arrays (?gclid[]=x) werden ignoriert, Werte über 255 Zeichen auf 255 gekürzt.
  • Ein Referrer mit dem eigenen Host (interne Navigation) oder ohne gültigen Host gilt als kein Referrer.
  • Steuerzeichen (U+0000–U+001F, U+007F, U+0080–U+009F) sowie die Zeilentrenner U+2028 und U+2029 werden aus allen erfassten Werten und aus site entfernt. So entstehen keine zusätzlichen Zeilen im Mail-Text.
  • Als Klick-ID zählt jeder Parametername, den PHP als gclid, gbraid oder wbraid liest, z. B. gcl%69d=… oder +gclid=….

Herkunftserfassung abschalten

Config kontaktformular.herkunft_erfassen (env KONTAKTFORMULAR_HERKUNFT_ERFASSEN), Standard an. Zum Abschalten (z. B. aus Datenschutzgründen) KONTAKTFORMULAR_HERKUNFT_ERFASSEN=false setzen; ebenso aus sind 0, off, no und ein leerer Wert. true, 1, on, yes oder nicht gesetzt = an.

  • Aus: Die Middleware HerkunftErfassen wird nicht eingehängt, es wird keine Herkunft in der Session gemerkt, und eine bereits vorhandene Session-Herkunft wird nicht übernommen. Die Spalte herkunft bleibt null.
  • Die Mail zeigt dann Herkunft: direkt, Einstieg: -, Referrer: - und Klick-ID: -.
  • Die Klick-IDs gclid, gbraid und wbraid werden aus site weiterhin entfernt.
  • Maßgeblich ist der Wert beim Start der App; eine Änderung per config([...]) zur Laufzeit wird für die Middleware nicht unterstützt.

Spalte herkunft

Die Migration des Pakets legt die nullable JSON-Spalte herkunft in kontaktformular an (php artisan migrate). Sie enthält die erfassten Werte, z. B. {"gclid":"…","utm_source":"google","einstieg":"/excel-programmierer","referrer":"google.com"}. Ohne gespeicherte Herkunft bleibt sie null; bestehende Anfragen werden nicht nachbefüllt.

Zeilen in der Mail

HTML- und Text-Teil der Mail enthalten direkt nach Site: vier Zeilen:

Herkunft: google_ads
Einstieg: /excel-programmierer
Referrer: google.com
Klick-ID: gclid
  • Herkunft: ist google_ads, wenn gclid, gbraid oder wbraid vorhanden ist oder utm_source=google und utm_medium=cpc (Groß-/Kleinschreibung egal); sonst organisch/sonstige, wenn ein Referrer vorhanden ist; sonst direkt.
  • Klick-ID: nennt nur den Namen des Parameters (bei mehreren den ersten in der Reihenfolge gclid, gbraid, wbraid), nie seinen Wert.
  • Fehlt ein Wert für Einstieg:, Referrer: oder Klick-ID:, steht dort -.
  • Die Klick-IDs gclid, gbraid und wbraid werden beim Speichern aus der übermittelten Seitenadresse (Formularwert site, Spalte url, Zeile Site:) entfernt. Alle übrigen Bestandteile bleiben unverändert, auch utm_*; war die Klick-ID der einzige Parameter, entfällt das ?.

Hinweis für die Datenschutzerklärung

  • Die Herkunft wird serverseitig in der Laravel-Session gemerkt (die es ohnehin gibt) und mit der Anfrage in der Datenbank gespeichert.
  • Es werden keine neuen Cookies gesetzt; das Paket schreibt nichts in localStorage oder sessionStorage und liefert dafür kein JavaScript aus.
  • Beim Session-Treiber cookie (SESSION_DRIVER=cookie) wird nichts erfasst, weil die Session-Daten dann im Browser lägen. Die Anfrage wird dann mit herkunft = null gespeichert, die Mail zeigt direkt bzw. -.
  • Gespeichert werden die Klick-ID (z. B. gclid), UTM-Parameter, die Einstiegsseite und der Host der verweisenden Seite. Die Datenschutzerklärung der Site sollte diese Verarbeitung im Abschnitt zum Kontaktformular nennen.

Löschfristen

Einstellung (.env) Standard Wirkung
KONTAKTFORMULAR_DATEIEN_LOESCHEN_NACH_TAGEN 30 Anhänge älter als die Frist werden gelöscht, die Anfrage bleibt (file_deleted_at wird gesetzt).
KONTAKTFORMULAR_ANFRAGEN_LOESCHEN_NACH_TAGEN nicht gesetzt Anfragen älter als die Frist werden samt Anhang gelöscht. Ohne Wert bleiben Anfragen dauerhaft erhalten.

Beide Werte müssen positive ganze Zahlen sein. Bei ungültigem Wert bricht der Command mit Fehlermeldung ab (Exit-Code 1) und löscht nichts.

Achtung: KONTAKTFORMULAR_DATEIEN_LOESCHEN_NACH_TAGEN= ohne Wert gilt als ungültig – dann stoppt auch die Löschung der Anhänge. Die Zeile entweder mit Zahl setzen oder ganz weglassen.

Anhänge liegen auf der Disk KONTAKTFORMULAR_DISK (Standard local) unter kontaktformular/<hash>.zip; bei Laravel 11+ ist das physisch storage/app/private/kontaktformular/. Die Disk braucht einen lokalen Pfad (das ZIP entsteht mit ZipArchive); Remote-Disks wie S3 werden nicht unterstützt. Vorhandene Anhänge werden nicht verschoben.

Command

php artisan kontaktformular:delete_old_files

Löscht zuerst die Anfragen nach der Anfragenfrist, danach die Anhänge nach der Dateifrist, und gibt die Zahl gelöschter Anhänge und Anfragen aus. Eine bereits fehlende Anhang-Datei ist kein Fehler.

Scheduler: Das Paket plant den Command täglich selbst ein. Mit KONTAKTFORMULAR_LOESCHEN_EINPLANEN=false (auch 0, off) entfällt die Einplanung. Plant ein Projekt ihn zusätzlich selbst ein, ist die doppelte Ausführung harmlos.

Routen

Route::post('anfrage', [KontaktformularController::class, 'anfrage'])->name('anfrage');
Route::get('kontaktformular/csrf-token', [KontaktformularController::class, 'csrf_token'])->name('kontaktformular.csrf_token');
Route::get('danke-formular', [KontaktformularController::class, 'danke_formular'])->name('danke_formular');
Route::get('danke-bot-formular', [KontaktformularController::class, 'danke_bot_formular'])->name('danke_bot_formular');
Route::get('kontaktformular/file/{hash}/{name}/{id}', [KontaktformularController::class, 'file_download'])->name('kontaktformular.file');

CSRF-Token auffrischen

GET /kontaktformular/csrf-token (Name kontaktformular.csrf_token) liefert {"token": "..."} mit Cache-Control: no-store. Die Vue-Komponente ruft sie beim Absenden auf und setzt das Token neu, damit eine abgelaufene Sitzung (z. B. Seite per „Zurück“ aus dem Cache) nicht zu 419 führt. Kommt die Seite per „Zurück“ aus dem Browser-Cache (pageshow mit persisted) und blieb der JS-Zustand erhalten, wird das Absenden wieder freigegeben. Schlägt der Abruf fehl, wird mit dem vorhandenen Token abgesendet. Die Route gilt nur für die gleiche Herkunft (kein CORS); die CSRF-Prüfung von POST /anfrage bleibt unverändert. Die URL kommt als Prop token-route (Vorgabe /kontaktformular/csrf-token).

Update: Hosts, die die Komponente (src/Resources/Vue/kontaktformular.vue) in ihr Bundle bauen, müssen nach dem Update neu bauen (npm run production).

Views

Im Hauptprojekt müssen layouts.master und layouts.masterBot existieren.

Namespace

ITHilbert\Kontaktformular