b2in/packages/flux-cms
2026-05-13 14:34:08 +02:00
..
components 10-04-2026 2026-04-10 17:18:17 +02:00
core Display Module 13-05-2026 2026-05-13 14:34:08 +02:00
starter-components 10-04-2026 2026-04-10 17:18:17 +02:00
ARCHITECTURE.md 10-04-2026 2026-04-10 17:18:17 +02:00
CONTRIBUTING.md First commit 2025-10-20 17:50:35 +02:00
INSTALLATION.md 10-04-2026 2026-04-10 17:18:17 +02:00
LICENSE.md First commit 2025-10-20 17:50:35 +02:00
MIGRATION.md 10-04-2026 2026-04-10 17:18:17 +02:00
README-FILE-UPLOAD.md 10-04-2026 2026-04-10 17:18:17 +02:00
README.md 10-04-2026 2026-04-10 17:18:17 +02:00
SETUP.md 10-04-2026 2026-04-10 17:18:17 +02:00

Flux CMS — Content Management für Laravel + Livewire + Flux UI

Überblick

Flux CMS ist ein modulares, mehrsprachiges Content-Management-System für Laravel-Anwendungen. Es nutzt Livewire Volt (Functional API) und Flux UI Pro als Admin-Oberfläche und speichert alle Inhalte übersetzbar in der Datenbank (spatie/laravel-translatable).

Kernkonzept: Bestehende Inhalte aus Laravel lang/-Dateien werden in die Datenbank migriert. Ein cms() Helper mit Fallback auf __() sorgt für nahtlose Migration — Seiten funktionieren sofort, auch wenn noch nicht alle Inhalte in der DB sind.

Tech-Stack

  • Laravel 12, Livewire 3, Volt (Functional API)
  • Flux UI Pro v2 (Komponenten-Bibliothek)
  • Tailwind CSS v4
  • spatie/laravel-translatable für Mehrsprachigkeit
  • intervention/image v3 für Bildverarbeitung
  • Pest v4 für Tests

Architektur

┌──────────────────────────────────────────────────────┐
│  Frontend (Blade/Livewire Views)                     │
│  {{ cms('welcome.hero.heading') }}                   │
│  {{ cms_media_url('welcome.hero.image') }}           │
│  {{ media_url('keyvisual.webp', 'hero') }}           │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│  Helper-Funktionen                                    │
│  cms()          → CmsContentService (DB + Cache)     │
│  cms_media_url() → CmsContent → CmsMedia → URL      │
│  media_url()     → CmsMedia → URL (mit Cache)        │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│  Datenbank (flux_cms_* Tabellen)                     │
│  CmsContent, CmsMedia, CmsNewsItem, CmsIndustry,    │
│  CmsFaq, CmsLinkedinPost, CmsDownload                │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│  Media Library (Storage / public disk)               │
│  cms/media/originals/  → Original-Uploads            │
│  cms/media/conversions/ → Generierte Bildgrößen      │
│  cms/media/thumbnails/  → Auto-generierte Thumbnails │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│  Admin UI (/admin/cms/*)                             │
│  Livewire Volt + Flux UI Pro                         │
│  Content-Editor, Medienbibliothek, Downloads,        │
│  Team, News, LinkedIn, FAQs, Industries              │
└──────────────────────────────────────────────────────┘

Datenbankmodelle

CmsContent (Kern)

Flexibler Key-Value-Store für alle Seiteninhalte.

Feld Typ Beschreibung
group string Seiten-/Dateiname (z.B. welcome, about, footer)
key string Dot-Notation-Pfad (z.B. hero.heading, cta.description)
type enum text, html, image, json, link
value JSON Übersetzbar: {"de": "...", "en": "..."}
order int Sortierung (chronologisch nach Dateistruktur)

CmsMedia (Medienbibliothek)

Zentrale Verwaltung aller Bilder und PDFs.

Feld Typ Beschreibung
filename string Original-Dateiname (z.B. keyvisual.webp)
path string Storage-Pfad (z.B. cms/media/originals/abc123.webp)
type string image, pdf, document
mime_type string MIME-Typ
file_size int Dateigröße in Bytes
original_width int Breite bei Bildern
original_height int Höhe bei Bildern
disk string Storage-Disk (default: public)
collection string Optionale Sammlung/Ordner
conversions JSON Generierte Bildgrößen-Pfade
title JSON Übersetzbar
alt_text JSON Übersetzbar
is_published bool Veröffentlichungsstatus

CmsDownload (Case Studies, Capabilities, Success Stories)

Vollständiges Download-Management mit sprachspezifischen PDFs.

Feld Typ Beschreibung
title JSON Übersetzbar
description JSON Übersetzbar
category string case_study, capability, success_story
icon string Heroicon-Name
sub_category string Detail-Kategorie (z.B. "R&D Product Support")
type_label JSON Übersetzbar (z.B. "Case Study" / "Capability")
alt JSON Übersetzbar: Alt-Text für Vorschaubild
file_path JSON Übersetzbar: PDF-Dateiname pro Sprache (DE/EN)
thumbnail string Vorschaubild-Dateiname (CmsMedia)
open_text JSON Übersetzbar: Button-Text "PDF öffnen"
download_text JSON Übersetzbar: Button-Text "PDF downloaden"
highlights JSON Kennzahlen [{"value": "100%", "label": "..."}]
checkpoints JSON Checkpunkte [{"value": "..."}]

Weitere Modelle

Modell Tabelle Zweck
CmsNewsItem flux_cms_news_items News-Band mit Icon, PDF, Datum, Bild (via CmsMedia)
CmsIndustry flux_cms_industries Industries-Band mit Sortierung
CmsFaq flux_cms_faqs FAQ-Einträge mit Kategorie und Hilfe-Text
CmsLinkedinPost flux_cms_linkedin_posts LinkedIn-Posts mit Bild (via CmsMedia)
CmsSearchIndex flux_cms_search_index Seitensuche: Keywords, Titel, Beschreibungen pro Route

Alle Modelle nutzen Spatie\Translatable\HasTranslations für DE/EN (erweiterbar).

CmsSearchIndex (Seitensuche)

Zentrales Index-Modell für die Frontend-Suche. Jeder Eintrag repräsentiert eine Seite/Route.

Feld Typ Beschreibung
item_id string Eindeutige ID (z.B. home, leistungen)
route string Laravel Named Route (z.B. home, leistungen)
route_params JSON Route-Parameter als Array
category JSON Übersetzbar: Kategorie (z.B. Startseite, Leistungen)
title_key string CMS-Key für Titel (löst via cms() auf)
title_fallback JSON Übersetzbar: Statischer Fallback-Titel
description_key string CMS-Key für Beschreibung
description_fallback_key string Sekundärer CMS-Key als Fallback
description_fallback_text JSON Übersetzbar: Statischer Fallback-Text
keywords JSON Übersetzbar: Array aus Keywords oder CMS-Keys (Dot-Notation)
is_published bool Veröffentlichungsstatus

Keywords: Können direkte Texte oder CMS-Keys (mit Punkt) sein. CMS-Keys werden automatisch aufgelöst.

// toFrontendArray() löst alle Keys auf
$entries = CmsSearchIndex::published()->ordered()->get()
    ->map(fn ($entry) => $entry->toFrontendArray())
    ->toArray();

Medienbibliothek

Konzept

Alle Bilder und PDFs werden zentral in einer Medienbibliothek verwaltet. Anstatt Dateien direkt an Inhalte hochzuladen, werden sie aus der Bibliothek ausgewählt. Vorteile:

  • Zentrale Verwaltung aller Assets
  • Automatische Bildoptimierung (Resize, Kompression, Formatkonvertierung)
  • Wiederverwendung von Medien über verschiedene Inhalte hinweg
  • Sprachunabhängige Bilder, sprachspezifische PDFs

Bildprofile (Conversions)

Definiert in config/flux-cms.php:

'media' => [
    'profiles' => [
        'hero'      => ['width' => 1920, 'height' => 800,  'format' => 'webp', 'quality' => 85],
        'card'      => ['width' => 768,  'height' => 512,  'format' => 'webp', 'quality' => 80],
        'thumbnail' => ['width' => 400,  'height' => 300,  'format' => 'webp', 'quality' => 75],
        'avatar'    => ['width' => 400,  'height' => 400,  'format' => 'webp', 'quality' => 80],
        'news'      => ['width' => 1200, 'height' => 630,  'format' => 'webp', 'quality' => 80],
        'thumb'     => ['width' => 200,  'height' => 200,  'format' => 'webp', 'quality' => 70],
    ],
],

Conversions werden automatisch beim Upload (Thumbnail) oder on-demand im Admin generiert.

MediaConversionService

$service = app(MediaConversionService::class);

// Einzelne Conversion
$service->convert($media, 'hero');

// Alle Conversions
$service->generateAllConversions($media);

// Thumbnail automatisch
$service->generateThumbnail($media);

Komponenten

Komponente Typ Beschreibung
MediaLibraryUploader Class-based Livewire Multi-File Drag&Drop Upload via flux:file-upload
MediaPicker Class-based Livewire Wiederverwendbares Modal zur Medienauswahl

MediaPicker-Verwendung:

<livewire:admin.cms.media-picker
    :value="$imageMediaId"
    field="news_image"
    type="image"
    profile="news"
    label="Bild wählen"
    :key="'img-' . $editingId" />

Feuert media-selected Event mit field, mediaId, url:

on(['media-selected' => function (string $field, ?int $mediaId, ?string $url) {
    if ($field === 'news_image') {
        $this->imageMediaId = $mediaId;
        $media = $mediaId ? CmsMedia::find($mediaId) : null;
        $this->image = $media ? $media->filename : '';
    }
}]);

Helper-Funktionen

cms() — Content abrufen

$value = cms('welcome.hero.heading');
$value = cms('key', ['highlight' => '<span>...</span>']);
$value = cms('key', locale: 'en');

Fallback-Kette: DB (mit Cache) → __() (Laravel-Translation)

cms_media_url() — Bild über CmsContent-Key

// Holt Dateiname aus CmsContent, löst über CmsMedia auf
$url = cms_media_url('welcome.hero.image');
$url = cms_media_url('welcome.hero.image', 'hero'); // mit Conversion-Profil

media_url() — Bild über Dateiname

// Löst CmsMedia-Dateiname direkt auf (mit In-Memory-Cache)
$url = media_url('keyvisual.webp');
$url = media_url('keyvisual.webp', 'hero'); // mit Conversion-Profil

Fallback: asset('assets/images/' . $filename) wenn nicht in CmsMedia.

tcms() — Content mit Tooltip-Verarbeitung

$text = tcms('welcome.hero.heading');

Dateien-Übersicht

Package-Kern (package/flux-cms/core/)

src/
├── Models/
│   ├── CmsContent.php           # Key-Value Content-Store
│   ├── CmsMedia.php             # Medienbibliothek-Einträge
│   ├── CmsNewsItem.php          # News (mit toFrontendArray())
│   ├── CmsDownload.php          # Downloads (mit toFrontendArray())
│   ├── CmsIndustry.php          # Industries
│   ├── CmsFaq.php               # FAQs
│   └── CmsLinkedinPost.php      # LinkedIn-Posts
├── Services/
│   ├── CmsContentService.php    # Content-Abruf mit Caching + Fallback
│   └── MediaConversionService.php # Bildverarbeitung (intervention/image)
├── Helpers/
│   ├── cms_helpers.php           # cms() und tcms() Helper
│   ├── MediaLibraryUploader.php  # Multi-File Upload Komponente
│   └── MediaPicker.php           # Medienauswahl-Modal
└── FluxCmsServiceProvider.php    # Package-Registrierung

database/
├── migrations/
│   ├── ..._create_flux_cms_contents_table.php
│   ├── ..._create_flux_cms_downloads_table.php
│   ├── ..._create_flux_cms_linkedin_posts_table.php
│   ├── ..._create_flux_cms_faqs_table.php
│   ├── ..._create_flux_cms_news_items_table.php
│   ├── ..._create_flux_cms_industries_table.php
│   └── ..._create_flux_cms_media_table.php
└── seeders-reference/

config/
└── flux-cms.php   # Locales, Cache, Media-Profiles, Auth

Projekt-Integration (Host-App)

app/
├── helpers.php                     # cms(), tcms(), cms_media_url(), media_url()
└── Livewire/Admin/Cms/
    ├── MediaLibraryUploader.php    # Multi-File Upload
    └── MediaPicker.php             # Medienauswahl-Modal

resources/views/
├── livewire/admin/cms/             # Volt-Admin-Komponenten
│   ├── content-index.blade.php     # Inhalte-Editor (text/html/image/json)
│   ├── news-index.blade.php        # News-Verwaltung
│   ├── downloads-index.blade.php   # Downloads (Case Studies/Capabilities/Stories)
│   ├── team-index.blade.php        # Team-Verwaltung
│   ├── linkedin-index.blade.php    # LinkedIn-Posts
│   ├── industries-index.blade.php  # Industries
│   ├── faqs-index.blade.php        # FAQ-Verwaltung
│   ├── media-index.blade.php       # Medienbibliothek
│   ├── media-library-uploader.blade.php
│   ├── media-picker.blade.php
│   └── dashboard-index.blade.php   # CMS-Dashboard
└── components/layouts/
    └── cms.blade.php               # CMS-Admin-Layout mit Sidebar

database/
├── migrations/
│   ├── ..._add_detail_columns_to_flux_cms_downloads_table.php
│   └── ..._change_file_path_to_json_on_flux_cms_downloads_table.php
└── seeders/
    ├── CmsContentSeeder.php        # Lang → DB Migration
    ├── CmsMediaSeeder.php          # Medien-Einträge + CmsContent image-Typen
    ├── CmsDownloadSeeder.php       # Case Studies, Capabilities, Success Stories
    ├── CmsNewsItemSeeder.php       # News-Band (mit Media-Filename-Mapping)
    ├── CmsLinkedinPostSeeder.php   # LinkedIn-Posts (mit Media-Filenames)
    ├── CmsIndustrySeeder.php
    └── CmsFaqSeeder.php

routes/web.php                      # CMS-Routes unter /admin/cms/*

Admin-Bereiche

Inhalte (/admin/cms/content)

  • Alle CmsContent-Einträge nach Gruppen
  • Text/HTML: Inline-Editor (Flux Editor) mit konfigurierbaren Toolbars
  • Image: MediaPicker mit Vorschau
  • JSON: Modal-Editor mit strukturierter Bearbeitung (Icons, Editor, Inputs)
  • Sprachumschaltung DE/EN

Medienbibliothek (/admin/cms/media)

  • Grid- und Listenansicht (Toggle)
  • Filter nach Typ (Bilder/PDFs/Dokumente), Sammlung, Dateiname
  • Multi-File Drag&Drop Upload
  • Detail-Sidebar: Metadaten, Alt-Text, Titel, Sammlung
  • Bildgrößen-Management: Einzelne oder alle Conversions generieren
  • PDF-Vorschau: Eingebettete PDF-Darstellung via iframe
  • Typ-Icons: Farbige Icons (blau=Bild, rot=PDF) neben Dateinamen

Downloads (/admin/cms/downloads)

  • Drei Kategorien: Case Studies, Capabilities, Success Stories
  • Kategoriefilter-Buttons
  • Vollständige Bearbeitung: Titel, Unterkategorie, Typ-Label, Alt-Text, Icon, Beschreibung
  • Vorschaubild: MediaPicker mit Bildvorschau
  • Sprachspezifische PDFs: Separater PDF-Upload pro Sprache (DE/EN) mit Vorschau
  • Highlights-Editor (Case Studies/Success Stories): Dynamische Kennzahl-Paare
  • Checkpoints-Editor (Capabilities): Dynamische Checkpunkte
  • Sortierung mit Pfeiltasten
  • toFrontendArray() für nahtlose Frontend-Integration

News-Band (/admin/cms/news)

  • CRUD mit Icon (Heroicon-Select + Vorschau), Datum, Autor
  • Bild: MediaPicker mit Vorschau in Liste und Formular
  • PDF: MediaPicker mit eingebetteter PDF-Vorschau
  • Rich-Text-Editor für Kurztext und Inhalt
  • Sprachumschaltung

Team (/admin/cms/team)

  • CRUD für Teammitglieder (JSON in CmsContent)
  • Profilbild: MediaPicker mit Vorschau (rund)
  • Name, Position, Kürzel, LinkedIn-URL, Kurzvorstellung
  • Profil-Text mit Rich-Text-Editor

LinkedIn-Posts (/admin/cms/linkedin)

  • CRUD mit Bild über MediaPicker (nicht mehr Text-Input)
  • Titel, Autor, Datum, URL, Tags
  • Rich-Text-Editor für Kurztext und Inhalt
  • Bildvorschau in Liste und Formular

Industries (/admin/cms/industries)

  • CRUD mit Sortierung (Pfeiltasten + numerisches Order-Feld)

FAQs (/admin/cms/faqs)

  • CRUD nach Kategorien
  • Rich-Text-Editor für Frage und Antwort

Suchindex (/admin/cms/search-index)

  • Zwei-Spalten-Layout: Liste links, Editor rechts
  • Jeder Eintrag = eine Seite/Route (z.B. home, leistungen)
  • Keywords können direkte Texte oder CMS-Keys (Dot-Notation) sein
  • Reindexieren-Button löst search:extract-keywords Artisan-Befehl aus
  • Vorschau zeigt aufgelöste Titel/Beschreibung/Keywords pro Sprache
  • Sprachumschaltung DE/EN

Content-Typen & Editor-Konfiguration

Text-Felder

Standard-Editor mit bold italic Toolbar. Im Admin automatisch erkannt.

HTML-Felder (Datenschutz, Impressum)

Voller Editor mit heading | bold italic underline strike | bullet ordered blockquote | link.

Image-Felder

MediaPicker mit Vorschau. Speichert CmsMedia-Dateiname als Wert.

JSON-Felder

Öffnen ein Modal mit strukturierter Bearbeitung:

  • Erkennung ob String-Array oder Objekt-Array
  • Icon-Felder → Searchable Heroicon-Select (nur Vorschau des gewählten Icons)
  • Text-Felder (description, content, quote) → Rich-Text-Editor
  • Verschachtelte JSON → Textarea
  • Standard-Felder → Input

:highlight Pattern

Für gradient-gestylte Textteile in Headings:

// Template
{!! cms('welcome.solutions.heading', [
    'highlight' => '<span class="text-gradient-premium">' . cms('welcome.solutions.heading_highlight') . '</span>',
]) !!}

Medien-Integration im Frontend

Bilder über CmsContent-Keys (dynamisch austauschbar)

<img src="{{ cms_media_url('welcome.hero.image') }}" />
<img src="{{ cms_media_url('welcome.hero.image', 'hero') }}" /> {{-- mit Conversion --}}

Bilder über Dateiname (direkt)

<img src="{{ media_url('keyvisual.webp') }}" />
<img src="{{ media_url($profile['image']) }}" /> {{-- z.B. Team-Profil --}}

Downloads (toFrontendArray)

@foreach (CmsDownload::published()->byCategory('case_study')->ordered()->get() as $dl)
    <x-download-article-card :article="$dl->toFrontendArray()" :index="$loop->index" />
@endforeach

News-Band (toFrontendArray)

@php
    $newsItems = CmsNewsItem::published()->ordered()->get()
        ->map(fn ($item) => $item->toFrontendArray())
        ->toArray();
@endphp

Die toFrontendArray() Methoden lösen media_url() automatisch auf — Bilder und PDFs sind sofort verlinkt.


Seeders

Ausführungsreihenfolge

$this->call([
    CmsContentSeeder::class,      // Hauptinhalte aus lang/ Dateien
    CmsMediaSeeder::class,         // Medien-Einträge + Image-Content
    CmsNewsItemSeeder::class,      // News-Band
    CmsIndustrySeeder::class,      // Industries
    CmsFaqSeeder::class,           // FAQs
    CmsLinkedinPostSeeder::class,  // LinkedIn-Posts
    CmsDownloadSeeder::class,      // Case Studies, Capabilities, Stories
]);

Medien-Seeder

Der CmsMediaSeeder erfüllt zwei Aufgaben:

  1. Erstellt CmsMedia-Einträge für alle hochgeladenen Dateien (Bilder + PDFs)
  2. Erstellt CmsContent-Einträge vom Typ image (z.B. welcome.hero.imagekeyvisual-small.webp)

Download-Seeder

Enthält inline alle 12 Einträge (4 Case Studies, 5 Capabilities, 3 Success Stories) mit:

  • Bilingualen Texten (DE/EN)
  • Sprachspezifischen PDF-Dateinamen (DE/EN)
  • Media-Dateinamen (statt alter Pfade)
  • Highlights / Checkpoints

Dateiname-Mapping

Die Seeder für News und LinkedIn mappen alte Pfade auf Media-Dateinamen:

// Alt: '/assets/images/capability-global-player.jpg'
// Neu: 'capability-global-player.webp' (CmsMedia-Dateiname)

Installation

→ Siehe SETUP.md für eine vollständige Schritt-für-Schritt-Anleitung. Für die Migration in ein bestehendes Projekt: MIGRATION.md.

Kurzfassung

  1. Package einbinden (composer.json Repository + require)
  2. composer update flux-cms/core
  3. php artisan vendor:publish --tag=flux-cms-config
  4. php artisan migrate
  5. Helper-Funktionen in app/helpers.php + composer.json autoload
  6. Admin-Views aus admin-reference/ kopieren
  7. Seeders kopieren & ausführen
  8. Routes registrieren
  9. intervention/image installieren: composer require intervention/image

Features-Übersicht

Feature Status Beschreibung
Mehrsprachige Inhalte DE/EN, erweiterbar
Content-Editor (Text/HTML/Image) Flux Editor mit konfigurierbaren Toolbars + MediaPicker
JSON-Modal-Editor Strukturierte Bearbeitung von Arrays
Heroicon-Select Searchable mit Vorschau (performant)
Medienbibliothek Zentrale Bild/PDF-Verwaltung mit Grid+Listenansicht
Bildoptimierung Automatische Conversions (Resize/Format/Qualität)
MediaPicker Wiederverwendbares Modal für Medienauswahl
PDF-Vorschau Eingebettete PDF-Darstellung im Admin
Downloads (erweitert) Case Studies, Capabilities, Success Stories mit Highlights/Checkpoints
Sprachspezifische PDFs Separate DE/EN PDFs pro Download
News-Verwaltung CRUD mit MediaPicker für Bild + PDF
Team-Verwaltung CRUD mit MediaPicker für Profilbilder
LinkedIn-Posts CRUD mit MediaPicker für Bilder
Industries-Verwaltung CRUD mit Sortierung
FAQ-Verwaltung CRUD nach Kategorien
Toast-Benachrichtigungen Flux Toast bei allen Aktionen
Fallback auf lang/ cms() → __() wenn nicht in DB
Cache Pro Gruppe, auto-invalidiert
Seeder mit cleanHtml() Automatische HTML-Bereinigung
Media-Seeder Wiederherstellung aller Medien nach DB-Reset
Suchindex Keywords, Titel, Beschreibungen pro Route mit CMS-Key-Auflösung

Offene Punkte / Roadmap

  • Drag & Drop Sortierung (Alternative zu Pfeilen)
  • Versionierung von Content-Änderungen
  • Rollen-basierter Zugriff auf CMS-Bereiche
  • Import/Export von Inhalten
  • Automatischer LinkedIn-API-Import
  • Bildbearbeitung (Crop/Rotate) im Admin