概述
插件兼容性扫描器用于检测已安装插件中与 XIUNOX 现代化改造不兼容的代码,涵盖 PHP 8.0+ 语法、Bootstrap 5 迁移、jQuery 去除、安全规范等方面。
入口:后台 → 插件管理 → 插件兼容性检查(/*****/?plugin-scanner.htm)
相关文件
文件 | 说明 |
|---|---|
| 扫描器核心,负责文件遍历、行级/文件级检测、结果构建 |
| 规则定义,包含分类、匹配模式、严重级别、中文名、PHP-only/HTML-only 分类 |
| 动态建议构建器,根据匹配内容生成具体迁移建议 |
| 后台路由,处理扫描/导出请求 |
| 扫描器页面模板 |
| 中文语言项( |
| 繁体中文语言项 |
| 英文语言项 |
使用方式
进入后台「插件兼容性检查」页面
点击「开始扫描」按钮
前端通过
fetch()请求?plugin-scanner-do.htm,后端返回 JSON 格式扫描结果结果按插件分组展示,支持按严重级别筛选
可导出 CSV 报告
扫描范围
扫描 plugin/ 目录下所有包含 conf.json 的插件目录,递归检查以下扩展名的文件:
.php— PHP 代码.htm/.html— 模板文件.js— JavaScript 脚本.css— 样式文件
自动跳过 img/、images/、fonts/、vendor/、node_modules/ 等目录。
为避免误报,扫描器会将 .htm/.php/.html 文件中的 <script>/<style> 块与 PHP/HTML 部分分离:
PHP-only 规则只扫描 PHP/HTML 部分,不会误扫
<script>中的 JS 代码HTML-only 规则只扫描 PHP/HTML 部分,不会扫描纯
.php文件
规则类别
严重级别说明
级别 | 含义 |
|---|---|
fatal | 致命:代码在 PHP 8.0+ 下会直接报错或崩溃 |
warning | 警告:存在安全风险或功能异常 |
medium | 中等:不兼容但不会直接崩溃,需要迁移 |
info | 信息:建议优化,非必须 |
PHP 兼容性规则
废弃函数(PHP 7.x 移除)— fatal
检测 mysql_* 系列函数及其他在 PHP 7.x 已移除的函数:
匹配模式 | 说明 |
|---|---|
| mysql 扩展已在 PHP 7.0 移除 |
| PHP 7.2 废弃,8.0 移除 |
| PHP 7.2 废弃,8.0 移除,用闭包替代 |
| PHP 7.0 移除,用 |
| PHP 7.0 移除,用 |
| PHP 7.0 移除,用 |
PHP 8 不兼容语法 — fatal
匹配模式 | 说明 |
|---|---|
| 按引用创建对象已移除,改用 |
|
|
花括号数组访问 — fatal
匹配模式 | 说明 |
|---|---|
|
|
HTTP_*_VARS 变量 — fatal
匹配模式 | 说明 |
|---|---|
| 已移除,用 |
PHP 8.0+ 废弃函数 — fatal
匹配模式 | 说明 |
|---|---|
| PHP 7.4 废弃、8.0 移除,始终返回 false |
| PHP 7.4 废弃、8.0 移除 |
| PHP 8.2 废弃,用 |
| PHP 8.2 废弃,用 |
| PHP 7.4 废弃、8.0 移除,用 |
| 对 PDO/MySQLi 对象返回 false(PHP 8.0+),改用 |
PHP 注释陷阱检测 — fatal
匹配模式 | 说明 |
|---|---|
| 单行注释 |
| 行首 |
| 行中 |
仅扫描
.php文件。PHP 解析器会把单行注释中的?>识别为 PHP 代码块结束标签,导致后续代码被当作纯文本输出。建议:移除注释中的<?php和?>,改用块注释/* */。
HEREDOC 语法检测 — fatal
匹配模式 | 说明 |
|---|---|
| HEREDOC 块内含 PHP 标签 |
仅扫描
.php文件。HEREDOC 语法中需使用{$variable}语法嵌入 PHP 变量,避免使用<?php echo ... ?>导致解析错误。
Service 类 SQL 未定义变量检测 — fatal
匹配模式 | 说明 |
|---|---|
| Service 类拼接 SQL 使用未定义变量 |
仅扫描
model/目录下 PHP 文件。Service 类中拼接 SQL 表名必须用$this->tablepre . '表名',不能使用未定义变量(如$tableName、$tablePrefix)。
APP_PATH 误用检测 — fatal
匹配模式 | 说明 |
|---|---|
| script src 用 APP_PATH |
| link href 用 APP_PATH |
仅扫描
.htm/.html文件。APP_PATH是文件系统绝对路径,浏览器无法访问,必须用$conf['view_url']生成资源 URL。
Hook 文件头检测
分类 | 级别 | 匹配模式 | 说明 |
|---|---|---|---|
| fatal |
| 会白屏!只能用 |
| info |
| 安全防护缺失,建议添加 exit 保护 |
检测
hook/*.*文件的首行内容。.htm 模板 hook 文件会被编译拼进模板执行,以<?php exit;开头会白屏;.php 源码 hook 文件建议以<?php exit;开头防止直接访问。
安全规则
危险函数 — warning
匹配模式 | 说明 |
|---|---|
| 代码注入/命令执行风险,应避免使用 |
权限安全(敏感字段修改)— warning
匹配模式 | 说明 |
|---|---|
| 应使用 |
| 应使用 |
| salt 由系统自动管理,不应直接修改 |
| 应使用 |
POST 表单缺少 CSRF 令牌 — warning
匹配模式 | 说明 |
|---|---|
| 所有 POST 表单必须包含 CSRF 令牌 |
此规则采用文件级检测:读取整个文件内容,检查是否同时包含
method="post"和CsrfService/csrf_token,若缺少则报告。
前端 MD5 哈希检测 — warning
匹配模式 | 说明 |
|---|---|
| 前端 MD5 哈希代码 |
| 前端 MD5 哈希代码 |
密码必须明文提交,由服务端
password_md5()处理。前端 MD5 哈希已移除,MD5.js 不得全局加载。
MD5.js 全局加载检测 — warning
匹配模式 | 说明 |
|---|---|
| 模板文件全局加载 md5.js |
仅扫描
.htm/.html文件。MD5.js 不得全局加载,前端 MD5 哈希已移除。
user_update 修改密码检测 — warning
匹配模式 | 说明 |
|---|---|
| 使用 user_update() 修改 password 字段 |
找回密码必须使用
user__update()而非user_update(),因为后者会过滤掉 password 字段。
数据库字符集检测 — warning
匹配模式 | 说明 |
|---|---|
| 数据库连接字符集为 utf8 |
| 设置字符集为 utf8 |
数据库连接字符集必须为 utf8mb4(支持 emoji 等 4 字节字符)。
裸 htmlspecialchars 检测 — warning
匹配模式 | 说明 |
|---|---|
| 裸写 htmlspecialchars 函数 |
禁止裸写
htmlspecialchars,必须用esc_html()/esc_attr()/esc_js()统一转义。
Bootstrap Tab 误用检测 — warning
匹配模式 | 说明 |
|---|---|
| 外层导航误用 Bootstrap Tab |
仅扫描
.htm/.html文件。外层导航(页面跳转)禁止用 Bootstrap Tab 系统(role="tablist"+data-bs-toggle="tab"),否则 Bootstrap 会把<a href="./?xxx.htm">的 href 当作 CSS 选择器,触发SyntaxError。应改为普通<a>链接跳转;内层导航(同一页面内的 tab 切换)才用 tab。
db_find_one 参数类型检测 — warning
匹配模式 | 说明 |
|---|---|
| 第 4 参数为字符串字面量 |
db_find_one()第 4 个参数$col必须传入数组(如array('fid', 'uid')),禁止传入字符串以避免implode()参数类型错误。
install.php 非幂等建表检测 — warning
匹配模式 | 说明 |
|---|---|
| 建表语句不幂等 |
仅对
install.php文件检测。install.php 所有建表语句必须用IF NOT EXISTS保证幂等。
Bootstrap 迁移规则
BS4 旧类名 — medium
匹配模式 | 替换建议 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 直接 |
BS4 旧 data 属性 — medium
匹配模式 | 替换建议 |
|---|---|
|
|
|
|
|
|
|
|
|
|
BS3 旧类名 — medium
匹配模式 | 替换建议 |
|---|---|
|
|
|
|
|
|
|
|
|
|
| Tabler Icons |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Bootstrap jQuery 插件调用 — warning
匹配模式 | 替换建议 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| htmx |
|
|
.button('loading')在 Bootstrap 5 中已移除,应使用 htmxhx-disabled-elt在请求期间禁用按钮,或用原生 JS 设置btn.disabled = true。
前端迁移规则
jQuery 使用 — medium
匹配模式 | 替换建议 |
|---|---|
| htmx 或原生 |
| htmx |
| htmx |
|
|
| 原生 JS class 或 htmx 组件 |
|
|
|
|
|
|
|
|
|
|
| 特性检测 |
|
|
|
|
| htmx 4 属性或原生 JS |
Fontello 旧图标 — medium
匹配模式 | 替换建议 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
非 Tabler Icons 图标库 — medium
匹配模式 | 说明 |
|---|---|
| Font Awesome 图标 → Tabler Icons |
| Bootstrap Icons → Tabler Icons |
| Glyphicon 图标 → Tabler Icons |
采用
class属性正则匹配模式,避免误匹配fa-出现在其他上下文(如变量名、注释)中。建议生成时会提取具体的fa-xxx/bi-xxx/glyphicon glyphicon-xxx类名,提示用户参考 Tabler Icons 查找对应图标。
代码规范规则
直接数据库操作 — info
仅检测 model/ 目录下的 PHP 文件中的原始 SQL 函数,避免业务路由的误报。
匹配模式 | 说明 |
|---|---|
| 原始 SQL 执行(非 SELECT 语句),注意 SQL 注入风险 |
| 原始 SQL 单条查询,建议优先使用 |
| 原始 SQL 多条查询,建议优先使用 |
共 3 个
db_*函数。此规则排除install.php、uninstall.php、unstall.php、upgrade.php文件(安装/升级脚本中直接操作数据库是合理的)。
保留注释抑制机制:
对于无法用 db_find* 替代的复杂 SQL(JOIN、系统表、复杂聚合等),在代码中添加包含 保留 db_sql_find 或 保留 db_exec 关键字的注释,扫描器会自动跳过后续 10 行的 direct_db 报告:
// 联表查询,db_find 不支持 JOIN,保留 db_sql_find
$sql = "SELECT a.*, b.name FROM a LEFT JOIN b ON a.id=b.id";
$rows = db_sql_find($sql); // 此行不会被报告
支持的关键字格式(正则 /(保留|@suppress).*db_(?:sql_find|sql_find_one|exec)/):
// 保留 db_sql_find// 保留 db_sql_find_one// 保留 db_exec// @suppress db_sql_find
注释必须放在 db_* 调用上方 10 行以内。抑制区间为注释行 + 后续 10 行,覆盖多行 SQL 拼接。
迁移指南:
原始函数 | 替代方案 | 适用场景 |
|---|---|---|
|
| 单条记录查询(无 GROUP BY) |
|
| 单条聚合查询(含 GROUP BY + COUNT/SUM/MAX) |
|
| 多条记录查询(无 GROUP BY) |
|
| 多条聚合查询(含 GROUP BY + COUNT/SUM/MAX) |
| 已封装,可直接使用 | 计数查询 |
| 视语句类型选择 | 非 SELECT 语句 |
保留 db_sql_find / db_exec 的合理场景:
以下复杂 SQL 无法用 db_find* 系列替代,可在代码中添加注释说明保留原因:
JOIN 查询:
SELECT ... FROM a LEFT JOIN b ON ...(db_find不支持 JOIN)系统表查询:
SELECT ... FROM INFORMATION_SCHEMA.*(如列存在性检查)非 SELECT 语句:
db_exec执行UPDATE ... SET x = GREATEST(x-1, 0)、INSERT IGNORE ... SELECT、含子查询的 DML时间函数聚合:
SELECT ... WHERE HOUR(FROM_UNIXTIME(...))(db_cond不支持 SQL 函数)
db_find_group / db_find_one_group 使用示例:
// 替代:db_sql_find_one("SELECT uid, COUNT(*) as cnt FROM tbl GROUP BY uid HAVING cnt > 5")
$row = db_find_one_group(
'tbl', // 表名(不含前缀)
[], // WHERE 条件
['uid'], // GROUP BY 字段
['cnt' => ['>' => 5]], // HAVING 条件(格式同 WHERE)
[], // ORDER BY
['uid', 'COUNT(*) as cnt'] // SELECT 字段(聚合字段必须用别名)
);
// 替代:db_sql_find("SELECT fid, COUNT(*) as cnt FROM tbl GROUP BY fid ORDER BY cnt DESC LIMIT 10")
$rows = db_find_group(
'tbl',
[],
['fid'],
[],
['cnt' => -1], // -1 降序,1 升序
1, 10, // page, pagesize
'fid', // 返回数组的 key 字段
['fid', 'COUNT(*) as cnt']
);
其他规则
conf.json 版本检查 — info
检查 conf.json 中 bbs_version 字段,若 < 4.5 则提示更新。
Hook 文件名检查 — info
检查 hook/ 目录下文件名是否符合 [a-z_][a-z0-9_]* 命名规范。
特殊处理逻辑
missing_csrf(跨行检测)
POST 表单的 CSRF 检测不能逐行匹配,因为 method="post" 和 CsrfService::input() 可能分布在文件不同位置。因此采用文件级检测:
读取整个文件内容
检查是否包含
method="post"若包含,再检查是否包含
CsrfService或csrf_token仅当 POST 表单存在且缺少 CSRF 令牌时才报告
direct_db(文件名排除)
安装/升级脚本中直接操作数据库是合理的,因此排除以下文件:
install.phpuninstall.phpunstall.php(Xiuno 旧版拼写)upgrade.php
service_undefined_var(路径限制)
仅在 model/ 目录下的 PHP 文件中检测,避免误报业务路由中的同名变量。检测条件:当前行包含 SQL 关键字(SELECT/INSERT/UPDATE/DELETE/FROM/INTO)且引用了 $tableName 或 $tablePrefix。
文件级跨行检测
以下规则需要读取整个文件内容并使用正则跨行匹配,无法逐行扫描:
分类 | 检测方式 |
|---|---|
| 检查 |
| 逐行匹配 |
| 正则匹配 |
| 正则匹配 |
| 正则匹配 |
| 正则匹配 |
| 正则匹配 |
| 读取 |
API 接口
扫描请求
GET /*****/?plugin-scanner-do.htm
返回 JSON 数组,每个元素代表一个插件的扫描结果:
[
{
"dir": "xn_tag",
"name": "标签",
"version": "1.0",
"bbs_version": "4.0",
"installed": true,
"enable": true,
"issues": [
{
"file": "plugin/xn_tag/hook/post_js.htm",
"line": 15,
"category": "jquery_usage",
"match": "$.ajax(",
"suggestion": "使用 htmx 或原生 fetch 替代 $.ajax",
"severity": "medium",
"context": "$.ajax({url: '...'})"
}
],
"total": 3,
"fatal": 0,
"warning": 1
}
]
CSV 导出
GET /*****/?plugin-scanner-export.htm
下载 CSV 文件,包含所有插件的扫描结果。
指定插件导出
GET /*****/?plugin-scanner-export.htm&dir=xn_tag
仅导出指定插件的扫描结果。
扩展规则
如需添加新的扫描规则,按以下步骤操作:
行级检测规则
在
lib/PluginScannerRules.php的getRules()中添加新分类和匹配模式(仅适用于行级检测)在
getSeverityLevels()中添加严重级别映射在
getCategoryNames()中添加中文名称如果是 PHP-only 规则,在
getPhpOnlyCategories()中添加(避免扫描 JS/CSS 内容)如果是 HTML-only 规则,在
getHtmlOnlyCategories()中添加(避免扫描纯 PHP 文件)对于需要动态建议的分类,在
lib/PluginScannerSuggestion.php的build()方法中添加 case 分支在
lang/zh-cn/bbs_*****.php、lang/zh-tw/bbs_*****.php、lang/en-us/bbs_*****.php中添加plugin_scanner_cat_*语言项
文件级检测规则
对于跨行检测、文件头检测等无法逐行扫描的规则,在 lib/PluginScanner.php 的 scanPluginDir() 方法中添加相应代码,使用 buildIssue() 辅助方法构建 issue 数组。
严重级别约定
fatal:PHP 8.0+ 下直接报错或崩溃(如废弃函数、注释陷阱、HEREDOC 错误、APP_PATH 误用)
warning:安全风险或功能异常(如 CSRF 缺失、MD5 哈希、字符集错误、Tab 误用)
medium:不兼容但不会崩溃,需要迁移(如 BS4/BS3 旧类名、jQuery 使用、图标库)
info:建议优化,非必须(如 direct_db、conf.json 版本、Hook 文件名)


