diff --git a/app/Support/HtmlHelper.php b/app/Support/HtmlHelper.php index d57893a2..249b152e 100644 --- a/app/Support/HtmlHelper.php +++ b/app/Support/HtmlHelper.php @@ -80,6 +80,36 @@ public static function sanitizeHtml(?string $html): string return self::htmlSanitizer()->sanitize($html); } + /** + * Render `libri.descrizione`, which may be either editor HTML or legacy + * plain text with newlines (imported records). + * + * Plain text gets nl2br so its line breaks survive. Content that is already + * block-structured HTML is left as-is: the editor (TinyMCE) separates its + *
paragraphs with a literal "\n", and running nl2br over that injects a
+ * stray
between every block, double-spacing the output (issue #313).
+ * The result is always sanitized. This intentionally remains specific to
+ * the book-description field addressed by issue #313.
+ *
+ * @param string|null $value Raw stored value
+ * @return string Sanitized HTML ready to echo
+ */
+ public static function bookDescription(?string $value): string
+ {
+ if ($value === null || $value === '') {
+ return '';
+ }
+
+ // If the value already carries block/line HTML, its line breaks are
+ // markup — nl2br would turn the newlines *between* blocks into
.
+ $hasBlockHtml = preg_match(
+ '/<(?:p|br|div|ul|ol|li|h[1-6]|blockquote|pre|table|hr)\b/i',
+ $value
+ ) === 1;
+
+ return self::sanitizeHtml($hasBlockHtml ? $value : nl2br($value, false));
+ }
+
/**
* Return a public absolute HTTP(S) URL, or an empty string when unsafe.
*
diff --git a/app/Views/frontend/book-detail.php b/app/Views/frontend/book-detail.php
index be4800fd..0e3e9227 100644
--- a/app/Views/frontend/book-detail.php
+++ b/app/Views/frontend/book-detail.php
@@ -2026,7 +2026,7 @@ class="book-cover-large img-fluid"
: \App\Support\MediaLabels::formatTracklist($musicDescription) ?>
-
= __("Nessuna descrizione disponibile per questo libro.") ?>
diff --git a/app/Views/libri/scheda_libro.php b/app/Views/libri/scheda_libro.php index f9a11671..c06831bb 100644 --- a/app/Views/libri/scheda_libro.php +++ b/app/Views/libri/scheda_libro.php @@ -718,7 +718,7 @@ class="inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-full ?> = \App\Support\MediaLabels::formatTracklist($descText) ?> - + diff --git a/tests/bookdescription-render-313.unit.php b/tests/bookdescription-render-313.unit.php new file mode 100644 index 00000000..b8dcdf75 --- /dev/null +++ b/tests/bookdescription-render-313.unit.php @@ -0,0 +1,58 @@ + per line break (not two). +$crlf = "Prima riga\r\nSeconda riga"; +$out = HtmlHelper::bookDescription($crlf); +$check(substr_count($out, 'Uno.
\nDue.
"; +$out = HtmlHelper::bookDescription($attr); +$check(!str_contains($out, 'Blocco.
"; +$out = HtmlHelper::bookDescription($mixed); +$check(!str_contains($out, '+ * paragraphs of a book description. + * + * Root cause: the book description was rendered with + * sanitizeHtml(nl2br($value, false)). Editor HTML separates its
blocks
+ * with a literal "\n", so nl2br turned every inter-block newline into a stray
+ *
, double-spacing the output. HtmlHelper::bookDescription() applies nl2br
+ * only to plain-text values and leaves block HTML untouched. The helper is
+ * intentionally specific to `libri.descrizione`; other fields are outside #313.
+ *
+ * Run: php tests/richtext-nl2br-313.unit.php (exit 0 iff all pass)
+ */
+
+$root = dirname(__DIR__);
+require $root . '/vendor/autoload.php';
+
+use App\Support\HtmlHelper;
+
+$pass = 0;
+$fail = 0;
+$check = static function (bool $ok, string $label) use (&$pass, &$fail): void {
+ if ($ok) { $pass++; echo " OK {$label}\n"; }
+ else { $fail++; echo " FAIL {$label}\n"; }
+};
+
+// ── 1. The bug: editor HTML must NOT gain
between paragraphs ────────────
+$html = "
Para uno.
\nPara due.
\nPara tre.
"; +$out = HtmlHelper::bookDescription($html); +$check(!str_contains($out, '') === 3, "02 all three paragraphs are preserved");
+
+// ── 2. Plain text still gets its line breaks ────────────────────────────────
+$plain = "Prima riga\nSeconda riga\nTerza riga";
+$out = HtmlHelper::bookDescription($plain);
+$check(substr_count($out, '
");
+
+// A single-block value that only uses
is already HTML → left as-is (no
+// doubling): the two
stay two, they are not turned into four.
+$brHtml = "Riga uno
Riga due";
+$out = HtmlHelper::bookDescription($brHtml);
+$check(substr_count($out, '
-only value is treated as HTML, not re-broken");
+
+// Other block markers are detected too (div / ul / heading).
+foreach (['
ok
'); +$check(!str_contains($out, ' text"); +$check(!str_contains($out, '