Put opening hours, service area and FAQ into the structured data
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

Settings gain opening hours per weekday and a list of places served;
LocalBusiness emits openingHoursSpecification (equal times grouped) and
areaServed. An accordion can be marked as FAQ (schema v23 property
`faq`), which turns its entries into a FAQPage built from the visible
title and text blocks. Elektro Bernhard gets the real office hours
(Mo–Fr 07:30–17:00), the service area from Karlsruhe to Offenburg and
two more questions. ADR 094.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Fable 5.1 committed 2026-09-14 22:26:47 +02:00
1 parent 3cb5b03dd9
commit 5f7ea63b76
12 files changed
+244 -9

No files matched your search

+29
View File
@@ -257,6 +257,29 @@ final class PageController extends AbstractController
*
* @return list<array{name: string, href: string}>
*/
/** @return list<array<string, mixed>> */
private function faqEntries(array $blocks): array
{
$entries = [];
foreach ($blocks as $block) {
if ('accordion' === ($block['type'] ?? null) && true === ($block['props']['faq'] ?? false)) {
foreach ($block['children'] ?? [] as $item) {
$texts = array_filter($item['children'] ?? [], static fn (array $child): bool => 'text' === ($child['type'] ?? null));
$answer = trim(implode("\n\n", array_map(
static fn (array $child): string => trim(html_entity_decode(strip_tags(preg_replace('#<br\s*/?>|</p>#i', "\n", (string) ($child['props']['text'] ?? '')) ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8')),
$texts,
)));
if ('' !== $answer && '' !== trim((string) ($item['props']['title'] ?? ''))) {
$entries[] = ['@type' => 'Question', 'name' => $item['props']['title'], 'acceptedAnswer' => ['@type' => 'Answer', 'text' => $answer]];
}
}
}
$entries = [...$entries, ...$this->faqEntries($block['children'] ?? [])];
}
return $entries;
}
private function breadcrumbs(array $page): array
{
if ($page['path'] === $this->settings->homePath()) {
@@ -382,6 +405,12 @@ final class PageController extends AbstractController
}
$blocks[] = $article;
}
// Ein als FAQ gekennzeichnetes Akkordeon wird zur FAQPage: die Titel sind die Fragen, die
// Textbausteine im Eintrag die Antwort. Nur was auf der Seite steht, steht auch hier.
$fragen = $this->faqEntries($page['document']['blocks'] ?? []);
if ([] !== $fragen) {
$blocks[] = ['@context' => 'https://schema.org', '@type' => 'FAQPage', 'mainEntity' => $fragen];
}
$trail = $this->breadcrumbs($page);
if (count($trail) > 1) {
$blocks[] = [
+1
View File
@@ -49,6 +49,7 @@ final class SettingsController extends AbstractController
// Fuer die Farbfelder: dieselbe Palette, die auch der Editor anbietet.
'palette' => $this->theme->load()['colors'],
'socialNetworks' => SocialProfiles::NETWORKS,
'weekdays' => SiteSettings::DAYS,
'socialPlaceholders' => SocialProfiles::PLACEHOLDERS,
]);
}