laoyang
|
6f51d50a58
|
3.2.30: 文章目录进后台(新增「自动目录」标签页)—— 位置/插入点/默认展开可配,手写 [TOC] 永远优先
后台「主题设置」新增第 5 个标签页「自动目录」,7 个选项:
目录位置 正文中间 / 页面左侧(浮动)/ 页面右侧(浮动)
添加方式 手动(只认 [TOC])/ 自动
自动插入位置 文章开头 / 第一个 H1、H2 之前 / 文章结尾
默认状态 默认显示 / 默认隐藏
另外:目录标题、收录层级(H1~H4)、是否显示序号
设计要点:
- 插入方式拆成 toc_mode(manual/auto) x toc_anchor(start/first/end) 两个正交
字段,不合成一个四值下拉:侧栏模式下插入位置毫无意义,合并会产生歧义。
- 手写 [TOC] 绝对优先:yang_toc_plan() 里 $has_marker 最先返回,inline_at
='marker' 且 rail=false —— 否则同一篇会出现两份目录,老文章升级后突然多
出一个目录。这条是刻意守住的。
- 纯函数 + 薄壳分层:normalize_settings / plan / inject / build_list 全部不查库,
单进程可喂几十种组合;settings() 只负责读库 + 调纯函数 + static 缓存。
- <details open> 是唯一可靠的「默认收起」手段:展开态不接受 CSS 覆盖,只能
由 open 属性决定,所以隐藏时必须不输出该属性。
- 侧栏收起态必须 translateX(±100%) 完全移出视口:只做 opacity 会让透明面板
留在原位吃掉正文点击(探针 elementFromPoint 才抓得到)。
- yang_toc_rail_ready() 开关:侧栏在 wp_footer 输出,那时正文可能没走
the_content(缓存插件/OEmbed),标题没 id ⇒ 侧栏链接全跳不动却看起来正常。
宁可不出侧栏。
- 后台联动隐藏「插入位置」不用 jQuery show()/hide():会把 .section 的行内
display 写成 block,悄悄回退 3.2.28 的 display:flow-root BFC 修复。改用
class + display:none!important。
验证(全部在本轮重跑):
_test_toc.php 53/0 旧 TOC 行为回归
_toc330_logic.php 88/0 逻辑判据
_toc330_falsify 13/13 注入全变红且红对地方
_geo330_rail.js 73/0 侧栏真实几何(左/右 x 展开/收起 x 宽窄屏)
_geo330_falsify 8/8
_guard330.php 46/0 后台控件与运行时默认值双向一致
_guard330_falsify 8/8
_admin330_tab_check 36/0 新标签页几何 + 联动 + 载入态
_admin330_falsify 8/8
_admin328_check 13/0 3.2.28 布局回归
发布:只发 zip + readme + update.json 到 theme-update/,不动服务器主题目录,
用户在 WP 后台自行升级(沿用 3.2.24 起的策略)。
|
2026-10-25 12:18:52 +08:00 |
|
laoyang
|
420434aacf
|
3.2.28: 修后台设置页第一个配置项后面的大块空白(.section 建独立 BFC,不再被左侧浮动标签列的 clear:both 拉长到标签列等高;真实渲染实测 405px→93px)
|
2026-10-25 10:13:44 +08:00 |
|
laoyang
|
92ffced7cf
|
3.2.26: 评论布局重排(姓名/时间垂直居中于头像、正文全宽下移 10px,锚点黄底不再压作者徽章);引导线与子块留 8px 空隙并与缩进同源(--yq-my-indent);修深层缩进晚一层生效的存量 bug;后台设置页标题 margin 收编(项间空白 42→22px)
|
2026-10-24 23:58:55 +08:00 |
|
laoyang
|
fb12d58067
|
3.2.25: 评论框改为所见即所得编辑器(B/I/颜色/表情可视化+序列化白名单+纯文本粘贴);后台资源 enqueue 版本号改跟 THEME_VERSION(修设置页重排被浏览器缓存挡住的问题)
|
2026-10-24 22:38:10 +08:00 |
|
laoyang
|
fd27b4d218
|
3.2.24: 评论富文本工具条(B/I/颜色/表情移入)+ kses 放行 span+style + 后台设置页标签左列竖排重排
|
2026-10-24 21:59:06 +08:00 |
|
laoyang
|
4beb09307c
|
3.2.17: 修复「保存设置时静默清空未提交的字段」
现象
在主题设置里填了「附加日期」(例如 10-23),过一阵发现它变回空了,
于是当天的哀悼效果不再出现,而其他设置都好好的,看不出哪里坏了。
根因
inc/options-framework/includes/class-options-framework-admin.php 的
validate_options() 里,对每个选项无条件取 $input[$id] 去 sanitize:
$clean[$id] = apply_filters('of_sanitize_'.$type, $input[$id], $option);
一旦某次提交里没有这个键(分组没渲染、字段被条件隐藏、表单中途出错只
提交了一半……),$input[$id] 是未定义索引 ⇒ null ⇒ sanitize_text_field()
返回空串 ⇒ 把用户存了很久的配置写成空。而且"清成空串"和"用户主动清空"
在库里完全一样,事后无法区分,只表现为"我明明填过,怎么没了"。
实测证据(线上 PHP 8.5.12,同一套用例)
修复前:3 通过 / 2 失败,失败项同时伴随
PHP Warning: Undefined array key "g_rip_extra" ... line 252
修复后:5 通过 / 0 失败
改法
- 循环外先取一次 get_option('kratos') 作 $saved_options(不在循环里查库)
- 提交了(array_key_exists($id,$input))才 sanitize
- 没提交则沿用 $saved_options 里的旧值;旧值也没有就不写这个键,
让运行时的默认值继续生效
- checkbox / multicheck 保持原语义:这两类"没提交"确实等于"没勾",
沿用旧值会导致开关关不掉。用 $is_toggle 单独判定,与"沿用旧值"互斥
为什么这个改动值得单独发一版
它不是界面问题,是**数据被静默改写**。凡是这种"用户没动、值却变了"的
缺陷,都会让人怀疑数据不可靠;而且每次后台保存都可能再触发一次。
验证
- 线上 _test_keep_old_value.php:5/5(修复前 3/5,用例 2 精确复现空串)
- 本地 _static_317.php:8/8,第 4 组自带注入(改成老写法必须变红)
- 全量流水线 verify_all.sh:rc=0
- 线上浏览器 _live315.py:纪念日分支 33/33、非纪念日分支 27/27
(判据改为「由配置侧独立算 expect_show,再与页面渲染对比」,
不再无条件断言"必须有灰度",也不再拿页面自身当预期值)
|
2026-10-23 23:18:51 +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
|
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
|
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
|
a826b26bee
|
从gitee代码库中初始化项目。
|
2022-01-07 11:15:42 +08:00 |
|