主题设置页加访问统计面板:接入百度统计 API,数据直出后台 #33

Closed
opened 2026-10-28 06:37:55 +00:00 by laoyang · 5 comments
Owner

需求

在主题设置页做一个访问统计面板:网站加一段第三方统计代码,我们在后端调用他们的 API,把数据显示在主题设置页面里。

拆解

这是一个需求,内含三个可独立验收的部分,但必须一起才算完成(少任何一步面板都不可用),所以不拆单:

① 前端埋点

  • 在页脚输出百度统计的 JS 代码(hm.js)
  • 复用现有的「收录与统计 → 统计代码」文本框(seo_statistical),不加新字段:那个框本来就是给用户填统计脚本的
  • 但要加一个开关,避免"填了代码但不想启用了"时只能删掉再粘回来

② 后端拉数据

  • 百度统计 Tongji API(OAuth 2.0)
  • ACCESS_TOKEN 有效期 30 天,refresh_token 10 年 ⇒ 必须做自动续期
  • 凭据(API Key / Secret Key / refresh_token)不能存 options 表(会被导出、被备份、被插件读),存独立文件,权限 600

③ 后台面板

  • 落在「收录与统计」标签页内,用一个 info 类型块渲染
  • 显示:今日/昨日 PV、UV、IP;最近 7 天趋势;跳出率、平均访问时长
  • 缓存 30 分钟(transient),不能每次打开后台都打 API

硬性约束

  • API 失败绝不能让设置页白屏或卡住。token 过期、配额用尽、网络不通,都要优雅降级成一行提示
  • 不能拖慢前台。埋点是异步 <script>,后端只在后台被访问时查 API
  • 百度统计返回里 "--" 表示无数据,(int)"--" === 0,必须单独判,不能当 0 参与计算
  • 主题 4.0.0 已上线,#32 已关,本版号 4.1.0

验收标准

  1. 前台页面 <head> 或页脚能看到百度统计代码,且只在开关打开时输出
  2. 主题设置页「收录与统计」下出现统计面板,显示真实数字
  3. 拔掉 token(改错凭据)时,面板显示「暂不可用 + 原因」,设置页其他部分照常可用
  4. 30 分钟内重复打开设置页,不产生新的 API 调用(看服务器日志确认)
  5. 前台首屏无新增阻塞请求
  6. php -l 全量通过;判据 + 反证全绿

前置依赖(需用户提供)

百度统计的 API Key / Secret Key,以及完成一次 OAuth 授权拿到 refresh_token。没有这些我无法端到端验证真实数据,只能验证降级路径。

附:为什么不选自建 Umami

实测该服务器 2 核 / load 2.28(已近满载),Umami 容器 183~255MB + PostgreSQL,官方最低要求 1GB 内存整机。内存够但 CPU 不够,会明显加重现有负载。除非先上 Redis 对象缓存把基线压下来,否则不碰。

## 需求 在主题设置页做一个**访问统计面板**:网站加一段第三方统计代码,我们在后端调用他们的 API,把数据显示在主题设置页面里。 ### 拆解 这是一个需求,内含三个可独立验收的部分,但**必须一起才算完成**(少任何一步面板都不可用),所以不拆单: **① 前端埋点** - 在页脚输出百度统计的 JS 代码(`hm.js`) - 复用现有的「收录与统计 → 统计代码」文本框(`seo_statistical`),**不加新字段**:那个框本来就是给用户填统计脚本的 - 但要加一个**开关**,避免"填了代码但不想启用了"时只能删掉再粘回来 **② 后端拉数据** - 百度统计 Tongji API(OAuth 2.0) - `ACCESS_TOKEN` 有效期 30 天,`refresh_token` 10 年 ⇒ 必须做自动续期 - 凭据(API Key / Secret Key / refresh_token)**不能存 options 表**(会被导出、被备份、被插件读),存独立文件,权限 600 **③ 后台面板** - 落在「收录与统计」标签页内,用一个 `info` 类型块渲染 - 显示:今日/昨日 PV、UV、IP;最近 7 天趋势;跳出率、平均访问时长 - **缓存 30 分钟**(transient),不能每次打开后台都打 API ### 硬性约束 - **API 失败绝不能让设置页白屏或卡住**。token 过期、配额用尽、网络不通,都要优雅降级成一行提示 - **不能拖慢前台**。埋点是异步 `<script>`,后端只在后台被访问时查 API - 百度统计返回里 `"--"` 表示无数据,`(int)"--" === 0`,必须单独判,不能当 0 参与计算 - 主题 4.0.0 已上线,#32 已关,本版号 **4.1.0** ### 验收标准 1. 前台页面 `<head>` 或页脚能看到百度统计代码,且只在开关打开时输出 2. 主题设置页「收录与统计」下出现统计面板,显示真实数字 3. 拔掉 token(改错凭据)时,面板显示「暂不可用 + 原因」,**设置页其他部分照常可用** 4. 30 分钟内重复打开设置页,不产生新的 API 调用(看服务器日志确认) 5. 前台首屏无新增阻塞请求 6. `php -l` 全量通过;判据 + 反证全绿 ### 前置依赖(需用户提供) 百度统计的 **API Key / Secret Key**,以及完成一次 OAuth 授权拿到 `refresh_token`。没有这些我无法端到端验证真实数据,只能验证降级路径。 ## 附:为什么不选自建 Umami 实测该服务器 2 核 / load 2.28(已近满载),Umami 容器 183~255MB + PostgreSQL,官方最低要求 1GB 内存整机。内存够但 CPU 不够,会明显加重现有负载。除非先上 Redis 对象缓存把基线压下来,否则不碰。
Author
Owner

4.1.0 已开发完成并发布到更新源,等你升级验证

代码提交:c125388(已推送 main)
发布包:https://yangyq.net/theme-update/kratos_yang-4.1.0.zip
md5:5eeaca87ae06d0a4c92adaaa3f293c67
(update.json 已指向 4.1.0,后台「外观 → 主题」应能看到升级提示)


三项验收标准逐条对照

① 前台能看到统计代码,且只在开关打开时输出 ✅

footer.php 把原有一行改成:

if (yang_stat_tracking_enabled() && kratos_option('seo_statistical')) {
    echo '<p>' . kratos_option('seo_statistical') . '</p>';
}

开关是新增的 stat_track_enable(复选框),默认开。三态处理:从未配置 ⇒ 开(和你原来的行为一致)、显式勾选 ⇒ 开、显式取消 ⇒ 关。false 被当成"用户主动关掉"而不是"没配置"——这是 of_sanitize_checkbox 未勾选写 false 带来的坑,判据专门钉了这一条。

② 设置页出现统计面板并显示真实数字 ⚠️ 代码就绪,等你补凭据

面板、13 个指标、缓存、自动续期全都做完了。但你还没有填凭据,所以现在打开设置页会看到一块引导("还没有接通百度统计" + 四步操作指引),不是数字。这是有意的:未配置时不打任何接口,不可能卡住。

③ 凭据错误时优雅降级 ✅

取数失败 ⇒ 面板显示「统计暂不可用」+ 具体原因,下面一行「设置页其它部分不受影响,可以照常修改并保存」。设置页其余控件完全正常。


本轮抓到的三个真 bug(都修了)

1. 刷新按钮是摆设 —— 面板 JS 点「刷新数据」会发 force=1,AJAX 处理器也读出了 $force,然后把它丢了,转头调渲染函数不带参数。后果:成功缓存 1800 秒,用户点刷新拿到的还是同一份数据,时间戳都不变,按钮看起来完全没反应。而"面板渲染"和"缓存"两段判据当时全绿——每一步单独看都对。修法是把 $force 一路传到 yang_stat_fetch(),并且加了 8 条判据(静态 4 条 + 真调 AJAX 的行为 4 条)专门盯这个参数链路。

2. 百度统计的错误格式和文档不一样 —— 我加了真实接口连通性测试(_stat_api_real.php),实测:

  • OAuth 端点假凭据 → HTTP 401 + {"error":"invalid_client","error_description":"unknown client id"}
  • getData 端点假 token → HTTP 200 + {"error_code":110,"error_msg":"Access token invalid or no longer valid"}

第一版代码只判了 200 和 error 字段,两种情况都会报成"接口返回里找不到 result 节点"——一句完全指向错误方向的话。已改成三种格式都认,token 失效还会额外提示「请重新走一次授权」。

3. yang_stat_num(1234.0) 返回 float 不返回 int —— is_float() 分支直接 return,跳过了整型归一,下游按类型分支的判断全错。


设计上几个值得说的决定

决定 为什么
凭据存 wp-content/uploads/.yang-stat-credentials.php(0600) 存主题目录的话,主题一升级整个目录被删,token 就没了。存 options 表则会被导出/备份/插件读到。打包脚本里还有一条硬闸门:源目录里出现这个文件就拒绝打包(不是静默跳过——静默跳过会让"泄漏发生了"变成"没事")
面板走 AJAX「先出壳、后取数」 同步渲染 = 8 个请求 × 5 秒超时 = 设置页最坏卡 40 秒
缓存成功 1800 秒 / 失败 300 秒 只缓存成功的话,接口一挂你每刷新一次就等一次超时;只缓存失败则恢复太慢
"--" 渲染成灰色破折号,不转 0 (int)"--" === 0。不单独判的话,"暂无数据"会显示成"0 次访问"——看着像没人来,其实还没统计出来
13 个指标全部可单独开关 每个指标一次 API 调用,配额按次计。只想看两三个数字的,关掉其余能省不少
旧 views 计数保留不动 你选的。它跟百度统计是两套数,一个在文章列表显示,一个在后台看趋势

验证(五套,全绿)

层 脚本 结果
PHP 语法 php -l 全量 98 文件 / 0 失败
正向判据 _stat410.php 145 / 0
判据反证 _stat410_falsify.php 19 组全抓到
真实接口 _stat_api_real.php 10 / 0
打包自检 _mkzip410.py 82 / 0
发布/打包判据反证 _mkzip410_falsify.py + _publish410_falsify.sh 15/0 + 21/0

发布流程按红线执行:只写 theme-update/,线上主题目录未被覆盖(脚本有断言:kratos_yang/style.css 仍是 4.0.0、目录里没有 theme-stat.php)。PUC 已实测能读到 4.1.0(直接调 checkForUpdates() 拿到的 version=4.1.0、package 指向本版包)。

顺带修了发布脚本自己三处判据缺陷:while read 里 ssh 吃掉 stdin 导致只 lint 了 1 个文件却报"全部通过"(假绿)、本地拿远端 php 路径解析 JSON 失败后空值走了 ok 分支(假绿)、把第三方 vendor 的 Deprecated 当语法错误(假红)。


需要你做的两件事

1. 在后台点升级(外观 → 主题 → 检查更新 → 升级到 4.1.0)

升级完告诉我,我要做两件收尾:重设 theme-update 目录 ACL(升级会重置权限),以及清 PUC transient 让后台立刻稳定显示 4.1.0。

2. 补百度统计凭据(这是面板出真数字的前提)

昨天你问「百度统计是不是停止维护了」——查证了,产品正常在维护。但有个更要紧的发现需要你核对:

你填的那串 192a62f2ae923a591128f92554cc80e0,在百度统计侧不是一个有效站点。

我是这么验的(交叉对照):

hash Referer hm.js 响应体
中国青年网 96951609… youth.cn 30278 字节
中国青年网 96951609… yangyq.net 30278 字节
中国青年网 96951609… 无 0 字节
我们的 192a62f2… yangyq.net 0 字节
我们的 192a62f2… 任意 0 字节

规律很清楚:有效 hash + 任意 Referer → 30278 字节;无效 hash → 恒 0 字节(无 Referer 时连有效 hash 也返回 0,所以不能用 curl 直接判埋点)。

也就是说,主题注入的代码完全正确(标准片段、async:true、HTTP 200 全部对),问题在百度后台这个 site_id 没有生效。麻烦你到「管理 → 网站列表」看一下 yangyq.net 那一行:域名是否填对、状态是否在"正常"、是不是建了但没提交校验。

这不阻塞代码——凭据和有效站点补齐后,面板立刻能出真数字。

## 4.1.0 已开发完成并发布到更新源,等你升级验证 代码提交:`c125388`(已推送 main) 发布包:`https://yangyq.net/theme-update/kratos_yang-4.1.0.zip` md5:`5eeaca87ae06d0a4c92adaaa3f293c67` (`update.json` 已指向 4.1.0,后台「外观 → 主题」应能看到升级提示) --- ### 三项验收标准逐条对照 **① 前台能看到统计代码,且只在开关打开时输出** ✅ `footer.php` 把原有一行改成: ```php if (yang_stat_tracking_enabled() && kratos_option('seo_statistical')) { echo '<p>' . kratos_option('seo_statistical') . '</p>'; } ``` 开关是新增的 `stat_track_enable`(复选框),**默认开**。三态处理:从未配置 ⇒ 开(和你原来的行为一致)、显式勾选 ⇒ 开、显式取消 ⇒ 关。`false` 被当成"用户主动关掉"而不是"没配置"——这是 `of_sanitize_checkbox` 未勾选写 `false` 带来的坑,判据专门钉了这一条。 **② 设置页出现统计面板并显示真实数字** ⚠️ 代码就绪,等你补凭据 面板、13 个指标、缓存、自动续期全都做完了。但**你还没有填凭据**,所以现在打开设置页会看到一块引导("还没有接通百度统计" + 四步操作指引),不是数字。这是有意的:未配置时不打任何接口,不可能卡住。 **③ 凭据错误时优雅降级** ✅ 取数失败 ⇒ 面板显示「统计暂不可用」+ **具体原因**,下面一行「设置页其它部分不受影响,可以照常修改并保存」。设置页其余控件完全正常。 --- ### 本轮抓到的三个真 bug(都修了) **1. 刷新按钮是摆设** —— 面板 JS 点「刷新数据」会发 `force=1`,AJAX 处理器也读出了 `$force`,**然后把它丢了**,转头调渲染函数不带参数。后果:成功缓存 1800 秒,用户点刷新拿到的还是同一份数据,时间戳都不变,按钮看起来完全没反应。而"面板渲染"和"缓存"两段判据当时全绿——每一步单独看都对。修法是把 `$force` 一路传到 `yang_stat_fetch()`,并且加了 8 条判据(静态 4 条 + 真调 AJAX 的行为 4 条)专门盯这个参数链路。 **2. 百度统计的错误格式和文档不一样** —— 我加了真实接口连通性测试(`_stat_api_real.php`),实测: - OAuth 端点假凭据 → **HTTP 401** + `{"error":"invalid_client","error_description":"unknown client id"}` - getData 端点假 token → **HTTP 200** + `{"error_code":110,"error_msg":"Access token invalid or no longer valid"}` 第一版代码只判了 `200` 和 `error` 字段,两种情况都会报成"接口返回里找不到 result 节点"——一句完全指向错误方向的话。已改成三种格式都认,token 失效还会额外提示「请重新走一次授权」。 **3. `yang_stat_num(1234.0)` 返回 float 不返回 int** —— `is_float()` 分支直接 return,跳过了整型归一,下游按类型分支的判断全错。 --- ### 设计上几个值得说的决定 | 决定 | 为什么 | |---|---| | 凭据存 `wp-content/uploads/.yang-stat-credentials.php`(0600) | 存主题目录的话,**主题一升级整个目录被删,token 就没了**。存 options 表则会被导出/备份/插件读到。打包脚本里还有一条硬闸门:源目录里出现这个文件就**拒绝打包**(不是静默跳过——静默跳过会让"泄漏发生了"变成"没事") | | 面板走 AJAX「先出壳、后取数」 | 同步渲染 = 8 个请求 × 5 秒超时 = 设置页最坏卡 40 秒 | | 缓存成功 1800 秒 / 失败 300 秒 | 只缓存成功的话,接口一挂你每刷新一次就等一次超时;只缓存失败则恢复太慢 | | `"--"` 渲染成灰色破折号,不转 0 | `(int)"--" === 0`。不单独判的话,"暂无数据"会显示成"0 次访问"——看着像没人来,其实还没统计出来 | | 13 个指标全部可单独开关 | 每个指标一次 API 调用,配额按次计。只想看两三个数字的,关掉其余能省不少 | | 旧 `views` 计数保留不动 | 你选的。它跟百度统计是两套数,一个在文章列表显示,一个在后台看趋势 | --- ### 验证(五套,全绿) | 层 | 脚本 | 结果 | |---|---|---| | PHP 语法 | `php -l` 全量 | 98 文件 / 0 失败 | | 正向判据 | `_stat410.php` | **145 / 0** | | 判据反证 | `_stat410_falsify.php` | **19 组全抓到** | | 真实接口 | `_stat_api_real.php` | **10 / 0** | | 打包自检 | `_mkzip410.py` | **82 / 0** | | 发布/打包判据反证 | `_mkzip410_falsify.py` + `_publish410_falsify.sh` | **15/0 + 21/0** | 发布流程按红线执行:只写 `theme-update/`,**线上主题目录未被覆盖**(脚本有断言:`kratos_yang/style.css` 仍是 4.0.0、目录里没有 `theme-stat.php`)。PUC 已实测能读到 4.1.0(直接调 `checkForUpdates()` 拿到的 version=4.1.0、package 指向本版包)。 顺带修了发布脚本自己三处判据缺陷:`while read` 里 `ssh` 吃掉 stdin 导致只 lint 了 1 个文件却报"全部通过"(假绿)、本地拿远端 php 路径解析 JSON 失败后空值走了 ok 分支(假绿)、把第三方 vendor 的 Deprecated 当语法错误(假红)。 --- ### 需要你做的两件事 **1. 在后台点升级**(外观 → 主题 → 检查更新 → 升级到 4.1.0) 升级完告诉我,我要做两件收尾:重设 `theme-update` 目录 ACL(升级会重置权限),以及清 PUC transient 让后台立刻稳定显示 4.1.0。 **2. 补百度统计凭据**(这是面板出真数字的前提) 昨天你问「百度统计是不是停止维护了」——查证了,**产品正常在维护**。但有个更要紧的发现需要你核对: > 你填的那串 `192a62f2ae923a591128f92554cc80e0`,在百度统计侧**不是一个有效站点**。 我是这么验的(交叉对照): | hash | Referer | `hm.js` 响应体 | |---|---|---| | 中国青年网 `96951609…` | `youth.cn` | **30278 字节** | | 中国青年网 `96951609…` | `yangyq.net` | **30278 字节** | | 中国青年网 `96951609…` | 无 | 0 字节 | | 我们的 `192a62f2…` | `yangyq.net` | **0 字节** | | 我们的 `192a62f2…` | 任意 | 0 字节 | 规律很清楚:**有效 hash + 任意 Referer → 30278 字节;无效 hash → 恒 0 字节**(无 Referer 时连有效 hash 也返回 0,所以不能用 curl 直接判埋点)。 也就是说,**主题注入的代码完全正确**(标准片段、`async:true`、HTTP 200 全部对),问题在百度后台这个 site_id 没有生效。麻烦你到「管理 → 网站列表」看一下 yangyq.net 那一行:域名是否填对、状态是否在"正常"、是不是建了但没提交校验。 这不阻塞代码——凭据和有效站点补齐后,面板立刻能出真数字。
Author
Owner

发布后复核发现并已修复:反证脚本污染了线上 PUC 缓存(点升级会 404)

现象

发布反证脚本(_publish410_falsify.sh)为了测「PUC 读到错包地址时判据会不会红」,
临时把线上 update.json 改成指向 kratos_yang-9.9.9-nonexistent.zip,
跑完立刻用 printf 还原,并做了字节级复核:

[OK] update.json 已还原
[OK] 公网复核:update.json 内容与原文逐字节一致

两条全绿,看起来干净。但真实状态是:后台点「升级到 4.1.0」会下载 404。

根因

测试那一下调用了 $myUpdateChecker->checkForUpdates(),PUC 把假地址
写进了 site_transient('update_themes')。transient 里的
response['kratos_yang']['package'] 是独立于 update.json 的一份拷贝,
还原文件不会让它失效 —— 而前台「检查更新」读的正是这份缓存,默认最长 12 小时。

实测证据(独立 PHP 进程读 transient):

RESP kratos_yang => new=4.1.0 url=https://yangyq.net/theme-update/kratos_yang-9.9.9-nonexistent.zip

已做的修复

  1. 线上:delete_site_transient('update_themes') + 强制 PUC 重拉,
    独立进程复核,当前终态:

    FINAL new=4.1.0 pkg=https://yangyq.net/theme-update/kratos_yang-4.1.0.zip
    FINAL_VERDICT=CLEAN
    

    且 update.json、主题目录均未被污染(无 9.9.9 残留,theme-stat.php 仍不存在,
    线上主题仍是 4.0.0 —— 红线守住)。

  2. 反证脚本:新增「2c. 清掉 PUC 缓存里残留的错包地址」段,还原文件之后显式清 transient,
    并断言缓存里必须是正确地址(不是"没有 nonexistent"就算过 —— 空也是错):

    [OK] 缓存已回到正确包地址(https://yangyq.net/theme-update/kratos_yang-4.1.0.zip)
    [OK] PUC 复读版本号仍是 4.1.0
    

    反证现为 23 通过 / 0 失败(原 21 条 + 新增 2 条)。

  3. 记忆:记为红线 #496(反证还原了文件却没还原由文件派生的缓存)。

一个值得记住的判据陷阱

delete_site_transient() 之后立刻 get_site_transient() 读到"还在"是正常的 ——
PUC 走 site_transient_update_themes 过滤器注入(见 #495),删完一读就重新填充。
所以不能用 $t === false 判"清干净了",要判重新填充出来的内容对不对。
这一点让"删除 → 复核"这条最直觉的写法假红,差点让我以为清不掉。

对验收的影响

没有影响。修复后后台检查更新读到的就是正确的 4.1.0 包地址,
可以直接点升级。这个小插曲只在"反证跑完到发现它"的这几分钟内存在,
且当时并无用户在点升级。

## 发布后复核发现并已修复:反证脚本污染了线上 PUC 缓存(点升级会 404) ### 现象 发布反证脚本(`_publish410_falsify.sh`)为了测「PUC 读到错包地址时判据会不会红」, 临时把线上 `update.json` 改成指向 `kratos_yang-9.9.9-nonexistent.zip`, 跑完立刻用 `printf` 还原,并做了**字节级复核**: ``` [OK] update.json 已还原 [OK] 公网复核:update.json 内容与原文逐字节一致 ``` 两条全绿,看起来干净。**但真实状态是:后台点「升级到 4.1.0」会下载 404。** ### 根因 测试那一下调用了 `$myUpdateChecker->checkForUpdates()`,PUC 把假地址 **写进了 `site_transient('update_themes')`**。transient 里的 `response['kratos_yang']['package']` 是独立于 `update.json` 的一份**拷贝**, 还原文件不会让它失效 —— 而前台「检查更新」读的正是这份缓存,默认最长 12 小时。 实测证据(独立 PHP 进程读 transient): ``` RESP kratos_yang => new=4.1.0 url=https://yangyq.net/theme-update/kratos_yang-9.9.9-nonexistent.zip ``` ### 已做的修复 1. **线上**:`delete_site_transient('update_themes')` + 强制 PUC 重拉, 独立进程复核,当前终态: ``` FINAL new=4.1.0 pkg=https://yangyq.net/theme-update/kratos_yang-4.1.0.zip FINAL_VERDICT=CLEAN ``` 且 `update.json`、主题目录均未被污染(无 `9.9.9` 残留,`theme-stat.php` 仍不存在, 线上主题仍是 4.0.0 —— 红线守住)。 2. **反证脚本**:新增「2c. 清掉 PUC 缓存里残留的错包地址」段,还原文件**之后**显式清 transient, 并断言缓存里**必须是正确地址**(不是"没有 nonexistent"就算过 —— 空也是错): ``` [OK] 缓存已回到正确包地址(https://yangyq.net/theme-update/kratos_yang-4.1.0.zip) [OK] PUC 复读版本号仍是 4.1.0 ``` 反证现为 **23 通过 / 0 失败**(原 21 条 + 新增 2 条)。 3. **记忆**:记为红线 **#496**(反证还原了文件却没还原由文件派生的缓存)。 ### 一个值得记住的判据陷阱 `delete_site_transient()` 之后**立刻** `get_site_transient()` 读到"还在"是**正常的** —— PUC 走 `site_transient_update_themes` **过滤器**注入(见 #495),删完一读就重新填充。 所以不能用 `$t === false` 判"清干净了",要判**重新填充出来的内容对不对**。 这一点让"删除 → 复核"这条最直觉的写法假红,差点让我以为清不掉。 ### 对验收的影响 **没有影响**。修复后后台检查更新读到的就是正确的 `4.1.0` 包地址, 可以直接点升级。这个小插曲只在"反证跑完到发现它"的这几分钟内存在, 且当时并无用户在点升级。
Author
Owner

线上真实环境预演抓到 2 处文案 bug(已修并重发)

前面六套判据全绿之后,我做了一件之前没做的事:把发布包解到服务器上,
在真实 WordPress + PHP 8.5 里加载 4.1.0 的 theme-stat.php
。
本地判据都是桩环境(_stat410.php 自带一套假的 kratos_option/get_option),
桩能证明逻辑对,证明不了它在真 WP 里不炸、也证明不了它输出给用户的是什么样子。

抓到的 bug:给用户看的文案里漏进了 Markdown 语法

yang_stat_render_shell() 渲染出的 HTML:

<li>把下面这个地址粘到浏览器,用**你自己的百度账号**登录并同意授权:</li>

浏览器会原样显示两个星号。同批还有一处,在 stat_track_enable 的 desc 里
(代码本身**不会被删除**),以及 readme 里一处(这十三项**都能单独关掉**)。

根因是这个项目的 PHP 注释/文档习惯用 **加粗** 写重点,
这串文字被搬进 HTML 字符串和 __() 文案时,星号就跟着进去了。

修复

三处 **…** → <b>…</b>,inc/theme-stat.php、inc/theme-options.php
各一行,readme 由 _readme410.py 重新生成。

新增判据(都做了注入反证)

脚本 判据 结果
_mkzip410.py 第 11b 段 剥 PHP 注释后扫字符串里的 **…** 83 通过 / 0 失败
_mkzip410_falsify.py 第 16 组 把 <b> 还原成 ** 必须变红 16/16 抓到
_readme410.py 第 8 段 本版节里不许有 Markdown 星号 注入后 38/1,恢复后 39/0

关键细节:判据必须先剥 PHP 注释再判 —— 否则会把所有用 ** 写重点的
技术注释都误报(本项目注释里到处都是)。

顺带修了两个判据脚本自身的缺陷

  1. _stat410.php 自己跑不起来。文件头文档写「跑法:php _stat410.php」,
    但 $GLOBALS['__theme_dir'] 只有反证脚本会注入,直接跑就
    Fatal error: Failed opening required '/inc/theme-stat.php'。
    判据脚本自己跑不起来 = 零判据绿灯,比假绿更难发现。
    ⇒ 加回落路径;找不到被测文件时 exit 2 并明说「没跑任何判据,这不是通过」。

  2. _publish410.sh 的 LOCAL_RD 默认值指向上一版遗留文件。
    默认写的是 /d/tmp/readme410.html,而 _readme410.py 的真实输出是
    /d/tmp/_readme_410.html。首跑就把上一版 readme 传上去了,
    出现 1 条失败;我第二次跑时手工加了 YANG_LOCAL_RD=... 又"好了"。
    「失败一次、重跑就好」这种偶发最容易被当成网络抖动糊过去,
    实际是默认值错位 + 手工覆盖掩盖。⇒ 改默认值,且默认路径不存在时直接停。

线上已重发

update.json : version 4.1.0, md5 5cf4e1b3242a2e092233fd573d0196d4
包三方 md5  : 本地 / update.json / 公网下载 全部一致
readme      : 57 节,星号 0 个
主题目录    : 仍是 4.0.0,无 theme-stat.php —— 红线守住,等你在后台点升级

真实 WP 环境复验:

LEN=1105  HAS_MD_STAR=no  HAS_BOLD_TAG=yes

顺便一个好消息:埋点通了

之前 hm.js?192a62f2… 一直返回 0 字节,说明百度侧那个 site_id 没生效。
你今天在后台补的统计代码已经存进 seo_statistical,现在实测:

hm.js?192a62f2…  →  HTTP 200,29896 字节
首页输出          →  <p><script> … hm.baidu.com/hm.js?192a62f2… </p>

埋点已真正生效,访问数据会开始积累。

## 线上真实环境预演抓到 2 处文案 bug(已修并重发) 前面六套判据全绿之后,我做了一件之前没做的事:**把发布包解到服务器上, 在真实 WordPress + PHP 8.5 里加载 4.1.0 的 `theme-stat.php`**。 本地判据都是桩环境(`_stat410.php` 自带一套假的 `kratos_option`/`get_option`), 桩能证明逻辑对,**证明不了它在真 WP 里不炸、也证明不了它输出给用户的是什么样子**。 ### 抓到的 bug:给用户看的文案里漏进了 Markdown 语法 `yang_stat_render_shell()` 渲染出的 HTML: ``` <li>把下面这个地址粘到浏览器,用**你自己的百度账号**登录并同意授权:</li> ``` 浏览器会**原样显示两个星号**。同批还有一处,在 `stat_track_enable` 的 `desc` 里 (`代码本身**不会被删除**`),以及 readme 里一处(`这十三项**都能单独关掉**`)。 根因是这个项目的 PHP 注释/文档习惯用 `**加粗**` 写重点, 这串文字被搬进 HTML 字符串和 `__()` 文案时,星号就跟着进去了。 ### 修复 三处 `**…**` → `<b>…</b>`,`inc/theme-stat.php`、`inc/theme-options.php` 各一行,readme 由 `_readme410.py` 重新生成。 ### 新增判据(都做了注入反证) | 脚本 | 判据 | 结果 | |---|---|---| | `_mkzip410.py` 第 11b 段 | 剥 PHP 注释后扫字符串里的 `**…**` | **83 通过 / 0 失败** | | `_mkzip410_falsify.py` 第 16 组 | 把 `<b>` 还原成 `**` 必须变红 | **16/16 抓到** | | `_readme410.py` 第 8 段 | 本版节里不许有 Markdown 星号 | 注入后 38/1,恢复后 **39/0** | 关键细节:判据必须**先剥 PHP 注释再判** —— 否则会把所有用 `**` 写重点的 技术注释都误报(本项目注释里到处都是)。 ### 顺带修了两个判据脚本自身的缺陷 1. **`_stat410.php` 自己跑不起来**。文件头文档写「跑法:`php _stat410.php`」, 但 `$GLOBALS['__theme_dir']` 只有反证脚本会注入,直接跑就 `Fatal error: Failed opening required '/inc/theme-stat.php'`。 **判据脚本自己跑不起来 = 零判据绿灯**,比假绿更难发现。 ⇒ 加回落路径;找不到被测文件时 `exit 2` 并明说「没跑任何判据,这不是通过」。 2. **`_publish410.sh` 的 `LOCAL_RD` 默认值指向上一版遗留文件**。 默认写的是 `/d/tmp/readme410.html`,而 `_readme410.py` 的真实输出是 `/d/tmp/_readme_410.html`。首跑就把**上一版 readme** 传上去了, 出现 1 条失败;我第二次跑时手工加了 `YANG_LOCAL_RD=...` 又"好了"。 **「失败一次、重跑就好」这种偶发最容易被当成网络抖动糊过去**, 实际是默认值错位 + 手工覆盖掩盖。⇒ 改默认值,且默认路径不存在时直接停。 ### 线上已重发 ``` update.json : version 4.1.0, md5 5cf4e1b3242a2e092233fd573d0196d4 包三方 md5 : 本地 / update.json / 公网下载 全部一致 readme : 57 节,星号 0 个 主题目录 : 仍是 4.0.0,无 theme-stat.php —— 红线守住,等你在后台点升级 ``` 真实 WP 环境复验: ``` LEN=1105 HAS_MD_STAR=no HAS_BOLD_TAG=yes ``` ### 顺便一个好消息:埋点通了 之前 `hm.js?192a62f2…` 一直返回 **0 字节**,说明百度侧那个 site_id 没生效。 你今天在后台补的统计代码已经存进 `seo_statistical`,现在实测: ``` hm.js?192a62f2… → HTTP 200,29896 字节 首页输出 → <p><script> … hm.baidu.com/hm.js?192a62f2… </p> ``` **埋点已真正生效**,访问数据会开始积累。
Author
Owner

4.1.1 已发布:修设置页链接无法新窗口打开

你反馈的问题

「数据导出服务」那个链接点下去,在本页跳走了,把正在改的设置页顶掉。

根因:不是链接写错,是被 WordPress 白名单静默剥掉了

源码里 target="_blank" 一直在(inc/theme-options.php:853)。真正的原因是
框架渲染 desc 时会过一遍 HTML 白名单:

// inc/options-framework/includes/class-options-interface.php:210 / 315
$output .= '<div class="explain">' . wp_kses($explain_value, $allowedtags) . '</div>';

而 WordPress 的全局 $allowedtags['a'] 只有 href 和 title。实测:

输入:<a href="..." target="_blank" rel="noopener">数据导出服务</a>
输出:<a href="...">数据导出服务</a>          ← target 和 rel 都不见了

wp_kses 剥属性时不报错、不留痕,所以代码上完全看不出来。

修法

在 kratos_options() 开头给白名单补两个属性(只补这两个,不放宽别的):

global $allowedtags;
$allowedtags['a']['target'] = true;
$allowedtags['a']['rel']    = true;

时机是对的:_optionsframework_options() 在第 47 行就调 kratos_options(),
早于所有 wp_kses 调用。

受影响的不止这一个链接

同一个原因,设置页里 5 处链接都点不开新窗口,一并修好了:

链接 位置
数据导出服务 统计面板 API Key 说明
robots.txt 收录与统计
设置-阅读-对搜索引擎的可见性 收录与统计
fanyi-api.baidu.com 百度翻译
ai.youdao.com 有道翻译

为什么版本号要升到 4.1.1

你已经把主题升到 4.1.0 了。PUC 用 version_compare($new, $installed, '>')
判严格大于 —— 同一版本号重发多少遍,后台都不会提示更新,你永远拿不到这次修复。
所以升到 4.1.1。

readme 按你选的方案 并入现有 V4.1.0 节,不新增一节(节数仍是 57),
在里面注明「这一条是 4.1.1 补的」以及 4.1.0 当时为什么没生效。

验证

线上真实 WordPress 环境(不是桩):

ALLOWED_BEFORE=href,title
ALLOWED_AFTER=href,title,target,rel
HAS_TARGET=YES

PUC 实测:

INSTALLED=4.1.0
NEW=4.1.1
PKG=https://yangyq.net/theme-update/kratos_yang-4.1.1.zip
HAS_UPDATE=YES

判据

脚本 结果
_mkzip410.py(含新增 11c 段 5 条) 88 通过 / 0 失败
_mkzip410_falsify.py(含新增 17/18 组) 18/18 抓到
_readme410.py(改造为替换模式) 42 通过 / 0 失败
_stat410.php 145 通过 / 0 失败
_stat410_falsify.php 19/19 抓到
_publish410.sh 0 失败 / 0 提醒

反证要点:删掉白名单补充必须变红、把补充挪出 kratos_options() 也必须变红 ——
两条都验过了,不是恒真。

线上终态

update.json : 4.1.1, md5 bd12b67f2c4ca5bec5053a02e49f2fe6
4.1.1 包    : 200(4.6 MB)
4.1.0 / 4.0.0 旧包 : 仍 200(旧客户端不 404)
readme      : 57 节,含「这一条是 4.1.1 补的」
主题目录    : 仍是 4.1.0(未被覆盖)—— 等你在后台点升级
git         : 51c1620 已推送

顺带修的两处「脚本自己过期」

  1. _mkzip410_falsify.py 的「版本号没改」注入锚点写死 Version: 4.1.0。
    升到 4.1.1 后锚点不存在,反证报「1 组没抓到」—— 看起来像判据没牙齿,
    实际是注入脚本自己过期了。改成动态读当前版本号。
  2. _publish410.sh 要求 readme 首节 == 本版号,但 4.1.1 并入 V4.1.0 节。
    改成两头都验:首节是 4.1.x,且本版修复说明真在里面。

百度统计凭据

明白,明天再申请。API Key / Secret / site_id / 授权码齐了告我,
我这边只差这一步就能看到真数据。

## 4.1.1 已发布:修设置页链接无法新窗口打开 ### 你反馈的问题 「数据导出服务」那个链接点下去,在本页跳走了,把正在改的设置页顶掉。 ### 根因:不是链接写错,是被 WordPress 白名单静默剥掉了 源码里 `target="_blank"` **一直在**(`inc/theme-options.php:853`)。真正的原因是 框架渲染 `desc` 时会过一遍 HTML 白名单: ```php // inc/options-framework/includes/class-options-interface.php:210 / 315 $output .= '<div class="explain">' . wp_kses($explain_value, $allowedtags) . '</div>'; ``` 而 WordPress 的全局 `$allowedtags['a']` **只有 `href` 和 `title`**。实测: ``` 输入:<a href="..." target="_blank" rel="noopener">数据导出服务</a> 输出:<a href="...">数据导出服务</a> ← target 和 rel 都不见了 ``` `wp_kses` 剥属性时不报错、不留痕,所以代码上完全看不出来。 ### 修法 在 `kratos_options()` 开头给白名单补两个属性(只补这两个,不放宽别的): ```php global $allowedtags; $allowedtags['a']['target'] = true; $allowedtags['a']['rel'] = true; ``` 时机是对的:`_optionsframework_options()` 在第 47 行就调 `kratos_options()`, 早于所有 `wp_kses` 调用。 ### 受影响的不止这一个链接 同一个原因,设置页里 **5 处**链接都点不开新窗口,一并修好了: | 链接 | 位置 | |---|---| | 数据导出服务 | 统计面板 API Key 说明 | | robots.txt | 收录与统计 | | 设置-阅读-对搜索引擎的可见性 | 收录与统计 | | fanyi-api.baidu.com | 百度翻译 | | ai.youdao.com | 有道翻译 | ### 为什么版本号要升到 4.1.1 你已经把主题升到 4.1.0 了。PUC 用 `version_compare($new, $installed, '>')` 判**严格大于** —— 同一版本号重发多少遍,后台都不会提示更新,你永远拿不到这次修复。 所以升到 4.1.1。 readme 按你选的方案 **并入现有 V4.1.0 节**,不新增一节(节数仍是 57), 在里面注明「这一条是 4.1.1 补的」以及 4.1.0 当时为什么没生效。 ### 验证 线上真实 WordPress 环境(不是桩): ``` ALLOWED_BEFORE=href,title ALLOWED_AFTER=href,title,target,rel HAS_TARGET=YES ``` PUC 实测: ``` INSTALLED=4.1.0 NEW=4.1.1 PKG=https://yangyq.net/theme-update/kratos_yang-4.1.1.zip HAS_UPDATE=YES ``` ### 判据 | 脚本 | 结果 | |---|---| | `_mkzip410.py`(含新增 11c 段 5 条) | **88 通过 / 0 失败** | | `_mkzip410_falsify.py`(含新增 17/18 组) | **18/18 抓到** | | `_readme410.py`(改造为替换模式) | **42 通过 / 0 失败** | | `_stat410.php` | **145 通过 / 0 失败** | | `_stat410_falsify.php` | **19/19 抓到** | | `_publish410.sh` | **0 失败 / 0 提醒** | 反证要点:删掉白名单补充必须变红、把补充挪出 `kratos_options()` 也必须变红 —— 两条都验过了,不是恒真。 ### 线上终态 ``` update.json : 4.1.1, md5 bd12b67f2c4ca5bec5053a02e49f2fe6 4.1.1 包 : 200(4.6 MB) 4.1.0 / 4.0.0 旧包 : 仍 200(旧客户端不 404) readme : 57 节,含「这一条是 4.1.1 补的」 主题目录 : 仍是 4.1.0(未被覆盖)—— 等你在后台点升级 git : 51c1620 已推送 ``` ### 顺带修的两处「脚本自己过期」 1. `_mkzip410_falsify.py` 的「版本号没改」注入锚点**写死** `Version: 4.1.0`。 升到 4.1.1 后锚点不存在,反证报「1 组没抓到」—— 看起来像判据没牙齿, 实际是注入脚本自己过期了。改成动态读当前版本号。 2. `_publish410.sh` 要求 readme 首节 **== 本版号**,但 4.1.1 并入 V4.1.0 节。 改成两头都验:首节是 4.1.x,且本版修复说明真在里面。 ### 百度统计凭据 明白,明天再申请。API Key / Secret / site_id / 授权码齐了告我, 我这边只差这一步就能看到真数据。
Author
Owner

用户确认可以关闭,凭据后续由站长自行在后台填写。

交付内容:主题设置页新增「访问统计」面板,调用百度统计 Tongji API 把数据直出后台,commit 见 4.1.0 / 4.1.1(c058ecc 之前的批次)。

关键设计三条:① 凭据(API Key / Secret Key / refresh_token)不进 options 表,单独存凭据文件,因此表单永远回显空框,空值语义必须是"不修改"而不是"清空";② 连接超时压到 5 秒 + blocking=false,任何失败路径都返回含 error 字段的数组,渲染层只负责显示不负责判成败,全链路无 wp_die 与异常外抛,后台绝不被外部 API 拖死;③ stat_auth_code 是一次性的,换完 refresh_token 即弃、不落库。

4.1.1 另修了该页链接无法新窗口打开的问题(wp_kses 静默剥 target/rel,在 kratos_options() 开头补白名单)。

线上已升级至 4.2.0 并验证通过。

用户确认可以关闭,凭据后续由站长自行在后台填写。 交付内容:主题设置页新增「访问统计」面板,调用百度统计 Tongji API 把数据直出后台,commit 见 4.1.0 / 4.1.1(`c058ecc` 之前的批次)。 关键设计三条:① 凭据(API Key / Secret Key / refresh_token)不进 options 表,单独存凭据文件,因此表单永远回显空框,空值语义必须是"不修改"而不是"清空";② 连接超时压到 5 秒 + blocking=false,任何失败路径都返回含 error 字段的数组,渲染层只负责显示不负责判成败,全链路无 wp_die 与异常外抛,后台绝不被外部 API 拖死;③ stat_auth_code 是一次性的,换完 refresh_token 即弃、不落库。 4.1.1 另修了该页链接无法新窗口打开的问题(wp_kses 静默剥 target/rel,在 kratos_options() 开头补白名单)。 线上已升级至 4.2.0 并验证通过。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: laoyang/wordpress-kratos-yang#33