Skip to main content

📧 FormIt (Kontaktformular)

FormIt nutze ich als Kontaktformular, es kann aber deutlich mehr und lässt sich zum Beispiel auch als Gästebuch (nutzt das noch jemand?) oder Kommentarbereich einsetzen.

formit V 5.1.1-pl

AjaxUpload 2.0.4-pl

FormIt.jpg

Chunk Beispiel: modxKontakt
(E-Mail-Adressen werden über eine eigene Verschlüsselung geschützt)

<!-- FormIt-Aufruf mit AjaxUpload, SaveForm, fiEmailEncrypt/fiDecryptMailAn -->

<!-- Felder, die von FormItSaveForm gespeichert werden -->
<!-- Anzeigenamen für FormItSaveForm müssen ALLE Felder aus &formFields abdecken -->

[[!FormIt?
  &preHooks=`Formit2AjaxUpload`
  &hooks=`spam,math,userAgent,fiDecryptMailAn,FormItSaveForm,AjaxUpload2Formit,AjaxUploadAttachments,email,AjaxUploadRemove,redirect`
  &formName=`contactForm`

  &formFields=`typ,mailAn,firstname,name,ort,telefon,email,text,row1,row2,row3,attachweb`

  &fieldNames=`email==E-Mail,
  firstname==Vorname,
  name==Name,
  typ==Darum geht's,
  mailAn==Empfänger,
  ort==Ort,
  telefon==Telefon,
  text==Nachricht,
  row1==REMOTE_ADDR,
  row2==REMOTE_HOST,
  row3==HTTP_USER_AGENT,
  attachweb==Anhänge`

  &ajaxuploadTarget=`uploads/`
  &ajaxuploadUid=`attachweb`

  &emailTpl=`contactEmailTpl`
  &emailTo=`[[+mailAn]]`
  &emailSubject=`Gesendet vom Kontaktformular [[++site_name]]`
  &redirectTo=`42`

  &mathMinRange=`1`
  &mathMaxRange=`12`

  &validate=`math:required,
  firstname:required:maxLength=50,
  name:required:maxLength=50,
  email:email:required:maxLength=60,
  text:required:stripTags:maxLength=2000,
  typ:required,
  mailAn:required,
  datenschutz:required,
  name2:blank`
]]

<!-- Hinweis:
Damit FormItSaveForm keine "Undefined array key" Warnings erzeugt, 
müssen alle Einträge in &formFields auch in &fieldNames vorhanden sein.
Nur wenn jedes Feld eindeutig gemappt ist, greift FormIt nicht auf 
nicht existierende Keys in $fieldLabels zu. -->

[[!+fi.error.error_message:notempty=`<p class="text-danger">[[!+fi.error.error_message]]</p>`]]

<form action="[[~[[*id]]]]" method="post" class="mb-3" enctype="multipart/form-data">

  <input type="hidden" name="name2" value="">
  <input name="resource_id" type="hidden" value="[[+fi.id]]">

  <div class="mb-3">
    <p class="mb-1">
      Hier könnt ihr Mitteilungen per Formular senden.
    </p>
  </div>

  <hr>

  <div class="row g-3 mb-3">

    <div class="col-md-6">
      <label for="typ" class="form-label">Darum geht's</label>
      <select required name="typ" id="typ" class="form-select">
        <option value="">Auswahl...</option>
        <option value="ThemaA" [[!+fi.typ:FormItIsSelected=`ThemaA`]]>Thema A</option>
        <option value="ThemaB" [[!+fi.typ:FormItIsSelected=`ThemaB`]]>Thema B</option>
        <option value="ThemaC" [[!+fi.typ:FormItIsSelected=`ThemaC`]]>Thema C</option>
        <option value="ThemaD" [[!+fi.typ:FormItIsSelected=`ThemaD`]]>Thema D</option>
        <option value="Sonstiges" [[!+fi.typ:FormItIsSelected=`Sonstiges`]]>Sonstiges</option>
      </select>
      [[+fi.error.typ:notempty=`<div class="text-danger small">[[+fi.error.typ]]</div>`]]
    </div>

    <div class="col-md-6">
      <label for="mailAn" class="form-label">E-Mail an</label>
      <select required name="mailAn" id="mailAn" class="form-select">
        <option value="">Auswahl...</option>

        <!-- Platzhalter-Empfänger für Anleitung -->
        <option value="[[fiEmailEncrypt? &input=`empfaenger1@example.com`]]"
                [[!+fi.mailAn:FormItIsSelected=`empfaenger1@example.com`]]>
          Ansprechpartner 1
        </option>

        <option value="[[fiEmailEncrypt? &input=`empfaenger2@example.com`]]"
                [[!+fi.mailAn:FormItIsSelected=`empfaenger2@example.com`]]>
          Ansprechpartner 2
        </option>

        <option value="[[fiEmailEncrypt? &input=`empfaenger3@example.com`]]"
                [[!+fi.mailAn:FormItIsSelected=`empfaenger3@example.com`]]>
          Ansprechpartner 3
        </option>

        <option value="[[fiEmailEncrypt? &input=`admin@example.com`]]"
                [[!+fi.mailAn:FormItIsSelected=`admin@example.com`]]>
          Website-Administrator
        </option>

      </select>
      [[+fi.error.mailAn:notempty=`<div class="text-danger small">[[+fi.error.mailAn]]</div>`]]
    </div>

  </div>

  <div class="row g-3 mb-3">

    <div class="col-md-6">
      <input required type="text" class="form-control" placeholder="Vorname"
             name="firstname" id="firstname"
             value="[[!+fi.firstname:htmlent]]">
      [[+fi.error.firstname:notempty=`<div class="text-danger small">[[+fi.error.firstname]]</div>`]]
    </div>

    <div class="col-md-6">
      <input required type="text" class="form-control" placeholder="Name"
             name="name" id="name"
             value="[[!+fi.name:htmlent]]">
      [[+fi.error.name:notempty=`<div class="text-danger small">[[+fi.error.name]]</div>`]]
    </div>

    <div class="col-md-6">
      <input type="text" class="form-control" placeholder="Ort"
             name="ort" id="ort"
             value="[[!+fi.ort:htmlent]]">
      [[+fi.error.ort:notempty=`<div class="text-danger small">[[+fi.error.ort]]</div>`]]
    </div>

    <div class="col-md-6">
      <input type="tel" class="form-control" placeholder="Telefon"
             name="telefon" id="telefon"
             value="[[!+fi.telefon:htmlent]]">
      [[+fi.error.telefon:notempty=`<div class="text-danger small">[[+fi.error.telefon]]</div>`]]
    </div>

    <div class="col-md-6">
      <input required type="email" class="form-control" placeholder="E-Mail"
             name="email" id="email"
             value="[[!+fi.email:htmlent]]">
      [[+fi.error.email:notempty=`<div class="text-danger small">[[+fi.error.email]]</div>`]]
    </div>

  </div>

  <div class="mb-3">
    <div class="d-flex align-items-start mb-2">
      <img src="[[++assets_url]]icons/32x32/text-x-generic.png" alt="Text" width="32" height="32" class="me-2">
      <label for="text" class="form-label mb-0">Deine Nachricht</label>
    </div>
    <textarea class="form-control" placeholder="Deine Nachricht"
              style="height:200px;" required
              name="text" id="text">[[!+fi.text:htmlent]]</textarea>
    [[+fi.error.text:notempty=`<div class="text-danger small">[[+fi.error.text]]</div>`]]
  </div>

  <div class="mb-3 hidden-print">
    [[!AjaxUpload?
      &uid=`attachweb`
      &acceptedFileTypes=`image/jpeg,image/gif,image/png,image/webp,application/pdf,text/plain,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document`
      &maxFileSize=`8MB`
      &maxFiles=`5`
      &showCredits=`0`
      &addCss=`1`
      &addJscript=`1`
    ]]
    <small class="form-text text-muted">(max. 5 Dateien und 8 MB!)</small>
  </div>

  <div class="mb-3">
    <fieldset class="mb-2">
      <label class="form-label">
        Was ist [[!fiZahl2Text? &input=`[[!+fi.op1]]`]]
        [[!+fi.operator:is=`-`:then=`weniger`:else=`und`]]
        [[!fiZahl2Text? &input=`[[!+fi.op2]]`]]?
      </label>

      [[+fi.error.math:notempty=`<div class="text-danger small">[[+fi.error.math]]</div>`]]

      <input required type="text" class="form-control w-auto"
             placeholder="Scheetz uas fir Mest"
             name="math" value="[[!+fi.math]]">

      <input type="hidden" name="operator" value="[[!+fi.operator]]">
    </fieldset>
  </div>

  <div class="mb-3">
    <div class="form-check">
      <input type="hidden" name="datenschutz[]" value="0">
      <input required type="checkbox" class="form-check-input"
             name="datenschutz[]" id="datenschutz"
             value="1" [[!+fi.datenschutz:FormItIsChecked=`1`]]>
      <label for="datenschutz" class="form-check-label">
        Ich habe die <a href="[[~123]]" target="_blank">Datenschutzerklärung</a> gelesen und akzeptiert
      </label>
    </div>
    [[+fi.error.datenschutz:notempty=`<div class="text-danger small">[[+fi.error.datenschutz]]</div>`]]
  </div>

  <div class="mb-3 hidden-print">
    <button type="submit" class="btn btn-primary">
      Abschicken
    </button>
  </div>

</form>

<hr>

Hinweis:
Ab AjaxUpload V2 musst du darauf achten, dass die uid ausschließlich in Kleinbuchstaben gesetzt wird, damit der Upload korrekt funktioniert. Danke an halftrainedharry.


Snippet: fiZahl2Text

<?php
# V22.11.001
# fiZahl2Text
#
# Mit dem Math Hook können Formular mit einer mathematischen Frage versehen werden, um Spam zu verhindern.
# Dieses Snippet wandelt Zahlen in einen Text um, was Spam-Bots das Berechnen erschweren sollte.
#
# Z.B.: <label>Was ist [[!fiZahl2Text? &input=`[[!+fi.op1]]`]] [[!+fi.operator:is=`-`:then=`weniger`:else=`und`]] [[!fiZahl2Text? &input=`[[!+fi.op2]]`]]?</label>
# Math Range zwischen 0 und 12, ansonsten das Snippet anpassen!

$intZahl=(int)$input;

switch ($intZahl) {
	case 0:
		$strZahl = 'null';
		break;
	case 1:
		$strZahl = 'eins';
		break;
	case 2:
		$strZahl = 'zwei';
		break;
	case 3:
		$strZahl = 'drei';
		break;
	case 4:
		$strZahl = 'vier';
		break;
	case 5:
		$strZahl = 'fünf';
		break;
	case 6:
		$strZahl = 'sechs';
		break;
	case 7:
		$strZahl = 'sieben';
		break;
	case 8:
		$strZahl = 'acht';
		break;
	case 9:
		$strZahl = 'neun';
		break;
	case 10:
		$strZahl = 'zehn';
		break;
	case 11:
		$strZahl = 'elf';
		break;
	case 12:
		$strZahl = 'zwölf';
		break;
	default:
		$strZahl = 'unknown';
		break;
}

return $strZahl;

Snippet: userAgent

<?php
# V 22.12.001

# Remark: Setzt für FormIt die technischen Absenderdaten.
# row1 = IP-Adresse des Besuchers
# row2 = Hostname per Reverse DNS (REMOTE_HOST ist oft nicht verfügbar)
# row3 = Browser-User-Agent
# Diese Werte werden im FormIt-Aufruf als zusätzliche Felder verwendet.

$hook->setValue('row1', $_SERVER['REMOTE_ADDR']);

# $hook->setValue('row2', $_SERVER['REMOTE_HOST']);
# Error Log: PHP warning Undefined array key $_SERVER['REMOTE_HOST']
# therefore:
$hook->setValue('row2', gethostbyaddr($_SERVER['REMOTE_ADDR']));

$hook->setValue('row3', $_SERVER['HTTP_USER_AGENT']);

return true;


🛡️ FormIt-Aufruf mit eigener Verschlüsselung um E-Mail Adressen zu schützen

  • Im gezeigten FormIt-Aufruf werden E-Mail-Adressen über eine eigene Verschlüsselung geschützt.
  • Dazu werden zwei Snippets benötigt.

💡 Idee
Statt E-Mail-Adressen im Klartext im HTML zu zeigen, werden sie:

  • beim Rendern im Formular verschlüsselt (Snippet fiEmailEncrypt)
  • beim Absenden im FormIt-Hook wieder entschlüsselt (fiDecryptMailAn)
  • an den email-Hook in Klartext übergeben

So sieht ein Bot im HTML nur kryptische Base64-Strings, aber der Mailversand funktioniert sauber.


1) Snippet fiEmailEncrypt (E-Mail verschlüsseln)

In MODX unter Elemente → Snippets anlegen:

Name: fiEmailEncrypt

<?php
/**
 * E-Mail-Adresse für Formulare verschlüsseln
 * Verwendung:
 * [[fiEmailEncrypt? &input=`person@example.org`]]
 */

if (empty($input)) {
    return '';
}

// Schlüssel – in beiden Snippets identisch halten
$key = 'MeinGeheimerKey123';

$plain  = trim($input);
$plen   = strlen($plain);
$klen   = strlen($key);
$outBin = '';

for ($i = 0; $i < $plen; $i++) {
    $outBin .= chr(ord($plain[$i]) ^ ord($key[$i % $klen]));
}

return base64_encode($outBin);

2) Snippet fiDecryptMailAn (FormIt-Hook)

In MODX unter Elemente → Snippets anlegen:

Name: fiDecryptMailAn

<?php
/**
 * FormIt-Hook: entschlüsselt mailAn
 * Muss VOR dem email-Hook ausgeführt werden
 */

/** @var fiHooks $hook */
$modx = $hook->modx;

$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
if ($method !== 'POST') {
    return true;
}

$values = $hook->getValues();
if (!is_array($values)) {
    return true;
}

$cipher = $values['mailAn'] ?? '';
if ($cipher === '') {
    return true;
}

// gleicher key wie in fiEmailEncrypt
$key  = 'MeinGeheimerKey123';
$klen = strlen($key);

$data = base64_decode($cipher, true);
if ($data === false) {
    $hook->addError('mailAn', 'Ungültige Empfänger-Adresse (Dekodierfehler).');
    return false;
}

$dlen  = strlen($data);
$plain = '';

for ($i = 0; $i < $dlen; $i++) {
    $plain .= chr(ord($data[$i]) ^ ord($key[$i % $klen]));
}

if (strpos($plain, '@') === false) {
    $hook->addError('mailAn', 'Ungültige Empfänger-Adresse.');
    return false;
}

$values['mailAn'] = $plain;
$hook->setValues($values);

return true;

3) FormIt-Aufruf (relevanter Teil)

Wichtig: fiDecryptMailAn vor email.

&hooks=`spam,math,userAgent,fiDecryptMailAn,FormItSaveForm,AjaxUpload2Formit,AjaxUploadAttachments,email,AjaxUploadRemove,redirect`

4) Select-Feld im Formular mit verschlüsselten Empfängern
<select name="mailAn" required>
  <option value="">Auswahl...</option>

  <option value="[[fiEmailEncrypt? &input=`empfaenger1@example.com`]]"
          [[!+fi.mailAn:FormItIsSelected=`empfaenger1@example.com`]]>
      Ansprechpartner 1
  </option>

  <option value="[[fiEmailEncrypt? &input=`empfaenger2@example.com`]]"
          [[!+fi.mailAn:FormItIsSelected=`empfaenger2@example.com`]]>
      Ansprechpartner 2
  </option>

  <option value="[[fiEmailEncrypt? &input=`admin@example.com`]]"
          [[!+fi.mailAn:FormItIsSelected=`admin@example.com`]]>
      Website-Administrator
  </option>
</select>

✅ Ergebnis
  • Keine Klartext-E-Mail-Adressen im HTML.
  • Klartext wird erst im Hook wiederhergestellt.
  • Voll kompatibel mit FormItSaveForm + email-Hook.
  • Selektierte Optionen funktionieren wie gewohnt.

Empfehlung
Der Versand über einen echten SMTP-Server mit Authentifizierung und SSL/TLS ist deutlich zuverlässiger als die Nutzung von localhost.
Sobald alles funktioniert, solltest du daher auf SMTP umstellen. Weitere Details findest du unter „SMTP E-Mail Einrichtung“.