b2in/packages/flux-cms/README.md
2026-04-10 17:18:17 +02:00

576 lines
22 KiB
Markdown

# 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
<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`:
```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' => '<span>...</span>']);
$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' => '<span class="text-gradient-premium">' . cms('welcome.solutions.heading_highlight') . '</span>',
]) !!}
```
---
## Medien-Integration im Frontend
### Bilder über CmsContent-Keys (dynamisch austauschbar)
```blade
<img src="{{ cms_media_url('welcome.hero.image') }}" />
<img src="{{ cms_media_url('welcome.hero.image', 'hero') }}" /> {{-- mit Conversion --}}
```
### Bilder über Dateiname (direkt)
```blade
<img src="{{ media_url('keyvisual.webp') }}" />
<img src="{{ media_url($profile['image']) }}" /> {{-- z.B. Team-Profil --}}
```
### Downloads (toFrontendArray)
```blade
@foreach (CmsDownload::published()->byCategory('case_study')->ordered()->get() as $dl)
<x-download-article-card :article="$dl->toFrontendArray()" :index="$loop->index" />
@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