本插件為 Discuz! 5.0 論壇擴展兩項實用功能:
highlight_string())安裝僅需修改兩個核心檔案,無需資料庫變更,相容性極佳。
尋找現有 copycode() 函數結尾,於其後新增三個函數:
// 執行 HTML/JS 代碼 (新視窗開啟)
function runCode(obj) {
var win = window.open('', '_blank', '');
win.document.open('text/html', 'replace');
win.document.writeln(obj.value);
win.document.close();
}
// 複製代碼到剪貼簿
function copycode(obj) {
var rng = document.body.createTextRange();
rng.moveToElementText(obj);
rng.scrollIntoView();
rng.select();
rng.execCommand("Copy");
rng.collapse(false);
}
// 另存代碼為檔案 (IE 專用 execCommand)
function saveCode(obj) {
var win = window.open('', '_blank', 'top=10000');
win.document.open('text/html', 'replace');
win.document.writeln(obj.value);
win.document.execCommand('saveas', '', 'code.htm');
win.close();
}
注意:createTextRange/execCommand為 IE 專有 API,現代瀏覽器建議改用navigator.clipboard.writeText()與Blob + URL.createObjectURL()實現跨瀏覽器相容。
尋找 $discuzcodes['codecount']++ 所在行 (約第 110 行),在其後新增兩個解析函數:
// --------- [runcode] HTML 代碼運行 ---------
function runcodedisp($code) {
global $discuzcodes;
$discuzcodes['pcodecount']++;
// 清理代碼:移除首尾換行、轉義 HTML 實體、還原引號
$code = htmlspecialchars(str_replace('\\"', '"', preg_replace("/^[\n\r]*(.+?)[\n\r]*$/is", "\\1", $code)));
$cid = $discuzcodes['codecount'];
$discuzcodes['codehtml'][$discuzcodes['pcodecount']] =
"
" .
"
" .
" " .
" " .
" " .
" 提示:您可以先修改部分代碼再運行" .
"
";
$discuzcodes['codecount']++;
return "[\tDISCUZ_CODE_{$discuzcodes['pcodecount']}\t]";
}
// --------- [php] PHP 代碼高亮 ---------
function phpcodedisp($code) {
global $discuzcodes;
$discuzcodes['pcodecount']++;
// 確保代碼包含 PHP 標籤,才能正確高亮
$addedTags = false;
if (!strpos($code, "\n") && !strpos($code, '') && substr($code, 0, 2) != '') {
$code = '<?' . trim($code) . ' ?>';
$addedTags = true;
}
// 捕獲 highlight_string 輸出
ob_start();
$oldLevel = error_reporting(0); // 抑制錯誤輸出干擾
highlight_string($code);
error_reporting($oldLevel);
$buffer = ob_get_contents();
ob_end_clean();
// 移除自動添加的標籤高亮
if ($addedTags) {
$openPos = strpos($buffer, '<?');
$closePos = strrpos($buffer, '?>');
if ($openPos !== false && $closePos !== false) {
$buffer = substr($buffer, 0, $openPos) .
substr($buffer, $openPos + 5, $closePos - $openPos - 5) .
substr($buffer, $closePos + 5);
}
}
// 清理輸出
$buffer = str_replace('"', '"', $buffer);
$buffer = str_replace('<br />', '', $buffer);
$cid = $discuzcodes['codecount'];
$discuzcodes['codehtml'][$discuzcodes['pcodecount']] =
"
" .
"" .
"PHP代碼如下:" .
"{$buffer}
";
$discuzcodes['codecount']++;
return "[\tDISCUZ_CODE_{$discuzcodes['pcodecount']}\t]";
}
尋找現有的 preg_replace 代碼區塊:
$message = preg_replace("/\s*\[code\](.+?)\[\/code\]\s*/ies", "codedisp('\\1')", $message);
在其下方新增:
// --------- runcode ----------
$message = preg_replace("/\s*\[runcode\](.+?)\[\/runcode\]\s*/ies", "runcodedisp('\\1')", $message);
// --------- php ----------
$message = preg_replace("/\s*\[php\](.+?)\[\/php\]\s*/ies", "phpcodedisp('\\1')", $message);
[php]
function hello($name) {
return "Hello, " . $name . "!";
}
echo hello("Discuz!");
[/php]
渲染後將顯示帶行號、關鍵字著色的代碼區塊,並提供下方「複製PHP代碼」按鈕。
[runcode]
<html>
<body>
<h1>Hello, Discuz!</h1>
<button onclick="alert('按鈕可互動')">點我</button>
<script>console.log('JS 執行正常')</script>
</body>
</html>
[/runcode]
渲染後顯示可編輯的 <textarea>,點擊「運行代碼」即在新分頁呈現執行結果,支援即時修改預覽。
| 關鍵點 | 實作邏輯 | 潛在風險與改進 |
|---|---|---|
| PHP 高亮核心 | highlight_string() 內建函數,輸出內聯 CSS 樣式 | 輸出 HTML 結構固定,難以自定義主題;建議改用 GeSHi 或 highlight.js (前端渲染) |
| 代碼清理策略 | 移除首尾空白、還原轉義引號、補全 PHP 標籤 | 正則 /^[\n\r]*(.+?)[\n\r]*$/is 若代碼含多行字串可能誤判;建議用 trim() 替代 |
| XSS 防護 | htmlspecialchars() 轉義 runcode 內容 | 運行時 document.writeln 直接寫入用戶代碼,若論壇允許非信任用戶發文,**極高風險** (存儲型 XSS)。生產環境**必須**加入 CSP、iframe 沙箱、或僅限管理員使用 |
| 瀏覽器相容 | execCommand('Copy/SaveAs') 僅 IE/舊 Edge 支援 | 現代化請參考 Clipboard API 與 File System Access API |
| 效能考量 | /e 修飾符 (PREG_REPLACE_EVAL) 在 PHP 5.5+ 棄用、7.2+ 移除 | **重大問題**:Discuz! 5 時代用 PHP 4/5.2,若移植至 PHP 7/8 必須改用 preg_replace_callback() |
<?php
// 使用 preg_replace_callback 替代 /e
$message = preg_replace_callback(
"/\s*\[php\](.+?)\[\/php\]\s*/is",
function ($matches) {
return phpcodedisp($matches[1]);
},
$message
);
// PHP 8.2+ 建議:改用前端高亮 (highlight.js)
// 後端僅輸出 ...
// 前端引入 highlight.js 自動著色,零後端負擔、支援 190+ 語言、主題豐富
這個插件展示了 Discuz! 代碼解析器的擴展機制:正則捕獲 → 回調處理 → 佔位符替換 → 二次渲染。雖然具體實作帶有時代特徵 (IE 專用 API、/e 修飾符、內建高亮),但其**架構思想**在現代論壇開發中依然適用。若您維護舊版 Discuz!,可直接套用;若開發新專案,建議採用**前端高亮 + CSP 隔離 + 現代 Clipboard API** 的組合方案。