在页面 /blogtime(ID 522)正文里写 [Archives],前台原样输出成 <p>[Archives]</p>,什么都没渲染。
/blogtime
[Archives]
<p>[Archives]</p>
服务器主题已是 3.2.46,inc/theme-archives.php 在、functions.php 第 66 行也 require 了, add_shortcode('archives', ...) 也注册成功(WP 环境下 $shortcode_tags['archives'] 可调用)。
inc/theme-archives.php
functions.php
add_shortcode('archives', ...)
$shortcode_tags['archives']
WordPress 的短代码标签是区分大小写的,我 3.2.46 里的注释写反了。
我在 inc/theme-archives.php 末尾写的是:
WP 的 shortcode 标签是大小写不敏感的(它内部统一 strtolower), 所以注册小写 archives 就能同时匹配 [Archives] / [ARCHIVES] / [archives]。
archives
这句话是错的,内核证据:
wp-includes/shortcodes.php:330
$tagregexp = implode( '|', array_map( 'preg_quote', $tagnames ) );
直接拿注册名拼正则,没有做任何大小写归一化。
wp-includes/shortcodes.php:155 / 189
preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER );
修饰符只有默认,没有 i ⇒ 正则大小写敏感。
i
do_shortcode_tag() 里 $tag = $m[2] 之后直接 $shortcode_tags[$tag] 精确键查表 ⇒ 名字对不上就是查不到。
do_shortcode_tag()
$tag = $m[2]
$shortcode_tags[$tag]
所以 add_shortcode('archives', ...) 只能匹配全小写 [archives], 文档里写的 [Archives](首字母大写)压根匹配不上,WordPress 就把原文原样吐出来。
[archives]
[archives] 长度=209239 是否被解析=是 [Archives] 长度=10 是否被解析=否(原样返回) [ARCHIVES] 长度=10 是否被解析=否(原样返回) [aRcHiVeS] 长度=10 是否被解析=否(原样返回)
[archives] 小写是正常的:渲染出 15 篇文章、12 个月份分组、年份标题 2026。
5b41726368697665735d
[
\[
同时注册两种大小写,覆盖用户最可能写的形态,并修正注释:
add_shortcode('archives', 'yang_archives_shortcode'); add_shortcode('Archives', 'yang_archives_shortcode');
⚠️ 注意不能靠 strtolower 之类"全局改",因为 WP 的 regex 是从 $shortcode_tags 的 key 直接拼出来的,唯一的办法就是把要支持的写法逐个注册。
strtolower
$shortcode_tags
同时 shortcode_atts() 第 3 个参数('archives')也要跟着改 —— 它只用于过滤 shortcode_atts_<tag> 钩子,两种注册下都要能命中,所以传当前实际匹配到的 tag。
shortcode_atts()
'archives'
shortcode_atts_<tag>
year=
month=
nav=
先回答你的猜测:不是 markdown 插件的问题,它没有转义任何东西。
数据库里存的原始字节是 5b41726368697665735d = [Archives], 干干净净,没有 [、没有 \[。页面输出 <p>[Archives]</p> 是 WordPress 把没匹配上的短代码原样吐出的默认行为。
WordPress 的短代码标签区分大小写。我在 3.2.46 里写的注释说"大小写不敏感、内部统一 strtolower"——这句是错的。
内核证据(wp-includes/shortcodes.php):
wp-includes/shortcodes.php
preg_match_all('/'.get_shortcode_regex().'/', ...)
if (!is_callable($shortcode_tags[$tag]))
$m[2]
所以 add_shortcode('archives', ...) 只能匹配全小写。你按文档写 [Archives],正则压根不认。
线上实测(真实 WP 环境跑 do_shortcode,不是模拟):
do_shortcode
[archives] 长度=209239 解析=是 [Archives] 长度=10 解析=否(原样返回) [ARCHIVES] 长度=10 解析=否(原样返回) [aRcHiVeS] 长度=10 解析=否(原样返回)
改签名时发现原来的 yang_archives_shortcode($atts, $tag = 'archives') 也错了。
yang_archives_shortcode($atts, $tag = 'archives')
WordPress 按 call_user_func($fn, $attr, $content, $tag) 传三个参数, 我漏了中间的 $content,于是 $tag 落到了第 2 个形参上、实际收到空串。
call_user_func($fn, $attr, $content, $tag)
$content
$tag
后果比较隐蔽:归档看起来能用(默认值恰好对),但 shortcode_atts_Archives 这个开发者扩展钩子退化成无主的 shortcode_atts_,等于悄悄失效。
shortcode_atts_Archives
shortcode_atts_
这个坑有意思在于:直接调函数是好的(f($atts, 'Archives') 完全正确), 只有过 do_shortcode 才错。所以断言必须走真实解析路径才抓得到。
f($atts, 'Archives')
Archives
ARCHIVES
($atts, $content = '', $tag = 'archives')
shortcode_atts
style.css 版本号 3.2.46 → 3.2.47
style.css
[aRcHiVeS](任意混合大小写)刻意不支持 —— 那要把每个位置都注册一条, 正则会膨胀到不可维护。三种习惯写法已覆盖真实使用。已加断言锁住这个取舍, 避免将来有人当 bug 报。
[aRcHiVeS]
原来的测试是假绿的,而且绿得很彻底。
_archives_unit.php 里 add_shortcode 的测试替身写的是:
_archives_unit.php
add_shortcode
$GLOBALS['__shortcodes'][strtolower($tag)] = $fn; // ← 多做了 WP 不做的事
它比真实 WordPress 更宽容。于是产品在线上坏着,本地 94 条断言全绿。 断言本身也软:只查 isset($__shortcodes['archives']), 从没验过 [Archives] 这个用户实际会写的形态。
isset($__shortcodes['archives'])
修法:
do_shortcode()
.yang-archives
反向验证 4 条(把错误改回去必须变红):
R5 只注册全小写 archives(3.2.46 的错误形态) → 红 10 条 R6 大写注册指向不存在的回调 → 红 4 条 R7 shortcode_atts 的 tag 写死小写 → 红 2 条 R8 签名去掉中间的 $content → 红 3 条
2j 评论关闭零输出 通过 54 / 0 失败 2k 评论关闭反向验证 真红 13 / 0 无效 2l 归档单测 通过 109 / 0 失败 (原 94,新增 15 条) 2m 归档端到端 通过 109 / 0 失败 2n 归档端到端反向 真红 21 / 0 无效 2o 归档源码级反向 真红 10 / 0 无效 (原 6,新增 4 条) lint 96 个 PHP 文件全过
线上真实 WordPress 验证(在服务器上用 WP 内核跑,不是本地模拟):
补注册前:只 ["archives"] → [Archives] 原样返回 补注册后:["archives","Archives","ARCHIVES"] [archives] 长度=209239 解析=是 [Archives] 长度=209239 解析=是 [ARCHIVES] 长度=209239 解析=是 [aRcHiVeS] 长度=10 解析=否(刻意)
/theme-update/
update.json
3.2.47
Version: 3.2.47
db2e9c4
main
如果后台没出现更新提示,可能是更新检查有缓存,等一会儿或去「仪表盘 → 更新」手动点一次"重新检查"。
工单先不关,你确认没问题再关。
你已经点过升级了,服务器主题目录现在是 3.2.47(注册条数 4)。 刚抓了线上真实页面确认:
GET https://yangyq.net/blogtime 200 / 252,647 字节
ar-item
ar-month
ar-stat
ar-nav
is-disabled
?arc_year=2025
渲染出来的结构长这样:
<div class="yang-archives" id="yang-archives-1" data-year="2026"> <div class="ar-head"> <h3 class="ar-year" data-role="year-title">2026</h3> <span class="ar-total" data-role="year-total">共 15 篇</span> <div class="ar-nav ar-nav-top">...</div> </div> <div class="ar-body" data-role="body"> <div class="ar-month"> <h4 class="ar-month-title">1 月<span class="ar-month-count">7</span></h4> <ul class="ar-list"><li class="ar-item">...</li></ul> </div> ... </div> </div>
抓页只能证明服务端首屏对了。下面三条要靠你点一下:
你点完上面三条没问题的话,我就关掉这个工单。(也可以直接回一句"好了")
按用户确认收尾,交付内容见上条发布评论。
交付版本:3.2.47(修 [Archives] 大写首字母不生效,WP 短代码标签区分大小写)
用户已验收(线上主题已升级至 3.2.58,包含本单全部改动),关闭本单。
No dependencies set.
The note is not visible to the blocked user.
现象
在页面
/blogtime(ID 522)正文里写[Archives],前台原样输出成<p>[Archives]</p>,什么都没渲染。服务器主题已是 3.2.46,
inc/theme-archives.php在、functions.php第 66 行也 require 了,add_shortcode('archives', ...)也注册成功(WP 环境下$shortcode_tags['archives']可调用)。根因
WordPress 的短代码标签是区分大小写的,我 3.2.46 里的注释写反了。
我在
inc/theme-archives.php末尾写的是:这句话是错的,内核证据:
wp-includes/shortcodes.php:330直接拿注册名拼正则,没有做任何大小写归一化。
wp-includes/shortcodes.php:155 / 189修饰符只有默认,没有
i⇒ 正则大小写敏感。do_shortcode_tag()里$tag = $m[2]之后直接$shortcode_tags[$tag]精确键查表 ⇒ 名字对不上就是查不到。所以
add_shortcode('archives', ...)只能匹配全小写[archives],文档里写的
[Archives](首字母大写)压根匹配不上,WordPress 就把原文原样吐出来。实测证据(线上 WP 环境直接跑 do_shortcode)
[archives]小写是正常的:渲染出 15 篇文章、12 个月份分组、年份标题 2026。责任归属
5b41726368697665735d=[Archives],没有被转义(没有[、没有\[)。修复方案
同时注册两种大小写,覆盖用户最可能写的形态,并修正注释:
⚠️ 注意不能靠
strtolower之类"全局改",因为 WP 的 regex 是从$shortcode_tags的 key 直接拼出来的,唯一的办法就是把要支持的写法逐个注册。
同时
shortcode_atts()第 3 个参数('archives')也要跟着改 —— 它只用于过滤shortcode_atts_<tag>钩子,两种注册下都要能命中,所以传当前实际匹配到的 tag。验收标准
[Archives](首字母大写)在页面上正常渲染归档列表。[archives](全小写)仍然正常(不能修一个坏一个)。year=/month=/nav=)在两种写法下都生效。已修好并发布 3.2.47
先回答你的猜测:不是 markdown 插件的问题,它没有转义任何东西。
数据库里存的原始字节是
5b41726368697665735d=[Archives],干干净净,没有
[、没有\[。页面输出<p>[Archives]</p>是 WordPress把没匹配上的短代码原样吐出的默认行为。
根因:我 3.2.46 只注册了小写标签
WordPress 的短代码标签区分大小写。我在 3.2.46 里写的注释说"大小写不敏感、内部统一 strtolower"——这句是错的。
内核证据(
wp-includes/shortcodes.php):preg_match_all('/'.get_shortcode_regex().'/', ...)iif (!is_callable($shortcode_tags[$tag]))$m[2]精确查表所以
add_shortcode('archives', ...)只能匹配全小写。你按文档写[Archives],正则压根不认。线上实测(真实 WP 环境跑
do_shortcode,不是模拟):顺带抓出第二个 bug:短代码签名漏了中间那个参数
改签名时发现原来的
yang_archives_shortcode($atts, $tag = 'archives')也错了。WordPress 按
call_user_func($fn, $attr, $content, $tag)传三个参数,我漏了中间的
$content,于是$tag落到了第 2 个形参上、实际收到空串。后果比较隐蔽:归档看起来能用(默认值恰好对),但
shortcode_atts_Archives这个开发者扩展钩子退化成无主的
shortcode_atts_,等于悄悄失效。这个坑有意思在于:直接调函数是好的(
f($atts, 'Archives')完全正确),只有过
do_shortcode才错。所以断言必须走真实解析路径才抓得到。改了什么
inc/theme-archives.phparchives/Archives/ARCHIVES三种写法($atts, $content = '', $tag = 'archives')shortcode_atts第 3 参数改为实际命中的$tag(原来写死'archives')style.css版本号 3.2.46 → 3.2.47测试也一起修了(这是本轮最该说的)
原来的测试是假绿的,而且绿得很彻底。
_archives_unit.php里add_shortcode的测试替身写的是:它比真实 WordPress 更宽容。于是产品在线上坏着,本地 94 条断言全绿。
断言本身也软:只查
isset($__shortcodes['archives']),从没验过
[Archives]这个用户实际会写的形态。修法:
strtolowerdo_shortcode()解析实现(大小写敏感、与内核同构的正则).yang-archives、混合大小写原样返回、未注册标签原样保留、
shortcode_atts收到的 tag 恰好是命中的那个反向验证 4 条(把错误改回去必须变红):
验证结果
线上真实 WordPress 验证(在服务器上用 WP 内核跑,不是本地模拟):
发布状态
/theme-update/,update.json已落3.2.47Version: 3.2.47,4 处add_shortcodedb2e9c4已推main请你验一下
/blogtime刷新,不需要改页面内容 ——[Archives]会自动开始渲染。[archives](全小写)也还正常,别修一个坏一个。工单先不关,你确认没问题再关。
线上已生效 —— 实测通过
你已经点过升级了,服务器主题目录现在是 3.2.47(注册条数 4)。
刚抓了线上真实页面确认:
[Archives]原样文字.yang-archives根容器ar-itemar-monthar-statar-navis-disabled(2026 是今年,正确禁用)?arc_year=2025(正确)渲染出来的结构长这样:
别忘了验这几下(JS 部分静态抓页看不出来)
抓页只能证明服务端首屏对了。下面三条要靠你点一下:
地址栏变成
?arc_year=2025。状态
db2e9c4(带 #17)你点完上面三条没问题的话,我就关掉这个工单。(也可以直接回一句"好了")
按用户确认收尾,交付内容见上条发布评论。
交付版本:3.2.47(修 [Archives] 大写首字母不生效,WP 短代码标签区分大小写)
用户已验收(线上主题已升级至 3.2.58,包含本单全部改动),关闭本单。