[开发]vn_block(屏蔽用户)插件修复报告

👑Lv.11 元老 🌞 骨干会员
2026-10-25 01:21:21

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/vn_block插件修复报告_2026-09-23.md。

vn_block(屏蔽用户)插件修复报告

  • 修复对象:plugins/vn_block/(对应审计报告 docs/vn_block插件审计报告_2026-09-23.md)
  • 修复日期:2026-09-23
  • 版本:1.0.0 → 1.0.1
  • 发布状态:仍未进入任何发布包(update/ / CHANGELOG.md 无记录)
  • 修复范围:审计报告 P0 全部 1 项 + P1 全部 4 项 + P2 全部 4 项 + P3 全部 5 项,共 14 项,另含修复过程中新发现的 3 项(见 §4)

一、结论速览

编号问题状态验证方式
P0-1点击屏蔽/取消屏蔽 404✅ 已修HTTP 实测:7 种回跳场景全部正确
P1-1缺 class_alias → 三生命周期方法静默跳过✅ 已修class_exists('\Plugin\vn_block\Plugin') = true
P1-216 个文件 CRLF✅ 已修全量复核:零 CR、零 BOM
P1-3后台路由注册在 route_register✅ 已修新建 admin_route_register.php + 重建缓存,后台页 200
P1-4#replies 内表单无 hx-disinherit✅ 已修渲染输出核对(18 处中 9 处为本插件表单)⚠️ 未做浏览器端验证
P2-1init_after 每请求跑 DDL✅ 已修戳文件已生成;代码路径核对
P2-2uninstall() 漏删 blk_settings✅ 已修副本干跑:两张表全部清空
P2-3搜索页过滤映射键写错✅ 已修A/B 实测:屏蔽者 0 命中 / 未屏蔽者 1 命中
P2-4视图未用 section('css')/section('title')✅ 已修渲染输出:<title> 正确、CSS 走 section
P3-1nav 钩子忽略 placement✅ 已修按 PC / 移动分别渲染
P3-2详情页 CSS 重复注入✅ 已修实测 CSS link 数 2 → 1
P3-3硬编码中文「未知用户」✅ 已修三语新增 unknown_user 键
P3-4图标硬编码与 plugin.json 不一致✅ 已修统一为 user-slash(&#xf506;)
P3-5setSetting() 不清请求级缓存✅ 已修缓存改为静态属性 + 写入失效

门禁全绿:编码/行尾 PASS、php -l PASS、零内联 PASS、无残留文件 PASS、i18n 16 键三语零差异 PASS、CSS 变量 8/8 存在且无硬编码色 PASS。


二、P0-1:404 的修复(核心改动)

改法

/block/toggle 是 POST-only 路由,控制器不能用 REQUEST_URI 回跳(会 302 回自身 → GET → 404)。改为「表单隐藏域 return_to + 服务端四重校验」:

// FrontController.php — toggle()
$this->redirect($this->safeReturnTo((string)($_POST['return_to'] ?? '')));

// FrontController.php:82 新增
private function safeReturnTo(string $raw): string
{
    $raw = \trim($raw);
    if ($raw === '' || $raw[0] !== '/') return '/blocked';           // 空 / 非根相对路径
    if (\str_starts_with($raw, '//')                                  // 协议相对 URL
        || \strpos($raw, '\\') !== false                              // 反斜杠(代理可归一为协议前缀)
        || \strpbrk($raw, "\r\n") !== false                           // 响应头注入
        || \strpos($raw, 'block/toggle') !== false                    // 回跳自身 → 死循环 404
    ) return '/blocked';
    return $raw;
}

三个入口的 return_to 来源:

入口`return_to` 值位置
主帖按钮$_SERVER['REQUEST_URI'](渲染期即当前详情页)thread_operation_after.php:26
回复按钮同上post_operation_after.php:26
管理页取消屏蔽固定 /blockedviews/manage.php:46

currentPath() 方法已删除(无用且是缺陷源头)。没有给 /block/toggle 补 GET 路由——那会把写操作暴露给预取器/爬虫。

实测(HTTP,登录态 user_id=2)

① return_to=/thread/89      -> 302 Location: /thread/89      ← 主路径修复
② return_to=/blocked        -> 302 Location: /blocked
③ return_to=//evil.com      -> 302 Location: /blocked        ← 开放重定向被拦
④ return_to=https://evil.x  -> 302 Location: /blocked        ← 被拦
⑤ return_to=/block/toggle   -> 302 Location: /blocked        ← 自跳被拦
⑥ 无 return_to              -> 302 Location: /blocked        ← 兜底
⑦ return_to=/\a(反斜杠)    -> 302 Location: /blocked        ← 被拦

GET /thread/89 -> 200     GET /blocked -> 200     (跳转目标均可达,404 消失)


三、其余修复明细

P1-1 class_alias(Plugin.php:271)

if (!\class_exists('Plugin\\vn_block\\Plugin', false)) {
    \class_alias(Plugin::class, 'Plugin\\vn_block\\Plugin', false);
}

实测:Plugin\vn_block\Plugin false → true,method_exists(..., 'uninstall') = true → 卸载不再留孤儿表。

P1-2 CRLF → LF(16 个文件)

用 Python 二进制读写逐个转换,转换前后 sha256 均记录(例:Plugin.php 5b5e48e6 → db3ce619、style.css 702b52c9 → 203bb8fb)。复核:全插件零 CR、零 BOM。

先转行尾再改代码,避免新写入的行与旧 CRLF 混行。

P1-3 后台路由改走专用钩子

  • 新建 hook/admin_route_register.php(路径不含 /admin,核心 admin 分组已带前缀):

```php

$router->get('/vn-block', ['\\Plugin\\VnBlock\\AdminController', 'settings']);

$router->post('/vn-block', ['\\Plugin\\VnBlock\\AdminController', 'save']);

```

  • route_register.php 删除这 2 行;plugin.json hooks 增补 admin_route_register;
  • Plugin::refresh() 重建 plugins/plugins_cache.json(已确认新钩子在缓存中、8 个 hook 文件全部存在);
  • AdminController.php 顶部错误注释("admin_route_register 未被核心触发")已改正。

实测:GET /admin/vn-block → 200(含侧栏、<title>贴子内的屏蔽按钮),POST → 302 /admin/vn-block?saved=1。

P1-4 hx-disinherit="*"

两个操作区钩子的 <form> 均加 hx-disinherit=""。渲染输出核对:页面内 18 处 hx-disinherit="" = 9 处核心楼层容器(thread/show.php:252)+ 9 处本插件表单。

⚠️ 本机无 Chromium,htmx 的实际接管行为未做浏览器端验证,建议在浏览器里点一次回复区的「屏蔽」确认。

P2-1 init_after 加戳文件守卫

$__stamp  = __DIR__ . '/../../../protected/.vn_block_table_cache';
$__dbFile = __DIR__ . '/../data/vn_block.sqlite';
if (is_file($__stamp) && is_file($__dbFile)) return;   // 两条件同时满足才短路
try { \Plugin\VnBlock\Plugin::activate(); @file_put_contents($__stamp, '1', LOCK_EX); } ...

比仓库既有 7 个插件多判了一个「库文件存在」——避免库被删(手工清理 data/、换服务器只传代码)时表永远建不出来。实测戳文件已生成(protected/.vn_block_table_cache,1 字节,与其余 7 个同款)。

P2-2 uninstall() 补删表

foreach (['blk_blocks', 'blk_settings'] as $__table) {
    try { self::db()->exec('DROP TABLE IF EXISTS ' . $__table); } catch ...
}

在库副本上干跑(不动真库):['blk_blocks','sqlite_sequence','blk_settings'] → ['sqlite_sequence']。

唯一残留是 SQLite 内部表 sqlite_sequence(AUTOINCREMENT 自带、不可删、无数据),非插件表。

P2-3 列表过滤映射修正

$listMap = [
    'forum/index'        => ['latestThreads'],
    'forum/category'     => ['latestThreads'],
    'home/index'         => ['latestThreads'],
    'home/index_default' => ['latestThreads'],
    'tags/show'          => ['threads'],
    'search/index'       => ['threadResults', 'results'],   // 原写 'threads'(不存在)→ 死映射
];

另外 Plugin::filterThreads() 兼容两种作者字段:装饰后行用 user_id、topic_index 原始行用 uid(覆盖搜索 LIKE 降级分支)。

A/B 实测(搜索「微博」):

身份命中被屏蔽作者的帖子
uid1(已屏蔽 275)0
uid2(未屏蔽)1

P2-4 视图区块规范化

两视图均改为 section('title') + section('css'),CSS 从内容区移到 css 区。

实测 /blocked → <title>我的屏蔽列表 - 燧石社区</title>;/admin/vn-block → <title>贴子内的屏蔽按钮 - 燧石社区</title>。

P3-1 / P3-4 导航钩子

按 $params['placement'] 分支:PC 用 .mn-dropdown-item(核心已补样式),移动端用裸 <a>(容器 .mn-mobile-nav-mine-menu a 是 flex + gap:8px,<i>+<span> 即可对齐)。图标统一为 user-slash(&#xf506;,与 plugin.json 一致),字号沿用核心工具类 mn-fs-14(零内联)。

P3-2 CSS 重复注入

注入条件改为「仅 htmx 局部请求才在 body 补注入」($_SERVER['HTTP_HX_REQUEST']):整页加载由 layout_head_end 的 head 注入负责,htmx 导航(只换 body、丢弃 head)由 body 兜底。

实测:/thread/89 整页加载 CSS link 2 → 1;带 HX-Request: true 时仍为 2(head 被 htmx 丢弃、body 生效)——符合预期。

P3-3 / P3-5

  • 三语各新增 3 键:user_not_found({name} 占位)、cannot_block_self、unknown_user;视图/控制器不再硬编码中文。顺带补了「按用户名屏蔽时命中自己」的提示(原先提交后毫无反应)。
  • setting() 的 static $cache 改为类静态属性 self::$settingCache,setSetting() 写入后 unset 该键。

四、修复过程中新发现并一并修掉的 3 项

#问题影响修复
N-1两视图的 CSS 版本号路径写错:__DIR__ . '/../../assets/style.css'(视图在 views/,../../assets 指向 plugins/assets,恒不命中)?v= 永远退化成 v=1 → 改 CSS 后浏览器不刷新(静默)改为 ../assets/style.css;实测版本号由 v=1 变为 v=d34b2380
N-2'profile/index' => 'userThreads' 语义错误:/profile 是「我的资料」页,userThreads 恒为本人帖子(ProfileController.php:122),过滤永远无意义死映射(无害但误导)删除该映射;改为支持公开个人主页 plugins/user_profile/front:主页主人被屏蔽时清空其帖子/博客列表
N-3AdminController.php 注释断言「admin_route_register 未被核心触发」——与核心 index.php:276 事实不符误导后续维护者随 P1-3 一并改正

N-2 的 A/B 实测(/u/275,uid1 已屏蔽 275):

身份页面命中该用户帖子标题数
uid1(已屏蔽)0(列表已清空)
uid2(未屏蔽)1


五、改动文件清单(20 个文本文件,sha256 前 12 位)

文件sha256说明
Plugin.php1fbc26978594uninstall 补删表 / 缓存静态化 / filterThreads 兼容 uid / class_alias
FrontController.php4c74347e684areturn_to + safeReturnTo / 删除 currentPath / i18n 提示
AdminController.phpb2e4f01e0ba3注释修正 + @version
plugin.jsoncbb09b8a2f9bversion 1.0.1 + 新增 admin_route_register
hook/admin_route_register.phpe014b1404ad3新增
hook/route_register.phpad99f825c55e移除后台路由
hook/init_after.phpf52d78ecc5ea戳文件守卫
hook/controller_view_before.php537e9f82f377映射修正 + 个人主页支持
hook/thread_operation_after.php4e0370bd8ad9return_to + hx-disinherit + CSS 注入条件
hook/post_operation_after.phpe24b68361d2d同上
hook/nav_user_menu_items.phpbeded4530608placement 分支 + 图标统一
hook/layout_head_end.phpee9bd8472c70仅行尾转换(逻辑未变)
views/manage.phpc3f339de8815section 区块 / return_to / 版本号路径 / i18n
views/admin_settings.phpe39872953e75section 区块 / 版本号路径
lang/zh.php74f5a56039c5+3 键
lang/en.php37dd2e03bb0d+3 键
lang/zh_tw.php347aae675156+3 键
assets/style.css203bb8fb0c5a仅行尾转换(逻辑未变)
data/.htaccess / data/web.config未变自动生成的防护文件

连带产物:plugins/plugins_cache.json 已重建(vn_block 条目含 8 个钩子、version 1.0.1);protected/.vn_block_table_cache 运行时戳文件(与其余 7 个插件同款)。


六、回归验证矩阵(全部实测)

项命令/方式结果
编码行尾Python 数 \r / BOMPASS(零 CR、零 BOM)
语法php -l × 20PASS
零内联正则扫 views/hook/cssPASS(零命中)
残留文件.bak/.revert/.tmp 扫描PASS(无)
i18n三语键集合/键序/引用比对PASS(各 16 键,缺 0 多 0,键序一致)
CSS变量存在性 + 硬编码色 + 花括号PASS(8/8 存在、无硬编码、22/22 配对)
生命周期类名class_exists + method_existsPASS
P0 回跳7 种 return_to全部正确(详见 §2)
详情页/thread/89200;占位提示生效;9 表单带 return_to;CSS 1 份
管理页/blocked200;标题正确;return_to=/blocked
后台页/admin/vn-block GET/POST200 / 302 saved=1
htmx 路径带 HX-Requestbody 补注入生效
搜索过滤A/B(uid1 vs uid2)0 vs 1 ✅
个人主页过滤A/B(uid1 vs uid2)0 vs 1 ✅
uninstall库副本干跑两表清空 ✅
戳文件删后请求自动重建 ✅

探针清理:blk_blocks 探针行(2→3)按内容标记删除,现 MAX(id)=2、行数 2 = 原始真实数据(1→43、1→275);blk_settings 恢复 0 行;2 个探针会话已 DELETE(残留 0);13 个临时文件已删。真库仅剩原始数据,未误删任何真实行。


七、未做 / 待确认

  1. 浏览器端验证缺失:本机无 Chromium,P1-4 的 htmx 隔离效果、以及 P3-1 的移动端菜单视觉对齐,建议老搭档在浏览器里各点一次确认(PC 用户下拉 + 移动端「我的」+ 回复区屏蔽按钮)。
  2. 发布包未动:update/ 与 CHANGELOG.md 未更新(等确认后再走发布流程;注意 version.php 是核心版本单一源,与插件 plugin.json 版本无关)。
  3. 搜索 LIKE 降级分支:filterThreads 已兼容 uid 字段,但该分支未实测触发(倒排索引正常时不会走到)。
  4. 回滚方式:本插件未进发布包,回滚 = 用 git/备份还原 plugins/vn_block/ 目录 + 重建缓存(Plugin::refresh())。未产生任何核心文件改动,data/ 与 assets/uploads/ 未被触碰。

修复报告完。

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| Views 2 | Replies 0

All Replies (0)

No replies yet

Please Log in