├Ś
Startseite RSS-Feed Info
PHP mail() Kontaktformular f├╝r den statischen Webseiten Generator Hugo

Hugo - Kontaktformular mit PHP

Hugo - Kontaktformular mit PHP

Hugo Website Anleitung f├╝r ein multilinguales PHP mail() Kontaktformular. Mit Honeypot Input Feldern, Eingabe├╝berpr├╝fung und Fehlerausgabe. Viel Aufwand f├╝r ein einfaches, aber sicheres Kontaktformular.

Es kommt wie so oft auf den Einsatzzweck und das Umfeld der Mail-Komponente an. In meinem Fall ben├Âtige ich nur ein einfaches Kontaktformular mit Textfeld und Benutzer E-Mail Adressfeld. Mein Webspace Provider stellt automatisch eine Mail-Server Umgebung zur Verf├╝gung. Aus diesem Grund ben├Âtige ich die bekannte PHPMailer Library nicht. Jede zus├Ątzliche Software Komponente muss gepflegt und upgedated werden.

Das Einbinden von fremdem Sourcecode erzeugt Arbeit (Updates) und ist Sicherheitsrelevant. Aus diesem Grund bin ich f├╝r Blogs von WordPress zu Hugo gewechselt. Ich m├Âchte programmieren und nicht dauernd irgendwelchen Sicherheitsupdates und gut gemeinten Plug-In “Verbesserungen” hinterherlaufen.

Den eigenen Provider testen ob er das mail() Umfeld zur Verf├╝gung stellt

Bevor Sie sich viel Arbeit machen, sollten Sie testen ob Ihr Provider die PHP mail() Funktion auf Ihrem Webspace oder Server erlaubt und konfiguriert hat. In meinem Beitrag - PHP in Hugo benutzen - habe ich erkl├Ąrt wie PHP in Hugo integriert werden kann. Deshalb setze ich dieses Wissen voraus. Kopieren Sie den folgenden Test Sourcecode in Ihre single.php:

{{ define "main" }}
<section class="container">
  <h1>{{ .Title }}</h1>
  {{ .Content }}

  <?php  
  $from = 'hello@example.com';
  $to = 'my@example.com';
  $subject = 'Example Mail';
  $message = 'My first PHP Mail.';
  $header  = 'MIME-Version: 1.0\r\n';
  $header .= 'Content-type: text/html; charset=utf-8\r\n';
  $header .= 'From: ' . $from;  
  
  if (!mail($to, $subject, $message, $header))
  {
    echo "Error.";
  }
  else {
    echo "Message sent.";
  }
  ?>

</section>
{{end}}

Die E-Mailadressen in $from und $to ersetzen Sie bitte mit Ihren eigenen Adressen. Danach erstellen Sie die Markdown Dateien. Wenn Sie keine multilinguale Website haben reicht die index.md:

content
|__ kontakt
    |__ index.md
    |__ index.en.md

Die Datei index.en.md:

---
title: "Contact"
date: 2021-05-12T18:55:35+02:00
lastupdate: ""
draft: false
slug: contact
translationKey: kontakt
description: "Contact form"
author: "Frank Kunert"
outputs: PHP
---

Have you found an error, would you like to point out something or simply say something nice? Then write me an email. Either via the contact form or directly to kontakt [ at ] tekki-tipps [.] de.  

Danach muss das public Verzeichnis von Hugo neu generiert werden. Kopieren Sie den Inhalt von public per sFTP auf den eigenen Webspace. Wenn Sie jetzt die Kontakt Webseite aufrufen, sollte der Hinweis “Message sent” angezeigt und die E-Mail in Ihrem Mail-Postfach eingegangen sein. Funktioniert dies nicht, sprechen Sie bitte mit Ihrem Provider.

Sourcecode f├╝r das Hugo PHP Kontaktformular

Die index.md und index.en.md sind erstellt. In diesem Abschnitt zeige ich den Sourcecode des Templates single.php, das Partial File contactForm.html und die PHP Datei contact.php mit Kommentare an.

Hugo Template single.php

{{ define "main" }}
<section class="container">
  <h1>{{ .Title }}</h1>
  {{ .Content }}
  
  {{ readFile "static/php/contact.php" | safeHTML }}

  {{ partial "contactForm" . }}

</section>
{{end}}

Unterhalb des Titels wird der Content von index.md ausgegeben. Mit der GO Funktion readFile wird die contact.php eingebunden und wartet auf den Klick des Absenden-Button. Danach wird das Partial contactForm.html mit dem eigentlichen Kontaktformular integriert.

Hugo Partial contactForm.html

<div class="form-wrapper">
  <form class="needs-validation" novalidate method="post">

    <div class="msg-text">
      <label for="messageText" class="form-label">{{ T "IhreNachricht" }}</label>
      <span class="required-field">*</span>
      <textarea class="form-control" id="messageText" rows="3" name="message" required></textarea>
    </div>

    <div class="name">
      <label for="name" class="form-label">Name</label>
      <span class="required-field">*</span>
      <input type="text" class="form-control" id="name" name="name" placeholder="Your Name" required>
    </div>

    <div class="website">
      <label for="website" class="form-label">Website</label>
      <span class="required-field">*</span>
      <input type="text" class="form-control" id="website" name="website" placeholder="Your Website URL" required>
    </div>
    
    <div class="email-adr">
      <label for="email" class="form-label">{{ T "EmailAdresse" }}</label>
      <span class="required-field">*</span>
      <input type="email" class="form-control" id="email" name="email" placeholder="name@example.com" required>
    </div>

    <div class="submit-btn">
      <button class="btn btn-success" type="submit">{{ T "EmailAbsenden" }}</button>
    </div>

  </form>
</div>

Auf den ersten Blick ist nichts ungew├Âhnlich an diesem HTML Form. Was auff├Ąllt ist zum Beispiel {{ T "IhreNachricht" }}. Dies ist eine Hugo spezifische Funktion f├╝r mehrsprachige Websites. T steht f├╝r Translate. Dann kommt ein Platzhalter, der Platzhalter ist in verschiedene Sprachen ├╝bersetzt. Es w├╝rde hier zu weit gehen dies alles zu erkl├Ąren. F├╝r multilinguale Webseiten werde ich zus├Ątzliche Blog Artikel schreiben.

<div class="form-wrapper">
  <form class="needs-validation" novalidate method="post">

HTML 5 validiert Input Felder und gibt einen entsprechenden Hinweis aus. Zum Beispiel wenn ein required Feld bei Klick auf den Absenden-Button nicht ausgef├╝llt wurde. Die Validierung ist sehr rudiment├Ąr und entspricht nicht meinen Anspr├╝chen. Au├čerdem verhindert es meine required Honeypot Felder. Um die Browser Validierung auszuschalten, muss novalidate angegeben werden.

Die einzelnen Input-Felder haben unter anderem den Parameter name. Das erste Eingabefeld, die Textarea, hat zum Beispiel den name-Parameterinhalt message. Bei Klick auf den Absenden-Button, wird der Feldinhalt auf dem Server gespeichert und ist im PHP Programm ├╝ber $_POST[‘message’] lesbar.

Die Felder name und website sind Honeypot-Felder die ├╝ber CSS mit display: none; ausgeblendet werden. Das required Attribut der Felder soll es f├╝r Spam-Robots schmackhafter machen diese auch auszuf├╝llen. Die novailidate Anweisung verhindert das automatische Blockieren durch den Browser. Wenn Sie einen Spam-Robot simulieren m├Âchten, schalten Sie ├╝ber den Browser das display: none; bei diesen Feldern aus. Dadurch werden die Felder im Formular wieder sichtbar.

Da ich das Bootstrap CSS-Framework benutze werden die Input-Felder dadurch gestylt. An der Stelle m├╝ssen Sie den CSS-Code ohne das CSS-Framework selber erweitern.

PHP Datei contact.php

An dieser Stelle zeige ich den kompletten Sourcecode von contact.php an. Weiter unten erkl├Ąre ich den Code dann in Abschnitten.

<?php

$from = 'hello@example.com';
$to = 'my@example.com';
$header  = "MIME-Version: 1.0\r\n";
$header .= "Content-type: text/html; charset=utf-8\r\n";
$header .= "From: $from\r\n";
$header .= "Reply-To: $to\r\n";
$header .= "X-Mailer: tekki-tipps.de\r\n";
$msg = '';
$email = '';
$text = '';
$langDE = TRUE;
if (substr($_SERVER['REQUEST_URI'],1,2) == "en" ) {
  $langDE = FALSE;
}

// this should catch a lot of spam bots
$honeypot = trim($_POST["name"]) . trim($_POST["website"]);

if(empty($honeypot)) {
  if ($_SERVER["REQUEST_METHOD"] == "POST") { 
    // Check user input
    $fielderror = 0;
    if (strlen($_POST["message"]) < 1) {
      $fielderror += 1;
    }
    else {
      $text= substr(nl2br(strip_tags($_POST['message'])), 0, 16384);
    }
    if (strlen($_POST["email"]) < 1) {
      $fielderror += 2;
    }
    elseif (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
      $fielderror += 4;
    }
    else {
      $email = $_POST["email"];
    }
    // User input error
    if ( $fielderror > 0 ) {
      $msg = "<div class='fielderror'>";
      switch ($fielderror) {
        case 1:
          if ($langDE) {
            $msg .= "Bitte das Nachrichtenfeld ausf├╝llen.";
          }
          else {
            $msg .= "Please fill in the message field.";
          }
          break;
        case 2:
          if ($langDE) {
            $msg .= "Bitte die E-Mailadresse ausf├╝llen.";
          }
          else {
            $msg .= "Please fill in the email address.";
          }
          break;
        case 3:
          if ($langDE) {
            $msg .= "Bitte das Nachrichtenfeld ausf├╝llen.<br>" . "Bitte die E-Mailadresse ausf├╝llen.";
          }
          else {
            $msg .= "Please fill in the message field.<br>" . "Please fill in the email address.";
          }
          break;
        case 4:
          if ($langDE) {
            $msg .= "Die E-Mailadresse ist ung├╝ltig.";
          }
          else {
            $msg .= "The email address is invalid.";
          }
          break;
        case 5:
          if ($langDE) {
            $msg .= "Bitte das Nachrichtenfeld ausf├╝llen.<br>" . "Die E-Mailadresse ist ung├╝ltig";
          }
          else {
            $msg .= "Please fill in the message field." . "The email address is invalid.";
          }
          break;
      }
      $msg .= "</div>";
      echo $msg;
    }
    // There was no user input error
    else {
      if ($langDE) {
        $subject =  'tekki-tipps.de - Kontaktformular';
        $message = 	"<h2>tekki-tipps.de - Kontaktformular</h2>" .
										"<p><strong>Von: </strong>" .	$email . "</p><hr>" . 
										"<p>" . $text . "</p>";
      }
      else {
        $subject =  'tekki-tipps.de - Contact Form';
        $message = 	"<h2>tekki-tipps.de - Contact Form</h2>" .
										"<p><strong>From: </strong>" .	$email . "</p><hr>" . 
										"<p>" . $text . "</p>";
      }
      if (!mail($to, $subject, $message, $header))
      {
        if ($langDE) {
          $msg = '<div class="mail-error">Entschuldigung, etwas ist schief gelaufen. Bitte versuchen Sie es sp├Ąter erneut.</div>';
        }
        else {
          $msg = '<div class="mail-error">Sorry, something went wrong. Please try again later.</div>';
        }
      } 
      else {
        if ($langDE) {
          $msg = '<div class="mail-success">Nachricht gesendet! Danke f├╝r die Kontaktaufnahme.</div>';
        }
        else {
          $msg = '<div class="mail-success">Message sent! Thanks for getting in contact.</div>';
        }
      }
      echo $msg;
    }
  }
}
else {
  $subject = 'tekki-tipps.de - BAD ROBOT - Contact Form';
  $message =  "<h2>tekki-tipps.de - BAD ROBOT - Contact Form</h2>";
  $text = substr(nl2br(strip_tags($_POST['message'])), 0, 16384);
  $message .= "<p>BAD ROBOT!</p>"
            . "<p>HTTP_USER_AGENT: " . $_SERVER["HTTP_USER_AGENT"] . "<br>"
            . "REMOTE_ADDR: " . $_SERVER["REMOTE_ADDR"] . "<br>"
            . "REMOTE_HOST: " . $_SERVER["REMOTE_HOST"] . "</p><hr>"
            . "<p>" . $text . "</p>";
  mail($to, $subject, $message, $header);
}
?>

contact.php - Allgemein

├ťbergeben Sie den Variablen $from und $to Ihre E-Mailadressen. In der $header Variablen wird unter anderem definiert, dass eine HTML-E-Mail verschickt wird.

Ich habe f├╝r meine multilinguale Website die Hugo Variante mit der L├Ąnderabk├╝rzung im URL-Pfad gew├Ąhlt. Die englische Version der Kontaktseite hat folgende URI: /en/contact/index.php. Wenn das en in der URI vorhanden ist, setze ich die Variable $langDE auf FALSE. Bei der Initialisierung ist die Variable auf TRUE eingestellt. Dadurch kann ich die Ausgabe der Statusmeldungen in der jeweiligen Sprache anzeigen.

contact.php - Honeypot

Es interessiert mich wie oft ein abgefangener Spam-Robot versucht mein Kontaktformular f├╝r seinen M├╝ll zu missbrauchen. Aus diesem Grund schicke ich mir selber eine E-Mail mit dem M├╝lltext.

// this should catch a lot of spam bots
$honeypot = trim($_POST["name"]) . trim($_POST["website"]);

if(empty($honeypot)) {
  ..
  ..
}
else {
  $subject = 'tekki-tipps.de - BAD ROBOT - Contact Form';
  $message =  "<h2>tekki-tipps.de - BAD ROBOT - Contact Form</h2>";
  $text = substr(nl2br(strip_tags($_POST['message'])), 0, 16384);
  $message .= "<p>BAD ROBOT!</p>" .
              "<p>HTTP_USER_AGENT: " . $_SERVER["HTTP_USER_AGENT"] . "<br>" .
              "REMOTE_ADDR: " . $_SERVER["REMOTE_ADDR"] . "<br>" .
              "REMOTE_HOST: " . $_SERVER["REMOTE_HOST"] . "</p><hr>" .
              "<p>" . $text . "</p>";
  mail($to, $subject, $message, $header);
}

Sobald etwas in den versteckten Input-Feldern Name und Website erfasst wurde, wird die normale E-Mail Verarbeitung umgangen und eine spezielle E-Mail verschickt. Wenn Sie diese Kontrolle nicht m├Âchten - einfach den else Zweig l├Âschen.

Example Spam Mail
Eine BAD ROBOT E-Mail sieht dann beim Empfang so aus.

Die REMOTE_ADDR ist in dem Beispiel ::1 da die Beispiel E-Mail ├╝ber meinen lokalen Webserver verarbeitet wurde. Siehe Beschreibung in meinem Blog Post - MAMP Webserver lokal mit Hugo benutzen . Ansonsten steht dort die IP-Adresse des Versenders. Ob das die richtige IP-Adresse ist oder ob diese manipuliert wurde ist unklar. Die REMOTE_HOST Angabe wird in den meisten F├Ąllen leer sein. Spamer sind mit ihren eigenen Daten sehr zur├╝ckhaltend.

Am 19. Jul. 2021 habe ich die Website ver├Âffentlicht und Google dar├╝ber durch eine sitemap.xml informiert. Am 20. Jul. 2021 war die Site noch nicht komplett im Index. Am 21. Jul. 2021 habe ich als erstes eine Spam-Mail erhalten. Zu dem Zeitpunkt war bisher noch kein externer Besucher auf der Website. Der Honeypot ist wirklich notwendig.

<Update - 22. Dezember 2022>

In der Zeit vom 21. Juli 2021 bis zum 22. Dezember 2022 habe ich 421 Spam-Mails ├╝ber den “else”-Zweig der “if”-Abfrage “empty Honeypot” erhalten. Von einer Verl├Ąngerung eines K├Ârperteils, ├╝ber Links zu Porno-Sites, Mails die meine Website toll finden - aber eine bessere Auffindbarkeit anbieten - Eric gebe es endlich auf oder einfach nur Links ohne zus├Ątzlichen Text.

Der Versand solcher durch Bots generierten bzw. ausgel├Âsten Spam-Mails erzeugt Arbeit und Kosten. Also muss es sich ja trotzdem lohnen. Mit anderen Worten, es gibt genug Menschen die auf solchen M├╝ll reagieren.

Ich schalte den “else”-Zweig jetzt ab. Das Experiment war interessant und aufschlussreich. Ohne solch einen Honeypot w├╝rde ich kein Kontaktformular mehr programmieren.

</Update>

contact.php - Benutzer Eingabefehler

Die Fehleingaben k├Ânnen in unterschiedlichen Kombinationen auftreten. Aus diesem Grund addiere ich jeweils eine andere Zahl hinzu. Es gibt maximal 5 unterschiedliche Fehlerkombinationen.

  if ($_SERVER["REQUEST_METHOD"] == "POST") { 
    // Check user input
    $fielderror = 0;
    if (strlen($_POST["message"]) < 1) {
      $fielderror += 1;
    }
    else {
      $text= substr(nl2br(strip_tags($_POST['message'])), 0, 16384);
    }
    if (strlen($_POST["email"]) < 1) {
      $fielderror += 2;
    }
    elseif (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
      $fielderror += 4;
    }
    else {
      $email = $_POST["email"];
    }
    // User input error
    if ( $fielderror > 0 ) {
      $msg = "<div class='fielderror'>";
      switch ($fielderror) {
        case 1:
          if ($langDE) {
            $msg .= "Bitte das Nachrichtenfeld ausf├╝llen.";
          }
          else {
            $msg .= "Please fill in the message field.";
          }
          break;
        case 2:
          if ($langDE) {
            $msg .= "Bitte die E-Mailadresse ausf├╝llen.";
          }
          else {
            $msg .= "Please fill in the email address.";
          }
          break;
        case 3:
          if ($langDE) {
            $msg .= "Bitte das Nachrichtenfeld ausf├╝llen.<br>" .
                    "Bitte die E-Mailadresse ausf├╝llen.";
          }
          else {
            $msg .= "Please fill in the message field.<br>" .
                    "Please fill in the email address.";
          }
          break;
        case 4:
          if ($langDE) {
            $msg .= "Die E-Mailadresse ist ung├╝ltig.";
          }
          else {
            $msg .= "The email address is invalid.";
          }
          break;
        case 5:
          if ($langDE) {
            $msg .= "Bitte das Nachrichtenfeld ausf├╝llen.<br>" .
                    "Die E-Mailadresse ist ung├╝ltig";
          }
          else {
            $msg .= "Please fill in the message field.<br>" .
                    "The email address is invalid.";
          }
          break;
      }
      $msg .= "</div>";
      echo $msg;
    }

Durch if($_SERVER[“REQUEST_METHOD”] == “POST”) wird auf den Klick des Absenden-Button gewartet. Der Inhalt des TextArea-Feldes (message) wird gepr├╝ft, ob ├╝berhaupt etwas eingegeben wurde. Wenn ja, wird die L├Ąnge auf maximal 16384 Bytes begrenzt. Dadurch wird ein bewusstes Sprengen der Speicherkapazit├Ąt unterbunden. Es werden HTML-Tags entfernt und Zeilenwechsel in ein HTML <br> Tag konvertiert.

Die eingegebene E-Mailadresse benutze ich in dem PHP-Code nur im Text. Mit FILTER_VALIDATE_EMAIL wird ├╝berpr├╝ft ob wirklich eine E-Mailadresse eingegeben wurde.

Wenn ein Fehler festgestellt wurde, wird in einer case Abfrage die entsprechende Fehlermeldung in der jeweiligen Sprache ausgegeben.

contact.php - der eigentliche Mail-Versand

Hier wird jetzt nur in unterschiedlichen Sprachen der E-Mailtext zusammengestellt.

// There was no user input error
    else {
      if ($langDE) {
        $subject =  'tekki-tipps.de - Kontaktformular';
        $message = 	"<h2>tekki-tipps.de - Kontaktformular</h2>" .
										"<p><strong>Von: </strong>" .	$email . "</p><hr>" .  
										"<p>" . $text . "</p>";
      }
      else {
        $subject =  'tekki-tipps.de - Contact Form';
        $message = 	"<h2>tekki-tipps.de - Contact Form</h2>" .
										"<p><strong>From: </strong>" .	$email . "</p><hr>" .  
										"<p>" . $text . "</p>";
      }
      if (!mail($to, $subject, $message, $header))
      {
        if ($langDE) {
          $msg = '<div class="mail-error">Entschuldigung, etwas ist schief gelaufen. Bitte versuchen Sie es sp├Ąter erneut.</div>';
        }
        else {
          $msg = '<div class="mail-error">Sorry, something went wrong. Please try again later.</div>';
        }
      } 
      else {
        if ($langDE) {
          $msg = '<div class="mail-success">Nachricht gesendet! Danke f├╝r die Kontaktaufnahme.</div>';
        }
        else {
          $msg = '<div class="mail-success">Message sent! Thanks for getting in contact.</div>';
        }
      }
      echo $msg;
    }

Nach dem Versand wird eine Statusmeldung ausgegeben.

SCSS f├╝r das Kontaktformular

Wie oben schon beschrieben benutze ich das Bootstrap Framework und muss deshalb nicht viele Anpassungen f├╝r das Design erstellen.

.fielderror,
.mail-error {
  color: $wk-color-4;
  font-size: $content-fontsize-xl;
  font-weight: 600;
}
.mail-success {
  color: var(--wk-accent-color-3);
  font-size: $content-fontsize-xl;
  font-weight: 600;
}
.form-wrapper {
  margin: 1.0rem 0;
  padding: 2.0rem;
  border: 1px solid var(--wk-accent-border-color);

  .required-field {
    color: $wk-color-4;
    font-weight: 600;
  }
  .name,
  .website {
    display: none;
  }
  .email-adr {
    margin: 1.5rem 0 2.0rem;
  }
  .submit-btn {
    display: flex;
    justify-content: flex-end;
    width: 100%;
  }
}

Warum brauche ich ein PHP Kontaktformular f├╝r mein Hugo Projekt?

Ein einfacher mailTo-Link im Impressum w├╝rde ja eigentlich ausreichen. Aber - in Deutschland gibt es sehr restriktive, rechtliche Vorgaben f├╝r Domain-Inhaber. Jeder muss ein Impressum zur Verf├╝gung stellen. Neben der Adresse des Domain-Inhabers muss eine E-Mailadresse und eine Telefonnummer des Inhabers angegeben werden. Da ich meine Telefonnummer nicht im Internet ver├Âffentlichen m├Âchte, muss ich stattdessen ein Kontaktformular zur Verf├╝gung stellen. Der Gesetzgeber geht davon aus, dass nicht jeder einen Mail-Provider hat und deshalb das Kontaktformular als Telefonnummer Ersatz zur Verf├╝gung gestellt werden muss.

Hugo ist ein statischer Website Generator. Die Dynamik f├╝r ein Kontaktformular erhalte ich durch PHP. Es gibt externe Dienste die Kontaktformulare zur Integration anbieten. Ich m├Âchte aber nicht auf externe Dienstleister, die Kosten spielen auch eine Rolle, angewiesen sein. Die rechtlichen Vorgaben der EU, f├╝r externe Dienstleistungen au├čerhalb der EU, sind sehr umfangreich bis unm├Âglich zu erf├╝llen.

Fazit

Ich programmiere normalerweise nicht in PHP. Sollte Ihnen ein sicherheitsrelevanter Fehler auffallen, schreiben Sie bitte einen Kommentar oder benutzen Sie das Kontaktformular um mich zu informieren. Durch die mail()-Funktion von PHP kann ich auf externe Libraries verzichten. Dadurch habe ich weniger Update Aufwand und kann mich um Dinge k├╝mmern die mir wichtig sind.

Das k├Ânnte Sie auch interessieren

Update:  |
13 Minuten Lesezeit
0
Dieser Beitrag wurde mit der Hugo-Version 0.115.2 erstellt.

Kommentare werden bei deutscher Spracheinstellung nicht in der englischen Variante der Webseite angezeigt und umgekehrt.

© 2023 - Frank Kunert  -  Ich ├╝ber mich