b2in/app/Models/CmsArticle.php
Kevin Adametz fcb10be120 Interior-Feinschliff: Hero-Slideshow, Lektorat, Rechtstexte, Aufräumen und Korrekturen 08.10.
Startseite und Heroes
- Hero-Slideshow (11 Motive inkl. Febrü) mit Infokarte, Laufzeit-Linie, größeren Klickflächen,
  Goldrahmen und mobiler Karte unter dem Bild; Zeile über der Überschrift, neue Vertrauenspunkte
- Hero-Stil auf Marken, Über uns und Kontakt übertragen, Breadcrumb entfernt
- Gründer-Leiste, Vision-Box auf Ocker, Showroom-Display in Box mit „Größer ansehen“/Vollbild
- Akzentfarbe #a8741f, größere Serif-Überschriften in der Interior-Welt
- Marken: Bildpause mit Zitat; Vereine aktualisiert und verlinkt

Inhalte
- Lektorat aller angezeigten Texte, /about-Korrekturen, Rechtstexte (DDG, TDDDG, Hosting Hetzner)
- Ablauf Maßanfertigung überall einheitlich, Magazin-Beitrag neu, alte Beiträge offline,
  deutsches Datum/Lesezeit, FAQ Projektausstattung, Otterpohl
- Referenzen-Seite per CMS-Schalter ausblendbar

CMS und Technik
- Tote Sektionen, Felder, Seiten und Komponenten entfernt und archiviert (dev/2026-08-28/archiv-*)
- Entwurf /entwurf, Farb-Schalter und /welcome entfernt; Test-Routen nur noch lokal
- Seitenbaum-Beschreibungen geprüft, fehlende CMS-Felder ergänzt, Markenliste aus „Kernmarken“
- Admin-Bildvorschauen repariert, verwendete Bilder in die Mediathek übernommen
- Seeder für das Deployment siehe dev/2026-08-28/entwicklungsplan.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:00:15 +00:00

208 lines
5.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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) || ($article['published'] ?? true) === false) {
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 ?? [],
];
}
}