Im Druckdialog lässt sich das Handbuch auch als PDF speichern. Jedes Kapitel beginnt auf einer neuen Seite.

Handbuch für Template-Programmierer

camindo CMS liefert Inhalte mittels Templates aus, beispielsweise HTML auf einer Website. CMS-Befehle bereiten Inhalte auf und fügen diese dynamisch im Template ein. Beispiel für ein stark vereinfachtes Seitentemplate:

<html>
<body>
<h1>(CMS:TEXT title)</h1>
<div>(CMS:TEXT text)</div>
<div>(CMS:MEDIA field=bild_klein attr[id]=bild1)</div>
<div>erstellt: (CMS:DATE created_at format="dd.MM.y")</div>
</body>
</html>

CMS-Befehle dienen dabei nicht nur als Platzhalter. Sie stellen komplexe gekapselte Funktionalitäten dar, die mittels Parametern angepasst und miteinander kombiniert werden können. Das spart viel Entwicklungszeit. Zudem sind sie erprobt und gewährleisten Stabilität.

Beispiele dafür, was CMS-Befehle leisten:

  • Inhalte bestimmter Kriterien aus der Datenbank ermitteln
  • Inhalte aufbereiten
  • Benutzereingaben prüfen, speichern oder versenden
  • Registrierung von Websitebesuchern, An- und Abmeldung
  • weitere Templates einbinden

Ausgabeformate: Neben Websites mit HTML-Seiten sind weitere Ausgabemedien und Publikationswege möglich. Inhalte können etwa als XML-Dateien, PDF-Dateien, E-Mails… formatiert werden und über eine URL zum Abruf bereitgestellt, als Dateien ins Dateisystem geschrieben oder über ein Netzwerk geschoben werden.

Template-Satz: Nicht jeder muss das Rad neu erfinden. Inhaltsbereiche (ihre Konfiguration inkl. Eingabefeldern, auf Wunsch mit Inhalten) und die zugehörigen Templates können im XML-Format exportiert werden. Beim Import in eine andere camindo-Installation bleiben die Beziehungen zwischen Bereichen und Templates bestehen. Der komplette Anwendungsteil ist sofort einsatzbereit. Das ermöglicht die einfache Integration fertiger Bausätze (Forum, Newsfeed, ...) oder auch den Export selbst entwickelter Anwendungen für die Wiederverwendung in späteren Projekten.

Inhalt

  1. Grundlagen – wie eine Seite entsteht, Adressen, Syntax, Listen, Verknüpfungen, das Konzept „jeder Datensatz hat eine Seite“, Filter, Mehrsprachigkeit, Sicherheits-Header (CSS/JS/JSON ausliefern, Einbetten in Partnerwebsites), zeitgesteuerte Templates, Templates im Dateisystem
  2. Template-Sätze – Funktionen kapseln, portabel halten, weitergeben und anpassen
  3. Trigger-Templates – PHP-Methoden für die Ereignisse der Datensätze eines Bereichs (Speichern, Löschen …) und für die Aktionen der Lösch-Policies
  4. Befehlsreferenz – eine Seite je Befehl (unten)

Wie Templates im Backend organisiert und den Inhaltsbereichen zugewiesen werden, beschreibt das Handbuch für Administratoren.

Befehlsreferenz

(CMS:BEFEHL wert parameter=wert parameter[schlüssel]="wert" …)

Befehlsname und Parameternamen sind unabhängig von Groß- und Kleinschreibung. Der erste Wert ohne = gilt als Hauptparameter des Befehls ((CMS:TEXT titel) = (CMS:TEXT field=titel)). Werte in Anführungszeichen dürfen PHP-Ausdrücke enthalten, so wie sie in einem PHP-String üblich sind ("{$set['x']}").

Ausgabeparameter sind bei den meisten Befehlen dieselben:
ifempty (Ersatz bei leerem Wert),
instead (Ersatz bei nicht leerem Wert),
prefix / suffix (nur bei nicht leerem Wert),
set (Ergebnis wird in die Variable $set[…] geschrieben, statt auf die Seite),
id (anderer Datensatz als Quelle),
replace (Teiltexte ersetzen) bei TEXT, DATE, DATETIME, TIME und NUMBER.

Ausgabe

Befehl
CMS:DATE Datum ausgeben
CMS:DATETIME Datum und Uhrzeit ausgeben
CMS:EMAIL E-Mail versenden, Formular-Pflichtfelder prüfen
CMS:MEDIA Bild oder Mediendatei ausgeben
CMS:MESSAGE Fehlermeldungen ausgeben
CMS:NUMBER Numerischen Inhalt ausgeben
CMS:RAW Inhalt unformatiert ausgeben
CMS:SNIPPET Snippet (Textbaustein) in der Sprache des Aufrufs ausgeben
CMS:TEXT Textinhalt ausgeben
CMS:TIME Uhrzeit ausgeben
CMS:URL URL eines Datensatzes erzeugen
CMS:XML XML-Deklaration ausgeben
CMS:ZIP Dateien mehrerer Datensätze als ZIP-Archiv herunterladen

Verknüpfungen

Befehl
CMS:RELATION Hauptfeld verknüpfter Datensätze ausgeben (nur für interne Vorschau!)

Abfragen

Befehl
CMS:SELECT Datensätze abfragen
CMS:SELECT_LANGVERSION Sprachen der Instanz als Ergebnismenge
CMS:RESULT_START Beginn der Ergebnisschleife
CMS:RESULT_END Ende der Ergebnisschleife

Navigation

Befehl
CMS:JUMP_LIST Blätter-Navigation erzeugen
CMS:JUMP_NEXT Weiterblättern-Verweis erzeugen
CMS:JUMP_PREV Zurückblättern-Verweis erzeugen

Steuerung

Befehl
CMS:CONTINUE Nach einem Success-Template weitermachen
CMS:CSP Script-Policy (CSP) der Seite anpassen
CMS:HEADER HTTP-Header senden
CMS:INCLUDE Template einbinden, wahlweise mit Ausgabe-Cache
CMS:NONCE Nonce für per PHP erzeugte Scripts
CMS:SAVEDATA Datensatz in einer PHP-Variable ablegen

Frontend-Editing

Befehl
CMS:FED Bearbeiten-Verweis bei Frontend-Editing
CMS:FED_CODE HTML nur bei Frontend-Editing ausgeben
CMS:FED_SCRIPT Script für die Bearbeiten-Verweise (einmal je Seite)

Community

Befehl
CMS:USER_2FA Zweiten Faktor verwalten
CMS:USER_AUTH Session des Frontend-Users prüfen
CMS:USER_BASKET Warenkorb bearbeiten
CMS:USER_DELETE Datensatz des Users löschen
CMS:USER_INPUT Datensatz aus Usereingaben speichern
CMS:USER_LOGIN Frontend-User anmelden
CMS:USER_LOGIN_CODE Anmeldung mit zweitem Faktor abschließen
CMS:USER_LOGOUT Frontend-User abmelden
CMS:USER_PASSWORD_RULES Passwort-Regeln für die Anzeige im Formular
CMS:USER_REGISTER Frontend-User registrieren (Profil-Datensatz anlegen)
CMS:USER_REGISTER_CONFIRM Registrierung freischalten
CMS:USER_PROTECT Template für nicht angemeldete Besucher sperren
CMS:USER_PW Passwort-Reset vorbereiten
CMS:USER_TOKEN Sicherheits-Token für Formulare

Die Community-Funktionen richtet der Administrator ein (siehe Handbuch für Administratoren).

Zusatzmodul PDF

Das Zusatzmodul PDF (camindo/module-pdf) ergänzt die Befehle (CMS:PDF_…), mit denen Templates PDF-Dokumente erzeugen – Seite für Seite, auf Wunsch auf einer PDF-Vorlage wie einem Briefbogen, mit Texten, HTML, Bildern, Linien und Tabellen:

(CMS:PDF_INIT margins=25,20,30,20)
(CMS:PDF_PAGE briefbogen.pdf)
(CMS:PDF_FONT helvetica style=B size=16)
(CMS:PDF_TEXT "Rechnung {$cms['nummer']}" y=50)
(CMS:PDF_PRINT rechnung.pdf)

Die vollständige Befehlsliste enthält die Dokumentation des Moduls.

1 Grundlagen der Template-Programmierung

Was ein Template ist

Ein Template ist ein Textdokument – meist HTML –, in dem CMS-Befehle stehen. Die Befehle fragen Datensätze ab, setzen Inhalte ein, binden andere Templates ein, werten Formulare aus und steuern den Ablauf. Dazwischen darf PHP-Code stehen.

(CMS:INCLUDE header)
<h1>(CMS:TEXT title)</h1>
<div>(CMS:TEXT text)</div>
<div>(CMS:MEDIA field=bild_klein attr[id]=bild1)</div>
<div>erstellt: (CMS:DATE created_at format="dd.MM.y")</div>
(CMS:INCLUDE footer)

Beim Speichern übersetzt camindo CMS das Template in PHP. Zur Laufzeit wird nur noch dieses PHP ausgeführt – es braucht keine Template-Engine, die den Text bei jedem Aufruf neu auswertet.

Wie eine Seite entsteht

Ruft ein Besucher eine Adresse auf, ermittelt camindo CMS:

  1. die Instanz – aus der Domain,
  2. die Sprache – aus dem Sprachkürzel der Adresse, sonst aus der Spracheinstellung des Browsers, sonst die Hauptsprache,
  3. den Datensatz – aus dem Slug oder der Nummer (record_id) in der Adresse; ohne Angabe den Start-Datensatz der Instanz,
  4. das Template – aus der Adresse, aus dem Datensatz oder das Standard-Template seines Inhaltsbereichs.

Die Inhalte eines aufgerufenen Datensatzes stehen im Template zur Verfügung. Standardmäßig beziehen sich alle Ausgabe-Befehle auf diesen Datensatz – vgl. oben (CMS:TEXT title). Führt das Template eine Abfrage mittels CMS:SELECT durch, stehen die Inhalte der ermittelten Datensätze in gleicher Weise im Ergebnis-Template zur Verfügung; siehe unten Listen, Select, Ergebnisse.

Adressen

[/{sprache}]/{slug oder nummer}[,{template}]
Adresse Bedeutung
/ Startseite der Instanz
/betriebsfeier-2026 Datensatz mit diesem Slug, Standard-Template
/termine/betriebsfeier-2026 Datensatz mit dem Slug betriebsfeier-2026 im Bereich mit dem Slug-Prefix termine
/termine Übersicht des Bereichs mit dem Slug-Prefix termine – sein Übersichts-Template, ohne Datensatz
/1758 Datensatz Nummer 1758
/en/summer-party-2026 englische Sprachversion
/betriebsfeier-2026,druck Datensatz mit dem Template druck – nur wenn es für den Bereich als URL-Override freigegeben ist
/0,suche Template suche ohne Datensatz – nur wenn es als „Standalone“ markiert ist

Reine Ziffernfolgen sind immer Datensatznummern, alles andere ist ein Slug. Ist der erste Teil der Adresse der Slug-Prefix eines Inhaltsbereichs, wird der Rest nur in diesem Bereich gesucht; sonst der ganze Pfad in den Bereichen ohne Präfix. Das Sprachkürzel steht in der Adresse, sobald die Instanz mehr als eine Sprache führt. Adressen erzeugen Sie mit CMS:URL – nie von Hand, dann stimmen Sprache, Slug und Domain immer.

Besteht die Adresse nur aus dem Sprachkürzel (/en, ohne Schrägstrich am Ende), leitet camindo CMS auf die Startseite der Sprache um (/en/ oder /en_US/ – je nachdem, wie diese eingerichtet ist). Führt der Auftritt nur eine Sprache und hat ein Datensatz genau diesen Slug, wird stattdessen der Datensatz gezeigt. Adressen, unter denen es nichts gibt – unbekannter Datensatz, unbekannte Sprache, nicht freigegebenes Template –, antworten mit dem Status 404.

Syntax der CMS-Befehle

(CMS:BEFEHL wert parameter=wert parameter[schlüssel]="wert" …)
  • Befehlsnamen und Parameternamen sind unabhängig von Groß- und Kleinschreibung.
  • Parameter sind durch Leerzeichen getrennt; ein Befehl darf über mehrere Zeilen gehen.
  • Die Reihenfolge, in der Parameter verarbeitet werden, bestimmt jeder Befehl selbst, nicht die Reihenfolge, in der die Parameter geschrieben werden.
  • Der erste Wert ohne = gilt als Hauptparameter des Befehls: (CMS:TEXT titel) ist dasselbe wie (CMS:TEXT field=titel).
  • Werte, die Leerzeichen enthalten, stehen in Anführungszeichen, einfache oder doppelte: (CMS:TEXT field="title") entspricht (CMS:TEXT field='title').
  • Unterschlüssel in eckigen Klammern: filter[area]=artikel. Leere Klammern hängen an eine Liste an: sort[]=datum sort[]=titel. Wird derselbe Unterschlüssel mehrfach angegeben, entsteht ebenfalls eine Liste ([] ist im Gegensatz zur PHP-Syntax für Arrays optional).
  • Statt = darf ein Vergleichsoperator zwischen Parameter und Wert stehen – ==, !=, !, !!, <, >, <=, >=, <<, >>: filter[preis] >= 100 ist dasselbe wie filter[preis]=">=100". Der Wert gilt dann unverändert, ein Operator an seinem Anfang wird nicht mehr ausgewertet: filter[code] < ">2" vergleicht mit dem Text „>2“. Bei = bleibt es wie gehabt – filter[code] = ">2" bedeutet „größer als 2“ (siehe Filter-Syntax).
  • Der Zeilenumbruch hinter einem CMS-Befehl wird geschluckt, analog zu einem <?php … ?>-Block.
  • Befehlsparameter in doppelten Anführungszeichen dürfen PHP-Variablen enthalten: attr[alt]="{$myvar}" (Parameter ohne Anführungszeichen werden intern wie Parameter mit doppelten Anführungszeichen behandelt).

Fehler – ein unbekannter Befehl, ein unbekannter Parameter, eine fehlende Klammer – verhindern das Speichern nicht. Die Detailansicht des Templates zeigt sie mit Zeilennummer an. Sie erscheinen auch auf der Startseite im Widget „Template-Meldungen“ – für den Fall, dass Templates über das Dateisystem bearbeitet werden.

CMS-Befehl und PHP-Code kombinieren

$set <?php … ?>-Blöcke dürfen frei im Template stehen, CMS-Befehle innerhalb von PHP-Blöcken führen allerdings zu Fehlern! Um Inhalte aus CMS-Befehlen in PHP-Blöcken zu verwenden, nutzen Sie den Parameter set (etwa set = myvar) und greifen den Inhalt im PHP-Block aus $set ($set['myvar']) auf.

$cms Inhalte aus dem aufgerufenen Datensatz stehen in $cms; ebenso Inhalte aus Datensätzen, die mittels CMS:SELECT ermittelt werden.

Für die Ausgabe sollten immer die CMS-Ausgabebefehle (etwa CMS:TEXT) verwendet werden. Diese Befehle kümmern sich um korrekte Formatierung, ein <?php echo $cms[…] ?> um nichts davon. Verwenden Sie $cms nur dort, wo ein Befehl einen Wert als Parameter braucht, etwa filter[kategorie]="{$cms['record_id']}".

Gemeinsame Parameter der Ausgabebefehle

Parameter Wirkung
ifempty Ersatztext, wenn der Wert leer ist
instead Ersatztext, wenn der Wert nicht leer ist
prefix, suffix Text davor bzw. dahinter – nur wenn der Wert nicht leer ist
set den Wert nicht ausgeben, sondern in $set[…] ablegen
id aus einem anderen Datensatz lesen (Nummer oder Slug), z. B. (CMS:TEXT titel id=impressum) – nur veröffentlichte Datensätze, wie in der URL
replace Teiltexte ersetzen (replace="alt?neu") – bei TEXT, NUMBER, DATE, DATETIME, TIME
(CMS:TEXT untertitel prefix="<h2>" suffix="</h2>")
(CMS:NUMBER preis format="%.2f" decimal="," suffix=" €" ifempty="auf Anfrage")

Werte in Attributen und Javascript

camindo CMS entfernt aus allen Datensatz-Inhalten Script, Event-Handler (onclick …), iframe, object, embed und javascript:-Links – beim Speichern und noch einmal bei der Ausgabe. HTML-Formatierung bleibt, deshalb gibt CMS:TEXT Inhalte unmaskiert aus.

Setzen Sie einen Wert in ein HTML-Attribut ein, maskieren Sie ihn mit specialchars=true – ein Anführungszeichen im Inhalt beendete sonst das Attribut:

<img src="…" alt="(CMS:TEXT title specialchars=true)">
<a href="(CMS:URL)" title="(CMS:TEXT teaser specialchars=true striptags=true lbrconvert=false)">…</a>

Dasselbe gilt für Formulareingaben, die Sie nach dem Absenden wieder ins Feld setzen:

<input name="data[nickname]" value="(CMS:TEXT useval="{$_POST['data']['nickname']}" specialchars=true)">

Befehle, die selbst maskieren: Einige Befehle maskieren ihre Ausgabe bereits mit htmlspecialchars. Dort entfällt specialchars – ein bereits maskierter Wert würde doppelt maskiert, aus & würde sichtbar &amp;:

Befehl maskiert
CMS:MEDIA alle Attribute des <img>-Tags: src, alt, width, height und attr[…] – nicht die Einzelwerte über subfield
CMS:USER_TOKEN den Wert des versteckten Feldes
CMS:USER_PASSWORD_RULES die gesamte Ausgabe (JSON für ein Attribut)
CMS:XML version und encoding
CMS:JUMP_LIST, CMS:JUMP_PREV, CMS:JUMP_NEXT die URL in {href}

Was Sie einem dieser Befehle übergeben, muss deshalb unmaskierter Text sein – ohne HTML-Tags und Entities:

(CMS:TEXT title striptags=true ampconvert=false lbrconvert=false set=title)
(CMS:MEDIA bild attr[alt]="{$set['title']}")

Nie maskiert werden prefix, suffix, ifempty und instead – sie stammen aus dem Template und dürfen HTML enthalten. specialchars kennen nur CMS:TEXT und CMS:SNIPPET. Werte anderer Befehle, etwa CMS:MEDIA … subfield=filename, CMS:RELATION oder CMS:RAW, legen Sie für ein Attribut mit set ab und geben sie über CMS:TEXT maskiert aus:

(CMS:MEDIA dokument subfield=filename set=dateiname)
<a href="(CMS:MEDIA dokument subfield=path)" title="(CMS:TEXT useval="{$set['dateiname']}" specialchars=true lbrconvert=false)">…</a>

CMS:URL und CMS:MEDIA … subfield=path erzeugen nur URL-taugliche Zeichen, ebenso CMS:TEXT … urlencode=true – sie können ohne Maskierung in href und src stehen.

Braucht Javascript einen Wert, geben Sie ihn als data--Attribut aus und lesen ihn dort – so steht nie Inhalt im Script-Code selbst:

<div id="produkt" data-title="(CMS:TEXT title specialchars=true)"></div>
<script>var title = document.getElementById('produkt').dataset.title;</script>

Listen: SELECT und die Ergebnisschleife

Listen entstehen immer aus zwei Templates: Das aufrufende Template fragt mit CMS:SELECT ab und nennt ein Listentemplate. Das Listentemplate durchläuft die Treffer zwischen CMS:RESULT_START und CMS:RESULT_END.

Aufrufendes Template:

<h2>Aktuelle Meldungen</h2>
(CMS:SELECT meldungen_liste
  filter[area]=meldungen
  sort=datum
  order=desc
  limit=10
  noresult="<p>Keine Meldungen.</p>"
)

Listentemplate meldungen_liste:

<ul>
(CMS:RESULT_START)
  <li><a href="(CMS:URL)">(CMS:TEXT titel)</a> – (CMS:DATE datum)</li>
(CMS:RESULT_END)
</ul>

Innerhalb der Schleife ist die jeweilige Zeile der laufende Datensatz. Ein Listentemplate darf selbst wieder CMS:SELECT enthalten – etwa die Schlagwörter jedes Artikels.

Blätterfunktion: count=true zählt alle Treffer; CMS:JUMP_LIST, CMS:JUMP_PREV und CMS:JUMP_NEXT erzeugen daraus die Navigation.

Veröffentlichung: Templates finden nur veröffentlichte Datensätze – freigeschaltet, Veröffentlichungsdatum erreicht, Ablaufdatum nicht erreicht. Die Parameter future, archive lockern das je Abfrage, etwa für ein Archiv oder eine Terminvorschau.

Der Parameter state ist ausschließlich für Backend-interne Ansichten vorgesehen.

Bereiche ansprechen

Inhaltsbereiche werden über ihren Namen oder über ihre Nummer angesprochen (area_id): filter[area]=meldungen, filter[area]=12 oder filter[area_id]=12. Mehrere Bereiche trennen Sie durch Komma. Weil Feldnamen systemweit einheitlich sind, lassen sich mehrere Bereiche gemeinsam filtern und sortieren:

(CMS:SELECT zeitleiste
  filter[area]=meldungen,termine,blog
  filter[datum] >= 2026-01-01
  sort=datum
  order=desc
)

sort=area sortiert nach Bereich; _default steht in Filtern und Sortierung für das Hauptfeld jedes Bereichs, egal wie es dort heißt.

Übersichtsseite eines Bereichs

Die Adresse aus dem Slug-Prefix allein (/termine) ruft das Übersichts-Template des Bereichs auf – ohne Datensatz. An dessen Stelle stehen die Angaben des Bereichs:

Wert Inhalt
area_id Nummer des Bereichs
area Name des Bereichs
area_label Beschriftung des Bereichs (Snippets in der Sprache des Aufrufs)
area_type, area_icon Typ und Icon des Bereichs
instance_id, language_id Instanz des Bereichs und Sprache des Aufrufs
slug_prefix Slug-Prefix in der Sprache des Aufrufs
record_id immer 0 – es gibt keinen laufenden Datensatz
overview true

So listet ein Übersichts-Template die Datensätze seines Bereichs, ohne dessen Namen fest einzutragen – und lässt sich für mehrere Bereiche verwenden:

<h1>(CMS:TEXT area_label specialchars=true)</h1>
(CMS:SELECT termin_liste
  filter[area]="{$cms['area_id']}"
  sort=datum order=asc
)

Verknüpfte Datensätze

Mit relation fragt CMS:SELECT die Datensätze ab, die mit dem laufenden (oder mit src_id genannten) Datensatz über ein Verknüpfungsfeld verbunden sind.

(CMS:SELECT galerie_liste
  relation=see_also
  sort="created_at desc"
)

Mit sort="_custom asc" erscheinen verknüpfte Datensätze in der Reihenfolge, die die Redaktion im Lookup-Feld (mittels der Pfeil-Schaltflächen) festgelegt hat:

(CMS:SELECT galerie_liste
  relation=bilder
  sort="_custom asc"
)

Nach Feldern verknüpfter Datensätze sortieren und filtern Sie mit der Punkt-Schreibweise: sort=autor.nachname.

Steht kein laufender Datensatz zur Verfügung und ist src_id nicht angegeben oder ungültig, findet die Suche mit dem Parameter relation nichts.

Das Konzept: Jeder Datensatz hat eine Seite

In camindo CMS hat jeder Datensatz eine eigene Seite im Frontend – nicht nur Artikel und Produkte, sondern auch jede Kategorie, jedes Schlagwort, jedes Material. Dafür braucht eine Website keine Parameter wie ?cat=12 oder ?tag=eiche:

  1. Verlinken: Listen verlinken jeden Datensatz mit CMS:URL – die Kategorie ebenso wie das Produkt.
  2. Standard-Template je Bereich: Jeder Inhaltsbereich bekommt ein Standard-Template – eine Kategorie-Seite, eine Schlagwort-Seite, eine Produkt-Seite. Mit einem Slug-Prefix erhält jede Art von Seite ihren eigenen Adressraum (/de/kategorie/wohnen, /de/schlagwort/eiche).
  3. Verknüpfte Inhalte listen: Auf der Seite steht der Datensatz in $cms; seine verknüpften Datensätze fragt das Template direkt über ihn ab.

Kategorie-Seite (Standard-Template des Bereichs kategorien):

<h1>(CMS:TEXT titel)</h1>
(CMS:TEXT text prefix="<p>" suffix="</p>")
(CMS:SELECT produkt_liste
  relation=rel_produkte
  sort=position
  order=asc
)

Listentemplate produkt_liste – die Produkte verlinken wieder auf ihre eigene Seite:

<ul>
(CMS:RESULT_START)
  <li><a href="(CMS:URL)">(CMS:TEXT titel)</a></li>
(CMS:RESULT_END)
</ul>

Die Navigation ist ebenso einfach – eine Liste aller Kategorien mit Links:

(CMS:RESULT_START)
<a href="(CMS:URL)">(CMS:TEXT titel)</a>
(CMS:RESULT_END)

Welche Abfrage die verknüpften Datensätze liefert, hängt davon ab, von welcher Seite die Verknüpfung ausgeht:

Verknüpfung Abfrage auf der Seite des Datensatzes
geht vom Datensatz aus (sein eigenes Verknüpfungsfeld) (CMS:SELECT liste relation=feldname)
Beziehungsfeld (relation) eines anderen Bereichs zeigt auf ihn (CMS:SELECT liste relation=rel_bereichsname) – über das Gegenfeld
Verweisfeld (pointer) eines anderen Bereichs zeigt auf ihn (CMS:SELECT liste filter[area]=bereichsname filter[feldname]="{$cms['record_id']}")

Beziehungsfelder (relation, relation1) sind in beide Richtungen verknüpft: Beim Anlegen erzeugt camindo CMS im Zielbereich ein Gegenfeld namens rel_{bereichsname} (etwa rel_produkte), das die Verknüpfung von der anderen Seite führt. Die Kategorie-Seite fragt ihre Produkte deshalb mit relation=rel_produkte ab, die Produktseite ihre Kategorien mit relation=kategorien.

Verweisfelder (pointer, pointer1) sind gerichtet – sie können dafür auf mehrere Bereiche zeigen. Der Zieldatensatz kennt seine Verweise nicht; das Gegenstück ist ein Filter auf das Verweisfeld mit der Nummer des laufenden Datensatzes:

(CMS:SELECT produkt_liste
  filter[area]=produkte
  filter[kategorie]="{$cms['record_id']}"
)

Achtung: Die Nummer des laufenden Datensatzes steht in $cms['record_id'] – einen Schlüssel id gibt es nicht. Ein leerer Filterwert hebt den Filter auf: filter[kategorie]="{$cms['id']}" findet deshalb nicht keine, sondern alle Produkte.

Suchen und freie Filterkombinationen bleiben Parameter (?q=…), denn sie sind keine Datensätze. Alles, was ein Datensatz ist, hat dagegen seine eigene Adresse – und damit auch Seitentitel, Beschreibung und Frontend-Editing wie jede andere Seite.

Filter-Syntax

Die Werte in filter[…] folgen derselben Syntax wie die Suchmaske im Backend:

Wert Bedeutung
Berlin gleich
Berlin,Hamburg eines von beiden (mit filter_mode[feld]=AND: beide)
Hen*, *mann, *berg* Platzhalter
!Berlin, !*mann ungleich
==Stern* exakt gleich, * ohne Platzhalterwirkung
>100, >=100, <100, <=100 Vergleiche – auch für Datumsangaben
>2 AND <4, Hen* OR Her* Bedingungen im selben Feld verknüpfen
2026, 2026-06 Datum: das ganze Jahr bzw. der ganze Monat
now, today, now-2h, today+7d Datum relativ zum Aufruf, in der Zeitzone des Benutzers bzw. der Sprache wie ein getipptes Datum; today ohne Stunden-Versatz ist der ganze Tag. Versatz in s, m, h, d
utc_now, utc_today, utc_now-15m dasselbe in UTC – für Vergleiche mit Zeitpunkten, die unabhängig vom Besucher gelten (Sperrfristen, Zeitfenster eines Scheduler-Laufs)
(empty), !(empty) ohne eigenen Wert (leer oder nicht übersetzt) bzw. mit

Der Operator darf auch zwischen Schlüssel und Wert stehen – praktisch, wenn der Wert aus einer Variablen kommt:

(CMS:SELECT sperrfrist_mail
  filter[area] = pressemeldungen
  filter[online_at] > {$set['last_run']}
  filter[online_at] <= {$set['this_run']}
  filter_mode[online_at] = AND
  future = true
)

entspricht filter[online_at]=">{$set['last_run']}" und filter[online_at]="<={$set['this_run']}". Mit einem solchen Operator ist der Wert selbst frei von Operatoren: Beginnt der Inhalt der Variablen mit <, > oder !, wird das als Text verglichen.

Ein Datumswert, der sich nicht auswerten lässt (etwa ein Unix-Zeitstempel wie $_SERVER['REQUEST_TIME']), wird ohne Meldung übergangen – die Liste zeigt dann alle Datensätze, als stünde der Filter nicht da. Für Analysen in Ihrer Entwicklungsumgebung hilft debug=true: Die erzeugte SQL-Abfrage wird angezeigt.

Textvergleiche nach Sprache: Text- und Zeichenfelder (auch _default) werden nach den Regeln der Sprachversion verglichen und sortiert, in der gesucht wird – abgeleitet aus dem Sprachkürzel, einzustellen ist nichts. Immer gilt: keine Unterscheidung von Groß-/Kleinschreibung und Akzenten (cafe findet „Café“), ß gleich ss. Dazu kommen die Regeln einzelner Sprachen:

Sprache Regel Beispiel
Deutsch ä = ae, ö = oe, ü = ue (Telefonbuch-Regel) Mue* findet „Müller“, Mul* nicht
Schwedisch å, ä, ö sind eigene Buchstaben o findet kein ö; ö sortiert hinter z
Dänisch, Norwegisch æ, ø, å sind eigene Buchstaben, aa = å Aa* findet „Ålesund“
Türkisch i/İ und ı/I sind verschieden

Eigene Regeln haben außerdem Bulgarisch, Esperanto, Estnisch, Galicisch, Isländisch, Kroatisch und Bosnisch, Lettisch, Litauisch, Polnisch, Rumänisch, Russisch, Serbisch (lateinisch), Slowakisch, Slowenisch, Spanisch, Tschechisch, Ungarisch und Vietnamesisch. Alle übrigen Sprachen – auch Englisch – folgen den allgemeinen.

Das gilt für =, Platzhalter und Vergleiche wie > gleichermaßen, ebenso für Volltextfelder (Mueller findet „Müller“, Muller nicht). Im Backend ist es die Sprache, die für die Instanz des durchsuchten Bereichs eingestellt ist; führen die Instanzen einer Suche auf verschiedene Regeln, gelten nur die allgemeinen.

Verschiedene Filter werden immer mit UND verknüpft. Volltextfelder verstehen +muss -darfnicht; verschlüsselte Felder nur den exakten Wert.

In Bereichen mit Baumstruktur liefert filter[parent_id]=42 alle Nachkommen des Datensatzes 42, zusammen mit filter[depth]=1 nur die direkten Kinder – die Grundlage für Navigationen.

Templates einbinden und organisieren

CMS:INCLUDE bindet ein anderes Template ein – etwa Kopf und Fuß der Seite:

(CMS:INCLUDE layout.kopf)
<main>…</main>
(CMS:INCLUDE layout.fuss)

Template-Namen müssen nur innerhalb ihres Template-Bereichs eindeutig sein. Gibt es einen Namen in mehreren Bereichen, sprechen Sie ihn als bereichsname.templatename an. Einen Namen, der nicht eindeutig ist, meldet die Seite als Fehler.

Übliche Aufteilung:

Art Aufgabe
Seiten-Templates Standard-Templates der Inhaltsbereiche: geben einen Datensatz als ganze Seite aus
Listentemplates durchlaufen die Treffer eines CMS:SELECT
Bausteine Kopf, Fuß, Navigation, Teaser – per CMS:INCLUDE eingebunden
Standalone-Templates Seiten ohne Datensatz: Suche, Sitemap, Formulare
Zeitgesteuerte Templates laufen als Aufgabe des Zeitplaners, ohne Besucher (siehe unten)
Listen-Templates (Backend) gestalten die Datensatzliste der Redaktion (siehe unten)

Caching

Aufwendige Bausteine – eine Navigation aus vielen Abfragen, eine Produktliste – kann CMS:INCLUDE mit caching zwischenspeichern: (CMS:INCLUDE layout.navigation caching=1h) erzeugt die Navigation höchstens einmal in der Stunde je Website und Sprache. Wovon die Ausgabe sonst noch abhängt, gehört in cacheparams – siehe CMS:INCLUDE.

Mehrsprachigkeit

Ein Template gibt automatisch die Sprachversion des Aufrufs aus – bei fehlender Übersetzung die Hauptsprache. Links mit CMS:URL behalten die Sprache bei; mittels Parameter lang wird explizit auf eine andere Sprachversion verlinkt. Einen Sprachumschalter bauen Sie mit CMS:SELECT_LANGVERSION.

Datums- und Zahlenformate richten sich nach der Sprachversion; die Zeitzone ebenso.

Feste Beschriftungen („Weiterlesen“, „Suchen“) pflegt die Redaktion als eigenständige Datensätze (etwa in einem Bereich namens „Sonstige Texte“) in allen Frontend-Sprachen. In diesem Bereich sollte es ein einsprachiges slug-Feld und ein mehrsprachiges text-Feld geben. Dann arbeitet CMS:TEXT als Snippet-Lieferant, vgl. CMS:TEXT mit id-Parameter.

<a href="(CMS:URL)">(CMS:TEXT id=mysnippets.readmore)</a>

Frontend-Editing

CMS:FED setzt an einen Inhalt eine Bearbeiten-Markierung, die nur Benutzer mit eingeschaltetem Frontend-Editing sehen. Ein Klick öffnet die Eingabemaske des Datensatzes im Backend. CMS:FED_CODE gibt HTML nur in diesem Fall aus – etwa ein Stylesheet für die Markierungen.

Sicherheits-Header: CSS, Javascript, JSON und Einbetten

camindo CMS schickt vor jedem Template einige HTTP-Header mit, die der Browser als Schutz umsetzt:

X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
Referrer-Policy: strict-origin-when-cross-origin

Für gewöhnliche HTML-Seiten müssen Sie nichts tun. Zwei Fälle wirken sich aber aus – und die Ursache ist nicht auf den ersten Blick zu sehen:

Templates, die CSS, Javascript oder JSON ausliefern

Ein Template wird als text/html ausgeliefert, wenn es nichts anderes sagt. Wegen nosniff nimmt der Browser dann den Inhalt nicht als Stylesheet oder Script an, auch wenn er richtig ist. Die Seite sieht ungestaltet aus, das Script läuft nicht – und die Konsole des Browsers meldet etwas wie „refused to execute script … MIME type ('text/html') is not executable“ bzw. „… not a supported stylesheet MIME type“.

Setzen Sie deshalb am Anfang jedes solchen Templates den passenden Typ – vor jeder Ausgabe, ohne Leerzeile davor:

(CMS:HEADER "Content-Type: text/css; charset=utf-8")
(CMS:HEADER "Content-Type: text/javascript; charset=utf-8")
(CMS:HEADER "Content-Type: application/json; charset=utf-8")

Ausgaben in anderen Websites einbetten

Eine Seite der Website lässt sich nur innerhalb derselben Website in einem <iframe> zeigen. Das schützt Anmeldung, Profil und Warenkorb davor, dass eine fremde Seite sie unsichtbar überlagert und Besuchern Klicks unterschiebt (Clickjacking). Bindet eine Partnerwebsite eine Seite per <iframe> ein, bleibt der Rahmen deshalb leer oder zeigt eine Fehlerseite des Browsers; die Konsole meldet „Refused to frame … because an ancestor violates … frame-ancestors“ bzw. „… X-Frame-Options to 'sameorigin'“.

Soll ein Template eingebettet werden dürfen – ein Widget, ein Terminkalender für Partner –, geben Sie das nur in diesem Template frei: X-Frame-Options ohne Wert entfernen und die erlaubten Websites in frame-ancestors nennen:

(CMS:HEADER "X-Frame-Options:")
(CMS:HEADER "Content-Security-Policy: frame-ancestors 'self' https://www.partner.example https://*.partner.example")

Beide Zeilen gehören zusammen: Ein Browser, der X-Frame-Options noch sieht, verweigert den Rahmen trotz frame-ancestors. Nennen Sie die Partner einzeln – frame-ancestors * erlaubt jeder Website das Einbetten – auch das der Seiten mit Formularen. Andere Einbindungen sind nicht betroffen: Bilder, Downloads, per Javascript geladene Daten (JSON) und Links auf Ihre Website funktionieren ohne Freigabe.

Mehr zu den Header-Zeilen: CMS:HEADER.

Templates im Dateisystem

Jedes Template liegt als Datei unter var/templates/{instance_id}/{bereichsname}/{templatename}.cms.php. Sie können es dort mit einem Code-Editor bearbeiten und mit Git verwalten. camindo CMS übernimmt Änderungen minütlich (oder sofort über „Aktualisieren“ im Modul Templates) und übersetzt das Template neu. Umbenennen sollten Sie Templates im Backend – im Dateisystem gilt Umbenennen als Löschen und Neuanlegen, und die Zuweisungen gehen verloren. Fehlermeldungen erscheinen im Widget „Template-Meldungen“.

Editor-Unterstützung

Für Visual Studio Code gibt es die Erweiterung „camindo CMS Templates“ (github.com/dirkintelligence/camindo-vscode, auch im Visual Studio Marketplace und bei Open VSX). Sie färbt in *.cms.php-Dateien die CMS-Befehle lila und die PHP-Ausdrücke darin orange – wie die Ansicht im Backend –, ergänzt nach (CMS: die Befehlsnamen und danach die Parameter des Befehls und zeigt beim Überfahren deren Beschreibung, in derselben Sprache wie das Backend.

Die Erweiterung kennt die Befehle des Kerns und der Standardmodule. Bringen Ihre Module eigene Befehle mit, schreiben Sie die Befehlsliste Ihres Projekts mit

php Core/cron/camindo.php template commands --all --out=var/vscode

und tragen das Verzeichnis in der VS-Code-Einstellung camindo.commandsFile ein.

Zeitgesteuerte Templates

Ein Template mit der Verwendung „Zeitgesteuert“ (Reiter „Zugriff“ des Templates) läuft nicht als Seite, sondern als Aufgabe des Zeitplaners (Aufgabentyp „Template“, siehe Handbuch für Administratoren). Über eine Adresse ist es nie erreichbar.

Es läuft wie ein Seitenaufruf ohne Datensatz: in der Website seines Template-Bereichs, in der Sprache, die die Aufgabe nennt – mit deren Texten, Snippets, Datumsformaten und Zeitzone. Es gibt keinen Besucher, also keine Anmeldung und keinen Warenkorb. Die Aufgabe übergibt das Zeitfenster seit dem letzten erfolgreichen Lauf:

Variable Inhalt
$set['last_run'] Beginn des Zeitfensters (ausschließlich), Zeitzone der Sprache
$set['this_run'] Ende des Zeitfensters (einschließlich), Zeitzone der Sprache
$set['last_run_utc'], $set['this_run_utc'] dasselbe in UTC

Beim ersten Lauf ist das Zeitfenster leer. Schlägt ein Lauf fehl, holt der nächste den ganzen Zeitraum nach – nichts geht verloren und nichts wird doppelt verarbeitet. Das Zeitfenster passt direkt in einen Filter; zwei Bedingungen für dasselbe Feld verknüpft filter_mode mit AND:

(CMS:SELECT sperrfrist_mail
  filter[area]=pressemeldungen
  filter[online_at] > {$set['last_run']}
  filter[online_at] <= {$set['this_run']}
  filter_mode[online_at]=AND
  future=true
)

Das Ergebnis-Template verschickt dann etwa mit CMS:EMAIL (submit=1) je Treffer eine Nachricht; CMS:URL mit absolute=true liefert die vollständige Adresse für Links. Meldet ein Befehl einen Fehler, gilt der Lauf als fehlgeschlagen. Was das Template ausgibt, speichert die Aufgabe auf Wunsch unter var/custom/ – so entsteht etwa eine Sitemap oder ein Feed als Datei.

Vorschau

Die Vorschau aus dem Backend zeigt auch unveröffentlichte Datensätze mit ihrem Template. Listen darin zeigen weiterhin nur veröffentlichte Datensätze, ebenso id=… und CMS:RELATION. In der Vorschau liefert $this->isPreview() true.

Listen-Templates für das Backend

Ein Inhaltsbereich kann ein Listen-Template haben, das nur im Backend verwendet wird: für eine zusätzliche Spalte der Datensatzliste und die ausführliche Vorschau in Lookup-Feldern. Im Template steht der Datensatz der Zeile als $cms bereit; die Ausgabebefehle CMS:TEXT, CMS:MEDIA… funktionieren wie im Frontend:

(CMS:MEDIA bild_klein width=60)
<small>(CMS:NUMBER preis format="%.2f" decimal="," suffix=" €")</small>

CMS:RELATION ist hier hilfreich, um das Hauptfeld verknüpfter Datensätze zu listen.

2 Template-Sätze

Gekapselte Funktionalität

Ein Template-Satz bündelt eine Funktion der Website: Templates, Inhaltsbereiche, ggf. Datensätze und Snippets. Beispiele:

Template-Satz Inhaltsbereiche Templates
News Meldungen, Rubriken Übersicht mit Blätterfunktion, Detailseite, Rubrikenseite, Archiv, Teaser für die Startseite, RSS-Feed
Veranstaltungen Termine, Orte Kalender, Detailseite mit Anfahrt, Terminvorschau
Mitgliederbereich Mitglieder (Profil), Beiträge (UGC) Registrierung, Bestätigung, Anmeldung, Passwort vergessen, Profil bearbeiten, geschützte Seiten
Seitenbaukasten Seiten, Textblock, Bildblock, Galerie (abhängige Bereiche) Seite, je Blocktyp ein Baustein

Ein guter Template-Satz ist direkt betriebsbereit: Importieren, Inhalte eingeben, fertig. Und er ist anpassbar an den individuellen Aufbau einer Website, ohne dass die Funktion neu entwickelt werden muss. Weitergegeben wird er per XML-Export mit der Option „Templates für Import in Fremddatenbank optimieren“ (siehe Handbuch für Administratoren).

Regeln für portable Template-Sätze

Ein Ordner, ein Template-Bereich

Legen Sie die Inhaltsbereiche und den Template-Bereich eines Satzes gemeinsam in einen Ordner. So exportiert ein Klick den ganzen Satz, und beim Import ist klar, was zusammengehört.

Namen und Ids direkt im Befehlsparameter

Templates, Inhaltsbereiche, Datensätze werden in den Templates mittels ihrer ID oder ihres Namens (bzw. Slugs) angesprochen. In einer fremden Datenbank sind diese unter Umständen schon belegt; dann vergibt der Importer neue und passt die Templates daran an. Die Anpassung gelingt aber nur, wenn IDs, Namen, Slugs direkt in den Befehlsparametern stehen. Steht im Parameter eine PHP-Variable, die erst zur Laufzeit ihren Wert enthält, schlägt die Anpassung fehl.

Einheitliche Feldnamen

Für Ihre eigenen Template-Sätze: Verwenden Sie dieselben Feldnamen wie im Rest der Website (titel, datum, text, bild, slug). Dann funktionieren bereichsübergreifende Listen – etwa eine Zeitleiste aus News und Terminen – ohne Anpassung, und ein Template-Satz lässt sich leicht mit bestehenden Bereichen kombinieren.

Für Template-Sätze in anderen Datenbanken: Nutzen Sie die Namens-Präfixe, damit es beim Import nicht zu Typkonflikten kommt (siehe Reservierte Namens-Präfixe).

Logik und Gestaltung trennen

Halten Sie die Logik (Abfragen, Filter, Blätterfunktion, Formularauswertung) in wenigen Templates zusammen und die Gestaltung in eigenen, kleinen Bausteinen:

news_uebersicht     (CMS:SELECT news_liste filter[area]=meldungen … count=true)
news_liste          (CMS:RESULT_START) (CMS:INCLUDE news_teaser) (CMS:RESULT_END)
                    (CMS:JUMP_LIST …)
news_teaser         <article class="teaser"> … (CMS:TEXT titel) … </article>

Wer den Satz an ein anderes Design anpasst, ändert nur news_teaser und die übrigen Gestaltungsbausteine; die Funktion bleibt unberührt.

Das Layout der Website einbinden

Verwenden Sie feste Kopf- und Fuß-Templates; diese können einfach durch vorhandene ersetzt werden.

Texte in einem eigenen Inhaltsbereich

Feste Beschriftungen („Weiterlesen“, „Zurück zur Übersicht“, Fehlermeldungen von Formularen) gehören in einen eigenen Inhaltsbereich (etwa „Beschriftungen“). Eingebunden werden sie mittels CMS:TEXT, z. B. (CMS:TEXT id=beschriftungen.weiterlesen). So können sie einfach für die spätere Website angepasst / übersetzt werden.

Label als Custom-Snippets

Um auch Bereichsnamen und Eingabefelder mehrsprachig zu halten, können Custom-Snippets angelegt werden (siehe Modul Snippets). Die im Template-Satz verwendeten Snippets werden automatisch in die Export-Datei integriert.

Einstellungen als Datensatz

Soll die Redaktion das Verhalten eines Satzes steuern – Anzahl der Einträge je Seite, Titel der Übersichtsseite, ein Einleitungstext –, legen Sie dafür einen kleinen Inhaltsbereich „Einstellungen“ mit einem einzigen Datensatz an und lesen ihn über seinen Slug:

(CMS:TEXT einleitung id=news-einstellungen)
(CMS:NUMBER eintraege_je_seite id=news-einstellungen set=limit)
(CMS:SELECT news_liste filter[area]=meldungen limit="{$set['limit']}" count=true)

Beispielinhalte mitliefern

Ein paar Beispiel-Datensätze zeigen nach dem Import sofort, wie der Satz aussieht und funktioniert. Exportieren Sie sie mit, samt Bildern.

Dokumentieren

Legen Sie dem Satz eine kurze Beschreibung bei – als Hinweis des Ordners oder als Datensatz: welche Templates als Standard-Template zuzuweisen sind, welche Layout-Templates erwartet werden, welche Snippets und Konverter der Satz nutzt.

CSS als Template

Binden Sie das benötigte CSS als Template ein – so erscheint der Template-Satz sofort im gewollten Design. Der Nutzer kann das CSS jederzeit in eine Datei auslagern. Beachten Sie jedoch, dass dieses Template gesonderte Header-Anweisungen benötigt, siehe Templates, die CSS, Javascript oder JSON ausliefern.

Einen Template-Satz einsetzen

  1. Importieren (Modul Inhalte → Startseite → Menü → Import), vorzugsweise zuerst in einer Testumgebung.
  2. Mit der Vorschau testen – insbesondere Verweise, die das Importprotokoll als „nicht zugeordnet“ meldet.
  3. Header und Footer anpassen oder durch eigene ersetzen.
  4. Falls es sich beim Template-Satz um eine ganze Website handelt: Startseiten-Id oder Startseiten-Template in der Instanz eintragen.
  5. Rechtliche Aspekte klären, ggf. Texte und Lösch-Policies anpassen.

3 Trigger-Templates

Ein Trigger-Template enthält PHP-Methoden, die camindo CMS bei Ereignissen der Datensätze eines Inhaltsbereichs aufruft – beim Anlegen, Speichern, Löschen, Wiederherstellen und bei den Aktionen einer Lösch-Policy. So lassen sich Inhalte beim Speichern aufbereiten (mehrere Felder zu einem zusammenfassen, Werte prüfen), verbundene Daten nachziehen oder personenbezogene Daten nach eigenen Regeln anonymisieren.

Die Methoden laufen, gleich woher die Änderung kommt: Eingabemaske, Frontend (CMS:SAVEDATA, CMS:USER_…), API, Import, Stapelverarbeitung, Papierkorb-Cronjob und Lösch-Policies.

Zuweisen

Ein Trigger-Template ist ein gewöhnliches Template in einem Template-Bereich. Sie weisen es in den Einstellungen des Inhaltsbereichs unter „Trigger-Templates“ zu (siehe Handbuch für Administratoren). Ein Bereich kann mehrere Trigger-Templates haben, und dasselbe Template darf mehreren Bereichen zugewiesen sein. So lassen sich wiederverwendbare Bausteine bilden – etwa ein Template, das in allen Bereichen Slugs pflegt, und daneben eines mit den Regeln eines einzelnen Bereichs.

Reihenfolge

Die Templates eines Bereichs laufen bei jedem Ereignis nacheinander in der angezeigten Reihenfolge; Sie ändern sie mit den Pfeil-Schaltflächen neben jedem Eintrag.

  • Ein Template ohne die Methode des Ereignisses wird übergangen.
  • Alle bekommen dieselben Variablen: Was ein Template an $data oder $affectedFields ändert, sieht das nächste schon.
  • Wirft ein Template eine Ausnahme, laufen die folgenden nicht mehr, und der Vorgang wird abgebrochen (siehe Abbrechen).
  • Jedes Template ist ein eigenes Objekt: Eigenschaften und Hilfsmethoden gleichen Namens in verschiedenen Templates stören sich nicht. Daten zwischen Templates tauschen Sie über $data aus.

Aufbau

Das Template enthält nur Methoden-Definitionen – keine Ausgabe, keine Anweisungen außerhalb der Methoden. camindo CMS setzt den übersetzten Code in den Rumpf einer Klasse; jede Methode wird so zur Methode eines Objekts:

<?php
// Trigger-Template "mitglieder_trigger" für den Bereich Mitglieder

function onSave(array &$affectedFields, array &$data, array &$previousData): void
{
    // Anzeigename aus Vor- und Nachname
    $data['name'] = trim(($data['vorname'] ?? '') . ' ' . ($data['nachname'] ?? ''));
    if (!in_array('name', $affectedFields, true)) $affectedFields[] = 'name';
}

private function normalize(string $text): string
{
    return trim(preg_replace('/\s+/', ' ', $text));
}
  • Alle Ereignis-Methoden sind optional. Fehlt eine, passiert bei dem Ereignis nichts.
  • Eigene Hilfsmethoden (private function …), Eigenschaften und Konstanten sind erlaubt.
  • CMS:SELECT ist erlaubt, um Inhalte verknüpfter Datensätze zu ermitteln (Anmerkungen unten beachten!)
  • Klassen sprechen Sie mit vollem Namen an (\Camindo\Core\Cms, \DateTimeImmutable) – use geht nicht, weil der Code mitten in einer Klasse steht.
  • Ein Template, das sich nicht laden lässt (Syntaxfehler, Ausgabe außerhalb der Methoden), bricht den Vorgang mit einer Fehlermeldung ab. Datensätze ohne die vorgesehene Aufbereitung zu speichern wäre schlimmer.

Was im Template bereitsteht

$this->cms Cms – snippet(), error(), select(), fetch() …
$this->db Database – eigene Abfragen
$this->record Record – das Dataset, das das Ereignis auslöst (load(), save() …)
$this->areaId Bereich, für den das Template geladen wurde. Ist es mehreren Bereichen zugewiesen, nehmen Sie den Bereich aus $data['area_id']
$this->temporary true beim Zwischenspeichern einer Arbeitskopie (Reiterwechsel in der Eingabemaske)
$this->email, $this->pagination … die Dienste, die auch Templates kennen
$cms['record_id'] Nummer des auslösenden Datensatzes – nur sie, für die CMS-Befehle; in onSave() die seiner Arbeitskopie (siehe Verknüpfte Inhalte abfragen)

Ereignisse

Methode Wann
onCreate(array &$data): void ein neuer Datensatz wird vorbelegt – Werte in $data erscheinen in der leeren Maske
onLoaded(array &$data): void nach jedem Laden eines Datensatzes (Eingabemaske, Stand vor dem Speichern …) – nicht bei CMS:SELECT im Frontend
onSave(array &$affectedFields, array &$data, array &$previousData): void vor der Prüfung und dem Schreiben. Ein Feld, das die Methode neu setzt, muss sie in $affectedFields eintragen
onSaved(array &$affectedFields, array &$data, array &$previousData): void nach dem Schreiben, vor dem Abschluss der Transaktion
onSoftDelete(array $data): void der Datensatz kommt in den Papierkorb
onRestore(array $data): void der Datensatz kommt aus dem Papierkorb zurück
onDelete(array $data): void endgültiges Löschen, vor dem Löschen in der Datenbank
onDeleted(array $data): void endgültiges Löschen, danach
onPrivacyAction(string $action, array &$data, array $context): void Aktion einer Lösch-Policy – siehe unten

$previousData ist der gespeicherte Stand vor der Änderung (leer bei einem neuen Datensatz).

Abbrechen

Alle Ereignisse außer onCreate und onLoaded laufen in der Transaktion des Vorgangs. Eine Ausnahme bricht ihn ab und macht ihn vollständig rückgängig; die Meldung erscheint in der Eingabemaske bzw. als Fehler der API:

function onSave(array &$affectedFields, array &$data, array &$previousData): void
{
    if (($data['ende'] ?? '') !== '' && $data['ende'] < ($data['beginn'] ?? '')) {
        throw new \Exception('Das Ende liegt vor dem Beginn.');
    }
}

Zwischenspeichern

Wechselt ein Redakteur in der Eingabemaske den Reiter, speichert camindo CMS den Zwischenstand der Arbeitskopie – auch dabei laufen onSave und onSaved, mit $this->temporary = true. Aufwendige oder nach außen wirkende Schritte (E-Mails, Schnittstellen) gehören hinter if ($this->temporary) return;.

Verschieben

Wird ein Datensatz in einen anderen Bereich verschoben, gelten die Trigger-Templates des Zielbereichs. $data enthält dort schon die Felder des Zielbereichs, $previousData den Stand im Herkunftsbereich mit dessen Feldnamen.

Wiedereintritt

Speichert ein Template in onSaved() denselben Datensatz erneut, liefe es endlos. Ein Ereignis wird deshalb je Template für denselben Datensatz nicht verschachtelt ausgeführt: Beim erneuten Speichern laufen die übrigen Trigger-Templates des Bereichs, das gerade laufende nicht. Andere Datensätze – auch desselben Bereichs – lösen ihre Trigger normal aus.

Verknüpfte Inhalte abfragen

Die Inhalte des auslösenden Datensatzes stehen in $data (neuer Stand) und $previousData (vorheriger Stand) – mehrsprachige Felder mit allen Sprachen. Dort lesen und ändern Sie sie, nicht über CMS:TEXT.

Für andere Datensätze, etwa die verknüpften, können Sie CMS:SELECT wie in jedem Template verwenden. Während eine Methode läuft, enthält $cms nur record_id, die Nummer des auslösenden Datensatzes. Ein CMS:SELECT mit relation braucht deshalb kein src_id – und sollte keines haben (siehe unten):

function onSave(array &$affectedFields, array &$data, array &$previousData): void
{
    $isVariant = false;
?>
(CMS:SELECT
  relation=variante_von
  state=any archive=true future=true
  resulttemplate=shop.trigger_variante_gefunden
)
<?php
    $data['ist_variante'] = $isVariant ? 'ja' : '';
    if (!in_array('ist_variante', $affectedFields, true)) $affectedFields[] = 'ist_variante';
}

Das Ergebnis-Template (shop.trigger_variante_gefunden) läuft im Rumpf der Methode: Es sieht deren Variablen und $this und setzt hier nur $isVariant = true;. Es darf nichts ausgeben.

Beachten Sie die Grenzen:

  • Neuer Stand der Verknüpfungen über $cms['record_id']. Während ein vorhandener Datensatz bearbeitet wird, hängen seine Verknüpfungen – ausgehende wie eingehende – an einer Arbeitskopie; erst nach dem Speichern gehen sie auf den Datensatz über. In onSave() – und in onSaved() beim Zwischenspeichern – enthält $cms['record_id'] deshalb die Nummer der Arbeitskopie: CMS:SELECT relation=… und CMS:RELATION ohne Quellangabe sehen so schon die geänderten Verknüpfungen, ebenso die Gegenrichtung („Wer verweist auf diesen Datensatz?“) mit dest_id="{$cms['record_id']}" bzw. filter[feld]="{$cms['record_id']}". $data['record_id'] ist dagegen immer die Nummer des Datensatzes selbst – damit erhalten Sie in onSave() den bisherigen Stand. In onSaved() nach dem endgültigen Speichern und in allen übrigen Methoden sind beide Nummern gleich.

  • Eingebettete Inhalte beim Zwischenspeichern: Verknüpfungen eines Feldes mit eingebetteten Inhalten zeigen beim Zwischenspeichern auf deren Arbeitskopien, die CMS:SELECT nicht findet. Beim endgültigen Speichern sind diese Inhalte schon übernommen und werden gefunden.

  • Veröffentlichung: CMS:SELECT findet auch hier nur, was die Website zeigen würde – freigegeben, Erscheinungsdatum erreicht, nicht abgelaufen. Geht es um die Struktur („Ist überhaupt ein Datensatz verknüpft?“), setzen Sie state=any archive=true future=true; sonst fehlen gesperrte, vordatierte und abgelaufene Datensätze. Datensätze im Papierkorb findet CMS:SELECT nie.

  • Rechte: Die Abfrage sieht, was der Auslöser sehen darf. Das Ergebnis kann sich deshalb unterscheiden, je nachdem, wer speichert:

    Auslöser Was CMS:SELECT findet
    Benutzer im Backend oder über die API nur Inhalte der Instanzen, Bereiche und Sprachen, die seine Benutzergruppe lesen darf (Superuser: alle)
    Website (CMS:SAVEDATA, CMS:USER_…) Inhalte der Instanz und ihrer lesbaren Instanzen
    Zeitplaner (auch später fällige Blöcke einer Lösch-Policy) alle
  • Sprache: Mehrsprachige Felder der gefundenen Datensätze erscheinen in einer Sprache – auf der Website in der Sprache der Seite, im Backend in der Inhaltssprache des Benutzers. Alle Sprachen liefert $this->record->load($id).

onPrivacyAction: Lösch-Policies

Eine Lösch-Policy legt fest, was beim Löschen eines Datensatzes geschieht. Was sich mit „Einzelne Felder überschreiben“ nicht ausdrücken lässt – verknüpfte Datensätze, Texte, die nur teilweise personenbezogen sind, mehrsprachige Inhalte je Sprache –, erledigt das Trigger-Template mit zwei Aktionen:

Aktion der Policy Aufruf
„Trigger-Template auf den Datensatz anwenden“ einmal, $data ist der Datensatz
„Trigger-Template auf das Audit-Log anwenden“ einmal je Eintrag im Audit-Log des Datensatzes, $data ist der Eintrag

Jeder solche Block hat eine Kennung (Kleinbuchstaben, Ziffern, _). Sie kommt als $action an – daran unterscheidet das Template die Blöcke, etwa einen sofortigen Schritt und einen nach einem Jahr.

function onPrivacyAction(string $action, array &$data, array $context): void
$context
target record oder audit
record_id der gelöschte Datensatz
user_id der Website-User, dem er gehört (record.user_id), sonst 0
area_id sein Bereich
source Auslöser: record (Datensatz wird gelöscht), profile (UGC: Profil wurde gelöscht) oder area (Bereich wurde gelöscht)
policy_id, job_id Policy und Auftrag
deleted nur bei record: true, wenn der Datensatz zu dem Zeitpunkt im Papierkorb liegt

Auf den Datensatz

$data enthält alle Felder des Datensatzes – mehrsprachige Felder als Array language_id => Wert, Dateifelder mit ihren Metadaten. Was die Methode an $data ändert, speichert camindo CMS anschließend:

  • im Policy-Modus: ohne Pflichtfeld- und Formatprüfung und ohne Rechte der Benutzergruppe – „unique“ gilt weiter;
  • Dateifelder, record_id, area_id, user_id und die übrigen Kennungen nicht. Eine Datei entfernen Sie mit „Einzelne Felder überschreiben“ (bild = "") im selben oder einem späteren Block;
  • im Audit-Log als Aktion „nach Lösch-Policy angepasst“ (privacy), ohne die vorherigen Werte.

Liegt der Datensatz im Papierkorb ($context['deleted']), läuft die Methode ebenfalls – etwa um verknüpfte Daten aufzuräumen –, gespeichert wird am Datensatz dann aber nichts. Andere Datensätze bearbeitet die Methode selbst über $this->record oder $this->db.

Auf das Audit-Log

Für jeden Eintrag im Verlauf des Datensatzes wird die Methode mit dem Eintrag aufgerufen:

Schlüssel
id, t Nummer und Zeitpunkt – bleiben unverändert
action create, update, delete, restore, purge, privacy …
actor type (admin, user, system), admin_id, user_id, ip, origin – Handelnde nur als Nummer, ohne Namen
label Bezeichnung des Datensatzes zu dem Zeitpunkt
changes Liste der Änderungen: k Feldname (mehrsprachig feld@de_DE), l Beschriftung, t Feldtyp, g Sprache, b vorher, a nachher

Geänderte Einträge schreibt camindo CMS zurück – in die Datei unter var/audit/ mit einem neuen Kontrollpunkt der Prüfsummenkette, label auch in die Datenbank. Einträge, die erst später entstehen (etwa beim endgültigen Löschen), durchlaufen die Methode nicht; planen Sie dafür einen späteren Block.

Hat der Bereich mehrere Trigger-Templates mit onPrivacyAction(), laufen sie nacheinander mit derselben Kennung; jedes prüft selbst, ob $action es betrifft.

Fehler

Hat keines der Trigger-Templates des Bereichs onPrivacyAction() oder wirft die Methode eine Ausnahme, scheitert der Auftrag: Er steht mit der Meldung im Log privacy und wird beim nächsten Lauf der Zeitplaner-Aufgabe „Lösch-Policies anwenden“ erneut versucht – höchstens 20-mal. Scheitert ein sofort fälliger Block, bricht das Löschen nicht ab: Die übrigen Blöcke laufen, der gescheiterte folgt mit dem nächsten Lauf.

CMS:DATE

Gibt den Datumsteil eines Datumswertes formatiert aus.

(CMS:DATE field [parameter=wert …])

Methode: Cms::cmsDate()
Ohne Schlüssel: der erste Wert ohne = gilt als field
Mindestens einer von: field, useval

Parameter

Parameter Wert Standard Pflicht Beschreibung
id Datensatz-Id oder Slug (aktueller Datensatz) Datensatz, aus dem gelesen wird. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL.
field ¹ Feld – ² Name eines Datensatzfeldes, dessen Inhalt als Quelle genutzt wird.
useval Datumswert – ² Wert in Textform, der als Quelle genutzt wird.
format Formatangaben – ICU-Muster, z. B. "dd.MM.y". Ohne Angabe entscheidet die Sprache der Seite über die Schreibweise.
timezone true / false false true: Der Name der Zeitzone wird angehängt (z. B. "Europe/Berlin").
replace[…] Text?Text – Kommt der Text vor dem Fragezeichen im formatierten Wert vor, wird er durch den Text nach dem Fragezeichen ersetzt (z. B. "Jan?Januar").
ifempty Text – Text, der ausgegeben wird, falls die Quelle ('field' oder 'useval') leer ist.
instead Text – Text, der anstelle der Quelle ausgegeben wird, sofern diese nicht leer ist.
prefix Text – Text, der vorangestellt wird, sofern der formatierte Wert nicht leer ist.
suffix Text – Text, der angehängt wird, sofern der formatierte Wert nicht leer ist.
set Array-Index von $set – Der formatierte Wert wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.
² mindestens einer der mit ² markierten Parameter muss angegeben werden.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Gibt den Datumsteil eines Datums-/Zeitwertes aus. Quelle ist ein Datums- oder Datum-Zeit-Feld (Metafelder online_at, created_at, changed_at oder eigene Felder). Ohne format entscheidet die Sprache der Seite über die Schreibweise:

(CMS:DATE online_at)                            07.03.2024   (de)
(CMS:DATE online_at format="EEEE, d. MMMM y")   Donnerstag, 7. März 2024
(CMS:DATE online_at format="y-MM-dd")           2024-03-07

Typisch in Listen:

<time datetime="(CMS:DATE online_at format='y-MM-dd')">(CMS:DATE online_at)</time>

CMS:DATE, CMS:DATETIME und CMS:TIME sind derselbe Befehl mit unterschiedlichem Ausschnitt; alle drei kennen dieselben Parameter (CMS:DATETIME zusätzlich autotime).

Parameter im Einzelnen

id

id liest aus einem anderen Datensatz statt aus dem laufenden - record_id oder Slug, genau wie in der URL:

(CMS:DATE online_at id=1977)
(CMS:DATE online_at id=impressum)

field

field ist der Hauptparameter und darf ohne Schlüssel stehen - beide Zeilen sind gleich:

(CMS:DATE field=online_at)
(CMS:DATE online_at)

useval

useval nimmt statt eines Feldes einen Wert direkt aus dem Template - meist eine PHP-Variable oder ein zuvor mit set abgelegter Wert:

(CMS:DATE useval="2024-12-24")
(CMS:DATE useval="{$set['wert']}")
(CMS:DATE useval="{$set['user']['created_at']}")

format

format ist ein ICU-Muster (d Tag, MM Monat zweistellig, MMMM Monatsname, y Jahr, HH:mm Uhrzeit, EEEE Wochentag). Ohne format entscheidet die Sprache der Seite (im Backend die Locale des Benutzers):

(CMS:DATE online_at format="d. MMMM y")

Ausgabe: 7. März 2024

timezone

(CMS:DATE online_at timezone=true)

Ausgabe: 07.03.2024 Europe/Berlin

replace

replace ersetzt Teiltexte im fertig formatierten Wert - Kurzform mit ? als Trenner oder ein Eintrag je Suchtext:

(CMS:DATE online_at format="MMM y" replace="Jan.?Januar")
(CMS:DATE online_at format="MMM y" replace[Jan.]="Januar" replace[x]="y")

Ausgabe: Januar 2026 statt Jan. 2026

ifempty

ifempty ersetzt die gesamte Ausgabe, wenn die Quelle leer ist; prefix und suffix entfallen dann:

(CMS:DATE offline_at prefix="<p>" suffix="</p>" ifempty="unbegrenzt")

Als leer gelten auch die Platzhalter für „kein Datum“ und „kein Ende“, mit denen camindo CMS etwa ein unbegrenztes offline_at speichert (vor dem 2. Januar 1000 bzw. ab dem 31. Dezember 8999) – mit und ohne format.

instead

instead tauscht den Wert aus, sobald die Quelle nicht leer ist - nützlich, wenn nur das Vorhandensein zählt:

(CMS:DATE offline_at instead="befristet" ifempty="-")

prefix

prefix / suffix umschließen den Wert und erscheinen nur, wenn er nicht leer ist - so entsteht kein leeres veröffentlicht am:

(CMS:DATE online_at prefix="veröffentlicht am " suffix="")

suffix

(CMS:DATE online_at suffix=" (Erstveröffentlichung)")

Siehe prefix.

set

set gibt nichts aus, sondern legt das Ergebnis unter $set['date'] für das umgebende PHP oder einen späteren Befehl ab:

(CMS:DATE online_at set=date)
<?php if ($set['date'] !== '') { ?> … <?php echo $set['date']; ?> … <?php } ?>

← Übersicht

CMS:DATETIME

Gibt Datum und Uhrzeit eines Datumswertes formatiert aus.

(CMS:DATETIME field [parameter=wert …])

Methode: Cms::cmsDatetime()
Ohne Schlüssel: der erste Wert ohne = gilt als field
Mindestens einer von: field, useval

Parameter

Parameter Wert Standard Pflicht Beschreibung
id Datensatz-Id oder Slug (aktueller Datensatz) Datensatz, aus dem gelesen wird. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL.
field ¹ Feld – ² Name eines Datensatzfeldes, dessen Inhalt als Quelle genutzt wird.
useval Datumswert – ² Wert in Textform, der als Quelle genutzt wird.
format Formatangaben – ICU-Muster, z. B. "dd.MM.y HH:mm". Ohne Angabe entscheidet die Sprache der Seite über die Schreibweise.
autotime true / false false true: Wenn keine Uhrzeit vorhanden ist (00:00), wird diese nicht angezeigt.
timezone true / false false true: Der Name der Zeitzone wird angehängt (z. B. "Europe/Berlin").
replace[…] Text?Text – Kommt der Text vor dem Fragezeichen im formatierten Wert vor, wird er durch den Text nach dem Fragezeichen ersetzt (z. B. "Jan?Januar").
ifempty Text – Text, der ausgegeben wird, falls die Quelle ('field' oder 'useval') leer ist.
instead Text – Text, der anstelle der Quelle ausgegeben wird, sofern diese nicht leer ist.
prefix Text – Text, der vorangestellt wird, sofern der formatierte Wert nicht leer ist.
suffix Text – Text, der angehängt wird, sofern der formatierte Wert nicht leer ist.
set Array-Index von $set – Der formatierte Wert wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.
² mindestens einer der mit ² markierten Parameter muss angegeben werden.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Gibt Datum und Uhrzeit aus. Wie CMS:DATE, nur mit Zeitanteil:

(CMS:DATETIME changed_at)                               07.03.2024, 14:30
(CMS:DATETIME changed_at format="dd.MM.y HH:mm")        07.03.2024 14:30
(CMS:DATETIME changed_at format="dd.MM.y HH:mm" autotime=true)

Mit autotime=true verschwindet die Uhrzeit, wenn im Wert keine gesetzt war (00:00:00) - für Termine „ganztägig“.

Parameter im Einzelnen

id

id liest aus einem anderen Datensatz statt aus dem laufenden - record_id oder Slug, genau wie in der URL:

(CMS:DATETIME changed_at id=1977)
(CMS:DATETIME changed_at id=impressum)

field

field ist der Hauptparameter und darf ohne Schlüssel stehen - beide Zeilen sind gleich:

(CMS:DATETIME field=changed_at)
(CMS:DATETIME changed_at)

useval

useval nimmt statt eines Feldes einen Wert direkt aus dem Template - meist eine PHP-Variable oder ein zuvor mit set abgelegter Wert:

(CMS:DATETIME useval="2024-03-07 14:30:00")
(CMS:DATETIME useval="{$set['wert']}")

format

format ist ein ICU-Muster (d Tag, MM Monat zweistellig, MMMM Monatsname, y Jahr, HH:mm Uhrzeit, EEEE Wochentag). Ohne format entscheidet die Sprache der Seite (im Backend die Locale des Benutzers):

(CMS:DATETIME online_at format="dd.MM.y HH:mm")

Ausgabe: 07.03.2024 14:30

autotime

Nur zusammen mit format: Enthält der Wert keine Uhrzeit (00:00:00), wird der Zeitanteil aus der Ausgabe entfernt:

(CMS:DATETIME event_at format="dd.MM.y HH:mm" autotime=true)

Ausgabe bei 2024-03-07 00:00:00: 07.03.2024 - bei 2024-03-07 14:30:00: 07.03.2024 14:30

timezone

timezone=true hängt den Namen der Zeitzone an, in der der Wert angezeigt wird:

(CMS:DATETIME online_at timezone=true)

Ausgabe: 07.03.2024, 14:30 Europe/Berlin

replace

replace ersetzt Teiltexte im fertig formatierten Wert - Kurzform mit ? als Trenner oder ein Eintrag je Suchtext:

(CMS:DATETIME changed_at replace=", ? um ")
(CMS:DATETIME changed_at replace[, ]=" um " replace[x]="y")

Ausgabe: 07.03.2024 um 14:30

ifempty

ifempty ersetzt die gesamte Ausgabe, wenn die Quelle leer ist; prefix und suffix entfallen dann:

(CMS:DATETIME event_at prefix="<p>" suffix="</p>" ifempty="Termin folgt")

Als leer gelten auch die Platzhalter für „kein Datum“ und „kein Ende“, mit denen camindo CMS etwa ein unbegrenztes offline_at speichert (vor dem 2. Januar 1000 bzw. ab dem 31. Dezember 8999) – mit und ohne format.

instead

instead tauscht den Wert aus, sobald die Quelle nicht leer ist - nützlich, wenn nur das Vorhandensein zählt:

(CMS:DATETIME event_at instead="Termin steht fest" ifempty="-")

prefix

prefix / suffix umschließen den Wert und erscheinen nur, wenn er nicht leer ist - so entsteht kein leeres Stand::

(CMS:DATETIME changed_at prefix="Stand: " suffix="")

suffix

(CMS:DATETIME changed_at suffix=" Uhr")

Siehe prefix.

set

set gibt nichts aus, sondern legt das Ergebnis unter $set['changed'] für das umgebende PHP oder einen späteren Befehl ab:

(CMS:DATETIME changed_at set=changed)
<?php if ($set['changed'] !== '') { ?> … <?php echo $set['changed']; ?> … <?php } ?>

← Übersicht

CMS:EMAIL

Versendet eine E-Mail. Prüft auf Wunsch vorab die Pflichtfelder eines Formulars.

(CMS:EMAIL [parameter=wert …])

Methode: Email::cmsEmail()
Mindestens einer von: subject, subjecttemplate, body, bodytemplate
PHP-Schnipsel: include_and_stop (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
to[…] E-Mail-Adresse – ja Empfänger-E-Mail-Adresse. Mehrere Empfänger als to[]=… oder kommasepariert (höchstens 20).
toname Text – Empfängername (nur bei genau einem Empfänger).
from E-Mail-Adresse email.noreply bzw. email.reply Absender-E-Mail-Adresse.
fromname Text – Absendername.
contenttype text/plain oder text/html text/plain MIME-Type der E-Mail.
subject Text (no subject) ² Betreff der E-Mail.
subjecttemplate Template-Name – ² Template für den Betreff (Tags und Zeilenumbrüche fallen weg). Hat Vorrang vor subject.
body Text – ² Nachrichtentext (Text oder HTML).
bodytemplate Template-Name – ² Template für den E-Mail-Body. Hat Vorrang vor body.
paths_absolute true / false false true: Alle serverrelativen Pfade im E-Mail-Body werden in absolute Pfade umgewandelt (nur bei contenttype="text/html").
sender_url (http://)Domain Protokoll und Hauptdomain der Instanz (Optional mit Protokoll) + Domain, um serverrelative Pfade in absolute Pfade wandeln zu können.
debug true / false false true: E-Mail wird in das Logfile var/log/{Datum}_mail-debug.log geschrieben, aber nicht versendet.
requiredfields Feldnamen, kommasepariert – Angabe der Pflichtfelder. Feldnamen (Index-Namen) im Request-Array data[…] (siehe Parameter data).
data PHP-Variablenname data Name des Request-Arrays, das die von requiredfields erwarteten Daten enthält (Standard data, also $_POST['data']).
submit Textwert – ja Der Befehl läuft nur, wenn submit einen Wert enthält. Ansonsten wird der CMS:EMAIL-Befehl ignoriert.
successtemplate Template-Name – Template, das nach erfolgreichem Absenden eingebunden wird; danach endet die Seite (außer mit CMS:CONTINUE).
message[…] Text – Fehlermeldung. Im Index: Anwendungsfall (siehe Meldungen); im Wert: Meldungstext.
attachment[…] Pfad – Dateien, die angehängt werden sollen. Pfad relativ zum var-Verzeichnis, nur aus download/, upload/ und modules/.
throttle true / Bucket-Name – Drosselung je Herkunft (IP): true = höchstens fünf E-Mails je Stunde (user.throttle.email_ip), sonst eine eigene Grenze aus user.throttle. Gedrosselt: Meldung throttled, keine E-Mail.
formguard true / false / Sekunden false true: Formularschutz gegen Robots – das Formular muss die Felder aus (CMS:USER_TOKEN formguard=true) enthalten und frühestens nach 3 Sekunden (user.formguard_min_seconds) abgeschickt werden. Eine Zahl setzt die Mindestzeit für dieses Formular (formguard=5), 0 schaltet ab. Sonst Meldung throttled.

² mindestens einer der mit ² markierten Parameter muss angegeben werden.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Kontaktformular mit Pflichtfeldprüfung und Bestätigungsseite:

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:EMAIL
  submit="$submit"
  to="info@example.com"
  fromname="Kontaktformular"
  subject="Kontaktanfrage"
  bodytemplate=contact_mail
  requiredfields="name,email,message"
  successtemplate=contact_thanks
)
(CMS:MESSAGE prefix="<div class=alert>" suffix="</div>")
<form method="post">
  <input name="data[name]"> <input name="data[email]" type="email">
  <textarea name="data[message]"></textarea>
  <button name="submit" value="1">Senden</button>
</form>

Der Ablauf:

  1. Beim ersten Aufruf ist submit leer – der Befehl tut nichts, das Formular erscheint.
  2. Nach dem Absenden prüft er die Pflichtfelder. Fehlt eines, wird nicht gesendet; CMS:MESSAGE zeigt die Meldung, und das Template läuft weiter – das Formular erscheint erneut.
  3. Sonst baut er die E-Mail, versendet sie und bindet successtemplate ein. Die Seite endet danach. Ohne successtemplate läuft das Template einfach weiter.

Versendet wird über das in der Konfiguration eingetragene SMTP-Postfach (email.smtp), ersatzweise über die PHP-Funktion mail() – wie alle E-Mails des CMS. Scheitert der Versand, steht der Grund in var/log/{Datum}_mail.log (Modul Logdateien, Art „Nicht versendete E-Mails“).

Was der Befehl hinterlässt

Variable Inhalt
$set['email_sent'] true nach erfolgreichem Versand (bzw. mit debug=true nach dem Protokollieren), sonst false
$set['email_missing'] Namen der leeren Pflichtfelder, z. B. um sie im Formular hervorzuheben
<input name="data[email]" class="<?php echo in_array('email', $set['email_missing'] ?? []) ? 'error' : ''; ?>">

Meldungen

Fehler landen bei CMS:MESSAGE. Den Text bestimmt message[index], sonst gilt der Standardtext:

Index Fall Standardtext
required ein Pflichtfeld ist leer Bitte füllen Sie alle Pflichtfelder aus.
to kein oder ein ungültiger Empfänger Die E-Mail hat keinen gültigen Empfänger.
from ungültiger Absender (auch: keiner konfiguriert) Die E-Mail hat keinen gültigen Absender.
content subjecttemplate/bodytemplate fehlt oder bricht ab Der Inhalt der E-Mail konnte nicht erzeugt werden.
attachment ein Anhang fehlt oder liegt außerhalb der erlaubten Verzeichnisse Ein Anhang der E-Mail wurde nicht gefunden.
send der Versand ist gescheitert Die E-Mail konnte nicht versendet werden.
throttled Drosselung (throttle) oder Formularschutz (formguard) Zu viele Anfragen. Bitte versuchen Sie es später erneut.

Die Standardtexte erscheinen in der Sprache der Seite.

Sicherheit

  • Empfänger nie aus dem Formular übernehmen. Steht in to eine Eingabe des Besuchers, kann jeder über Ihr Formular E-Mails an beliebige Adressen schicken. Ist eine der Adressen in to ungültig, sendet der Befehl gar nicht.
  • Kontaktformulare drosseln: throttle=true begrenzt die E-Mails je Besucher, formguard=true (mit (CMS:USER_TOKEN formguard=true) im Formular) hält Robots auf: (CMS:EMAIL submit="$submit" to="info@example.com" … throttle=true formguard=true). Bestellbestätigungen und andere E-Mails, deren Empfänger Sie selbst bestimmen, brauchen das nicht.
  • Absender: Absender ist immer der Websitebetreiber, nie der Besucher: Viele Mailserver nehmen nur E-Mails an, deren Absender zur eigenen Domain passt. Setzen Sie die Adresse des Besuchers deshalb besser in den Text als in from; ohne from gilt die Absenderadresse aus der Konfiguration.
  • Zeilenumbrüche in Betreff, Namen und Adressen werden entfernt – eingeschleuste Kopfzeilen (etwa ein zusätzliches Bcc:) sind damit ausgeschlossen.
  • HTML-E-Mails: Eingaben des Besuchers im Body-Template mit (CMS:TEXT useval="{$_POST['data']['…']}" specialchars=true) ausgeben.
  • Anhänge nur aus var/download/, var/upload/ und var/modules/ – Konfiguration, Protokolle und Sicherungen lassen sich nicht anhängen.

Parameter im Einzelnen

to

(CMS:EMAIL to="info@example.com" …)
(CMS:EMAIL to[]="info@example.com" to[]="vertrieb@example.com" …)

toname

Gilt nur, wenn genau ein Empfänger angegeben ist:

(CMS:EMAIL to="info@example.com" toname="Beispiel GmbH" …)

from

(CMS:EMAIL from="shop@example.com" …)

Ohne Angabe die Absenderadresse aus der Konfiguration (email.noreply, ersatzweise email.reply). Ist keine davon eingetragen, kann ohne from nicht gesendet werden.

fromname

(CMS:EMAIL fromname="Kontaktformular Beispiel GmbH" …)

contenttype

(CMS:EMAIL contenttype="text/html" bodytemplate=newsletter_html …)

Bei text/html erzeugt das CMS zusätzlich eine Textfassung für E-Mail-Programme ohne HTML-Darstellung.

subject

(CMS:EMAIL subject="Kontaktanfrage" …)

subjecttemplate

Betreff aus einem Template erzeugen (z. B. mit Feldern des laufenden Datensatzes). Tags, Entities und Zeilenumbrüche fallen weg:

(CMS:EMAIL subjecttemplate=order_subject …)

body

(CMS:EMAIL body="Neue Anfrage von {$_POST['data']['name']}" …)

bodytemplate

Nachrichtentext aus einem Template – dort stehen alle CMS-Befehle zur Verfügung, der laufende Datensatz, $set und die Formulardaten in $_POST. Variablen, die Sie im aufrufenden Template mit PHP gesetzt haben, sieht das Body-Template nicht; legen Sie sie bei Bedarf in $set ab.

(CMS:EMAIL contenttype="text/html" bodytemplate=contact_mail …)

contact_mail:

<p>Neue Anfrage von (CMS:TEXT useval="{$_POST['data']['name']}" specialchars=true)
(CMS:TEXT useval="{$_POST['data']['email']}" specialchars=true):</p>
<p>(CMS:TEXT useval="{$_POST['data']['message']}" specialchars=true)</p>

specialchars=true gehört zu HTML-Mails. In einer Textmail (text/plain, Standard) kann Besucher-Text nichts ausführen; specialchars würde dort nur Entities wie &amp; sichtbar machen.

Template-Namen lassen sich wie bei CMS:INCLUDE mit dem Bereich angeben (bereich.name).

paths_absolute

Serverrelative Pfade (/media/…) in src, href, action, background, poster und url(…) des HTML-Bodys absolut machen:

(CMS:EMAIL contenttype="text/html" bodytemplate=newsletter_html paths_absolute=true …)

sender_url

Domain für paths_absolute, wenn nicht Protokoll und Hauptdomain der Instanz gelten sollen; ohne Protokoll gilt das der Instanz:

(CMS:EMAIL paths_absolute=true sender_url="https://www.example.com" …)

debug

E-Mail nur ins Logfile var/log/{Datum}_mail-debug.log schreiben, nicht senden – zum Testen eines Formulars. Der Befehl verhält sich sonst wie beim echten Versand (Pflichtfelder, successtemplate):

(CMS:EMAIL debug=true …)

requiredfields

Pflichtfelder im Request-Array data[…] (bzw. dem über data benannten Array); fehlt eines, wird nicht gesendet und CMS:MESSAGE zeigt die Meldung. Bei Feldern mit mehreren Werten (Checkbox-Gruppe data[themen][]) genügt ein ausgewählter Wert:

(CMS:EMAIL requiredfields="name,email,message" …)

data

Name des Request-Arrays mit den Formulardaten (Standard data, also $_POST['data']):

<input name="form[name]">
(CMS:EMAIL data=form requiredfields="name" …)

submit

Der Befehl läuft nur, wenn submit einen Wert hat – beim ersten Aufruf des Formulars also nicht:

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:EMAIL submit="$submit" …)
<button name="submit" value="1">Senden</button>

successtemplate

Nach erfolgreichem Versand wird dieses Template eingebunden und die Seite beendet (ohne CMS:CONTINUE):

(CMS:EMAIL successtemplate=contact_thanks …)

message

Eigene Meldungstexte je Fehlerfall (Indizes siehe Meldungen):

(CMS:EMAIL message[required]="Bitte alle Pflichtfelder ausfüllen." …)

attachment

Dateien relativ zum var-Verzeichnis anhängen – nur aus download/, upload/ und modules/:

(CMS:EMAIL attachment[]="download/agb.pdf" …)

throttle

(CMS:EMAIL throttle=true …)
(CMS:EMAIL throttle=newsletter …)

Ein eigener Name braucht eine Grenze in der Konfiguration (user.throttle.newsletter, Systemadministrator); fehlt sie, gilt die von true.

formguard

(CMS:EMAIL formguard=true …)
<form method="post">(CMS:USER_TOKEN formguard=true) …</form>

← Übersicht

CMS:MEDIA

Erzeugt ein Image-Tag oder gibt den Inhalt einer Mediendatei in Textform aus.

(CMS:MEDIA field [parameter=wert …])

Methode: Cms::cmsMedia()
Ohne Schlüssel: der erste Wert ohne = gilt als field
Mindestens einer von: field, useval

Parameter

Parameter Wert Standard Pflicht Beschreibung
id Datensatz-Id oder Slug (aktueller Datensatz) Datensatz, aus dem gelesen wird. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL.
field ¹ – – ² Name eines Upload- oder Thumbnail-Feldes.
subfield path / file / filename / mime_type / extension / size / width / height / version – Einzelwert statt IMG-Tag: path (Pfad ab DOCUMENT_ROOT für src/href, mit sprechendem Dateinamen; bei auth/download/user_auth über file.php), file (absoluter Pfad im Dateisystem), filename (ursprünglicher Dateiname), mime_type, extension (ohne Punkt), size (Bytes; mit autoformat lesbar), width / height (Pixel, 0 wenn unbekannt), version (Versionszähler der Datei). Der Einzelwert wird nicht maskiert.
auth true / false false true: Der Verweis zeigt auf /file.php statt ins Medienverzeichnis. Für Felder mit "Nur für angemeldete Frontend-User" (user_auth) gilt das automatisch - file.php prüft dort die Frontend-Session.
download Text – Datei wird über /file.php unter dem angegebenen Dateinamen zum Download angeboten (Dateiendung wird automatisch ergänzt).
width Text – Breite des IMG-Tags, mit oder ohne CSS-Einheit (Standard: px).
height Text – Höhe des IMG-Tags, mit oder ohne CSS-Einheit (Standard: px).
attr[…] Array – Zusätzliche Attribute für das IMG-Tag (Schlüssel: Attributname, Wert: Attributwert). Die Werte werden automatisch mit htmlspecialchars maskiert.
autoformat true / false true true: Der via 'subfield' gewählte Inhalt wird je nach Typ formatiert (z. B. Dateigrößen in Bytes/KB/MB dynamisch nach Wert).
instead Text – Überschreibt den formatierten Wert, sofern dieser nicht leer ist.
ifempty Text – Alternativtext, falls der formatierte Wert leer (oder "0") ist.
prefix Text – Text, der vorangestellt wird, sofern der formatierte Wert nicht leer (oder "0") ist.
suffix Text – Text, der angehängt wird, sofern der formatierte Wert nicht leer (oder "0") ist.
useval Array – ² Datei-Array (etwa aus CMS:SAVEDATA) samt field_id und record_id - nur per PHP-Aufruf, ohne field (siehe Beispiel).
set Array-Index von $set – Der formatierte Wert wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.
² mindestens einer der mit ² markierten Parameter muss angegeben werden.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Quelle ist ein Upload-Feld. Ohne subfield entsteht ein <img>-Tag mit den gespeicherten Maßen, alt ist der ursprüngliche Dateiname:

(CMS:MEDIA image)

Ausgabe: <img src="/media/1/12/1977,strand.jpg?v=3" alt="strand.jpg" width="1200" height="800">

Der Pfad trägt den ursprünglichen Dateinamen hinter einem Komma (sprechende URL für Browser und Suchmaschinen, als Slug ohne Endung); die Datei selbst heißt 1977.jpg – eine Rewrite-Regel im Medienverzeichnis wirft den Namensteil ungeprüft weg. ?v= trägt die Versionsnummer der Datei: nach einem neuen Upload ändert sich die Adresse, der Browser lädt die Datei neu.

Mit subfield wird ein Einzelwert der Datei ausgegeben - so lassen sich beliebige Tags bauen oder Downloads verlinken:

<a href="(CMS:MEDIA document subfield=path)">
  (CMS:MEDIA document subfield=filename) ((CMS:MEDIA document subfield=size))
</a>

Ausgabe: <a href="/media/1/13/1977,agb.pdf?v=3">agb.pdf (123.5 KB)</a>

Kein Bild hochgeladen? ifempty liefert den Ersatz - prefix/suffix entfallen dann:

(CMS:MEDIA image width=300 height="" prefix="<figure>" suffix="</figure>" ifempty="<img src='/static/placeholder.png'>")

Verfügbare subfield-Werte

subfield Inhalt
path Pfad ab DOCUMENT_ROOT für src/href: /media/{instanz}/{feld}/{datensatz},{name}.{endung} - bei geschützten Feldern, auth=true und download stattdessen der /file.php-Aufruf
file absoluter Pfad im Dateisystem des Servers (…/public/media/1/12/1977.jpg) - für Dateioperationen in PHP, nie für Links
filename ursprünglicher Dateiname (vor dem Upload)
mime_type z. B. image/jpeg
extension Dateiendung ohne Punkt, z. B. jpg
size Dateigröße; mit autoformat (Standard) lesbar (123.5 KB), mit autoformat=false in Bytes
width / height Bildmaße in Pixeln, 0 wenn nicht ermittelbar
version Versionszähler der Datei - steigt bei jedem neuen Upload in dasselbe Feld

url ist ein Alias für path (Schreibweise aus C9-Vorabversionen). Bei mehrsprachigen Upload-Feldern liefert der Befehl die Datei der aktuellen Sprache bzw. die der Hauptsprache als Fallback (1977_2,strand.jpg).

Geschützte Dateien (Feldoption „nur für angemeldete Frontend-User“): Der Verweis zeigt automatisch auf /file.php, das die Frontend-Session prüft. auth=true erzwingt diesen Weg auch für ungeschützte Felder, download="name" bietet die Datei zum Herunterladen an.

Parameter im Einzelnen

id

id liest aus einem anderen Datensatz statt aus dem laufenden - record_id oder Slug, genau wie in der URL:

(CMS:MEDIA image id=1977)
(CMS:MEDIA image id=impressum)

field

Name des Upload-Feldes; darf ohne Schlüssel stehen:

(CMS:MEDIA field=image)
(CMS:MEDIA image)

subfield

Einzelwert statt <img>-Tag (Liste siehe oben):

<img src="(CMS:MEDIA image subfield=path)" alt="(CMS:TEXT title specialchars=true)" loading="lazy">
Dateityp: (CMS:MEDIA document subfield=mime_type)

Einzelwerte über subfield werden nicht maskiert. path enthält nur URL-taugliche Zeichen und kann direkt in src oder href stehen. Den ursprünglichen Dateinamen (filename) wählt der Hochladende – in einem Attribut maskieren Sie ihn über CMS:TEXT:

(CMS:MEDIA document subfield=filename set=dateiname)
<a href="(CMS:MEDIA document subfield=path)" title="(CMS:TEXT useval="{$set['dateiname']}" specialchars=true lbrconvert=false)">…</a>

path und file unterscheiden sich im Zweck - Verweis für den Browser gegenüber Zugriff durch PHP:

(CMS:MEDIA document subfield=path)      /media/1/13/1977,agb.pdf?v=3
(CMS:MEDIA document subfield=file)      /var/www/example/public/media/1/13/1977.pdf
(CMS:MEDIA document subfield=file set=file)
<?php echo filesize($set['file']) > 5e6 ? 'große Datei' : ''; ?>

auth

Verweis über /file.php erzwingen - etwa, damit eine Vorschau denselben Zugriffsweg nimmt wie das geschützte Original:

<p>Vorschau: (CMS:MEDIA thumb)</p>
<p>Original (nur angemeldet): (CMS:MEDIA image auth=true)</p>

Bei Feldern mit der Option „nur für angemeldete Frontend-User“ gilt das automatisch.

download

Datei zum Herunterladen anbieten; download ist der Dateiname beim Speichern, die Endung wird ergänzt:

<a href="(CMS:MEDIA document subfield=path download="AGB-2026")">AGB herunterladen</a>

Ausgabe: <a href="/file.php?file=13/1977&v=2&download=AGB-2026">AGB herunterladen</a>

width

Breite des <img>-Tags; ohne Angabe die gespeicherte Bildbreite. Ein leerer Wert lässt das Attribut weg, der Browser rechnet dann proportional:

(CMS:MEDIA image width=300 height="")

Ausgabe: <img src="/media/1/12/1977,strand.jpg?v=3" alt="strand.jpg" width="300">

height

(CMS:MEDIA image width="" height=200)
(CMS:MEDIA image width="100%" height="")

attr

Weitere Attribute; Schlüssel = Attributname. Vorhandene (src, alt, width, height) lassen sich damit überschreiben:

(CMS:TEXT title striptags=true ampconvert=false lbrconvert=false set=title)
(CMS:MEDIA image attr[alt]="{$set['title']}" attr[class]="hero" attr[loading]="lazy")

CMS:MEDIA maskiert alle Attributwerte des <img>-Tags selbst mit htmlspecialchars – auch die aus attr. Übergeben Sie deshalb unmaskierten Text, ohne specialchars=true; sonst wird doppelt maskiert und aus & wird sichtbar &amp;. Aus dem Attributnamen entfernt der Befehl alle Zeichen außer Buchstaben, Ziffern, _ und -.

autoformat

Betrifft nur subfield=size: Standardmäßig lesbar (123.5 KB), mit false in Bytes:

(CMS:MEDIA document subfield=size)                   123.5 KB
(CMS:MEDIA document subfield=size autoformat=false)  126464

instead

Fester Text, wenn eine Datei vorhanden ist (das Tag bzw. der Einzelwert selbst wird nicht ausgegeben):

Bild: (CMS:MEDIA image instead="vorhanden" ifempty="fehlt")

ifempty

Ersatz, wenn keine Datei vorhanden ist (bzw. der Einzelwert leer oder 0):

(CMS:MEDIA image ifempty="<img src='/static/placeholder.png' alt=''>")
(CMS:MEDIA image subfield=width ifempty="unbekannt")

prefix

prefix / suffix umschließen den Wert und erscheinen nur, wenn er nicht leer ist - so entsteht kein leeres <figure></figure>:

(CMS:MEDIA image prefix="<figure>" suffix="</figure>")

suffix

(CMS:MEDIA image suffix="</figure>")

Siehe prefix.

useval

Statt eines Feldes ein Datei-Array übergeben - z. B. aus einem mit CMS:SAVEDATA gesicherten Datensatz. Parameterwerte im Template sind immer Strings; ein Array lässt sich deshalb nur per direktem PHP-Aufruf der Methode übergeben. field darf dabei nicht angegeben sein – es hätte Vorrang und läse das Feld des laufenden Datensatzes. Weil das Datei-Array die Feld-Id nicht enthält, gehören field_id und record_id ausdrücklich hinein:

(CMS:SAVEDATA author id="{$cms['author_id']}")
<?php $this->cms->cmsMedia([
    'useval' => $GLOBALS['author']['portrait'] + ['record_id' => $GLOBALS['author']['record_id'], 'field_id' => 17],
    'width'  => 80, 'height' => '',
]); ?>

17 steht hier für die Id des Upload-Feldes portrait. Einfacher ist fast immer (CMS:MEDIA portrait id="{$cms['author_id']}") - id lädt den fremden Datensatz direkt und findet das Feld über seinen Namen.

set

set gibt nichts aus, sondern legt das Ergebnis unter $set['img'] für das umgebende PHP oder einen späteren Befehl ab:

(CMS:MEDIA image subfield=path set=img)
<?php if ($set['img'] !== '') { ?> … <?php echo $set['img']; ?> … <?php } ?>

← Übersicht

CMS:MESSAGE

Erzeugt eine Textausgabe aus aktuellen Fehlermeldungen.

(CMS:MESSAGE useval [parameter=wert …])

Methode: Cms::cmsMessage()
Ohne Schlüssel: der erste Wert ohne = gilt als useval

Parameter

Parameter Wert Standard Pflicht Beschreibung
useval ¹ Text – Wert in Textform, der als Quelle genutzt wird.
prefix Text – Text, der vor den Meldungen ausgegeben wird, sofern es welche gibt.
suffix Text – Text, der nach den Meldungen ausgegeben wird, sofern es welche gibt.
ifempty Text – Text, falls keine Meldung vorhanden ist.
instead Text – Text, der anstelle der Meldungen ausgegeben wird.
set Array-Index von $set – Die Meldung wird nicht angezeigt, sondern in die Variable $set geschrieben.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Gibt die Meldungen aus, die Befehle dieser Seite abgelegt haben - typischerweise Feldfehler von CMS:USER_INPUT, CMS:USER_LOGIN oder CMS:USER_REGISTER. Ohne Meldung bleibt der Befehl still:

(CMS:USER_LOGIN container=members submit="$submit" successtemplate=welcome)
<p class="error">(CMS:MESSAGE)</p>

Mehrere Meldungen werden mit <br> getrennt. Eine Umrahmung gibt es nur über prefix/suffix – sie erscheint nur, wenn es Meldungen gibt:

(CMS:MESSAGE prefix="<div class=alert>" suffix="</div>")

Parameter im Einzelnen

useval

Eigenen Text statt der Systemmeldungen ausgeben (die Meldungen werden dann ignoriert; ist useval leer, erscheinen wieder die Meldungen):

(CMS:MESSAGE useval="{$set['hinweis']}" prefix="<p class=hint>" suffix="</p>")

Der Text wird unverändert ausgegeben. Setzen Sie hier keine Werte aus dem Request ($_GET, $_POST) ein – dafür gibt es (CMS:TEXT useval="…" specialchars=true).

prefix

(CMS:MESSAGE prefix="<div class=alert>" suffix="</div>")

Ohne Angabe werden die Meldungen ohne Umrahmung ausgegeben.

suffix

(CMS:MESSAGE prefix="<ul class=errors><li>" suffix="</li></ul>")

ifempty

Text, wenn keine Meldung vorliegt:

(CMS:MESSAGE ifempty="Alles in Ordnung.")

instead

Fester Text, sobald eine Meldung vorliegt - wenn der Wortlaut nicht angezeigt werden soll:

(CMS:MESSAGE instead="Bitte prüfen Sie Ihre Eingaben.")

set

Meldungen für PHP bereitstellen, etwa um sie neben dem jeweiligen Feld zu zeigen:

(CMS:MESSAGE set=errors)
<?php if ($set['errors'] !== '') { ?><div class="alert"><?php echo $set['errors']; ?></div><?php } ?>

← Übersicht

CMS:NUMBER

Gibt einen Zahlenwert formatiert aus.

(CMS:NUMBER field [parameter=wert …])

Methode: Cms::cmsNumber()
Ohne Schlüssel: der erste Wert ohne = gilt als field
Mindestens einer von: field, useval

Parameter

Parameter Wert Standard Pflicht Beschreibung
id Datensatz-Id oder Slug (aktueller Datensatz) Datensatz, aus dem gelesen wird. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL.
field ¹ Feld – ² Name eines Datensatzfeldes, dessen Inhalt als Quelle genutzt wird.
useval Zahl – ² Wert in Textform, der als Quelle genutzt wird.
ifempty Text – Text, der ausgegeben wird, wenn die Quelle leer ist oder der Zahlenwert (nach mul/add) 0 ergibt.
instead Zahl – Zahl, die zur Quelle wird, sofern die ursprüngliche Quelle nicht leer ist.
mul Zahl 1 Zahl, mit der die Quelle multipliziert wird.
add Zahl 0 Zahl, die zur Quelle addiert wird.
format Formatierung – Formatierung analog zum PHP-Befehl sprintf().
decimals Zahl 0-10 – Feste Anzahl Nachkommastellen (gerundet); decimal und thousands_separator wirken weiter. Ohne Angabe so viele, wie die Zahl hat.
decimal Zeichen . Zeichen, durch das der Dezimalpunkt ersetzt wird.
thousands_separator Zeichen – Zeichen, das als Tausendertrennzeichen (alle drei Stellen von rechts nach links) eingefügt wird.
nullcent Text – Text, durch den ein fehlender Nachkommawert ersetzt wird.
replace[…] Text?Text – Kommt der Text vor dem Fragezeichen im formatierten Wert vor, wird er durch den Text nach dem Fragezeichen ersetzt (z. B. ".00?" entfernt leere Nachkommastellen).
prefix Text – Text, der dem formatierten Wert vorangestellt wird, sofern dieser nicht "0" oder leer ist.
suffix Text – Text, der dem formatierten Wert angehängt wird, sofern dieser nicht "0" oder leer ist.
set Array-Index von $set – Der formatierte Wert wird nicht ausgegeben, sondern stattdessen in der Variable $set gespeichert.
sizefactor Anzahl Bytes eines Kilobytes – 1000 oder 1024. Wenn > 0, wird eine lesbare Dateigröße berechnet (z. B. 15.2 GB).

¹ kann ohne Schlüssel als erster Wert angegeben werden.
² mindestens einer der mit ² markierten Parameter muss angegeben werden.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Gibt einen Zahlenwert formatiert aus. Ohne weitere Parameter erscheint die Zahl so, wie sie gespeichert ist - mit Punkt als Dezimaltrenner und ohne Tausendertrennzeichen:

(CMS:NUMBER price)                                        1234.5
(CMS:NUMBER price decimal="," thousands_separator=".")    1.234,5
(CMS:NUMBER price decimals=2 decimal="," thousands_separator="." suffix=" €")   1.234,50 €

Rechnen vor der Ausgabe (mul, add), Dateigrößen lesbar machen (sizefactor):

(CMS:NUMBER price mul=1.19 decimals=2 decimal="," suffix=" € inkl. MwSt.")
(CMS:NUMBER filesize sizefactor=1024)                     15.2 MB

Ergibt die Rechnung 0, erscheint 0 (formatiert, z. B. 0,00 mit decimals=2):

(CMS:NUMBER useval="{$a}" add=-1)                          0  (bei $a = 1)

Sobald prefix, suffix oder ifempty angegeben ist, gilt der Wert 0 dagegen als leer - eine Preisangabe mit 0 bleibt also ohne €. Maßgeblich ist der Zahlenwert nach mul/add, nicht der formatierte Text: auch 0.00 (mit decimals oder format) gilt dann als leer. Eine leere Quelle (Feld ohne Inhalt, leeres useval) ergibt immer eine leere Ausgabe - auch mit mul/add.

Parameter im Einzelnen

id

id liest aus einem anderen Datensatz statt aus dem laufenden - record_id oder Slug, genau wie in der URL:

(CMS:NUMBER price id=1977)
(CMS:NUMBER price id=impressum)

field

field ist der Hauptparameter und darf ohne Schlüssel stehen - beide Zeilen sind gleich:

(CMS:NUMBER field=price)
(CMS:NUMBER price)

useval

useval nimmt statt eines Feldes einen Wert direkt aus dem Template - meist eine PHP-Variable oder ein zuvor mit set abgelegter Wert:

(CMS:NUMBER useval="{$this->db->num_rows_unlimited}")
(CMS:NUMBER useval="{$set['wert']}")

Komma als Dezimaltrenner wird verstanden: useval="3,5" ergibt 3.5.

ifempty

Mit ifempty gilt auch der Zahlenwert 0 als leer - egal, wie er formatiert würde:

(CMS:NUMBER stock ifempty="ausverkauft" suffix=" Stück")

Ausgabe bei stock = 0: ausverkauft - bei 12: 12 Stück

instead

instead ersetzt die Quelle (nicht das Ergebnis), wenn sie nicht leer ist - Rechnung und Formatierung laufen danach auf dem neuen Wert:

(CMS:NUMBER price instead=99 decimal=",")

mul

(CMS:NUMBER price mul=1.19 decimals=2 decimal="," suffix=" €")   Bruttopreis
(CMS:NUMBER share mul=100 suffix=" %")                           0.25 → 25 %

add

(CMS:NUMBER price add=4.90 decimals=2 decimal="," suffix=" € inkl. Versand")

mul wird vor add angewendet: Ergebnis = Quelle × mul + add.

format

sprintf()-Muster; es bestimmt die Ausgabe allein, decimal, thousands_separator, decimals und nullcent bleiben dann außen vor. Für Preise mit Dezimalkomma und Tausenderpunkt ist deshalb decimals der richtige Parameter, format für Sonderfälle wie führende Nullen:

(CMS:NUMBER price format="%.2f")        1234.50
(CMS:NUMBER position format="%03d")     007

decimals

Feste Anzahl Nachkommastellen, kaufmännisch gerundet - die Trennzeichen aus decimal und thousands_separator gelten weiter:

(CMS:NUMBER price decimals=2 decimal="," thousands_separator=".")

Ausgabe bei 1234.5: 1.234,50 - bei 19.999: 20,00

Ein Betrag in Cent (Feldtyp Währung) wird mit mul=0.01 zu Euro:

(CMS:NUMBER cur_preis mul=0.01 decimals=2 decimal="," suffix=" €")

decimal

(CMS:NUMBER price decimal=",")

Ausgabe: 1234,5

thousands_separator

(CMS:NUMBER views thousands_separator=".")
(CMS:NUMBER views thousands_separator=" ")

Ausgabe: 1.234.567 bzw. 1 234 567

nullcent

Ersatz für fehlende Nachkommastellen (ohne Angabe: keiner) - für Preise, die immer zwei Stellen zeigen sollen:

(CMS:NUMBER price decimal="," nullcent="00" suffix=" €")

Ausgabe bei 1234: 1234,00 € - bei 1234.5 bleibt 1234,5 € (nur fehlende Nachkommastellen werden ersetzt; für festes Runden decimals=2 nutzen).

replace

replace ersetzt Teiltexte im fertig formatierten Wert - Kurzform mit ? als Trenner oder ein Eintrag je Suchtext:

(CMS:NUMBER price replace=".00?")
(CMS:NUMBER price replace[.00]="" replace[x]="y")

Ausgabe bei 12.00: 12

prefix

Erscheint nur, wenn der formatierte Wert weder leer noch 0 ist:

(CMS:NUMBER discount prefix="- " suffix=" %")

suffix

(CMS:NUMBER price suffix=" €")

Siehe prefix.

set

set gibt nichts aus, sondern legt das Ergebnis unter $set['price'] für das umgebende PHP oder einen späteren Befehl ab:

(CMS:NUMBER price set=price)
<?php if ($set['price'] !== '') { ?> … <?php echo $set['price']; ?> … <?php } ?>

Der gespeicherte Wert ist ein String in der formatierten Schreibweise.

sizefactor

Dateigröße lesbar machen (Bytes → KB / MB / GB); 1024 für binäre, 1000 für dezimale Einheiten:

(CMS:NUMBER filesize sizefactor=1024)      15.2 MB
(CMS:NUMBER filesize sizefactor=1000)      15.9 MB

Für Upload-Felder geht das auch direkt mit (CMS:MEDIA image subfield=size).


← Übersicht

CMS:RAW

Gibt einen Inhalt unformatiert aus.

(CMS:RAW field [parameter=wert …])

Methode: Cms::cmsRaw()
Ohne Schlüssel: der erste Wert ohne = gilt als field

Parameter

Parameter Wert Standard Pflicht Beschreibung
id Datensatz-Id oder Slug (aktueller Datensatz) Datensatz, aus dem gelesen wird. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL.
field ¹ Feld – ja Name des Feldes, dessen Inhalt ausgegeben wird.
ifempty Text – Text, der ausgegeben wird, falls das Feld leer ist.
instead Text – Text, der anstelle des Feldinhalts ausgegeben wird, sofern dieser nicht leer ist.
prefix Text – Text, der vorangestellt wird, sofern der Feldinhalt nicht leer ist.
suffix Text – Text, der angehängt wird, sofern der Feldinhalt nicht leer ist.
set Array-Index von $set – Der Inhalt wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Gibt den Feldinhalt ohne jede Aufbereitung aus - kein Maskieren, keine Umwandlung von Zeilenumbrüchen, keine Entities. Gedacht für Felder, die fertiges HTML enthalten (Editor-Felder) oder für Werte, die unverändert in PHP/JavaScript landen sollen:

<div class="content">(CMS:RAW html_text)</div>

Dem Inhalt muss man vertrauen: Was ein Redakteur ins Feld schreibt, landet unverändert auf der Seite – bis auf aktive Inhalte (<script>, <iframe>, Event-Handler, javascript:-Links), die camindo CMS schon beim Speichern und beim Lesen aus der Datenbank entfernt. Für Texte aus Benutzereingaben (ugc-Bereiche) CMS:TEXT mit specialchars=true verwenden.

Die Ausgabeparameter ifempty, instead, prefix, suffix und set wirken wie bei allen Ausgabebefehlen.

Parameter im Einzelnen

id

id liest aus einem anderen Datensatz statt aus dem laufenden - record_id oder Slug, genau wie in der URL:

(CMS:RAW html_text id=1977)
(CMS:RAW html_text id=impressum)

field

field ist der Hauptparameter und darf ohne Schlüssel stehen - beide Zeilen sind gleich:

(CMS:RAW field=html_text)
(CMS:RAW html_text)

ifempty

ifempty ersetzt die gesamte Ausgabe, wenn die Quelle leer ist; prefix und suffix entfallen dann:

(CMS:RAW html_text prefix="<p>" suffix="</p>" ifempty="<p>Kein Inhalt.</p>")

instead

instead tauscht den Wert aus, sobald die Quelle nicht leer ist - nützlich, wenn nur das Vorhandensein zählt:

(CMS:RAW html_text instead="vorhanden" ifempty="-")

prefix

prefix / suffix umschließen den Wert und erscheinen nur, wenn er nicht leer ist - so entsteht kein leeres <section></section>:

(CMS:RAW html_text prefix="<section>" suffix="</section>")

suffix

(CMS:RAW html_text suffix="</section>")

Siehe prefix.

set

set gibt nichts aus, sondern legt das Ergebnis unter $set['html'] für das umgebende PHP oder einen späteren Befehl ab:

(CMS:RAW html_text set=html)
<?php if ($set['html'] !== '') { ?> … <?php echo $set['html']; ?> … <?php } ?>

← Übersicht

CMS:SNIPPET

Gibt ein Snippet (Textbaustein) in der Sprache des Aufrufs aus.

(CMS:SNIPPET key [parameter=wert …])

Methode: Cms::cmsSnippet()
Ohne Schlüssel: der erste Wert ohne = gilt als key

Parameter

Parameter Wert Standard Pflicht Beschreibung
key ¹ Snippet-Schlüssel – ja Schlüssel des Snippets, z. B. custom.weiterlesen (Snippet-Set der Instanz) oder ein Core-Snippet ohne Präfix.
param[…] Text – Werte für Platzhalter: param[name]=Wert ersetzt {name} im Snippet und füllt ICU-Parameter (plural, select).
specialchars true / false false true: Sonderzeichen werden in ihr HTML-Äquivalent umgewandelt (für Attribute).
ifempty Text – Text, der ausgegeben wird, wenn es das Snippet nicht gibt (ohne ifempty erscheint der Schlüssel).
prefix Text – Text, der vorangestellt wird, sofern das Snippet nicht leer ist.
suffix Text – Text, der angehängt wird, sofern das Snippet nicht leer ist.
set Array-Index von $set – Der Text wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden. […] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Feste Beschriftungen einer Website – „Weiterlesen“, „In den Warenkorb“, Überschriften von Listen – pflegt die Redaktion als Snippets der Instanz in allen Sprachen (Modul Snippets, siehe Handbuch für Administratoren). Das Template gibt sie mit CMS:SNIPPET aus, immer in der Sprache des Aufrufs; fehlt eine Übersetzung, erscheint der englische Text (en_US):

<a class="more" href="(CMS:URL)">(CMS:SNIPPET custom.weiterlesen)</a>

custom. meint das Snippet-Set der Instanz. Ohne Präfix sind die Snippets des Cores gemeint – im Frontend nur die allgemeinen, nicht die der Backend-Oberfläche.

In einem Attribut maskieren Sie den Text mit specialchars=true:

<button aria-label="(CMS:SNIPPET custom.menue specialchars=true)">…</button>

Platzhalter. param[name] ersetzt {name} im Snippet. So bleibt die Wortstellung Sache der Übersetzung – etwa das Währungszeichen vor oder hinter dem Betrag:

Snippet custom.preis:   en "€{betrag}"   de "{betrag} €"
(CMS:NUMBER preis mul=0.01 format="%.2f" set=betrag)
(CMS:SNIPPET custom.preis param[betrag]="{$set['betrag']}")

Beginnt ein Snippet mit einer ICU-Nachricht ({anzahl, plural, one {# Artikel} other {# Artikel}}), füllt param deren Werte:

(CMS:SNIPPET custom.treffer param[anzahl]="{$set['count']}")

Schlüssel aus dem Datensatz. Der Schlüssel darf PHP-Variablen enthalten – etwa, um einen Auswahlwert zu übersetzen:

(CMS:SNIPPET "custom.status_{$cms['status']}" ifempty="–")

Gibt es das Snippet nicht, steht der Schlüssel selbst auf der Seite – so fällt ein fehlender Text beim Entwickeln auf. ifempty gibt stattdessen einen Ersatz aus.

Innerhalb eines Parameters eines anderen Befehls lässt sich CMS:SNIPPET nicht verschachteln; dort liefert {$this->cms->snippet('custom.weiterlesen')} den Text:

(CMS:TEXT teaser suffix=" {$this->cms->snippet('custom.weiterlesen')}")

← Übersicht

CMS:TEXT

Gibt einen Text formatiert aus.

(CMS:TEXT field [parameter=wert …])

Methode: Cms::cmsText()
Ohne Schlüssel: der erste Wert ohne = gilt als field
Mindestens einer von: field, useval, id

Parameter

Parameter Wert Standard Pflicht Beschreibung
id Datensatz-Id oder Slug – ² Datensatz, aus dem gelesen wird. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL. Ohne 'field' wird das Feld "text" gelesen.
field ¹ Feld – ² Name eines Datensatzfeldes, dessen Inhalt als Quelle genutzt wird.
useval Text – ² Wert in Textform, der als Quelle genutzt wird.
ifempty Text – Text, der zur Quelle wird, wenn die ursprüngliche Quelle leer ist (weitere Parameter, außer 'set', werden dann ignoriert).
instead Text – Text, der zur Quelle wird, wenn die ursprüngliche Quelle nicht leer ist.
entitydecode true / false true true: Entities im Format &...; werden zurück in Unicode-Zeichen gewandelt.
trim true / false false true: Leerzeichen am Textanfang und -ende werden entfernt.
striptags true / false false true: HTML- und Script-Tags werden entfernt.
if[…] Text?Text – Wenn die Quelle dem Text vor dem Fragezeichen entspricht, wird der Text nach dem Fragezeichen zur neuen Quelle.
else Text – Trifft keine der vorherigen IF-Abfragen zu, wird dieser Wert zur Quelle.
replace[…] Text?Text – Kommt der Text vor dem Fragezeichen in der Quelle vor, wird er durch den Text nach dem Fragezeichen ersetzt.
limit Zahl >=0 – Kürzt den Text auf die angegebene Anzahl an Zeichen und hängt "…" an; mit angehängtem s (z. B. 200s) wird am letzten Wortende gekürzt.
lbrconvert true / false true true: Zeilenumbrüche werden in HTML-Zeilenumbrüche (<br>-Tags) umgewandelt.
specialchars true / false false true: Sonderzeichen werden in ihr HTML-Äquivalent umgewandelt.
urlconvert true / false false true: Internet-Adressen werden automatisch in klickbare Links umgewandelt.
emailconvert true / false / encrypt false true: E-Mail-Adressen werden verlinkt. encrypt: Die Links werden mittels JavaScript leicht verschlüsselt.
emaillimit >= 0 – Kürzt automatisch verlinkte E-Mail-Adressen auf x sichtbare Zeichen.
emailtext Text – Verlinkter Text, der anstelle einer E-Mail-Adresse angezeigt wird (nur bei aktiviertem "emailconvert").
target Fenster / Frame _self Setzt das Link-Target für automatisch erzeugte Links (urlconvert, emailconvert).
markdown true / false false true: Der Text ist Markdown und wird in HTML umgewandelt (HTML im Text wird maskiert); die übrigen Umwandlungen entfallen.
urlencode true / false false true: Der Text wird nach RFC 3986 kodiert (rawurlencode) – für URL-Bestandteile, auch in Attributen ohne weitere Maskierung.
urllimit Anzahl Zeichen 80 Maximale Länge des sichtbaren Textes für automatisch erzeugte Links (nur für externe Links geeignet!).
ampconvert true / false true true: Ein alleinstehendes & wird automatisch in das HTML-Entity & umgewandelt.
prefix Text – Text, der der Quelle vorangestellt wird, sofern diese nicht leer ist.
suffix Text – Text, der an die Quelle angehängt wird, sofern diese nicht leer ist.
iso true / false false true: Die Ausgabe wird in den Zeichensatz ISO-8859-1 konvertiert.
charset Text utf-8 Zeichensatz, in den die Ausgabe konvertiert werden soll.
set Array-Index von $set – Der formatierte Wert wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.
² mindestens einer der mit ² markierten Parameter muss angegeben werden.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Der Standardbefehl für Feldinhalte. Er liest ein Feld des laufenden Datensatzes, wandelt Zeilenumbrüche in <br>, maskiert alleinstehende & und gibt das Ergebnis aus:

<h1>(CMS:TEXT title)</h1>
<p>(CMS:TEXT text)</p>

Ein Feld nur ausgeben, wenn es gefüllt ist - prefix/suffix erscheinen nur bei nicht leerem Wert:

(CMS:TEXT teaser prefix="<p class=teaser>" suffix="</p>")

Wert für PHP zwischenspeichern statt ausgeben:

(CMS:TEXT title set=title)
<title><?php echo $set['title']; ?> – Beispiel GmbH</title>

Statuswert in Text übersetzen:

(CMS:TEXT status_id if[0]="online" if[1]="gesperrt" if[2]="unbestätigt" else="unbekannt")

Reihenfolge der Verarbeitung: Quelle → if/else → replace → trim → striptags → limit → (markdown bzw. urlencode beenden hier) → entitydecode → specialchars bzw. ampconvert → urlconvert / emailconvert → lbrconvert → iso/charset → ifempty / instead / prefix / suffix → Ausgabe oder set.

Parameter im Einzelnen

id

id liest aus einem anderen Datensatz - record_id oder Slug, wie in der URL. Nur bei CMS:TEXT darf id allein stehen: dann wird das Feld text gelesen. Wie in der URL muss der Datensatz veröffentlicht sein (freigegeben, Veröffentlichungsdatum erreicht; ein abgelaufener bleibt lesbar), sonst ist der Wert leer – auch in der Vorschau des Backends.

(CMS:TEXT title id=1977)          Titel des Datensatzes 1977
(CMS:TEXT id=impressum)           Feld "text" des Datensatzes mit dem Slug "impressum"

field

field ist der Hauptparameter und darf ohne Schlüssel stehen - beide Zeilen sind gleich:

(CMS:TEXT field=title)
(CMS:TEXT title)

useval

useval nimmt statt eines Feldes einen Wert direkt aus dem Template - meist eine PHP-Variable oder ein zuvor mit set abgelegter Wert:

(CMS:TEXT useval="{$_GET['q']}" specialchars=true)
(CMS:TEXT useval="{$set['wert']}")

Werte aus dem Request ($_GET, $_POST) immer mit specialchars=true ausgeben – sonst kann ein präparierter Link beliebiges HTML und Script in die Seite setzen.

Damit lassen sich die Text-Filter (striptags, specialchars, limit …) auch auf fremde Werte anwenden.

ifempty

ifempty ersetzt die gesamte Ausgabe, wenn die Quelle leer ist; prefix und suffix entfallen dann:

(CMS:TEXT teaser prefix="<p>" suffix="</p>" ifempty="Kein Teaser vorhanden.")

Achtung: Ist die Quelle leer, werden auch alle Umwandlungen übersprungen - der ifempty-Text wird unverändert ausgegeben.

instead

instead tauscht den Wert aus, sobald die Quelle nicht leer ist - nützlich, wenn nur das Vorhandensein zählt:

(CMS:TEXT image instead="✔ Bild vorhanden" ifempty="-")

entitydecode

Standardmäßig werden Entities (&amp;, &auml;, &#8211;) zurück in Zeichen gewandelt. Für Inhalte, die die Entities behalten sollen (z. B. für XML-Feeds):

(CMS:TEXT text entitydecode=false)

trim

(CMS:TEXT title trim=true prefix="[" suffix="]")

Ausgabe: [Hallo Welt] statt [ Hallo Welt ]

striptags

HTML- und Script-Tags entfernen, etwa für Teaser aus einem HTML-Feld oder für Meta-Tags. Jedes Tag wird durch ein Leerzeichen ersetzt (aus <p>a</p><p>b</p> wird a b), der Text zwischen den Tags bleibt stehen:

<meta name="description" content="(CMS:TEXT text striptags=true limit=160 lbrconvert=false)">

if

Wertetabelle: Trifft der Wert einen Schlüssel, wird der Eintrag zur neuen Quelle. Zwei Schreibweisen - Unterschlüssel oder Kurzform mit Fragezeichen:

(CMS:TEXT status_id if[0]="online" if[1]="gesperrt")
(CMS:TEXT status_id if="0?online" if[]="1?gesperrt")

Nicht getroffene Werte bleiben unverändert - außer else ist angegeben.

Enthält der Ersatztext eines Ziffern-Schlüssels selbst ein Fragezeichen, nehmen Sie die Kurzform: if[]="1?Wirklich?". Bei if[1]="Wirklich?" wäre nicht zu erkennen, ob 1 der Suchwert ist oder die Kurzform Wirklich → `gemeint war (in PHP sind beide Schreibweisen der Schlüssel1). Für Text-Schlüssel (if[aktiv]="Ja?"`) gilt das nicht.

else

(CMS:TEXT status_id if[0]="online" else="nicht online")

Ausgabe bei status_id = 2: nicht online

replace

Teiltexte in der Quelle ersetzen - Kurzform mit ? oder ein Eintrag je Suchtext:

(CMS:TEXT text replace="\n?<br><br>" lbrconvert=false)
(CMS:TEXT title replace[GmbH]="Gesellschaft mbH" replace[&]="und")

Wie bei if gilt: Ersatztexte mit Fragezeichen für einen Ziffern-Suchtext in der Kurzform schreiben (replace[]="0?null?").

limit

Text auf eine Zeichenzahl kürzen; ein angehängtes s kürzt am letzten Wortende („soft“). Angehängt wird das Snippet ellipsis (…):

<p>(CMS:TEXT text striptags=true limit=200s)</p>
<td>(CMS:TEXT title limit=30)</td>

lbrconvert

Standardmäßig werden Zeilenumbrüche zu <br>. Für Inhalte in <pre>, in Attributen oder in JavaScript abschalten:

<pre>(CMS:TEXT code lbrconvert=false)</pre>

specialchars

Alle Sonderzeichen maskieren (< → &lt;, " → &quot; …) - für Attribute und für Inhalte, die kein HTML enthalten dürfen:

<input value="(CMS:TEXT title specialchars=true lbrconvert=false)">

urlconvert

Adressen (http://…, https://…, www.…) im Text klickbar machen:

(CMS:TEXT text urlconvert=true target=_blank urllimit=24)

Ausgabe: <a href="https://example.com/sehr/lange/adresse" target="_blank">https://example.com/sehr…</a>

emailconvert

E-Mail-Adressen verlinken; encrypt setzt den Link per JavaScript zusammen, sodass die Adresse nicht im Quelltext steht. Das <script> dafür trägt die Nonce der Seite und läuft deshalb auch unter einer durchgesetzten Script-Policy (CMS:CSP):

(CMS:TEXT contact emailconvert=true)
(CMS:TEXT contact emailconvert=encrypt emailtext="E-Mail schreiben")

emaillimit

Sichtbaren Teil einer verlinkten Adresse kürzen:

(CMS:TEXT contact emailconvert=true emaillimit=12)

Ausgabe: <a href="mailto:redaktion@example.com">redaktion@ex…</a>

emailtext

Fester Linktext statt der Adresse (nur mit emailconvert):

(CMS:TEXT contact emailconvert=true emailtext="Kontakt aufnehmen")

target

Link-Target für die von urlconvert erzeugten Links:

(CMS:TEXT text urlconvert=true target=_blank)

markdown

Gibt ein Feld aus, das Markdown enthält – etwa eines mit der Extension Markdown (visuell) – und wandelt es in HTML um:

<div class="text">(CMS:TEXT text markdown=true)</div>

Umgewandelt wird mit Parsedown; HTML im Text wird maskiert, Links auf javascript: und ähnliche Ziele werden entschärft. Zusätzlich zum üblichen Markdown versteht der Parameter die Formate der Extension Markdown (visuell):

Markdown Ergebnis
++Text++ unterstrichen (<u>)
::: center … ::: die eingeschlossenen Absätze zentriert (left, center, right; als <div style="text-align: …">)
<br> Zeilenumbruch, auch in Tabellenzellen
&shy;, &nbsp; … bleiben Sonderzeichen

markdown beendet die Verarbeitung: entitydecode, specialchars, lbrconvert, urlconvert usw. entfallen. prefix, suffix, ifempty und set wirken wie gewohnt.

urlencode

Den Wert als URL-Bestandteil kodieren. Alle weiteren Umwandlungen entfallen dann:

<a href="/suche?q=(CMS:TEXT title urlencode=true)">ähnliche Artikel</a>

Ausgabe: /suche?q=Hallo%20Welt

urllimit

Sichtbare Länge automatisch verlinkter Adressen (Standard 80 Zeichen, 0 = ungekürzt):

(CMS:TEXT text urlconvert=true urllimit=30)

ampconvert

Standardmäßig wird ein alleinstehendes & zu &amp;, vorhandene Entities bleiben. Abschalten, wenn der Inhalt bereits vollständig maskiert ist:

(CMS:TEXT html_snippet ampconvert=false)

prefix

prefix / suffix umschließen den Wert und erscheinen nur, wenn er nicht leer ist - so entsteht kein leeres <p class=teaser></p>:

(CMS:TEXT teaser prefix="<p class=teaser>" suffix="</p>")

suffix

(CMS:TEXT author_name suffix=" (Autor)")

Siehe prefix.

iso

Ausgabe nach ISO-8859-1 umkodieren (Kurzform für charset=ISO-8859-1), etwa für alte Schnittstellen:

(CMS:TEXT title iso=true)

charset

(CMS:TEXT title charset=windows-1252)

set

set gibt nichts aus, sondern legt das Ergebnis unter $set['title'] für das umgebende PHP oder einen späteren Befehl ab:

(CMS:TEXT title set=title)
<?php if ($set['title'] !== '') { ?> … <?php echo $set['title']; ?> … <?php } ?>

← Übersicht

CMS:TIME

Gibt die Uhrzeit eines Datums- oder Zeitwertes formatiert aus.

(CMS:TIME field [parameter=wert …])

Methode: Cms::cmsTime()
Ohne Schlüssel: der erste Wert ohne = gilt als field
Mindestens einer von: field, useval

Parameter

Parameter Wert Standard Pflicht Beschreibung
id Datensatz-Id oder Slug (aktueller Datensatz) Datensatz, aus dem gelesen wird. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL.
field ¹ Feld – ² Name eines Datensatzfeldes, dessen Inhalt als Quelle genutzt wird.
useval Zeitwert – ² Wert in Textform, der als Quelle genutzt wird.
format Formatangaben – ICU-Muster, z. B. "HH:mm". Ohne Angabe entscheidet die Sprache der Seite über die Schreibweise; volle Minuten werden ohne Sekunden gezeigt.
timezone true / false false true: Der Name der Zeitzone wird angehängt (z. B. "Europe/Berlin").
replace[…] Text?Text – Kommt der Text vor dem Fragezeichen im formatierten Wert vor, wird er durch den Text nach dem Fragezeichen ersetzt (z. B. "Jan?Januar").
ifempty Text – Text, der ausgegeben wird, falls die Quelle ('field' oder 'useval') leer ist.
instead Text – Text, der anstelle der Quelle ausgegeben wird, sofern diese nicht leer ist.
prefix Text – Text, der vorangestellt wird, sofern der formatierte Wert nicht leer ist.
suffix Text – Text, der angehängt wird, sofern der formatierte Wert nicht leer ist.
set Array-Index von $set – Der formatierte Wert wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.
² mindestens einer der mit ² markierten Parameter muss angegeben werden.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Gibt nur die Uhrzeit eines Datums-/Zeitwertes aus:

(CMS:TIME event_at)                    14:30
(CMS:TIME event_at format="HH:mm:ss")  14:30:00
(CMS:TIME event_at suffix=" Uhr")      14:30 Uhr

Ohne format werden volle Minuten ohne Sekunden gezeigt.

Parameter im Einzelnen

id

id liest aus einem anderen Datensatz statt aus dem laufenden - record_id oder Slug, genau wie in der URL:

(CMS:TIME event_at id=1977)
(CMS:TIME event_at id=impressum)

field

field ist der Hauptparameter und darf ohne Schlüssel stehen - beide Zeilen sind gleich:

(CMS:TIME field=event_at)
(CMS:TIME event_at)

useval

useval nimmt statt eines Feldes einen Wert direkt aus dem Template - meist eine PHP-Variable oder ein zuvor mit set abgelegter Wert:

(CMS:TIME useval="2024-03-07 14:30:00")
(CMS:TIME useval="{$set['wert']}")

format

format ist ein ICU-Muster (d Tag, MM Monat zweistellig, MMMM Monatsname, y Jahr, HH:mm Uhrzeit, EEEE Wochentag). Ohne format entscheidet die Sprache der Seite (im Backend die Locale des Benutzers):

(CMS:TIME online_at format="HH:mm")

Ausgabe: 14:30

h:mm a ergibt die 12-Stunden-Schreibweise (2:30 PM).

timezone

(CMS:TIME event_at timezone=true)

Ausgabe: 14:30 Europe/Berlin

replace

replace ersetzt Teiltexte im fertig formatierten Wert - Kurzform mit ? als Trenner oder ein Eintrag je Suchtext:

(CMS:TIME event_at replace=":?.")
(CMS:TIME event_at replace[:]="." replace[x]="y")

Ausgabe: 14.30

ifempty

ifempty ersetzt die gesamte Ausgabe, wenn die Quelle leer ist; prefix und suffix entfallen dann:

(CMS:TIME event_at prefix="<p>" suffix="</p>" ifempty="ganztägig")

instead

instead tauscht den Wert aus, sobald die Quelle nicht leer ist - nützlich, wenn nur das Vorhandensein zählt:

(CMS:TIME event_at instead="mit Uhrzeit" ifempty="-")

prefix

prefix / suffix umschließen den Wert und erscheinen nur, wenn er nicht leer ist - so entsteht kein leeres ab Uhr:

(CMS:TIME event_at prefix="ab " suffix=" Uhr")

suffix

(CMS:TIME event_at suffix=" Uhr")

Siehe prefix.

set

set gibt nichts aus, sondern legt das Ergebnis unter $set['time'] für das umgebende PHP oder einen späteren Befehl ab:

(CMS:TIME event_at set=time)
<?php if ($set['time'] !== '') { ?> … <?php echo $set['time']; ?> … <?php } ?>

← Übersicht

CMS:URL

Erzeugt die URL eines Datensatzes.

(CMS:URL id [parameter=wert …])

Methode: Cms::cmsUrl()
Ohne Schlüssel: der erste Wert ohne = gilt als id

Parameter

Parameter Wert Standard Pflicht Beschreibung
id ¹ Datensatz-Id oder Slug (aktueller Datensatz) Datensatz, auf den verwiesen werden soll. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL.
area Bereichsname oder -Id – Verweis auf die Übersicht des Bereichs: die Adresse aus seinem Slug-Präfix (/de/termine), die sein Übersichts-Template zeigt.
template Templatename – Template, mit dem der Datensatz gezeigt werden soll (zweiter URL-Teil nach dem Komma): Name oder bereich.name. Ohne Angabe das Template, das der Bereich zuweist.
lang Sprachkürzel (aktuelle Sprache) Sprachversion, auf die verwiesen werden soll (standardmäßig die des laufenden Aufrufs). Führt die Instanz mehr als eine Sprache, steht das Kürzel immer in der URL; bei nur einer Sprache nie. Ein mehrsprachiger Slug wird in der Zielsprache eingesetzt. Ein Kürzel, das die Website nicht führt, wird übergangen.
protocol Übertragungsprotokoll (Protokoll der Instanz) http oder https, nur mit absolute bzw. host. Ohne Angabe das Protokoll der Instanz, sonst das des Aufrufs.
host Hostname – z. B. example.com für den Wechsel der Domain, über die die Website aufgerufen wird.
absolute true / false false true: Der Pfad wird inkl. Protokoll und Domain erzeugt. false: Der Pfad ist serverrelativ und beginnt mit einem Slash.
set Array-Index von $set – Die URL wird nicht ausgegeben, sondern in $set eingetragen.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Erzeugt die Frontend-URL eines Datensatzes - mit Slug, wenn er einen hat, sonst mit der record_id; bei mehrsprachigen Auftritten immer mit Sprachsegment:

<a href="(CMS:URL)">(CMS:TEXT title)</a>

Ausgabe: <a href="/de/hallo-welt">Hallo Welt</a>

Auf eine andere Ansicht desselben Datensatzes verweisen (template), auf einen anderen Datensatz (id) oder in eine andere Sprache (lang):

(CMS:URL template=print)                /de/hallo-welt,print
(CMS:URL 1977)                          /de/1977   (Datensatz ohne Slug)
(CMS:URL impressum lang=en)             /en/legal-notice   (Slug der Zielsprache)
(CMS:URL farben/blau)                   /de/farben/blau   (Bereich mit Slug-Prefix)

Führt der Inhaltsbereich des Datensatzes einen Slug-Prefix (Einstellung des Bereichs, z. B. farben), steht er vor dem Slug: /de/farben/blau. Der Präfix gilt je Sprache – mit lang also der Präfix der Zielsprache. Ein id mit Slug meint den Pfad, wie er in der Adresse steht (id=farben/blau); id=blau findet nur einen Datensatz eines Bereichs ohne Präfix.

Sprachumschalter - in einem CMS:SELECT_LANGVERSION-Listentemplate. Die Zeilen dort sind Sprachen, keine Datensätze; CMS:URL ohne id verweist deshalb auf den Datensatz der Seite:

<a href="(CMS:URL lang="{$cms['code']}")">(CMS:TEXT endonym)</a>

Absolute URL für Feeds, E-Mails oder og:url:

<link rel="canonical" href="(CMS:URL absolute=true)">

Parameter im Einzelnen

id

Datensatz, auf den verwiesen wird; ohne Angabe der laufende. Darf ohne Schlüssel stehen:

(CMS:URL 1977)
(CMS:URL id=impressum)
(CMS:URL "{$cms['parent_id']}")       Verweis auf den Elternknoten

area

Verweist auf die Übersicht eines Bereichs: die Adresse, die nur aus seinem Slug-Präfix besteht, in der Sprache des Aufrufs bzw. aus lang. Sie zeigt das Übersichts-Template des Bereichs (Einstellung des Bereichs, „Template-Zuweisung“):

<a href="(CMS:URL area=termine)">Alle Termine</a>        /de/termine
(CMS:URL area=termine lang=en)                         /en/events

Hat der Bereich keinen Slug-Präfix, bleibt die Ausgabe leer. Im Übersichts-Template selbst gibt es keinen laufenden Datensatz; CMS:URL ohne id verweist dort auf dieselbe Übersicht – ein Sprachumschalter mit CMS:SELECT_LANGVERSION funktioniert deshalb wie auf jeder Datensatzseite.

template

Zweiter URL-Teil (nach dem Komma): das Template, mit dem der Datensatz gezeigt werden soll - Name oder bereich.name:

(CMS:URL template=print)
(CMS:URL template=artikeltemplates.print)

Ausgabe: /de/hallo-welt,print

lang

Sprachversion, auf die der Link zeigt (Kürzel aus der Sprachverwaltung); ohne Angabe die Sprache des laufenden Aufrufs:

(CMS:URL lang=en)

Führt der Auftritt nur eine Sprache, steht nie ein Sprachsegment in der URL. Ein Kürzel, das die Website nicht führt, wird übergangen – die URL behält die Sprache des Aufrufs.

Ist das Feld slug des Bereichs mehrsprachig, hat jede Sprachversion ihren eigenen Slug - und die Ziel-URL muss den Slug der Zielsprache tragen, denn aufgelöst wird er in der Sprache der URL. CMS:URL setzt ihn deshalb bei lang selbst ein:

Seite /de/esstisch-nord-180

(CMS:URL lang=en)        /en/dining-table-nord-180
(CMS:URL lang=sv)        /sv/matbord-nord-180

Ist der Slug in der Zielsprache nicht übersetzt, steht dort der Slug der Hauptsprache. Führt die Instanz des Datensatzes die Zielsprache nicht, gilt ihre Hauptsprache. Findet sich kein Slug, steht die record_id in der URL (bei canonical = 1 leitet die Seite von dort auf den Slug um). Je Datensatz und Sprache kostet das eine Datenbankabfrage, die für den Aufruf gemerkt wird; ohne lang oder bei einem einsprachigen Slug-Feld keine.

protocol

Nur zusammen mit absolute bzw. host:

(CMS:URL absolute=true protocol=https)

Ausgabe: https://www.example.com/de/hallo-welt

host

Andere Domain; impliziert absolute=true:

(CMS:URL host=shop.example.com)

Ausgabe: https://shop.example.com/de/hallo-welt (Protokoll der Instanz)

absolute

Vollständige URL mit Protokoll und Haupt-Domain der Instanz:

<link rel="canonical" href="(CMS:URL absolute=true)">

Ausgabe: <link rel="canonical" href="https://www.example.com/de/hallo-welt">

set

set gibt nichts aus, sondern legt das Ergebnis unter $set['url'] für das umgebende PHP oder einen späteren Befehl ab:

(CMS:URL  set=url)
<?php if ($set['url'] !== '') { ?> … <?php echo $set['url']; ?> … <?php } ?>

← Übersicht

CMS:XML

Erzeugt eine XML-Deklaration.

(CMS:XML encoding [parameter=wert …])

Methode: Cms::cmsXml()
Ohne Schlüssel: der erste Wert ohne = gilt als encoding

Parameter

Parameter Wert Standard Pflicht Beschreibung
encoding ¹ encoding utf-8 Zeichenkodierung, die verwendet werden soll.
version version 1.0 XML-Version.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

encoding und version werden mit htmlspecialchars maskiert ausgegeben.

Anwendung

Erzeugt die XML-Deklaration <?xml version="1.0" encoding="utf-8"?>. Sie muss das erste Zeichen der Ausgabe sein - der Befehl steht deshalb in der ersten Zeile eines Feed-Templates, ohne Leerzeile davor:

(CMS:XML)
<rss version="2.0">
  <channel>
    <title>(CMS:TEXT title specialchars=true lbrconvert=false)</title>
    (CMS:SELECT filter[area]=artikel limit=20 resulttemplate=rss_item)
  </channel>
</rss>

Dazu passend (CMS:HEADER "Content-Type: application/rss+xml; charset=utf-8") – er gibt selbst nichts aus und steht deshalb vor (CMS:XML) in derselben Zeile bzw. ohne Leerzeile dazwischen:

(CMS:HEADER "Content-Type: application/rss+xml; charset=utf-8")(CMS:XML)
<rss version="2.0">

Die Deklaration in ein Template zu tippen ginge nicht: <?xml würde PHP als öffnendes Tag lesen.

Parameter im Einzelnen

encoding

(CMS:XML encoding=ISO-8859-1)

Ausgabe: <?xml version="1.0" encoding="ISO-8859-1"?> - die Inhalte dann mit charset=ISO-8859-1 ausgeben.

version

(CMS:XML version=1.1)

← Übersicht

CMS:ZIP

Bietet die Dateien mehrerer Datensätze als ZIP-Archiv zum Download an und beendet die Seite.

(CMS:ZIP field=feld[,feld…] filter[…]=wert | relation=feld | ids=… | source=basket [parameter=wert …])

Methode: CmsZip::cmsZip() (nachgeladener Dienst, siehe unten)
Ohne Schlüssel: der erste Wert ohne = gilt als field
Pflicht: field und eine Auswahl: filter, relation, ids oder source

Parameter

Parameter Wert Standard Pflicht Beschreibung
field ¹ Feldname(n), kommasepariert – ja Upload- oder Thumbnail-Feld(er). Je Datensatz gilt das erste, das der Besucher abrufen darf.
filter[…] Wert – ² Auswahl der Datensätze wie bei CMS:SELECT.
filter_op[…] Operator = Vergleichsoperator wie bei CMS:SELECT.
filter_mode[…] AND / OR OR Verknüpfung mehrerer Werte wie bei CMS:SELECT.
relation Feldname – ² Die verknüpften Datensätze eines Datensatzes.
src_id Datensatz-Id aktueller Datensatz Quell-Datensatz für relation. Leer oder 0: keine Dateien.
ids Datensatz-Ids, kommasepariert – ² Ausdrücklich genannte Datensätze.
source basket – ² Die Datensätze im Warenkorb der Session – etwa ein Leuchtkasten.
category Zahl 0 Warenkorb-Kategorie für source=basket.
sort[…] Feldname – Reihenfolge wie bei CMS:SELECT; ohne Angabe die von ids bzw. des Warenkorbs.
order[…] asc / desc asc Sortierrichtung.
name Text download Name des Archivs ohne .zip.
filenames Muster {slug}.{extension} Dateinamen im Archiv (siehe unten).
credits Feldnamen, kommasepariert – Legt eine CREDITS.txt bei: je Datei ihr Name und die Inhalte dieser Felder.
maxfiles Zahl Einstellung Höchstzahl der Dateien – nur kleiner als die Einstellung user.zip.max_files.
maxsize MB Einstellung Höchstgröße in MB – nur kleiner als die Einstellung user.zip.max_mb.
future true / false false Auch vordatierte Datensätze (wie bei CMS:SELECT).
archive true / false false Auch abgelaufene Datensätze (wie bei CMS:SELECT).
cookiename Cookiename usess Session-Cookie, wie bei CMS:USER_LOGIN.
message[…] Text Core-Snippet Eigene Meldungen: message[empty], message[limit], message[throttled].

¹ kann ohne Schlüssel als erster Wert angegeben werden.
² mindestens einer der mit ² markierten Parameter muss angegeben werden.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Pressebilder einer Meldung, alle Motive eines Modells, die Auswahl aus einem Leuchtkasten: CMS:ZIP packt die Dateien mehrerer Datensätze in ein Archiv und schickt es dem Besucher als Download. Die Seite endet damit – was danach im Template steht, erscheint nur, wenn kein Archiv entstanden ist.

Der Befehl gehört deshalb an den Anfang eines eigenen Templates, vor jede Ausgabe (das Frontend puffert nicht; nach der ersten Ausgabe meldet der Befehl einen Fehler). Ein eigenständiges Template, das ohne Datensatz aufgerufen wird, eignet sich gut:

(CMS:NUMBER useval="{$_GET['id']}" set=zip_id)
(CMS:ZIP relation=bilder src_id="{$set['zip_id']}" sort="_custom asc"
         field="fil_druck,fil_web" name="Pressebilder" credits="titel,bildunterschrift,bildnachweis")
(CMS:INCLUDE layout_kopf)
(CMS:MESSAGE prefix="<div class='fehler'>" suffix="</div>")
(CMS:INCLUDE layout_fuss)

Verlinkt wird es mit der Id des Datensatzes, dessen Bilder gemeint sind:

(CMS:URL id=0 template=pressebilder_zip set=zip_url)
<a href="<?php echo $set['zip_url']; ?>?id=(CMS:TEXT record_id)">Alle Bilder als ZIP</a>

CMS:NUMBER macht aus dem Adressparameter eine Zahl. Fehlt er, ist src_id leer – dann gibt es keine Dateien, nie „alle“.

Welche Datensätze. Die Auswahl folgt denselben Regeln wie CMS:SELECT: filter, relation mit src_id oder ids. Es zählen nur veröffentlichte Datensätze aus den Instanzen der Website (future und archive wie bei CMS:SELECT). Eine Angabe ist Pflicht – ohne Auswahl entsteht kein Archiv.

Mit source=basket kommen die Datensätze aus dem Warenkorb der Session (CMS:USER_BASKET). Eine eigene Kategorie trennt den Leuchtkasten vom Einkauf:

(CMS:USER_BASKET add variable=bilder category=2)       im Template des Leuchtkastens
(CMS:ZIP source=basket category=2 field=fil_web name=Auswahl)   im Download-Template

Welche Datei. field nennt ein oder mehrere Datei-Felder. Je Datensatz kommt die erste Datei ins Archiv, die der Besucher auch einzeln abrufen dürfte – dieselbe Prüfung wie beim Download über file.php. Ist ein Feld nur für angemeldete Besucher freigegeben, erhält es nur, wer mit einem passenden Profil angemeldet ist; alle anderen bekommen das nächste Feld der Liste. So liefert field="fil_druck,fil_web" der angemeldeten Presse die Druckversion, allen anderen die Web-Version. Datensätze ohne erlaubte Datei werden übergangen.

Dateinamen. filenames legt die Namen im Archiv fest:

Platzhalter Inhalt
{slug} Slug des Datensatzes (ohne Slug seine Id)
{id} Id des Datensatzes
{filename} ursprünglicher Dateiname ohne Endung
{extension} Endung der Datei
{feldname} Inhalt eines Feldes des Datensatzes

Die Endung wird ergänzt, wenn das Muster sie nicht enthält; doppelte Namen erhalten -2, -3 …

Bildnachweise. credits legt eine CREDITS.txt bei – je Datei ihr Name und darunter die Inhalte der genannten Felder, etwa Titel, Bildunterschrift und Fotograf.

Grenzen. Ein Archiv enthält höchstens user.zip.max_files Dateien (Standard 100) mit zusammen user.zip.max_mb MB (Standard 500). maxfiles und maxsize können diese Werte für ein Template nur verkleinern. Überschreitet die Auswahl die Größe, entsteht kein Archiv.

Fertige Archive bleiben user.zip.retention_hours Stunden (Standard 24) im Zwischenspeicher; dieselbe Auswahl wird aus dem Speicher geliefert. Neue Archive sind je Herkunft gedrosselt (user.throttle.zip_ip, Standard 30 je Stunde).

Meldungen. Entsteht kein Archiv, läuft das Template weiter. CMS:MESSAGE gibt die Meldung aus, $set['zip_error'] nennt den Grund:

Grund Bedeutung
empty keine Datei, die der Besucher abrufen darf
limit Auswahl größer als die erlaubte Größe
throttled zu viele Archive in kurzer Zeit

Die Texte kommen aus den Core-Snippets zip_error_empty, zip_error_limit und zip_error_throttled; message[empty]=… usw. ersetzt sie im Template.


← Übersicht

CMS:RELATION

Wichtig: CMS:RELATION ist nur für Templates des Backends gedacht (Listen-, Trigger-Templates). Es prüft keine Zugriffsrechte: Es liefert die verknüpften Datensätze unabhängig von Instanz- und Bereichsrechten. Wie bei CMS:SELECT zählen aber nur veröffentlichte Ziele (freigegeben, Veröffentlichungsdatum erreicht, Ablaufdatum nicht erreicht) – auch im Backend und in der Vorschau; future, archive und state lockern das. Datensätze im Papierkorb und Arbeitskopien erscheinen nie.

Gibt das Hauptfeld (Titel) aller Datensätze aus, die über ein Verknüpfungsfeld mit dem aktuellen Datensatz verbunden sind – in der bevorzugten Sprachversion des Backends (je Instanz; führt eine Instanz sie nicht, deren Hauptsprache). Datensätze mit leerem Hauptfeld erscheinen nicht.
Für Frontend-Templates nutzen Sie (CMS:SELECT relation=…)

(CMS:RELATION field [parameter=wert …])

Methode: Cms::cmsRelation()
Ohne Schlüssel: der erste Wert ohne = gilt als field

Parameter

Parameter Wert Standard Pflicht Beschreibung
id Datensatz-Id oder Slug (aktueller Datensatz) Datensatz, dessen Verweise ausgegeben werden. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL.
field ¹ Feld – Name des Verweisfeldes (Feldtyp relation, pointer oder link), dessen Ziele ausgegeben werden. Ein anderer Feldtyp ergibt eine Fehlermeldung.
limit Zahl >0 1000 Maximale Anzahl der einbezogenen Datensätze in der Ergebnismenge.
future true / false false true: Auch vordatierte Ziele (online_at noch nicht erreicht) werden ausgegeben (wie bei CMS:SELECT).
archive true / false false true: Auch abgelaufene Ziele (offline_at erreicht) werden ausgegeben (wie bei CMS:SELECT).
state Status-Id oder any 0 Ohne Angabe nur freigegebene Ziele (status_id 0). Eine Status-Id > 0 nur Ziele mit diesem Status, "any" Ziele jeden Status (wie bei CMS:SELECT).
lang Sprachkürzel (aktuelle Sprache) Kürzel der Sprachversion, deren Inhalte angefordert werden (wirkt sich auf mehrsprachige Felder aus). Noch nicht umgesetzt - derzeit gilt immer die Sprache des Aufrufs.
separator Trennzeichen \| Text, der zwischen den Ergebnissen stehen soll.
prefix Text – Text, der der Quelle vorangestellt wird, sofern diese nicht leer ist.
suffix Text – Text, der an die Quelle angehängt wird, sofern diese nicht leer ist.
ifempty Text – Text, der zur Quelle wird, wenn die ursprüngliche Quelle leer ist (weitere Parameter, außer 'set', werden ignoriert).
instead Text – Überschreibt die Ausgabe, falls diese nicht leer ist.
set Array-Index von $set – Die Ausgabe der Quelle wird umgeleitet und in $set eingetragen.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Gibt das Hauptfeld (Titel) aller Datensätze aus, auf die ein Verweisfeld des laufenden Datensatzes zeigt - in der im Backend festgelegten Reihenfolge:

Themen: (CMS:RELATION topics separator=", ")

Ausgabe: Themen: Reisen, Fotografie, Island

Der Befehl liefert nur die Titel. Sollen die verknüpften Datensätze selbst ausgegeben werden (mit Bild, Link, Datum …), ist CMS:SELECT relation=topics der richtige Weg:

(CMS:SELECT relation=topics resulttemplate=topic_badge)

Rückwärts-Verknüpfungen (welche Artikel zeigen auf dieses Thema?) gehen über CMS:SELECT filter[topics]="{$cms['record_id']}".

Parameter im Einzelnen

field

Name des Verweisfeldes; darf ohne Schlüssel stehen:

(CMS:RELATION field=topics)
(CMS:RELATION topics)

id

Die Verweise eines anderen Datensatzes - per record_id oder Slug, wie in der URL. Der Datensatz muss wie in der URL veröffentlicht sein, sonst bleibt die Ausgabe leer:

(CMS:RELATION topics id=1977)
(CMS:RELATION topics id=island-reise)

limit

(CMS:RELATION topics limit=3 separator=", ")

Es werden höchstens drei Titel ausgegeben - die ersten drei in der Reihenfolge des Verweisfeldes.

future

Auch Ziele, deren Veröffentlichungsdatum noch nicht erreicht ist:

(CMS:RELATION termine future=true separator=", ")

archive

Auch abgelaufene Ziele:

(CMS:RELATION topics archive=true)

state

Alle Ziele unabhängig vom Status – etwa in einem Listen-Template des Backends:

(CMS:RELATION topics state=any future=true archive=true)

lang

Noch nicht umgesetzt - es gilt die Sprache des Aufrufs.

(CMS:RELATION topics lang=en)

separator

(CMS:RELATION topics separator=", ")
(CMS:RELATION topics separator="</li><li>" prefix="<ul><li>" suffix="</li></ul>")

Die zweite Zeile ergibt eine Liste: <ul><li>Reisen</li><li>Fotografie</li></ul>.

prefix

prefix / suffix umschließen den Wert und erscheinen nur, wenn er nicht leer ist - so entsteht kein leeres Themen::

(CMS:RELATION topics prefix="Themen: " suffix="")

suffix

(CMS:RELATION topics suffix=".")

Siehe prefix.

ifempty

ifempty ersetzt die gesamte Ausgabe, wenn die Quelle leer ist; prefix und suffix entfallen dann:

(CMS:RELATION topics prefix="<p>" suffix="</p>" ifempty="keine Themen zugeordnet")

instead

instead tauscht den Wert aus, sobald die Quelle nicht leer ist - nützlich, wenn nur das Vorhandensein zählt:

(CMS:RELATION topics instead="Themen vorhanden" ifempty="-")

set

set gibt nichts aus, sondern legt das Ergebnis unter $set['topics'] für das umgebende PHP oder einen späteren Befehl ab:

(CMS:RELATION topics set=topics)
<?php if ($set['topics'] !== '') { ?> … <?php echo $set['topics']; ?> … <?php } ?>

← Übersicht

CMS:SELECT

Durchsucht die Datenbank anhand der angegebenen (Filter-)Kriterien und liefert die gefundenen Datensätze zurück.

(CMS:SELECT resulttemplate [parameter=wert …])

Methode: Cms::cmsSelect()
Ohne Schlüssel: der erste Wert ohne = gilt als resulttemplate
Mindestens einer von: filter, relation
PHP-Schnipsel: push_result (davor) · include, pop_result (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
resulttemplate ¹ Templatename – ja Listentemplate, das zur Verarbeitung der Ergebnisse eingebunden wird.
noresulttemplate Templatename – Detailtemplate, das eingebunden wird, falls die Abfrage keine Ergebnisse liefert.
noresult Text – Text, der unmittelbar ausgegeben wird, falls die Abfrage keine Ergebnisse liefert.
filter[…][…] Wert oder EMPTY – ² Im Index: Name des zu durchsuchenden Feldes (relation_any für ein beliebiges Verweisfeld; area für den Namen des Inhaltsbereichs, eine Zahl gilt dort als area_id). Im Wert: der zu suchende Wert.
filter_mode[…] AND / OR OR Modus, mit dem mehrere Werte desselben Feldes verknüpft werden (filter_mode[feld]=AND).
filter_op[…][…] =, ==, !=, !!, >, <, >>, <<, >=, <= = Vergleichsoperator für das angegebene Feld (filter_op[feld]=">"). Alternativ steht der Operator zwischen Schlüssel und Wert (filter[preis] > 100) oder direkt vor dem Wert (filter[preis]=">100").
skip Zahl >=0 0 Anzahl der übersprungenen Datensätze in der Ergebnismenge. Wirkt nur zusammen mit limit.
limit Zahl >0 – Maximale Anzahl der einbezogenen Datensätze in der Ergebnismenge - zugleich die Seitengröße für CMS:JUMP_LIST, CMS:JUMP_PREV und CMS:JUMP_NEXT. Ohne Angabe oder 0: unbegrenzt, keine Blätterleiste.
sort[…] Feldname(n), kommasepariert changed_at Feld, nach dem sortiert wird (vgl. order); mehrere kommasepariert oder als sort[]=…; area sortiert nach dem Namen des Inhaltsbereichs, verweisfeld.feld nach einem Feld des verknüpften Datensatzes.
order[…] desc / asc asc Reihenfolge, in der sortiert wird (vgl. sort). Ohne sort gilt changed_at desc.
lang Sprachkürzel (aktuelle Sprache) Kürzel der Sprachversion, deren Inhalte angefordert werden (wirkt sich auf mehrsprachige Felder aus). Noch nicht umgesetzt - derzeit gilt immer die Sprache des Aufrufs.
relation Feldname(n) kommasepariert oder 'ANY' – ² Feldnamen von Beziehungs-/Verweisfeldern, in denen die gesuchten Datensätze gespeichert sind (mit src_id); mit dest_id die Felder, über die die gesuchten Datensätze auf dest_id verweisen. ANY: irgendein Verweisfeld.
src_id Datensatz-ID(s) aktueller Datensatz Quell-Datensatz, dessen Verknüpfungen ausgewertet werden (ohne Angabe: aktueller Datensatz).
dest_id Datensatz-ID – Rückrichtung zu relation: die Datensätze, die über relation auf diesen Datensatz verweisen (relation=ANY: über irgendein Verweisfeld). Ohne src_id wird dann kein aktueller Datensatz als Quelle eingesetzt.
nextof Datensatz-ID – Nur mit relation: Nachbarn des angegebenen Datensatzes in der Verweisliste - die Einträge hinter ihm (order=asc) bzw. vor ihm (order=desc), in der Reihenfolge der Liste; mit limit=1 der nächste bzw. vorige Datensatz.
count true / false false true: Die Anzahl der Datensätze in der Ergebnismenge wird ermittelt, unabhängig von "limit" und "skip" (im resulttemplate in $this->db->num_rows_unlimited; Grundlage der JUMP-Befehle).
hits true / false true false: Es wird nur gezählt (schließt count=true ein), es werden keine Datensätze geladen und kein resulttemplate eingebunden. Die Gesamtzahl steht danach im aufrufenden Template in $this->db->num_rows_unlimited; noresult/noresulttemplate gelten, wenn sie 0 ist.
archive true / false false true: Auch abgelaufene Datensätze (offline_at erreicht) werden in die Abfrage einbezogen.
future true / false false true: Auch vordatierte Datensätze (online_at noch nicht erreicht) werden in die Abfrage einbezogen.
debug true / false false true: Der erzeugte Datenbank-Query wird für Support-Analysen ausgegeben.
sql – – Raw SQL-Zusatz in der WHERE-Klausel (Achtung: nur für den Support!).
state Status-Id oder any 0 Ohne Angabe nur freigegebene Datensätze (status_id 0). Eine Status-Id > 0 findet nur Datensätze mit diesem Status, "any" Datensätze jeden Status.
request Kommaliste – Übernimmt Filter- und Sortierwerte aus der URL – Kommaliste oder Array der erlaubten Schlüssel: filter[feld], filter_op[feld], filter_mode[feld], skip, limit, sort, order (z. B. "filter[titel],sort,order,skip"). filter nur mit Feldname. Im Befehl gesetzte Werte haben immer Vorrang; für ein im Befehl gefiltertes Feld fügt die URL nichts hinzu.
set Array-Index von $set – Die gesamte Ausgabe (samt eingebundenem Template) wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.
² mindestens einer der mit ² markierten Parameter muss angegeben werden.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Fragt Datensätze ab und bindet für die Treffer das resulttemplate ein; darin läuft CMS:RESULT_START … CMS:RESULT_END über die Zeilen. Der Befehl ist der Kern jeder Liste.

Gefunden werden nur veröffentlichte Datensätze: freigegeben (status_id 0), Erscheinungsdatum (online_at) erreicht, Ende (offline_at) nicht erreicht. Die Parameter future, archive und state lockern das je Abfrage - auch in der Vorschau aus dem Backend, die zwar einen unveröffentlichten Datensatz zeigt, aber darin die Listen so, wie die Website sie zeigt.

<h2>Neueste Artikel</h2>
(CMS:SELECT artikel_teaser
  filter[area]=artikel
  sort=online_at order=desc
  limit=10
  noresult="<p>Noch keine Artikel.</p>"
)

Listentemplate artikel_teaser:

<ul>
(CMS:RESULT_START)
  <li><a href="(CMS:URL)">(CMS:TEXT title)</a> – (CMS:DATE online_at)</li>
(CMS:RESULT_END)
</ul>

Häufige Muster:

Ein Datensatz nach Slug:          (CMS:SELECT detail filter[slug]=impressum limit=1)
Nur ein Thema:                    (CMS:SELECT liste filter[area]=artikel filter[topics]=42)
Suche im Titel:                   (CMS:SELECT liste filter[area]=artikel filter[title]="*{$_GET['q']}*")
Zeitraum:                         (CMS:SELECT liste filter[area]=artikel filter[online_at] >= 2026-01-01)
Verknüpfte Datensätze:            (CMS:SELECT topic_badge relation=topics)
Kinder im Baum:                   (CMS:SELECT nav filter[parent_id]="{$cms['record_id']}" filter[depth]=1 sort=position order=asc)
Blätterbare Liste:                (CMS:SELECT liste filter[area]=artikel limit=20 skip="{$_REQUEST['skip']}" count=true)
Suchformular mit Sortierung:      (CMS:SELECT liste filter[area]=artikel limit=20 count=true request="filter[title],sort,order,skip")

Mehrere Werte für dasselbe Feld werden mit OR verknüpft (filter_mode[feld]=AND schaltet um); verschiedene Felder immer mit AND. Vergleichsoperatoren stehen zwischen Schlüssel und Wert (filter[preis] > 100, filter[farbe] != rot) oder direkt vor dem Wert (">100"); Platzhalter und (empty) gehören in den Wert (Hen*) - die vollständige Filtersyntax steht in den Grundlagen.

Ein CMS:SELECT darf innerhalb eines Listentemplates stehen (verschachtelte Listen); das äußere Ergebnis wird automatisch beiseitegelegt und danach fortgesetzt.

Parameter im Einzelnen

resulttemplate

Listentemplate für die Treffer; darf ohne Schlüssel stehen. Name oder bereich.name:

(CMS:SELECT resulttemplate=artikel_teaser filter[area]=artikel)
(CMS:SELECT artikel_teaser filter[area]=artikel)
(CMS:SELECT artikeltemplates.artikel_teaser filter[area]=artikel)

noresulttemplate

Template ohne Treffer - etwa mit einem Suchformular oder Hinweistext:

(CMS:SELECT suchergebnis filter[area]=artikel filter[title]="*{$_GET['q']}*" noresulttemplate=suche_leer)

noresult

Kurzer Text ohne Treffer, ohne eigenes Template:

(CMS:SELECT liste filter[area]=artikel noresult="<p>Keine Einträge.</p>")

filter

Schlüssel = Feldname (eigene Felder, Metafelder wie record_id, status_id, online_at, parent_id, user_id, das virtuelle slug, _default für das Hauptfeld, area für den Bereichsnamen), Wert = Suchwert mit optionalem Operator:

filter[area]=artikel                     Bereich nach Name (Zahl = area_id)
filter[area]=artikel,news                zwei Bereiche (OR)
filter[title]="Hen*"                     beginnt mit
filter[price] > 100                      größer als
filter[price]=">100"                     dasselbe, Operator im Wert
filter[price]=">100 AND <500"            Ausdruck in einem Feld
filter[online_at]=2026                   das ganze Jahr
filter[termin] >= today                  ab heute (0 Uhr, Zeitzone des Aufrufs)
filter[online_at] > utc_now-7d           die letzten sieben Tage, in UTC gerechnet
filter[teaser]="(empty)"                 leer / nicht übersetzt
filter[bild]="(empty)"                   Datei-Feld ohne Datei
filter[topics]=42                        Verweisfeld zeigt auf Datensatz 42
filter[topics]="(empty)"                 Verweisfeld ohne Verweis
filter[relation_any]=42                  irgendein Verweisfeld zeigt auf 42
filter[topics.title]="Island"            Feld des verknüpften Datensatzes
filter[tags]=a filter[tags]=b            zwei Werte, OR (siehe filter_mode)
filter[parent_id]=17 filter[depth]=1     direkte Kinder von 17
filter[parent_id]=null filter[depth]=1   oberste Ebene (Datensätze ohne Elterndatensatz)

filter[depth] ist kein Filter auf die Einrückung eines Datensatzes, sondern gibt an, wie viele Ebenen unterhalb von filter[parent_id] durchsucht werden: 1 = nur direkte Kinder, 2 = bis zu den Enkeln, ohne Angabe = alle Nachkommen. Ohne filter[parent_id] bleibt es wirkungslos (Hinweis beim Speichern des Templates bzw. mit debug=true); filter[depth]=0 liefert keine Datensätze. Der Wert unter „Einzurückende Stellen“ in der Datensatzinfo (0 = oberste Ebene) ist dagegen absolut – die oberste Ebene finden Sie mit filter[parent_id]=null filter[depth]=1.

Textfelder werden nach den Regeln der Sprache verglichen und sortiert, in der gesucht wird: Groß-/Kleinschreibung und Akzente spielen keine Rolle, ß gleich ss, im Deutschen außerdem ä gleich ae (filter[title]="Mue*" findet „Müller“), im Schwedischen dagegen ist ö ein eigener Buchstabe (o findet kein ö). Die Regeln ergeben sich aus dem Sprachkürzel; siehe Grundlagen.

Ein leerer Wert hebt den Filter auf - praktisch für optionale Filter aus dem Request, gefährlich bei Tippfehlern: filter[kategorie]="{$cms['id']}" (den Schlüssel id gibt es nicht, richtig ist record_id) liefert alle Datensätze.

filter_mode

Verknüpfung mehrerer Werte desselben Feldes (Standard OR):

(CMS:SELECT liste filter[area]=artikel filter[tags]=reisen filter[tags]=island filter_mode[tags]=AND)

Findet Artikel, die beide Tags tragen.

filter_op

Vergleichsoperator getrennt vom Wert - gleichwertig zur Schreibweise im Wert und zum Operator zwischen Schlüssel und Wert:

(CMS:SELECT liste filter[area]=artikel filter[price]=100 filter_op[price]=">")
(CMS:SELECT liste filter[area]=artikel filter[price]=">100")
(CMS:SELECT liste filter[area]=artikel filter[price] > 100)

skip

Offset für die Blätterfunktion; meist aus dem Request. skip wirkt nur zusammen mit limit – ohne limit wird es ignoriert, die Abfrage liefert dann alle Treffer ab dem ersten:

(CMS:SELECT liste filter[area]=artikel limit=20 skip="{$_REQUEST['skip']}" count=true)

Die Blätterleiste (CMS:JUMP_LIST) steht im resulttemplate liste.

limit

(CMS:SELECT teaser filter[area]=artikel limit=3)

Höchstens drei Treffer. Ohne limit (oder mit 0) liefert die Abfrage alle Treffer – bei großen Bereichen gehört deshalb immer ein limit dazu. limit ist zugleich die Seitengröße der Blätterbefehle CMS:JUMP_LIST, CMS:JUMP_PREV und CMS:JUMP_NEXT im resulttemplate; ohne limit gibt es keine Blätterleiste.

sort

Sortierfeld, Richtung über order oder direkt im Wert:

sort=online_at order=desc
sort="title asc"
sort=position order=asc                     Reihenfolge aus dem Backend (Baum/Liste)
sort=_default                               Hauptfeld des Bereichs
sort=area                                   nach Bereichsname
sort=author.name                            Feld des verknüpften Datensatzes
sort[]=status_id sort[]=title order[]=asc order[]=asc

order

Richtung zu sort: asc (Standard, sobald sort angegeben ist) oder desc. Ohne sort sortiert der Befehl nach changed_at desc – die zuletzt geänderten zuerst.

(CMS:SELECT liste filter[area]=artikel sort=title order=asc)

lang

Noch nicht umgesetzt - es gilt die Sprache des Aufrufs.

relation

Treffer sind die Datensätze, auf die das Verweisfeld des Quell-Datensatzes zeigt - ohne src_id die des laufenden Datensatzes. Die Reihenfolge des Verweisfeldes liefert sort=_custom; ohne sort gilt die Standardsortierung (changed_at desc):

(CMS:SELECT topic_badge relation=topics)
(CMS:SELECT galerie relation=bilder sort="_custom asc")

Mehrere Feldnamen, kommagetrennt, gelten als ODER; ANY meint irgendein Verweisfeld. sort=_custom und nextof setzen genau einen Feldnamen voraus.

(CMS:SELECT teaser relation="topics,tags")
(CMS:SELECT teaser relation=any)

Die umgekehrte Richtung - Datensätze, die auf den laufenden zeigen:

(CMS:SELECT produkt_liste relation=rel_produkte)
(CMS:SELECT produkt_liste filter[area]=produkte filter[kategorie]="{$cms['record_id']}")
(CMS:SELECT verweis_liste relation=any dest_id="{$cms['record_id']}")

Die erste Zeile gilt für Beziehungsfelder (relation): Sie haben im Zielbereich ein Gegenfeld rel_{bereichsname}. Verweisfelder (pointer) haben keins - ihr Gegenstück ist der Filter auf das Verweisfeld (zweite Zeile). Siehe Jeder Datensatz ist eine Seite.

src_id

Quell-Datensatz für relation, wenn nicht der laufende gemeint ist:

(CMS:SELECT topic_badge relation=topics src_id=1977)
(CMS:SELECT topic_badge relation=topics src_id="{$set['user_id']}")

Ein leerer Wert oder 0 meint ebenfalls den laufenden Datensatz. Gibt es keinen – etwa in einem eigenständigen Template ohne Datensatz –, findet die Abfrage nichts (und nicht etwa alle Datensätze des Bereichs).

dest_id

Die Rückrichtung zu relation: Treffer sind die Datensätze, die über das Verweisfeld – mit relation=any über irgendeines – auf dest_id zeigen, etwa „Wo wird dieses Bild verwendet?":

(CMS:SELECT verweis_liste relation=any dest_id="{$cms['record_id']}")
(CMS:SELECT artikel_liste relation=topics dest_id=1977)

Mit dest_id setzt der Befehl ohne src_id keinen laufenden Datensatz als Quelle ein. Dasselbe als Filterwert, auch für mehrere Ziele: filter[relation_any]="1977,1978".

nextof

Nur zusammen mit relation: statt aller verknüpften Datensätze die Nachbarn des Datensatzes nextof in derselben Verweisliste - die Einträge hinter ihm bei order=asc (Standard), die vor ihm bei order=desc, jeweils in der Reihenfolge der Liste (die Sortierung ist damit festgelegt, sort wird ignoriert). Mit limit=1 ist das der nächste bzw. vorige Datensatz - etwa für „weiter“/„zurück“ in einer Bildergalerie, deren Bilder am Artikel 2422 hängen:

(CMS:SELECT galerie_weiter relation=bilder src_id=2422 nextof="{$cms['record_id']}" limit=1)
(CMS:SELECT galerie_zurueck relation=bilder src_id=2422 nextof="{$cms['record_id']}" order=desc limit=1)

Steht nextof nicht in der Liste, gibt es keine Treffer.

count

Gesamtzahl unabhängig von limit/skip ermitteln - Grundlage für CMS:JUMP_* und für Angaben wie „Treffer 21–40 von 137“:

(CMS:SELECT liste filter[area]=artikel limit=20 skip="{$_REQUEST['skip']}" count=true)

Im resulttemplate liste stehen dann zwei Zahlen: $this->db->num_rows sind die Treffer dieser Seite (höchstens limit), $this->db->num_rows_unlimited alle Treffer der Abfrage:

(CMS:NUMBER useval="{$this->db->num_rows_unlimited}") Treffer
(CMS:RESULT_START) … (CMS:RESULT_END)
(CMS:JUMP_LIST)

Beide Zahlen gelten nur im resulttemplate: Nach dem CMS:SELECT ist wieder das umgebende Ergebnis aktiv – dieselbe Regel, die verschachtelte Listen möglich macht. Wird nur die Zahl gebraucht, zählt hits=false im aufrufenden Template.

hits

hits=false zählt nur (count=true ist damit eingeschlossen): Es werden keine Datensätze geladen und kein resulttemplate eingebunden – der Name muss trotzdem angegeben werden. Die Gesamtzahl steht danach im aufrufenden Template in $this->db->num_rows_unlimited:

(CMS:SELECT x filter[area]=artikel filter[topics]=42 hits=false)
(CMS:NUMBER useval="{$this->db->num_rows_unlimited}") Artikel zu diesem Thema

Ist die Zahl 0, gelten noresult bzw. noresulttemplate wie gewohnt:

(CMS:SELECT x filter[area]=artikel filter[topics]=42 hits=false noresult="Noch keine Artikel zu diesem Thema.")

Steht das zählende CMS:SELECT in einem resulttemplate, ersetzt die Zahl dort die Gesamtzahl des umgebenden Ergebnisses; $this->db->num_rows und die laufende Ergebnisschleife bleiben unberührt. Eine Blätterleiste (CMS:JUMP_LIST) des umgebenden Ergebnisses gehört dann vor das zählende CMS:SELECT.

archive

Abgelaufene Datensätze (offline_at erreicht) einbeziehen - z. B. für ein Archiv vergangener Veranstaltungen:

(CMS:SELECT archiv filter[area]=events archive=true sort=online_at order=desc)

Ein abgelaufener Datensatz bleibt übrigens unter seiner eigenen Adresse erreichbar; er fehlt nur in Listen ohne archive.

future

Vordatierte Datensätze (online_at noch nicht erreicht) einbeziehen - z. B. eine Vorschau auf kommende Beiträge:

(CMS:SELECT demnaechst filter[area]=artikel future=true filter[online_at] > now)

debug

SQL und Bind-Werte der Abfrage ausgeben (nur zur Entwicklung). Darüber stehen verworfene Filter mit Begründung – etwa ein unbekannter Feldname oder filter[depth] ohne filter[parent_id]:

(CMS:SELECT liste filter[area]=artikel debug=true)

sql

Roh-SQL für die WHERE-Klausel - nur für den Support, keine Benutzereingaben hineinreichen:

(CMS:SELECT liste filter[area]=artikel sql="AND `record`.`record_id` % 2 = 0")

set

Die gesamte Ausgabe – das Listentemplate mit allen Treffern, bei noresult dessen Text – landet in $set statt auf der Seite. So lässt sich etwa eine Adresse aus einer Abfrage in einem anderen Befehl weiterverwenden:

(CMS:SELECT url_only filter[area]=seiten filter[kennung]=warenkorb limit=1 set=warenkorb_url)
<a class="cart" href="<?php echo $set['warenkorb_url']; ?>">…</a>
(CMS:TEXT titel prefix="<a href='{$set['warenkorb_url']}'>" suffix="</a>")

Auch die Ausgabe von debug=true und PHP-Hinweise aus dem Listentemplate stehen dann in $set und nicht auf der Seite – zum Untersuchen set vorübergehend weglassen. Endet die Seite innerhalb der Abfrage (etwa durch ein Success-Template), wird das bis dahin Erzeugte normal ausgegeben.

state

Ohne Angabe nur freigegebene Datensätze (status_id 0). Eine Status-Id findet nur Datensätze mit genau diesem Status, any hebt den Statusfilter auf:

(CMS:SELECT liste filter[area]=artikel state=2)
(CMS:SELECT liste filter[area]=artikel state=any)

request

Übernimmt Filter, Sortierung und Blättern aus der URL (GET oder POST) – für Suchformulare und sortierbare Listen, ohne jeden Wert einzeln in den Befehl zu schreiben. Angegeben wird, was übernommen werden darf, als Kommaliste oder Array:

request="sort,order,skip"                                  nur Sortierung und Blättern
request="filter[title],filter[price],filter_op[price]"     Suche in Titel und Preis
request[]=sort request[]="filter[title]"                   dasselbe als Array

Erlaubt sind skip, limit, sort, order sowie filter, filter_op und filter_mode nur mit Feldname (filter[title]). Ein filter ohne Feldname und all werden ignoriert und als Fehler gemeldet – sonst dürfte ein Besucher nach jedem Feld der gefundenen Datensätze suchen, auch nach Feldern, die die Seite gar nicht anzeigt, etwa einer internen Notiz.

In der URL heißen die Schlüssel wie im Befehl, nur filterop und filtermode ohne Unterstrich: ?filter[title]=Hen*&sort=title&order=desc&skip=20.

(CMS:SELECT liste filter[area]=artikel limit=20 count=true request="filter[title],sort,order,skip")

Die URL kann die Abfrage nur einschränken, nie erweitern:

  • Werte im Befehl haben Vorrang. Für ein Feld, das der Befehl filtert, übernimmt request weder Werte noch filter_op/filter_mode – ?filter[area]=intern oder ?filterop[area]=!= ändern an filter[area]=artikel nichts. Felder eines verknüpften Datensatzes zählen zum Verweisfeld (autor.name gehört zu autor), area und area_id gelten als dasselbe Feld.
  • skip, limit, sort und order aus dem Befehl ersetzen den Wert der URL vollständig. limit aus der URL gilt nur als positive Zahl – „unbegrenzt“ lässt sich nicht anfordern.
  • Verschlüsselte Felder finden nur den exakten Wert; Platzhalter (*) und Größer/Kleiner liefern keine Treffer.
  • Passwortfelder lassen sich grundsätzlich weder filtern noch sortieren.

← Übersicht

CMS:SELECT_LANGVERSION

Liefert die verfügbaren Sprachen als Datensätze zurück.

(CMS:SELECT_LANGVERSION resulttemplate)

Methode: Cms::cmsSelectLanguages()
Ohne Schlüssel: der erste Wert ohne = gilt als resulttemplate
PHP-Schnipsel: push_result (davor) · include, pop_result (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
resulttemplate ¹ Templatename – ja Listentemplate, das zur Verarbeitung der Ergebnisse eingebunden wird.
set Array-Index von $set – Die gesamte Ausgabe (samt eingebundenem Template) wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Stellt die Sprachen des Auftritts als Ergebnismenge bereit - Hauptsprache zuerst - und bindet das resulttemplate ein. Jede Zeile trägt language_id, code, label, endonym und current (1 = Sprache des laufenden Aufrufs). Damit entsteht ein Sprachumschalter:

<ul class="lang">
(CMS:SELECT_LANGVERSION lang_item)
</ul>

Listentemplate lang_item:

(CMS:RESULT_START)
<li<?php if ($cms['current']) echo ' class="active"'; ?>>
  <a href="(CMS:URL lang="{$cms['code']}")" hreflang="(CMS:TEXT code)">(CMS:TEXT endonym)</a>
</li>
(CMS:RESULT_END)

Die Zeilen sind keine Datensätze; CMS:URL ohne id verweist deshalb auf den äußeren Datensatz - den der Seite bzw. die Zeile der umgebenden Ergebnisschleife -, nur in der jeweils anderen Sprache. Ist das Feld slug mehrsprachig, setzt CMS:URL den Slug der Zielsprache ein (/de/esstisch-nord-180 → /en/dining-table-nord-180), siehe CMS:URL. Bei nur einer Sprache enthält die Ergebnismenge genau eine Zeile.

Auf einer Seite ohne Datensatz (eigenständiges Template, /de/0,suche) gibt es keinen äußeren Datensatz; das Template gehört dann ausdrücklich in den Verweis:

<a href="(CMS:URL lang="{$cms['code']}" template=suche)">(CMS:TEXT endonym)</a>

Parameter im Einzelnen

resulttemplate

Listentemplate; darf ohne Schlüssel stehen:

(CMS:SELECT_LANGVERSION resulttemplate=lang_item)
(CMS:SELECT_LANGVERSION lang_item)

← Übersicht

CMS:RESULT_START

Markiert den Beginn einer Schleife zur Verarbeitung von CMS:SELECT-Ergebnissen.

(CMS:RESULT_START)

Kein Methodenaufruf – die Wirkung steckt allein im PHP-Schnipsel
PHP-Schnipsel: loop_start (danach)

Parameter

Dieser Befehl hat keine Parameter.

Anwendung

Öffnet die Schleife über die Treffer eines CMS:SELECT (bzw. CMS:SELECT_LANGVERSION, CMS:USER_BASKET function=load). Steht im Listentemplate; alles zwischen CMS:RESULT_START und CMS:RESULT_END wird je Treffer ausgeführt, die Befehle darin lesen aus der aktuellen Zeile:

<table>
(CMS:RESULT_START)
<tr>
  <td><?php echo $this->cms->row_no + 1; ?></td>
  <td><a href="(CMS:URL)">(CMS:TEXT title)</a></td>
  <td>(CMS:DATE online_at)</td>
</tr>
(CMS:RESULT_END)
</table>

Innerhalb der Schleife stehen zur Verfügung: $cms (die aktuelle Zeile als Array), $this->cms->row_no (0-basierte Zeilennummer), $this->db->num_rows (Treffer auf dieser Seite) und $this->db->num_rows_unlimited (Gesamtzahl bei count=true).

Der Befehl hat keine Parameter und erzeugt keinen Methodenaufruf - er wird direkt zu einer for-Schleife kompiliert.


← Übersicht

CMS:RESULT_END

Markiert das Ende einer Schleife zur Verarbeitung von CMS:SELECT-Ergebnissen.

(CMS:RESULT_END)

Kein Methodenaufruf – die Wirkung steckt allein im PHP-Schnipsel
PHP-Schnipsel: loop_end (danach)

Parameter

Dieser Befehl hat keine Parameter.

Anwendung

Schließt die mit CMS:RESULT_START geöffnete Schleife:

(CMS:RESULT_START)
  <li>(CMS:TEXT title)</li>
(CMS:RESULT_END)

Jedes CMS:RESULT_START braucht genau ein CMS:RESULT_END im selben Template - der Compiler erzeugt daraus { und }; ein fehlendes Ende führt zu einem PHP-Syntaxfehler beim Aufruf der Seite.


← Übersicht

CMS:JUMP_LIST

Erzeugt eine Blätter-Navigation (Pagination), z. B. 1 … 5 | 6 | 7 | 8 | 9 … 15.

(CMS:JUMP_LIST separator [parameter=wert …])

Methode: Pagination::cmsJumpList()
Ohne Schlüssel: der erste Wert ohne = gilt als separator

Parameter

Parameter Wert Standard Pflicht Beschreibung
skip Zahl >= 0 $_REQUEST[skip] Versatz der aktuellen Seite (Anzahl übersprungener Ergebnisse). Ohne Angabe gilt der Request-Parameter skip.
maximum Zahl >= 1 7 Anzahl der gleichzeitig angezeigten (mitwandernden) Seitenverweise, ohne die Verweise auf erste und letzte Seite.
boundaries true / false true true: Verweise auf die erste und die letzte Seite werden immer angezeigt, sobald sie außerhalb des mitwandernden Bereichs liegen.
separator ¹ Trennzeichen (Leerzeichen) Text zwischen zwei Seitenverweisen. Vor und hinter der Auslassung ('ellipsis') wird er nicht eingefügt.
tpl_link HTML-Vorlage <a href="{href}">{page}</a> Vorlage für einen Seitenverweis. {page} wird durch die Seitennummer ersetzt, {href} durch die Seiten-URL. Die URL ist HTML-maskiert (&amp; statt &), ' und \ sind prozentkodiert – {href} kann direkt in einem Attribut stehen.
tpl_active HTML-Vorlage <strong>{page}</strong> Vorlage für die aktuelle Seite. {page} wird durch die Seitennummer ersetzt, {href} durch die Seiten-URL.
ellipsis Code … Platzhalter für ausgelassene Seiten - erscheint zwischen einem Boundary-Verweis und dem mitwandernden Bereich.
replace Text?Text – Einfache Textersetzung im fertigen HTML. Trenner ist das Fragezeichen: "suchen?ersetzen".
prefix Text – Text vor der Navigation, nur wenn eine Navigation ausgegeben wird.
suffix Text – Text nach der Navigation, nur wenn eine Navigation ausgegeben wird.
ifempty Text – Ausgabe, wenn das Ergebnis auf eine einzige Seite passt oder das CMS:SELECT kein limit hat und deshalb keine Navigation nötig ist.
set Array-Index von $set – Die Navigation wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Blätterleiste zu einem CMS:SELECT mit count=true. Die Seitengröße ist das limit der Abfrage; ohne limit gibt es keine Seiten und der Befehl gibt nur ifempty aus. Der aktuelle Versatz kommt aus $_REQUEST['skip'].

Der Befehl steht im resulttemplate des CMS:SELECT (vor oder hinter der Ergebnisschleife): Nur dort gelten Trefferzahl und limit dieser Abfrage – hinter dem CMS:SELECT im aufrufenden Template ist wieder das äußere Ergebnis aktiv.

(CMS:SELECT liste filter[area]=artikel limit=20 skip="{$_REQUEST['skip']}" count=true)

Template liste:

(CMS:RESULT_START)
  <h2>(CMS:TEXT titel)</h2>
(CMS:RESULT_END)
<nav class="pager">
(CMS:JUMP_PREV) (CMS:JUMP_LIST separator=" | ") (CMS:JUMP_NEXT)
</nav>

Ausgabe: ❮❮ 1 … 5 | 6 | 7 | 8 | 9 … 15 ❯❯

Die Links behalten alle übrigen Parameter der aktuellen URL (Suchbegriff, Filter) und setzen nur skip neu. Passt das Ergebnis auf eine Seite, wird nichts ausgegeben (bzw. ifempty).

Bootstrap-artige Leiste:

(CMS:JUMP_LIST
  prefix="<ul class=pagination>" suffix="</ul>"
  tpl_link="<li><a href='{href}'>{page}</a></li>"
  tpl_active="<li class=active><span>{page}</span></li>"
  ellipsis="<li class=disabled><span>…</span></li>"
  separator=""
)

Parameter im Einzelnen

skip

Versatz der aktuellen Seite, falls er nicht aus $_REQUEST['skip'] kommt:

(CMS:JUMP_LIST skip="{$offset}")

maximum

Anzahl der mitwandernden Seitenverweise (ohne erste/letzte Seite):

(CMS:JUMP_LIST maximum=5)

Ausgabe auf Seite 7 von 15: 1 … 5 6 7 8 9 … 15

boundaries

Verweise auf erste und letzte Seite unterdrücken:

(CMS:JUMP_LIST boundaries=false)

Ausgabe: 5 6 7 8 9 statt 1 … 5 6 7 8 9 … 15

separator

Text zwischen zwei Seitenverweisen; darf ohne Schlüssel stehen:

(CMS:JUMP_LIST " | ")
(CMS:JUMP_LIST separator=" · ")

Vorlage je Seitenverweis; {href} und {page} werden ersetzt:

(CMS:JUMP_LIST tpl_link="<a class=page href='{href}'>{page}</a>")

tpl_active

Vorlage für die aktuelle Seite:

(CMS:JUMP_LIST tpl_active="<span class='page current'>{page}</span>")

ellipsis

Platzhalter für ausgelassene Seiten (zwischen erster/letzter Seite und dem mitwandernden Bereich):

(CMS:JUMP_LIST ellipsis=" ··· ")

replace

Einfache Ersetzung im fertigen HTML, Trenner ? (nur ein Paar):

(CMS:JUMP_LIST replace="<strong>?<strong class=current>")

prefix

Erscheint nur, wenn eine Leiste ausgegeben wird:

(CMS:JUMP_LIST prefix="<nav class=pager>" suffix="</nav>")

suffix

(CMS:JUMP_LIST suffix="</nav>" prefix="<nav>")

ifempty

Ausgabe, wenn alles auf eine Seite passt (oder das CMS:SELECT kein limit hat):

(CMS:JUMP_LIST ifempty="<span class=muted>alle Einträge</span>")

set

(CMS:JUMP_LIST set=pager)
<?php if ($set['pager'] !== '') { ?><nav><?php echo $set['pager']; ?></nav><?php } ?>

← Übersicht

CMS:JUMP_NEXT

Erzeugt einen Weiterblättern-Verweis.

(CMS:JUMP_NEXT tpl_link [parameter=wert …])

Methode: Pagination::cmsJumpNext()
Ohne Schlüssel: der erste Wert ohne = gilt als tpl_link

Parameter

Parameter Wert Standard Pflicht Beschreibung
skip Zahl >= 0 $_REQUEST[skip] Versatz der aktuellen Seite (Anzahl übersprungener Ergebnisse). Ohne Angabe gilt der Request-Parameter skip.
tpl_link ¹ HTML-Vorlage <a href="{href}"><strong>❯❯</strong></a> Vorlage für den Verweis, solange es eine nächste Seite gibt. {href} wird durch die Ziel-URL ersetzt. Die URL ist HTML-maskiert (&amp; statt &), ' und \ sind prozentkodiert – {href} kann direkt in einem Attribut stehen.
tpl_inactive HTML-Vorlage <span>❯❯</span> Vorlage für den nicht anwählbaren Verweis, wenn es keine nächste Seite gibt. {href} wird ebenfalls ersetzt.
replace Text?Text – Einfache Textersetzung im fertigen HTML. Trenner ist das Fragezeichen: "suchen?ersetzen".
prefix Text – Text vor dem Verweis, nur wenn ein Verweis ausgegeben wird.
suffix Text – Text nach dem Verweis, nur wenn ein Verweis ausgegeben wird.
ifempty Text – Ausgabe, wenn das CMS:SELECT kein limit hat (dann gibt es keine Seiten). Passt alles auf eine Seite, erscheint tpl_inactive.
set Array-Index von $set – Der Verweis wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Verweis auf die nächste Seite einer blätterbaren Liste (CMS:SELECT mit count=true und limit). Der Befehl steht wie CMS:JUMP_LIST im resulttemplate der Abfrage, hier bei limit=20:

(CMS:JUMP_PREV) Seite <?php echo intdiv((int)($_REQUEST['skip'] ?? 0), 20) + 1; ?> (CMS:JUMP_NEXT)

Ausgabe: ❮❮ Seite 3 ❯❯

Gibt es keine nächste Seite – auch wenn alles auf eine Seite passt –, erscheint tpl_inactive (Standard: das Symbol ohne Link). Die Seitengröße ist das limit des CMS:SELECT; ohne limit erscheint nur ifempty.

Parameter im Einzelnen

skip

(CMS:JUMP_NEXT skip="{$offset}")

Vorlage mit Link; {href} wird ersetzt. Darf ohne Schlüssel stehen:

(CMS:JUMP_NEXT "<a class=next href='{href}'>weiter ›</a>")

tpl_inactive

Vorlage ohne nächste Seite ({href} wird auch hier ersetzt – mit der Adresse, die die nächste Seite hätte):

(CMS:JUMP_NEXT tpl_link="<a href='{href}'>weiter ›</a>" tpl_inactive="<span class=disabled>weiter ›</span>")

replace

(CMS:JUMP_NEXT replace="❯❯?→")

prefix

(CMS:JUMP_NEXT prefix="<li class=next>" suffix="</li>")

suffix

(CMS:JUMP_NEXT suffix="</li>" prefix="<li>")

ifempty

(CMS:JUMP_NEXT ifempty="")

Greift nur, wenn das CMS:SELECT kein limit hat; Standard ist leer.

set

(CMS:JUMP_NEXT set=next)

← Übersicht

CMS:JUMP_PREV

Erzeugt einen Zurückblättern-Verweis.

(CMS:JUMP_PREV tpl_link [parameter=wert …])

Methode: Pagination::cmsJumpPrev()
Ohne Schlüssel: der erste Wert ohne = gilt als tpl_link

Parameter

Parameter Wert Standard Pflicht Beschreibung
skip Zahl >= 0 $_REQUEST[skip] Versatz der aktuellen Seite (Anzahl übersprungener Ergebnisse). Ohne Angabe gilt der Request-Parameter skip.
tpl_link ¹ HTML-Vorlage <a href="{href}"><strong>❮❮</strong></a> Vorlage für den Verweis, solange es eine vorherige Seite gibt. {href} wird durch die Ziel-URL ersetzt. Die URL ist HTML-maskiert (&amp; statt &), ' und \ sind prozentkodiert – {href} kann direkt in einem Attribut stehen.
tpl_inactive HTML-Vorlage <span>❮❮</span> Vorlage für den nicht anwählbaren Verweis, wenn es keine vorherige Seite gibt. {href} wird ebenfalls ersetzt.
replace Text?Text – Einfache Textersetzung im fertigen HTML. Trenner ist das Fragezeichen: "suchen?ersetzen".
prefix Text – Text vor dem Verweis, nur wenn ein Verweis ausgegeben wird.
suffix Text – Text nach dem Verweis, nur wenn ein Verweis ausgegeben wird.
ifempty Text – Ausgabe, wenn das CMS:SELECT kein limit hat (dann gibt es keine Seiten). Passt alles auf eine Seite, erscheint tpl_inactive.
set Array-Index von $set – Der Verweis wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Verweis auf die vorherige Seite - Gegenstück zu CMS:JUMP_NEXT, wie dieses im resulttemplate eines CMS:SELECT mit limit (das limit ist die Seitengröße):

<nav>(CMS:JUMP_PREV "<a href='{href}'>‹ zurück</a>" tpl_inactive="") (CMS:JUMP_NEXT "<a href='{href}'>weiter ›</a>" tpl_inactive="")</nav>

Auf der ersten Seite erscheint tpl_inactive (hier leer), sonst der Link – auch wenn alles auf eine Seite passt.

Parameter im Einzelnen

skip

(CMS:JUMP_PREV skip="{$offset}")
(CMS:JUMP_PREV "<a class=prev href='{href}'>‹ zurück</a>")

tpl_inactive

(CMS:JUMP_PREV tpl_inactive="<span class=disabled>‹ zurück</span>")

replace

(CMS:JUMP_PREV replace="❮❮?←")

prefix

(CMS:JUMP_PREV prefix="<li class=prev>" suffix="</li>")

suffix

(CMS:JUMP_PREV suffix="</li>" prefix="<li>")

ifempty

(CMS:JUMP_PREV ifempty="")

Greift nur, wenn das CMS:SELECT kein limit hat; Standard ist leer.

set

(CMS:JUMP_PREV set=prev)

← Übersicht

CMS:CONTINUE

Setzt die Template-Abarbeitung trotz eines Success-Templates fort.

(CMS:CONTINUE)

Methode: Cms::cmsContinue()

Parameter

Dieser Befehl hat keine Parameter.

Anwendung

Befehle mit successtemplate (CMS:USER_LOGIN, CMS:USER_LOGIN_CODE, CMS:USER_INPUT, CMS:USER_REGISTER, CMS:USER_REGISTER_CONFIRM, CMS:USER_PW, CMS:USER_DELETE, CMS:EMAIL) binden im Erfolgsfall ihr Template ein und beenden danach die Seite; ebenso CMS:USER_PROTECT und CMS:USER_PW mit ihrem errortemplate und CMS:USER_LOGIN mit dem codetemplate. Steht CMS:CONTINUE vor dem Befehl, läuft das Template nach dem eingebundenen Template stattdessen weiter:

(CMS:CONTINUE)
(CMS:USER_INPUT
  container=comments
  submit="$submit"
  successtemplate=comment_saved
)
<h2>Alle Kommentare</h2>
(CMS:SELECT comment_list
  filter[area]=comments
)

Ohne CMS:CONTINUE würde nach comment_saved nichts mehr ausgegeben - mit ihm erscheint die Bestätigung oberhalb der Liste. Das Flag gilt genau einmal: Der nächste dieser Befehle, der tatsächlich ein Template einbindet, verbraucht es.


← Übersicht

CMS:CSP

Ändert die Script-Policy (Content-Security-Policy) dieser Seite: weitere Script-Quellen, Formularziele oder ein anderer Modus.

(CMS:CSP [parameter=wert …])

Methode: Cms::cmsCsp()

Parameter

Parameter Wert Standard Pflicht Beschreibung
script Quellen – Weitere Script-Quellen, durch Leerzeichen oder Komma getrennt.
form Quellen – Weitere Formularziele (schaltet form-action ein).
mode off / report / enforce – Modus für diese Seite; leer: Einstellung der Website.

Anwendung

Für jede Website lässt sich im Backend eine Script-Policy einschalten (Instanz, Abschnitt „Script-Policy (CSP)“). Dann laufen nur noch Scripts,

  • die die Nonce der Seite tragen (ein Zufallswert je Aufruf) – im Template schreiben Sie dafür <script(CMS:NONCE)>, siehe CMS:NONCE, oder
  • die von der eigenen Website oder aus den eingetragenen Quellen geladen werden (<script src="/js/app.js">).

Code, der über Inhalte in die Seite gerät (ein Kommentar mit <script>, ein Bild mit onerror), trägt keine Nonce und läuft nicht. Voreinstellung ist „aus“; „Nur melden“ lässt alles laufen und schreibt Verstöße ins Protokoll csp – so sieht man vor dem Durchsetzen, was blockiert würde.

CMS:CSP passt die Policy für eine Seite an, etwa die Kasse mit dem Script des Zahlungsanbieters:

(CMS:CSP script="https://js.stripe.com" form="https://checkout.stripe.com")

Der Befehl muss vor der ersten Ausgabe stehen, am besten ganz oben im Template; steht er später oder ist mode unbekannt, legt er eine Fehlermeldung ab (CMS:MESSAGE). Mehrere Aufrufe sammeln Quellen, getrennt durch Leerzeichen oder Komma. Erlaubt sind Host-Angaben (https://js.example.com, *.example.com, mit Port oder Pfad); Schlüsselwörter wie 'unsafe-inline' und reine Schemata wie https: werden verworfen – sie höben die Policy auf.

Was die Policy blockiert und wie es richtig geht:

blockiert stattdessen
<button onclick="…">, <form onsubmit="…"> data-…-Attribut und addEventListener in einem <script> des Templates
<a href="javascript:…"> <a href="#" data-…> + Listener
<script> ohne Nonce <script(CMS:NONCE)>
echo '<script>…</script>' in PHP (CMS:NONCE set=nonce) und echo '<script' . $set['nonce'] . '>' – siehe CMS:NONCE
fremde Scripts (Tag-Manager, Widgets) Quelle in der Instanz eintragen oder CMS:CSP script=…; lädt ein Script weitere nach, „Nachgeladene Scripts erlauben“ einschalten

Beim Speichern eines Templates meldet der Editor jedes <script> ohne (CMS:NONCE), jedes on…=-Attribut und jedes javascript: als Hinweis; auf der Startseite sammelt sie das Widget „Template-Meldungen“ – auch für Templates, die im Dateisystem bearbeitet werden.

Wer eine ganz eigene Policy will (auch für Styles, Bilder, Verbindungen), sendet sie mit CMS:HEADER und holt die Nonce mit (CMS:NONCE format=value).

Parameter im Einzelnen

script

(CMS:CSP script="https://maps.example.com https://cdn.example.com")

form

(CMS:CSP form="https://www.paypal.com")

mode

Eine Seite mit einem alten Widget vorübergehend ausnehmen oder nur melden lassen:

(CMS:CSP mode=report)
(CMS:CSP mode=off)

← Übersicht

CMS:HEADER

Sendet einen HTTP-Header.

(CMS:HEADER text [parameter=wert …])

Methode: Cms::cmsHeader()
Ohne Schlüssel: der erste Wert ohne = gilt als text

Parameter

Parameter Wert Standard Pflicht Beschreibung
text ¹ Text – Textzeile, die als Header an den Browser gesendet werden soll. Ohne Wert (Name:) wird der Header entfernt.
overwrite true / false true false: Ein vorausgegangener Header desselben Typs wird nicht überschrieben; es wird ein neuer Header hinzugefügt.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Sendet einen HTTP-Header. Muss vor der ersten Ausgabe stehen - also am Anfang des Templates, vor jedem HTML und ohne Leerzeile davor:

(CMS:HEADER "Content-Type: application/json; charset=utf-8")
{"title": (CMS:TEXT title specialchars=true lbrconvert=false prefix='"' suffix='"')}

Weitere typische Fälle:

(CMS:HEADER "Content-Type: application/rss+xml; charset=utf-8")
(CMS:HEADER "Cache-Control: no-store")
(CMS:HEADER "HTTP/1.1 404 Not Found")

Weiterleitung – CMS-Befehle lassen sich nicht ineinander schachteln; die Adresse kommt deshalb erst per set in $set und dann in den Header:

(CMS:URL id=startseite set=ziel)
(CMS:HEADER "Location: {$set['ziel']}")

Liefert ein Template CSS, Javascript oder JSON aus, gehört der passende Content-Type an den Anfang – camindo CMS sendet X-Content-Type-Options: nosniff, der Browser führt ein Script mit dem Standardtyp text/html deshalb nicht aus:

(CMS:HEADER "Content-Type: text/javascript; charset=utf-8")

Ist schon etwas ausgegeben, wird eine Fehlermeldung abgelegt (CMS:MESSAGE) und der Header nicht gesendet.

Parameter im Einzelnen

text

Die Header-Zeile; darf ohne Schlüssel stehen:

(CMS:HEADER text="X-Robots-Tag: noindex")
(CMS:HEADER "X-Robots-Tag: noindex")

overwrite

Standardmäßig ersetzt ein Header einen vorherigen desselben Namens; overwrite=false hängt einen weiteren an:

(CMS:HEADER "Link: </style.css>; rel=preload; as=style")
(CMS:HEADER "Link: </app.js>; rel=preload; as=script" overwrite=false)

Voreinstellungen ersetzen oder entfernen

camindo CMS sendet vor dem Template einige Sicherheits-Header:

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: private, no-cache

Die Seite lässt sich damit nur innerhalb der eigenen Website in einen Rahmen legen (Hintergrund und Fehlerbilder: Grundlagen). Jeder dieser Header lässt sich ersetzen; ohne Wert wird er entfernt. Eine Seite, die ein Partner einbetten darf:

(CMS:HEADER "X-Frame-Options:")
(CMS:HEADER "Content-Security-Policy: frame-ancestors 'self' https://partner.example")

Ist für die Website eine Script-Policy eingeschaltet (Backend, Instanz), sendet camindo stattdessen script-src 'self' 'nonce-…' samt frame-ancestors 'self'; für einzelne Seiten passen Sie sie mit CMS:CSP an.

Eine eigene Content-Security-Policy ersetzt die Voreinstellung – nehmen Sie frame-ancestors dann mit auf (die Nonce der Seite liefert CMS:NONCE):

(CMS:HEADER "Content-Security-Policy: frame-ancestors 'self'; script-src 'self' https://cdn.example")

Zwischenspeichern (Cache)

Seiten werden bei jedem Aufruf neu erzeugt. Mit der Voreinstellung Cache-Control: private, no-cache speichert kein Proxy und kein CDN die Seite, und der Browser fragt vor jeder Anzeige erneut beim Server an – geänderte Inhalte erscheinen sofort. Die Zurück-Schaltfläche des Browsers bleibt trotzdem schnell.

Gehört eine Seite einem einzelnen Besucher – es läuft eine Sitzung eines Frontend-Users (auch ein Warenkorb ohne Anmeldung, siehe CMS:USER_BASKET) oder das Frontend-Editing ist eingeschaltet –, sendet camindo stattdessen Cache-Control: private, no-store: Die Seite wird nirgends gespeichert.

Eine Seite, die sich selten ändert, darf länger zwischengespeichert werden:

(CMS:HEADER "Cache-Control: public, max-age=3600")

Setzen Sie den Header an den Template-Anfang. Läuft danach noch ein CMS:USER_*-Befehl mit einer Sitzung, gilt wieder private, no-store – persönliche Seiten landen so in keinem Zwischenspeicher.


← Übersicht

CMS:INCLUDE

Bindet ein Template ein.

(CMS:INCLUDE template)
(CMS:INCLUDE template caching=5m cacheparams=$params)

Methode: Cms::cmsInclude()
Ohne Schlüssel: der erste Wert ohne = gilt als template
PHP-Schnipsel: include_cached (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
template ¹ Templatename – ja Detailtemplate, das eingebunden wird.
set Array-Index von $set – Die gesamte Ausgabe (samt eingebundenem Template) wird nicht ausgegeben, sondern in der Variable $set gespeichert.
caching Sekunden, 30s, 5m, 1h, 1d oder infinite – Die Ausgabe des Templates wird so lange zwischengespeichert und danach ohne erneute Abarbeitung ausgeliefert. Ohne Angabe, leer oder 0: kein Cache.
cacheparams null, bool, int, string oder Array – Alles, wovon die Ausgabe abhängt (Datensatz, Seite, Kunde …): je Wert entsteht eine eigene Cache-Datei. Instanz und Sprache zählen von selbst mit.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Bindet ein anderes Template an dieser Stelle ein - für Kopf- und Fußbereiche, Navigationen, wiederkehrende Blöcke:

(CMS:INCLUDE header)
<main>
  <h1>(CMS:TEXT title)</h1>
  (CMS:INCLUDE text_image_block)
</main>
(CMS:INCLUDE footer)

Das eingebundene Template sieht denselben Datensatz ($cms), dieselben $set-Werte und, innerhalb einer Ergebnisschleife, dieselbe Zeile. Ein Name ohne Bereich muss im Zugriffsbereich der Instanz eindeutig sein, sonst bereich.name:

(CMS:INCLUDE templates.header)

Die Verschachtelungstiefe ist durch admin.max_include_depth (Standard 20) begrenzt - ein Template, das sich selbst einbindet, bricht mit einer Fehlermeldung ab.

Parameter im Einzelnen

template

Name oder bereich.name (keine Id); darf ohne Schlüssel stehen. Im Backend-Editor ist der Wert ein Link auf das Template:

(CMS:INCLUDE template=header)
(CMS:INCLUDE header)
(CMS:INCLUDE templates.header)
(CMS:INCLUDE "{$set['layout']}")        dynamisch

set

Die Ausgabe des eingebundenen Templates landet in $set statt auf der Seite – etwa um einen Baustein zweimal zu verwenden oder an einen anderen Befehl weiterzugeben:

(CMS:INCLUDE preis set=preis)
<p class="preis"><?php echo $set['preis']; ?></p>
<meta property="product:price" content="(CMS:TEXT useval="{$set['preis']}" striptags=true specialchars=true)">

Mit caching kommt auch der abgefangene Text aus dem Cache.

caching

Schaltet den Ausgabe-Cache für diesen Aufruf ein: Beim ersten Aufruf läuft das Template wie gewohnt, seine Ausgabe wird unter var/cache/include/ abgelegt. Jeder weitere Aufruf mit denselben Angaben liefert innerhalb der Frist diese Datei aus – das Template, seine Abfragen und eingebundenen Templates laufen dann nicht.

Wert Frist
300 oder 300s 300 Sekunden
5m 5 Minuten
1h 1 Stunde
1d 1 Tag
infinite unbegrenzt – bis das Template geändert wird
leer oder 0 kein Cache (etwa um ihn per Variable abzuschalten)
(CMS:INCLUDE navigation caching=1h)
(CMS:INCLUDE footer caching=1d)
(CMS:INCLUDE teaser caching="{$set['cache_time']}")

Ein ungültiger Wert (caching=5x) erscheint als Fehlermeldung; das Template läuft dann ohne Cache.

cacheparams

Der Cache unterscheidet von selbst nach Website und Sprache. Hängt die Ausgabe von weiteren Dingen ab – dem Datensatz, der Seite einer Liste, einem Filter, dem angemeldeten Kunden –, gehören sie in cacheparams. Für jeden anderen Wert entsteht eine eigene Cache-Datei.

Erlaubt sind null, true/false, Zahlen, Texte und Arrays. Eine Variable ohne Anführungszeichen wird unverändert übergeben, auch ein Array:

<?php $params = ['id' => $cms['record_id'], 'seite' => (int)($_GET['skip'] ?? 0)]; ?>
(CMS:INCLUDE produktliste caching=10m cacheparams=$params)
(CMS:INCLUDE produkt_box caching=1h cacheparams=$cms['record_id'])
(CMS:INCLUDE produkt_box caching=1h cacheparams="produkt-{$cms['record_id']}")

Arrays werden als Ganzes verglichen – dieselben Schlüssel in anderer Reihenfolge ergeben eine andere Cache-Datei.

Was der Cache beachtet

  • Wird das eingebundene Template geändert, gelten seine Cache-Dateien sofort nicht mehr. Ändern sich dagegen Inhalte im Backend, sieht der Besucher sie erst nach Ablauf der Frist. Wählen Sie die Frist danach, wie schnell Änderungen sichtbar werden müssen.
  • Ein Template, das selbst Templates einbindet, legt deren Ausgabe mit ab. Ändert sich ein solches inneres Template, bleibt die gecachte Ausgabe bis zum Ablauf der Frist.
  • Gespeichert wird nur die Ausgabe. Was das Template sonst tut – Werte in $set setzen, CMS:HEADER, CMS:CSP, Cookies, E-Mails –, geschieht bei einem Treffer nicht. Solche Befehle gehören ins aufrufende Template.
  • Die Nonce der Script-Policy (CMS:NONCE) ist in jeder Auslieferung die aktuelle.
  • Formulare mit Sicherheits-Token (CMS:USER_TOKEN, CMS:USER_INPUT) und persönliche Inhalte angemeldeter Kunden nicht cachen – oder den Kunden in cacheparams aufnehmen.
  • Meldet ein Befehl beim Erzeugen einen Fehler, wird die Ausgabe nicht abgelegt.
  • In der Vorschau aus dem Backend und bei eingeschaltetem Frontend-Editing gilt kein Cache.
  • Rufen viele Besucher gleichzeitig ein abgelaufenes Template auf, erzeugt es nur einer; die anderen warten kurz und bekommen dann das Ergebnis.

Aufgeräumt werden abgelaufene Dateien von der Zeitplaner-Aufgabe „Frontend-Cache aufräumen“; ganz geleert wird der Cache mit php Core/cron/camindo.php cache include. Beides richtet der Systemadministrator ein.


← Übersicht

CMS:NONCE

Gibt die Nonce der Seite aus – für jedes Script im Template.

(CMS:NONCE [parameter=wert …])

Methode: Cms::cmsNonce()

Parameter

Parameter Wert Standard Pflicht Beschreibung
format "attribute" / "value" "attribute" attribute: nonce="…" (mit führendem Leerzeichen, für ein <script>-Tag). value: nur der Wert.
set Array-Index von $set – Die Ausgabe wird nicht ausgegeben, sondern in $set gespeichert.

Anwendung

Ist die Script-Policy der Website durchgesetzt (siehe CMS:CSP), laufen nur Scripts mit der Nonce der Seite – einem Zufallswert, der mit jedem Aufruf wechselt. Schreiben Sie deshalb jedes Script im Template so:

<script(CMS:NONCE)>
document.querySelectorAll('[data-confirm]').forEach(function (form) {
  form.addEventListener('submit', function (ev) {
    if (!confirm(form.dataset.confirm)) ev.preventDefault();
  });
});
</script>

Auch eingebundene Dateien bekommen sie, wenn sie nicht von der eigenen Website kommen:

<script src="https://cdn.example.com/lib.js"(CMS:NONCE)></script>

camindo CMS setzt die Nonce nicht selbst ein. Fehlt sie, meldet der Template-Editor beim Speichern einen Hinweis („script without (CMS:NONCE)“), ebenso das Widget „Template-Meldungen“ auf der Startseite. Solange die Script-Policy aus ist, schadet das Attribut nicht – schreiben Sie es trotzdem gleich mit, dann lässt sich die Policy später einschalten, ohne die Templates zu ändern.

onclick="…", onsubmit="…" und href="javascript:…" laufen unter der Policy gar nicht, auch nicht mit Nonce: verlegen Sie den Code in ein <script(CMS:NONCE)> mit addEventListener (Beispiel oben).

Erzeugt PHP das Tag, holen Sie die Nonce mit set:

(CMS:NONCE set=nonce)
<?php echo '<script' . $set['nonce'] . '>var shop = ' . json_encode($config) . ';</script>'; ?>

Für eine eigene Policy per CMS:HEADER:

(CMS:NONCE format=value set=nonce)
(CMS:HEADER "Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{$set['nonce']}'")

Eine Seite, die ein Proxy oder Cache mit fester Nonce ausliefert, schützt nicht mehr – Seiten mit Script-Policy nicht als Ganzes cachen.

Parameter im Einzelnen

format

<script(CMS:NONCE)>…</script>
(CMS:NONCE format=value)

set

(CMS:NONCE set=nonce)

← Übersicht

CMS:SAVEDATA

Speichert die Daten des aktuellen Datensatzes in der angegebenen Variable.

(CMS:SAVEDATA phpvar [parameter=wert …])

Methode: Cms::cmsSaveData()
Ohne Schlüssel: der erste Wert ohne = gilt als phpvar

Parameter

Parameter Wert Standard Pflicht Beschreibung
phpvar ¹ Index innerhalb $GLOBALS – ja Variable, in der die Daten des aktuellen Datensatzes abgelegt werden sollen.
id Datensatz-Id oder Slug – Datensatz, dessen Daten geladen werden sollen. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL. Ohne Angabe der aktuell geladene Datensatz.
lang Sprachversions-Kürzel $lang Gibt an, in welcher Sprache die Inhalte mehrsprachiger Felder geladen werden sollen. Noch nicht umgesetzt - derzeit gilt immer die Sprache des Aufrufs.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Legt einen kompletten Datensatz als Array in einer globalen PHP-Variable ab - für Logik, die mit den Ausgabebefehlen allein nicht zu schreiben ist:

(CMS:SAVEDATA article)
<?php if ($article['price'] > 100 && $article['stock'] > 0) { ?>
  <span class="badge">Premium</span>
<?php } ?>

Mit id einen anderen Datensatz laden, etwa den Elternknoten, um mit seinen Werten zu rechnen:

(CMS:SAVEDATA parent id="{$cms['parent_id']}")
<?php if ($parent['price'] > $cms['price']) { ?> … <?php } ?>

Geht es nur um die Ausgabe von Feldern eines anderen Datensatzes, brauchen Sie CMS:SAVEDATA nicht – die Ausgabebefehle lesen mit id selbst:

<a href="(CMS:URL "{$cms['parent_id']}")">(CMS:TEXT title id="{$cms['parent_id']}")</a>
<footer>(CMS:TEXT footer_text id=konfiguration)</footer>

Das Array enthält die Metafelder (record_id, area_id, online_at …) und die Felder des Bereichs in der aktuellen Sprache; Upload-Felder als Datei-Array (filename, mime_type, size …). Verweisfelder fehlen – ihre Ziele liefert CMS:SELECT relation=…, ihre Anzahl (CMS:SELECT x relation=topics hits=false) – danach steht sie in $this->db->num_rows_unlimited. Gibt es den Datensatz nicht, ist das Array leer.

Parameter im Einzelnen

phpvar

Name der Variable; darf ohne Schlüssel stehen. Nur Buchstaben, Ziffern und _. Das Array steht danach direkt als $article im Template – auch in mit CMS:INCLUDE eingebundenen Templates – und zusätzlich in $GLOBALS['article'], etwa für eigene PHP-Funktionen:

(CMS:SAVEDATA phpvar=article)
(CMS:SAVEDATA article)
<?php echo $article['record_id']; ?>

Namen, die mit _ beginnen, sowie this und GLOBALS sind nur über $GLOBALS[…] erreichbar.

id

Anderer Datensatz - record_id oder Slug:

(CMS:SAVEDATA parent id="{$cms['parent_id']}")
(CMS:SAVEDATA site id=konfiguration)

lang

Noch nicht umgesetzt - es gilt die Sprache des Aufrufs.


← Übersicht

CMS:FED

Erzeugt einen Verweis zum Bearbeiten eines Datensatzes, wenn das Frontend-Editing aktiviert ist.

(CMS:FED id [parameter=wert …])

Methode: Cms::cmsFed()
Ohne Schlüssel: der erste Wert ohne = gilt als id

Parameter

Parameter Wert Standard Pflicht Beschreibung
label Text [»] Text, der verlinkt wird.
id ¹ Datensatz-Id oder Slug (aktueller Datensatz) Datensatz, auf den verwiesen wird. Reine Ziffernfolge = record_id, alles andere = Slug - dieselbe Regel wie in der URL.
info true / false false true: Der Verweis öffnet die Detailansicht des Datensatzes statt der Eingabemaske.
prefix Text – Text, der (unverlinkt) vor dem Verweis ausgegeben wird.
suffix Text – Text, der (unverlinkt) nach dem Verweis ausgegeben wird.
set Array-Index von $set – Der Verweis wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Bearbeiten-Verweis ins Backend - nur sichtbar, wenn der Betrachter das Frontend-Editing in seinen persönlichen Backend-Einstellungen eingeschaltet hat (Cookie fed). Für Besucher ohne Cookie gibt der Befehl nichts aus, die Seite bleibt unverändert:

<h1>(CMS:TEXT title) (CMS:FED)</h1>

Ausgabe (mit Cookie): <h1>Hallo Welt <a href="#" data-c9-fed="1977" class="c9-fed"><span style="color:#f00">[&raquo;]</span></a></h1> – den Klick wertet (CMS:FED_SCRIPT) aus (einmal je Seite) und öffnet das Backend in einem neuen Tab, bei fester Backend-Domain unter deren Adresse.

In einer Liste je Zeile, mit eigenem Text:

(CMS:RESULT_START)
<li>(CMS:TEXT title) (CMS:FED label="✎" prefix=" " )</li>
(CMS:RESULT_END)

Das Backend verlangt weiterhin die Anmeldung - der Verweis öffnet bei Bedarf den Anmeldedialog über der Eingabemaske.

Parameter im Einzelnen

label

Linktext (HTML erlaubt); Standard ist ein rotes [»] (<span style="color:#f00">[&raquo;]</span>):

(CMS:FED label="bearbeiten")
(CMS:FED label="<img src='/static/edit.svg' alt='bearbeiten'>")

id

Anderer Datensatz; darf ohne Schlüssel stehen:

(CMS:FED 1977)
(CMS:FED id="{$cms['parent_id']}" label="Eltern bearbeiten")

info

Detailansicht statt Eingabemaske öffnen:

(CMS:FED info=true label="Details")

prefix

Unverlinkter Text vor dem Verweis - nur, wenn der Verweis ausgegeben wird:

(CMS:FED prefix="<span class=edit>" suffix="</span>")

suffix

(CMS:FED suffix="</span>" prefix="<span class=edit>")

set

(CMS:FED set=edit)
<?php if ($set['edit'] !== '') { ?><div class="toolbar"><?php echo $set['edit']; ?></div><?php } ?>

← Übersicht

CMS:FED_CODE

Blendet HTML-Code ein, wenn das Frontend-Editing aktiviert ist.

(CMS:FED_CODE code [parameter=wert …])

Methode: Cms::cmsFedCode()
Ohne Schlüssel: der erste Wert ohne = gilt als code

Parameter

Parameter Wert Standard Pflicht Beschreibung
code ¹ HTML-Code – HTML-Code, der ausgegeben werden soll, wenn das Frontend-Editing eingeschaltet ist.
instead HTML-Code – Alternative Ausgabe, wenn das Frontend-Editing ausgeschaltet ist.
set Array-Index von $set – Der Code wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Beliebiges HTML, das nur bei eingeschaltetem Frontend-Editing erscheint - etwa eine Werkzeugleiste oder ein Hinweis auf den Bearbeitungsmodus:

(CMS:FED_CODE "<div class=fed-bar>Bearbeitungsmodus – <a href='/backend/'>Backend öffnen</a></div>")

Mit instead eine Alternative für Besucher ohne Frontend-Editing:

<body class="(CMS:FED_CODE code="editing" instead="public")">

Parameter im Einzelnen

code

HTML bei eingeschaltetem Frontend-Editing; darf ohne Schlüssel stehen:

(CMS:FED_CODE code="<link rel=stylesheet href='/static/fed.css'>")
(CMS:FED_CODE "<link rel=stylesheet href='/static/fed.css'>")

instead

Ausgabe für Besucher ohne Frontend-Editing:

(CMS:FED_CODE code="<p>Sie sehen die Redaktionsansicht.</p>" instead="")

set

(CMS:FED_CODE code=1 instead=0 set=fed)
<?php if ($set['fed']) { ?> … <?php } ?>

← Übersicht

CMS:FED_SCRIPT

Stellt das Script für die Bearbeiten-Verweise aus CMS:FED bereit – einmal je Seite.

(CMS:FED_SCRIPT [set])

Methode: Cms::cmsFedScript()
Ohne Schlüssel: der erste Wert ohne = gilt als set

Parameter

Parameter Wert Standard Pflicht Beschreibung
set ¹ Array-Index von $set – Der Script-Block wird nicht ausgegeben, sondern in der Variable $set gespeichert.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Die Verweise aus CMS:FED enthalten kein Ziel und keinen Javascript-Code. Den Klick wertet erst das Script aus CMS:FED_SCRIPT aus und öffnet die Eingabemaske (bzw. mit info=true die Detailansicht) im Backend in einem neuen Tab – bei fester Backend-Domain unter deren Adresse. Ohne CMS:FED_SCRIPT auf der Seite bleibt ein Klick auf den Verweis wirkungslos.

Schreiben Sie den Befehl einmal in ein Template, das jede Seite einbindet, üblicherweise in den Footer vor </body>:

(CMS:FED_SCRIPT)
</body>
</html>

Die Reihenfolge zu den CMS:FED-Verweisen spielt keine Rolle. Wie CMS:FED gibt der Befehl nur etwas aus, wenn der Betrachter das Frontend-Editing eingeschaltet hat; für Besucher bleibt die Seite unverändert. Steht der Befehl mehrfach auf einer Seite (etwa über mehrere CMS:INCLUDE), erscheint das Script trotzdem nur einmal.

Das <script>-Tag trägt die Nonce der Seite und läuft deshalb auch unter einer durchgesetzten Script-Policy (siehe CMS:CSP).

Parameter im Einzelnen

set

set gibt nichts aus, sondern legt den Script-Block unter $set['fed'] ab, etwa um ihn an einer anderen Stelle auszugeben:

(CMS:FED_SCRIPT set=fed)
<?php echo $set['fed']; ?>

← Übersicht

CMS:USER_2FA

Zweiten Faktor des angemeldeten Frontend-Users anzeigen, einrichten (Authenticator-App oder Code per E-Mail) und abschalten.

(CMS:USER_2FA [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsTwoFactor()

Parameter

Parameter Wert Standard Pflicht Beschreibung
function status / totp_setup / totp_enable / code_enable / disable / recovery_renew status Was der Befehl tut (siehe unten).
submit Textwert – Schreibende Funktionen laufen nur, wenn in diesem Feld ein Text enthalten ist.
data PHP-Variablenname data Name des PHP-Arrays mit den Eingaben.
codefield Feldname code Eingabefeld für den App-Code in data[…].
currentfield Feldname current_password Eingabefeld für das Passwort in data[…].
message[…] Standardmeldung – Fehlermeldungen. Im Index: Anwendungsfall; im Wert: Meldungstext.
cookiename Cookiename usess Name des Session-Cookies [a-z0-9].
csrf true / false true true: Schreibende Funktionen verlangen ein Token aus CMS:USER_TOKEN.

Anwendung

Der Befehl gehört auf eine geschützte Seite des Kundenkontos (nach CMS:USER_PROTECT). Er wirkt nur, wenn der Profil-Bereich einen zweiten Faktor zulässt (Einstellung „Zweiter Faktor“ nicht „aus“, sonst Meldung policy).

function Wirkung Bestätigt mit
status nur lesen –
totp_setup neuen Schlüssel für die Authenticator-App erzeugen – gespeichert wird noch nichts: $set['totp_setup'] = token, qr (fertiges HTML mit QR-Code), secret (zum Abtippen, in Vierergruppen), uri (otpauth://…, öffnet auf dem Telefon direkt die App), qr_script (Adresse der QR-Bibliothek) –
totp_enable Schlüssel speichern, zehn Wiederherstellungscodes erzeugen ($set['recovery_codes'], nur jetzt im Klartext) data[token] und dem ersten Code aus der App (data[code])
code_enable Code per E-Mail bei jeder Anmeldung Passwort (data[current_password]); ist eine App eingerichtet, dem App-Code
disable abschalten – verlangt der Bereich immer einen zweiten Faktor, wird aus der App der Code per E-Mail App-Code oder Wiederherstellungscode bzw. Passwort
recovery_renew neue Wiederherstellungscodes, die alten verfallen App-Code

Nach jeder Funktion stehen in $set: second_factor ('', totp, email), second_factor_policy (Einstellung des Bereichs: none, optional, new_device, required), second_factor_since (UTC), recovery_count; nach einer Änderung second_factor_changed (totp_enabled, totp_disabled, code_enabled, code_disabled, recovery_renewed) – etwa für eine Hinweis-E-Mail an den Frontend-User.

Zum Einrichten scannt der Frontend-User den QR-Code mit der App; er enthält den Schlüssel (nicht die Codes – die erzeugt die App danach selbst, je sechs Ziffern). Alternativ tippt er den Schlüssel ab oder öffnet auf dem Telefon den Link. $set['totp_setup']['qr'] bringt alles mit: ein Element mit dem QR-Code, die Bibliothek qrcode-generator (dieselbe wie im Backend, aus dessen Assets) und ein kleines Script, beide mit der Nonce der Seite – es läuft also auch unter einer durchgesetzten Script-Policy. Geben Sie es nur aus (echo), die Größe steuern Sie per CSS (.c9-qr { width: 200px }).

<?php
$isPost = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
$action = $isPost ? (string)($_POST['action'] ?? '') : (isset($_GET['setup']) ? 'totp_setup' : 'status');
$submit = $isPost ? '1' : '';
?>
(CMS:USER_PROTECT errortemplate=u_login)
(CMS:USER_2FA function="$action" submit="$submit")
<p class="error">(CMS:MESSAGE)</p>
<?php if (!empty($set['recovery_codes'])) { ?>
  <p>Ihre Wiederherstellungscodes (bitte sicher aufbewahren):</p>
  <ul><?php foreach ($set['recovery_codes'] as $c) echo '<li>' . htmlspecialchars($c) . '</li>'; ?></ul>
<?php } ?>
<?php if (!empty($set['totp_setup'])) { $s = $set['totp_setup']; ?>
  <?php echo $s['qr']; ?>
  <p>Schlüssel für Ihre Authenticator-App: <code><?php echo htmlspecialchars($s['secret']); ?></code>
     – oder <a href="<?php echo htmlspecialchars($s['uri']); ?>">in der App öffnen</a>.</p>
  <form method="post">
    (CMS:USER_TOKEN)
    <input type="hidden" name="action" value="totp_enable">
    <input type="hidden" name="data[token]" value="<?php echo htmlspecialchars($s['token']); ?>">
    <input name="data[code]" autocomplete="one-time-code" required>
    <button>Einschalten</button>
  </form>
<?php } elseif ($set['second_factor'] === '') { ?>
  <p><a href="?setup=1">Authenticator-App einrichten</a></p>
<?php } ?>

Meldungs-Indizes: auth (nicht angemeldet), policy, code (App-Code falsch), current (Passwort falsch), setup (Einrichtung abgelaufen – 15 Minuten), blocked (zu viele Fehlversuche), token. Falsche Codes und Passwörter zählen wie bei der Anmeldung.

Hat ein Frontend-User Telefon und Wiederherstellungscodes verloren, setzt der Betreiber den zweiten Faktor im Backend zurück (Datensatz des Profils, Menü „Zweiter Faktor“).

Parameter im Einzelnen

function

(CMS:USER_2FA function=status)
(CMS:USER_2FA function="$action" submit="$submit")

submit

Nur schreibende Funktionen brauchen ihn – sie gehören in ein POST-Formular:

<?php $submit = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' ? '1' : ''; ?>

data, codefield, currentfield

(CMS:USER_2FA function=disable submit="$submit" data=sec codefield=otp currentfield=pw)

message

(CMS:USER_2FA function="$action" submit="$submit" message[code]="Der Code stimmt nicht.")

← Übersicht

CMS:USER_AUTH

Prüft eine Session-ID auf Gültigkeit.

(CMS:USER_AUTH [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsAuth()

Parameter

Parameter Wert Standard Pflicht Beschreibung
sessionid Session-ID – Session des Frontend-Users anhand der angegebenen sessionid wieder aufnehmen anstatt über ein Cookie.
filter[…][…] vgl. Parameter "filter[]" von CMS:SELECT – Filterkriterien für den Profil-Datensatz, die beim angemeldeten Frontend-User erfüllt sein müssen.
mode[…] AND / OR OR Modus, mit dem Werte verknüpft werden, die dasselbe Feld betreffen.
cookiename Cookiename usess Name des Session-Cookies [a-z0-9].

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Nimmt die Frontend-Session auf und stellt den angemeldeten Frontend-User bereit, ohne die Seite zu sperren - für Seiten, die angemeldeten und anonymen Besuchern unterschiedliche Inhalte zeigen. Ergebnis in $set:

$set[…] Inhalt
user_id record_id des Profils, 0 = nicht angemeldet
user der Profil-Datensatz als Array ([] wenn nicht angemeldet)
user_session_id Session-Id, '' = keine Session
password_reset 1 nach einer Anmeldung über den Link aus CMS:USER_PW, solange ein neues Passwort ohne das bisherige gesetzt werden darf, sonst 0
(CMS:USER_AUTH)
<?php if ($set['user_id']) { ?>
  Hallo <?php echo htmlspecialchars($set['user']['nickname']); ?> – <a href="/0,u_logout">abmelden</a>
<?php } else { ?>
  <a href="/0,u_login">anmelden</a>
<?php } ?>

Der Befehl gehört an den Anfang des Templates (vor jeder Ausgabe), damit das Session-Cookie gesetzt werden kann. Für Seiten, die nur angemeldete Frontend-User sehen dürfen, ist CMS:USER_PROTECT der passende Befehl.

Parameter im Einzelnen

sessionid

Session per Parameter statt Cookie aufnehmen - für Aufrufe aus Apps oder von anderen Domains:

(CMS:USER_AUTH sessionid="{$_GET['sid']}")

filter

Zusätzliche Bedingung an das Profil, Syntax wie CMS:SELECT. Trifft sie nicht zu, gilt der Frontend-User als nicht angemeldet:

(CMS:USER_AUTH filter[role]=editor)
(CMS:USER_AUTH filter[status_id]=0 filter[newsletter]=1)

mode

Verknüpfung mehrerer Werte desselben Feldes in filter (Standard OR):

(CMS:USER_AUTH filter[role]=editor filter[role]=admin mode[role]=OR)

cookiename

Name des Session-Cookies (Standard usess), etwa für zwei getrennte Anmeldungen auf einer Domain:

(CMS:USER_AUTH cookiename=shop)

Muss bei allen USER_*-Befehlen derselbe sein.


← Übersicht

CMS:USER_BASKET

Bearbeiten eines Warenkorbs.

(CMS:USER_BASKET function [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsBasket()
Ohne Schlüssel: der erste Wert ohne = gilt als function

Parameter

Parameter Wert Standard Pflicht Beschreibung
function ¹ "load" / "add" / "update" / "update_addon" "load" ja Funktion, die ausgeführt werden soll.
overwrite false / true false function=add + overwrite=true: Die Anzahl der bereits gespeicherten Artikel wird beim Hinzufügen aktualisiert.
category Kategorie-ID 0 Warenkorb-Kategorie.
container Container-IDs oder -Namen, kommasepariert – Nur Datensätze aus diesen Containern kommen in den Warenkorb. Ohne Angabe: veröffentlichte Datensätze aus Containern vom Typ "record" oder "ugc" (nie Profile).
variable Variablenname – Name der Variable, in der Artikel-IDs als Array gespeichert sind.
duration Anzahl Tage 30 Anzahl der Tage, die der Warenkorb gespeichert bleibt, sofern keine Änderungen durchgeführt werden.
basket_addon Text – Text (serialisierte Daten), die gespeichert werden sollen.
csrf true / false true true: Der Request muss ein gültiges Token aus CMS:USER_TOKEN enthalten (Schutz gegen CSRF). false schaltet die Prüfung für diesen Befehl ab – nur mit gutem Grund.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Warenkorb je Frontend-Session - funktioniert auch ohne Anmeldung (anonyme Session). Ergebnis in $set:

$set[…] Inhalt
basket record_id => Menge
basket_count Summe der Mengen
basket_addon mit update_addon gespeicherter Text der Kategorie

Artikel hinzufügen (Formular mit items[]), Warenkorb anzeigen:

<?php $f = $_POST['f'] ?? 'load'; ?>
(CMS:USER_BASKET function="$f" variable=items)
(CMS:USER_BASKET load)
<h2>Warenkorb (<?php echo (int)$set['basket_count']; ?> Stück)</h2>
<form method="post"><input type="hidden" name="f" value="update">
  (CMS:USER_TOKEN)
(CMS:RESULT_START)
  <p>(CMS:TEXT title): <input name="items[(CMS:NUMBER record_id)]" value="<?php echo (int)$set['basket'][$cms['record_id']]; ?>" size="3"></p>
(CMS:RESULT_END)
<button>Mengen aktualisieren (0 = entfernen)</button>
</form>

Nur nach load stehen die Artikel als Ergebnismenge bereit - CMS:RESULT_START läuft über sie wie nach einem CMS:SELECT. Nach add, update und update_addon ist $set aktuell, für die Liste folgt deshalb ein zweiter Aufruf mit load (Beispiel oben). Ohne gültiges Token aus CMS:USER_TOKEN ändert eine schreibende Funktion nichts. Ein „In den Warenkorb“-Formular in einer Artikelliste:

<form method="post" action="/0,u_basket">
  (CMS:USER_TOKEN)
  <input type="hidden" name="f" value="add">
  <button name="items[]" value="(CMS:TEXT record_id)">In den Warenkorb</button>
</form>

Parameter im Einzelnen

function

Darf ohne Schlüssel stehen:

(CMS:USER_BASKET load)                       Inhalt lesen, Artikel als Ergebnismenge
(CMS:USER_BASKET add variable=items)         Ids aus $_REQUEST['items'] hinzufügen
(CMS:USER_BASKET update variable=items)      Mengen setzen (0 entfernt)
(CMS:USER_BASKET update_addon basket_addon="{$note}")

overwrite

Bei add: Menge setzen statt addieren:

(CMS:USER_BASKET add variable=items overwrite=true)

Mit items[5]=2 liegt Artikel 5 danach genau 2-mal im Korb, auch wenn er schon drin war.

category

Mehrere Warenkörbe je Session, z. B. Merkliste neben dem Einkaufskorb:

(CMS:USER_BASKET add variable=items category=1)
(CMS:USER_BASKET load category=1)

container

Beschränkt den Warenkorb auf bestimmte Inhaltsbereiche (Namen oder Ids, kommasepariert). Ohne Angabe sind veröffentlichte Datensätze aus Bereichen vom Typ „record“ oder „ugc“ erlaubt, nie Profile. Die Prüfung gilt auch beim Lesen: Artikel außerhalb der Bereiche erscheinen weder in $set['basket'] noch in der Ergebnismenge.

(CMS:USER_BASKET add variable=items container=produkte,zubehoer)
(CMS:USER_BASKET load container=produkte,zubehoer)

variable

Name des Request-Parameters mit den Artikel-Ids - als Liste (items[]=5, Menge 1 je Nennung) oder mit Mengen (items[5]=2):

<input type="hidden" name="cart[<?php echo $id; ?>]" value="1">
(CMS:USER_BASKET add variable=cart)

duration

Tage, die der Warenkorb ohne Änderung erhalten bleibt (Standard 30):

(CMS:USER_BASKET add variable=items duration=7)

basket_addon

Freitext zur Kategorie ablegen (Lieferhinweis, Gutscheincode, serialisierte Daten):

(CMS:USER_BASKET update_addon basket_addon="{$_POST['note']}")
<?php echo htmlspecialchars($set['basket_addon']); ?>

← Übersicht

CMS:USER_DELETE

Löscht einen Datensatz, der dem angemeldeten Frontend-User gehört.

(CMS:USER_DELETE id [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsDelete()
Ohne Schlüssel: der erste Wert ohne = gilt als id
PHP-Schnipsel: include_and_stop (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
id ¹ Datensatz-ID – ja Datensatz, der gelöscht werden soll.
ignoreuser true / false false true: Der zu löschende Datensatz muss nicht dem angemeldeten Frontend-User gehören (Achtung: Sicherheitshinweise beachten!).
successtemplate Templatename – ja Template, das eingebunden wird, wenn der Eintrag erfolgreich gelöscht wurde.
message_error Text Zugriff verweigert! Fehlermeldung, falls der Benutzer keinen Zugriff auf den Datensatz hat.
csrf true / false true true: Der Request muss ein gültiges Token aus CMS:USER_TOKEN enthalten (Schutz gegen CSRF). false schaltet die Prüfung für diesen Befehl ab – nur mit gutem Grund.

¹ kann ohne Schlüssel als erster Wert angegeben werden.

Anwendung

Löscht einen Datensatz, der dem angemeldeten Frontend-User gehört (record.user_id) - oder das eigene Profil, dann werden alle Sessions und bekannten Geräte des Users entfernt.

Was „löschen“ heißt, bestimmt die Lösch-Policy des Bereichs mit ihren Blöcken für den Auslöser „Datensatz wird gelöscht“ – derselbe wie beim Löschen im Backend: in den Papierkorb, endgültig, einzelne Felder überschreiben (etwa Status auf „deaktiviert“ und Anmeldename user_{record_id}), auch zeitversetzt. Löscht der User sein Profil, werden zugleich alle seine Beiträge nach deren Policy gelöscht (Auslöser „UGC: Profil wurde gelöscht“). Hat die Policy keinen Block für diesen Auslöser, gibt der Befehl eine Fehlermeldung aus. Siehe Lösch-Policies.

Ein schon zum Löschen vorgemerkter Datensatz kann nicht erneut gelöscht werden.

<?php $id = (int)($_POST['id'] ?? 0); if ($id > 0) { ?>
(CMS:USER_DELETE id="$id" successtemplate=u_deleted message_error="Dieser Eintrag gehört Ihnen nicht.")
<?php } ?>
(CMS:MESSAGE)
<form method="post">
  (CMS:USER_TOKEN)
  <input type="hidden" name="id" value="<?php echo (int)$_GET['id']; ?>">
  <button>Eintrag endgültig löschen</button>
</form>

Der Befehl kennt kein submit: Er läuft bei jedem Aufruf und meldet ohne gültige id einen Fehler - deshalb die PHP-Bedingung um den Befehl (CMS-Befehle werden zu PHP-Code, ein umgebendes if ist erlaubt). Nach Erfolg wird successtemplate eingebunden und die Seite beendet (außer mit CMS:CONTINUE).

Parameter im Einzelnen

id

Datensatz-Id; darf ohne Schlüssel stehen. Ohne gültige Id erscheint die Meldung aus message_error:

(CMS:USER_DELETE "{$_POST['id']}" successtemplate=u_deleted)
(CMS:USER_DELETE id="{$set['user_id']}" successtemplate=u_account_closed)     eigenes Profil löschen

ignoreuser

Besitzprüfung und Anmeldung übergehen - das Template trägt dann die Verantwortung (z. B. Löschen per geheimem Link):

(CMS:USER_DELETE id="{$id}" ignoreuser=true successtemplate=u_deleted)

successtemplate

(CMS:USER_DELETE id="$id" successtemplate=u_deleted)

u_deleted kann z. B. „Eintrag gelöscht“ ausgeben und per CMS:INCLUDE die Navigation zeigen.

message_error

Text, wenn der Datensatz nicht existiert, nicht dem Frontend-User gehört oder niemand angemeldet ist (Ausgabe über CMS:MESSAGE). Fehlt das Token aus CMS:USER_TOKEN, erscheint stattdessen die Standardmeldung für ein abgelaufenes Formular:

(CMS:USER_DELETE id="$id" successtemplate=u_deleted message_error="Löschen nicht möglich.")

← Übersicht

CMS:USER_INPUT

Erzeugt oder aktualisiert einen Datensatz aus Daten, die ein Frontend-User angegeben hat.

(CMS:USER_INPUT [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsInput()
PHP-Schnipsel: include_and_stop (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
successtemplate Templatename – Template, das eingebunden wird, wenn der Eintrag erfolgreich gespeichert wurde.
data PHP-Variablenname data Name des PHP-Arrays, das die zu speichernden Daten enthält.
overwrite[…] Schlüssel-Wert-Paare – Explizites Überschreiben der Eingaben des Frontend-Users (auch Metafelder, außer record_id, area_id, instance_id, tmp, deleted, origin_id, owner_id, created_by, changed_by). Hier keine Eingaben des Frontend-Users weiterreichen!
container Container-ID oder -Name – ja ID oder Name des Content-Containers, in dem der neue Datensatz gespeichert werden soll.
message[…] Text Standardtext Fehlermeldung abhängig vom Index (siehe Tabelle).
submit Textwert – ja Der Befehl wird nur ausgeführt, wenn in diesem Feld ein Text enthalten ist.
fields kommaseparierte Liste – Namen der Eingabefelder, die geschrieben werden dürfen. Leer = alle definierten Felder des Containers.
id Datensatz-ID – Falls angegeben, wird versucht diesen Datensatz zu aktualisieren (UPDATE). Andernfalls wird ein neuer erstellt (CREATE).
ignoreuser true / false false true: Der anzupassende Datensatz muss nicht dem angemeldeten Frontend-User gehören (Achtung: Sicherheitshinweise beachten!).
forceupdate true / false false true: Es wird erzwungen, den über Parameter "id" angegebenen Datensatz zu aktualisieren. Wirft im Fehlerfall eine Meldung.
masterid Datensatz-ID – Nur beim Anlegen in Bereichen mit Baumstruktur: Elternknoten des neuen Datensatzes (parent_id).
owner Benutzer-ID – Nur beim Anlegen: admin_id des Backend-Owners (record.owner_id, created_by). Ohne Angabe gilt user.default_owner_id.
removefiles kommaseparierte Liste – Namen der Upload-Felder, deren Inhalt gelöscht werden soll.
currentfield Feldname current_password Nur Profil-Container: Name des Formularfeldes data[…] mit dem bisherigen Passwort. Ein neues Passwort wird nur mit dem bisherigen gespeichert – außer direkt nach der Anmeldung über den Reset-Link. Meldung current.
csrf true / false true true: Der Request muss ein gültiges Token aus CMS:USER_TOKEN enthalten (Schutz gegen CSRF). false schaltet die Prüfung für diesen Befehl ab – nur mit gutem Grund.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Speichert Formulareingaben als Datensatz - neu anlegen oder (mit id) einen eigenen Datensatz ändern. Die Felder heißen data[feldname], genau wie in der Backend-Eingabemaske; Uploads laufen über dasselbe Formular. Nach Erfolg steht $set['record_id'], das successtemplate wird eingebunden und die Seite beendet:

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_INPUT container=comments submit="$submit" successtemplate=u_saved)
(CMS:USER_AUTH)
<p class="error">(CMS:MESSAGE)</p>
<form method="post" enctype="multipart/form-data">
  (CMS:USER_TOKEN)
  <textarea name="data[comment]">(CMS:TEXT useval="{$_POST['data']['comment']}" specialchars=true lbrconvert=false)</textarea>
  <input type="file" name="data[photo]">
  <button name="submit" value="1">Speichern</button>
</form>

Eigenen Datensatz ändern:

<?php $submit = $_POST['submit'] ?? ''; $id = (int)($_POST['id'] ?? 0); ?>
(CMS:USER_INPUT container=comments id="$id" forceupdate=true submit="$submit" successtemplate=u_saved)

Kontaktformular ohne Anmeldung (ignoreuser), Absender-IP in ein eigenes Feld remote_ip des Bereichs schreiben:

(CMS:USER_INPUT container=anfragen ignoreuser=true submit="$submit" fields="name,email,message"
  overwrite[remote_ip]="{$_SERVER['REMOTE_ADDR']}" successtemplate=anfrage_danke)

Wer schreiben darf: In ugc-Bereichen angemeldete Frontend-User, deren Profil-Bereich beim Bereich freigeschaltet ist; in profile-Bereichen nur das eigene Profil (mit id, ein neues Profil legt nur CMS:USER_REGISTER an); in gewöhnliche Datensatzbereiche nur mit ignoreuser=true. Alle Prüfungen des Bereichs (Pflichtfelder, eindeutige Werte, Dateitypen) gelten wie im Backend; Fehler erscheinen über CMS:MESSAGE. Meldungs-Indizes für message[…]: container, access, notfound, save, current, blocked, owner (kein gültiger Owner beim Anlegen), token.

Passwort ändern

Ein neues Passwort im eigenen Profil speichert der Befehl nur zusammen mit dem bisherigen Passwort im Feld data[current_password] – eine fremde Person, die an eine offene Sitzung gelangt, kann das Konto so nicht übernehmen. Ausnahme: direkt nach der Anmeldung über den Link aus CMS:USER_PW; dann steht $set['password_reset'] auf 1, und das Feld entfällt (eine Stunde lang, bis das neue Passwort gespeichert ist). Nach dem Speichern werden alle anderen Anmeldungen des Profils beendet, und $set['password_changed'] ist 1 – das successtemplate kann den Inhaber per CMS:EMAIL benachrichtigen.

(CMS:USER_PROTECT errortemplate=u_denied)
<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_INPUT container=members id="{$set['user_id']}" forceupdate=true fields=password
  submit="$submit" successtemplate=u_pw_saved)
<p class="error">(CMS:MESSAGE)</p>
<form method="post">
  (CMS:USER_TOKEN)
  <?php if (empty($set['password_reset'])) { ?>
  <input type="password" name="data[current_password]" autocomplete="current-password">
  <?php } ?>
  <input type="password" name="data[password]" autocomplete="new-password"
    data-c9-password="(CMS:USER_PASSWORD_RULES container=members)">
  <button name="submit" value="1">Speichern</button>
</form>

Trägt das Passwort-Feld im Bereich die Extension Passwort-Richtlinie, prüft der Server Mindestlänge und Zeichenklassen; mit CMS:USER_PASSWORD_RULES zeigt das Formular dieselben Regeln schon beim Tippen an.

Parameter im Einzelnen

successtemplate

Nach dem Speichern eingebunden; darin ist der gespeicherte Datensatz der laufende ($cms), $set['record_id'] seine Id:

(CMS:USER_INPUT container=comments submit="$submit" successtemplate=u_saved)

Ohne successtemplate läuft das Template nach dem Speichern normal weiter.

data

Name des Request-Arrays (Standard data):

<input name="form[comment]">
(CMS:USER_INPUT container=comments data=form submit="$submit")

overwrite

Werte fest vorgeben - auch Metafelder - unabhängig davon, was der Frontend-User schickt. Keine Eingaben des Frontend-Users hier einsetzen. Ausgenommen sind record_id, area_id, instance_id, tmp, deleted, origin_id, owner_id, created_by und changed_by; ohne Angabe setzt der Befehl needs_review auf 1:

(CMS:USER_INPUT container=comments submit="$submit"
  overwrite[article_id]="{$cms['record_id']}"
  overwrite[status_id]=2
)

container

Bereich (Name oder Id), in dem gespeichert wird - Typ ugc oder profile, mit ignoreuser=true auch ein gewöhnlicher Datensatzbereich:

(CMS:USER_INPUT container=comments …)
(CMS:USER_INPUT container=58 …)

message

Eigene Meldungstexte:

(CMS:USER_INPUT container=comments submit="$submit"
  message[access]="Bitte melden Sie sich an, um zu kommentieren."
  message[save]="Der Kommentar konnte nicht gespeichert werden."
)

submit

Der Befehl läuft nur, wenn submit einen Wert hat - beim ersten Aufruf des Formulars also nicht:

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_INPUT container=comments submit="$submit" …)
<button name="submit" value="1">Speichern</button>

fields

Nur diese Felder aus den Eingaben übernehmen - alles andere im Formular wird ignoriert:

(CMS:USER_INPUT container=members id="{$set['user_id']}" fields="nickname,avatar" submit="$submit")

id

Vorhandenen Datensatz ändern; ohne forceupdate wird bei unbekannter/fremder Id ein neuer angelegt:

(CMS:USER_INPUT container=comments id="{$_POST['id']}" submit="$submit")

ignoreuser

Keine Anmeldung und keine Besitzprüfung - für Kontakt- und Gästebuchformulare. Das Template trägt die Verantwortung (Spam-Schutz, Feldliste per fields):

(CMS:USER_INPUT container=gaestebuch ignoreuser=true fields="name,message" submit="$submit" successtemplate=gb_danke)

forceupdate

Mit id: Statt bei fremder/unbekannter Id einen neuen Datensatz anzulegen, Meldung notfound ausgeben:

(CMS:USER_INPUT container=comments id="$id" forceupdate=true submit="$submit")

masterid

Elternknoten in einem Bereich mit Baumstruktur (wird zu parent_id):

(CMS:USER_INPUT container=forum masterid="{$cms['record_id']}" submit="$submit")     Antwort unter dem aktuellen Beitrag

currentfield

Anderer Name für das Feld mit dem bisherigen Passwort (siehe Passwort ändern):

(CMS:USER_INPUT container=members id="{$set['user_id']}" fields=password currentfield=old_pw …)
<input type="password" name="data[old_pw]">

removefiles

Upload-Felder leeren - z. B. über eine Checkbox „Foto entfernen“:

<label><input type="checkbox" name="remove_photo" value="photo"> Foto entfernen</label>
(CMS:USER_INPUT container=comments id="$id" removefiles="{$_POST['remove_photo']}" submit="$submit")

← Übersicht

CMS:USER_LOGIN

Überprüft die Anmeldedaten eines Frontend-Users und erzeugt eine neue Session.

(CMS:USER_LOGIN [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsLogin()
PHP-Schnipsel: include_and_stop (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
successtemplate – – ja Detailtemplate, das nach einer erfolgreichen Anmeldung eingebunden wird.
container Container-ID oder -Name – ja ID oder Name des Profil-Containers, in dem die Daten der Frontend-User gespeichert sind.
data PHP-Variablenname data Name des PHP-Arrays, in dem die Anmeldedaten enthalten sind.
message[…] Standardmeldung – Fehlermeldungen. Im Index: Anwendungsfall; im Wert: Meldungstext (vgl. Tabelle).
hash Hash – Ein Login kann auch mittels Hash durchgeführt werden (Token aus CMS:USER_PW, Passwort vergessen).
trim true / false false true: Leerzeichen am Anfang und Ende der Eingaben entfernen.
cookie true / false true true: Es wird standardmäßig ein Cookie gesetzt, um die Anmeldung über mehrere Seiten hinweg aufrechtzuerhalten.
submit Textwert – Der Befehl wird nur ausgeführt, wenn in diesem Feld ein Text enthalten ist. Entfällt bei der Anmeldung per "hash".
cookiename Cookiename usess Name des Session-Cookies [a-z0-9].
csrf true / false true true: Der Request muss ein gültiges Token aus CMS:USER_TOKEN enthalten (Schutz gegen CSRF). false schaltet die Prüfung für diesen Befehl ab – nur mit gutem Grund.
codetemplate – – Template für den zweiten Schritt der Anmeldung, wenn der Profil-Bereich einen zweiten Faktor verlangt. Pflicht, sobald der Bereich einen einsetzt.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Prüft Anmeldename und Passwort gegen den Profil-Bereich und legt eine Session an (Cookie). Die Felder sind die im Bereich als Anmeldename und Passwort festgelegten (data[nickname], data[password]); nach Erfolg wird successtemplate eingebunden, darin ist das Profil der laufende Datensatz und $set['user_id'] / $set['user'] sind gefüllt:

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_LOGIN container=members submit="$submit" successtemplate=u_welcome)
<h1>Anmeldung</h1>
<p class="error">(CMS:MESSAGE)</p>
<form method="post">
  (CMS:USER_TOKEN)
  <input name="data[nickname]" value="(CMS:TEXT useval="{$_POST['data']['nickname']}" specialchars=true)">
  <input type="password" name="data[password]">
  <button name="submit" value="1">Anmelden</button>
</form>

Anmeldung über den Link aus CMS:USER_PW (Passwort vergessen):

(CMS:USER_LOGIN container=members hash="{$_GET['hash']}" successtemplate=u_newpw)

Der Befehl gehört an den Anfang des Templates (Cookie vor der Ausgabe). Fehlversuche werden gezählt (Brute-Force-Schutz). Meldungs-Indizes: failed, blocked, disabled (Status 1), unconfirmed (Status 2), hash, container, token. Gezählt wird je Anmeldename und zusätzlich je Herkunft (IP-Adresse): wer von einem Anschluss aus viele Konten mit je einem Passwort durchprobiert, wird ebenfalls gesperrt (blocked).

Zweiter Faktor

Im Profil-Bereich legt der Administrator fest, ob eine Anmeldung in zwei Schritten nötig ist (Einstellung „Zweiter Faktor“: aus, freiwillig, Code bei neuem Gerät, immer). Braucht eine Anmeldung einen Code, meldet USER_LOGIN nach dem richtigen Passwort noch niemanden an, sondern bindet codetemplate ein. Dort stehen:

$set[…] Inhalt
second_factor totp (Code aus der Authenticator-App) oder email (Code per E-Mail)
login_pending 1
pending_user das Profil (auch der laufende Datensatz) – etwa für die E-Mail-Adresse
login_code beim Code per E-Mail: der sechsstellige Code, den Sie verschicken

Das codetemplate verschickt den Code mit CMS:EMAIL und zeigt das Formular für CMS:USER_LOGIN_CODE (Beispiel dort). Wie alle CMS:USER_*-Befehle verschickt USER_LOGIN selbst keine E-Mail – das tut immer das Template. Auch die Anmeldung über den Link aus „Passwort vergessen“ (hash) verlangt den Code, wenn der Frontend-User eine Authenticator-App eingerichtet hat; die Einrichtung selbst übernimmt CMS:USER_2FA.

(CMS:USER_LOGIN container=members submit="$submit" successtemplate=u_welcome codetemplate=u_code_mail)

Anmeldung von einem neuen Gerät

camindo merkt sich die Geräte (Browser), mit denen sich ein Profil anmeldet. Kommt die Anmeldung von einem unbekannten Gerät und hat das Profil schon andere, steht im successtemplate $set['new_device'] auf 1; $set['device'] enthält label (z. B. „Firefox / macOS“), ip und time (UTC). Den Hinweis an den Inhaber schicken Sie selbst – vor der Weiterleitung, mit CMS:EMAIL:

<?php
$to = (string)($set['user']['email'] ?? '');
$send = (!empty($set['new_device']) && $to !== '') ? '1' : '';
$body = "Neue Anmeldung: {$set['device']['label']}, IP {$set['device']['ip']}, {$set['device']['time']} UTC."
      . " Waren Sie das nicht, setzen Sie bitte ein neues Passwort.";
?>
(CMS:EMAIL submit="$send" to="$to" subject="Neue Anmeldung" body="$body")

Die allererste Anmeldung eines Profils löst keinen Hinweis aus. Ob stattdessen eine SMS geht, legt der Systemadministrator fest (siehe Handbuch für Administratoren, Betrieb).

Parameter im Einzelnen

successtemplate

(CMS:USER_LOGIN container=members submit="$submit" successtemplate=u_welcome)

In u_welcome: Hallo (CMS:TEXT nickname) - das Profil ist dort der laufende Datensatz.

container

Profil-Bereich (Name oder Id):

(CMS:USER_LOGIN container=members …)

data

Name des Request-Arrays mit den Anmeldedaten (Standard data):

<input name="login[nickname]"> <input type="password" name="login[password]">
(CMS:USER_LOGIN container=members data=login submit="$submit" successtemplate=u_welcome)

message

(CMS:USER_LOGIN container=members submit="$submit" successtemplate=u_welcome
  message[failed]="Name oder Passwort falsch."
  message[unconfirmed]="Bitte bestätigen Sie zuerst Ihre E-Mail-Adresse."
  message[blocked]="Zu viele Fehlversuche – bitte später erneut versuchen."
)

hash

Anmeldung per Einmal-Token aus CMS:USER_PW statt mit Passwort; submit entfällt:

(CMS:USER_LOGIN container=members hash="{$_GET['hash']}" successtemplate=u_newpw)

In u_newpw setzt der Frontend-User per CMS:USER_INPUT container=members id="{$set['user_id']}" fields=password ein neues Passwort. Nach dieser Anmeldung ist $set['password_reset'] 1: eine Stunde lang (Einstellung user.password_token_expiration) braucht er dafür das bisherige Passwort nicht – siehe CMS:USER_INPUT, Passwort ändern.

codetemplate

Template für den zweiten Schritt (siehe oben):

(CMS:USER_LOGIN container=members submit="$submit" successtemplate=u_welcome codetemplate=u_code_mail)
(CMS:USER_LOGIN container=members hash="{$_GET['hash']}" successtemplate=u_newpw codetemplate=u_code_mail)

Fehlt es, obwohl der Bereich einen zweiten Faktor verlangt, scheitert die Anmeldung mit failed und einem Eintrag im Protokoll user.

trim

Leerzeichen um Name und Passwort entfernen (Kopierfehler auf Mobilgeräten):

(CMS:USER_LOGIN container=members trim=true submit="$submit" successtemplate=u_welcome)

Kein Cookie setzen - die Session-Id steht dann nur in $set['user_session_id'] (für Apps, die sie selbst weiterreichen):

(CMS:USER_LOGIN container=members cookie=false submit="$submit" successtemplate=api_token)

submit

Nur ausführen, wenn das Formular abgeschickt wurde:

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_LOGIN container=members submit="$submit" successtemplate=u_welcome)

cookiename

(CMS:USER_LOGIN container=members cookiename=shop submit="$submit" successtemplate=u_welcome)

Muss bei allen USER_*-Befehlen gleich sein.


← Übersicht

CMS:USER_LOGIN_CODE

Zweiter Schritt der Anmeldung: prüft den Code aus der Authenticator-App, per E-Mail oder einen Wiederherstellungscode und meldet den Frontend-User an.

(CMS:USER_LOGIN_CODE [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsLoginCode()
PHP-Schnipsel: include_and_stop (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
successtemplate – – ja Detailtemplate, das nach der erfolgreichen Anmeldung eingebunden wird.
submit Textwert – Der Code wird nur geprüft, wenn in diesem Feld ein Text enthalten ist.
resend true / false false true: neuen Code per E-Mail erzeugen (gedrosselt) und codetemplate einbinden.
codetemplate – – Template, das nach resend den neuen Code mit CMS:EMAIL verschickt – dasselbe wie bei CMS:USER_LOGIN.
data PHP-Variablenname data Name des PHP-Arrays mit den Eingaben.
codefield Feldname code Eingabefeld für den Code in data[…].
message[…] Standardmeldung – Fehlermeldungen. Im Index: Anwendungsfall; im Wert: Meldungstext.
sessionid Session-Id – Wartende Anmeldung ohne Cookie (Apps) – $set['user_session_id'] nach CMS:USER_LOGIN.
cookiename Cookiename usess Name des Session-Cookies [a-z0-9].
csrf true / false true true: Der Request muss ein gültiges Token aus CMS:USER_TOKEN enthalten.

Anwendung

Verlangt der Profil-Bereich einen zweiten Faktor, bindet CMS:USER_LOGIN nach dem richtigen Passwort das codetemplate ein. Die Anmeldung wartet dann in einer Session ohne Frontend-User; USER_LOGIN_CODE schließt sie ab.

Verteilen Sie die Arbeit auf zwei Templates:

  • u_code_mail verschickt den Code ($set['login_code']) mit CMS:EMAIL und bindet dann das Formular ein. Es ist das codetemplate von CMS:USER_LOGIN und von CMS:USER_LOGIN_CODE (für „Neuen Code senden“).
  • u_code enthält CMS:USER_LOGIN_CODE und das Formular; es ist das Ziel des Formulars.

Ohne submit und resend füllt der Befehl nur $set, und das Template läuft weiter. Innerhalb eines Aufrufs wertet er submit/resend nur beim ersten Mal aus – wenn u_code_mail nach dem Nachsenden wieder u_code einbindet, passiert nichts doppelt. Geben Sie den Schaltflächen eigene Namen: das Anmeldeformular hat schon submit=1 mitgeschickt.

u_code_mail:

<?php
$to = (string)($set['pending_user']['email'] ?? '');
$send = (!empty($set['login_code']) && $to !== '') ? '1' : '';
$body = "Ihr Anmeldecode: {$set['login_code']} (gültig 10 Minuten)";
?>
(CMS:EMAIL submit="$send" to="$to" subject="Ihr Anmeldecode" body="$body")
(CMS:INCLUDE u_code)

u_code:

<?php
$isPost = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
$submit = ($isPost && !empty($_POST['code_submit'])) ? '1' : '';
$resend = ($isPost && !empty($_POST['code_resend'])) ? 'true' : 'false';
?>
(CMS:USER_LOGIN_CODE submit="$submit" resend="$resend" successtemplate=u_welcome codetemplate=u_code_mail)
<h1>Anmeldung bestätigen</h1>
<p class="error">(CMS:MESSAGE)</p>
<?php if (!empty($set['code_resent'])) { ?><p>Ein neuer Code ist unterwegs.</p><?php } ?>
<?php if (!empty($set['login_pending'])) { ?>
<form method="post" action="/0,u_code">
  (CMS:USER_TOKEN)
  <input name="data[code]" autocomplete="one-time-code" required>
  <button name="code_submit" value="1">Bestätigen</button>
</form>
<?php if ($set['second_factor'] === 'email') { ?>
<form method="post" action="/0,u_code">
  (CMS:USER_TOKEN)
  <button name="code_resend" value="1">Neuen Code senden</button>
</form>
<?php } ?>
<?php } else { ?>
<p><a href="/0,u_login">Zur Anmeldung</a></p>
<?php } ?>

Für einen Code per SMS schreiben Sie in u_code_mail statt CMS:EMAIL den Aufruf Ihres SMS-Dienstes – der Core bleibt davon unberührt.

Der Befehl gehört an den Anfang des Templates (Cookie vor der Ausgabe).

$set[…] Inhalt
login_pending 1, solange eine Anmeldung auf den Code wartet
second_factor totp oder email
pending_user das Profil der wartenden Anmeldung
login_code nach resend: der neue Code (im codetemplate)
code_resent 1 nach resend
nach Erfolg wie USER_LOGIN (user_id, user, new_device …), dazu second_factor_used (totp, email, recovery) und recovery_remaining

Sicherheit: Ein Code per E-Mail gilt 10 Minuten (user.login_code_expiration). Jeder falsche Code zählt wie ein falsches Passwort; nach drei falschen Codes verfällt die wartende Anmeldung (Meldung expired) und der Frontend-User beginnt wieder mit dem Passwort. Ein Wiederherstellungscode gilt nur einmal. „Neuen Code senden“ ist gedrosselt (drei je 10 Minuten).

Meldungs-Indizes: code (falsch), expired (abgelaufen oder zu viele Fehlversuche), blocked, throttled (zu oft neuer Code), token.

Parameter im Einzelnen

successtemplate

Wie bei USER_LOGIN – das Profil ist dort der laufende Datensatz:

(CMS:USER_LOGIN_CODE submit="$submit" successtemplate=u_welcome)

submit

<?php $submit = !empty($_POST['code_submit']) ? '1' : ''; ?>
(CMS:USER_LOGIN_CODE submit="$submit" successtemplate=u_welcome)

resend, codetemplate

<?php $resend = !empty($_POST['code_resend']) ? 'true' : 'false'; ?>
(CMS:USER_LOGIN_CODE resend="$resend" submit="$submit" successtemplate=u_welcome codetemplate=u_code_mail)

data, codefield

<input name="login[otp]">
(CMS:USER_LOGIN_CODE data=login codefield=otp submit="$submit" successtemplate=u_welcome)

message

(CMS:USER_LOGIN_CODE submit="$submit" successtemplate=u_welcome
  message[code]="Der Code stimmt nicht."
  message[expired]="Bitte melden Sie sich erneut an."
)

sessionid, cookiename

Wie bei CMS:USER_AUTH.


← Übersicht

CMS:USER_LOGOUT

Beendet und löscht die Session eines angemeldeten Frontend-Users.

(CMS:USER_LOGOUT [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsLogout()

Parameter

Parameter Wert Standard Pflicht Beschreibung
submit Textwert – Ist der Parameter angegeben, meldet der Befehl nur ab, wenn er einen Text enthält (z. B. submit="$logout" aus dem Abmelde-Formular) – leer passiert nichts, auch keine Meldung token. Ohne den Parameter meldet der Befehl bei jedem Aufruf ab (gültiges Token vorausgesetzt).
deletecookie true / false true false: Nur die serverseitige Session wird gelöscht. true: Auch der Browser-Cookie wird gelöscht.
cookiename Cookiename usess Name des Session-Cookies [a-z0-9].
csrf true / false true true: Der Request muss ein gültiges Token aus CMS:USER_TOKEN enthalten (Schutz gegen CSRF). false schaltet die Prüfung für diesen Befehl ab – nur mit gutem Grund.

Anwendung

Beendet die Session des angemeldeten Users (auch den Warenkorb der Session) und löscht das Cookie. Danach ist $set['user_id'] = 0:

(CMS:USER_LOGOUT)
(CMS:INCLUDE header)
<p>Sie wurden abgemeldet. <a href="/0,u_login">Erneut anmelden</a></p>

Die Abmeldung wird per POST-Formular mit Token ausgelöst – ein einfacher Link genügt nicht, sonst könnte jede fremde Seite Ihre Besucher abmelden. Ohne gültiges Token meldet der Befehl nicht ab und legt die Meldung token ab (CMS:MESSAGE):

<form method="post" action="/0,u_logout">
  (CMS:USER_TOKEN)
  <button name="logout" value="1">Abmelden</button>
</form>

Damit der direkte Aufruf der Abmeldeseite keine Meldung token erzeugt, geben Sie submit an – leer (die Seite wurde nur aufgerufen) tut der Befehl dann nichts:

<?php $logout = $_POST['logout'] ?? ''; ?>
(CMS:USER_LOGOUT submit="$logout")

Auch dieser Befehl gehört vor die erste Ausgabe, damit das Cookie gelöscht werden kann.

Parameter im Einzelnen

submit

Nur abmelden, wenn das Abmelde-Formular abgeschickt wurde:

<?php $logout = $_POST['logout'] ?? ''; ?>
(CMS:USER_LOGOUT submit="$logout")

deletecookie

Nur die serverseitige Session löschen, das Cookie stehen lassen:

(CMS:USER_LOGOUT deletecookie=false)

cookiename

(CMS:USER_LOGOUT cookiename=shop)

← Übersicht

CMS:USER_PASSWORD_RULES

Gibt die Regeln der Passwort-Richtlinie eines Profil-Containers samt Texten aus – für die Anzeige im Formular (Stärke-Balken und Sprechblase mit den Anforderungen).

(CMS:USER_PASSWORD_RULES container=… [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsPasswordRules()

Parameter

Parameter Wert Standard Pflicht Beschreibung
container Container-ID oder -Name – ja Profil-Container, dessen Passwort-Feld gemeint ist.
field Feldname Anmelde-Passwortfeld Passwort-Feld des Containers. Trägt es die Extension Passwort-Richtlinie, gelten deren Regeln, sonst die Vorgaben des Administrators (der Server prüft dann nicht).
set Array-Index von $set – Die Ausgabe wird nicht ausgegeben, sondern in $set gespeichert.

Die Ausgabe ist bereits mit htmlspecialchars für ein HTML-Attribut maskiert – nicht zusätzlich maskieren.

Anwendung

Beim Registrieren und beim Ändern des Passworts prüft der Server die Passwort- Richtlinie (Mindestlänge, Sonderzeichen, Groß- und Kleinbuchstaben), wenn der Administrator dem Passwort-Feld die Extension „Passwort-Richtlinie“ zugewiesen hat. Damit Ihre Besucher die Anforderungen schon beim Tippen sehen, gibt es dazu ein kleines Script – dasselbe, das im Backend läuft. Es zeigt unter dem Feld einen Balken (rot = schwach, orange = mittel, grün = stark) und eine Sprechblase, in der erfüllte Anforderungen abgehakt werden.

1. Script und CSS einbinden. Kopieren Sie die beiden Dateien der Extension unverändert in Ihr Template – als <style> und <script(CMS:NONCE)> (die Nonce braucht das Script unter einer Script-Policy, siehe CMS:NONCE), am besten in ein eigenes Include, das Sie am Ende der Seite einbinden:

Core/Extensions/Password/public/css/password.ext.css   →  <style> … </style>
Core/Extensions/Password/public/js/password.ext.js     →  <script(CMS:NONCE)> … </script>

Die Dateien brauchen nichts vom Backend und keine Bibliothek. Nach einem Update von camindo CMS kopieren Sie sie neu, falls sich die Regeln geändert haben.

2. Das Feld markieren. Das Attribut data-c9-password bekommt die Regeln aus diesem Befehl – die Texte stehen darin in der Sprache der Seite:

<input type="password" name="data[password]" autocomplete="new-password"
  data-c9-password="(CMS:USER_PASSWORD_RULES container=members)">
(CMS:INCLUDE password_assets)

Das Script stattet beim Laden der Seite jedes Feld mit data-c9-password aus. Für Felder, die erst später per Javascript entstehen: C9Password.attach(feld, optionen).

Wichtig: Die Anzeige ist eine Hilfe, keine Prüfung. Maßgeblich ist der Server – ohne die Extension am Feld nimmt er jedes Passwort an, auch wenn das Formular die Regeln zeigt. Die Fehlermeldung bei zu schwachem Passwort erscheint wie andere Feldfehler über CMS:MESSAGE.

Ein vollständiges Formular zum Ändern des Passworts steht bei CMS:USER_INPUT.

Parameter im Einzelnen

container

Name oder ID des Profil-Containers:

(CMS:USER_PASSWORD_RULES container=members)

field

Ein anderes Passwort-Feld als das zur Anmeldung festgelegte:

(CMS:USER_PASSWORD_RULES container=members field=pin)

set

Die Ausgabe nicht ausgeben, sondern in $set['pwrules'] ablegen (bereits für ein HTML-Attribut maskiert):

(CMS:USER_PASSWORD_RULES container=members set=pwrules)

← Übersicht

CMS:USER_REGISTER

Erzeugt einen Profil-Datensatz aus den angegebenen Daten und setzt den Status auf status=2.

(CMS:USER_REGISTER [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsRegister()
PHP-Schnipsel: include_and_stop (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
successtemplate – – ja Detailtemplate, das nach erfolgreicher Registrierung eingebunden wird.
container Container-ID oder -Name – ja ID oder Name des Profil-Containers, in dem die Daten der Frontend-User gespeichert werden sollen.
emailfield Feldname – ja Feldname, in dem die E-Mail des Frontend-Users gespeichert ist (dorthin wird der Freischalt-Link gesendet).
fields kommaseparierte Liste – Namen der Eingabefelder, die geschrieben werden dürfen. Leer = alle definierten Felder des Containers.
overwrite[…] Schlüssel-Wert-Paare – Explizites Überschreiben der Eingaben des Frontend-Users (auch Metafelder, außer record_id, area_id, instance_id, tmp, deleted, origin_id, owner_id, created_by, changed_by). Hier keine Eingaben des Frontend-Users weiterreichen!
message[…] Meldung – Fehlermeldung. Im Index: Anwendungsfall; im Wert: Meldungstext (vgl. Tabelle bei CMS:USER_INPUT).
submit Textwert – ja Der Befehl wird nur ausgeführt, wenn in diesem Feld ein Text enthalten ist.
owner Benutzer-ID – admin_id des Backend-Owners des neuen Profils (record.owner_id, created_by). Ohne Angabe gilt user.default_owner_id.
data PHP-Variablenname data Name des PHP-Arrays, das die zu speichernden Daten enthält.
csrf true / false true true: Der Request muss ein gültiges Token aus CMS:USER_TOKEN enthalten (Schutz gegen CSRF). false schaltet die Prüfung für diesen Befehl ab – nur mit gutem Grund.
formguard true / false / Sekunden false true: Formularschutz gegen Robots – das Formular muss die Felder aus (CMS:USER_TOKEN formguard=true) enthalten und frühestens nach 3 Sekunden (user.formguard_min_seconds) abgeschickt werden. Eine Zahl setzt die Mindestzeit für dieses Formular (formguard=5), 0 schaltet ab. Sonst Meldung throttled.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Legt ein Profil an (Status 2 = unbestätigt) und erzeugt einen Freischalt-Token in $set['hash']; das successtemplate verschickt den Bestätigungslink, dessen Zielseite CMS:USER_REGISTER_CONFIRM aufruft. Anmeldename und E-Mail müssen im Bereich noch frei sein, emailfield muss eine gültige Adresse enthalten. Anmeldenamen, die mit user_ beginnen, sind reserviert – so heißen anonymisierte Profile (user_{record_id}, siehe Lösch-Policies); die Registrierung lehnt sie ab:

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_REGISTER container=members emailfield=kontakt_email fields="nickname,kontakt_email,password"
  submit="$submit" successtemplate=u_registered)
<h1>Registrieren</h1>
<p class="error">(CMS:MESSAGE)</p>
<form method="post">
  (CMS:USER_TOKEN)
  <input name="data[nickname]"> <input name="data[kontakt_email]" type="email">
  <input name="data[password]" type="password">
  <button name="submit" value="1">Konto anlegen</button>
</form>

u_registered verschickt den Link mit CMS:EMAIL. Die Adresse steht dort in $set['email'] – immer unter diesem Namen, gleich wie das Feld aus emailfield heißt (hier kontakt_email). Unter dem Feldnamen findet sie sich nur im Profil, $set['user']['kontakt_email']:

(CMS:URL id=0 template=u_confirm absolute=true set=link)
(CMS:EMAIL submit="1" to="{$set['email']}" subject="Bitte bestätigen"
  body="Konto freischalten: {$set['link']}?hash={$set['hash']}")
(CMS:MESSAGE)
<p>Bitte bestätigen Sie Ihre E-Mail-Adresse.</p>

Meldungs-Indizes: container, email, exists, save, throttled, owner (kein gültiger Owner, siehe owner), token sowie Feldfehler des Bereichs.

Drosselung und Formularschutz

Jede Registrierung schickt eine E-Mail an die eingegebene Adresse. camindo begrenzt das (Drosselung): fünf Registrierungen je Herkunft (IP-Adresse) und Stunde, drei je E-Mail-Adresse und Tag, 200 je Website und Stunde. Gezählt werden Registrierungen und Versuche mit einem schon vergebenen Namen – ein Tippfehler bei der Passwort-Richtlinie zählt nicht. Meldung dann: throttled, $set['throttled'] = 1. Wie bei CMS:USER_PW schützt formguard=true (mit (CMS:USER_TOKEN formguard=true) im Formular) vor Robots.

Die Meldung exists verrät, dass es einen Namen oder eine Adresse schon gibt; die Drosselung begrenzt, wie oft sich das ausprobieren lässt.

Nie bestätigte Registrierungen löscht camindo nach sieben Tagen (user.unconfirmed_days): wer die Adresse eines anderen einträgt, blockiert sie so nur kurz.

Parameter im Einzelnen

successtemplate

Nach dem Anlegen eingebunden. Darin stehen:

$set[…] Inhalt
hash Freischalt-Token für den Bestätigungslink
email die geprüfte E-Mail-Adresse – unabhängig vom Namen des Feldes aus emailfield
user_id record_id des neuen Profils
user das Profil als Array (Feldnamen als Schlüssel); es ist auch der laufende Datensatz
(CMS:USER_REGISTER container=members emailfield=email submit="$submit" successtemplate=u_registered)

container

(CMS:USER_REGISTER container=members …)

emailfield

Feld mit der E-Mail-Adresse - wird geprüft und in $set['email'] abgelegt (unter diesem festen Namen, nicht unter dem Feldnamen):

(CMS:USER_REGISTER container=members emailfield=kontakt_email …)

fields

Nur diese Felder aus dem Formular übernehmen:

(CMS:USER_REGISTER container=members emailfield=email fields="nickname,email,password" …)

overwrite

Feste Werte, auch Metafelder - keine Usereingaben. Ausgenommen sind record_id, area_id, instance_id, tmp, deleted, origin_id, owner_id, created_by und changed_by; der Status (status_id 2) lässt sich hier überschreiben – dann entfällt die Bestätigung per Link:

(CMS:USER_REGISTER container=members emailfield=email overwrite[role]=member overwrite[source]=web …)

message

(CMS:USER_REGISTER container=members emailfield=email submit="$submit" successtemplate=u_registered
  message[exists]="Name oder E-Mail sind bereits vergeben."
  message[email]="Bitte eine gültige E-Mail-Adresse angeben."
)

submit

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_REGISTER … submit="$submit")

data

<input name="reg[nickname]"> …
(CMS:USER_REGISTER container=members emailfield=email data=reg submit="$submit" successtemplate=u_registered)

← Übersicht

CMS:USER_REGISTER_CONFIRM

Schaltet einen Profil-Datensatz frei und setzt status=0.

(CMS:USER_REGISTER_CONFIRM [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsRegisterConfirm()
PHP-Schnipsel: include_and_stop (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
successtemplate – – ja Detailtemplate, das eingebunden wird, wenn die Registrierung erfolgreich abgeschlossen werden konnte.
hash Hash-ID – ja Hash-ID, die mittels CMS:USER_REGISTER erzeugt und per E-Mail versendet wurde.
message[…] Meldung – Fehlermeldung. Im Index: Anwendungsfall; im Wert: Meldungstext (vgl. Tabelle).

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Löst den Token aus CMS:USER_REGISTER ein und schaltet das Profil frei (Status 0). Steht auf der Seite, auf die der Bestätigungslink zeigt:

(CMS:USER_REGISTER_CONFIRM hash="{$_GET['hash']}" successtemplate=u_confirmed
  message[hash]="Der Link ist ungültig oder abgelaufen.")
(CMS:INCLUDE header)
<p class="error">(CMS:MESSAGE)</p>

u_confirmed:

<p>Ihr Konto ist freigeschaltet – <a href="/0,u_login">jetzt anmelden</a>.</p>

Tokens verfallen nach 24 Stunden (user.token_expiration). Nach Erfolg sind $set['user_id'] und $set['user'] gefüllt; angemeldet ist der Frontend-User damit noch nicht.

Parameter im Einzelnen

successtemplate

(CMS:USER_REGISTER_CONFIRM hash="{$_GET['hash']}" successtemplate=u_confirmed)

hash

Token aus dem Link. Ist er leer, unbekannt, abgelaufen oder schon eingelöst, erscheint die Meldung hash – der Befehl kennt kein submit:

(CMS:USER_REGISTER_CONFIRM hash="{$_GET['hash']}" successtemplate=u_confirmed)

message

(CMS:USER_REGISTER_CONFIRM hash="{$_GET['hash']}" successtemplate=u_confirmed message[hash]="Dieser Link ist nicht mehr gültig.")

← Übersicht

CMS:USER_PROTECT

Schützt ein Template vor dem Zugriff nicht angemeldeter Website-Besucher.

(CMS:USER_PROTECT [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsProtect()
PHP-Schnipsel: include_and_stop (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
errortemplate Templatename – ja Detailtemplate, das eingebunden wird, falls der Frontend-User keinen Zugriff auf den Container hat oder nicht angemeldet ist.
filter[…][…] vgl. Parameter "filter[]" von CMS:SELECT – Filterkriterien für den Profil-Datensatz, die beim angemeldeten Frontend-User erfüllt sein müssen.
sessionid Session-ID – Session des Frontend-Users anhand der angegebenen sessionid wieder aufnehmen anstatt über ein Cookie.
cookiename Cookiename usess Name des Session-Cookies [a-z0-9].

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Sperrt ein Template für nicht angemeldete Besucher: Ist niemand angemeldet (oder trifft filter nicht zu), wird errortemplate eingebunden und die Seite beendet. Sonst läuft das Template weiter, $set['user_id'] und $set['user'] sind gefüllt:

(CMS:USER_PROTECT errortemplate=u_login)
(CMS:INCLUDE header)
<h1>Mein Konto</h1>
<p>Angemeldet als <?php echo htmlspecialchars($set['user']['nickname']); ?></p>

Nur für eine Gruppe von Frontend-Usern (Profil-Feld role):

(CMS:USER_PROTECT errortemplate=u_denied filter[role]=editor)

Das errortemplate ist typischerweise das Anmeldeformular selbst - so landet der Besucher nach der Anmeldung wieder auf der geschützten Seite.

Parameter im Einzelnen

errortemplate

Template für nicht angemeldete Besucher (oder wenn filter nicht zutrifft):

(CMS:USER_PROTECT errortemplate=u_login)
(CMS:USER_PROTECT errortemplate=templates.u_denied filter[role]=editor)

filter

Zusätzliche Bedingung an das Profil, Syntax wie CMS:SELECT:

(CMS:USER_PROTECT errortemplate=u_denied filter[role]=editor)
(CMS:USER_PROTECT errortemplate=u_denied filter[paid_until] >= "{$today}")

sessionid

(CMS:USER_PROTECT errortemplate=api_denied sessionid="{$_GET['sid']}")

cookiename

(CMS:USER_PROTECT errortemplate=u_login cookiename=shop)

← Übersicht

CMS:USER_PW

Bereitet das Zurücksetzen eines Passworts vor.

(CMS:USER_PW [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsPw()
PHP-Schnipsel: include_and_stop (danach)

Parameter

Parameter Wert Standard Pflicht Beschreibung
successtemplate – – ja Detailtemplate, das eingebunden wird, falls "uniquevalue" im Feld "uniquefield" einem Profil zugeordnet werden konnte.
errortemplate – – ja Detailtemplate, das eingebunden wird, falls "uniquevalue" angegeben wurde, aber keinem Profil zugeordnet werden konnte.
container Container-ID oder -Name – ja ID oder Name des Profil-Containers, in dem die abzugleichenden Profile gespeichert sind.
uniquefield Feldname – ja Name des Feldes, über das gesucht wird (Feldtyp chars, text oder encrypted, z. B. "email"). Der Wert muss genau ein Profil treffen.
uniquevalue Text – Gesuchter Wert im Feld "uniquefield", z. B. $_POST[email].
submit Textwert – ja Der Befehl wird nur ausgeführt, wenn in diesem Feld ein Text enthalten ist.
message[…] Text – Fehlermeldung. Im Index: Anwendungsfall; im Wert: Meldungstext (vgl. Tabelle).
csrf true / false true true: Der Request muss ein gültiges Token aus CMS:USER_TOKEN enthalten (Schutz gegen CSRF). false schaltet die Prüfung für diesen Befehl ab – nur mit gutem Grund.
formguard true / false / Sekunden false true: Formularschutz gegen Robots – das Formular muss die Felder aus (CMS:USER_TOKEN formguard=true) enthalten und frühestens nach 3 Sekunden (user.formguard_min_seconds) abgeschickt werden. Eine Zahl setzt die Mindestzeit für dieses Formular (formguard=5), 0 schaltet ab. Sonst Meldung throttled.

[…] – der Parameter nimmt Unterschlüssel an, z. B. name[schlüssel]=wert.

Anwendung

Erster Schritt „Passwort vergessen“: Sucht das Profil über ein eindeutiges Feld (meist die E-Mail-Adresse) und erzeugt einen Einmal-Token in $set['hash']. Das successtemplate verschickt den Link; die Zielseite meldet den Frontend-User mit (CMS:USER_LOGIN hash=…) an, danach setzt er per CMS:USER_INPUT ein neues Passwort:

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_PW container=members uniquefield=email uniquevalue="{$_POST['data']['email']}"
  submit="$submit" successtemplate=u_pwsent errortemplate=u_pwfail)
<h1>Passwort vergessen</h1>
<form method="post">
  (CMS:USER_TOKEN)
  <input name="data[email]" type="email">
  <button name="submit" value="1">Link anfordern</button>
</form>

u_pwsent verschickt den Link mit CMS:EMAIL:

<?php $link = 'https://www.example.com/0,u_hashlogin?hash=' . rawurlencode($set['hash']); ?>
(CMS:EMAIL submit="1" to="{$set['user']['email']}" subject="Neues Passwort"
  body="Link (eine Stunde gültig): $link")
(CMS:MESSAGE)
<p>Wir haben Ihnen einen Link geschickt.</p>

Meldungs-Indizes: notfound, container, throttled, token.

Drosselung und Formularschutz

Jede Anfrage verschickt eine E-Mail an eine Adresse, die jemand eingetippt hat. Damit niemand über Ihr Formular fremde Postfächer flutet, zählt camindo die Anfragen (Drosselung): höchstens drei je eingegebener Adresse und Stunde, zehn je Herkunft (IP-Adresse) und 200 je Website (Systemadministrator: user.throttle). Gezählt wird der eingegebene Wert – ob es das Konto gibt, spielt keine Rolle. Ist eine Grenze erreicht, entsteht kein Link, und der Befehl bindet das errortemplate ein – mit $set['throttled'] = 1 und der Meldung throttled. Zeigen Sie dort denselben Text wie im Erfolgsfall, verrät die Seite weder, ob es das Konto gibt, noch, dass gedrosselt wurde.

Gegen Robots, die jedes Formular blind füllen, hilft zusätzlich der Formularschutz: formguard=true am Befehl und (CMS:USER_TOKEN formguard=true) im Formular. Wer das Formular schneller als in drei Sekunden abschickt oder das unsichtbare Köderfeld füllt, bekommt dieselbe Antwort wie bei der Drosselung:

(CMS:USER_PW container=members formguard=true uniquefield=email uniquevalue="{$_POST['data']['email']}"
  submit="$submit" successtemplate=u_pwsent errortemplate=u_pwfail)
…
<form method="post">
  (CMS:USER_TOKEN formguard=true)
  …
</form>

Der Link gilt eine Stunde und nur einmal (Einstellung user.password_token_expiration). Er funktioniert auf jedem Gerät – wer sein Passwort vergessen hat, sitzt oft gerade an einem neuen. Kommt die Anmeldung über den Link von einem unbekannten Gerät, meldet CMS:USER_LOGIN das wie jede andere Anmeldung.

Zeigen Sie im errortemplate denselben Text wie im successtemplate („Falls zu dieser Adresse ein Konto existiert, haben wir einen Link geschickt."). Sonst kann jeder ausprobieren, welche E-Mail-Adressen bei Ihnen ein Konto haben.

Parameter im Einzelnen

successtemplate

Eingebunden, wenn ein Profil gefunden wurde; darin $set['hash'], $set['user'], und das Profil ist der laufende Datensatz:

(CMS:USER_PW container=members uniquefield=email uniquevalue="$email" submit="$submit" successtemplate=u_pwsent errortemplate=u_pwfail)

errortemplate

Eingebunden, wenn uniquevalue angegeben war, aber kein Profil passt:

(CMS:USER_PW … errortemplate=u_pwfail)

container

(CMS:USER_PW container=members …)

uniquefield

Feld, über das gesucht wird (Feldtyp chars, text oder encrypted). Verglichen wird wörtlich, ohne Groß-/Kleinschreibung; trifft der Wert mehr als ein Profil, gilt das wie „nicht gefunden“ – markieren Sie das Feld deshalb im Bereich als eindeutig:

(CMS:USER_PW container=members uniquefield=email …)
(CMS:USER_PW container=members uniquefield=nickname …)

uniquevalue

Gesuchter Wert, meist aus dem Formular. Leer = der Befehl tut nichts:

(CMS:USER_PW container=members uniquefield=email uniquevalue="{$_POST['data']['email']}" …)

submit

<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_PW … submit="$submit")

message

(CMS:USER_PW … message[notfound]="Zu dieser Adresse gibt es kein Konto.")

← Übersicht

CMS:USER_TOKEN

Gibt das Sicherheits-Token (CSRF-Token) für ein Formular aus, das einen schreibenden CMS:USER_*-Befehl auslöst.

(CMS:USER_TOKEN [parameter=wert …])

Methode: Modules\Record\RecordUser::cmsToken()

Parameter

Parameter Wert Standard Pflicht Beschreibung
format "field" / "value" "field" field: verstecktes Eingabefeld <input type="hidden" name="_token">, der Wert mit htmlspecialchars maskiert. value: nur das Token (für Ajax), unmaskiert.
set Array-Index von $set – Die Ausgabe wird nicht ausgegeben, sondern in $set gespeichert. Das Token steht zusätzlich immer in $set['user_token'].
formguard true / false false true: zusätzlich Zeitstempel und ein unsichtbares Köderfeld ausgeben (Formularschutz gegen Robots).

Anwendung

Das Token verhindert, dass eine fremde Website im Namen Ihrer Besucher Formulare abschickt (Cross-Site Request Forgery): Anmelden, Registrieren, Passwort-Reset, Beiträge speichern oder löschen, Abmelden, Warenkorb ändern. Diese Befehle prüfen das Token und brechen ohne gültiges Token mit einer Meldung (Index token) ab:

Befehl geprüft
USER_LOGIN, USER_REGISTER, USER_PW, USER_INPUT, USER_2FA wenn submit gefüllt ist (die Anmeldung per hash nicht)
USER_LOGIN_CODE wenn submit oder resend gesetzt ist
USER_DELETE immer
USER_LOGOUT immer – mit submit nur, wenn submit gefüllt ist
USER_BASKET bei add, update, update_addon – load nicht; ohne gültiges Token wird nur gelesen

Das Token hängt an einem Cookie. Der erste Aufruf auf einer Seite muss deshalb vor jeder Ausgabe stehen – am Anfang des Templates (bzw. im Kopf-Include vor <!doctype>), mit set, damit dort nichts ausgegeben wird. Danach schreiben Sie (CMS:USER_TOKEN) in jedes Formular, das einen dieser Befehle auslöst:

(CMS:USER_TOKEN set=token)
<?php $submit = $_POST['submit'] ?? ''; ?>
(CMS:USER_LOGIN container=members submit="$submit" successtemplate=u_welcome)
<!doctype html>
…
<form method="post">
  (CMS:USER_TOKEN)
  <input name="data[nickname]"> <input type="password" name="data[password]">
  <button name="submit" value="1">Anmelden</button>
</form>

Formularschutz gegen Robots

(CMS:USER_TOKEN formguard=true) gibt zusätzlich einen signierten Zeitstempel und ein Köderfeld aus, das Besucher nicht sehen. Befehle mit formguard=true (CMS:USER_REGISTER, CMS:USER_PW, CMS:EMAIL) weisen das Formular ab, wenn das Köderfeld gefüllt ist oder es schneller als nach drei Sekunden bzw. später als nach einer Stunde abgeschickt wurde. Beide Zeiten stellt der Systemadministrator ein (user.formguard_min_seconds, formguard_max_seconds); für ein einzelnes Formular setzt formguard=5 am Befehl die Mindestzeit auf fünf Sekunden – etwa für ein langes Formular, das niemand in drei Sekunden ausfüllt. Das hält Robots auf, die blind jedes Formular füllen – kein Captcha, kein fremder Dienst. Ein externes Captcha kann der Systemadministrator zusätzlich anbinden (security.form_challenge); dessen Widget binden Sie dann selbst ein.

<form method="post">
  (CMS:USER_TOKEN formguard=true)
  …
</form>

Für Ajax-Aufrufe schicken Sie das Token als POST-Feld _token oder im Header X-Camindo-Token mit. Ein FormData aus einem Formular mit (CMS:USER_TOKEN) enthält es bereits:

<script>
fetch('/0,basket_api', {
  method: 'POST',
  headers: { 'X-Camindo-Token': '(CMS:USER_TOKEN format=value)' },
  body: new URLSearchParams({ product: 42 })
});
</script>

Steht der erste Aufruf zu spät, gibt es kein Token: an Stelle des Feldes steht ein HTML-Kommentar mit Hinweis, und das Formular scheitert mit der Meldung token.

Das Cookie (ucsrf) gilt für alle Formulare der Website und überlebt die Anmeldung. Im Query-String (GET) wird es nicht angenommen – schreibende Aktionen gehören in POST-Formulare.

Die Prüfung lässt sich je Befehl mit csrf=false abschalten, der Systemadministrator kann sie mit user.csrf = false global abschalten. Beides öffnet die Befehle für fremde Websites und ist nur für Übergangszeiten gedacht.


← Übersicht

Stand: camindo CMS 9.1.3