diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a4e8bb..fa94989 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,31 @@
Dieses Changelog wird ab Version `1.0.0` neu geführt.
+## v1.0.0.beta.19 (08. Juni 2026)
+
+### Added
+- Umfassende Rexstan-Qualitätsrunde über das gesamte Addon durchgeführt und final auf fehlerfreien Stand gebracht.
+- Zusätzliche Härtung für JavaScript-Datenübergabe in der URL-Mapping-Konfiguration ergänzt: JSON-Werte werden jetzt vorab berechnet und mit sicheren Fallbacks ausgegeben, damit fehlerhafte Eingabedaten kein ungültiges Inline-Skript erzeugen.
+
+### Changed
+- Signaturen, Typangaben und Rückgabetypen in zentralen Klassen und Backend-Seiten konsolidiert, um Laufzeitverhalten und statische Analyse in Einklang zu bringen.
+- JSON-Decode-/Encode-Grenzen in mehreren Flows vereinheitlicht und defensiver umgesetzt.
+- Interne Generator-Pfade für dynamische JSON-LD-Ausgabe bereinigt und auf konsistente Payload-Erzeugung umgestellt.
+- Backend-Controller- und Formularlogik für Artikel-, Dynamic-URL-, WebSite- und LocalBusiness-Seiten strukturell verbessert, ohne öffentliche API zu ändern.
+
+### Fixed
+- Mehrere potenzielle Laufzeitprobleme bei gemischten Datentypen behoben (insbesondere bei Mapping-, Config- und Vorschaupfaden).
+- Fehlerhafte bzw. fragile JSON-LD-Skriptgenerierung in dynamischen Fällen korrigiert.
+- Statische Analysewarnungen zu Array-Shapes, immer-wahren Bedingungen und uneindeutigen Typpfaden in relevanten Dateien beseitigt.
+- Kontaktpunkt-Filterung im LocalBusiness-Flow so angepasst, dass nur tatsächlich leere Werte entfernt werden und der Rest stabil bleibt.
+
+### Quality
+- Vollständiger Rexstan-Lauf auf Addon-Ebene erfolgreich abgeschlossen: keine verbleibenden Fehler.
+- Bestehende Funktionalität wurde dabei rückwärtskompatibel weitergeführt (kein BC-Break in Konfiguration oder Ausgabeformat beabsichtigt).
+
+### Notes
+- Diese Version ist ein technischer Stabilisierungs- und Qualitätsrelease, der die Grundlage für die nächsten Feature-Schritte verbessert.
+
## v1.0.0.beta.18 (08. Juni 2026)
### Fixed
diff --git a/boot.php b/boot.php
index e720e56..3444ded 100644
--- a/boot.php
+++ b/boot.php
@@ -1,5 +1,8 @@
isAvailable()) {
try {
- $urlManager = \Url\Url::resolveCurrent();
-
+ $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);
}
@@ -46,36 +49,33 @@
// 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;
-
+
// Legacy-Meta-Daten ausgeben (nach letztem im
)
$legacyMeta = trim(rex_config::get('jsonld_manager', 'legacy_meta_raw', ''));
if ($legacyMeta !== '') {
- // Nur ausgeben, wenn Template erlaubt ist (wie bei JSON-LD)
- if (function_exists('jsonld_is_template_output_allowed') && jsonld_is_template_output_allowed($article)) {
- // Suche alle im
- $headStart = stripos($content, '');
- if ($headStart !== false && $headEnd !== false && $headEnd > $headStart) {
- $headContent = substr($content, $headStart, $headEnd - $headStart);
- // Finde alle Tags
- preg_match_all('/]*>/i', $headContent, $metaMatches, PREG_OFFSET_CAPTURE);
- if (!empty($metaMatches[0])) {
- $lastMeta = end($metaMatches[0]);
- $insertPos = $headStart + $lastMeta[1] + strlen($lastMeta[0]);
- $content = substr($content, 0, $insertPos) . "\n" . $legacyMeta . "\n" . substr($content, $insertPos);
- } else {
- // Kein gefunden, vor einfügen
- $content = str_replace('', $legacyMeta . "\n", $content);
- }
+ // Suche alle im
+ $headStart = stripos($content, '');
+ if ($headStart !== false && $headEnd !== false && $headEnd > $headStart) {
+ $headContent = substr($content, $headStart, $headEnd - $headStart);
+ // Finde alle Tags
+ preg_match_all('/]*>/i', $headContent, $metaMatches, PREG_OFFSET_CAPTURE);
+ if (!empty($metaMatches[0])) {
+ $lastMeta = end($metaMatches[0]);
+ $insertPos = $headStart + $lastMeta[1] + strlen($lastMeta[0]);
+ $content = substr($content, 0, $insertPos) . "\n" . $legacyMeta . "\n" . substr($content, $insertPos);
} else {
// Kein gefunden, vor einfügen
$content = str_replace('', $legacyMeta . "\n", $content);
}
+ } else {
+ // Kein gefunden, vor einfügen
+ $content = str_replace('', $legacyMeta . "\n", $content);
}
}
if (!empty($jsonLdOutput)) {
@@ -87,7 +87,7 @@
}
// Extension Point für Cache-Invalidierung bei Artikel-Änderungen
-rex_extension::register('ART_UPDATED', function($ep) {
+rex_extension::register('ART_UPDATED', function($ep): void {
if (class_exists('\FriendsOfRedaxo\JsonLdManager\Frontend\Renderer')) {
$articleId = 0;
$params = $ep->getParams();
@@ -98,7 +98,7 @@
$articleId = (int) $params['article_id'];
}
- \FriendsOfRedaxo\JsonLdManager\Frontend\Renderer::clearCache($articleId > 0 ? $articleId : null);
+ Renderer::clearCache($articleId > 0 ? $articleId : null);
}
});
@@ -109,21 +109,18 @@
rex_view::addCssFile(rex_url::addonAssets('jsonld_manager', 'css/jsonld_manager.css'));
rex_view::addJsFile(rex_url::addonAssets('jsonld_manager', 'js/jsonld_manager.js'));
}
-
+
$hideDynamicUrlsSubpage = static function (): void {
- $filter = static function ($page) {
- if (!$page || !method_exists($page, 'getSubpages') || !method_exists($page, 'setSubpages')) {
+ $filter = static function ($page): void {
+ if (!$page instanceof rex_be_page) {
return;
}
$subpages = $page->getSubpages();
- if (!is_array($subpages)) {
- return;
- }
foreach ($subpages as $key => $subpage) {
- $subpageKey = method_exists($subpage, 'getKey') ? (string) $subpage->getKey() : (string) $key;
- $subpageFullKey = method_exists($subpage, 'getFullKey') ? (string) $subpage->getFullKey() : '';
+ $subpageKey = (string) $subpage->getKey();
+ $subpageFullKey = (string) $subpage->getFullKey();
if ($subpageKey === 'dynamic_urls' || $subpageFullKey === 'jsonld_manager/dynamic_urls') {
unset($subpages[$key]);
}
@@ -137,11 +134,9 @@
// Aktuelle Navigation (inkl. Parent) absichern
$current = rex_be_controller::getCurrentPageObject();
- if ($current) {
+ if ($current instanceof rex_be_page) {
$filter($current);
- if (method_exists($current, 'getParent')) {
- $filter($current->getParent());
- }
+ $filter($current->getParent());
}
};
@@ -158,13 +153,13 @@
rex_view::addCssFile(rex_url::addonAssets('jsonld_manager', 'css/hide_dynamic_urls_tab.css'));
}
- rex_extension::register('PACKAGES_INCLUDED', function() use ($hideDynamicUrlsSubpage, $shouldHideDynamicUrls) {
+ rex_extension::register('PACKAGES_INCLUDED', function() use ($hideDynamicUrlsSubpage, $shouldHideDynamicUrls): void {
if ($shouldHideDynamicUrls()) {
$hideDynamicUrlsSubpage();
}
});
- rex_extension::register('PAGE_PREPARED', function() use ($hideDynamicUrlsSubpage, $shouldHideDynamicUrls) {
+ rex_extension::register('PAGE_PREPARED', function() use ($hideDynamicUrlsSubpage, $shouldHideDynamicUrls): void {
if (!$shouldHideDynamicUrls()) {
return;
}
@@ -179,7 +174,7 @@
});
}
-rex_extension::register('ART_DELETED', function($ep) {
+rex_extension::register('ART_DELETED', function($ep): void {
if (class_exists('\FriendsOfRedaxo\JsonLdManager\Frontend\Renderer')) {
$articleId = 0;
$params = $ep->getParams();
@@ -190,6 +185,6 @@
$articleId = (int) $params['article_id'];
}
- \FriendsOfRedaxo\JsonLdManager\Frontend\Renderer::clearCache($articleId > 0 ? $articleId : null);
+ Renderer::clearCache($articleId > 0 ? $articleId : null);
}
});
diff --git a/lib/CustomJsonLdHelper.php b/lib/CustomJsonLdHelper.php
index b205234..88d4088 100644
--- a/lib/CustomJsonLdHelper.php
+++ b/lib/CustomJsonLdHelper.php
@@ -2,13 +2,16 @@
namespace FriendsOfRedaxo\JsonLdManager;
+use JsonException;
+use RuntimeException;
+
class CustomJsonLdHelper
{
private const MAX_RAW_LENGTH = 30000;
private const MAX_DEPTH = 20;
/**
- * @return array{raw:string,data:array,errors:array,warnings:array}
+ * @return array{raw:string,data:array,errors:array,warnings:array}
*/
public static function parseCustomObject(string $rawJson): array
{
@@ -33,7 +36,7 @@ public static function parseCustomObject(string $rawJson): array
try {
$decoded = json_decode($rawJson, true, 512, JSON_THROW_ON_ERROR);
- } catch (\JsonException $e) {
+ } catch (JsonException $e) {
return [
'raw' => $rawJson,
'data' => [],
@@ -54,7 +57,7 @@ public static function parseCustomObject(string $rawJson): array
$warnings = [];
try {
$sanitized = self::sanitizeObject($decoded, 0, $warnings);
- } catch (\RuntimeException $e) {
+ } catch (RuntimeException $e) {
return [
'raw' => $rawJson,
'data' => [],
@@ -114,7 +117,7 @@ public static function mergeIntoSchema(array $schema, array $customData, array $
private static function sanitizeObject(array $object, int $depth, array &$warnings): array
{
if ($depth > self::MAX_DEPTH) {
- throw new \RuntimeException('Custom-JSON ist zu tief verschachtelt.');
+ throw new RuntimeException('Custom-JSON ist zu tief verschachtelt.');
}
$clean = [];
@@ -138,12 +141,12 @@ private static function sanitizeObject(array $object, int $depth, array &$warnin
/**
* @param mixed $value
* @param array $warnings
- * @return mixed
+ * @return array|array|string|int|float|bool|null
*/
- private static function sanitizeValue($value, int $depth, array &$warnings)
+ private static function sanitizeValue($value, int $depth, array &$warnings): array|string|int|float|bool|null
{
if ($depth > self::MAX_DEPTH) {
- throw new \RuntimeException('Custom-JSON ist zu tief verschachtelt.');
+ throw new RuntimeException('Custom-JSON ist zu tief verschachtelt.');
}
if (is_array($value)) {
diff --git a/lib/DomainConfig.php b/lib/DomainConfig.php
index b7bf960..900a273 100644
--- a/lib/DomainConfig.php
+++ b/lib/DomainConfig.php
@@ -2,22 +2,30 @@
namespace FriendsOfRedaxo\JsonLdManager;
+use rex_addon;
+use rex_sql;
+use rex;
+use rex_sql_exception;
+use rex_yrewrite;
+use rex_url;
+
class DomainConfig
{
private const SESSION_KEY = 'jsonld_manager_active_domain_id';
+ /** @return array> */
public static function getDomains(): array
{
// Prüfen ob YRewrite installiert und aktiv ist
- if (!\rex_addon::get('yrewrite')->isAvailable()) {
+ if (!rex_addon::get('yrewrite')->isAvailable()) {
return [];
}
-
+
try {
- $sql = \rex_sql::factory();
- $sql->setQuery('SELECT id, domain, mount_id, start_id FROM ' . \rex::getTable('yrewrite_domain') . ' ORDER BY domain ASC');
+ $sql = rex_sql::factory();
+ $sql->setQuery('SELECT id, domain, mount_id, start_id FROM ' . rex::getTable('yrewrite_domain') . ' ORDER BY domain ASC');
return $sql->getArray();
- } catch (\rex_sql_exception $e) {
+ } catch (rex_sql_exception $e) {
// Fallback wenn Tabelle nicht existiert oder leer ist
return [];
}
@@ -33,17 +41,22 @@ public static function getActiveDomainId(): int
// Prüfe URL-Parameter
$requested = \rex_request('domain_id', 'int', 0);
if ($requested > 0 && self::domainExists($requested)) {
- if (\rex::isBackend()) {
+ if (rex::isBackend()) {
\rex_set_session(self::SESSION_KEY, $requested);
}
return $requested;
}
// Frontend: ohne Session arbeiten (verhindert Fehler bei nicht eingeloggten Besuchern)
- if (!\rex::isBackend()) {
- if (\rex_addon::get('yrewrite')->isAvailable() && class_exists('rex_yrewrite')) {
- $currentDomain = \rex_yrewrite::getCurrentDomain();
- if ($currentDomain && method_exists($currentDomain, 'getId')) {
+ if (!rex::isBackend()) {
+ if (rex_addon::get('yrewrite')->isAvailable() && class_exists('rex_yrewrite')) {
+ $currentDomain = rex_yrewrite::getCurrentDomain();
+ if ($currentDomain instanceof \rex_yrewrite_domain) {
+ $currentDomainId = (int) $currentDomain->getId();
+ if ($currentDomainId > 0) {
+ return $currentDomainId;
+ }
+ } elseif (is_object($currentDomain) && method_exists($currentDomain, 'getId')) {
$currentDomainId = (int) $currentDomain->getId();
if ($currentDomainId > 0) {
return $currentDomainId;
@@ -62,7 +75,7 @@ public static function getActiveDomainId(): int
$domains = self::getDomains();
if (!empty($domains)) {
$fallbackDomainId = (int) $domains[0]['id'];
- if (\rex::isBackend()) {
+ if (rex::isBackend()) {
\rex_set_session(self::SESSION_KEY, $fallbackDomainId);
}
return $fallbackDomainId;
@@ -71,31 +84,32 @@ public static function getActiveDomainId(): int
return 1; // Notfall-Fallback
}
+ /** @return array|null */
public static function getActiveDomain(): ?array
{
$activeDomainId = self::getActiveDomainId();
$domains = self::getDomains();
-
+
foreach ($domains as $domain) {
if ((int) $domain['id'] === $activeDomainId) {
return $domain;
}
}
-
+
return null;
}
public static function domainExists(int $domainId): bool
{
- if (!\rex_addon::get('yrewrite')->isAvailable()) {
+ if (!rex_addon::get('yrewrite')->isAvailable()) {
return false;
}
- $sql = \rex_sql::factory();
+ $sql = rex_sql::factory();
try {
- $sql->setQuery('SELECT id FROM ' . \rex::getTable('yrewrite_domain') . ' WHERE id = ?', [$domainId]);
+ $sql->setQuery('SELECT id FROM ' . rex::getTable('yrewrite_domain') . ' WHERE id = ?', [$domainId]);
return $sql->getRows() > 0;
- } catch (\rex_sql_exception $e) {
+ } catch (rex_sql_exception $e) {
return false;
}
}
@@ -126,8 +140,8 @@ public static function renderDomainSelect(int $activeDomainId): string
$options .= '';
}
- $currentUrl = \rex_url::currentBackendPage();
-
+ $currentUrl = rex_url::currentBackendPage();
+
return '