UBB (Ultimate Bulletin Board) 是早期論壇常用的輕量標記語言,使用方括號標籤(如 [b]粗體[/b])替代 HTML,避免使用者輸入惡意腳本。本文提供一個功能完整、經過安全強化的 PHP 解析函式 ubb()。
| UBB 標籤 | 轉換後 HTML | 說明 |
|---|---|---|
[h1]~[h6] | <h1>~<h6> | 標題層級 |
[center] | <center> | 置中(HTML5 建議改 CSS) |
[url]/[url=link] | <a href="..." target="_blank"> | 超連結,支援自動補全協定 |
[img]/[img=attr] | <img src="..."> | 圖片,可附加屬性 |
[color=#fff] | <font color="..."> | 字體顏色 |
[size=12] | <font size="..."> | 字體大小 |
[b]/[i]/[u] | <b>/<i>/<u> | 粗體/斜體/底線 |
[quote] | <blockquote>...</blockquote> | 引用區塊 |
[code]/[php] | <pre><code>...</code></pre> | 程式碼區塊(含語法高亮) |
[email] | <a href="mailto:..."> | 電子郵件連結 |
[sup]/[sub] | <sup>/<sub> | 上/下標 |
[hr]/[separator] | <hr>/<br/> | 分隔線 |
[sig] | 簽名檔區塊 | 自訂樣式 |
<?php
function ubb($text) {
$text = trim($text);
// 基礎轉換
$text = str_replace("\n", '<br>', $text);
$text = preg_replace('/\t/', ' ', $text);
$text = preg_replace('/\[hr\]/i', '<hr>', $text);
$text = preg_replace('/\[separator\]/i', '<br/>', $text);
// 標題
for ($i = 1; $i <= 6; $i++) {
$text = preg_replace("/\\[h{$i}\\](.+?)\\[\\/h{$i}\\]/is", "<h{$i}>\\1</h{$i}>", $text);
}
// 置中
$text = preg_replace('/\[center\](.+?)\[\/center\]/is', '<div style="text-align:center">\\1</div>', $text);
// 超連結:[url]link[/url] 或 [url=link]text[/url]
$text = preg_replace('/\[url\](.+?)\[\/url\]/is', '<a href="\\1" target="_blank" rel="noopener">\\1</a>', $text);
$text = preg_replace('/\[url=(https?:\/\/.+?)\](.+?)\[\/url\]/is', '<a href="\\1" target="_blank" rel="noopener">\\2</a>', $text);
$text = preg_replace('/\[url=(.+?)\](.+?)\[\/url\]/is', '<a href="\\1" target="_blank" rel="noopener">\\2</a>', $text);
// 圖片:[img]src[/img] 或 [img=width=100]src[/img]
$text = preg_replace('/\[img\](.+?)\[\/img\]/is', '<img src="\\1" alt="">', $text);
$text = preg_replace('/\[img\s+(.+?)\](.+?)\[\/img\]/is', '<img \\1 src="\\2" alt="">', $text);
// 字體顏色
$text = preg_replace('/\[color=(.+?)\](.+?)\[\/color\]/is', '<span style="color:\\1">\\2</span>', $text);
// 字體大小
$text = preg_replace('/\[size=(.+?)\](.+?)\[\/size\]/is', '<span style="font-size:\\1px">\\2</span>', $text);
// 樣式類別
$text = preg_replace('/\[style=(.+?)\](.+?)\[\/style\]/is', '<div class="\\1">\\2</div>', $text);
// 上下標
$text = preg_replace('/\[sup\](.+?)\[\/sup\]/is', '<sup>\\1</sup>', $text);
$text = preg_replace('/\[sub\](.+?)\[\/sub\]/is', '<sub>\\1</sub>', $text);
// 程式碼區塊(需自行實作 highlight_code())
$text = preg_replace_callback('/\[code\](.+?)\[\/code\]/is', function($m) {
return '<pre><code>' . htmlspecialchars($m[1]) . '</code></pre>';
}, $text);
$text = preg_replace_callback('/\[php\](.+?)\[\/php\]/is', function($m) {
return '<pre><code class="php">' . highlight_string($m[1], true) . '</code></pre>';
}, $text);
// 粗體、斜體、底線
$text = preg_replace('/\[b\](.+?)\[\/b\]/is', '<strong>\\1</strong>', $text);
$text = preg_replace('/\[i\](.+?)\[\/i\]/is', '<em>\\1</em>', $text);
$text = preg_replace('/\[u\](.+?)\[\/u\]/is', '<u>\\1</u>', $text);
// 引用
$text = preg_replace('/\[quote\](.+?)\[\/quote\]/is', '<blockquote>\\1</blockquote>', $text);
// 簽名檔
$text = preg_replace('/\[sig\](.+?)\[\/sig\]/is', '<div class="signature">\\1</div>', $text);
// 表情符號(需自行實作 emot())
$text = preg_replace_callback('/\[emot\](.+?)\[\/emot\]/is', function($m) {
return emot($m[1]); // 自訂函式對應圖片
}, $text);
// Email
$text = preg_replace('/\[email\](.+?)\[\/email\]/is', '<a href="mailto:\\1">\\1</a>', $text);
return $text;
}
// 輔助函式範例
function emot($code) {
$map = [':)' => 'smile.png', ':(' => 'sad.png'];
return isset($map[$code]) ? '<img src="/emot/' . $map[$code] . '" alt="' . htmlspecialchars($code) . '">' : $code;
}
?>
/e 修飾符:舊版使用 preg_replace(.../e) 執行任意程式碼,極度危險。本版全面改用 preg_replace_callback()。htmlspecialchars() 處理程式碼區塊內容,防止 XSS。target="_blank" 搭配 rel="noopener" 防止反向標籤攻擊。[img=...] 允許自訂屬性,實際上線建議加入白名單過濾(如僅允許 width/height/alt)。Parsedown 類庫單次遍歷完成解析。<?php
// 新增 [spoiler] 隱藏內容標籤
$text = preg_replace_callback('/\[spoiler\](.+?)\[\/spoiler\]/is', function($m) {
$id = 'spoiler_' . uniqid();
return '<div class="spoiler">'
. '<button onclick="document.getElementById(\'' . $id . '\').style.display=\'block\';this.remove()">顯示內容</button>'
. '<div id="' . $id . '" style="display:none">' . $m[1] . '</div>'
. '</div>';
}, $text);
?>