# 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. ```php // 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`: ```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 ```php $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:** ```blade ``` Feuert `media-selected` Event mit `field`, `mediaId`, `url`: ```php 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 ```php $value = cms('welcome.hero.heading'); $value = cms('key', ['highlight' => '...']); $value = cms('key', locale: 'en'); ``` **Fallback-Kette:** DB (mit Cache) → `__()` (Laravel-Translation) ### `cms_media_url()` — Bild über CmsContent-Key ```php // 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 ```php // 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 ```php $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: ```php // Template {!! cms('welcome.solutions.heading', [ 'highlight' => '' . cms('welcome.solutions.heading_highlight') . '', ]) !!} ``` --- ## Medien-Integration im Frontend ### Bilder über CmsContent-Keys (dynamisch austauschbar) ```blade {{-- mit Conversion --}} ``` ### Bilder über Dateiname (direkt) ```blade {{-- z.B. Team-Profil --}} ``` ### Downloads (toFrontendArray) ```blade @foreach (CmsDownload::published()->byCategory('case_study')->ordered()->get() as $dl) @endforeach ``` ### News-Band (toFrontendArray) ```blade @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 ```php $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.image` → `keyvisual-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: ```php // Alt: '/assets/images/capability-global-player.jpg' // Neu: 'capability-global-player.webp' (CmsMedia-Dateiname) ``` --- ## Installation → Siehe [SETUP.md](SETUP.md) für eine vollständige Schritt-für-Schritt-Anleitung. Für die Migration in ein bestehendes Projekt: [MIGRATION.md](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