侧栏挂件一致性:标题可配、标签列数、重复 id、URL 转义 #20

Closed
opened 2026-10-26 14:46:11 +00:00 by laoyang · 2 comments
Owner

来源

3.2.49(#19 热点只显示一条)修完后,通读 inc/theme-widgets.php 全文(547 行)逐个挂件核对,剩下的 5 处问题。与 #19 是独立可分别验收的另一件事,故单独立单。

需求清单

一、四个挂件的标题在后台改不了(不读 $args)

register_sidebar() 已经注册了 before_title = '<div class="title">' / after_title = '</div>',但下列挂件完全不用 $args,标题要么硬编码、要么根本没有:

挂件 现状
widget_tags echo '<div class="title">' . __('标签聚合', 'kratos') . '</div>'; 硬编码,后台无输入框
widget_ad 完全没有标题,也没有 title 字段
widget_about 同上
widget_posts 同上

widget_search 是唯一有 title 字段的(但它自己手写 <div class="title">,也不读 $args)。

诉求:四个挂件都加 title 字段(form() 输入框 + update() 清洗 + 前台渲染),渲染时走 $args['before_title'] / after_title,让侧栏注册的版式统一生效。

二、标签聚合的列数随屏宽反转(窄屏反而多一列)

线上真实栅格(.k-main > .container > .row > .col-lg-8 + .col-lg-4.sidebar)逐视口实测:

视口 侧栏宽 内容宽 标签宽 每行
1600 427px 336.7px 164px 2
1440 427px 336.7px 164px 2
1311 427px 336.7px 164px 2
1310 427px 336.7px 110px 3
1280 320px 230px 110px 2
1180 320px 230px 110px 2

根因在 assets/css/kratos.css 的 @media screen and (max-width: 1310px):它把 .k-main .sidebar .w-tags .item a 的宽度从 164px 改成 110px。但侧栏在这个区间并没有变窄(仍 336.7px 内容区),于是窗口变窄 1px、列数反而从 2 变成 3。

违反的是一条基本性质:视口越窄,每行能放的元素数不该更多。

诉求:列数只由容器宽度决定,随视口同向变化(单调不增)。不要靠「再调一个魔法数字」糊过去——现在的 164/110 就是两次魔法数字叠加的结果。

三、widget_posts 有重复 id

widget() 里同一套标签页导航写了两份(桌面带图标、手机不带图标):

<div class="nav nav-tabs d-none d-xl-flex" id="nav-tab">   ← 桌面
  <a id="nav-new-tab" ...>  <a id="nav-hot-tab" ...>  <a id="nav-random-tab" ...>
</div>
<div class="nav nav-tabs d-xl-none" id="nav-tab">          ← 手机
  <a id="nav-new-tab" ...>  <a id="nav-hot-tab" ...>  <a id="nav-random-tab" ...>
</div>

nav-tab、nav-new-tab、nav-hot-tab、nav-random-tab 各出现两次 ⇒ HTML 非法;aria-labelledby="nav-new-tab" 只能指到第一份。而且挂件可以添加多个实例,多实例时 id 撞得更彻底。

诉求:id 全局唯一。桌面/手机两份导航合并成一份,靠 CSS 在窄屏隐藏图标。

四、搜索框写死 id

widget_search::widget() 硬编码 id="searchform" / id="search" / id="searchsubmit"。主题/核心的另一个搜索表单也用 searchform 时会撞;挂两个搜索挂件同样撞。

诉求:id 唯一化(或改用 class / aria-label),并补上可访问性所需的 label 关联。

五、图片 URL 未加引号,且缺转义

// widget_ad
'<a href="' . $url . '" ...><img src="' . $image . '"><div class="prompt">' . $subtitle . '</div></a>'
//            ↑ 未 esc_url            ↑ src 的值没有引号

// widget_about
'<div class="background" style="background:url(' . $background . ') no-repeat center center;...'
//                                            ↑ url() 里没引号

url() 里不加引号,只要路径含空格、括号、单引号就会截断;src= 的值不加引号,URL 里一旦有空格,后半截会被解析成另一个属性。

诉求:URL 加引号并 esc_url(),文本 esc_html(),补 alt。

验收标准

  • 四个挂件在后台设标题后,前台走 before_title / after_title 版式显示;清空则不输出标题节点。
  • 标签列数在 1024 / 1180 / 1280 / 1310 / 1311 / 1440 / 1600 各视口下单调不增(窄屏不得比宽屏列数多);标签不溢出容器。
  • 渲染出的 HTML 里 id 无重复(用 DOM 解析器统计,不靠肉眼);一个页面挂两个「文章聚合」也不撞。
  • 图片 URL 含空格 / 括号 / 引号时,src 与 url() 都不被截断。
  • 全部改动走真实渲染 + 真浏览器几何验证,不只看源码。

为什么单独建单

#19 只处理「热点标签页为什么只出 1 条」(候选池被最近30天时间窗卡住),改的是排序与取数逻辑。本单是另外 5 处独立缺陷:标题可配置性、标签列数随屏宽反转(CSS 媒体查询)、重复 HTML id、搜索框固定 id、图片 URL 引号与转义。两者文件区域不同(#19 在 most_comm_posts()/yang_hot_posts(),本单在 widget_*::widget() 与 kratos.css),可各自单独验收,不属于同一需求的补充。#15~#18 是归档页/评论区/短代码,与侧栏挂件无关。

## 来源 3.2.49(#19 热点只显示一条)修完后,通读 `inc/theme-widgets.php` 全文(547 行)逐个挂件核对,剩下的 5 处问题。与 #19 是**独立可分别验收**的另一件事,故单独立单。 ## 需求清单 ### 一、四个挂件的标题在后台改不了(不读 `$args`) `register_sidebar()` 已经注册了 `before_title = '<div class="title">'` / `after_title = '</div>'`,但下列挂件**完全不用 `$args`**,标题要么硬编码、要么根本没有: | 挂件 | 现状 | |---|---| | `widget_tags` | `echo '<div class="title">' . __('标签聚合', 'kratos') . '</div>';` 硬编码,后台无输入框 | | `widget_ad` | 完全没有标题,也没有 `title` 字段 | | `widget_about` | 同上 | | `widget_posts` | 同上 | `widget_search` 是唯一有 `title` 字段的(但它自己手写 `<div class="title">`,也不读 `$args`)。 **诉求**:四个挂件都加 `title` 字段(`form()` 输入框 + `update()` 清洗 + 前台渲染),渲染时走 `$args['before_title']` / `after_title`,让侧栏注册的版式统一生效。 ### 二、标签聚合的列数随屏宽**反转**(窄屏反而多一列) 线上真实栅格(`.k-main > .container > .row > .col-lg-8 + .col-lg-4.sidebar`)逐视口实测: | 视口 | 侧栏宽 | 内容宽 | 标签宽 | 每行 | |---|---|---|---|---| | 1600 | 427px | 336.7px | 164px | 2 | | 1440 | 427px | 336.7px | 164px | 2 | | **1311** | **427px** | **336.7px** | **164px** | **2** | | **1310** | **427px** | **336.7px** | **110px** | **3** | | 1280 | 320px | 230px | 110px | 2 | | 1180 | 320px | 230px | 110px | 2 | 根因在 `assets/css/kratos.css` 的 `@media screen and (max-width: 1310px)`:它把 `.k-main .sidebar .w-tags .item a` 的宽度从 164px 改成 110px。**但侧栏在这个区间并没有变窄**(仍 336.7px 内容区),于是窗口变窄 1px、列数反而从 2 变成 3。 违反的是一条基本性质:**视口越窄,每行能放的元素数不该更多**。 **诉求**:列数只由容器宽度决定,随视口同向变化(单调不增)。不要靠「再调一个魔法数字」糊过去——现在的 164/110 就是两次魔法数字叠加的结果。 ### 三、`widget_posts` 有重复 id `widget()` 里同一套标签页导航写了两份(桌面带图标、手机不带图标): ```html <div class="nav nav-tabs d-none d-xl-flex" id="nav-tab"> ← 桌面 <a id="nav-new-tab" ...> <a id="nav-hot-tab" ...> <a id="nav-random-tab" ...> </div> <div class="nav nav-tabs d-xl-none" id="nav-tab"> ← 手机 <a id="nav-new-tab" ...> <a id="nav-hot-tab" ...> <a id="nav-random-tab" ...> </div> ``` `nav-tab`、`nav-new-tab`、`nav-hot-tab`、`nav-random-tab` 各出现两次 ⇒ HTML 非法;`aria-labelledby="nav-new-tab"` 只能指到第一份。而且挂件**可以添加多个实例**,多实例时 id 撞得更彻底。 **诉求**:id 全局唯一。桌面/手机两份导航合并成一份,靠 CSS 在窄屏隐藏图标。 ### 四、搜索框写死 id `widget_search::widget()` 硬编码 `id="searchform"` / `id="search"` / `id="searchsubmit"`。主题/核心的另一个搜索表单也用 `searchform` 时会撞;挂两个搜索挂件同样撞。 **诉求**:id 唯一化(或改用 class / `aria-label`),并补上可访问性所需的 label 关联。 ### 五、图片 URL 未加引号,且缺转义 ```php // widget_ad '<a href="' . $url . '" ...><img src="' . $image . '"><div class="prompt">' . $subtitle . '</div></a>' // ↑ 未 esc_url ↑ src 的值没有引号 // widget_about '<div class="background" style="background:url(' . $background . ') no-repeat center center;...' // ↑ url() 里没引号 ``` `url()` 里不加引号,只要路径含空格、括号、单引号就会截断;`src=` 的值不加引号,URL 里一旦有空格,后半截会被解析成另一个属性。 **诉求**:URL 加引号并 `esc_url()`,文本 `esc_html()`,补 `alt`。 ## 验收标准 - 四个挂件在后台设标题后,前台走 `before_title` / `after_title` 版式显示;清空则不输出标题节点。 - 标签列数在 1024 / 1180 / 1280 / 1310 / 1311 / 1440 / 1600 各视口下**单调不增**(窄屏不得比宽屏列数多);标签不溢出容器。 - 渲染出的 HTML 里 `id` 无重复(用 DOM 解析器统计,不靠肉眼);一个页面挂两个「文章聚合」也不撞。 - 图片 URL 含空格 / 括号 / 引号时,`src` 与 `url()` 都不被截断。 - 全部改动走真实渲染 + 真浏览器几何验证,不只看源码。 --- ### 为什么单独建单 #19 只处理「热点标签页为什么只出 1 条」(候选池被最近30天时间窗卡住),改的是排序与取数逻辑。本单是另外 5 处独立缺陷:标题可配置性、标签列数随屏宽反转(CSS 媒体查询)、重复 HTML id、搜索框固定 id、图片 URL 引号与转义。两者文件区域不同(#19 在 most_comm_posts()/yang_hot_posts(),本单在 widget_*::widget() 与 kratos.css),可各自单独验收,不属于同一需求的补充。#15~#18 是归档页/评论区/短代码,与侧栏挂件无关。
Author
Owner

3.2.50 已发布到主题更新通道

提交:8367231(main 已推送)
升级方式:WP 后台 → 外观 → 主题 → 检查更新 → 升到 3.2.50(服务器 wp-content/themes/kratos_yang/ 未被覆盖,仍为 3.2.49,等你在后台点升级)


五处改动

① 标题不读 $args(4 个挂件标题后台改不了)

新增 yang_widget_title($args, $instance, $fallback, $id_base),5 个挂件统一走它:

  • 先读 $instance['title'],空则回落 $fallback
  • 过 widget_title 过滤器(兼容别的插件改标题)
  • 输出用 $args['before_title'] / after_title(所以换侧栏样式也跟着变)
  • 仍为空则整个标题节点不输出(不留空壳 <div class="title"></div>)

四个挂件的 update() 补 title、form() 补输入框 + 「留空则不显示标题」说明。
widget_tags 留空回落「标签聚合」,老站点显示不变。

② 标签列数随屏宽反转(1440 一列、1180 反而两列)

逐视口实测线上真实栅格,真实断点不在 1440/1180,而在 1311→1310:

视口 侧栏宽 内容区 标签宽 每行
1600 / 1440 / 1311 427px 336.7px 164px 2 个
1310 427px(不变) 336.7px 110px 3 个
1280 / 1180 / 1024 320px 230px 110px 2 个

根因:@media (max-width:1310px) 只把格子压窄、不改侧栏宽度(侧栏 992px 以上恒为容器 1/3),格子变小了自然能塞更多。

改为 width: calc(50% - 2px) + 父容器 font-size:0(消掉 inline-block 之间的空白宽度),1310 断点里删掉这两条 110px 规则(分类/归档的 110px 不是网格,保留)。
判据守的是「列数单调不增」,不是「某视口恰好两列」 —— 后者等于把当前 bug 固化成期望值。

③ 重复 id nav-tab(桌面一份、手机一份)

两份导航合并为一份,id 全部带实例后缀:yang_widget_uid($this) 取 $widget->id(如 widget_posts-2),输出 {uid}-tab / {uid}-new 等,aria-controls / aria-labelledby 同步指向真实 id。两个实例并用也不撞。

④ 搜索框固定 id(searchform / search / searchsubmit)

同样改为 {uid}-form / {uid}-field / {uid}-submit,并补 <label class="sr-only" for="{uid}-field">搜索</label>。

⑤ 图片 URL 未加引号 + 未转义

src / href 一律加引号 + esc_url(),alt 用 wp_strip_all_tags + esc_attr,简介用 nl2br(esc_html())。

自曝一个我自己写出来的 bug:一开始写成 esc_url() 套在 esc_attr() 里 = 双重转义,& 变 &amp;amp;,CSS 里拿到字面量 amp,背景图 404。是 _widgets_consistency.php 的「背景 URL 完整」判据抓出来的。改为 url() 内部用 esc_url_raw()(只做协议/字符清洗,不入实体),整个 style 属性交给 esc_attr() 统一转义一次。

顺带把侧栏标签页图标在 <1200px 隐藏,避免三个 tab 挤在一起。


验证结果(全绿)

脚本 结果 守什么
_widgets_consistency.php 68 / 0 标题走 $args · id 唯一 · URL 引号与转义 · 后台表单
_widgets_consistency_falsify.php 24 / 0 10 组注入各 2 条(基线1·自检3·注入20)
_widgets_hot_test.php 33 / 0 热点渲染
_widgets_hot_falsify.php 20 / 0 热点反向
_widgets_hot_geom.js 24 / 0 真实浏览器几何
_widgets_ids_dom.js 5 / 0 真实 DOM 查重复 id · 老 id 彻底消失
_widgets_visual20.js 30 / 0 像素级:图片 naturalWidth>0、标题装饰线、标签等宽、tab 点击真生效
_widgets_icon_wide.js 7 / 0 断点精确在 1200
_widgets_tags_geom.js 5 / 0 列数单调性
发布脚本 R1~R10 10/10 真红 每条修复逐一破坏,对应判据必须报红

发布链路:包内 7 条改动逐条回读 · HTTP 下载 md5 一致 · unzip -t 通过 · update.json 最后才落版本号。


等你升级到 3.2.50 并确认这五处没问题后,我再关闭本工单。

## 3.2.50 已发布到主题更新通道 **提交**:`8367231`(`main` 已推送) **升级方式**:WP 后台 → 外观 → 主题 → 检查更新 → 升到 3.2.50(服务器 `wp-content/themes/kratos_yang/` **未被覆盖**,仍为 3.2.49,等你在后台点升级) --- ### 五处改动 **① 标题不读 `$args`(4 个挂件标题后台改不了)** 新增 `yang_widget_title($args, $instance, $fallback, $id_base)`,5 个挂件统一走它: - 先读 `$instance['title']`,空则回落 `$fallback` - 过 `widget_title` 过滤器(兼容别的插件改标题) - 输出用 `$args['before_title']` / `after_title`(所以换侧栏样式也跟着变) - 仍为空则**整个标题节点不输出**(不留空壳 `<div class="title"></div>`) 四个挂件的 `update()` 补 `title`、`form()` 补输入框 + 「留空则不显示标题」说明。 `widget_tags` 留空回落「标签聚合」,**老站点显示不变**。 **② 标签列数随屏宽反转(1440 一列、1180 反而两列)** 逐视口实测线上真实栅格,真实断点不在 1440/1180,而在 **1311→1310**: | 视口 | 侧栏宽 | 内容区 | 标签宽 | 每行 | |---|---|---|---|---| | 1600 / 1440 / 1311 | 427px | 336.7px | 164px | 2 个 | | **1310** | **427px(不变)** | 336.7px | **110px** | **3 个** | | 1280 / 1180 / 1024 | 320px | 230px | 110px | 2 个 | 根因:`@media (max-width:1310px)` **只把格子压窄、不改侧栏宽度**(侧栏 992px 以上恒为容器 1/3),格子变小了自然能塞更多。 改为 `width: calc(50% - 2px)` + 父容器 `font-size:0`(消掉 inline-block 之间的空白宽度),1310 断点里删掉这两条 110px 规则(分类/归档的 110px 不是网格,保留)。 **判据守的是「列数单调不增」,不是「某视口恰好两列」** —— 后者等于把当前 bug 固化成期望值。 **③ 重复 id `nav-tab`(桌面一份、手机一份)** 两份导航合并为一份,id 全部带实例后缀:`yang_widget_uid($this)` 取 `$widget->id`(如 `widget_posts-2`),输出 `{uid}-tab` / `{uid}-new` 等,`aria-controls` / `aria-labelledby` 同步指向真实 id。**两个实例并用也不撞。** **④ 搜索框固定 id(`searchform` / `search` / `searchsubmit`)** 同样改为 `{uid}-form` / `{uid}-field` / `{uid}-submit`,并补 `<label class="sr-only" for="{uid}-field">搜索</label>`。 **⑤ 图片 URL 未加引号 + 未转义** `src` / `href` 一律加引号 + `esc_url()`,`alt` 用 `wp_strip_all_tags` + `esc_attr`,简介用 `nl2br(esc_html())`。 > **自曝一个我自己写出来的 bug**:一开始写成 `esc_url()` 套在 `esc_attr()` 里 = **双重转义**,`&` 变 `&amp;amp;`,CSS 里拿到字面量 `amp`,背景图 404。是 `_widgets_consistency.php` 的「背景 URL 完整」判据抓出来的。改为 `url()` 内部用 `esc_url_raw()`(只做协议/字符清洗,不入实体),整个 `style` 属性交给 `esc_attr()` 统一转义一次。 顺带把侧栏标签页图标在 `<1200px` 隐藏,避免三个 tab 挤在一起。 --- ### 验证结果(全绿) | 脚本 | 结果 | 守什么 | |---|---|---| | `_widgets_consistency.php` | **68 / 0** | 标题走 `$args` · id 唯一 · URL 引号与转义 · 后台表单 | | `_widgets_consistency_falsify.php` | **24 / 0** | 10 组注入各 2 条(基线1·自检3·注入20) | | `_widgets_hot_test.php` | **33 / 0** | 热点渲染 | | `_widgets_hot_falsify.php` | **20 / 0** | 热点反向 | | `_widgets_hot_geom.js` | **24 / 0** | 真实浏览器几何 | | `_widgets_ids_dom.js` | **5 / 0** | 真实 DOM 查重复 id · 老 id 彻底消失 | | `_widgets_visual20.js` | **30 / 0** | 像素级:图片 `naturalWidth>0`、标题装饰线、标签等宽、tab 点击真生效 | | `_widgets_icon_wide.js` | **7 / 0** | 断点精确在 1200 | | `_widgets_tags_geom.js` | **5 / 0** | 列数单调性 | | 发布脚本 R1~R10 | **10/10 真红** | 每条修复逐一破坏,对应判据必须报红 | 发布链路:包内 7 条改动逐条回读 · HTTP 下载 md5 一致 · `unzip -t` 通过 · `update.json` **最后**才落版本号。 --- **等你升级到 3.2.50 并确认这五处没问题后,我再关闭本工单。**
Author
Owner

按用户确认收尾,交付见上条发布评论。

交付版本:3.2.50(挂件标题可配、标签列数、重复 id、URL 转义 5 处一并修)

用户已验收,关闭本单。

按用户确认收尾,交付见上条发布评论。 交付版本:3.2.50(挂件标题可配、标签列数、重复 id、URL 转义 5 处一并修) 用户已验收,关闭本单。
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#20