B2 主题的统计插件(b2_tongji)本站一直在用。B2 主题升级到 5.9.x 之后,这个插件陆续出了问题:先是后台首页样式全乱,接着发现签到、搜索、下载三个管理页也一条数据都没有。
下面是完整的排查与修复记录。四条故障里,两条是插件本身的问题,一条是 B2 升级后数据表变更导致的,还有一条是排查过程中容易踩的坑。
环境
- WordPress 6.x / PHP 8.2
- B2 商业主题 5.9.x(加密分发)
- 统计插件 b2_tongji,依赖 B2 主题内置的 CMB2 库
一、后台首页样式错乱:CDN 下线
插件首页是一个 Vue + Vuetify 写的管理面板,但它的前端资源用的是外链 CDN:
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.11/vue.min.js"></script>
<link href="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/vuetify/2.3.8/vuetify.min.css" rel="stylesheet">
<script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/vuetify/2.3.8/vuetify.min.js"></script>
这几个字节跳动系域名已经全局 404。Vue 加载失败,Vuetify 的自定义标签就不会被编译,页面退化成一大堆没有样式的原始标签——顶部统计卡片竖排、Sparkline 和图表区全空。
修复方式是把这三个文件下载到插件自己的目录里,改用相对路径引用:
<script src="<?php echo B2_TJ_URL.'assets/vendor/vue.min.js';?>"></script>
<link href="<?php echo B2_TJ_URL.'assets/vendor/vuetify.min.css';?>" rel="stylesheet">
<script src="<?php echo B2_TJ_URL.'assets/vendor/vuetify.min.js';?>"></script>
同一文件里还有两处错位的 </h2>,分别写在「今日登录」和「今日签到」卡片的 <span> 里,会破坏 DOM 结构,一并删掉。
二、页面报 Undefined array key:系统消息没有用户数据
私信记录等页面要显示用户昵称和主页链接,直接调用了 B2 的方法:
B2ModulesCommonUser::get_user_public_data( $user_id );
当 $user_id 为 0(系统消息)或用户已被删除时,这个方法返回的是 array( 'error' => ... ),不含 link 与 name 键,页面就会刷出一整屏 Undefined array key 警告,对应列显示空白。
加一层容错包装即可,对正常用户的行为与原方法完全一致:
function tj_user_data( $user_id ) {
$d = B2ModulesCommonUser::get_user_public_data( $user_id );
if ( ! is_array( $d ) ) { $d = array(); }
if ( ! isset( $d['link'] ) ) { $d['link'] = ''; }
if ( ! isset( $d['name'] ) ) {
$d['name'] = $user_id ? ( '用户#' . $user_id ) : '系统';
}
return $d;
}
再把插件里 10 处 User::get_user_public_data(...) 统一替换成 tj_user_data(...),报错即归零。
三、签到管理没有数据:B2 换了记录表
这一条是升级导致的。B2 5.x 的 User::user_mission() 已经把签到记录从旧表改写到新表:
// 旧:写入 wp_zrz_message,msg_type = 16
Message::add_message( array(
'user_id' => $user_id,
'msg_type' => 16,
...
) );
// 新:写入 wp_b2_gold,type = user_mission
Gold::update_data( array(
...
'type' => 'user_mission',
'type_text' => __( '签到', 'b2' ),
) );
而插件仍在查询 wp_zrz_message WHERE msg_type = 16——这张表的最新数据停在 2022 年,所以列表永远是空的。真实签到数据已经全部落在 wp_b2_gold 里。
需要把查询源换掉,并做字段映射(字段名几乎全变了):
| 用途 | 旧字段(wp_zrz_message) | 新字段(wp_b2_gold) |
|---|---|---|
| 表名 | wp_zrz_message | wp_b2_gold |
| 筛选条件 | msg_type = 16 | type = ‘user_mission’ |
| 排序 | msg_id | id |
| 用户 | user_id | to |
| 本次增加积分 | msg_credit | no |
| 积分总数 | msg_credit_total | total |
| 签到时间 | msg_date | date |
改完后签到管理页恢复显示,分页统计的条数与 wp_b2_gold 中 type='user_mission' 的实际行数完全一致。
四、搜索管理没有数据:PHP 8 下 REST 回调不可调用
插件注册了一个 REST 路由用来记录搜索词:
register_rest_route( 'b2/v1', '/tjsearch', array(
...
'callback' => array( __CLASS__, 'tj_search' ),
) );
问题在于 tj_search 声明为 public function(非静态)。PHP 8 起,is_callable( array( 'ClassName', 'nonStaticMethod' ) ) 返回 false,于是 WordPress 直接判定处理器无效:
{"code":"rest_invalid_handler","message":"此路由的处理器无效。","data":{"status":500}}
前台每次搜索都会请求这个接口并失败,搜索记录表自然一直是空的。这两个方法体内都没有用到 $this,直接改成静态即可:
public static function tj_search( $request ) { ... }
public static function tj_search_fun( $request ) { ... }
改完接口恢复返回 success,写入正常。
五、下载管理没有数据:其实一切正常
下载统计用的是 B2 的 b2_download_file 钩子,实测触发一次 filter 就能正确写入一条记录,字段映射也没有问题。
表之所以是空的,有两个原因:
- 站点近期确实没有下载行为;
- 插件每天的定时任务会清理 8 天前的下载 / 搜索 / 异常记录。
所以这一项不需要改代码。如果希望记录保留更久,需要调整清理周期。
六、一个容易踩的坑:CMB2 回调收到的不是 box
插件的后台页面用 CMB2 的 options page 机制注册,回调函数里是这样取 tab 分组名的:
$tab_group = $cmb_options->cmb->prop( 'tab_group' );
看起来有点绕,但它是对的——CMB2 调用 display_cb 时传入的是 CMB2_Options_Hookup 实例,而不是 CMB2 box 本身:
// CMB2_Options_Hookup.php 中
call_user_func( $callback, $this );
CMB2_Options_Hookup::__get() 的可用属性白名单只有 object_type、option_key、cmb 三个。所以:
- 取 box 上的属性,必须走
$cmb_options->cmb->prop( ... ); - 取 option key,用
$cmb_options->option_key; - 改成
$cmb_options->prop( ... )或$cmb_options->cmb_id都会直接致命错误——前者方法不存在,后者不在__get()白名单里。
排查时要特别注意:如果只用一个「把 box 实例直接传给回调」的简易脚本去测,是测不出这个问题的,因为传参对象和真实运行时不一致。
七、验证方式
修复完成后,用两条路径交叉验证:
- 真实管理后台引导路径:完整加载
wp-admin/admin.php(含鉴权、菜单注册、page hook),逐个渲染插件的 12 个后台页面,确认全部正常退出、无致命错误、无 PHP 警告。 - 真实登录态 HTTP 请求:带正常登录 Cookie 直接访问各后台页面,返回 200,页面结构完整(Vue 挂载点、导航页签、图表画布齐全)。
只加载 wp-load.php 的简易验证会绕过 WordPress 的致命错误处理器,容易得出「一切正常」的错误结论,这一点值得注意。
八、后续注意
- 不要再把 Vue / Vuetify 改回 CDN 外链,继续用插件目录内的本地资源。
- 签到数据源是
wp_b2_gold(type='user_mission'),wp_zrz_message已废弃,不要改回去。 - 任何新增读取用户公开数据的页面,统一使用
tj_user_data(),避免再出现数组键缺失。 - 插件依赖 B2 主题内置的 CMB2,B2 再次升级后若后台页面白屏,优先检查这里的兼容性。
小结
B2 生态里的插件大多是第三方个人维护,主题一升级就容易出现「明明没改代码却坏了」的情况。常见的原因集中在三类:外链资源失效、数据库表结构变更、PHP 版本升级带来的语法与反射行为变化。排查时先确认后端逻辑本身是否还能正常拿到数据,再往前端资源和框架兼容性上找,效率会高很多。