Konzepte
Diese Seite erklärt die Grundprinzipien hinter Ultimate Popups: Wie Popups geladen werden, was Auslöser und Anzeigehäufigkeit zur Laufzeit bedeuten, wie das Targeting funktioniert und was die Cookie-Consent-Registrierung bewirkt.
Wie ein Popup den Besucher erreicht
Das Plugin arbeitet in zwei Phasen pro Seitenaufruf.
Phase 1 — Serverseitige Übereinstimmung (PHP): Bei jedem Storefront-Seitenaufruf fragt der PopupSubscriber alle aktiven Popups ab und bewertet jedes gegen:
- Den aktuellen Verkaufskanal.
- Den Zielmodus (Seitenroute oder benutzerdefiniertes URL-Muster).
- Shopware-Rule-Builder-Regeln (alle angehängten Regeln müssen zutreffen — UND-Logik).
Popups, die alle Prüfungen bestehen, werden der Seite als ssd_popups-Erweiterung auf dem Seitenobjekt hinzugefügt.
Phase 2 — Clientseitiges Laden (JavaScript): Die Twig-Vorlage (base.html.twig) rendert für jedes übereinstimmende Popup ein <div data-ssd-popup …>-Element, das die Optionen des Popups als JSON-Datenattribut trägt — einschließlich der aufgelösten Viewport-Matrix { desktop, tablet, mobile }. Nach dem Laden der Seite liest die JavaScript-Klasse SsdPopupPlugin jedes Element, prüft den Häufigkeitscap (Browser-Speicher) und — wenn der Cap es erlaubt — wendet eine eventuell vorhandene Einwilligungs-Sperre an, wartet dann auf die Auslösebedingung und lädt anschließend den Popup-Inhalt per XHR-POST von /ssd-digital/popup/content/{id}.
Der Server rendert die CMS-Seite über PopupContentLoader, der die richtige CMS-Seite für das erkannte Gerät auswählt und alle CMS-Slot-Daten auflöst. Die HTML-Antwort wird über Shopwares PseudoModalUtil in ein Bootstrap-Modal eingefügt.
Diese Aufteilung bedeutet: Der Server entscheidet, welche Popups berechtigt sind; der Browser entscheidet, ob und wann sie angezeigt werden.
Auslöser
Ein Auslöser ist das Ereignis, das das Popup zum Öffnen veranlasst (vorbehaltlich des Häufigkeitscaps).
Sofort
Der einfachste Auslöser. Nach dem Seitenaufruf öffnet das JS-Plugin das Popup sofort oder wartet displayDelay Sekunden. Es ist keine Benutzerinteraktion erforderlich.
Exit-Intent
Das JS-Plugin hängt einen mouseleave-Listener auf document.documentElement an. Wenn der Besucher den Mauszeiger über die obere Kante der Seite hinaus bewegt (event.clientY < 0), feuert der Listener, entfernt sich selbst und öffnet das Popup. Dies feuert nur einmal pro Seitenaufruf.
Exit-Intent ist grundsätzlich ein Zeigegerät-Muster. Das Plugin erkennt Touch-only-Geräte über window.matchMedia('(hover: hover)'). Auf Nicht-Hover-Geräten (die meisten Smartphones, viele Tablets) gibt es keinen Cursor, der verfolgt werden könnte, sodass das Plugin auf eine zeitgesteuerte Anzeige zurückfällt: Es zeigt das Popup nach der für das aktive Gerät konfigurierten Verzögerung an, standardmäßig nach 30 Sekunden, wenn keine Verzögerung gesetzt ist (0 = sofort). Für volle Kontrolle auf Smartphones: entsperren Sie den Auslöser im Tab Mobil und stellen Sie Sofort mit Ihrer eigenen Verzögerung ein.
Anzeigehäufigkeit
Der Häufigkeitscap wird vollständig im Browser erzwungen, bevor die XHR-Anfrage gestellt wird. Das JS-Plugin prüft den Browser-Speicher anhand der Popup-ID:
| Häufigkeit | Speichertyp | Schlüssel | Prüfung |
|---|---|---|---|
| Immer | — | — | Immer bestanden. |
| Einmal pro Sitzung | Session-Cookie | ssd_popup_<id> | Bestanden, wenn Cookie fehlt. |
| Einmal pro Tag | localStorage | ssd_popup_<id> | Bestanden, wenn Schlüssel fehlt oder Zeitstempel > 24 h alt. |
| Einmalig | localStorage | ssd_popup_<id> | Bestanden, wenn Schlüssel fehlt. |
Wenn das Popup öffnet, schreibt _markAsShown() den Schlüssel. Bei Immer wird nichts geschrieben. Bei Einmal pro Sitzung ist der Wert ein Session-Cookie (ohne Ablaufdatum) — über alle Tabs und Fenster hinweg gültig und beim Schließen des Browsers gelöscht; sessionStorage wurde bewusst vermieden, da es pro Tab gilt und das Popup beim Öffnen einer Unterseite in einem neuen Tab erneut auslöste. Bei Einmal pro Tag und Einmalig bleibt der Schlüssel sitzungsübergreifend in localStorage bestehen.
Auswirkungen:
- Caps gelten pro Browser-Profil, nicht pro Shopware-Kundenkonto.
- Der Privatmodus/Inkognito-Modus startet mit leerem Speicher — ein Besucher im Privatmodus sieht das Popup wie zum ersten Mal.
- Das Löschen von Browser-Daten setzt alle Caps für diesen Browser zurück.
Einwilligungs-Sperre
Ein Popup kann so konfiguriert werden, dass es auf Cookie-Einwilligung wartet. Das Feld Erforderliche Cookie-Einwilligung (pro Gerät) enthält eine Liste von Cookie-Namen; wenn es nicht leer ist, wird der JS-Plugin-Auslöser erst aktiviert, nachdem der Besucher alle dieser Cookies akzeptiert hat.
Beim Laden prüft das Plugin die betreffenden Cookies über CookieStorage. Wenn sie bereits akzeptiert sind, wird der Auslöser normal aktiviert. Wenn nicht, abonniert das Plugin Shopwares CookieConfiguration_Update-Event (das veröffentlicht wird, wenn der Besucher eine Cookie-Auswahl im Consent-Banner speichert) und aktiviert den Auslöser erst, wenn die erforderlichen Cookies vorhanden sind. Der Auslöser wird höchstens einmal aktiviert.
Dies gilt für Inhalte, die rechtlich erst nach Einwilligung geladen werden dürfen — z. B. ein Popup, das ein Drittanbieter-Newsletter- oder Marketing-Widget einbettet. Die auswählbaren Optionen stammen aus Shopwares eigenen registrierten Cookie-Gruppen (die Gruppe „Erforderlich" ist ausgenommen, da diese Cookies immer gesetzt sind). Lassen Sie das Feld leer, um das Popup unabhängig von der Einwilligung anzuzeigen.
Dies unterscheidet sich von der eigenen Cookie-Consent-Registrierung des Plugins, die lediglich den Häufigkeits-Speicher des Plugins (ein Session-Cookie sowie localStorage) beim Cookie-Manager deklariert.
Seitenausrichtung
Wenn ein Popup keinen Zielmodus gesetzt hat, ist es auf jeder Storefront-Seite berechtigt.
Seitenbeschränkungsmodus verwendet eine statische Zuordnung von lesbaren Routenschlüsseln (home, product, category, …) zu Shopware-Routennamen (frontend.home.page, frontend.detail.page, frontend.navigation.page, …). Der Subscriber liest das _route-Attribut der aktuellen Anfrage und vergleicht es mit dem target_routes-Array des Popups. Mindestens ein Schlüssel muss übereinstimmen.
Benutzerdefinierter URL-Modus arbeitet mit dem rohen Anfragepfad (der SEO-freundlichen URL, nicht dem intern umgeschriebenen Pfad). Der Subscriber entfernt das Verkaufskanal-Basispräfix und testet dann jedes gespeicherte Muster gegen den Pfad mithilfe eines Regex, der * als .* behandelt. Die Übereinstimmung ist nicht case-sensitiv und toleriert abschließende Schrägstriche.
Kategorie-Einschränkung ist ein Sub-Filter der Seitenbeschränkung. Sie gilt nur, wenn die aktuelle Route frontend.navigation.page ist (eine Kategorie-/Listingseite). Der Subscriber liest die navigationId aus der Anfrage und prüft, ob sie in der categories-Sammlung des Popups erscheint. Wenn keine Kategorien an das Popup angehängt sind, bestehen alle Kategorieseiten.
Produkt-Einschränkung spiegelt den Kategorie-Filter für Produktseiten. Sie gilt nur, wenn die aktuelle Route frontend.detail.page ist (eine Produktdetailseite). Der Subscriber liest die productId aus der Anfrage und prüft, ob sie in der products-Sammlung des Popups erscheint. Eine Variantenseite trägt die eigene Varianten-ID — ein auf eine bestimmte Variante gepinntes Popup erscheint nur dort. Wenn keine Produkte an das Popup angehängt sind, bestehen alle Produktdetailseiten.
Gerätespezifische Einstellungen und Vererbung
Fast jede Anzeigeeinstellung ist pro Gerät konfigurierbar — CMS-Seite, Modalgröße, vertikale Ausrichtung, Header ausblenden, Helles Schließen-Symbol, Auslöser, Verzögerung, erforderliche Cookie-Einwilligung und eigenes CSS. Desktop ist die Basis; Tablet und Mobil erben davon, bis ein Feld entsperrt wird. Die Speicherung verwendet nullable tablet_*/mobile_*-Override-Spalten, wobei NULL „Desktop-Wert übernehmen" bedeutet (flaches Coalesce — keine Tablet→Mobil-Kaskade). Siehe die Konfigurationsreferenz für die Spaltenübersicht.
Die Aufgabenteilung ist bewusst gewählt: PHP löst nur die Vererbung auf (es liefert eine effektive { desktop, tablet, mobile }-Matrix ohne NULL-Werte), und JavaScript entscheidet, welches Gerät der Besucher nutzt, indem es den --sw-current-breakpoint des Themes liest (XS/SM → mobile, MD → tablet, LG/XL/XXL → desktop). Da die Breakpoint-Autorität das eigene SCSS des Themes ist, bleibt das Popup auch dann korrekt, wenn ein Theme seine Breakpoints verschiebt.
Das JS-Plugin abonniert Shopwares Viewport/hasChanged-Event. Wenn sich die Viewport-Klasse ändert, während das Popup offen ist (Skalierung oder Gerätedrehung), wendet das Plugin die Darstellungseinstellungen des neuen Geräts erneut an — und ruft für CMS-Inhalte die passende CMS-Seite ab (über den device-Parameter in der XHR-Anfrage, mit Fallback auf die Desktop-Seite, wenn eine Variante nicht zugewiesen ist) und tauscht den Modal-Body-HTML aus, ohne das Popup zu schließen. Der Anzeige-auslöser wird jedoch einmalig beim Laden für das Gerät zum Ladezeitpunkt ausgewertet und wird bei Größenänderung nicht erneut aktiviert — ein Auslöser betrifft den Einstiegsmoment.
Eigenes CSS wird in ein <style>-Element injiziert und automatisch in einen per-Popup-per-Gerät-Bereich eingeschlossen, sodass die Selektoren eines Händlers nur dieses eine Popup auf dem aktiven Gerät betreffen; der eingeschlossene String wird bei Breakpoint-Wechsel ausgetauscht.
HTTP-Caching
Das Plugin ist Cache-bewusst. Der PopupSubscriber sendet AddCacheTagEvent mit einem globalen Tag (ssd_popup-id) und Per-Popup-Tags (ssd_popup-<id>) für jedes Popup, das zur aktuellen Seite gepasst hat. Dies teilt Shopwares HTTP-Cache mit, welche Cache-Einträge mit welchen Popups verknüpft sind.
Wenn ein Popup gespeichert wird, lauscht der PopupCacheInvalidationSubscriber auf EntityWrittenContainerEvent, extrahiert die IDs der geschriebenen Popups und ruft CacheInvalidator::invalidate() mit den entsprechenden Tags auf. Dies stellt sicher, dass Seiten, die ein bearbeitetes Popup zeigen, sofort invalidiert und neu aufgebaut werden.
Cookie-Consent-Registrierung
Das Plugin registriert sich im Shopware Cookie-Manager, um transparent über seine Browser-Speichernutzung zu sein. Es registriert sich unter der Gruppe Erforderlich (nicht Optional), weil die Häufigkeitsbeschränkungsdaten — ein Session-Cookie für „Einmal pro Sitzung" und localStorage für „Einmal pro Tag"/„Einmalig" — für das Kernverhalten des Plugins notwendig sind. Ohne sie würden diese Caps nicht funktionieren.
Die Registrierung erscheint im Cookie-Manager als „Ultimate Popup" und ist mit dem Cookie-Bezeichner ssd_ultimate_popup verknüpft. DSGVO-konforme Shops, die Shopwares Consent-System verwenden, zeigen diesen Eintrag Besuchern im Cookie-Einstellungsdialog.
Rule-Builder-Integration
Das Plugin nutzt Shopwares bestehenden Rule Builder wieder, ohne eigene Rule-Bedingungen hinzuzufügen. Jede unter Einstellungen > Regeln erstellte Regel kann an ein Popup angehängt werden.
Regeln werden serverseitig ausgewertet: Der PopupSubscriber vergleicht die angehängten Regel-IDs des Popups mit der SalesChannelContext::getRuleIds()-Liste (die Shopware für den aktuellen Besucher und Anfrage-Kontext vorauswertet). Alle angehängten Regeln müssen in der aktiven Regelliste des Kontexts erscheinen — dies ist UND-Logik. Ein Popup ohne angehängte Regeln passt zu allen Besuchern.
Das bedeutet: Sie können Popups mit jeder Kombination aus Warenkorbwert, Kundengruppe, Land, Sprache, Browser, Betriebssystem, Zeitfenster oder einer anderen vom Shopware-Rule-Builder unterstützten Bedingung ausrichten.
