Put opening hours, service area and FAQ into the structured data
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:
1 parent
3cb5b03dd9
commit
5f7ea63b76
12 files changed
+244
-9
No files matched your search
@@ -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[] = [
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user