
# 插件兼容性扫描器
## 概述
插件兼容性扫描器用于检测已安装插件中与 Xiuno BBS 4.5+ 现代化改造不兼容的代码,涵盖 PHP 8.0+ 语法、Bootstrap 5 迁移、jQuery 去除、安全规范等方面。
**入口**:后台 → 插件管理 → 插件兼容性检查(`/admin/?plugin-scanner.htm`)
## 相关文件
| 文件 | 说明 |
|------|------|
| `lib/PluginScanner.php` | 扫描器核心,定义规则与扫描逻辑 |
| `admin/route/plugin_scanner.php` | 后台路由,处理扫描/导出请求 |
| `admin/view/htm/plugin_scanner.htm` | 扫描器页面模板 |
| `lang/zh-cn/bbs_admin.php` | 中文语言项(`plugin_scanner_*`) |
| `lang/en-us/bbs_admin.php` | 英文语言项 |
## 使用方式
1. 进入后台「插件兼容性检查」页面
2. 点击「开始扫描」按钮
3. 前端通过 `fetch()` 请求 `?plugin-scanner-do.htm`,后端返回 JSON 格式扫描结果
4. 结果按插件分组展示,支持按严重级别筛选
5. 可导出 CSV 报告
## 扫描范围
扫描 `plugin/` 目录下所有包含 `conf.json` 的插件目录,递归检查以下扩展名的文件:
- `.php` — PHP 代码
- `.htm` / `.html` — 模板文件
- `.js` — JavaScript 脚本
- `.css` — 样式文件
自动跳过 `img/`、`images/`、`fonts/` 目录。
## 规则类别
### 严重级别说明
| 级别 | 含义 |
|------|------|
| **fatal** | 致命:代码在 PHP 8.0+ 下会直接报错或崩溃 |
| **warning** | 警告:存在安全风险或功能异常 |
| **medium** | 中等:不兼容但不会直接崩溃,需要迁移 |
| **info** | 信息:建议优化,非必须 |
### PHP 兼容性规则
#### 废弃函数(PHP 7.x 移除)— fatal
检测 `mysql_*` 系列函数及其他在 PHP 7.x 已移除的函数:
| 匹配模式 | 说明 |
|----------|------|
| `mysql_connect` 等 19 个 | mysql 扩展已在 PHP 7.0 移除 |
| `each(` | PHP 7.2 废弃,8.0 移除 |
| `create_function(` | PHP 7.2 废弃,8.0 移除,用闭包替代 |
| `split(` / `spliti(` | PHP 7.0 移除,用 `preg_split()` 或 `explode()` |
| `ereg(` / `ereg_replace(` 等 | PHP 7.0 移除,用 `preg_match()` / `preg_replace()` |
| `call_user_method(` | PHP 7.0 移除,用 `call_user_func()` |
#### PHP 8 不兼容语法 — fatal
| 匹配模式 | 说明 |
|----------|------|
| `&new ` | 按引用创建对象已移除,改用 `new` |
| `preg_replace.*\/e` | `/e` 修饰符已移除,用 `preg_replace_callback()` |
#### 花括号数组访问 — fatal
| 匹配模式 | 说明 |
|----------|------|
| `{$` | `{$arr['key']}` 语法已移除,改用 `[$arr['key']]` |
#### HTTP_*_VARS 变量 — fatal
| 匹配模式 | 说明 |
|----------|------|
| `HTTP_POST_VARS` 等 | 已移除,用 `$_POST` / `$_GET` / `$_SESSION` |
#### PHP 8.0+ 废弃函数 — fatal
| 匹配模式 | 说明 |
|----------|------|
| `get_magic_quotes_gpc` | PHP 7.4 废弃、8.0 移除,始终返回 false |
| `get_magic_quotes_runtime` | PHP 7.4 废弃、8.0 移除 |
| `utf8_encode(` | PHP 8.2 废弃,用 `mb_convert_encoding()` |
| `utf8_decode(` | PHP 8.2 废弃,用 `mb_convert_encoding()` |
| `money_format(` | PHP 7.4 废弃、8.0 移除,用 `NumberFormatter` |
| `is_resource(` | 对 PDO/MySQLi 对象返回 false(PHP 8.0+),改用 `instanceof` |
### 安全规则
#### 危险函数 — warning
| 匹配模式 | 说明 |
|----------|------|
| `eval(` / `system(` / `exec(` 等 | 代码注入/命令执行风险,应避免使用 |
#### 权限安全(敏感字段修改)— warning
| 匹配模式 | 说明 |
|----------|------|
| `user_update.*password` | 应使用 `user_change_password()` |
| `user_update.*gid` | 应使用 `user_change_group()` |
| `user_update.*salt` | salt 由系统自动管理,不应直接修改 |
| `user_update.*password_hash` | 应使用 `user_change_password()` |
#### POST 表单缺少 CSRF 令牌 — warning
| 匹配模式 | 说明 |
|----------|------|
| `method="post"` 但无 `CsrfService` / `csrf_token` | 所有 POST 表单必须包含 CSRF 令牌 |
> 此规则采用文件级检测:读取整个文件内容,检查是否同时包含 `method="post"` 和 `CsrfService`/`csrf_token`,若缺少则报告。
### Bootstrap 迁移规则
#### BS4 旧类名 — medium
| 匹配模式 | 替换建议 |
|----------|----------|
| `ml-` | `ms-` |
| `mr-` | `me-` |
| `pl-` | `ps-` |
| `pr-` | `pe-` |
| `form-group` | `mb-3` |
| `custom-select` | `form-select` |
| `custom-control` | `form-check` |
| `btn-block` | `w-100` |
| `input-group-prepend` / `input-group-append` | 直接 `input-group-text` |
#### BS4 旧 data 属性 — medium
| 匹配模式 | 替换建议 |
|----------|----------|
| `data-toggle` | `data-bs-toggle` |
| `data-dismiss` | `data-bs-dismiss` |
| `data-target` | `data-bs-target` |
| `data-slide-to` | `data-bs-slide-to` |
| `data-slide` | `data-bs-slide` |
#### BS3 旧类名 — medium
| 匹配模式 | 替换建议 |
|----------|----------|
| `panel-heading` | `card-header` |
| `panel-body` | `card-body` |
| `panel-footer` | `card-footer` |
| `panel-default` 等 | `card` + 颜色 |
| `well` | `card .card-body` 或自定义样式 |
| `glyphicon` | Tabler Icons `ti-*` |
| `pull-left` | `float-start` |
| `pull-right` | `float-end` |
| `hidden-xs` | `d-none .d-sm-block` |
| `visible-xs` | `d-sm-none` |
| `label-default` 等 | `badge .bg-*` |
| `img-responsive` | `img-fluid` |
| `img-circle` | `rounded-circle` |
| `img-rounded` | `rounded` |
| `col-xs-` | `col-`(xs 断点已移除) |
#### Bootstrap jQuery 插件调用 — warning
| 匹配模式 | 替换建议 |
|----------|----------|
| `.modal(` | `new bootstrap.Modal()` |
| `.dropdown(` | `new bootstrap.Dropdown()` |
| `.tooltip(` | `new bootstrap.Tooltip()` |
| `.popover(` | `new bootstrap.Popover()` |
| `.collapse(` | `new bootstrap.Collapse()` |
| `.carousel(` | `new bootstrap.Carousel()` |
| `.alert(` | `new bootstrap.Alert()` |
| `.button(` | `new bootstrap.Button()` |
| `.tab(` | `new bootstrap.Tab()` |
### 前端迁移规则
#### jQuery 使用 — medium
| 匹配模式 | 替换建议 |
|----------|----------|
| `$.ajax(` | htmx 或原生 `fetch` |
| `$.post(` | htmx `hx-post` 或原生 `fetch` |
| `$.get(` | htmx `hx-get` 或原生 `fetch` |
| `$.each(` | `Array.forEach()` |
| `$.fn.` | Alpine.js 组件 |
| `$.extend(` | `Object.assign()` |
| `$.trim(` | `String.trim()` |
| `$.parseJSON(` | `JSON.parse()` |
| `$.isArray(` | `Array.isArray()` |
| `$.isFunction(` | `typeof fn === "function"` |
| `$.browser` | 特性检测 |
| `$(document).ready` | `DOMContentLoaded` |
| `$(function(` | `DOMContentLoaded` |
| `jQuery(` | htmx + Alpine.js |
#### Fontello 旧图标 — medium
| 匹配模式 | 替换建议 |
|----------|----------|
| `icon-lock` | `ti-lock` |
| `icon-home` | `ti-home` |
| `icon-edit` | `ti-pencil` |
| `icon-remove` | `ti-trash` |
| `icon-eye` | `ti-eye` |
| `icon-ok` | `ti-check` |
| `icon-cog` / `icon-cogs` | `ti-settings` |
| `icon-comment` | `ti-message` |
| `icon-user` | `ti-user` |
| `icon-envelope` | `ti-mail` |
| `icon-key` | `ti-key` |
| `icon-star` | `ti-star` |
#### 非 Tabler Icons 图标库 — medium
| 匹配模式 | 说明 |
|----------|------|
| `fa-` | Font Awesome 图标 → Tabler Icons `ti-*` |
| `bi-` | Bootstrap Icons → Tabler Icons `ti-*` |
| `glyphicon-` | Glyphicon 图标 → Tabler Icons `ti-*` |
### 代码规范规则
#### 直接数据库操作 — info
| 匹配模式 | 说明 |
|----------|------|
| `db_exec(` / `db_update(` / `db_insert(` / `db_delete(` / `db_find(` / `db_create(` / `db_drop(` | 建议使用 model 层封装 |
> 此规则排除 `install.php`、`uninstall.php`、`unstall.php`、`upgrade.php` 文件(安装/升级脚本中直接操作数据库是合理的)。
### 其他规则
#### 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()` 可能分布在文件不同位置。因此采用文件级检测:
1. 读取整个文件内容
2. 检查是否包含 `method="post"`
3. 若包含,再检查是否包含 `CsrfService` 或 `csrf_token`
4. 仅当 POST 表单存在且缺少 CSRF 令牌时才报告
### direct_db(文件名排除)
安装/升级脚本中直接操作数据库是合理的,因此排除以下文件:
- `install.php`
- `uninstall.php`
- `unstall.php`(Xiuno 旧版拼写)
- `upgrade.php`
## API 接口
### 扫描请求
```
GET /admin/?plugin-scanner-do.htm
```
返回 JSON 数组,每个元素代表一个插件的扫描结果:
```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 /admin/?plugin-scanner-export.htm
```
下载 CSV 文件,包含所有插件的扫描结果。
### 指定插件导出
```
GET /admin/?plugin-scanner-export.htm&dir=xn_tag
```
仅导出指定插件的扫描结果。
## 扩展规则
如需添加新的扫描规则,编辑 `lib/PluginScanner.php`:
1. 在 `$rules` 数组中添加新类别和匹配模式
2. 在 `$severityLevels` 数组中添加严重级别映射
3. 在 `getCategoryName()` 方法的 `$names` 数组中添加中文名称
4. 在 `lang/zh-cn/bbs_admin.php` 和 `lang/en-us/bbs_admin.php` 中添加 `plugin_scanner_cat_*` 语言项
如需特殊处理逻辑(如跨行检测、文件名排除),在 `scanPluginDir()` 方法中添加相应代码。