From d3f1ceecc1b94add1d0a787fd44d67f5003b2f75 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Sun, 6 Sep 2026 13:06:38 +0200 Subject: [PATCH] Dynamische URLs: Schema nur noch einmal im @graph ausgeben MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der OUTPUT_FILTER hat auf dynamischen URLs zwei Ausgaben erzeugt: das Schema im @graph über JsonLdGenerator und zusätzlich ein separates Script über generateDynamicJsonLd(). Jetzt ruft boot.php nur noch jsonld_render() auf. - generateDynamicSchema(): @id für alle Typen, image/photo/logo werden zu Medien-URLs aufgelöst, URL-Profil und Datensatz landen im Debug-Meta - DynamicContent::mediaUrl() öffentlich, gemeinsam genutzt - generateDynamicJsonLd() bleibt für manuellen Template-Einsatz Fixes #17 Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 1 + README.md | 2 ++ boot.php | 31 ++++--------------------------- lib/DynamicContent.php | 6 +++++- lib/DynamicJsonLd.php | 9 +++++++-- lib/JsonLdGenerator.php | 31 ++++++++++++++++++++++++++----- 6 files changed, 45 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adaa1bb..b850584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### Fixed - Die Live-Vorschau im Dynamische-URLs-Editor zeigt jetzt tatsächlich die Werte des ersten Datensatzes; zuvor blieben gemappte Felder wegen einer falschen Datenstruktur immer leer. +- Auf dynamischen URLs (URL-AddOn) wurde das Schema des Datensatzes doppelt ausgegeben: einmal im `@graph` über `JsonLdGenerator` und einmal als separates Script über `generateDynamicJsonLd()`. Der `OUTPUT_FILTER` ruft jetzt nur noch `jsonld_render()` auf; das dynamische Schema ersetzt das WebPage-Schema im `@graph`, erhält eine `@id` und löst `image`/`photo`/`logo`-Dateinamen zu Medien-URLs auf. Das Debug-Overlay zeigt weiterhin URL-Profil und Datensatz-ID. `generateDynamicJsonLd()` bleibt für den manuellen Einsatz in Templates erhalten (#17). ## v1.0.13 (26. August 2026) diff --git a/README.md b/README.md index bf0f287..5cb2974 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ Wenn das AddOn `URL` installiert ist und dort mindestens ein Profil angelegt wur Dort lassen sich JSON-LD-Zuordnungen für dynamische Inhalte konfigurieren, zum Beispiel für Datensätze aus YForm-Tabellen, die über URL-Profile ausgegeben werden. +Auf einer dynamischen URL ersetzt das zugeordnete Schema (z. B. `Product`) das `WebPage`-Schema im gemeinsamen `@graph` neben `Organization`, `WebSite` und `BreadcrumbList`; es wird nur einmal ausgegeben. Wer die automatische Ausgabe abgeschaltet hat, kann das Schema eines Datensatzes im Template auch einzeln mit `generateDynamicJsonLd($profileId, $dataId)` als eigenes Script-Tag ausgeben. + Das ist optional und nicht nötig, um die normale JSON-LD-Ausgabe für Website und Artikel zu verwenden. #### Strukturierte Teilobjekte (Angebot, Adresse, Öffnungszeiten, …) diff --git a/boot.php b/boot.php index fbebb79..240b90d 100644 --- a/boot.php +++ b/boot.php @@ -1,6 +1,5 @@ isAvailable()) { - try { - $urlManager = Url::resolveCurrent(); - - if ($urlManager) { - // Dynamische URL erkannt - JSON-LD für URL-Profil generieren - $profileId = $urlManager->getProfileId(); - $dataId = $urlManager->getDatasetId(); - - if ($profileId && $dataId) { - $dynamicJsonLdOutput = generateDynamicJsonLd($profileId, $dataId); - } - } - } catch (Exception $e) { - // Fehler beim URL-Parsing ignorieren - } - } - - // Standard JSON-LD immer zusätzlich ausgeben - $jsonLdOutput .= jsonld_render(); - // Dynamisches URL-JSON-LD zusätzlich anhängen (falls vorhanden) - $jsonLdOutput .= $dynamicJsonLdOutput; + // Eine Ausgabe für alles: Auf dynamischen URLs (URL-AddOn) ersetzt + // JsonLdGenerator::generateForArticle() das WebPage-Schema im @graph + // durch das Schema des URL-Profil-Mappings. + $jsonLdOutput = jsonld_render(); // Legacy-Meta-Daten ausgeben (nach letztem im ) $legacyMeta = trim(rex_config::get('jsonld_manager', 'legacy_meta_raw', '')); diff --git a/lib/DynamicContent.php b/lib/DynamicContent.php index 2b23465..ac254ce 100644 --- a/lib/DynamicContent.php +++ b/lib/DynamicContent.php @@ -268,7 +268,11 @@ private static function resolveItemUrl(array $row, array $options): ?string return null; } - private static function mediaUrl(string $value): string + /** + * Löst einen Medienpool-Dateinamen (oder eine Medialiste) zur vollständigen URL auf. + * Absolute URLs und Pfade bleiben unverändert. + */ + public static function mediaUrl(string $value): string { if (str_starts_with($value, 'http://') || str_starts_with($value, 'https://') || str_starts_with($value, '/')) { return $value; diff --git a/lib/DynamicJsonLd.php b/lib/DynamicJsonLd.php index 1d5bde9..8ecccdc 100644 --- a/lib/DynamicJsonLd.php +++ b/lib/DynamicJsonLd.php @@ -3,8 +3,13 @@ namespace FriendsOfRedaxo\JsonLdManager { /** - * Dynamisches JSON-LD für URL-Profile generieren - * + * Dynamisches JSON-LD für URL-Profile als eigenständiges Script-Tag generieren. + * + * Wird nicht mehr automatisch über den OUTPUT_FILTER aufgerufen: Die automatische + * Ausgabe läuft ausschließlich über JsonLdGenerator (Schema im @graph). Diese + * Funktion bleibt für den manuellen Einsatz in Templates erhalten, wenn die + * automatische Ausgabe deaktiviert ist. + * * @param int|string $profileId ID des URL-Profils * @param int|string $dataId ID des Datensatzes * @return string JSON-LD Script oder leer diff --git a/lib/JsonLdGenerator.php b/lib/JsonLdGenerator.php index f6b03d1..d1c7980 100644 --- a/lib/JsonLdGenerator.php +++ b/lib/JsonLdGenerator.php @@ -25,6 +25,13 @@ */ class JsonLdGenerator { + /** + * URL-Profil und Datensatz des zuletzt erzeugten dynamischen Schemas (für Debug-Meta). + * + * @var array{dynamic_profile_id: int, dynamic_data_id: int}|null + */ + private static ?array $lastDynamicContext = null; + /** * Baut die komplette JSON-LD-Ausgabe fuer einen Artikel. * Diese Methode ist die gemeinsame Quelle fuer Backend-Vorschau, AJAX und Frontend. @@ -109,6 +116,9 @@ public static function getArticleOutput(int $articleId, int|array|string|null $b if (!empty($branchNames)) { $meta['branch_names'] = $branchNames; } + if (self::$lastDynamicContext !== null) { + $meta = array_merge($meta, self::$lastDynamicContext); + } $output['meta'] = $meta; } catch (Exception $e) { $output['error'] = $e->getMessage(); @@ -344,8 +354,9 @@ public static function resolveBranchIdsForArticle(int $articleId, mixed $clangId public static function generateForArticle(int $articleId, int|array|string|null $branchId = null, bool $isDebugMode = false, mixed $clangId = null): array { if (!$articleId) return []; - + $jsonLdItems = []; + self::$lastDynamicContext = null; $effectiveClangId = self::normalizeClangId($clangId); $currentArticle = rex_article::get($articleId, $effectiveClangId); if (!$currentArticle) return []; @@ -1415,11 +1426,21 @@ private static function generateDynamicSchema(array $dynamicMapping, int $articl } } - // @ID für Verlinkungen - if ($schemaType === 'NewsArticle') { - $schema['@id'] = $schema['url'] . '#' . strtolower($schemaType); + // Medien-Dateinamen aus YForm zu vollständigen URLs auflösen + foreach (['image', 'photo', 'logo'] as $mediaProperty) { + if (isset($schema[$mediaProperty]) && is_string($schema[$mediaProperty])) { + $schema[$mediaProperty] = DynamicContent::mediaUrl($schema[$mediaProperty]); + } } - + + // @id für Verlinkungen + $schema['@id'] = $schema['url'] . '#' . strtolower($schemaType); + + self::$lastDynamicContext = [ + 'dynamic_profile_id' => (int) ($profile['id'] ?? 0), + 'dynamic_data_id' => (int) ($dataset['id'] ?? 0), + ]; + return $schema; } catch (Exception $e) {