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

Mit nur zwei Hooks kannst du genau dieses Problem lösen – ohne zusätzliche Erweiterung:

  • postLogin Hook – 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. PostLoginListener.php

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:

  • 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(FrontendUser $user): void
    {
        // Mitglieder-Daten aus der Datenbank abrufen
        $dbUser = Database::getInstance()
            ->prepare("SELECT * FROM tl_member WHERE id = ?")
            ->limit(1)
            ->execute($user->id)
            ->fetchAssoc();

        // Daten temporär in der Session speichern
        if ($dbUser) {
            $this->session->set('user_data_before_update', $dbUser);
        }
    }
}

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:

  1. 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.
  2. Änderungen ermitteln – Es wird überprüft, ob sich die Werte geändert haben. Bestimmte Felder wie password, username oder lastLogin werden dabei ignoriert.
  3. E-Mail senden – Wenn Änderungen erkannt werden, versenden wir eine E-Mail, die die alten und neuen Werte der geänderten Felder enthält.
  4. 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;

    // Felder, die ignoriert 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
    {
        // Vorherige Daten aus der Session abrufen
        $oldData = $this->session->get('user_data_before_update', []);

        if (empty($oldData)) {
            return; // Keine alten Daten? Dann nichts tun.
        }

        // Änderungen ermitteln
        $changedData = [];

        foreach ($oldData as $field => $oldValue) {
            if (in_array($field, $this->excludedFields)) {
                continue;
            }

            $newValue = $data[$field] ?? null;

            if ($this->valuesChanged($oldValue, $newValue)) {
                $changedData[$field] = ['alt' => $oldValue, 'neu' => $newValue];
            }
        }

        if (empty($changedData)) {
            return;
        }

        // E-Mail versenden
        $this->sendEmail($member, $changedData);

        // Neue Werte für zukünftige Vergleiche speichern
        $this->saveUserDataToSession($member);
    }

    private function valuesChanged($oldValue, $newValue): bool
    {
        if ((empty($oldValue) || $oldValue === null) && (empty($newValue) || $newValue === null)) {
            return false;
        }

        if ($oldValue === $newValue) {
            return false;
        }

        if (is_numeric($oldValue) && is_numeric($newValue)) {
            return (float)$oldValue !== (float)$newValue;
        }

        return $oldValue !== $newValue;
    }

    private function sendEmail(FrontendUser $member, array $changedData): void
    {
        $emailText = "Hallo,\n\n";
        $emailText .= "das Mitglied mit der E-Mail-Adresse {$member->email} hat folgende Daten geändert:\n\n";

        foreach ($changedData as $field => $values) {
            $fieldLabel = $GLOBALS['TL_LANG']['tl_member'][$field][0] ?? ucfirst($field);
            $emailText .= "{$fieldLabel} (alt): " . $values['alt'] . "\n";
            $emailText .= "{$fieldLabel} (neu): " . $values['neu'] . "\n\n";
        }

        $email = new Email();
        $email->from = 'no-reply@deinedomain.de';
        $email->subject = 'Mitgliedsdaten geändert';
        $email->text = $emailText;
        $email->sendTo('kontakt@deinedomain.de');
    }

    private function saveUserDataToSession(FrontendUser $member): void
    {
        $dbUser = Database::getInstance()
            ->prepare("SELECT * FROM tl_member WHERE id = ?")
            ->limit(1)
            ->execute($member->id)
            ->fetchAssoc();

        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.

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!

Zurück zur Newsübersicht