From d81dd71a462ba47294652a186fc5a427623a2d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20FAMIBELLE-PRONZOLA?= Date: Mon, 27 Jul 2026 01:55:21 +0400 Subject: [PATCH] fix: markdown double-encoding and robust date/API access --- includes/lib/markdown.php | 80 +++++++++++++++++++++++++------------ tests/php/format-test.php | 67 +++++++++++++++++++++++++++++++ tests/php/markdown-test.php | 45 ++++++++++++++++----- 3 files changed, 156 insertions(+), 36 deletions(-) diff --git a/includes/lib/markdown.php b/includes/lib/markdown.php index c2968d7..e54260d 100644 --- a/includes/lib/markdown.php +++ b/includes/lib/markdown.php @@ -13,26 +13,30 @@ function markdown_to_html($markdown) { // Échapper tout le contenu pour éviter les injections XSS $markdown = htmlspecialchars($markdown, ENT_QUOTES, 'UTF-8'); - + // Tableau pour stocker les liens convertis $links = []; $link_count = 0; - + + // Note : le texte étant déjà échappé ci-dessus, les URLs extraites le sont + // aussi (« & » est devenu « & »). Il ne faut PAS les ré-échapper dans + // les callbacks ci-dessous, sinon on obtient un double encodage (« &amp; »). + // Conversion des liens Markdown [texte](url) $markdown = preg_replace_callback('/\[([^\]]+)\]\(([^)]+)\)/s', function($matches) use (&$links, &$link_count) { $text = $matches[1]; $url = $matches[2]; - + // Assurer que l'URL est correctement formée if (!preg_match('/^https?:\/\//i', $url)) { // Ajouter http:// si l'URL ne commence pas par http:// ou https:// $url = 'http://' . $url; } - + $placeholder = "___LINK_{$link_count}___"; - $links[$placeholder] = '' . $text . ''; + $links[$placeholder] = '' . $text . ''; $link_count++; - + return $placeholder; }, $markdown); @@ -41,29 +45,29 @@ function markdown_to_html($markdown) { $protocolUrlPattern = '/(https?:\/\/[^\s<]+[^\s<\.)])/i'; $markdown = preg_replace_callback($protocolUrlPattern, function($matches) use (&$links, &$link_count) { $url = $matches[1]; - + $placeholder = "___LINK_{$link_count}___"; - $links[$placeholder] = '' . $url . ''; + $links[$placeholder] = '' . $url . ''; $link_count++; - + return $placeholder; }, $markdown); - + // 2. Domaines sans protocole (comme "o-k-i.net", "gong.gp", "NUVEL.NU") // Détecte les domaines avec TLD communs qui ne font pas partie d'autre chose $domainPattern = '/\b([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,63})\b/'; $markdown = preg_replace_callback($domainPattern, function($matches) use (&$links, &$link_count) { $domain = $matches[0]; - + // Éviter de convertir des éléments qui ressemblent à des versions/numéros ou qui sont déjà dans des liens if (preg_match('/^v?\d+\.\d+/', $domain) || strpos($domain, '___LINK_') !== false) { return $domain; } - + $placeholder = "___LINK_{$link_count}___"; - $links[$placeholder] = '' . $domain . ''; + $links[$placeholder] = '' . $domain . ''; $link_count++; - + return $placeholder; }, $markdown); @@ -71,19 +75,45 @@ function markdown_to_html($markdown) { $markdown = preg_replace('/\*\*(.*?)\*\*/s', '$1', $markdown); $markdown = preg_replace('/\*(.*?)\*/s', '$1', $markdown); - // Conversion des listes à puces - $markdown = preg_replace('/^- (.*?)$/m', '
  • $1
  • ', $markdown); - $markdown = preg_replace('/(
  • .*?<\/li>\n?)+/s', '', $markdown); - - // Conversion des listes numérotées - $markdown = preg_replace('/^\d+\. (.*?)$/m', '
  • $1
  • ', $markdown); - $markdown = preg_replace('/(
  • .*?<\/li>\n?)+/s', '
      $0
    ', $markdown); - + // Conversion des listes (à puces et numérotées) en une seule passe ligne à + // ligne : les items consécutifs de même type forment une seule liste ; une + // ligne hors liste ou un changement de type ferme la liste courante. + $lines = explode("\n", $markdown); + $markdown = ''; + $listType = null; // 'ul', 'ol' ou null (hors liste) + foreach ($lines as $line) { + $itemType = null; + $itemText = null; + if (preg_match('/^- (.*)$/', $line, $matches)) { + $itemType = 'ul'; + $itemText = $matches[1]; + } elseif (preg_match('/^\d+\. (.*)$/', $line, $matches)) { + $itemType = 'ol'; + $itemText = $matches[1]; + } + + if ($itemType !== $listType) { + if ($listType !== null) { + $markdown .= '\n"; + } + if ($itemType !== null) { + $markdown .= '<' . $itemType . ">\n"; + } + $listType = $itemType; + } + + $markdown .= ($itemType !== null ? '
  • ' . $itemText . '
  • ' : $line) . "\n"; + } + if ($listType !== null) { + $markdown .= '\n"; + } + $markdown = rtrim($markdown, "\n"); + // Gestion des retours à la ligne $markdown = nl2br($markdown); - - // Nettoyage des balises br dans les listes - $markdown = preg_replace('/<\/li>
    /', '', $markdown); + + // Nettoyage des balises br autour des listes + $markdown = preg_replace('/(<\/li>|