diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ae906f..ed03318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## **04.06.2026 Version 1.3.1** + +- fix: mehrere RexStan-Befunde behoben (u. a. Null-Safety beim Slice-Rendering, typisierte Konstruktoren, generische Extension-Point-Annotationen) +- fix: Preview-Rendering stabilisiert durch defensivere Parameterbehandlung und saubere Rücksetzung des REDAXO-Kontexts bei `force_fe` +- fix: Settings-Formular abgesichert (Admin-Null-Check); das Speichern des Templates validiert nun den Pflichtplatzhalter `BLOCK_PEEK_CONTENT` +- ux: Assets werden nur noch auf `content/edit` geladen statt global im Backend +- chore: internen Dateizugriff auf die REDAXO-API (`rex_file::get`) vereinheitlicht + ## **04.06.2026 Version 1.3.0** - feat: resolve sprog wildcards (`{{ … }}`) in slice previews when the `sprog` addon is installed — parsed in the preview's clang before the iframe is built, so backend previews match the translated frontend output (sprog's own wildcard filter only runs on the frontend) diff --git a/boot.php b/boot.php index 09dd422..b3c7226 100644 --- a/boot.php +++ b/boot.php @@ -2,13 +2,12 @@ use FriendsOfRedaxo\BlockPeek\TemplateListHider; -/** @var rex_addon_interface $addon */ -$addon = $this; +$addon = rex_addon::get('block_peek'); if ($addon->getConfig('inactive') !== '|1|') { - if (rex::isBackend() && rex::getUser()) { - rex_view::addJsFile($this->getAssetsUrl('BlockPeek.js')); - rex_view::addCssFile($this->getAssetsUrl('BlockPeek.css')); + if (rex::isBackend() && rex::getUser() && rex_be_controller::getCurrentPage() === 'content/edit') { + rex_view::addJsFile($addon->getAssetsUrl('BlockPeek.js')); + rex_view::addCssFile($addon->getAssetsUrl('BlockPeek.css')); rex_extension::register('PACKAGES_INCLUDED', function () { rex_extension::register('SLICE_BE_PREVIEW', \FriendsOfRedaxo\BlockPeek\Extension::register(...), rex_extension::LATE); }); diff --git a/lang/de_de.lang b/lang/de_de.lang index 9c32c30..1b06ed7 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -15,6 +15,7 @@ block_peek_no_permission = Du hast keine Berechtigung, diese Seite anzusehen. block_peek_template = BlockPeek Template block_peek_template_notice = Hinweis: Der Platzhalter BLOCK_PEEK_CONTENT ist Voraussetzung, um den eigentlichen Slice-Inhalt anzuzeigen.
Das Template wird als verstecktes REDAXO-Template gespeichert (Key: block_peek_internal). Bitte REX_ARTICLE[...] nicht hinzufügen, ausser alle Slices sollen gerendert werden.
Tailwind 4: Wenn das developer Addon installiert ist, wird das Template automatisch nach data/addons/developer/templates/<Name> [<id>]/template.php auf die Festplatte synchronisiert, sodass Tailwind die Utility-Klassen via @source findet. block_peek_template_saved = Template gespeichert. +block_peek_template_missing_placeholder = Das Template muss den Platzhalter BLOCK_PEEK_CONTENT enthalten. # Configuration — Cache block_peek_cache_fieldset = Cache diff --git a/lang/en_gb.lang b/lang/en_gb.lang index 43d898e..9e9af0f 100644 --- a/lang/en_gb.lang +++ b/lang/en_gb.lang @@ -15,6 +15,7 @@ block_peek_no_permission = You do not have permission to view this page. block_peek_template = BlockPeek Template block_peek_template_notice = Note: The placeholder BLOCK_PEEK_CONTENT is required to display the actual slice content.
The template is stored as a hidden REDAXO template (key: block_peek_internal). Do not add REX_ARTICLE[...] unless you want all slices rendered.
Tailwind 4: if you use the developer addon, the template auto-syncs to disk at data/addons/developer/templates/<Name> [<id>]/template.php and Tailwind can discover utility classes via @source. block_peek_template_saved = Template saved. +block_peek_template_missing_placeholder = The template must contain the BLOCK_PEEK_CONTENT placeholder. # Configuration — Cache block_peek_cache_fieldset = Cache diff --git a/lib/Extension.php b/lib/Extension.php index be65878..e332892 100644 --- a/lib/Extension.php +++ b/lib/Extension.php @@ -8,27 +8,50 @@ class Extension { + /** + * @param rex_extension_point $ep + */ public static function register(rex_extension_point $ep): void { - /** @var rex_addon $addon */ - $addon = rex_addon::get('block_peek'); - $minHeight = (int) $addon->getConfig('iframe_min_height') ?: 300; - $zoomFactor = (float) $addon->getConfig('iframe_zoom_factor') ?: 0.5; - $sliceData = $ep->getParams(); - $revision = $sliceData['revision'] ?? 0; - $slice = rex_article_slice::getArticleSliceById($sliceData['slice_id'], false, 0); - if (!$slice) { - $revision = 1; - $slice = rex_article_slice::getArticleSliceById($sliceData['slice_id'], false, 1); - } - $updateDate = $slice->getValue('updatedate'); - $generator = new Generator(articleId: $sliceData['article_id'], clangId: $sliceData['clang'], sliceId: $sliceData['slice_id'], moduleId: $sliceData['module_id'], ctypeId: $sliceData['ctype'], updateDate: $updateDate, revision: $revision); - $content = $generator->getContent(); - $html = - '
- + /** @var rex_addon $addon */ + $addon = rex_addon::get('block_peek'); + $minHeight = (int) $addon->getConfig('iframe_min_height') ?: 300; + $zoomFactor = (float) $addon->getConfig('iframe_zoom_factor') ?: 0.5; + + $articleId = (int) $ep->getParam('article_id', 0); + $clangId = (int) $ep->getParam('clang', 0); + $sliceId = (int) $ep->getParam('slice_id', 0); + $revision = (int) $ep->getParam('revision', 0); + + if ($articleId <= 0 || $clangId <= 0 || $sliceId <= 0) { + return; + } + + $slice = rex_article_slice::getArticleSliceById($sliceId, false, 0); + if ($slice === null) { + $revision = 1; + $slice = rex_article_slice::getArticleSliceById($sliceId, false, 1); + } + + $updateDateValue = $slice?->getValue('updatedate') ?? 0; + $updateDate = is_numeric($updateDateValue) + ? (int) $updateDateValue + : (int) strtotime((string) $updateDateValue); + + $generator = new Generator( + articleId: $articleId, + clangId: $clangId, + sliceId: $sliceId, + updateDate: $updateDate, + revision: $revision, + ); + + $content = $generator->getContent(); + $html = + '
+
'; - $ep->setSubject($html); + $ep->setSubject($html); } } diff --git a/lib/Generator.php b/lib/Generator.php index 7cf3792..41bbc3a 100644 --- a/lib/Generator.php +++ b/lib/Generator.php @@ -23,23 +23,19 @@ class Generator private int $articleId = 0; private int $clangId = 0; private int $sliceId = 0; - private int $moduleId = 0; - private int $ctypeId = 0; private int $updateDate = 0; private int $revision = 0; protected int $DEFAULT_TTL; public bool $cacheActive = true; - public function __construct($articleId, $clangId, $sliceId, $ctypeId, $moduleId, $updateDate, $revision) + public function __construct(int $articleId, int $clangId, int $sliceId, int $updateDate, int $revision) { $this->addon = rex_addon::get('block_peek'); $this->articleId = $articleId; $this->clangId = $clangId; $this->sliceId = $sliceId; - $this->ctypeId = $ctypeId; - $this->moduleId = $moduleId; $this->updateDate = $updateDate; $this->revision = $revision; @@ -104,41 +100,46 @@ private function fetchTemplateUpdateDate(int $templateId): int private function prepareOutput(int $templateId): string { $forceFeContext = (bool) $this->addon->getConfig('force_fe', false); + $wasBackendContext = rex::getProperty('redaxo'); if ($forceFeContext) { rex::setProperty('redaxo', false); } - $context = new rex_article_content($this->articleId, $this->clangId); - $context->setSliceRevision($this->revision); - $context->setTemplateId($templateId); - - $wrapperHtml = $context->getArticleTemplate(); - $sliceHtml = $context->getSlice($this->sliceId); - - $sliceHtml = '
' . $sliceHtml . '
'; - $html = str_replace('BLOCK_PEEK_CONTENT', $sliceHtml, $wrapperHtml); - - $html = $this->injectPosterAndStyles($html); - $html = $this->setHtmlLang($html); - - // Resolve sprog wildcards ({{ … }}) for the preview's language. sprog only - // runs its OUTPUT_FILTER on the frontend (sprog/boot.php: if (!rex::isBackend())), - // so without this the backend preview shows raw wildcards. Pass the preview's - // clang explicitly — Wildcard::parse() defaults to rex_clang::getCurrentId(), - // which in the backend is the admin's clang, not necessarily the one previewed. - if (rex_addon::get('sprog')->isAvailable()) { - $html = \Sprog\Wildcard::parse($html, $this->clangId); + try { + $context = new rex_article_content($this->articleId, $this->clangId); + $context->setSliceRevision($this->revision); + $context->setTemplateId($templateId); + + $wrapperHtml = $context->getArticleTemplate(); + $sliceHtml = $context->getSlice($this->sliceId); + + $sliceHtml = '
' . $sliceHtml . '
'; + $html = str_replace('BLOCK_PEEK_CONTENT', $sliceHtml, $wrapperHtml); + + $html = $this->injectPosterAndStyles($html); + $html = $this->setHtmlLang($html); + + // Resolve sprog wildcards ({{ … }}) for the preview's language. sprog only + // runs its OUTPUT_FILTER on the frontend (sprog/boot.php: if (!rex::isBackend())), + // so without this the backend preview shows raw wildcards. Pass the preview's + // clang explicitly — Wildcard::parse() defaults to rex_clang::getCurrentId(), + // which in the backend is the admin's clang, not necessarily the one previewed. + if (rex_addon::get('sprog')->isAvailable()) { + $html = \Sprog\Wildcard::parse($html, $this->clangId); + } + + return rex_extension::registerPoint(new rex_extension_point('BLOCK_PEEK_OUTPUT', $html, [ + 'article_id' => $this->articleId, + 'clang' => $this->clangId, + 'slice_id' => $this->sliceId, + 'updateDate' => $this->updateDate, + 'revision' => $this->revision, + ])); + } finally { + if ($forceFeContext) { + rex::setProperty('redaxo', $wasBackendContext); + } } - - $html = rex_extension::registerPoint(new rex_extension_point('BLOCK_PEEK_OUTPUT', $html, [ - 'article_id' => $this->articleId, - 'clang' => $this->clangId, - 'slice_id' => $this->sliceId, - 'updateDate' => $this->updateDate, - 'revision' => $this->revision, - ])); - - return $html; } private function injectPosterAndStyles(string $html): string diff --git a/lib/TemplateInstaller.php b/lib/TemplateInstaller.php index 28ba98a..b5b9783 100644 --- a/lib/TemplateInstaller.php +++ b/lib/TemplateInstaller.php @@ -7,6 +7,7 @@ use rex_config; use rex_extension; use rex_extension_point; +use rex_file; use rex_sql; use rex_template; use rex_template_cache; @@ -106,8 +107,8 @@ private static function buildContent(): string private static function loadDefaultContent(): string { $path = rex_addon::get('block_peek')->getPath('templates/default.php'); - $content = @file_get_contents($path); - if ($content === false) { + $content = rex_file::get($path); + if (!is_string($content) || $content === '') { return "\n\n\n\n\n\nBLOCK_PEEK_CONTENT\n\n\n"; } return $content; diff --git a/lib/TemplateListHider.php b/lib/TemplateListHider.php index 59043c3..44a374f 100644 --- a/lib/TemplateListHider.php +++ b/lib/TemplateListHider.php @@ -14,6 +14,8 @@ class TemplateListHider * OUTPUT_FILTER (LATE) on the templates list page only. Strips the for our * hidden row by matching template_id= with a non-digit boundary so id "1" * doesn't accidentally match "10". + * + * @param rex_extension_point $ep */ public static function register(rex_extension_point $ep): void { @@ -34,9 +36,6 @@ public static function register(rex_extension_point $ep): void $hideId = $template->getId(); $content = $ep->getSubject(); - if (!is_string($content)) { - return; - } $result = preg_replace_callback( '/]*>.*?<\/tr>/s', diff --git a/package-lock.json b/package-lock.json index 295ff48..906d566 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "block_peek-assets", - "version": "1.2.1", + "version": "1.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "block_peek-assets", - "version": "1.2.1", + "version": "1.3.1", "devDependencies": { "autoprefixer": "^10.5.0", "js-yaml": "^4.1.1", diff --git a/package.json b/package.json index 8c631a0..94d3e62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "block_peek-assets", - "version": "1.3.0", + "version": "1.3.1", "description": "Asset build for BlockPeek Addon", "scripts": { "version:sync": "node scripts/sync-version.js", diff --git a/package.yml b/package.yml index 2d3d7dd..237d4c1 100644 --- a/package.yml +++ b/package.yml @@ -1,5 +1,5 @@ package: block_peek -version: '1.3.0' +version: '1.3.1' license: MIT author: Friends Of REDAXO, Yves Torres supportpage: https://github.com/FriendsOfREDAXO/block_peek diff --git a/pages/settings.php b/pages/settings.php index 902a9de..8983d12 100644 --- a/pages/settings.php +++ b/pages/settings.php @@ -4,7 +4,8 @@ /** @var rex_addon $this */ -if (!rex::getUser()->isAdmin()) { +$user = rex::getUser(); +if ($user === null || !$user->isAdmin()) { echo rex_view::error(rex_i18n::msg('block_peek_no_permission')); return; } @@ -26,21 +27,25 @@ ) { $newContent = rex_post('block_peek_template_content', 'string', ''); - $sql = rex_sql::factory(); - $sql->setTable(rex::getTable('template')); - $sql->setWhere('id = :id', ['id' => $templateId]); - $sql->setValue('content', $newContent); - $sql->addGlobalUpdateFields(); - $sql->update(); - - rex_template_cache::delete($templateId); - rex_template_cache::generate($templateId); - rex_extension::registerPoint(new rex_extension_point('TEMPLATE_UPDATED', '', ['id' => $templateId])); - - $templateMessage = rex_view::success(rex_i18n::msg('block_peek_template_saved')); - - // Re-fetch by id (we just confirmed it exists above; safer than forKey here). - $template = new rex_template($templateId); + if (strpos($newContent, 'BLOCK_PEEK_CONTENT') === false) { + $templateMessage = rex_view::error(rex_i18n::msg('block_peek_template_missing_placeholder')); + } else { + $sql = rex_sql::factory(); + $sql->setTable(rex::getTable('template')); + $sql->setWhere('id = :id', ['id' => $templateId]); + $sql->setValue('content', $newContent); + $sql->addGlobalUpdateFields(); + $sql->update(); + + rex_template_cache::delete($templateId); + rex_template_cache::generate($templateId); + rex_extension::registerPoint(new rex_extension_point('TEMPLATE_UPDATED', '', ['id' => $templateId])); + + $templateMessage = rex_view::success(rex_i18n::msg('block_peek_template_saved')); + + // Re-fetch by id (we just confirmed it exists above; safer than forKey here). + $template = new rex_template($templateId); + } } $currentContent = (string) $template->getTemplate(); @@ -124,7 +129,7 @@ class="form-control rex-code rex-html-code" $field->setAttribute('min', '0.1'); $field->setAttribute('max', '1.0'); $field->setAttribute('step', '0.05'); -$field->setAttribute('placeholder', '0,5'); +$field->setAttribute('placeholder', '0.5'); $field->setAttribute('style', 'width: 80px;'); $field = $form->addCheckboxField('force_fe');