b2in/app/helpers.php
Kevin Adametz 4b07bbdcae Startseite ruhiger, Otterpohl mit Montage, Marken-Bilder und Farbwelt-Entwurf
- Hinweisleiste per B2IN_ANNOUNCEMENT_BAR ausgeblendet (Standard aus)
- Hero: „Räume aus einer Hand.“ als neue Überschrift, zweiter Button entfernt
- Projekttext mit zentralem deutschen Vertragspartner (Punkt statt Gedankenstrich)
- Otterpohl als vierte Kernmarke: Text v2, Abschnitt „Montage“, Titelbild getauscht
- Markenabschnitte: Absätze mit „- “ als Liste mit fetter Einleitung
- Titelbilder und Galerien für CABINET, Rotpunkt, Febrü und Otterpohl (WebP)
- Feedback 17.09. in den Markentexten, Demobilder entfernt, WhatsApp-Nummer
- Palette „Modern“ fest ohne Schalter, Entwurf /entwurf mit Hero-Slideshow
- Seeder-Liste fürs Deployment im Entwicklungsplan

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:34:21 +00:00

285 lines
8.6 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
use FluxCms\Core\Models\CmsMedia;
use FluxCms\Core\Services\CmsContentService;
if (! function_exists('legal_page')) {
/**
* Rechtstexte: zuerst CMS (Gruppe „legal“), sonst Lang-Datei b2in_legal.
*
* @return array<string, mixed>
*/
function legal_page(string $key): array
{
$fromCms = app(CmsContentService::class)->get('legal.'.$key);
if (is_array($fromCms) && isset($fromCms['content'])) {
return $fromCms;
}
return trans('b2in_legal.'.$key);
}
}
if (! function_exists('cms')) {
/**
* Resolve a CMS content value by dot-notation key.
*
* First segment is the group, rest is the key: "home.hero.title"
* Falls back to __() if no DB entry exists.
*
* @param array<string, string> $replace
*/
function cms(string $key, array $replace = [], ?string $locale = null): mixed
{
return app(CmsContentService::class)->get($key, $replace, $locale);
}
}
if (! function_exists('cms_theme_section')) {
/**
* Theme-Sektion: zuerst CMS (gemäß cms_section_map), sonst Lang b2in.themes.{theme}.{section}.
*
* @return array<string, mixed>|string|mixed
*/
function cms_theme_section(string $sectionKey, ?string $theme = null): mixed
{
$theme = $theme ?? config('app.theme', 'b2in');
/** @var array<string, array{0: string, 1: string}> $sections */
$sections = config('cms_section_map.sections', []);
/** @var array<string, string> $langKeys */
$langKeys = config('cms_section_map.lang_keys', []);
if (isset($sections[$sectionKey])) {
[$group, $key] = $sections[$sectionKey];
$fromCms = app(CmsContentService::class)->getIfExists("{$group}.{$key}");
if ($fromCms !== null) {
return $fromCms;
}
}
$langKey = $langKeys[$sectionKey] ?? $sectionKey;
return trans("b2in.themes.{$theme}.{$langKey}");
}
}
if (! function_exists('whatsapp_link')) {
/**
* Klick-zum-Chat-Link, sofern im CMS eine Nummer hinterlegt ist (global.contact.whatsapp).
*
* Liefert einen leeren String, solange keine Nummer bestaetigt ist – der Button erscheint dann
* gar nicht erst, statt auf eine erfundene Nummer zu zeigen.
*/
function whatsapp_link(): string
{
$service = app(CmsContentService::class);
// Die Nummer kann als eigener Eintrag oder als Feld der Sektion `global.contact` liegen.
$number = $service->getIfExists('global.contact.whatsapp');
if ($number === null) {
$contact = $service->getIfExists('global.contact');
$number = is_array($contact) ? ($contact['whatsapp'] ?? null) : null;
}
if (! is_string($number)) {
return '';
}
$digits = preg_replace('/\D+/', '', $number);
return $digits === '' ? '' : 'https://wa.me/'.$digits;
}
}
if (! function_exists('theme_image_url')) {
/**
* Bild-URL für Theme-/CMS-Inhalte: zuerst Medienbibliothek (CmsMedia, oft nur Dateiname z. B. *.webp),
* sonst statische Assets unter public/img/assets/ (Legacy-Pfade wie b2in/…).
*/
function theme_image_url(?string $value): string
{
if ($value === null) {
return '';
}
$trimmed = ltrim(trim($value), '/');
if ($trimmed === '') {
return '';
}
if (str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) {
return $trimmed;
}
if (str_starts_with($trimmed, 'storage/')) {
return asset($trimmed);
}
if (str_starts_with($trimmed, 'immobile/')) {
return asset('storage/'.$trimmed);
}
$candidates = array_values(array_unique(array_filter([$trimmed, basename($trimmed)])));
foreach ($candidates as $candidate) {
if (CmsMedia::query()->where('filename', $candidate)->exists()) {
return media_url($candidate);
}
}
if (str_contains($trimmed, '/')) {
return asset('img/assets/'.$trimmed);
}
return media_url($trimmed);
}
}
if (! function_exists('tcms')) {
/**
* Typed CMS – always returns a string.
*
* @param array<string, string> $replace
*/
function tcms(string $key, array $replace = [], ?string $locale = null): string
{
$text = cms($key, $replace, $locale);
return is_string($text) ? $text : (string) $text;
}
}
if (! function_exists('cms_media_url')) {
/**
* Resolve a CMS content key (type=image) to a full media URL.
* Falls back to asset('assets/images/...') if not found in media library.
*/
function cms_media_url(string $key, string $profile = ''): string
{
$filename = cms($key);
if (! $filename || ! is_string($filename) || $filename === $key) {
$fallback = str_replace('.', '/', $key);
return asset('assets/images/'.basename($fallback));
}
return media_url($filename, $profile);
}
}
if (! function_exists('media_url')) {
/**
* Resolve a CmsMedia filename to its full storage URL.
* Uses in-memory cache to avoid repeated DB queries.
*/
function media_url(?string $filename, string $profile = ''): string
{
if (! $filename || $filename === '') {
return '';
}
static $resolved = [];
$cacheKey = $filename.'|'.$profile;
if (isset($resolved[$cacheKey])) {
return $resolved[$cacheKey];
}
$media = CmsMedia::where('filename', $filename)->first();
if (! $media) {
$resolved[$cacheKey] = asset('assets/images/'.$filename);
return $resolved[$cacheKey];
}
if ($profile && $media->hasConversion($profile)) {
$resolved[$cacheKey] = $media->getConversionUrl($profile);
} else {
$resolved[$cacheKey] = $media->getUrl();
}
return $resolved[$cacheKey];
}
}
if (! function_exists('b2in_world')) {
/**
* Farbwelt der aktuellen Seite: `interior` (Standard) oder `immobilien`.
*
* Die Immobilien-Welt umfasst die Immobilien-Routen samt Detailseiten und den
* Entwicklungs-Vorschauen. Das Layout setzt den Wert als Body-Klasse
* (`world-interior` / `world-immobilien`), theme-b2in.css schaltet daran die Farben um.
* Livewire-Komponenten merken sich die Welt in mount(), weil ein späterer
* Livewire-Request keine Seitenroute mehr trägt.
*/
function b2in_world(): string
{
if (app()->bound('b2in.world')) {
return (string) app('b2in.world');
}
$routeName = request()->route()?->getName();
if (is_string($routeName) && (str_starts_with($routeName, 'immobilien') || str_starts_with($routeName, 'dev.immobilien'))) {
return 'immobilien';
}
return 'interior';
}
}
if (! function_exists('b2in_palette')) {
/**
* Abweichende Farbpalette der aktuellen Seite – leer im Normalfall.
*
* Der Entwurf der Startseite (Nachtrag 19, /entwurf) bindet „greige“; Logo und Layout
* richten sich danach. Die Wahl „Bisher/Neu“ des Farb-Schalters läuft dagegen im Browser
* und taucht hier nicht auf.
*/
function b2in_palette(): string
{
return app()->bound('b2in.palette') ? (string) app('b2in.palette') : '';
}
}
if (! function_exists('b2in_worlds')) {
/**
* Die beiden Welten für den Welten-Wechsler in Kopf- und Fußzeile.
*
* @return array<string, array{label: string, url: string, active: bool}>
*/
function b2in_worlds(?string $current = null): array
{
$current = $current ?? b2in_world();
return [
'interior' => [
'label' => __('ui.world_interior'),
'url' => url('/'),
'active' => $current === 'interior',
],
'immobilien' => [
'label' => __('ui.world_immobilien'),
'url' => route('immobilien'),
'active' => $current === 'immobilien',
],
];
}
}
if (! function_exists('b2in_world_set')) {
/**
* Welt für den laufenden Request festlegen, wenn sie nicht an der Route hängt –
* etwa auf der Magazin-Detailseite, die in der Welt ihres Beitrags rendert.
* Muss vor dem Rendern von Kopf- und Fußzeile geschehen (beide halten die Welt in mount() fest).
*/
function b2in_world_set(string $world): void
{
app()->instance('b2in.world', $world);
}
}