Ein REDAXO-Addon zur Verwaltung von domain- und sprachspezifischen Template-Einstellungen über DocBlock-Konfiguration.
- 📝 DocBlock-basierte Konfiguration - Template-Settings direkt im Template-Code definieren
- 🌍 Multi-Domain Support - Unterschiedliche Einstellungen pro YRewrite-Domain
- 🌐 Mehrsprachigkeit - Separate Einstellungen für jede Sprache mit Fallback
- 🎨 20+ Feldtypen - text, textarea, cke5, number, email, tel, date, time, color, colorselect, media, medialist, select, checkbox, link, linklist u.v.m.
- 🔧 Native REDAXO Widgets - Volle Integration von Linkmap, Medienpicker und Bootstrap Selectpicker
- 🎨 Visuelle Farbauswahl - Colorselect mit farbigen Badges
- 🏢 Globale Variablen - Mandantenübergreifende Einstellungen (SSO, Firmeninfos, API-Keys)
- � Notice-Felder - Hinweise, Warnungen und Informationen direkt im Einstellungsformular
- 🔄 Domain-Einstellungen kopieren - Settings einer Domain auf andere Domains übertragen
- �🚀 Einfache Frontend-API - Statische Klassen-Methoden mit optionalen Domain/Sprach-Parametern
- 🔌 Erweiterbar - Extension Point System für eigene Feldtypen durch externe Addons
Ab Version 1.x können externe Addons eigene Feldtypen registrieren:
// In boot.php des externen Addons
rex_extension::register('TEMPLATE_MANAGER_FIELD_RENDERERS', function($ep) {
$renderers = $ep->getSubject();
$renderers[] = new \MeinAddon\TemplateManagerFieldRenderer();
return $renderers;
});Siehe EXTERNAL_FIELD_RENDERER_EXAMPLE.md für vollständige Beispiele.
- Addon im REDAXO-Backend installieren
- Optional: Demo-Template über Template Manager → Setup importieren
- Eigene Templates mit DOMAIN_SETTINGS erstellen
Das Addon enthält ein vorkonfiguriertes Demo-Template:
- Name: Modern Business (Demo)
- Features:
- Dark/Light Mode Support (automatisch basierend auf System-Einstellungen)
- 16+ konfigurierbare Einstellungen
- Modernes, responsives Design ohne Framework-Abhängigkeiten
- CSS Custom Properties für einfaches Theming
- Barrierefrei (WCAG 2.1 AA)
- Font Awesome 6 Integration
- Demo-Content wenn noch kein Content vorhanden
Enthaltene Feldtypen:
text- Firmenname, Slogancolorselect- Akzentfarbe (8 vordefinierte Brand-Farben)email- Kontakt E-Mailtel- Telefonnummernumber- Mitarbeiteranzahl, Gründungsjahrmedia- Logomedialist- Header-Bilderlink- Startseitelinklist- Footer-Linkscategory- Hauptkategoriecategorylist- Service-Kategoriencheckbox- Breadcrumbs anzeigen, Kontaktinfo im Headersocial_links- Social Media Links (Repeater mit Sortierung)opening_hours- Strukturierte Öffnungszeiten (Google-Style)
Import über: Template Manager → Setup → Demo-Template jetzt importieren
Ab Version 1.4.0 unterstützt der Template Manager Globale Variablen. Diese sind ideal für Einstellungen, die über alle Domains und Templates hinweg identisch sein sollen (z.B. Firmenname, Social Media Profile, API-Keys), aber dennoch sprachspezifisch verwaltet werden müssen.
Die Definition erfolgt unter Template Manager -> Globale Variablen im Tab Definition. Hier wird das gleiche DocBlock-Format verwendet wie in den Templates, jedoch mit dem Marker GLOBAL_SETTINGS:
/**
* GLOBAL_SETTINGS
*
* --- Unternehmen [fa-solid fa-building] ---
* tm_company_name: text|Firmenname|Mein Unternehmen|Offizieller Name
* tm_company_email: email|E-Mail|info@beispiel.de|Zentrale Kontaktadresse
*
* --- Social Media [fa-brands fa-share-nodes] ---
* tm_social_facebook: text|Facebook URL||Link zum Profil
* tm_social_instagram: text|Instagram URL||Link zum Profil
*/Sobald eine Definition gespeichert wurde, erscheint automatisch der Tab Werte. Dort können pro installierter Sprache die konkreten Inhalte gepflegt werden.
Der Zugriff erfolgt identisch zu den Template-Settings über die TemplateManager::get() Methode:
<?php
use FriendsOfRedaxo\TemplateManager\TemplateManager;
// Holt den Wert (Priorität: Template-Setting > Globales Setting > Default)
$companyName = TemplateManager::get('tm_company_name');
?>Kaskadierung (Priority):
- Template-Setting: Wenn im aktuellen Template ein Feld mit gleichem Namen definiert und befüllt ist.
- Globales Setting: Wenn im Template nichts definiert ist (oder kein Wert vorliegt), wird der globale Wert gezogen.
- Default-Wert: Der in der Definition angegebene Standardwert.
Füge einen PHP-DocBlock-Kommentar am Anfang deines Templates ein mit einem DOMAIN_SETTINGS Abschnitt:
<?php
/**
* Mein Template
*
* Beschreibung des Templates
*
* DOMAIN_SETTINGS
* tm_logo: media|Logo||Firmenlogo
* tm_company_name: text|Firmenname|Muster GmbH|Offizieller Firmenname
* tm_primary_color: colorselect|Akzentfarbe|#005d40:#005d40 Grün,#7D192C:#7D192C Rot,#1e87f0:#1e87f0 Blau|Hauptfarbe
* tm_contact_email: email|E-Mail|info@beispiel.de|Kontakt E-Mail-Adresse
* tm_contact_phone: tel|Telefon|+49 123 456789|Kontakt-Telefonnummer
* tm_footer_links: linklist|Footer-Links||Artikel-IDs für Footer-Navigation
* tm_header_images: medialist|Header-Bilder||Bilder für Header-Slideshow
* tm_show_breadcrumbs: checkbox|Breadcrumbs anzeigen||Breadcrumb-Navigation aktivieren
*/
?>
<!DOCTYPE html>
<html>
<!-- Template-Code hier -->
</html>Wichtig:
- Der DocBlock muss in
<?php ... ?>eingebettet sein, damit er nicht als HTML ausgegeben wird - Alle Feldnamen müssen mit
tm_beginnen (Template Manager Prefix) - Nur Templates mit
tm_Prefix in den Settings werden als konfigurierbar erkannt
Jede Setting-Zeile folgt diesem Format:
tm_feldname: typ|Label|DefaultWert|Beschreibung
Wichtig: Alle Feldnamen müssen mit tm_ beginnen (Template Manager Prefix)!
Ab Version 1.3.0 können Einstellungen in thematische Gruppen organisiert werden:
Syntax:
--- Gruppenname ---
Optional mit Font Awesome 6 Icon:
--- Gruppenname [fa-solid fa-icon] ---
Optional mit Rechteverwaltung (nur für bestimmte Benutzergruppen sichtbar):
--- Gruppenname [admin,developer] ---
Beides kombiniert:
--- Gruppenname [fa-solid fa-icon] [admin,developer] ---
Beispiel:
/**
* DOMAIN_SETTINGS
*
* --- Branding & Design [fa-solid fa-palette] ---
* tm_company_name: text|Firmenname|Muster GmbH|Offizieller Firmenname
* tm_logo: media|Logo||Firmenlogo (Header)
* tm_primary_color: colorselect|Akzentfarbe|#005d40|Hauptfarbe
*
* --- Kontaktinformationen [fa-solid fa-address-book] ---
* tm_contact_email: email|E-Mail|info@beispiel.de|Kontakt E-Mail
* tm_contact_phone: tel|Telefon|+49 123 456789|Telefonnummer
*
* --- Navigation & Links [fa-solid fa-bars] [admin,developer] ---
* tm_footer_links: linklist|Footer-Links||Artikel-IDs für Footer
* tm_show_breadcrumbs: checkbox|Breadcrumbs anzeigen||Navigation aktivieren
*/Features:
- Icons: Font Awesome 6 Icons (solid, brands, regular) werden automatisch vor dem Gruppennamen angezeigt
- Rechte: Admin sieht immer alles, andere Benutzer nur Gruppen ohne Rechte oder mit passender Rolle
- Accordion: Gruppen werden als Bootstrap-Akkordeons dargestellt, erste Gruppe standardmäßig geöffnet
- Ohne Gruppen: Alte Darstellung ohne Akkordeons bleibt erhalten
| Typ | Beschreibung | Beispiel Default |
|---|---|---|
| Text-Felder | ||
text |
Einzeiliges Textfeld | Beispieltext |
textarea |
Mehrzeiliges Textfeld | Längerer Text |
cke5 |
WYSIWYG Editor (CKE5) | <p>HTML Content</p> |
email |
E-Mail-Adresse mit Validierung | info@beispiel.de |
url |
URL/Link (extern) | https://beispiel.de |
tel |
Telefonnummer | +49 123 456789 |
| Numerische Felder | ||
number |
Zahleneingabe (inkl. Dezimal) | 42 oder 3.14 |
| Datum/Zeit | ||
date |
Datum (YYYY-MM-DD) | 2024-01-15 |
datetime-local |
Datum + Zeit | 2024-01-15T10:30 |
time |
Uhrzeit | 14:30 |
| Farben | ||
color |
HTML5 Color Picker | #005d40 |
colorselect |
Vordefinierte Farben (Selectpicker mit Badges) | #005d40:Brand-Grün,#7D192C:Brand-Rot |
| Medien | ||
media |
Einzelne Mediendatei (natives Widget) | logo.png |
medialist |
Liste von Mediendateien (natives Widget) | bild1.jpg,bild2.jpg |
| Auswahl | ||
select |
Dropdown-Auswahl | wert1:Label 1,wert2:Label 2 |
checkbox |
Ja/Nein Checkbox | `` (leer = nicht aktiviert) |
| Links | ||
link |
Interner REDAXO-Link (natives Widget) | 5 (Artikel-ID) |
linklist |
Liste interner Links (natives Widget) | 1,5,8 (Artikel-IDs) |
external_linklist |
Externe Link-Liste mit Live-Vorschau | Name|URL|Beschreibung (ein Link pro Zeile) |
| Struktur | ||
category |
Kategorie-Auswahl (hierarchische Struktur) | 5 (Kategorie-ID) |
categorylist |
Mehrere Kategorien auswählen | 1,5,8 (Kategorie-IDs) |
| Spezial-Felder | ||
notice |
Hinweis-/Warnbox im Formular (kein Datenbankfeld) | info |
social_links |
Social Media Links Repeater | JSON (Icon + URL + Label) |
opening_hours |
Strukturierte Öffnungszeiten | JSON (Wochentage + Sonderzeiten) |
Der Feldtyp notice ist ein rein visuelles Hinweisfeld im Einstellungsformular. Er speichert keinen Wert in der Datenbank, sondern zeigt einen formatierten Alert-Block an – ideal für Erklärungen, Warnungen oder Trennungshilfen zwischen Feldergruppen.
Syntax:
tm_key: notice|Label|Typ|Text
Typen:
| Typ | Farbe | Verwendung |
|---|---|---|
info |
Blau (Standard) | Neutrale Hinweise, Erklärungen |
success |
Grün | Positive Infos, Bestätigungen |
warning |
Orange | Wichtige Hinweise, Vorsicht |
danger |
Rot | Kritische Warnungen |
Beispiele:
tm_hint_colors: notice|Hinweis|info|Farben werden als CSS Custom Properties ausgegeben.
tm_warn_api: notice|Achtung|warning|Änderungen an API-Keys erfordern einen Cache-Flush.
tm_info_media: notice|Info|success|Bilder sollten mindestens 1200×600 px groß sein.
tm_alert_delete: notice|Warnung|danger|Das Löschen kann nicht rückgängig gemacht werden!
Hinweis: Das
tm_-Prefix ist auch bei Notice-Feldern erforderlich. Der Schlüssel kann beliebig gewählt werden, da er nicht in der Datenbank landet.
Settings eines Templates können mit einem Klick von einer Domain auf eine andere übertragen werden. Die Funktion ist nur für Admins oder Benutzer mit dem Recht template_manager[copy] sichtbar.
Vorgehen:
- Template Manager → Template Einstellungen → Template und Quell-Domain wählen
- Button „Einstellungen kopieren nach …" unterhalb der Auswahl klicken
- Ziel-Domain wählen, Sprachen auswählen, Überschreiben-Option setzen
- „Jetzt kopieren" klicken → Ergebnis zeigt kopierte / übersprungene / fehlerhafte Einträge
Optionen:
- Sprachen: Nur ausgewählte Sprachen werden übertragen
- Vorhandene Werte überschreiben (Standard aktiv): Bestehende Werte in der Ziel-Domain werden ersetzt; deaktiviert werden nur fehlende Werte ergänzt
Der Feldtyp external_linklist ermöglicht die Verwaltung mehrerer externer Links mit Live-Vorschau im Backend:
Format: Ein Link pro Zeile im Format Name|URL|Beschreibung (Beschreibung optional)
Beispiel:
tm_footer_partners: external_linklist|Partner-Links||Externe Partner verlinken
Backend-Features:
- ✅ Live-Vorschau mit Validierung (zeigt Fehler sofort an)
- ✅ URL-Validierung (muss mit http:// oder https:// beginnen)
- ✅ Format-Hilfe direkt im Feld
- ✅ Kommentare möglich (Zeilen mit # oder // am Anfang)
- ✅ Monospace-Font für bessere Lesbarkeit
Eingabe-Beispiel:
WDFV|https://wdfv.de|Westdeutscher Fußballverband
FVN|https://fvn.de|Fußballverband Niederrhein
FVM|https://fvm.de|Fußballverband Mittelrhein
# Kommentare sind möglich
FLVW|https://flvw.de|Fußball- und Leichtathletik-Verband Westfalen
Frontend-Nutzung:
<?php
use FriendsOfRedaxo\TemplateManager\TemplateManager;
use FriendsOfRedaxo\TemplateManager\ExternalLinklistWidget;
// Variante 1: Direkt als HTML rendern
echo '<ul>';
echo ExternalLinklistWidget::renderHtml(
TemplateManager::get('tm_footer_partners'),
true // true = Links in neuem Tab öffnen
);
echo '</ul>';
// Variante 2: Als Array parsen für individuelle Verarbeitung
$links = ExternalLinklistWidget::parse(
TemplateManager::get('tm_footer_partners')
);
foreach ($links as $link) {
echo '<div class="partner-card">';
echo '<h3>' . rex_escape($link['name']) . '</h3>';
echo '<p>' . rex_escape($link['description']) . '</p>';
echo '<a href="' . rex_escape($link['url']) . '" target="_blank">';
echo 'Zur Website <i class="icon-external"></i>';
echo '</a>';
echo '</div>';
}
?>Rückgabe-Format der parse()-Methode:
[
[
'name' => 'WDFV',
'url' => 'https://wdfv.de',
'description' => 'Westdeutscher Fußballverband'
],
// ...
]Typische Verwendung:
- Footer-Links zu Verbänden/Partnern
- Social Media Links
- Externe Ressourcen
- Sponsor-Listen
- Tool/Service-Verzeichnisse
Bei select und colorselect Feldern werden die Optionen im Default-Wert definiert:
Select:
tm_header_style: select|Header-Stil|standard|standard:Standard,modern:Modern,minimal:Minimal|Auswahl des Header-Designs
Colorselect (mit visuellen Farb-Badges):
tm_primary_color: colorselect|Akzentfarbe|#005d40:#005d40 Brand-Grün,#7D192C:#7D192C Brand-Rot,#1e87f0:#1e87f0 Blau|Hauptfarbe
Format: wert:Label,wert2:Label2 oder einfach wert1,wert2,wert3
Hinweis: colorselect zeigt farbige Badges im Bootstrap Selectpicker an - ideal für vordefinierte Farbpaletten!
Der Feldtyp cke5 bietet einen vollwertigen WYSIWYG-Editor:
Beispiel mit Default-Profil:
tm_welcome_text: cke5|Willkommenstext||Editor-Inhalt mit HTML-Formatierung
Mit eigenem Profil (Profil im Default-Wert angeben):
tm_footer_text: cke5|Footer-Text|simple|Editor mit 'simple' Profil
tm_description: cke5|Beschreibung|full|Editor mit 'full' Profil
Features:
- Profil-Angabe im Default-Wert (leer = 'default')
- Automatische Sprach-Erkennung (User + Content)
- Fallback zu Textarea wenn CKE5 nicht verfügbar
- Unterstützt alle CKE5-Profile aus dem Backend
Frontend-Ausgabe:
<!-- Direktausgabe (HTML ist bereits formatiert) -->
<?= TemplateManager::get('tm_welcome_text') ?>
<!-- Mit Fallback -->
<?= TemplateManager::get('tm_welcome_text', '<p>Standard-Text</p>') ?>Wichtig: CKE5-Inhalte sind bereits HTML-formatiert und sollten nicht mit rex_escape() ausgegeben werden!
Der Feldtyp category bietet eine hierarchische Kategorie-Auswahl mit korrekter Struktur-Darstellung:
Beispiel:
tm_news_category: category|News-Kategorie|5|Kategorie für News-Artikel
tm_main_category: category|Hauptkategorie||Root-Kategorie auswählen
Features:
- Hierarchische Darstellung mit Einrückung
- Kategorie-IDs werden angezeigt: "Name [ID]"
- Berechtigungs-Prüfung (nur Kategorien mit Zugriff)
- "Homepage" Option für Root-Level (ID: 0)
- Berücksichtigt aktuelle Sprache
- Bootstrap Selectpicker mit Live-Search
Frontend-Nutzung:
<?php
use FriendsOfRedaxo\TemplateManager\TemplateManager;
// Kategorie-ID abrufen
$categoryId = TemplateManager::get('tm_news_category');
if ($categoryId) {
// Kategorie-Objekt laden
$category = rex_category::get($categoryId);
if ($category) {
echo '<h2>' . rex_escape($category->getName()) . '</h2>';
// Artikel der Kategorie auflisten
$articles = $category->getArticles();
foreach ($articles as $article) {
if (!$article->isStartArticle()) {
echo '<a href="' . $article->getUrl() . '">';
echo rex_escape($article->getName());
echo '</a><br>';
}
}
}
}
?>Typische Verwendung:
- News-Kategorie für Artikel-Listen
- Landingpage-Kategorie
- Produkt-Kategorie
- Filterkategorien
Der Feldtyp categorylist bietet Mehrfachauswahl von Kategorien mit hierarchischer Darstellung:
Beispiel:
tm_news_categories: categorylist|News-Kategorien||Mehrere Kategorien für News-Filter
tm_product_categories: categorylist|Produkt-Kategorien|5,8|Standard-Produkt-Kategorien
Features:
- Mehrfachauswahl mit Checkboxen
- Hierarchische Darstellung mit Einrückung
- Kategorie-IDs werden angezeigt: "Name [ID]"
- "Alle auswählen / Keine" Buttons
- Berechtigungs-Prüfung
- Bootstrap Selectpicker mit Live-Search
Frontend-Nutzung:
<?php
use FriendsOfRedaxo\TemplateManager\TemplateManager;
// Kategorie-IDs abrufen (komma-separiert)
$categoryIds = TemplateManager::get('tm_news_categories');
if ($categoryIds) {
$categoryIds = array_filter(array_map('intval', explode(',', $categoryIds)));
echo '<div class="category-filter">';
foreach ($categoryIds as $catId) {
$category = rex_category::get($catId);
if ($category) {
echo '<a href="' . $category->getUrl() . '" class="btn">';
echo rex_escape($category->getName());
echo '</a> ';
}
}
echo '</div>';
// Oder: Artikel aus allen ausgewählten Kategorien
$articles = [];
foreach ($categoryIds as $catId) {
$category = rex_category::get($catId);
if ($category) {
$articles = array_merge($articles, $category->getArticles());
}
}
// Artikel ausgeben...
}
?>Typische Verwendung:
- Mehrere News-Kategorien
- Produkt-Filtergruppen
- Content-Aggregation aus verschiedenen Bereichen
- Multi-Category Landing Pages
Der Feldtyp social_links bietet eine komfortable Verwaltung von Social Media Links mit Drag & Drop Sortierung:
Beispiel:
tm_social_links: social_links|Social Media Links||Verlinkte Social-Media-Profile
tm_social_links: social_links|Social Media Links|fa|Nur Font Awesome Icons
tm_social_links: social_links|Social Media Links|uk|Nur UIKit Icons
tm_social_links: social_links|Social Media Links|both|Beide Icon-Sets (Standard)
Icon-Modus (im Default-Wert):
fa- Nur Font Awesome Iconsuk- Nur UIKit Iconsbothoder leer - Beide Icon-Sets (Standard)
Backend-Features:
- ✅ 30+ vordefinierte Social Icons (Font Awesome + UIKit)
- ✅ Drag & Drop Sortierung mit Handle
- ✅ Pfeil-Buttons zum manuellen Sortieren
- ✅ Live Icon-Vorschau bei der Auswahl
- ✅ Optionales Label pro Link
- ✅ Icon-Set wählbar (fa, uk, both)
Enthaltene Icons:
- Facebook, Twitter/X, Instagram, LinkedIn, Xing, YouTube
- TikTok, Pinterest, WhatsApp, Telegram, GitHub, GitLab
- Discord, Slack, Mastodon, Threads, Bluesky, Reddit
- Snapchat, Vimeo, Dribbble, Behance, Flickr, Spotify
- SoundCloud, Twitch, RSS, E-Mail, Telefon, Webseite
Frontend-Nutzung:
<?php
use FriendsOfRedaxo\TemplateManager\TemplateManager;
$socialLinksJson = TemplateManager::get('tm_social_links');
$socialLinks = $socialLinksJson ? json_decode($socialLinksJson, true) : [];
if (!empty($socialLinks)): ?>
<div class="social-links">
<?php foreach ($socialLinks as $link):
if (empty($link['url'])) continue;
$icon = $link['icon'] ?? '';
$url = $link['url'];
$label = $link['label'] ?? '';
// Icon-Klasse ermitteln
if (str_starts_with($icon, 'uk-icon-')) {
// UIKit Icon
$ukIcon = str_replace('uk-icon-', '', $icon);
$iconHtml = '<span uk-icon="icon: ' . rex_escape($ukIcon) . '"></span>';
} else {
// Font Awesome (für FA6 CDN)
$brandIcons = ['fa-facebook', 'fa-twitter', 'fa-instagram', /* ... */];
$faClass = in_array($icon, $brandIcons, true)
? 'fa-brands ' . $icon
: 'fa-solid ' . $icon;
$iconHtml = '<i class="' . rex_escape($faClass) . '"></i>';
}
?>
<a href="<?= rex_escape($url) ?>"
target="_blank"
rel="noopener noreferrer"
<?= $label ? 'title="' . rex_escape($label) . '"' : '' ?>>
<?= $iconHtml ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>JSON-Datenstruktur:
[
{ "icon": "fa-facebook", "url": "https://facebook.com/firma", "label": "Facebook" },
{ "icon": "fa-instagram", "url": "https://instagram.com/firma", "label": "Instagram" },
{ "icon": "uk-icon-github", "url": "https://github.com/firma", "label": "" }
]Der Feldtyp opening_hours bietet eine professionelle Verwaltung von Öffnungszeiten wie bei Google My Business:
Beispiel:
tm_opening_hours: opening_hours|Öffnungszeiten||Geschäftszeiten inkl. Feiertage
Backend-Features:
- ✅ Reguläre Zeiten pro Wochentag (Mo-So)
- ✅ Mehrere Zeitfenster pro Tag (z.B. für Mittagspausen: 9-12 + 14-18 Uhr)
- ✅ Status-Optionen: Geöffnet / Geschlossen / 24h geöffnet
- ✅ Schnellaktionen: "Mo → Werktage", "Alle geschlossen", "Zurücksetzen"
- ✅ Kopier-Funktion: Zeiten auf andere Tage/Gruppen übertragen
- ✅ Sonderzeiten/Feiertage mit 15 vordefinierten deutschen Feiertagen
- ✅ Bewegliche Feiertage: Ostern, Pfingsten, Christi Himmelfahrt etc.
- ✅ Live-Vorschau mit Tabelle und "heute"-Markierung
- ✅ 3 Tabs: Reguläre Zeiten | Sonderzeiten | Vorschau
- ✅ Freitext/Notiz-Feld für zusätzliche Hinweise (z.B. "Termine nach Vereinbarung")
- ✅ Auto-Repair: Feiertage werden automatisch erkannt und repariert
Vordefinierte Feiertage:
- Neujahr, Karfreitag, Ostersonntag, Ostermontag
- Tag der Arbeit, Christi Himmelfahrt, Pfingstsonntag, Pfingstmontag
- Fronleichnam, Tag der Deutschen Einheit, Allerheiligen
- Heiligabend, 1. + 2. Weihnachtstag, Silvester
Frontend-Nutzung mit OpeningHoursHelper (empfohlen):
Die OpeningHoursHelper-Klasse bietet eine komfortable API mit Übersetzungen und vorformatierten Daten:
<?php
use FriendsOfRedaxo\TemplateManager\TemplateManager;
use FriendsOfRedaxo\TemplateManager\OpeningHoursHelper;
// Helper instanziieren (Sprache wird automatisch erkannt)
$helper = new OpeningHoursHelper(
TemplateManager::get('tm_opening_hours'),
rex_clang::getCurrent()->getCode() // 'de', 'en' etc.
);
if ($helper->hasData()):
$regularHours = $helper->getRegular(); // Alle Wochentage einzeln
$groupedHours = $helper->getRegularGrouped(); // Tage mit gleichen Zeiten zusammengefasst
$specialHours = $helper->getSpecial(5, true); // Max 5, nur zukünftige
$currentStatus = $helper->getCurrentStatus(); // Jetzt geöffnet?
?>
<!-- Status-Badge -->
<?php if ($currentStatus['is_open']): ?>
<span class="badge badge-success"><?= rex_escape($currentStatus['label']) ?></span>
<?php if ($currentStatus['next_change_label']): ?>
<small><?= rex_escape($currentStatus['next_change_label']) ?></small>
<?php endif; ?>
<?php else: ?>
<span class="badge badge-danger"><?= rex_escape($currentStatus['label']) ?></span>
<?php endif; ?>
<!-- Gruppierte Öffnungszeiten (empfohlen für kompakte Darstellung) -->
<h3><?= rex_escape($helper->translate('labels.opening_hours')) ?></h3>
<table>
<?php foreach ($groupedHours as $group): ?>
<tr<?= $group['contains_today'] ? ' class="today"' : '' ?>>
<td><?= rex_escape($group['label']) ?></td> <!-- z.B. "Mo - Fr" oder "Sa, So" -->
<td class="<?= $group['is_closed'] ? 'closed' : '' ?>">
<?= rex_escape($group['formatted']) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
<!-- Freitext/Notiz anzeigen -->
<?php if ($helper->hasNote()): ?>
<p class="note"><em><?= rex_escape($helper->getNote()) ?></em></p>
<?php endif; ?>
<!-- Alternative: Alle Tage einzeln -->
<!--
<table>
<?php foreach ($regularHours as $day): ?>
<tr<?= $day['is_today'] ? ' class="today"' : '' ?>>
<td>
<?= rex_escape($day['label']) ?>
<?= $day['is_today'] ? ' <small>(' . rex_escape($helper->translate('labels.today')) . ')</small>' : '' ?>
</td>
<td class="<?= $day['is_closed'] ? 'closed' : '' ?>">
<?= rex_escape($day['formatted']) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
-->
<!-- Sonderöffnungszeiten -->
<?php if (!empty($specialHours)): ?>
<h4><?= rex_escape($helper->translate('labels.special_hours')) ?></h4>
<ul>
<?php foreach ($specialHours as $special): ?>
<li>
<strong><?= rex_escape($special['display_name']) ?>:</strong>
<?= rex_escape($special['formatted']) ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php endif; ?>OpeningHoursHelper - Verfügbare Methoden:
| Methode | Beschreibung |
|---|---|
hasData() |
Prüft ob Daten vorhanden sind |
getRegular(bool $shortLabels = false) |
Reguläre Zeiten als Array (jeden Tag einzeln) |
getRegularGrouped(bool $shortLabels = true) |
Aufeinanderfolgende Tage mit gleichen Zeiten zusammengefasst |
getSpecial(?int $limit, bool $futureOnly) |
Sonderzeiten als Array |
getToday() |
Heutigen Tag mit Status |
isOpenNow() |
Prüft ob aktuell geöffnet |
getCurrentStatus() |
Status-Array für "Jetzt geöffnet"-Anzeige |
getNote() |
Freitext/Notiz abrufen (z.B. "Termine nach Vereinbarung") |
hasNote() |
Prüft ob eine Notiz vorhanden ist |
translate(string $key) |
Übersetzung abrufen |
setLocale(string $locale) |
Sprache ändern |
setTranslations(string $locale, array $translations) |
Eigene Übersetzungen setzen |
Eigene Übersetzungen hinzufügen:
$helper = new OpeningHoursHelper($json);
// Französisch hinzufügen
$helper->setTranslations('fr', [
'weekdays' => [
'monday' => 'Lundi',
'tuesday' => 'Mardi',
// ...
],
'status' => [
'closed' => 'Fermé',
'open_24h' => 'Ouvert 24h/24',
'open' => 'Ouvert',
],
'labels' => [
'today' => "aujourd'hui",
'opening_hours' => "Heures d'ouverture",
'special_hours' => 'Horaires spéciaux',
'we_are_open' => 'Nous sommes ouverts',
'we_are_closed' => 'Nous sommes fermés',
'time_suffix' => 'h',
],
]);Datenstruktur der Arrays:
// getRegular() gibt zurück (jeden Tag einzeln):
[
'monday' => [
'key' => 'monday',
'label' => 'Montag', // Übersetzt
'label_short' => 'Mo', // Kurz
'label_full' => 'Montag', // Lang
'status' => 'open', // open, closed, 24h
'status_label' => 'Geöffnet', // Übersetzt
'is_today' => true, // Bool
'is_open' => true, // Bool
'is_closed' => false, // Bool
'is_24h' => false, // Bool
'times' => [ // Rohdaten
['open' => '09:00', 'close' => '12:00'],
['open' => '14:00', 'close' => '18:00'],
],
'times_formatted' => '09:00–12:00, 14:00–18:00 Uhr', // Formatiert
'formatted' => '09:00–12:00, 14:00–18:00 Uhr', // Status oder Zeiten
],
// ...
]
// getRegularGrouped() gibt zurück (aufeinanderfolgende Tage mit gleichen Zeiten zusammengefasst):
[
[
'label' => 'Mo - Fr', // Kurzform-Bereich
'label_full' => 'Montag - Freitag', // Langform-Bereich
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
'day_count' => 5,
'status' => 'open',
'status_label' => 'Geöffnet',
'is_open' => true,
'is_closed' => false,
'is_24h' => false,
'times' => [...],
'times_formatted' => '09:00–18:00 Uhr',
'formatted' => '09:00–18:00 Uhr',
'contains_today' => true, // Ist der aktuelle Tag in dieser Gruppe?
],
[
'label' => 'Sa, So', // Zwei aufeinanderfolgende Tage
'label_full' => 'Samstag, Sonntag',
'day_count' => 2,
'is_closed' => true,
'formatted' => 'Geschlossen',
'contains_today' => false,
],
]
// getSpecial() gibt zurück:
[
[
'date' => '12-24', // Originaldatum
'date_resolved' => '2026-12-24', // Aufgelöstes Datum
'date_formatted' => '24.12.', // Formatiert
'name' => 'Heiligabend', // Name
'display_name' => 'Heiligabend', // Name oder formatiertes Datum
'status' => 'open',
'status_label' => 'Geöffnet',
'is_holiday' => true, // Vordefinierter Feiertag
'is_open' => true,
'is_closed' => false,
'times' => [...],
'times_formatted' => '09:00–14:00 Uhr',
'formatted' => '09:00–14:00 Uhr',
],
// ...
]
// getCurrentStatus() gibt zurück:
[
'is_open' => true,
'label' => 'Wir haben geöffnet',
'today' => [...], // getToday() Daten
'next_change' => '18:00', // Nächste Statusänderung
'next_change_label' => 'Schließt um 18:00 Uhr',
]JSON-Datenstruktur:
{
"regular": {
"monday": {
"status": "open",
"times": [
{ "open": "09:00", "close": "12:00" },
{ "open": "14:00", "close": "18:00" }
]
},
"saturday": { "status": "closed", "times": [] },
"sunday": { "status": "closed", "times": [] }
},
"special": [
{
"date": "12-24",
"name": "Heiligabend",
"status": "open",
"times": [{ "open": "09:00", "close": "14:00" }],
"holiday": true
},
{
"date": "2026-01-02",
"name": "Betriebsferien",
"status": "closed",
"times": [],
"holiday": false
}
],
"note": "Termine nach Vereinbarung unter 0123-456789"
}Typische Verwendung:
- Geschäfts-/Ladenöffnungszeiten
- Praxis-/Kanzlei-Sprechstunden
- Restaurant-Öffnungszeiten mit Mittagspause
- Service-Hotline Erreichbarkeit
Die Settings werden über statische Methoden der TemplateManager Klasse abgerufen:
<?php
use FriendsOfRedaxo\TemplateManager\TemplateManager;
// Einfacher Zugriff (aktuelle Domain + Sprache)
$companyName = TemplateManager::get('tm_company_name');
$logo = TemplateManager::get('tm_logo');
// Mit Fallback-Wert
$primaryColor = TemplateManager::get('tm_primary_color', '#005d40');
// Bestimmte Sprache abrufen (z.B. Sprach-ID 2)
$companyNameEN = TemplateManager::get('tm_company_name', null, null, 2);
// Bestimmte Domain abrufen (z.B. Domain-ID 1)
$companyDomain1 = TemplateManager::get('tm_company_name', null, 1, null);
// Bestimmte Domain UND Sprache
$companyDomain1EN = TemplateManager::get('tm_company_name', null, 1, 2);
// Alle Settings als Array
$allSettings = TemplateManager::getAll();
?><!-- Logo anzeigen -->
<?php if (TemplateManager::get('tm_logo')): ?>
<img src="<?= rex_url::media(TemplateManager::get('tm_logo')) ?>" alt="Logo">
<?php endif; ?>
<!-- Firmenname mit Fallback -->
<h1><?= rex_escape(TemplateManager::get('tm_company_name', 'Muster GmbH')) ?></h1>
<!-- CKE5 WYSIWYG Inhalt (kein rex_escape!) -->
<?= TemplateManager::get('tm_welcome_text', '<p>Willkommen!</p>') ?>
<!-- E-Mail -->
<?php if (TemplateManager::get('tm_contact_email')): ?>
<a href="mailto:<?= rex_escape(TemplateManager::get('tm_contact_email')) ?>">
<?= rex_escape(TemplateManager::get('tm_contact_email')) ?>
</a>
<?php endif; ?>
<!-- Telefon -->
<?php if (TemplateManager::get('tm_contact_phone')): ?>
<a href="tel:<?= rex_escape(TemplateManager::get('tm_contact_phone')) ?>">
<?= rex_escape(TemplateManager::get('tm_contact_phone')) ?>
</a>
<?php endif; ?>
<!-- Linklist verarbeiten -->
<?php if (TemplateManager::get('tm_footer_links')): ?>
<ul>
<?php
$links = explode(',', TemplateManager::get('tm_footer_links'));
foreach ($links as $articleId) {
$article = rex_article::get(trim($articleId));
if ($article) {
echo '<li><a href="' . $article->getUrl() . '">' .
rex_escape($article->getName()) . '</a></li>';
}
}
?>
</ul>
<?php endif; ?>
<!-- Medialist verarbeiten -->
<?php if (TemplateManager::get('tm_header_images')): ?>
<div class="slideshow">
<?php
$images = explode(',', TemplateManager::get('tm_header_images'));
foreach ($images as $image) {
$image = trim($image);
if ($image) {
echo '<img src="' . rex_url::media($image) . '" alt="">';
}
}
?>
</div>
<?php endif; ?>
<!-- Checkbox prüfen -->
<?php if (TemplateManager::get('tm_show_breadcrumbs')): ?>
<!-- Breadcrumb-Code hier -->
<?php endif; ?>
<!-- CSS Custom Properties mit Colorselect -->
<style>
:root {
--primary-color: <?= TemplateManager::get('tm_primary_color', '#005d40') ?>;
--primary-dark: color-mix(in srgb, var(--primary-color) 80%, black);
}
</style>- Template Manager → Template Einstellungen öffnen
- Template aus Liste auswählen → Konfigurieren klicken
- Domain und Sprache wählen
- Einstellungen in den Sprach-Tabs eingeben
- Speichern klicken (speichert alle Sprachen gleichzeitig)
- Jede Sprache hat einen eigenen Tab
- Die erste Sprache dient als Fallback für andere Sprachen
- Leere Felder in Sprache 2+ werden automatisch durch Sprache 1 ersetzt
- Alle Sprachen werden gemeinsam gespeichert
- Jede YRewrite-Domain kann eigene Einstellungen haben
- Domain-Auswahl über Dropdown oben
- Default-Domain (ID 0) wird immer als letzte angezeigt
- Settings werden getrennt pro Domain + Sprache gespeichert
Template Manager → Setup → Demo-Template jetzt importieren
Importiert das vorkonfigurierte Modern Business Template mit 5 Einstellungen und modernem Design.
Template Manager → Setup → Verwaiste Settings entfernen
Entfernt Settings von gelöschten Templates aus der Datenbank. Nützlich wenn Templates gelöscht wurden und deren Settings verblieben sind.
Das Addon erstellt die Tabelle rex_template_settings:
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | int | Primary Key |
| template_id | int | REDAXO Template-ID |
| domain_id | int | YRewrite Domain-ID |
| clang_id | int | Sprach-ID |
| setting_key | varchar(191) | Setting-Name (mit tm_ Prefix) |
| setting_value | text | Gespeicherter Wert |
| created_date | datetime | Erstellungsdatum |
| updated_date | datetime | Änderungsdatum |
UNIQUE KEY: (template_id, domain_id, clang_id, setting_key)
- Immer
tm_Prefix verwenden - Sprechende Namen:
tm_contact_emailstatttm_email1 - Gruppierung durch Prefix:
tm_footer_col1_title,tm_footer_col2_title
Gib immer sinnvolle Default-Werte an:
tm_company_name: text|Firmenname|Muster GmbH|Offizieller Firmenname
So funktioniert die Website auch ohne Konfiguration.
Im Template immer mit Fallback-Werten arbeiten:
<?= rex_escape(TemplateManager::get('tm_company_name', 'Meine Firma')) ?>Die Methode gibt den Default-Wert zurück, wenn das Setting nicht existiert.
Nutze das Beschreibungs-Feld für Hinweise:
tm_google_analytics: text|Google Analytics ID||GA4 Tracking-ID (Format: G-XXXXXXXXXX)
<?php
/**
* Simple Business Template
*
* DOMAIN_SETTINGS
* tm_logo: media|Logo||Firmenlogo
* tm_company_name: text|Firmenname|Muster GmbH|Firmenname
* tm_contact_email: email|E-Mail|info@beispiel.de|Kontakt E-Mail
* tm_primary_color: text|Akzentfarbe|#005d40|Akzentfarbe (Hex)
* tm_footer_text: textarea|Footer-Text|© 2024 Muster GmbH|Copyright-Text
*/
use FriendsOfRedaxo\TemplateManager\TemplateManager;
?>
<!DOCTYPE html>
<html>
<head>
<title><?= $this->getValue('name') ?> | <?= TemplateManager::get('tm_company_name') ?></title>
<style>
:root { --primary: <?= TemplateManager::get('tm_primary_color', '#005d40') ?>; }
body { font-family: sans-serif; }
header { background: var(--primary); color: white; padding: 1rem; }
</style>
</head>
<body>
<header>
<?php if (TemplateManager::get('tm_logo')): ?>
<img src="<?= rex_url::media(TemplateManager::get('tm_logo')) ?>" alt="Logo">
<?php endif; ?>
<h1><?= TemplateManager::get('tm_company_name') ?></h1>
</header>
<main><?= $this->getArticle() ?></main>
<footer>
<p><?= nl2br(rex_escape(TemplateManager::get('tm_footer_text'))) ?></p>
<a href="mailto:<?= TemplateManager::get('tm_contact_email') ?>">Kontakt</a>
</footer>
</body>
</html>use FriendsOfRedaxo\TemplateManager\TemplateParser;
use FriendsOfRedaxo\TemplateManager\TemplateManager;Der Parser sucht nach diesem Pattern:
#\*\s+(tm_\w+):\s*([^|]+)\|([^|]*)\|([^|]*)\|(.*)$#mNur Zeilen mit tm_ Prefix werden erfasst!
1 Bei jedem Frontend-Request:
- TemplateManager::get() wird aufgerufen
- Beim ersten Aufruf: Cache wird geladen
- Aktuelle Domain + Sprache werden automatisch ermittelt
- Settings aus DB geladen mit Fallback-Logik
- Ergebnis wird pro Request gecacht (nach Domain/Sprach-Kombination)
TemplateManager::get(
string $key, // Setting-Key (z.B. 'tm_company_name')
mixed $default = null, // Fallback wenn nicht vorhanden
?int $domainId = null, // Optional: Domain-ID (null = aktuelle)
?int $clangId = null // Optional: Sprach-ID (null = aktuelle)
): mixed
TemplateManager::getAll(
?int $domainId = null, // Optional: Domain-ID (null = aktuelle)
?int $clangId = null // Optional: Sprach-ID (null = aktuelle)
): array- REDAXO: >= 5.17
- YRewrite: >= 2.0 (für Multi-Domain Support)
- PHP: >= 8.0
- Optional: UIKit Theme Builder (für
uikit_theme_selectFeldtyp)
MIT License
- ✨ Neuer Feldtyp
notice: Hinweis-, Info-, Warn- und Danger-Boxen direkt im Einstellungsformular (kein DB-Wert) - 🔄 Domain-Einstellungen kopieren: Settings eines Templates per Formular von einer Domain auf eine andere übertragen
- 🔒 Rechteverwaltung Copy: Nur Admins oder User mit
template_manager[copy]können kopieren - 🌑 Dark Mode: OpeningHours-Widget mit vollständigem Dark-Mode via CSS Custom Properties (
--oh-*) - 🐛 Bugfix: YRewrite Default-Domain (ID null) wurde als „Unbekannt" angezeigt –
(int)Cast behebt Typen-Vergleich
- 🏢 Globale Variablen (
GLOBAL_SETTINGS): Domainübergreifende Einstellungen mit eigenem Menüpunkt - 🔑 Kaskadierung: Template-Setting → Globales Setting → Default-Wert
- 📝 Akkordeon-Gruppen: Felder in thematische, aufklappbare Gruppen mit Icons organisieren
- 🔒 Gruppen-Rechte: Gruppen pro Benutzerrolle sichtbar/unsichtbar
- ✨ Neuer Feldtyp:
external_linklistfür externe Link-Listen mit Live-Vorschau - 🎨 Repeater-Funktionalität: Strukturierte externe Links (Name|URL|Beschreibung)
- 🔍 Live-Validierung: URL-Prüfung und Format-Feedback im Backend
- 📝 Kommentar-Support: Zeilen mit # oder // werden ignoriert
- 🎯 ExternalLinklistWidget: Neue Helper-Klasse mit parse() und renderHtml() Methoden
- 🏗️ Architektur: Eigener FieldRenderer statt textarea-Missbrauch
- 📚 Dokumentation: Umfassende Beispiele für external_linklist Nutzung
- ✨ Neue Feldtypen:
banner_selectfür UIKit Banner Design Integration - 🎨 Footer Design: Professionelle rechtliche Gestaltung mit kompakten Link-Abständen
- 📱 Mobile Optimierung: Verbesserte Navigation mit größerem Hamburger-Icon
- 🌐 Multi-Column Footer: Unterstützung für flexible Grid-Layouts
- ♿ Accessibility: Responsive Legal-Navigation mit verbesserter Mobile-Darstellung
- 🎉 Erste öffentliche Version
- 📝 DocBlock-basierte Konfiguration
- 🌍 Multi-Domain Support mit YRewrite
- 🌐 Mehrsprachigkeit mit Fallback-Logik
- 🎨 20+ Feldtypen
- 🔧 Native REDAXO Widgets
- 🚀 Statische Frontend-API
- 📦 Demo-Template
- GitHub Issues: https://github.com/FriendsOfREDAXO/template_manager/issues
- GitHub Repository: https://github.com/FriendsOfREDAXO/template_manager
Entwickelt von Friends Of REDAXO