Nachtrag 11: Herkunfts-Sektion liest Ort, Entfernung und Rolle aus der Markenverwaltung (drei neue Felder an cms_brands, BrandOriginSeeder), Kartenpositionen im Code, „Marken“ in der Sidebar. Nachtrag 12: Inhalte-Editor zeigt die Seiten der Website mit ihren Sektionen in Reihenfolge (config/cms_pages.php, CmsPageTree), Felder in Klartext, fehlende Sektionen aus der Sprachdatei anlegbar, Vorschau der echten Seite im iframe mit Sprung zur Sektion; Anker an allen lebenden Sektionen nachgezogen. Nachtrag 13: Referenzen als eigenes Modul (CmsReference, Portal-Seite, /referenzen mit Fallback auf die Markengalerien); Immobilien-Projekte aus Sidebar und CMS-Übersicht genommen, Route bleibt. Dazu .gitignore für _b2in_interior_shop/ und docs/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
115 lines
3 KiB
PHP
115 lines
3 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;
|
||
|
||
/**
|
||
* Referenzprojekt der Interior-Welt: ein realisierter Auftrag mit Titelbild, weiteren Bildern
|
||
* und den beteiligten Kernmarken (Nachtrag 13). Erscheint auf /referenzen.
|
||
*
|
||
* Nicht zu verwechseln mit {@see CmsProject} – das sind die Immobilienprojekte.
|
||
*/
|
||
class CmsReference extends Model
|
||
{
|
||
use HasFactory, HasTranslations;
|
||
|
||
protected $table = 'cms_references';
|
||
|
||
protected $fillable = [
|
||
'title',
|
||
'location',
|
||
'year',
|
||
'description',
|
||
'image',
|
||
'gallery',
|
||
'brand_slugs',
|
||
'is_published',
|
||
'order',
|
||
];
|
||
|
||
/** @var array<string> */
|
||
public array $translatable = ['title', 'description'];
|
||
|
||
protected function casts(): array
|
||
{
|
||
return [
|
||
'gallery' => 'array',
|
||
'brand_slugs' => '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')->orderBy('id');
|
||
}
|
||
|
||
/**
|
||
* Galerie in eine feste Form bringen: nur Einträge mit Bild, Bildunterschrift getrimmt.
|
||
*
|
||
* @param mixed $gallery
|
||
* @return list<array{image: string, caption: string}>
|
||
*/
|
||
public static function normalizeGallery($gallery): array
|
||
{
|
||
if (! is_array($gallery)) {
|
||
return [];
|
||
}
|
||
|
||
$out = [];
|
||
|
||
foreach ($gallery as $item) {
|
||
$image = trim((string) (is_array($item) ? ($item['image'] ?? '') : $item));
|
||
|
||
if ($image === '') {
|
||
continue;
|
||
}
|
||
|
||
$out[] = [
|
||
'image' => $image,
|
||
'caption' => is_array($item) ? trim((string) ($item['caption'] ?? '')) : '',
|
||
];
|
||
}
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Beteiligte Marken als Liste mit Name und Link, aufgelöst gegen die veröffentlichten Marken.
|
||
*
|
||
* Eine unveröffentlichte oder gelöschte Marke fällt heraus.
|
||
*
|
||
* @param array<string, array<string, mixed>> $brands Ergebnis von CmsBrand::forFrontend()
|
||
* @return list<array{slug: string, name: string, link: ?string}>
|
||
*/
|
||
public function resolveBrands(array $brands): array
|
||
{
|
||
$out = [];
|
||
|
||
foreach ($this->brand_slugs ?? [] as $slug) {
|
||
$slug = (string) $slug;
|
||
|
||
if (! isset($brands[$slug])) {
|
||
continue;
|
||
}
|
||
|
||
$out[] = [
|
||
'slug' => $slug,
|
||
'name' => (string) ($brands[$slug]['name'] ?? $slug),
|
||
'link' => ! empty($brands[$slug]['has_detail_page']) ? '/marken/'.$slug : null,
|
||
];
|
||
}
|
||
|
||
return $out;
|
||
}
|
||
}
|