原程式碼使用 eregi()/eregi_replace() (POSIX Regex,**PHP 5.3 棄用、PHP 7.0 移除**),且正則表達式對 RFC 5322 (Email) 與 RFC 3986 (URL) 涵蓋不足。
<?php
function validateEmail($email) { return eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $email); }
function validateURL($url) { return eregi("^((ht|f)tp://)((([a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3}))|(([0-9]{1,3}\.){3}([0-9]{1,3})))((/|\?)[a-z0-9~#%&'_\+=:\?\.-]*)*)$", $url); }
function convertURLS($text) { /* ... eregi_replace ... */ }
function convertMail($text) { /* ... eregi_replace ... */ }
function convertAllLinks($text) { return convertMail(convertURLS($text)); }
?>
filter_var + 選擇性 Regexfunction validateEmail(string $email): bool {
// 基礎格式 + 長度限制 (RFC 5321: 254 字元)
if (strlen($email) > 254) return false;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
// 選擇性:禁止連續點、開頭結尾點、IP 域名等邊緣案例
// 一般應用 filter_var 已足夠,嚴格場景可加強正則
return true;
}
filter_var + parse_url 深度檢查function validateURL(string $url): bool {
if (!filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED)) {
return false;
}
$parsed = parse_url($url);
// 限制協議
if (!in_array($parsed['scheme'] ?? '', ['http', 'https', 'ftp'])) return false;
// 選擇性:禁止 localhost、私有 IP、IDN 攻擊等
return true;
}
preg_replace_callback 統一處理避免「先轉 URL 再轉 Email」導致巢狀 <a> 標籤錯誤,單次掃描同時處理。
function autoLink(string $text, array $options = []): string {
$defaults = [
'target' => '_blank',
'rel' => 'noopener noreferrer',
'class' => 'auto-link',
'maxLength' => 80, // 顯示文字截斷
'emailClass' => 'auto-link-email',
'urlClass' => 'auto-link-url',
];
$opts = array_merge($defaults, $options);
// 統一正則:優先匹配 URL (含 scheme),再匹配 www. 開頭,最後匹配 Email
// 使用 (*SKIP)(*FAIL) 避免重複匹配已轉換區塊 (需 PCRE)
$pattern = '
~
(?: (https?|ftp):// [^\s<>"\']++ ) # 1: 完整 URL
|
(?: www\.[^\s<>"\']++ ) # 2: www. 開頭
|
(?: [\w.+-]++ @ [\w.-]++ \.[a-z]{2,} ) # 3: Email
~ix
';
return preg_replace_callback($pattern, function($m) use ($opts) {
if (!empty($m[1])) { // 完整 URL
$url = $m[0];
$display = strlen($url) > $opts['maxLength'] ? substr($url, 0, $opts['maxLength']) . '…' : $url;
$attrs = 'class="' . htmlspecialchars($opts['urlClass']) . '" target="' . $opts['target'] . '" rel="' . $opts['rel'] . '" href="' . htmlspecialchars($url) . '"';
return "<a $attrs>$display</a>";
}
if (!empty($m[2])) { // www.
$url = 'https://' . $m[0];
$display = strlen($m[0]) > $opts['maxLength'] ? substr($m[0], 0, $opts['maxLength']) . '…' : $m[0];
$attrs = 'class="' . htmlspecialchars($opts['urlClass']) . '" target="' . $opts['target'] . '" rel="' . $opts['rel'] . '" href="' . htmlspecialchars($url) . '"';
return "<a $attrs>$display</a>";
}
if (!empty($m[3])) { // Email
$email = $m[0];
$display = strlen($email) > $opts['maxLength'] ? substr($email, 0, $opts['maxLength']) . '…' : $email;
$attrs = 'class="' . htmlspecialchars($opts['emailClass']) . '" href="mailto:' . htmlspecialchars($email) . '"';
return "<a $attrs>$display</a>";
}
return $m[0];
}, $text);
}
league/html-to-text 或 voku/url-parser若專案需處理複雜 HTML 內容 (避免破壞既有標籤)、Unicode 域名 (IDN)、短網址展開、安全性掃描,建議引入 Composer 套件:
composer require league/html-to-text voku/url-parser
use League\HTMLToText\HTMLToText;
use Voku\Helper\UrlParser;
$parser = new UrlParser();
$text = $parser->getLinks($htmlContent); // 解析既有連結
// 或使用 HTMLToText 轉純文字後再 autoLink
| 功能 | 舊式 | 現代建議 |
|---|---|---|
| Email 驗證 | eregi(...{2,3})$ | filter_var(FILTER_VALIDATE_EMAIL) + 長度檢查 |
| URL 驗證 | 複雜 POSIX Regex | filter_var(FILTER_VALIDATE_URL, FLAGS) + parse_url |
| 自動連結 | eregi_replace 多次掃描 | preg_replace_callback 單次掃描 + 回呼 |
| 編碼處理 | 假設 ASCII | u 修飾符支援 UTF-8/IDN |
htmlspecialchars(),href 協議白名單。mb_ereg_replace_callback。棄用 eregi 系列,擁抱 filter_var (C 級效能) 與 preg_replace_callback (靈活控制)。對於生產系統,優先考慮成熟 Composer 套件處理邊緣案例與安全性,自行維護正則僅適合簡單、可控場景。