Zum Inhalt springen
365CMS

Theme-Entwicklung (Referenz)

Version 3.4.00 (Changelog bis 3.4.08)Status StableStand 2026-10-02
Auf dieser Seite
  1. English (summary)
  2. Deutsch
  3. 1. Mindestaufbau
  4. 2. theme.json
  5. 3. Rendering-Vertrag
  6. 4. Hooks im Theme
  7. 5. Sicherheit & CSP
  8. 6. Datenschutz
  9. 7. Customizer, Menüs, Mitgliederbereich
  10. 8. Versionierung & Auslieferung
  11. Verwandte Dokumente

English (summary)

A theme is a directory CMS/themes/<slug>/ with at least style.css (header comment) and index.php or functions.php; theme.json declares templates, partials, menu locations and customizer sections, update.json the version metadata. ThemeManager::render($template, $data) includes header.php, <template>.php (fallback index.php) and footer.php; templates receive their data as local variables. Themes must be CSP-compatible (nonce on every inline <script>/<style>, no inline handlers), fire head and body_end, escape all output and respect the privacy settings (local fonts, external consent banner).

Deutsch

1. Mindestaufbau

CMS/themes/mein-theme/
├── style.css          # Pflicht: Header-Kommentar (Theme Name, Version, Author, Description)
├── functions.php      # Theme-Bootstrap (Hooks, Helfer, Assets) – oder index.php
├── theme.json         # Metadaten, Templates, Partials, Menüpositionen, Customizer-Bereiche
├── update.json        # Version, min_cms_version, Changelog (für Marketplace/Updates)
├── header.php  footer.php
├── index.php          # Fallback für alle Templates
├── home.php  page.php  blog.php  blog-single.php  category.php  tag.php
├── author.php  archive.php  search.php  contact.php  404.php  error.php
├── login.php  register.php  forgot-password.php   # nur für Auth-Modus „legacy“
├── partials/          # wiederverwendbare Bausteine
├── includes/          # PHP-Helfer
├── js/  …             # Theme-Skripte (extern, CSP)
├── admin/customizer.php (+ admin/customizer/)     # optional: eigener Customizer
└── member/<seite>.php                              # optional: Overrides des Mitgliederbereichs

Health-Check beim Aktivieren (ThemeManager::healthCheckTheme()): style.css vorhanden, index.php oder functions.php vorhanden, alle PHP-Dateien syntaktisch gültig.

2. theme.json

{
  "name": "Mein Theme",
  "slug": "mein-theme",
  "version": "1.0.0",
  "author": "Firma",
  "description": "…",
  "tags": ["blog", "responsive"],
  "supports": ["custom-logo", "sticky-header", "custom-css", "categories"],
  "menus": { "primary": "Hauptmenü", "footer": "Footer" },
  "templates": { "home": "home.php", "page": "page.php", "blog-single": "blog-single.php", "404": "404.php" },
  "partials": { "header": "header.php", "footer": "footer.php", "sidebar": "partials/sidebar.php" },
  "customization": { "colors": { … } },
  "post_templates": [
    { "id": "default", "label": "Standard", "description": "", "file": "blog-single.php", "meta_fields": [] }
  ]
}
  • menus registriert Menüpositionen (zusätzlich Filter register_menu_locations).
  • post_templates (optional) bietet im Beitragseditor eine Template-Auswahl mit eigenen Metafeldern an; ohne Angabe gibt es nur „Standard“. cms-default definiert keine Beitrags-Templates.

3. Rendering-Vertrag

  • Rahmen: ThemeManager::render() bindet header.php, das Template und footer.php ein und zählt den Seitenaufruf (TrackingService). Templates rufen den Rahmen nicht selbst auf; nur error.php (vom Fatal-Handler direkt eingebunden) rendert ihn selbst.
  • Template-Hierarchie: <template>.php → index.php. Der Filter template_name kann umleiten. Für authors und sitemap (Routen /autoren, /sitemap) greift ohne eigenes Template der Fallback.
  • Daten je Template (als Variablen verfügbar):
Template Variablen
home – (Startseite liest selbst: Customizer-Modus posts oder landing)
page $page (Array, content = gerendertes, bereinigtes HTML; auch Hub-Sites), $contentLocale
blog $posts (Objekte), $total, $currentPage, $totalPages, $perPage (Paginierung ?p=N)
blog-single $post (Objekt), $contentLocale
category $category (Array), $posts, $query, $total, $currentPage, $totalPages, $perPage; Übersicht /kategorie mit $overviewItems
tag $tag (name, slug, slug_de, slug_en), $posts, $query, … ; Übersicht /tag mit $overviewItems
author $author, $posts, $total, $currentPage, $totalPages, $perPage
search $results (Arrays mit _type, _type_label, slug, title, meta_description; Plugin-Treffer zusätzlich url, optional date), $query, $type, $sort (relevance/date), $total, $location, $filter
contact, 404 –
login, register, forgot-password nur im Auth-Modus legacy (../admin/themes-design/CMS-LOGINPAGE.md)
  • Suche: Treffer sind bereits gefiltert (jedes Suchwort im sichtbaren Text) und sortiert; ohne Suchbegriff keine Treffer. Formulare senden q, type, sort.
  • Links: Beitrags-URLs mit PermalinkService::buildPostUrl(), Archive mit cms_get_archive_url(), lokalisierte Pfade mit ContentLocalizationService::buildLocalizedPath().
  • Es gibt keine Klassen \CMS\CMS, PageService, PostService, ContentHelper und keine Methode ThemeManager::registerMenuLocation().

4. Hooks im Theme

Pflicht/Empfehlung Hook Wo
Pflicht Hooks::doAction('head') im <head> von header.php (SEO-Meta, Consent, Analytics, Styles)
Pflicht Hooks::doAction('body_end') vor </body> in footer.php (Consent-Skript, Web Vitals, PhotoSwipe)
Empfohlen Hooks::applyFilters('page_title', $titel) <title>
Optional before_header, after_header, before_footer, after_footer löst der Core aus

before_footer und body_end laufen je Request nur einmal. Eigene Theme-Aktionen meldet man in functions.php an, z. B. Hooks::addAction('head', [$this, 'enqueueStyles'], 10).

5. Sicherheit & CSP

  • header.php gibt cms_csp_runtime_tags() als erstes Skript aus (der Core ergänzt es zur Sicherheit als erste head-Aktion).
  • Jedes Inline-<style>/<script> trägt Security::instance()->nonceAttr(). Inline-Eventhandler (onclick=), javascript:-URLs, eval() und Fremdskripte ohne Security::allowCspSources() werden blockiert; style-Attribute schreibt der Core in nonce-geschützte Klassen um.
  • Alle Ausgaben escapen (htmlspecialchars(..., ENT_QUOTES, 'UTF-8'), esc_html(), esc_url(), esc_attr()); $page['content'] / $post-Inhalt ist bereits bereinigtes HTML.
  • Formulare (Kontakt, Newsletter, Favoriten) brauchen ein CSRF-Token: öffentliche POSTs prüft der Router global mit der Aktion form_guard (Security::instance()->generateToken('form_guard')), Logout mit logout.
  • Keine Zugangsdaten oder Deployment-Daten im Theme-Verzeichnis.

6. Datenschutz

  • Bei privacy_use_local_fonts = 1 keine Google-Fonts laden; lokale Schriften kommen aus der Schriftverwaltung (../admin/themes-design/FONTS.md).
  • Eigene Cookie-Banner nur ausgeben, wenn CookieConsentService::getInstance()->isManagedExternally() false ist.
  • Externe Einbettungen (YouTube, Karten) nur nach hasConsentForService().

7. Customizer, Menüs, Mitgliederbereich

  • Customizer: admin/customizer.php wird unter /admin/theme-editor eingebunden (../admin/themes-design/CUSTOMIZER.md). Werte über ThemeCustomizer::instance()->get($bereich, $schluessel, $standard) lesen, als CSS-Variablen ausgeben.
  • Menüs: ThemeManager::instance()->getMenu('primary') liefert die Einträge (title, url, target, icon, parent_id).
  • Mitgliederbereich: member/<seite>.php im Theme überschreibt die Core-Seite; member/dashboard.php aktiviert zusätzlich die Route /dashboard.

8. Versionierung & Auslieferung

  • theme.json, update.json (min_cms_version ≥ 3.4.00), style.css und die Versionskonstante in functions.php (bei cms-default: MERIDIAN_THEME_VERSION) tragen dieselbe Version.
  • Auslieferung als ZIP mit dem Theme-Ordner auf oberster Ebene; für den Marketplace mit SHA-256-Prüfsumme (../admin/themes-design/MARKETPLACE.md).
  • Ein statischer Vertragsprüfer (TESTS/theme-contract/run.php) wird in früheren Release-Notizen erwähnt, ist in diesem Repository aber nicht enthalten.

Verwandte Dokumente

README.md · DEVELOPMENT.md · COMPONENTS.md · DESIGN-SYSTEM.md · JAVASCRIPT.md · ../core/HOOKS-REFERENCE.md

Bereit für ein CMS, das mitdenkt?

Installieren Sie 365CMS auf Ihrem Server und legen Sie in wenigen Minuten los.