
Email-Benachrichtigung bei Datenänderungen von Mitgliedern (Frontend-Benutzern) in Contao
In Contao können sich Mitglieder (Frontend-Benutzer) selbst registrieren und ihre Profildaten ändern. Doch wie wirst du als Administrator über Änderungen informiert?
Zwar kann das Notification Center für solche Benachrichtigungen genutzt werden, doch es sendet immer alle Mitgliedsdaten – ohne anzuzeigen, welche sich tatsächlich geändert haben.
Dabei ist es aus mehreren Gründen wichtig, Änderungen gezielt zu protokollieren:
- Sicherheit – Ungewöhnliche Änderungen (z. B. neue E-Mail-Adresse) erkennen
- Verwaltung – Wichtige Anpassungen im Blick behalten
- Dokumentation – Änderungen nachvollziehbar machen
Update: Mit einem eigenen Inserttag und einem Hook kannst du genau dieses Problem lösen – ohne zusätzliche Erweiterung:
(Vorher: Mit 2 Hooks ... steht noch am Ende des Beitrags)
- member::member2session – Speichert die aktuellen Mitgliedsdaten temporär in der Session
- updatePersonalData Hook – Erkennt Änderungen und sendet eine E-Mail-Benachrichtigung
Diese Lösung wurde erfolgreich mit Contao 4.13 getestet. Wie du das Ganze umsetzt, erfährst du jetzt!
1. MemberInsertTagListener.php
Das Insertag member::member2session wird einfach an einer beliebigen Stelle ins Template des Datenänderungs-Moduls "member_grouped.html5"
geschrieben. Damit werden die aktuellen Benutzerdaten aus der tl_member-Tabelle abgerufen und in der Session gespeichert.
So funktioniert der Code:
- Überprüfung auf Frontend-User:
Es wird geprüft, ob ein eingeloggter Frontend-User existiert. - Daten aus der Datenbank abrufen:
Die aktuellen Mitgliedsdaten des eingeloggten Benutzers werden aus der tl_member-Tabelle abgerufen. Die Daten werden mit der id des Benutzers selektiert. - Daten in der PHP-Session speichern:
Die abgerufenen Benutzerdaten werden unter dem Schlüssel user_data_before_update in der Session gespeichert. Diese gespeicherten Daten dienen später als Referenz, um zu erkennen, welche Werte sich nach einer Änderung des Profils verändert haben.
Diese gespeicherten Werte dienen als Referenz, um später festzustellen, welche Daten tatsächlich geändert wurden.
Auf zur Implementierung: Falls nicht vorhanden, erstelle im Wurzelverzeichnis deiner Contao-Installation den Ordner src und erstelle darin einen Ordner EventListener. Darin lege dann die Dateil MemberInsertTagListener.php an. Die Datei liegt dann also unter src/EventListener/MemberInsertTagListener.php. Und hier der Code:
<?php
namespace App\EventListener;
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
use Contao\FrontendUser;
use Contao\Database;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
#[AsHook('replaceInsertTags')]
class MemberInsertTagListener
{
private SessionInterface $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function __invoke(string $tag)
{
$chunks = explode('::', $tag);
// Prüfen, ob es sich um unser Insert-Tag handelt
if ($chunks[0] !== 'member' || $chunks[1] !== 'member2session') {
return false;
}
$user = FrontendUser::getInstance();
// Prüfen, ob der Benutzer eingeloggt ist
if (!$user->id) {
return 'Kein Benutzer eingeloggt';
}
// Benutzerdaten aus der Datenbank holen
$dbUser = Database::getInstance()
->prepare("SELECT * FROM tl_member WHERE id = ?")
->limit(1)
->execute($user->id)
->fetchAssoc();
// Alte Benutzerdaten in die Session speichern
if ($dbUser) {
$this->session->set('user_data_before_update', $dbUser);
return 'Benutzerdaten wurden in die Session gespeichert';
}
return 'Keine Daten gefunden';
}
}
Sobald du den den Cache im Contao-Manager leerst oder auf der Console (vendor/bin/contao-console cache:clear), werden die Mitgliedsdaten bei jedem Login in der PHP-Session gespeichert.
2. UpdatePersonalDataListener.php
Wenn ein Mitglied seine Daten ändert, müssen wir die neuen Werte mit den alten vergleichen. Dazu dienen die Daten, die wir zuvor in der PHP-Session gespeichert haben. Falls Änderungen festgestellt werden, versenden wir eine E-Mail-Benachrichtigung an den Administrator.
So funktioniert der Code:
- Vorherige Daten aus der Session abrufen – Die Mitgliedsdaten, die wir beim Login in der Session gespeichert haben, werden abgerufen, um sie mit den neuen Werten zu vergleichen.
- Änderungen ermitteln – Es wird überprüft, ob sich die Werte geändert haben. Bestimmte Felder wie password, username oder lastLogin werden dabei ignoriert.
- E-Mail senden – Wenn Änderungen erkannt werden, versenden wir eine E-Mail, die die alten und neuen Werte der geänderten Felder enthält.
- Neue Werte speichern – Nach dem Vergleich speichern wir die aktuellen Mitgliedsdaten wieder in der Session, um sie für zukünftige Vergleiche zur Verfügung zu haben.
Auf zur Implementierung: Die Ordner src und EventListener sollten bereits existieren. Lege dann die Datei UpdatePersonalDataListener.php zusätzlich an. Die Datei liegt dann also ebenso unter src/EventListener/UpdatePersonalDataListener.php. Und hier der Code:
<?php
namespace App\EventListener;
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
use Contao\FrontendUser;
use Contao\Email;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Contao\Database;
#[AsHook('updatePersonalData')]
class UpdatePersonalDataListener
{
private SessionInterface $session;
// Liste der Felder, die ausgeschlossen werden sollen
private array $excludedFields = [
'memberImage',
'agreement',
'session',
'currentLogin',
'lastLogin',
'dateAdded',
'password',
'login',
'groups',
'tstamp',
'id',
'username',
];
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function __invoke(FrontendUser $member, array $data, $module): void
{
// Alte Formulardaten aus der Session holen (vor der Änderung)
$oldData = $this->session->get('user_data_before_update', []);
if (empty($oldData)) {
return; // Falls keine alten Daten vorhanden sind, abbrechen
}
// Geänderte Daten feststellen
$changedData = [];
// Durchlaufen der alten und neuen Daten und Vergleichen
foreach ($oldData as $field => $oldValue) {
// Wenn das Feld in der Exklusivliste ist, überspringen
if (in_array($field, $this->excludedFields)) {
continue;
}
$newValue = $data[$field] ?? null;
// Wenn der Wert sich wirklich geändert hat
if ($this->valuesChanged($oldValue, $newValue, $field)) {
// Die geänderten Werte in changedData speichern
$changedData[$field] = [
'alt' => $this->formatValue($oldValue, $field),
'neu' => $this->formatValue($newValue, $field)
];
}
}
// Wenn keine Änderungen festgestellt wurden, nichts tun
if (empty($changedData)) {
return;
}
// E-Mail senden mit den geänderten Daten
$this->sendEmail($member, $changedData);
// Alte Benutzerdaten nach dem Update wieder in der Session speichern
$this->saveUserDataToSession($member);
}
private function valuesChanged($oldValue, $newValue, string $field = ''): bool
{
// Falls das Feld "dateOfBirth" ist, ins Datumsformat umwandeln
$oldValue = $this->formatValue($oldValue, $field);
$newValue = $this->formatValue($newValue, $field);
// Wenn der alte Wert und der neue Wert leer sind ("" oder NULL), keine Änderung
if ((empty($oldValue) || $oldValue === null) && (empty($newValue) || $newValue === null)) {
return false;
}
// Wenn der Wert unverändert ist (gleich alt und neu), dann ignorieren
return $oldValue !== $newValue;
}
private function formatValue($value, string $field)
{
if ($field === 'dateOfBirth' && is_numeric($value)) {
return date('d.m.Y', (int)$value);
}
return $value;
}
private function sendEmail(FrontendUser $member, array $changedData): void
{
// Beginn der E-Mail-Nachricht
$emailText = "Hallo,\n\n";
$emailText .= "das Mitglied mit der E-Mail-Adresse {$member->email} hat folgende Daten geändert:\n\n";
// Durchlaufen der geänderten Daten und Formatieren des Textes
foreach ($changedData as $field => $values) {
// Überprüfen, ob ein benutzerfreundlicher Name für das Feld in den Sprachdateien vorhanden ist
$fieldLabel = isset($GLOBALS['TL_LANG']['tl_member'][$field])
? $GLOBALS['TL_LANG']['tl_member'][$field][0] // Das erste Element enthält den Labelnamen
: ucfirst($field); // Wenn kein Label vorhanden, den Feldnamen verwenden
$emailText .= "{$fieldLabel} (alt): " . $values['alt'] . "\n";
$emailText .= "{$fieldLabel} (neu): " . $values['neu'] . "\n\n";
}
// E-Mail erstellen
$email = new Email();
$email->from = 'reply@deinedomain.de';
$email->subject = 'Änderung von Mitgliedsdaten';
$email->text = $emailText; // Der formatierte Text wird hier gesetzt
$email->sendTo('kontakt@deinedomain.de');
}
private function saveUserDataToSession(FrontendUser $member): void
{
// Benutzerdaten aus der Datenbank holen
$dbUser = Database::getInstance()
->prepare("SELECT * FROM tl_member WHERE id = ?")
->limit(1)
->execute($member->id)
->fetchAssoc();
// Alte Benutzerdaten in der Session speichern
if ($dbUser) {
$this->session->set('user_data_before_update', $dbUser);
}
}
}
Code Anpassungen:
-
Ignorierte Felder: Bestimmte Felder wie password, username, lastLogin und andere systemrelevante Felder werden ignoriert, da sie nicht für die Überwachung von Änderungen relevant sind. Hier kannst du weitere Felder hinzufügen bzw. auch entfernen.
-
E-Mail-Konfiguration: In der Funktion sendEmail() müssen die E-Mail-Adressen des Absenders und Empfängers angepasst werden. Der Absender ist no-reply@deinedomain.de und der Empfänger ist kontakt@deinedomain.de. Passe diese E-Mail-Adressen an deine tatsächlichen Werte an.
- Geburtdatum: Das Geburtdatum wird von Timestamp zu Datum umgewandelt. Nicht der Timestamp wird verglichen, sondern das Datumsformat dd.mm.yyyy. Das Datumsformat wird dann auch in der E-Mail verwendet, wenn es Änderungen gab.
Wenn du noch den Cache im Contao-Manager leerst, wird bei jeder Änderung die E-Mail-Benachrichtigung ausgelöst und die neuen Mitgliedsdaten werden in der PHP-Session gespeichert.
Fazit
Mit dieser Lösung hast du ein effektives Änderungs-Tracking für Mitglieder in Contao implementiert – ganz ohne die Notwendigkeit einer zusätzlichen Erweiterung. Du kannst nun Änderungen an Mitgliedsdaten automatisch überwachen und dich per E-Mail benachrichtigen lassen, ohne dabei auf externe Tools angewiesen zu sein. Diese Lösung wurde erfolgreich mit Contao 4.13 getestet.
Ich freue mich über dein Feedback und stehe für Fragen gerne zur Verfügung!
Veraltet: 1. PostLoginListener.php
Diese Lösung hatte ich zunächst. Sie hat auch gut funktioniert, allerdings habe ich beim Testen die Daten direkt in der Datenbank geändert, wodurch die Session nicht geändert wurde. Daher nutze ich jetzt den Inserttag. Dennoch zeige ich hier gern meine erste Lösung:
Wenn sich ein Mitglied einloggt, speichern wir seine aktuellen Profildaten temporär in der PHP-Session. Dadurch können wir sie später mit den neuen Werten vergleichen, falls das Mitglied Änderungen an seinen Daten vornimmt.
So funktioniert der Code:
- Überprüfung ob Backend User - in dem Fall tue nichts! Wir wollen die Frontend User Aktion.
- Daten aus der Datenbank abrufen – Die aktuellen Mitgliedsdaten werden aus der tl_member-Tabelle geladen.
- Daten in der PHP-Session speichern – Die abgerufenen Daten werden unter dem Schlüssel user_data_before_update in der Session gesichert.
Diese gespeicherten Werte dienen als Referenz, um später festzustellen, welche Daten tatsächlich geändert wurden.
Auf zur Implementierung: Falls nicht vorhanden, erstelle im Wurzelverzeichnis deiner Contao-Installation den Ordner src und erstelle darin einen Ordner EventListener. Darin lege dann die Dateil PostLoginListener.php an. Die Datei liegt dann also unter src/EventListener/PostLoginListener.php. Und hier der Code:
<?php
namespace App\EventListener;
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
use Contao\FrontendUser;
use Contao\Database;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
#[AsHook('postLogin')]
class PostLoginListener
{
private SessionInterface $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function __invoke($user): void
{
if ($user instanceof BackendUser) {
return;
}
// Alte Benutzerdaten aus der Datenbank holen
$dbUser = Database::getInstance()
->prepare("SELECT * FROM tl_member WHERE id = ?")
->limit(1)
->execute($user->id)
->fetchAssoc();
// Alte Benutzerdaten in der Session speichern
if ($dbUser) {
$this->session->set('user_data_before_update', $dbUser);
}
}
}