' . htmlspecialchars($m[1], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . ''; return "__CODEBLOCK_{$idx}__"; }, $text); // Escape remaining text to avoid XSS $text = htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); // Basic tags $simple = [ '/\[b\](.*?)\[\/b\]/is' => '$1', '/\[i\](.*?)\[\/i\]/is' => '$1', '/\[u\](.*?)\[\/u\]/is' => '$1', '/\[s\](.*?)\[\/s\]/is' => '$1', ]; foreach ($simple as $pat => $rep) { $text = preg_replace($pat, $rep, $text); } // [url=link]text[/url] and [url]link[/url] $text = preg_replace_callback('/\[url=(.*?)\](.*?)\[\/url\]/is', function ($m) { $url = $this->sanitizeUrl(html_entity_decode($m[1])); $label = $m[2]; return '' . $label . ''; }, $text); $text = preg_replace_callback('/\[url\](.*?)\[\/url\]/is', function ($m) { $url = $this->sanitizeUrl(html_entity_decode($m[1])); return '' . $url . ''; }, $text); // [img]url[/img] $text = preg_replace_callback('/\[img\](.*?)\[\/img\]/is', function ($m) { $src = $this->sanitizeUrl(html_entity_decode($m[1])); return ''; }, $text); // [quote]...[/quote] $text = preg_replace('/\[quote\](.*?)\[\/quote\]/is', '
$1
', $text); // [list] and [*] // Convert [list]...[*]item[*]...[/list] to $text = preg_replace_callback('/\[list\](.*?)\[\/list\]/is', function ($m) { $items = preg_split('/\[\*\]/', $m[1]); $out = ''; foreach ($items as $it) { $it = trim($it); if ($it === '') continue; $out .= '
  • ' . $it . '
  • '; } return ''; }, $text); // sizes and colors: simple inline styles $text = preg_replace('/\[size=(\d+)\](.*?)\[\/size\]/is', '$2', $text); $text = preg_replace('/\[color=(#[0-9a-fA-F]{3,6}|[a-zA-Z]+)\](.*?)\[\/color\]/is', '$2', $text); // Preserve line breaks $text = nl2br($text); // Restore code blocks if (!empty($codeBlocks)) { foreach ($codeBlocks as $i => $html) { $text = str_replace('__CODEBLOCK_' . $i . '__', $html, $text); } } return $text; } protected function sanitizeUrl($url) { $url = trim($url); // allow relative paths if (strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0 || strpos($url, '/') === 0) { return htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } // fallback: prefix with http:// if looks like domain if (preg_match('/^[A-Za-z0-9\-\.]+(\:[0-9]+)?(\/.*)?$/', $url)) { return 'http://' . htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } return '#'; } }