Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, …)
Expand Down
31 changes: 4 additions & 27 deletions boot.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?php

use Url\Url;
use FriendsOfRedaxo\JsonLdManager\Frontend\Renderer;
use FriendsOfRedaxo\JsonLdManager\LlmsTxt;

Expand Down Expand Up @@ -42,32 +41,10 @@
return $content;
}

$jsonLdOutput = '';
$dynamicJsonLdOutput = '';

// Prüfe ob es eine dynamische URL ist (URL-Addon)
if (rex_addon::get('url')->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 <meta ...> im <head>)
$legacyMeta = trim(rex_config::get('jsonld_manager', 'legacy_meta_raw', ''));
Expand Down
6 changes: 5 additions & 1 deletion lib/DynamicContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions lib/DynamicJsonLd.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 26 additions & 5 deletions lib/JsonLdGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 [];
Expand Down Expand Up @@ -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) {
Expand Down