Arbeitsplan 14.09.2026, Punkte 1.1 bis 1.4 (Nachträge 6 bis 10 im Entwicklungsplan): - Farbwelt Interior (Tanne, Creme, Sand, Ocker, Clay) als Standard, Immobilien-Welt behält das bisherige Blau über body.world-immobilien; Logo je Welt, Favicons neu, Welten-Wechsler in Kopf- und Fußzeile. - Saubere Trennung der Welten: eigene Navigation, Ankündigungsleiste, Magazinliste, Über-uns-Seite, FAQ, Kontaktseite und Abschluss-CTA für Immobilien; Beiträge tragen eine Welt (Migration + Admin-Feld). Abschluss-CTA Ton in Ton statt Grün-zu-Ocker. - Kontakt: Betreffe in Gruppen Interior / Immobilien / B2in, Marken darin. Terminanfrage-Buttons öffnen das Widget mit vorgewähltem Anliegen; Select-Felder einheitlich gestaltet. - Tischlerei als vierte Kernmarke (Platzhalter), Kachelraster 2×2. - Herkunfts-Sektion „Aus der Region gefertigt“ mit schematischer Standortkarte und Ablauf CABINET (Beratung Bielefeld, Fertigung Köln, Aufbau mit Tischlerei). Deployment: migrate, WorldSplitCmsSeeder, CmsBrandSeeder, OriginSectionCmsSeeder (Abschnitt 8 im Entwicklungsplan). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
208 lines
5.8 KiB
PHP
208 lines
5.8 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use Illuminate\Database\Eloquent\Builder;
|
||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||
use Illuminate\Database\Eloquent\Model;
|
||
use Spatie\Translatable\HasTranslations;
|
||
|
||
class CmsArticle extends Model
|
||
{
|
||
use HasFactory, HasTranslations;
|
||
|
||
protected $table = 'cms_articles';
|
||
|
||
/**
|
||
* Alte numerische Artikel-IDs (resources/lang/{locale}/b2in.php) → Slug.
|
||
*
|
||
* Wird an drei Stellen gebraucht: beim Anlegen durch den CmsArticleSeeder, für den
|
||
* Lang-Fallback und für die 301-Weiterleitung der alten /magazin/{id}-URLs.
|
||
*
|
||
* @var array<int, string>
|
||
*/
|
||
public const LEGACY_ID_TO_SLUG = [
|
||
1 => 'escrow-system-dubai-investoren',
|
||
2 => 'spotlight-al-jaddaf-hotspot',
|
||
3 => 'turnkey-investments-moeblierung-rendite',
|
||
4 => 'supply-chain-management-vertragssicherheit',
|
||
5 => 'local-for-local-regionaler-moebelhandel',
|
||
];
|
||
|
||
public const WORLD_INTERIOR = 'interior';
|
||
|
||
public const WORLD_IMMOBILIEN = 'immobilien';
|
||
|
||
/** @var array<int, string> */
|
||
public const WORLDS = [self::WORLD_INTERIOR, self::WORLD_IMMOBILIEN];
|
||
|
||
/**
|
||
* Beiträge aus dem Bestand, die zur Immobilien-Welt gehören (Dubai-Themen).
|
||
*
|
||
* @var array<int, string>
|
||
*/
|
||
public const LEGACY_IMMOBILIEN_SLUGS = [
|
||
'escrow-system-dubai-investoren',
|
||
'spotlight-al-jaddaf-hotspot',
|
||
'turnkey-investments-moeblierung-rendite',
|
||
];
|
||
|
||
protected $fillable = [
|
||
'slug',
|
||
'title',
|
||
'subtitle',
|
||
'image',
|
||
'category',
|
||
'world',
|
||
'date_label',
|
||
'read_time',
|
||
'author',
|
||
'content',
|
||
'is_published',
|
||
'order',
|
||
];
|
||
|
||
/** @var array<string> */
|
||
public array $translatable = [
|
||
'title',
|
||
'subtitle',
|
||
'content',
|
||
];
|
||
|
||
protected function casts(): array
|
||
{
|
||
return [
|
||
'author' => 'array',
|
||
'is_published' => 'boolean',
|
||
'order' => 'integer',
|
||
];
|
||
}
|
||
|
||
public function scopePublished(Builder $query): Builder
|
||
{
|
||
return $query->where('is_published', true);
|
||
}
|
||
|
||
public function scopeOrdered(Builder $query): Builder
|
||
{
|
||
return $query->orderBy('order')->orderByDesc('created_at');
|
||
}
|
||
|
||
public function scopeForWorld(Builder $query, string $world): Builder
|
||
{
|
||
return $query->where('world', $world);
|
||
}
|
||
|
||
/**
|
||
* Veröffentlichte Artikel fürs Frontend, nach Slug indiziert.
|
||
*
|
||
* Das CMS gewinnt. Solange keine Artikel gepflegt sind, greift die Lang-Datei als Fallback –
|
||
* dieselbe Reihenfolge wie bei cms_theme_section(), damit das Magazin auf einer frischen
|
||
* Installation nicht leer ist.
|
||
*
|
||
* Mit `$world` nur die Beiträge dieser Farbwelt (Interior oder Immobilien) – die beiden
|
||
* Magazinlisten sind getrennt, die Detailseite rendert in der Welt ihres Beitrags.
|
||
*
|
||
* @return array<string, array<string, mixed>>
|
||
*/
|
||
public static function forFrontend(?string $world = null): array
|
||
{
|
||
$maintained = static::query()->published()->exists();
|
||
|
||
if ($maintained) {
|
||
return static::query()
|
||
->published()
|
||
->when($world !== null, fn (Builder $query) => $query->forWorld($world))
|
||
->ordered()
|
||
->get()
|
||
->mapWithKeys(fn (self $article) => [$article->slug => $article->toFrontendArray()])
|
||
->all();
|
||
}
|
||
|
||
$articles = static::fallbackFromLang();
|
||
|
||
if ($world === null) {
|
||
return $articles;
|
||
}
|
||
|
||
return array_filter($articles, fn (array $article) => ($article['world'] ?? self::WORLD_INTERIOR) === $world);
|
||
}
|
||
|
||
/**
|
||
* Slug zu einer alten numerischen Magazin-ID, falls bekannt.
|
||
*/
|
||
public static function slugForLegacyId(int|string $id): ?string
|
||
{
|
||
return self::LEGACY_ID_TO_SLUG[(int) $id] ?? null;
|
||
}
|
||
|
||
/**
|
||
* Slug eines Artikels aus der Lang-Datei.
|
||
*
|
||
* Neue Artikel bringen ihren Slug selbst mit (`'slug' => …`). Die fünf ursprünglichen
|
||
* Artikel hatten keinen und werden weiter über {@see LEGACY_ID_TO_SLUG} aufgelöst – die
|
||
* Konstante bleibt damit das, was sie ist: die Karte für die alten `/magazin/{id}`-URLs.
|
||
*
|
||
* @param array<string, mixed> $article
|
||
*/
|
||
public static function slugForLangArticle(int|string $id, array $article): ?string
|
||
{
|
||
$slug = $article['slug'] ?? null;
|
||
|
||
if (is_string($slug) && $slug !== '') {
|
||
return $slug;
|
||
}
|
||
|
||
return self::slugForLegacyId($id);
|
||
}
|
||
|
||
/**
|
||
* @return array<string, array<string, mixed>>
|
||
*/
|
||
private static function fallbackFromLang(): array
|
||
{
|
||
$articles = trans('b2in.articles');
|
||
|
||
if (! is_array($articles)) {
|
||
return [];
|
||
}
|
||
|
||
$out = [];
|
||
|
||
foreach ($articles as $id => $article) {
|
||
if (! is_array($article)) {
|
||
continue;
|
||
}
|
||
|
||
$slug = self::slugForLangArticle($id, $article);
|
||
|
||
if ($slug === null) {
|
||
continue;
|
||
}
|
||
|
||
$out[$slug] = $article + ['slug' => $slug, 'world' => self::WORLD_INTERIOR];
|
||
}
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* @return array<string, mixed>
|
||
*/
|
||
public function toFrontendArray(): array
|
||
{
|
||
return [
|
||
'id' => $this->id,
|
||
'slug' => $this->slug,
|
||
'title' => $this->title,
|
||
'subtitle' => $this->subtitle,
|
||
'image' => $this->image,
|
||
'category' => $this->category,
|
||
'world' => $this->world ?? self::WORLD_INTERIOR,
|
||
'date' => $this->date_label,
|
||
'readTime' => $this->read_time,
|
||
'author' => $this->author ?? [],
|
||
'content' => $this->content ?? [],
|
||
];
|
||
}
|
||
}
|