Umsetzung von AP0-AP6 aus dev/2026-08-28/entwicklungsplan.md sowie der vorab gefundenen Befunde T1 und B1-B13. Befunde - CmsContentSeeder überschrieb bei jedem Lauf alle redaktionellen Inhalte; jetzt Redaktionsschutz plus gezielter Relaunch-Seeder (B1) - Magazin-CMS war nicht ans Frontend angebunden, Artikel jetzt über Slugs mit 301 von den alten numerischen URLs (B2) - Vier veröffentlichte Display-Playlists zeigten auf b2in.test und wären live nicht ladbar gewesen (B13) - Info-Tablet speicherte geleerte Öffnungszeiten nicht (T1) - /service und /portfolio lieferten seit Februar 500 (B12) Website - Startseite neu strukturiert und umgetextet, eigene Seite /marken mit Detailseite je Kernmarke, /netzwerk und Altrouten leiten dorthin - Galerie je Marke, getrennt nach realisierten Projekten und Visualisierungen; ohne gesetzte Kategorie gilt ein Bild als Entwurf - Seite /referenzen, gespeist aus den Markengalerien - Showroom-Display im 9:16-Rahmen über eine schreibgeschützte Embed-Route ohne Preview-Token - Terminanfrage-Widget auf allen Seiten, Markenauswahl aus dem CMS, WhatsApp per Klick-zum-Chat-Link mit vorbereitetem Text - FAQ, Über-uns, Kontakt und Magazin inhaltlich auf Interior umgestellt Sonstiges - Vorschau-Instanzen liefern noindex und verlinken sich bei unbekanntem Host auf sich selbst statt auf eine fremde Domain - Seeder-Zugangsdaten aus dem Code in die Konfiguration (B8) - Ablauf für die Vorschau: dev/2026-08-28/preview-deployment.md 833 Tests grün (3084 Assertions), Pint sauber. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
160 lines
5 KiB
PHP
160 lines
5 KiB
PHP
<?php
|
||
|
||
namespace Database\Seeders;
|
||
|
||
use FluxCms\Core\Models\CmsContent;
|
||
use FluxCms\Core\Services\CmsContentService;
|
||
use Illuminate\Database\Seeder;
|
||
use Illuminate\Support\Facades\Lang;
|
||
|
||
class CmsContentSeeder extends Seeder
|
||
{
|
||
/**
|
||
* Überschreibt bestehende CMS-Einträge mit den Werten aus den Lang-Dateien.
|
||
*
|
||
* Standardmäßig aus, damit ein erneuter Seeder-Lauf die im Admin gepflegten Inhalte
|
||
* nicht zurücksetzt. Nur einschalten, wenn die Lang-Dateien bewusst als Quelle der
|
||
* Wahrheit gelten sollen – vorher ein DB-Backup ziehen:
|
||
*
|
||
* $seeder = new CmsContentSeeder;
|
||
* $seeder->overwriteExisting = true;
|
||
* $seeder->run();
|
||
*/
|
||
public bool $overwriteExisting = false;
|
||
|
||
/**
|
||
* Nur diese Section-Keys verarbeiten (leer = alle).
|
||
*
|
||
* Erlaubt einen gezielten Lauf für einzelne Sektionen, ohne den Rest anzufassen.
|
||
*
|
||
* @var array<int, string>
|
||
*/
|
||
public array $onlySections = [];
|
||
|
||
/**
|
||
* Sections to skip (not migrated, handled elsewhere or irrelevant).
|
||
*
|
||
* netzwerk_cabinet_partner / netzwerk_immobilien_hint: siehe NetzwerkPageCmsSeeder
|
||
* (nur einfügen wenn leer, damit manuelle CMS-Anpassungen nicht überschrieben werden).
|
||
*
|
||
* @var array<string>
|
||
*/
|
||
protected array $skipSections = [
|
||
'netzwerk_cabinet_partner',
|
||
'netzwerk_immobilien_hint',
|
||
];
|
||
|
||
public function run(): void
|
||
{
|
||
/** @var array<string, array{0: string, 1: string}> $sectionMap */
|
||
$sectionMap = config('cms_section_map.sections', []);
|
||
/** @var array<string, string> $langKeys */
|
||
$langKeys = config('cms_section_map.lang_keys', []);
|
||
|
||
/** @var array<string, mixed> $configTheme */
|
||
$configTheme = config('content.themes.b2in', []);
|
||
/** @var array<string, mixed> $langDe */
|
||
$langDe = Lang::get('b2in.themes.b2in', [], 'de');
|
||
/** @var array<string, mixed> $langEn */
|
||
$langEn = Lang::get('b2in.themes.b2in', [], 'en');
|
||
|
||
if (! is_array($langDe) || empty($langDe)) {
|
||
$this->command?->warn('No b2in theme found in resources/lang/de/b2in.php (key: b2in.themes.b2in) – skipping.');
|
||
|
||
return;
|
||
}
|
||
|
||
if (! is_array($langEn)) {
|
||
$langEn = [];
|
||
}
|
||
|
||
/** @var array<string, mixed> $themeDe */
|
||
$themeDe = array_merge($configTheme, $langDe);
|
||
/** @var array<string, mixed> $themeEn */
|
||
$themeEn = array_merge($configTheme, $langEn);
|
||
|
||
$created = 0;
|
||
$updated = 0;
|
||
$skipped = 0;
|
||
|
||
foreach ($sectionMap as $sectionKey => $groupKeyPair) {
|
||
if ($this->onlySections !== [] && ! in_array($sectionKey, $this->onlySections, true)) {
|
||
continue;
|
||
}
|
||
|
||
if ($this->onlySections === [] && in_array($sectionKey, $this->skipSections, true)) {
|
||
continue;
|
||
}
|
||
|
||
$langSectionKey = $langKeys[$sectionKey] ?? $sectionKey;
|
||
|
||
if (! array_key_exists($langSectionKey, $themeDe)) {
|
||
$this->command?->warn("Unmapped or missing theme data for section: {$sectionKey} (lang key: {$langSectionKey}) – skipping.");
|
||
|
||
continue;
|
||
}
|
||
|
||
$sectionDataDe = $themeDe[$langSectionKey];
|
||
$sectionDataEn = $themeEn[$langSectionKey] ?? $sectionDataDe;
|
||
|
||
[$group, $key] = $groupKeyPair;
|
||
$type = $this->detectType($sectionDataDe);
|
||
|
||
$content = CmsContent::query()
|
||
->where('group', $group)
|
||
->where('key', $key)
|
||
->first();
|
||
|
||
$value = [
|
||
'de' => $sectionDataDe,
|
||
'en' => $sectionDataEn,
|
||
];
|
||
|
||
if ($content) {
|
||
if (! $this->overwriteExisting) {
|
||
$skipped++;
|
||
|
||
continue;
|
||
}
|
||
|
||
$content->update([
|
||
'type' => $type,
|
||
'value' => $value,
|
||
]);
|
||
$updated++;
|
||
} else {
|
||
CmsContent::query()->create([
|
||
'group' => $group,
|
||
'key' => $key,
|
||
'type' => $type,
|
||
'value' => $value,
|
||
'order' => 0,
|
||
]);
|
||
$created++;
|
||
}
|
||
}
|
||
|
||
$this->command?->info("CmsContent: {$created} created, {$updated} updated, {$skipped} kept (existing CMS content).");
|
||
|
||
if ($skipped > 0 && ! $this->overwriteExisting) {
|
||
$this->command?->comment('Bestehende CMS-Inhalte wurden nicht angetastet. Zum Überschreiben $overwriteExisting = true setzen.');
|
||
}
|
||
|
||
if ($created > 0 || $updated > 0) {
|
||
app(CmsContentService::class)->clearCache();
|
||
}
|
||
}
|
||
|
||
private function detectType(mixed $data): string
|
||
{
|
||
if (is_array($data)) {
|
||
return 'json';
|
||
}
|
||
|
||
if (is_string($data) && preg_match('/<[^>]+>/', $data)) {
|
||
return 'html';
|
||
}
|
||
|
||
return 'text';
|
||
}
|
||
}
|