Funktionsweise & Übersicht
Zielgruppe: Alle (Administratoren, Shop-Benutzer, Entwickler)
Was ist das Modul?
DigitalProductPage stellt für jeden Shop-Benutzer eine oder mehrere öffentlich erreichbare Landingpages bereit. Eine Landingpage ist eine Seite mit fester URL, die per QR-Code, Mail oder Messenger weitergegeben werden kann – typische Einsatzfälle:
digitale Speisekarte eines Restaurants
Veranstaltungs- oder Event-Karte
Saisonkarte (Mittagstisch, Catering, Sommerkarte)
Visitenkarten-Ersatz mit personalisiertem Profil
Das Modul unterscheidet sich vom älteren DigitalProduct-Modul: dort bekommt jede Bestellung eine eigene Landingpage. Hier verwaltet stattdessen jeder Shop-Benutzer seine eigenen Landingpages über die Shop-Oberfläche – die Bestellung der personalisierten Druckdaten (z.B. ein Flyer mit aufgedrucktem QR-Code) ist davon entkoppelt.
URL-Aufbau
Jede Landingpage wird über eine URL mit drei Bausteinen erreicht:
wide760/page/{prefix}/{name}[/{tab}]]]>Baustein | Beschreibung | Beispiel |
|---|---|---|
| Öffentliche Benutzer-ID, eindeutig pro Shop-Benutzer. Wahlweise eine automatisch generierte ULID oder ein normalisierter Wert aus einem Benutzerfeld (z.B. Kundennummer). |
|
| Vom Shop-Benutzer wählbarer Name (Slug) der konkreten Landingpage. Eindeutig pro Benutzer. |
|
| Optionaler Unter-Tab innerhalb derselben Landingpage (z.B. einzelne Speisekartenbereiche). |
|
Vollständige Beispiele:
wide760Wichtig: Es gibt keinen Root-Endpunkt
/page/{prefix}ohne Slug. Wer nur den Prefix kennt, bekommt eine 404 — so kann durch reines Probieren des Prefixes nicht festgestellt werden, ob ein Benutzer existiert oder welche Landingpages er hat.
Prefix-Strategie
Der {prefix} macht den entscheidenden Unterschied zwischen sicher und enumerierbar:
Strategie | URL-Beispiel | Eigenschaft |
|---|---|---|
ULID (Standard) |
| Nicht erratbar, garantiert eindeutig, kein Pflichtfeld nötig. |
Aus Benutzerfeld (opt-in) |
| Sprechender, kann aber doppelt vorkommen oder leer sein. Erfordert sorgfältige Konfiguration (siehe Admin-Anleitung). |
Egal welche Strategie gewählt wird, das Modul speichert die fertige öffentliche ID zentral und modulübergreifend ab, nicht direkt am Benutzer. So bleibt die Benutzerverwaltung unverändert, und derselbe Mechanismus ist später auch für andere Entitäten (Artikel, Bestellungen) wiederverwendbar.
Slug-Historie
Wenn ein Shop-Benutzer den Namen einer Landingpage ändert, wird der alte Name in einer Historie gespeichert, damit bereits gedruckte QR-Codes auf den alten Namen weiterhin funktionieren (HTTP 301 auf den neuen Namen). Der Benutzer sieht in der Verwaltung, wie oft jeder alte Name noch aufgerufen wurde, und kann ihn explizit freigeben, sobald er sich sicher ist, dass keine alten QR-Codes mehr im Umlauf sind.
Beim Umbenennen und Löschen wird der History-Eintrag unbedingt angelegt — auch wenn die alte Adresse nie aufgerufen wurde. Hintergrund: Eine neue Landingpage kann durchaus per QR-Code gedruckt werden, bevor der erste Scan passiert; ohne History-Eintrag gäbe es für den gedruckten QR-Code keine Weiterleitung. Historie-Einträge, die nie benutzt wurden, räumt der Benutzer selbst über die „Freigeben"-Aktion auf.
Release-Pipeline: Vom Bestellabschluss zum Tab
Wenn ein Shop-Benutzer einen Artikel mit Printess-Feld-Zuordnung bestellt und die Bestellung freigegeben wird, greift die Release-Pipeline:
Slug-Rename: Der im Printess-Formular eingegebene Landingpage-Name wird mit dem aktuellen Slug verglichen. Weicht er ab, wird die Landingpage automatisch umbenannt (der alte Slug wandert in die Historie und löst weiterhin eine 301-Weiterleitung aus).
Tab-Erstellung: Der im Printess-Formular eingegebene Tab-Name wird als neuer Tab unterhalb der Landingpage angelegt.
Nachbestellungs-Handling: Ist die aktuelle Bestellung eine Nachbestellung (Reorder), wird der Tab der Vorbestellung in den Papierkorb verschoben und ein neuer Tab mit dem aktuellen Inhalt angelegt. So bleibt die Zuordnung zur alten Bestellung in der Historie erhalten.
Kollisions-Suffix: Existiert bereits ein aktiver Tab mit demselben Slug, bekommt der neue Tab ein Datums-Suffix (z.B.
mittag-new-2026-04-15-143200). Der Shop-Benutzer sieht in der Verwaltung einen „Neuer Tab wartet"-Hinweis und kann den alten Tab per Ein-Klick-Ersetzen ablösen.Header-Medium auflösen: Ist am Artikel ein Header-Medium-Feld zugeordnet, wird der vom Kunden zurückgegebene URL-Wert geprüft und als Bild oder Video auf der Landingpage des zugehörigen Tabs angezeigt.
Tagged-PDF: Die Screen-Version der Bestellposition wird so erzeugt, dass die PDF-Tag-Struktur erhalten bleibt (siehe unten).
Tab↔Bestellposition-Verknüpfung
Jeder Tab wird bei der Erstellung über die Release-Pipeline mit genau einer Bestellposition verknüpft. Diese 1:1-Verknüpfung:
verhindert das Löschen eines Tabs, solange eine aktive Bestellposition verknüpft ist (Delete-Schutz),
ermöglicht die Anzeige der verknüpften Bestell-ID als Badge in der Shop-Verwaltung,
bleibt bei Nachbestellungen nachvollziehbar, weil der abgelöste alte Eintrag mit einem Zeitstempel markiert wird, statt gelöscht zu werden.
Tagged-PDF (Screen-Version)
Für Landingpage-Artikel erzeugt die Release-Pipeline eine Tagged-PDF als Screen-Version. Im Gegensatz zu normalen Printess-Screen-Versionen wird diese PDF ohne Ghostscript-Optimierung gespeichert, weil GS die PDF-Tag-Struktur (Überschriften-Hierarchie) zerstören würde. Diese Tags werden auf der Landingpage ausgelesen, um ein navigierbares Kapitelverzeichnis darzustellen.
Papierkorb für Tabs
Tabs (Sub-Slugs) können vom Shop-Benutzer in einen Papierkorb verschoben werden — z.B. wenn die Mittagskarte einer Saison nicht mehr aktuell ist, ihre Verknüpfung mit der ursprünglichen Bestellung aber für eine spätere Nachbestellung erhalten bleiben soll. Beim Löschen wird der Slug automatisch um ein Datums-Suffix erweitert (z.B. mittag → mittag-deleted-2026-04-14-113910), damit der ursprüngliche Slug sofort wieder für einen neuen Tab verfügbar ist. Aufruf-Statistiken bleiben erhalten.
Beim Wiederherstellen versucht das System, den ursprünglichen Slug zurückzusetzen — geht das nicht (weil ein neuer Tab den Slug inzwischen belegt hat), bleibt der Tab mit dem Suffix bestehen und der Benutzer kann ihn manuell umbenennen.
Ein endgültiges Löschen aus der Datenbank gibt es im Shop-UI nicht; das wäre eine manuelle Support-Aktion.
Öffentliche Landingpage-Darstellung
Die Landingpage unter /page/{prefix}/{name}[/{tab}] ist eine eigenständige Seite — komplett vom Shop-Layout losgelöst, eigenes HTML-Dokument mit eigenem CSS/JS. Das Design orientiert sich an einer dunklen, mobil-optimierten Menükarten-Darstellung (primärer Use-Case: QR-Code-Scan auf dem Smartphone).
Seitenstruktur
Bereich | Beschreibung |
|---|---|
Header | Sticky, zeigt den Firmennamen des Shop-Benutzers, Burger-Button für Side-Menu |
Banner | Optionaler Bild-/Video-Hero (pro Tab konfigurierbar, dreistufiger Fallback; siehe unten). Dismissible, fadet beim Scrollen aus |
Suchfeld | Textsuche über PDF-Inhalte |
Tab-Bar | Horizontale Tab-Buttons (nur sichtbar bei > 1 Tab), sticky unterhalb Header |
PDF-Viewer | Pro Tab ein Container für die gerenderten PDF-Seiten |
Info-Bereich | Adress- und Kontaktdaten des Shop-Benutzers |
Side-Menu | Slide-in-Menü rechts: Tab-Switcher, Kapitelnavigation (aus PDF-Tags), Info-Button |
Back-to-Top | Scroll-Button, erscheint ab 150px Scrolltiefe |
PDF-Serving-Endpoint
Pro Tab wird das personalisierte PDF über einen eigenen Endpoint geladen:
wide760Aspekt | Detail |
|---|---|
Authentifizierung | Keine (sessionless, wie die Seite selbst) |
Identifikation |
|
Cache-Busting |
|
Cache-Header | Mit |
Session-Cookies | Werden aus der PDF-Response entfernt, damit Browser-Caching funktioniert |
Template-Architektur
Die Seite nutzt eine Vererbungs-Struktur mit überschreibbaren Blöcken, damit Kunden-Templates einzelne Bereiche anpassen können. Alle Block-Namen tragen den Prefix dpp- und sind nach Release permanent (dürfen nicht umbenannt werden).
Das CSS liegt als externe, browser-cachebare Datei vor. Alle Farben nutzen CSS-Variablen (--dpp-color-*) und sind damit auf ein zukünftiges Theming vorbereitet (aktuell nicht konfigurierbar). Zusätzlich wird das Shop-eigene Custom-CSS geladen, sodass Shop-Administratoren Feinheiten per Backend-CSS anpassen können.
JavaScript-Architektur
Die Landingpage nutzt zwei getrennte JavaScript-Komponenten:
PdfViewer: PDF-Rendering, Kapitelnavigation aus dem PDF-Tag-Tree (Überschriften-Hierarchie), Textsuche mit Highlight-Overlays, Sidebar-Toggle.
DppApp: UI-Shell mit Banner-Dismiss (Scroll oder Close-Klick), Back-to-Top-Button, Info-Section-Toggle, Sidebar-Backdrop-Close und Tab-URL-Sync.
Beim Tab-Wechsel greifen beide Komponenten ineinander: Der PdfViewer tauscht das PDF aus, DppApp aktualisiert die Browser-URL und tauscht das Banner. Der Browser-Back-Button navigiert korrekt zwischen den Tabs zurück.
Landing Title (Anzeigename)
Jede Landingpage hat neben dem URL-Slug einen optionalen Title (sprechender Anzeigename). Während der Slug URL-kompatibel sein muss (nur Kleinbuchstaben, Bindestriche, keine Sonderzeichen), kann der Title beliebige Zeichen enthalten — z.B. „Pizzeria Venezia" statt pizzeria-venezia.
Eigenschaft | Slug | Title |
|---|---|---|
Verwendung | URL-Pfad ( | HTML |
Zeichen | Nur URL-sichere Zeichen (normalisiert) | Beliebig (Umlaute, Leerzeichen, Sonderzeichen) |
Eindeutigkeit | Eindeutig pro Shop-Benutzer | Nicht eindeutig (mehrere Landings können gleich heißen) |
Pflicht | Ja | Nein (Fallback auf Firmenname, dann Slug) |
Editierbar | Shop-Verwaltung + Release-Pipeline | Shop-Verwaltung |
Fallback-Kette für die Anzeige:
Title (wenn gesetzt)
Firmenname aus der Benutzeradresse
Slug
Der Title wird in der Shop-Verwaltung über ein Inline-Formular mit zwei Feldern bearbeitet: „Anzeigename" und „URL-Pfad". Beide können unabhängig voneinander geändert werden.
Dreistufige Header-Medien
Das Hero-Medium oberhalb des PDF-Viewers (Banner) kann in drei Stufen konfiguriert werden. Der effektive Wert wird pro Tab live aufgelöst — es gibt keine Persistierung auf Landing-Ebene, d.h. Änderungen an Shop- oder Artikel-Defaults schlagen sofort auf alle bestehenden Tabs durch, die keinen eigenen Printess-Return haben.
Fallback-Kette pro Tab (niedrigste → höchste Priorität)
Stufe | Quelle | Gesetzt durch | Wann wirkt's? |
|---|---|---|---|
1. Shop-Default | Modul-Konfigurationsseite — Medien-URL, Medientyp, Poster-URL, Alt-Text | Administrator | Für alle Tabs des Shops ohne höhere Stufe |
2. Artikel-Override | Artikel-Einstellungen im Backend — gleiche vier Felder | Administrator | Für Tabs, deren Bestellposition auf diesen Artikel zeigt |
3. Printess-Return | Vom Kunden im Printess-Editor gewählter Bild-/Video-Link | Release-Pipeline beim Freigeben der Bestellung | Für genau diesen Tab (1:1 zur Bestellposition) |
Die Auflösung greift pro Feld: Ein Printess-Return liefert typischerweise nur URL + Typ, Poster-URL und Alt-Text fallen weiterhin auf Artikel- oder Shop-Ebene zurück.
Typ-Erkennung (Bild vs. Video)
Aus einer URL allein ist nicht eindeutig, ob es sich um ein Bild oder ein Video handelt. Daher läuft beim Setzen einer URL genau einmal ein HEAD-Request mit folgenden Regeln:
Bedingung | Verhalten |
|---|---|
Nur | Andere Schemata werden abgelehnt. |
Host nicht im privaten/internen Netzbereich | SSRF-Schutz; DNS-Resolution vor dem Request. |
| Wird als Bild oder Video erkannt und gespeichert. |
HEAD schlägt fehl (Timeout, 404) | Fallback auf Extension-Parse ( |
Das Ergebnis wird auf allen drei Ebenen zusammen mit der URL persistiert. Die öffentliche Landingpage macht nie einen Laufzeit-HEAD.
Rendering
Bild-Medium: wird mit hoher Lade-Priorität eingebunden und für den initial sichtbaren Tab zusätzlich per Preload-Hinweis im HTML-Head referenziert.
Video-Medium: wird als Autoplay-Loop ohne Ton eingebettet, mit optionalem Poster-Bild als Platzhalter während des Ladens. Kein Preload (Mobile-Datenvolumen).
Keine URL auf keiner Stufe: Banner-Bereich wird auf Höhe 0 geschrumpft, kein Layout-Shift.
Tab-Wechsel: Das Banner wird live getauscht — ohne Seiten-Reload. Hat der Benutzer den Banner des aktuellen Tabs geschlossen und der nächste Tab hat dieselbe Banner-URL, bleibt er geschlossen.
Layout: Fester Seitenverhältnis-Rahmen (Standard 16:9) plus
object-fit: cover— kein Layout-Shift beim Swap oder Erstladen.
Scope / Nicht enthalten
Die Umsetzung beschränkt sich bewusst auf das Hero-Medium. Nicht enthalten:
Farbtheme-Overrides (CSS-Variablen sind vorbereitet, können aber noch nicht über die drei Stufen gesetzt werden)
Layout-Varianten (Höhe, Position, Overlay-Text)
Datei-Upload als Eingabe (aktuell nur URL oder Printess-Return)
Shop-Benutzer-UI für manuelle Landing-Overrides (die dritte Stufe entsteht ausschließlich aus dem Printess-Editor-Feld)
QR-Code-Landingpage-Auswahl im Editor
Für Artikel, die einen QR-Code auf eine Landingpage drucken sollen — ohne dass der Artikel selbst Teil der Landing-Personalisierung ist — gibt es eine zusätzliche Artikel-Einstellung. Typische Einsatzfälle: Visitenkarten, Flyer, Tischaufsteller, die nur auf eine bestehende Landingpage verweisen sollen, aber keine eigene Landingpage erzeugen.
Funktionsprinzip
Am Artikel wird ein Printess-Listenfeld (z.B. ein Select-List) als QR-Code-Feld zugeordnet. Beim Öffnen des Printess-Editors wird dieses Feld dynamisch mit allen aktiven Landing- und Tab-URLs des Shop-Benutzers befüllt. Der Endkunde sieht im Editor ein Dropdown, wählt eine Adresse, und diese URL wird 1:1 als Wert des Listenfelds gesetzt und vom Printess-Template als QR-Code gerendert.
Unterschied zu den anderen Artikel-Settings
Aspekt | Prefix / Slug / SubSlug / Header-Medium | QR-Code-Listenfeld |
|---|---|---|
Feldtyp | Freitext (Textbox, Textfeld) | Listenfeld (Select-List o.ä.) |
Filter im Picker | Nur Freitext-Felder aktiv | Nur Listenfelder aktiv, Freitext disabled mit Suffix „Freitext – nicht kompatibel" |
Auslöser für Release-Pipeline | Ja — löst Slug-Rename / Tab-Create / Tagged-PDF aus | Nein — reiner Dropdown-Seed, keine Release-Auswertung |
Ziel-Feld im Editor | Wird vom Kunden frei editiert / als URL angezeigt | Wird vom Kunden aus fester Liste gewählt |
Listenaufbau
Die Liste wird pro Editor-Open-Aufruf neu gebaut:
Lazy-Create: Hat der Shop-Benutzer noch keine Landingpage, wird eine Default-Landingpage inkl. URL-Prefix angelegt — gleicher Pfad wie beim regulären Editor-Open.
Filter: Nur aktive Landingpages und aktive Tabs.
Sortierung: Pro Landingpage zuerst die Hauptseite, dann die Tabs in ihrer festgelegten Reihenfolge. Bei mehreren Landingpages werden diese in der Reihenfolge aus der Benutzer-Verwaltung angeordnet.
Label: Pro Landingpage der Anzeigename (Fallback auf Slug). Pro Tab: „<Landing-Name> – <Tab-Name>".
Wert: Die vollständige öffentliche URL der Landingpage bzw. des Tabs.
Default-Wert im Editor
Der erste Listeneintrag (= Hauptseite der ersten aktiven Landingpage) wird zusätzlich als Vorbelegung gesetzt, sodass das Dropdown beim ersten Editor-Aufruf nicht leer erscheint. Beim Re-Editieren einer bereits personalisierten Bestellung wird der vorher gewählte Wert vom Kunden beibehalten.
Keine Release-Integration
Der Release-Handler greift auf das gewählte Listenfeld nicht zu. Es wird nicht protokolliert, welche URL der Kunde als QR-Code bestellt hat, und es werden keine Slug- oder Tab-Historien-Einträge erzeugt. Die gedruckte URL ist einzig aus dem QR-Code des gerenderten Druckstücks ablesbar.
Sicherheit
Maßnahme | Schutz vor |
|---|---|
Kein Root-Endpunkt | Enumeration der Benutzer über reines Prefix-Probieren |
ULID als Standard-Prefix | Erratbarkeit der Benutzer-IDs |
| Indexierung durch Suchmaschinen |
| Crawling der Landingpages |
Sessionless Route | Kein Cookie, kein Session-File bei anonymen Aufrufen |
Rate Limiting auf | Brute-Force-Enumeration und Scraping |
Eindeutige öffentliche IDs pro Mandant/Shop | Doppelte Prefixe innerhalb desselben Shops |
Delete-Schutz für Tabs mit aktiver Bestellpositions-Verknüpfung | Versehentliches Löschen von Tabs, die noch mit einer aktiven Bestellposition verknüpft sind |
Inaktive Tabs werden auf der öffentlichen Landingpage nicht angezeigt | Versehentliche Sichtbarkeit deaktivierter Inhalte |
HTTPS-Pflicht + Block von privaten/internen Netzen + MIME-Whitelist bei Header-Medien | SSRF über externe Header-Medien-URLs, Einbettung falsch deklarierter Content-Types |
Glossar
Begriff | Bedeutung |
|---|---|
Shop-Administrator | Mitarbeiter im Backend, der das Modul für einen Shop aktiviert und konfiguriert. |
Shop-Benutzer | Endkunde des Shops, der seine eigenen Landingpages über die Shop-Oberfläche pflegt. |
Prefix | Öffentliche Benutzer-ID, erster Pfadteil nach |
Slug | Vom Shop-Benutzer wählbarer Name der Landingpage. |
Sub-Slug / Tab | Optionaler Unter-Tab innerhalb einer Landingpage (z.B. „Mittag", „Getränke"). |
ULID | „Universally Unique Lexicographically Sortable Identifier" – 26-stellige, nicht erratbare ID. |
Release-Pipeline | Automatische Verarbeitung beim Bestellabschluss: Slug-Rename, Tab-Erstellung, Nachbestellungs-Handling, Header-Medium auflösen, Tagged-PDF-Erzeugung. |
Tab↔Bestellposition-Verknüpfung | 1:1-Zuordnung zwischen einem Tab und einer Bestellposition. Ermöglicht Delete-Schutz und Order-Badge in der Shop-Verwaltung. |
Tagged-PDF | Screen-Version-PDF mit erhaltener Tag-Struktur (Überschriften-Hierarchie). Wird ohne Ghostscript-Optimierung gespeichert, damit die Kapitelstruktur auf der Landingpage ausgelesen werden kann. |
Pending Replacement | Zustand, bei dem ein neuer Tab mit Kollisions-Suffix (z.B. |
DppApp | JavaScript-UI-Shell der öffentlichen Landingpage. Steuert Banner-Dismiss, Back-to-Top, Info-Section, Sidebar-Backdrop und Tab-URL-Sync. |
PdfViewer | JavaScript-Komponente für das PDF-Rendering inklusive Kapitelnavigation aus den PDF-Tags und Textsuche mit Highlight-Overlays. |
Landing Title | Optionaler sprechender Anzeigename einer Landingpage (z.B. „Pizzeria Venezia"). Unabhängig vom URL-Slug, beliebige Zeichen erlaubt. Wird in HTML |
Cache-Buster |
|
Header-Medium / Hero-Banner | Bild oder Video oberhalb des PDF-Viewers auf der öffentlichen Landingpage. Pro Tab aufgelöst, dreistufiger Fallback Shop → Artikel → Printess-Return. |
QR-Code-Listenfeld | Printess-Listenfeld-Typ, das am Artikel zugeordnet wird. Im Editor automatisch mit allen Landing- und Tab-URLs des Shop-Benutzers gefüllt. Unabhängig von der DPP-Personalisierungs-Pipeline — reine Druck-Auswahl. |