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>
187 lines
5.5 KiB
PHP
187 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use FluxCms\Core\Models\CmsContent;
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* Seitenbaum für den Inhalte-Editor (config/cms_pages.php, Nachtrag 12).
|
|
*
|
|
* Verbindet die Registry mit dem Datenbestand: je Sektion, ob sie in flux_cms_contents liegt
|
|
* (dann mit `content_id`), und was sonst noch in der Datenbank steht, aber in keiner Seite
|
|
* eingetragen ist („Nicht mehr verwendet“).
|
|
*/
|
|
class CmsPageTree
|
|
{
|
|
/** Schlüssel des Sammelpunkts für nicht eingetragene Sektionen. */
|
|
public const UNLISTED = 'unlisted';
|
|
|
|
/**
|
|
* Alle Seiten mit ihren Sektionen in Website-Reihenfolge.
|
|
*
|
|
* @return array<string, array{title: string, path: ?string, sections: list<array<string, mixed>>}>
|
|
*/
|
|
public function pages(): array
|
|
{
|
|
$rows = CmsContent::query()
|
|
->get(['id', 'group', 'key'])
|
|
->keyBy(fn (CmsContent $row) => $row->group.'/'.$row->key);
|
|
|
|
$pages = [];
|
|
|
|
foreach (config('cms_pages.pages', []) as $pageKey => $page) {
|
|
$sections = [];
|
|
|
|
foreach ($page['sections'] ?? [] as $section) {
|
|
$id = $section['group'].'/'.$section['key'];
|
|
|
|
$sections[] = array_merge($section, [
|
|
'page' => $pageKey,
|
|
'path' => $section['path'] ?? $page['path'] ?? null,
|
|
'anchor' => $section['anchor'] ?? null,
|
|
'content_id' => $rows[$id]->id ?? null,
|
|
'theme_key' => $this->themeKey($section['group'], $section['key']),
|
|
]);
|
|
}
|
|
|
|
$pages[$pageKey] = [
|
|
'title' => $page['title'],
|
|
'path' => $page['path'] ?? null,
|
|
'sections' => $sections,
|
|
];
|
|
}
|
|
|
|
$unlisted = $this->unlistedSections($rows->keys()->all(), $rows);
|
|
|
|
if ($unlisted !== []) {
|
|
$pages[self::UNLISTED] = [
|
|
'title' => config('cms_pages.unlisted.title', 'Nicht mehr verwendet'),
|
|
'path' => null,
|
|
'sections' => $unlisted,
|
|
];
|
|
}
|
|
|
|
return $pages;
|
|
}
|
|
|
|
/**
|
|
* Eine Sektion aus der Registry samt Datenbankbezug, oder null.
|
|
*
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
public function section(string $group, string $key): ?array
|
|
{
|
|
foreach ($this->pages() as $page) {
|
|
foreach ($page['sections'] as $section) {
|
|
if ($section['group'] === $group && $section['key'] === $key) {
|
|
return $section;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Klartext-Beschriftung eines Feldes: Sektions-Override, dann Wörterbuch, sonst lesbar gemacht.
|
|
*/
|
|
public function fieldLabel(string $field, ?string $group = null, ?string $key = null): string
|
|
{
|
|
if ($group !== null && $key !== null) {
|
|
foreach (config('cms_pages.pages', []) as $page) {
|
|
foreach ($page['sections'] ?? [] as $section) {
|
|
if ($section['group'] === $group && $section['key'] === $key && isset($section['fields'][$field])) {
|
|
return $section['fields'][$field];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$labels = config('cms_pages.field_labels', []);
|
|
|
|
if (isset($labels[$field])) {
|
|
return $labels[$field];
|
|
}
|
|
|
|
return Str::ucfirst(str_replace('_', ' ', $field));
|
|
}
|
|
|
|
/**
|
|
* Adresse der Vorschau für eine Sektion: Frontend-Domain, Pfad, Cache-Brecher und Sprungmarke.
|
|
*/
|
|
public function previewUrl(?array $section, int $version = 0): ?string
|
|
{
|
|
$path = $section['path'] ?? null;
|
|
|
|
if ($path === null) {
|
|
return null;
|
|
}
|
|
|
|
$url = rtrim(config('domains.protocol', 'https://').config('domains.domain_b2in'), '/')
|
|
.'/'.ltrim($path, '/')
|
|
.'?cms-preview='.$version;
|
|
|
|
if (! empty($section['anchor'])) {
|
|
$url .= '#'.$section['anchor'];
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Theme-Section-Schlüssel (Lang-Datei) zu Gruppe/Key, für „Aus Vorlage anlegen“.
|
|
*/
|
|
public function themeKey(string $group, string $key): ?string
|
|
{
|
|
foreach (config('cms_section_map.sections', []) as $themeKey => $target) {
|
|
if ($target === [$group, $key]) {
|
|
return $themeKey;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $ids
|
|
* @param \Illuminate\Support\Collection<string, CmsContent> $rows
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
private function unlistedSections(array $ids, $rows): array
|
|
{
|
|
$listed = [];
|
|
|
|
foreach (config('cms_pages.pages', []) as $page) {
|
|
foreach ($page['sections'] ?? [] as $section) {
|
|
$listed[$section['group'].'/'.$section['key']] = true;
|
|
}
|
|
}
|
|
|
|
$out = [];
|
|
|
|
foreach ($ids as $id) {
|
|
if (isset($listed[$id])) {
|
|
continue;
|
|
}
|
|
|
|
$row = $rows[$id];
|
|
|
|
$out[] = [
|
|
'group' => $row->group,
|
|
'key' => $row->key,
|
|
'page' => self::UNLISTED,
|
|
'path' => null,
|
|
'anchor' => null,
|
|
'title' => $row->group.' / '.$row->key,
|
|
'description' => null,
|
|
'content_id' => $row->id,
|
|
'theme_key' => null,
|
|
];
|
|
}
|
|
|
|
usort($out, fn (array $a, array $b) => [$a['group'], $a['key']] <=> [$b['group'], $b['key']]);
|
|
|
|
return $out;
|
|
}
|
|
}
|