Commit Graph
17 Commits
Author SHA1 Message Date
laoyang 0799268661 3.2.16: 删三项无用功能、选项重排为 10 组、修移动端提示条偏左
删除
- 微信二维码:配置项 s_wechat / 隐藏上传 s_wechat_url、footer 浮动块、
  默认图注册表条目、CSS 规则、wechat.png
- Font Awesome:选项 g_fontawesome、theme-core 入队、58KB 样式、
  assets/fonts/webfonts 全套字体(2.8MB,CSS 删后零引用)
- 多人模式:选项 multiusers、page-content / page-toolbar 两处分支

选项重排(9 组 → 10 组)
全站配置 / 顶部导航 / 首页配置 / 文章配置 / 站长信息 /
页脚配置 / 哀悼模式 / 收录与统计 / 邮件通知 / 自定义代码
哀悼 5 个字段从「首页配置」独立成「哀悼模式」——它不是首页专属功能。
g_404/g_chrome/g_animate/g_gravatar 等归「全站配置」,s_copyright 归「页脚配置」。

修复
- footer.php 版权行此前无条件硬编码,后台 s_copyright 填了不生效
- 附加日期命中时 yang_mourning_today_name() 返回空串,提示条退化成
  只说「今天是纪念日」而不给名字
- 小屏哀悼提示条 padding 写成 `7px 40px 7px 12px`(右宽左窄给关闭按钮
  让位),内容盒中心 181 vs 视口中心 195,整体偏左 14px。关闭按钮本就
  absolute 不占内部流,改为左右等宽 + 缩小按钮。桌面端同类错误 3.2.14 已修,
  此处漏了同一个断点。

加载顺序
theme-options.php 可能早于 theme-mourning.php / theme-custom-code.php 加载,
新增 yang_opt_* 四个 function_exists 包装,避免裸调用导致后台白屏。

验证:静态断言 62/0 · 后台面板 14/0 · 反向验证 7/7 与 44/0 ·
提示条 4 视口几何 20/0(注入旧 bug 精确复现 -14px)· 线上 3 视口 33/0
2026-10-23 22:56:10 +08:00
laoyang 96bbb3d1b5 v3.2.14 哀悼提示条改到整页最上方正中,只在纪念日当天出现
用户要求:哀悼说明「放在最上方的最中间,只在纪念日、哀悼模式开启时才显示,
其他时间不显示」。

改的过程中量出三个真问题:

1. 提示条与站点标题重叠 32px。提示条从 y=0 开始、高 40px,而导航栏
   .k-nav 是 position:absolute; top:0、z-index 1030;提示条 z-index 9990
   ⇒ 直接盖在 Logo 上。CSS 每个字都没写错,就是叠在一起。
2. 内容偏左 38px。为了给右侧关闭按钮让位写了 padding-right:46px、
   左侧只有 18px ⇒ 文字中心 602 vs 视口中心 640。"最中间"没做到。
3. 页脚有常驻的「纪念日哀悼说明」链接,一年 365 天都显示。平时打开
   站点的人既没看到黑白也不知在哀悼什么,这个入口本身就是困惑源。

修法:
- 提示条占文档流第一行、width:100%,内容交给内层 .ymb-inner 居中,
  左右 padding 等宽、关闭按钮 absolute 不占内部流 ⇒ 几何中心 = 视口中心。
- .k-nav 是绝对定位,body 的 padding 推不动它(这个原因单看代码想不到,
  是量几何量出来的)⇒ 单独补一条 body.has-yang-mourning-bar .k-nav{top:var(--ymb-h)}。
  偏移量由脚本读真实高度写入,窄屏换行后也准。
- 不用 fixed:那会滚动时顶部永远压一条深色带,内容从它下面穿过去。
- 移除页脚常驻入口与 wp_footer 注册,说明页入口只在提示条里。
  非哀悼日输出 0 字节。

验证:
- run_tests2 163 条(新增 9 条位置契约)
- _check_hidden.php 19 条(非哀悼日 0 字节、每个纪念日都能触发)
- mourning_bar_geometry.py 28 条(线上真实页面实测 4 种视口:
  顶部贴边、横跨整宽、居中误差 ≤1px、与标题和导航栏零重叠)
- falsify 44 条注入全被抓住(新增 9 条)

两个假绿当场被反面验证抓出并修掉:
- 判据照着 fixed 方案写,实现改成 relative 后 4 条断言集体假红
  (教训:设计前提变了,判据必须跟着改)。
- 页脚断言用 ob_start 取输出,但函数是 return 不是 echo ⇒ $fl 恒空
  ⇒ 注入"返回一个链接"仍全绿(教训:否定式断言在空输出上恒真)。
2026-10-23 20:43:25 +08:00
laoyang ef9a58dc51 v3.2.13 图片选项全部自带默认图,新装站点不再出现空洞/破图
根本原因:kratos_option() 运行时只读数据库,`std` 仅在后台表单预填和
「恢复默认」时生效,前台根本读不到。因此新装站点所有图片项都是空值。
另外 isset() 挡不住「用户清空」——of_sanitize_upload() 对空输入返回 '',
键存在且值为空串,调用方传的兜底值被绕过。

同时不能把绝对 URL 写进 std:它会随保存一起写库,主题目录改名/换 CDN/
迁站后永久 404。本站 s_wechat_url / g_donate_alipay / g_donate_wechat
三项就指向已删除的 kratos-master 目录,一直在破图(这三项是 hidden 字段,
后台界面上看不到,所以坏了很久没人发现)。

做法:
- 新增 inc/theme-option-defaults.php,图片默认值注册表(11 项),
  默认图一律运行时给出,不写数据库。
- yang_option_image() 三层判断:空/纯空白 → 默认图;指向已不存在的主题
  目录 → 默认图;文件不存在则不输出地址(避免换来一个 404)。
- 陈旧判断用 get_template()(主题 slug)而非 basename(目录名),避免本地
  开发目录名与 slug 不一致时误伤当前主题自己的图。
- 模板统一走取值函数:header(logo/favicon/banner)、footer(微信码/国旗)、
  404、index、article 缩略图、widgets 头像、page-toolbar、theme-core 赞赏码。
- 国旗与 favicon 的渲染抽成 yang_flag_block() / yang_favicon_tag(),
  空值时不输出空 src / 空 href。
- 后台 9 处 std 改为 yang_options_default_img(),与前台默认图同源;
  该包装函数自带 function_exists 防护,单独加载 theme-options.php 不会 Fatal。

新增图片(本地生成,合计 291KB):default-404 / default-share /
default-empty / default-avatar / default-banner / default-flag / favicon.ico。
favicon 为 WordPress 风格自绘(品牌蓝圆底 + 白色 W),非站长个人专用那张。

验证:run_tests 146 条(新增第 4 组图片默认值 22+ 条);falsify 35 条注入
全部能被抓住(新增 10 条);verify_all.sh 14 组全绿。
2026-10-23 19:50:07 +08:00
laoyang 704a9346e1 3.2.12 紧急修复:点「测试邮件」引发无限递归、内存耗尽
3.2.11 引入的事故。线上表现:
  Allowed memory size of 268435456 bytes exhausted
  in .../wp-includes/class-wp-block-parser.php on line 91
前台首页、后台登录页都正常,只有点那个按钮才炸。

根因不在区块解析器 —— 那只是递归链最后执行到的一行,看它会完全跑偏。

validate_options() 是 register_setting() 注册的 sanitize_callback,
WP 把它挂在 `sanitize_option_kratos` 过滤器上(wp-includes/option.php:3073)。
3.2.11 为了让"刚改的 SMTP 配置立刻生效",在这个回调里调了
update_option('kratos', $clean)。而 update_option() 内部**会再跑一遍**
同名过滤器 ⇒ 又调回 validate_options ⇒ 再 update_option ⇒ 无限递归。
每层都在复制 $clean 派生的大数组,直到吃满 256M。

本地实测确认递归存在:回调被调用 3 次、嵌套深度 2。

改法:不写库,改用一次性覆盖层。
- 分支里把 $clean 放进 $GLOBALS['__yang_options_override']
- kratos_option() 优先读它,取不到再读库
- 效果等价(点测试用的就是刚填的值),但不触发任何过滤器,不可能递归
- 覆盖层是本请求内的全局,不落库、不跨请求残留

assertions: run_tests 104 → 110
- 分支里**没有** update_option(这条是红线,直接对应本次事故)
- 分支里有覆盖层
- 覆盖层真生效:kratos_option 优先返回新值
- 覆盖层未命中的键仍读库(不能把别的配置一起抹掉)
- 撤掉覆盖层回到库值(不跨请求残留)
- 桩与产品代码一致:直接读 autoload.php 确认它也认覆盖层
  (桩比产品温和是这次漏检的根源之一 —— 桩里 update_option 不挂
   sanitize 过滤器,所以注入递归 bug 后 harness 根本不崩)

顺带修了两个测试基础设施问题:
1. wp_stub.php 的 kratos_option 与真实实现语义不一致。已同步,
   并加断言防止两边再次漂移(否则"测的是桩、不是产品代码")。
2. falsify 的 rebuild() 没把被 file_get_contents 读的 autoload.php
   复制进副本 ⇒ 新断言读不到文件 ⇒ 基线假红。

falsify 24 → 25:注入「分支里调 update_option」能抓住(靠静态红线,
不靠跑崩)、注入「去掉覆盖层」能抓住、注入「退回裸 wp_mail」能抓住。

全量 14 组全绿;run_tests 110 / run_tests2 154 / falsify 25。
2026-10-23 18:49:06 +08:00
laoyang 9a053169e9 3.2.11 后台「测试邮件」改走主题模板,并修发信早于落库
两个问题叠在一起,都不报错,只是「测了等于没测」:

1. 按钮发的是纯文本
   validate_options() 里 isset($_POST['sendmail']) 那个分支只有一句裸
   wp_mail(),正文是「恭喜您 SMTP 邮件服务配置成功!」。于是点测试收到的
   东西和真实评论通知(HTML 模板)完全不同,没法用它验收版式。
   主题里 yang_mail_send_test() 本来就是干这件事的,但全仓库零调用点 ——
   注释写着「后台测试按钮走这条」,实际上从没接上线。

2. 发信发生在配置落库之前
   validate_options() 是 register_setting 的回调,返回值之后才由 WP
   写进数据库。在分支里直接发信,主题配 PHPMailer 时读 kratos_option()
   拿到的还是上一次保存的主机/账号/密码。刚改完 SMTP 密码点测试,
   测的是旧密码,表现成「明明填对了却发不出去」。

改法:
- 分支里先 update_option('kratos', $clean) 再调 yang_send_test_mail()
- yang_send_test_mail() 优先调主题的 yang_mail_send_test();
  主题被换掉时退回原来的纯文本,不能因此白屏
- send_mail_notice() 从「一律发送完成」改成三态:WP_Error / wp_mail
  返回 false / 成功,前两者用 error 样式。原来密码错了也显示成功,
  用户会去邮箱里干等。

assertions: run_tests 95 → 104 条(新增 2k 组 8 条 + 1 条)
- sendmail 分支确实调用主题发信函数、确实先落库
- 落库位置在发信位置之前(判顺序,不判有没有)
- 分支里没有裸 wp_mail
- 发信函数对主题缺失做了兜底
- 提示语分三态,两类失败都用 error 样式

falsify 22 → 24 条:
- 注入「先发信后落库」⇒ 变红(这条一开始是假绿:判据比的是注释里
  那两个词谁先出现,注释里 update_option 和函数名都在,恒真。
  改成先剥注释再比代码里的位置)
- 注入「退回裸 wp_mail」⇒ 变红

顺带修了 falsify 的一个基础设施坑:options-framework 那批文件在工作区
是 CRLF,锚点里写 \n 对多行内容恒不匹配,报的是「命中 0 次」,
很像锚点选错了。inject() 现在先试原样、不中再把 \n 换 \r\n 重试一次。

全量 12 组 / 506 条断言全绿。
2026-10-23 18:28:53 +08:00
laoyang 6f50533e8e 3.2.10 修复邮件页脚显示字面量 ©
邮件正文会被 yang_mail_escape_for_wpdiscuz() 无差别地把 & 加倍,用来抵消
wpDiscuz 在过滤器之后又做的那一次 html_entity_decode —— 不加倍的话,访客
评论里的 <script> 会在解码时还原成活标签。

但这个替换是全局的,连模板骨架里手写的字符实体也一起被加倍。于是
&copy; 变成 &amp;copy;,能否正常显示完全取决于后面那次解码有没有执行:
走了解码 → 还原成实体 → 浏览器显示 ©;没走 → 只解析一次 → 收件人看到
字面量 &copy;。用户遇到的正是后者。

修法不是在转义函数上开口子(那会重新打开脚本注入口),而是让骨架不再
依赖实体:版权符号与 3px 分隔条的不换行空格一律输出 UTF-8 字符本身。
整封邮件本就声明 charset=UTF-8,这样无论被解码几次都不会变形。

用户数据(站名、标题、URL)仍走 esc_html/esc_url —— 那类 & 的转义是
必要的,加倍后能由那次解码还原,属于应当保留的一类。

assertions: run_tests 89 → 95 条(新增 1e 组 6 条)
- 骨架里零手写实体(0 次 decode 也不露原文)
- 经 0~5 次 html_entity_decode 始终是 ©、从不露实体原文
- URL/站名里的 & 仍被转义(不能把 esc_html 一起废掉)
- 分隔条是 U+00A0 裸字符,过过滤器后仍是裸字符

falsify 20 → 22 条,两处注入(页脚退回实体、分隔条退回实体)都能抓住。
全量流水线 14 组 / 465 条断言全绿。
2026-10-23 17:29:37 +08:00
laoyang 99e5a1ec0a 版本号 3.2.8 -> 3.2.9(邮件版式重做:白底 + 深色头部 + 暖色点缀)
用户对 3.2.8 的"整封同色系"给出了明确否定,并说明要什么:
背景要白的;顶部深蓝底白字;「新回复」标签不能用蓝色,
要黄或红、与头部有区分度;「查看完整讨论」按钮红底白字。

版式骨架改成了「白底 + 深头 + 暖色点缀」:
- page_bg 由主色浅底改为纯白;
- 头部改为主色的深版(deepen_to 压到白字 7:1),白字达 AAA;
- 标签用暖色族,具体哪个由"与头部色相距离"挑(pick_warm),
  主色偏红时金黄才撞得开;
- 按钮固定红 #c62828 + 白字(5.62:1);
- 新回复竖线改用同一套暖色,与标签呼应。

顺带修掉两个真 bug:
- 标题色的循环从最暗档开始扫,实际取到的是最暗档(亮蓝下几乎全黑),
  注释还写着"取最亮的达标档",方向与实现相反。改为从 0.90 往下扫,
  并加色度下限(不低于种子 70%),避免浅主色被压成黑色;
- 纯中性灰主色下 21px 标题取到 #717171 中灰,发虚。改为深墨色。

测试同步重写(旧判据服务的是"整封同色系",在白底版式下必红):
- mail_color_audit.py 改为守四条性质:白底、头部深且白字达标、
  标签暖色且撞开、按钮红底白字;定位靠结构不靠猜颜色;
- pixel_color_audit.py 改为像素级守同一组性质:白像素 ≥45%、
  顶部横跨的深色带、带内白字像素、带内撞色像素;
  撞色必须判「带内」(判全图会把底部红按钮算进来,没牙齿);
- falsify_color.py 四处注入全部退回旧做法,两层都能抓住;
- run_tests/run_tests2/visual_audit/mail_design_audit/mail_shot 里
  写死 #24292e 或"按钮跟主色"的过期断言一并改成本质性质。

verify_all.sh 14 组 / 463 条断言全绿(上一轮 320 条)。
2026-10-23 17:08:55 +08:00
laoyang 9c48ba56eb 版本号 3.2.7 -> 3.2.8(邮件配色整封同色系化 + 对比度修复)
邮件模板原来只有品牌条一个色块,其余大面积底色全是中性灰,
主色稍朴素(主题默认 #24292e)整封信看起来就是黑白的。

- 新增 yang_mail_palette():底色/描边/强调文字全部从主色同色系派生
- 新增 yang_mail_chroma()/hsl()/hsl_hex()/ensure_chroma()/has_hue()
- 修复深浅判定:白字对比度 = 黑字对比度的交叉点在 L≈0.179,不是 0.5。
  用 0.5 会把亮蓝 #00a2ff(L=0.331)判成深色配白字,只有 2.76:1
- 新增 yang_mail_fg_on():在白与深之间取对比度更高者,
  中灰 #808080 上从 4.41 提到 5.32(AA 达标)
- 修复:与白混合是同比压缩色度,0.94 档压 16 倍 ⇒ 近中性主色
  派生出的浅底只剩 0.002,物理不可见。加彩度兜底,按各自亮度补足
- 修复:页脚改浅底后 12px 说明文字降到 4.26:1,muted 改为按背景推导
- 品牌条/按钮仍严格用用户填的原色,补彩度只作用于派生色
2026-10-23 16:13:48 +08:00
laoyang 269364bed4 版本号 3.2.6 -> 3.2.7(纪念日全部默认不勾 + 彩色说明页 + 邮件彩色 + 双引用块)
哀悼默认值全部改为不勾选
- 九个纪念日的 default 全改成 0。之前法定那三个是 1,导致全新安装的站点
  一到日子就自己变黑白,而站主根本没同意过这件事。
- 后台多选组件的 std 全为 0,打开设置页一个都不勾。
- 「从未保存过」与「保存过但全不勾」两条路径都返回空数组,
  不能因为结果为空就回退到默认 —— 那等于用户取消勾选无效。

新增彩色说明页(inc/theme-mourning-page.php + pages/page-mourning.php)
- 站点变黑白后必须有个地方解释清楚,否则访客只看到一片灰、以为站点坏了。
- 三处入口:顶部提示条(wp_body_open,首屏可见)、页脚常驻链接、
  [yang_mourning] 短代码。
- 页面内容由真实配置驱动,不是写死的文案:没勾的日期不会出现,
  今天在列表里带「今天」标记,一个都没勾时明说「不会生效」。
- 页面必须保持彩色,否则解释「为什么是灰的」的页面自己也是灰的。
  取消灰度做了两层:filter:none 与 grayscale(0%) 都写,且带 !important;
  优先级放在 wp_head 的 2,晚于灰度样式的 1。
- 路由走 parse_request 优先级 5 而非 template_redirect:
  避免先走一遍 404 判定与 404 模板。带子目录安装时先剥掉前缀再比对。
- 不抢占用户自己建的页面:如果站里已有同名 slug 的页面就放手。
  否则用户会觉得「我建的页面坏了」,且没有任何提示。

邮件不受影响,且回复邮件同时给出双方内容
- 邮件是独立 HTML,不走 wp_head,结构上就不可能被灰度,已断言验证。
- theme-mail-template 的单个 quote 改成 quotes 列表,一个模板同时渲染
  「被回复的那条」与「新的回复」,最后一块用主色高亮(回复是重点),
  前面的用弱化色。只有一块时退化为主色,与旧行为一致。
- 通过 wpdiscuz_email_content 重建整封邮件,不需要改插件。
  审核通过通知用 wpdiscuz_comment_approved_email_content 走同一模板,
  但它只给 post_id、不给评论对象,能放的内容受此限制。

两个必须记下来的坑
- wpDiscuz 在过滤器之后还会做一次 html_entity_decode 再发信。
  esc_html 的输出会被那一步还原成真标签,评论里的 <script> 直接生效。
  所以返回前先把 & 编码一次。本地 stub 没有这一步,测试看不出来。
- header.php 从来没有调用 wp_body_open,挂在上面的东西全部静默失效。
  不调用不会报错,只会「什么都没发生」。已补在 <body> 之后。

yang_mail_text 保留换行
- 原来用 wp_strip_all_tags($t, true),第二个参数 remove_breaks 会把换行
  压成空格;而 strip_tags 遇到 <br> 是直接删掉不补换行的。
  结果整条评论被拍成一行,模板里的 nl2br 成了死代码。
- 改为先把块级边界降级成 \n,再去标签(remove_breaks=false)。
  收行内空白用 [^\S\n] 而不是 [ \t\f\v]:PCRE 的 \v 是垂直空白类,
  会把 \n 一起匹配掉。

说明页对比度修正(WCAG AA)
- 金色 #b8860b 在白底只有 3.06:1,当装饰色没问题,当 12-15px 文字偏虚。
  拆成 --ym-gold(装饰)与 --ym-gold-text(#946600,4.75:1,仍属金色)。
- 分组小标题 #9aa5b1 只有 2.36:1,改用 #5f6b78(5.12:1)。
- 「今天」标签白字压金底 3.25:1,改用 #8a6100(5.54:1)。
2026-10-23 14:43:47 +08:00
laoyang 37c52948c4 版本号 3.2.5 -> 3.2.6(统一邮件出口 + 邮件模板 + 纪念日自动哀悼 + 去掉广告位)
评论邮件重复的根因是发信源不止一个,分两层处理:一层管「谁发」,一层管「发几封」。
只做前者的话邮件照样重复,只是都从同一个 SMTP 出去。

邮件治理(新增 inc/theme-mail.php)
- phpmailer_init 优先级 9999 接管通道,保证主题配置优先于其它 SMTP 插件;
  接管前先重置可能被别的插件设过的字段,避免残留半套配置。
- From 强制等于 SMTP 登录账号。QQ / 163 会以「发件人不符」拒收。
- wp_mail 优先级 99 统一 From 与 Content-Type;调用方自带的 From 与
  Content-Type 被剥离后重建,自定义 header 保留。
- wp_mail 优先级 100 做短期去重:同一收件人 + 同主题 + 同正文,90 秒内只发一次。
  用 transient 是因为评论通知跨请求(wpDiscuz 走 AJAX、审核通知走后台请求),
  静态变量跨不过去。90 秒足够宽(一次提交引发的通知都在同一秒)也足够窄
  (手动重发通常隔几分钟,不该被吞)。
- 被去重的那次把 to 置空并由 pre_wp_mail 返回 true,而不是 return null:
  返回 null 会改变 wp_mail 返回值语义,有的插件据此判定失败并重试,反而更多封。
- 用 filter 关掉核心的 notify_postauthor / notify_moderator,不改进数据库里的
  comments_notify / moderation_notify。改用户设置属于越权,而且用户下次进后台
  一勾又回来了;filter 是可逆的。只拦通知,不影响评论审核流程。
- 线上站点 m_smtp=1 但 m_host 为空时判定为「未就绪」并完全放手。只勾开关不填
  主机时若强行接管,所有邮件会静默失败,比回退到 mail() 更糟。

邮件模板(新增 inc/theme-mail-template.php)
- 表格布局 + 全内联样式:Outlook 2007-2019 用 Word 引擎,不认 flex / grid;
  Gmail 会剥掉 <head> 里的 style 块(含 media query)。
- 不用外部图片、不用 CSS 变量、不用 ::before。按钮的 padding 放在 td 上,
  因为 Outlook 忽略 a 的 padding。
- 宽度用混合写法:HTML 属性 width=600 给 Outlook,CSS 的
  width:100% + max-width:600px 让手机窄屏自动收窄。只写死 600px 会让手机横向滚动。
- badge 背景用预算的实色而非 rgba。Outlook 不支持 rgba,会当成非法值丢掉,
  半透明背景变成全透明,badge 直接消失。
- 主色下沿那条 3px 深色带必须 height 同时写成 HTML 属性和 CSS,
  否则 Outlook 把它撑成一行文字高,比例就毁了。
- 用 WCAG 相对亮度判断主色深浅来决定叠在上面的文字用白还是深色。
  简单取平均会把 #00ff00 判成中等亮度(人眼对绿最敏感、蓝最不敏感)。
- 次要文字统一调到 #5f6b78。原来的浅灰在白底上只有 1.84:1,看着"高级"但读不出来;
  现在最低 4.62:1,全部过 AA。

theme-smtp.php
- 删掉 comment_notify / comment_approved 两个钩子及其硬编码模板。这两个钩子与
  wpDiscuz 的 notifyCommentSubscribers 完全重复,是重复收信的直接来源。

纪念日自动哀悼(新增 inc/theme-mourning.php)
- 法定纪念日三个默认勾选:09-03 抗战胜利纪念日、09-30 烈士纪念日、
  12-13 南京大屠杀死难者国家公祭日。依据是 2014 年全国人大常委会两次决定,
  已在代码注释里记下来源。
- 民间通行做法(05-12、09-18 等)默认不勾,由用户自己决定。替用户做价值判断不合适。
- 一次性全国哀悼日(2020-04-04 等)支持 YYYY-MM-DD 写法,只在当年生效。
  不能当周年循环,否则明年 4 月 4 日又自动变黑白。
- 按站点时区判断(current_time),不按服务器时区。服务器在 UTC 时会晚 8 小时生效。
- 只挂 wp_head 而不限定 is_home():老代码限定首页,详情页不黑白,那是 bug。
- 打印时取消灰度,否则打印出来是灰的。
- 保留旧的 g_rip 手动常开开关。不能因为升级把原本黑白中的站点变回彩色。

options-framework
- 补 multicheck 的渲染分支与 of_sanitize_multicheck。框架原来只在保存侧处理了
  这个类型,没有渲染分支,所以这个类型此前根本不可用。
- 值存成 array(键 => 1|0) 而不是「勾选了的键组成的列表」:列表无法区分
  「用户明确全部取消」和「从未配置」,会导致取消勾选后静默回退到默认值。
- 读取侧用 array_key_exists 判断是否保存过,而不是判断值是否为空。
- 配套补上 of-multicheck 样式,没有样式时 9 个选项会挤成一行流水文字。

去掉广告配置
- 删除主题选项里的广告配置段,以及 single.php 里上下两处广告位输出。

验证
- 全主题 68 个 PHP 文件语法检查通过。
- 行为测试 82 条、multicheck 闭环 25 条、邮件几何 15 条、设计审计 23 条。
- 反向验证 16 个注入 bug 全部被断言抓住(去重失效、不写标记、to 未清空、
  From 未剥离、亮度算法退化、时区改用 date()、全不勾回退默认等)。
- 邮件最低文字对比度 4.62:1;320px 视口下卡片自动收窄到 296px 不溢出。
2026-10-23 13:55:23 +08:00
laoyang 4f0d19fb91 版本号 3.2.4 -> 3.2.5(修 Prism 报错,项目地址改为自建 Git)
## 修 Prism is not defined

首页控制台报 `Uncaught ReferenceError: Prism is not defined`,来源是
wp-editormd 插件(不是本主题):它在 Prism 脚本加载之前就无条件执行了
`Prism.plugins.autoloader.languages_path = "..."`。

线上页面里的真实顺序:
    第 668 行  该赋值语句
    第 695 行  prism-core.min.js
    第 696 行  prism-autoloader.min.js

在 `wp_head` 优先级 0 垫一个最小空壳 `window.Prism`(含 plugins.autoloader
这条访问链路),让那句赋值有地方可写。已核对 prism-core.min.js 源码:它开头
只读 `u.Prism.manual` 与 `u.Prism.disableWorkerMessageHandler`,结尾
`global.Prism = Prism` 整体覆盖 —— 空壳必定被真实对象替换,不承担持久职责,
只为吸收那条过早的赋值。已用 jsdom 验证:去掉空壳重现原始报错,加上后消失,
且之后 `Prism.highlightAll` 仍可正常调用。

## 项目地址统一到 code.yangyq.net

原作者署名、原主题地址、README、语言包、JS 里的控制台横幅全部换成自建仓库地址。
LICENSE 文件保持原样不动(MIT 要求保留原始版权声明)。

## CDN 分支改成拿不到就回退本地

原来的 `g_cdn` 分支指向 `cdn.jsdelivr.net/gh/vtrois/kratos`,本仓库在自建
Gitea 上,jsDelivr 只认 github/npm/wordpress,实测该地址 404。现改为仅在
`YANG_THEME_CDN_REPO` 常量存在时才走 CDN,否则一律用
`get_template_directory_uri()`。留一个 404 的地址比留空更危险:页面能开、
样式全丢,极难排查。

## 顺带

- `.gitattributes` 补 `*.mo binary`:原来是靠"含 NUL"启发式判定,不是契约
- `update.json` 的版本号跟进到 3.2.5
- `kratos.min.js` / `kratos.min.css` 用压缩器重新生成,不手工改产物
2026-10-23 12:55:41 +08:00
laoyang b36f4b184a 版本号 3.2.3 -> 3.2.4(修复升级时二进制资源被行尾归一化破坏) 2026-10-23 12:27:05 +08:00
laoyang 16ea692e5d 版本号 3.2.2 -> 3.2.3(用于验证自动更新全链路) 2026-10-23 10:47:23 +08:00
laoyang eaabc7717f 把自定义样式从主题目录搬到主题选项,升级不再丢
原来主题里有个 custom/ 目录(custom.css / custom.js / custom.php),
是给用户随手加样式的入口。问题是它位于主题目录内,而 WordPress 升级
主题的做法是把整个主题目录删掉再解压(class-theme-upgrader.php 里
clear_destination = true,随后 delete_old_theme() 递归删除),
所以每次升级这些文件都会丢。custom.php 还会被 require,
等于在主题里留了一个执行任意 PHP 的口子。

改成后台选项:
- 主题选项新增「自定义代码」组,含自定义 CSS / 自定义 JS 两个输入框
- 内容存在数据库的 kratos 选项里,与主题文件解耦,升级、重装都不影响
- 分别在主题样式和主题脚本之后加载,优先级最高

为此新增了 code 字段类型,没有复用 textarea:textarea 的 sanitizer
走 wp_kses(),CSS 的子选择器 > 和 JS 的 < 会被当成 HTML 标签剥掉,
样式会静默失效。code 类型只去掉 PHP 标签、打断 </style> 和 </script>,
其余原样保留。渲染时也不走公共的 stripslashes —— options.php 已经
wp_unslash 过,再剥一层会把 CSS 的 \f101、JS 的 \n 啃坏。

自定义 CSS 的默认值就是原来 custom.css 的内容,逐字节一致(354 字节),
所以升级瞬间站点外观没有任何变化。清空输入框保存即可彻底去掉。

验证:真实 WP 7.1.3 / PHP 8.5.12 环境 20 条断言全过(含 DB 往返、
子选择器 / 反斜杠保真、危险内容过滤),生产选项指纹前后一致;
反向注入「默认值改空」能立刻被抓到。
2026-10-23 10:35:56 +08:00
laoyang 8a55457210 版本号升到 3.2.1
自动升级靠 style.css 的 Version 判断有没有新版本,不升号线上永远收不到更新。
同时 THEME_VERSION 是静态资源 ?ver= 的缓存键,升号后浏览器会重新拉取,
避免用户拿着旧 CSS 看不到本次的样式修复。
2026-10-23 09:06:35 +08:00
laoyang 8aba7d7cc3 发现之前的代码,好像和原主题的代码混合在一起,因此,重新提交一份纯净的代码,这样也不再和之前的主题进行关联了。完全由我自己来维护。 2022-05-14 17:50:47 +08:00
laoyang a826b26bee 从gitee代码库中初始化项目。 2022-01-07 11:15:42 +08:00