prepared and gallery fixes
This commit is contained in:
103
app/Services/BbcodeConverter.php
Normal file
103
app/Services/BbcodeConverter.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class BbcodeConverter
|
||||
{
|
||||
/**
|
||||
* Convert simple BBCode to HTML. Safe-escapes content and supports basic tags.
|
||||
*/
|
||||
public function convert(?string $text): string
|
||||
{
|
||||
if ($text === null) return '';
|
||||
|
||||
// Normalize line endings
|
||||
$text = str_replace(["\r\n", "\r"], "\n", $text);
|
||||
|
||||
// Protect code blocks first
|
||||
$codeBlocks = [];
|
||||
$text = preg_replace_callback('/\[code\](.*?)\[\/code\]/is', function ($m) use (&$codeBlocks) {
|
||||
$idx = count($codeBlocks);
|
||||
$codeBlocks[$idx] = '<pre><code>' . htmlspecialchars($m[1], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</code></pre>';
|
||||
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' => '<strong>$1</strong>',
|
||||
'/\[i\](.*?)\[\/i\]/is' => '<em>$1</em>',
|
||||
'/\[u\](.*?)\[\/u\]/is' => '<span style="text-decoration:underline;">$1</span>',
|
||||
'/\[s\](.*?)\[\/s\]/is' => '<del>$1</del>',
|
||||
];
|
||||
|
||||
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 '<a href="' . $url . '" rel="noopener noreferrer" target="_blank">' . $label . '</a>';
|
||||
}, $text);
|
||||
$text = preg_replace_callback('/\[url\](.*?)\[\/url\]/is', function ($m) {
|
||||
$url = $this->sanitizeUrl(html_entity_decode($m[1]));
|
||||
return '<a href="' . $url . '" rel="noopener noreferrer" target="_blank">' . $url . '</a>';
|
||||
}, $text);
|
||||
|
||||
// [img]url[/img]
|
||||
$text = preg_replace_callback('/\[img\](.*?)\[\/img\]/is', function ($m) {
|
||||
$src = $this->sanitizeUrl(html_entity_decode($m[1]));
|
||||
return '<img src="' . $src . '" alt="" />';
|
||||
}, $text);
|
||||
|
||||
// [quote]...[/quote]
|
||||
$text = preg_replace('/\[quote\](.*?)\[\/quote\]/is', '<blockquote>$1</blockquote>', $text);
|
||||
|
||||
// [list] and [*]
|
||||
// Convert [list]...[*]item[*]...[/list] to <ul><li>...</li></ul>
|
||||
$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 .= '<li>' . $it . '</li>';
|
||||
}
|
||||
return '<ul>' . $out . '</ul>';
|
||||
}, $text);
|
||||
|
||||
// sizes and colors: simple inline styles
|
||||
$text = preg_replace('/\[size=(\d+)\](.*?)\[\/size\]/is', '<span style="font-size:$1px;">$2</span>', $text);
|
||||
$text = preg_replace('/\[color=(#[0-9a-fA-F]{3,6}|[a-zA-Z]+)\](.*?)\[\/color\]/is', '<span style="color:$1;">$2</span>', $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 '#';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user