主题设置新增「写作增强」标签页:四引擎(别名/标签/分类/摘要)自动生成 #25

Closed
opened 2026-10-27 06:50:07 +00:00 by laoyang · 2 comments
Owner

来源

用户 2026-10-27 提出,希望写文章时自动生成别名(英文 slug)、标签、分类、摘要。先问「主题能做吗,还是必须用插件」,我做了四组实测后给出方案,用户确认后开工。

一、需求(用户确认的 7 条)

# 决策 用户原话
1 标签优先从已有池匹配,匹配不了才新增;每篇新增数量上限由用户设置(默认 3) 「优先从已有标签池匹配,如果确实匹配不了,则新增,每篇文章有新增数量限制,例如3,这个可以让用户自己设置」
2 只在点击发布时触发,不在自动保存时触发 「那就在点击发布的时候吧,否则用户还没写几个字,标签也确实不好生成」
3 绝不覆盖已有值;只对新建文章生效,旧文章完全不动 「只有在新增的时候才启用这个功能,修改都不改变别名,因为别名可能意味着url,url变化了会造成大量的404错误」
4 分类不排除小样本,直接给最接近的一个 「不排除,直接给他选中最接近的分类就可以了」
5 摘要只写 post_excerpt 「只写post_excerpt」
6 服务商内置 3 个 「三个吧,前三个」
7 每项独立引擎,能本地就不用外部服务 「每项都是独立引擎,是不是要用外部服务,你来判断,能不用就不用,为了安全着想」

用户已删除原 wp-autotags 插件(该插件把文章内容发往第三方 cws.9sep.org)。

二、线上环境实测(决定了方案边界)

事实 数值 影响
已发布文章 967 篇 —
已有摘要的文章 0 篇 摘要功能立刻有收益
已有英文 slug 的文章 966 / 967 绝不能覆盖,否则全站 404
标签总数 703 个(其中 338 个只用过 1 次) 需要新增上限防膨胀
分类总数 29 个(9 个少于 20 篇,最少 3 篇) 小分类仍参与推荐
中文分词扩展 无 scws / 无 jieba 纯 PHP 做不到"发现新词"
编辑器 经典编辑器 + wp-editormd(非 Gutenberg) 自动保存触发频率低
PHP 8.5.12,有 intl / mbstring / curl / OPcache 拼音与外部调用可用

三、四引擎技术路线(各自独立,逐个可开关)

① 别名(英文 slug)—— 必须外部 API

纯 PHP 做不到翻译。实测三条路:

路线 结果 判定
intl 拼音 信息焦虑综合症 → xin-xi-jiao-lu-zong-he-zheng 可用但不是翻译,太长
966 组历史数据做词对齐 5 个测试标题里 3 个译出 0 个词 不可用(数据太稀疏)
翻译 API(MyMemory) Information Anxiety Syndrome → information-anxiety-syndrome 可用

② 标签 —— 本地优先,外部可选

  • 本地引擎(默认):703 个标签当词典,正向最大匹配 + TF-IDF。
    实测 60 篇 / 243 个人工标签:Top8 召回 74.9%,无一篇全错,单篇 17~27ms。
  • 外部引擎(可选):匹配不到时调用服务商 API 提关键词,用于发现新词。
  • 新增上限:每篇最多新增 N 个(用户可设,默认 3),且新词需满足「正文出现 ≥2 次」。
  • 纯 n-gram 方案已排除:Top50 只命中 4~7 个真标签,效果不可用。

③ 分类 —— 纯本地

  • 朴素贝叶斯(第一版)只有 60%,且疯狂预测仅 6 篇的「病毒相关」
    (p(tag|cat) 分母小 ⇒ 小分类被抬高几十倍)。
  • 改为 P(分类|标签) 加权投票(天然在分类间归一化):
    Top1 90.0% / Top3 99.0%(290 篇单分类文章)。
  • 按用户要求不排除小样本分类。

④ 摘要 —— 纯本地

  • 抽取式(TextRank 简化版)+ 中文分句 + 词典词权加权。
  • 实测 4ms 出 150 字,5 篇人工阅读通顺、抓得住主旨。
  • 只写 post_excerpt。

四、服务商清单(服务器侧已探活)

服务商 探活 是否需 Key 说明
MyMemory HTTP 200 否 免费、开箱能用,国外,有每日限额
百度翻译 HTTP 200 是(APPID + 密钥) 国内稳定,免费版每月 5 万字符
有道翻译 HTTP 200 是(应用 ID + 密钥) 国内备选
Google 翻译 HTTP 000 — 服务器不可达,排除
LibreTranslate 超时 — 排除

后台需为每个服务商提供如何申请 Key 的说明(含申请入口链接与步骤)。

五、待实现清单

  1. inc/theme-options.php 新增「写作增强」标签页(type=heading 分组,框架天然支持多标签页)
  2. inc/theme-writing.php(新建)四引擎 + 服务商适配层
  3. 后台选项:
    • 普通复选框 ×4(别名 / 标签 / 分类 / 摘要,各自独立开关)
    • 标签:引擎选择(本地 / 外部)、每篇新增上限(数字)
    • 别名:服务商选择 + API 凭据 + 申请说明
    • 标签外部引擎:服务商选择 + API 凭据(可复用同组凭据)
  4. 触发时机:publish_post(只发布)+ 新建判定($update === false)+ 幂等标记
  5. 安全:Key 存 get_option('kratos'),页面上脱敏显示(只显示后 4 位)

六、硬约束(红线)

  1. 绝不覆盖已有值 —— slug 变更 = 全站 404。只填空白字段。
  2. 绝不处理旧文章 —— 只在新建文章发布时触发。
  3. 外部 API 失败必须静默降级 —— 不能让发布流程卡住或报错。
  4. 不依赖外部服务的功能不许因网络问题失效 —— 分类/摘要纯本地。
  5. 幂等 —— 同一篇文章重复发布不重复调用外部 API、不重复新增标签。

七、验收标准

  • 后台「主题设置」出现「写作增强」标签页,四引擎均可独立开关
  • 新建文章发布后:slug / 标签 / 分类 / 摘要 按勾选项自动填入
  • 旧文章(任何状态)的 slug / 标签 / 分类 / 摘要 零变化(数据库前后比对)
  • 已手填过 slug 的新文章,发布后 slug 不被改动
  • 标签新增数不超过设定上限
  • 外部 API 断网时,分类与摘要仍正常工作
  • 外部 API 报错时,发布流程不中断、不报错
  • 同一篇文章重复保存,外部 API 调用次数不增加

为什么单独建单

主题设置新增「写作增强」标签页 + 四引擎自动生成(别名/标签/分类/摘要)。是在途工单里没有的新功能域,与 #24 分类挂件(已关闭)、#22 最近评论挂件等均无关。

## 来源 用户 2026-10-27 提出,希望写文章时自动生成**别名(英文 slug)、标签、分类、摘要**。先问「主题能做吗,还是必须用插件」,我做了四组实测后给出方案,用户确认后开工。 ## 一、需求(用户确认的 7 条) | # | 决策 | 用户原话 | |---|---|---| | 1 | 标签优先从已有池匹配,匹配不了才新增;**每篇新增数量上限**由用户设置(默认 3) | 「优先从已有标签池匹配,如果确实匹配不了,则新增,每篇文章有新增数量限制,例如3,这个可以让用户自己设置」 | | 2 | 只在**点击发布**时触发,不在自动保存时触发 | 「那就在点击发布的时候吧,否则用户还没写几个字,标签也确实不好生成」 | | 3 | **绝不覆盖已有值**;只对新建文章生效,旧文章完全不动 | 「只有在新增的时候才启用这个功能,修改都不改变别名,因为别名可能意味着url,url变化了会造成大量的404错误」 | | 4 | 分类**不排除小样本**,直接给最接近的一个 | 「不排除,直接给他选中最接近的分类就可以了」 | | 5 | 摘要**只写 `post_excerpt`** | 「只写post_excerpt」 | | 6 | 服务商内置 3 个 | 「三个吧,前三个」 | | 7 | **每项独立引擎**,能本地就不用外部服务 | 「每项都是独立引擎,是不是要用外部服务,你来判断,能不用就不用,为了安全着想」 | 用户已删除原 `wp-autotags` 插件(该插件把文章内容发往第三方 `cws.9sep.org`)。 ## 二、线上环境实测(决定了方案边界) | 事实 | 数值 | 影响 | |---|---|---| | 已发布文章 | 967 篇 | — | | 已有摘要的文章 | **0 篇** | 摘要功能立刻有收益 | | 已有英文 slug 的文章 | 966 / 967 | **绝不能覆盖**,否则全站 404 | | 标签总数 | 703 个(其中 **338 个只用过 1 次**) | 需要新增上限防膨胀 | | 分类总数 | 29 个(**9 个少于 20 篇**,最少 3 篇) | 小分类仍参与推荐 | | 中文分词扩展 | **无 scws / 无 jieba** | 纯 PHP 做不到"发现新词" | | 编辑器 | 经典编辑器 + wp-editormd(非 Gutenberg) | 自动保存触发频率低 | | PHP | 8.5.12,有 intl / mbstring / curl / OPcache | 拼音与外部调用可用 | ## 三、四引擎技术路线(各自独立,逐个可开关) ### ① 别名(英文 slug)—— 必须外部 API 纯 PHP 做不到翻译。实测三条路: | 路线 | 结果 | 判定 | |---|---|---| | intl 拼音 | `信息焦虑综合症` → `xin-xi-jiao-lu-zong-he-zheng` | 可用但不是翻译,太长 | | 966 组历史数据做词对齐 | 5 个测试标题里 **3 个译出 0 个词** | **不可用**(数据太稀疏) | | 翻译 API(MyMemory) | `Information Anxiety Syndrome` → `information-anxiety-syndrome` | **可用** | ### ② 标签 —— 本地优先,外部可选 - **本地引擎(默认)**:703 个标签当词典,正向最大匹配 + TF-IDF。 实测 60 篇 / 243 个人工标签:**Top8 召回 74.9%,无一篇全错**,单篇 17~27ms。 - **外部引擎(可选)**:匹配不到时调用服务商 API 提关键词,用于**发现新词**。 - **新增上限**:每篇最多新增 N 个(用户可设,默认 3),且新词需满足「正文出现 ≥2 次」。 - **纯 n-gram 方案已排除**:Top50 只命中 4~7 个真标签,效果不可用。 ### ③ 分类 —— 纯本地 - 朴素贝叶斯(第一版)**只有 60%**,且疯狂预测仅 6 篇的「病毒相关」 (`p(tag|cat)` 分母小 ⇒ 小分类被抬高几十倍)。 - 改为 **P(分类|标签) 加权投票**(天然在分类间归一化): **Top1 90.0% / Top3 99.0%**(290 篇单分类文章)。 - 按用户要求**不排除小样本分类**。 ### ④ 摘要 —— 纯本地 - 抽取式(TextRank 简化版)+ 中文分句 + 词典词权加权。 - 实测 **4ms 出 150 字**,5 篇人工阅读通顺、抓得住主旨。 - 只写 `post_excerpt`。 ## 四、服务商清单(服务器侧已探活) | 服务商 | 探活 | 是否需 Key | 说明 | |---|---|---|---| | MyMemory | HTTP 200 | 否 | 免费、开箱能用,国外,有每日限额 | | 百度翻译 | HTTP 200 | 是(APPID + 密钥) | 国内稳定,免费版每月 5 万字符 | | 有道翻译 | HTTP 200 | 是(应用 ID + 密钥) | 国内备选 | | ~~Google 翻译~~ | **HTTP 000** | — | 服务器不可达,排除 | | ~~LibreTranslate~~ | 超时 | — | 排除 | 后台需为每个服务商提供**如何申请 Key 的说明**(含申请入口链接与步骤)。 ## 五、待实现清单 1. **`inc/theme-options.php`** 新增「写作增强」标签页(`type=heading` 分组,框架天然支持多标签页) 2. **`inc/theme-writing.php`**(新建)四引擎 + 服务商适配层 3. 后台选项: - 普通复选框 ×4(别名 / 标签 / 分类 / 摘要,各自独立开关) - 标签:引擎选择(本地 / 外部)、每篇新增上限(数字) - 别名:服务商选择 + API 凭据 + 申请说明 - 标签外部引擎:服务商选择 + API 凭据(可复用同组凭据) 4. **触发时机**:`publish_post`(只发布)+ 新建判定(`$update === false`)+ 幂等标记 5. **安全**:Key 存 `get_option('kratos')`,页面上**脱敏显示**(只显示后 4 位) ## 六、硬约束(红线) 1. **绝不覆盖已有值** —— slug 变更 = 全站 404。只填空白字段。 2. **绝不处理旧文章** —— 只在新建文章发布时触发。 3. **外部 API 失败必须静默降级** —— 不能让发布流程卡住或报错。 4. **不依赖外部服务的功能不许因网络问题失效** —— 分类/摘要纯本地。 5. **幂等** —— 同一篇文章重复发布不重复调用外部 API、不重复新增标签。 ## 七、验收标准 - [ ] 后台「主题设置」出现「写作增强」标签页,四引擎均可独立开关 - [ ] 新建文章发布后:slug / 标签 / 分类 / 摘要 按勾选项自动填入 - [ ] 旧文章(任何状态)的 slug / 标签 / 分类 / 摘要 **零变化**(数据库前后比对) - [ ] 已手填过 slug 的新文章,发布后 slug **不被改动** - [ ] 标签新增数不超过设定上限 - [ ] 外部 API 断网时,分类与摘要仍正常工作 - [ ] 外部 API 报错时,发布流程不中断、不报错 - [ ] 同一篇文章重复保存,外部 API 调用次数不增加 --- ### 为什么单独建单 主题设置新增「写作增强」标签页 + 四引擎自动生成(别名/标签/分类/摘要)。是在途工单里没有的新功能域,与 #24 分类挂件(已关闭)、#22 最近评论挂件等均无关。
Author
Owner

已发布 3.2.57,等你确认后关闭

代码已提交并推送:0a5486e(commit 信息带 #25)。
发布包已落到 theme-update/,update.json 已指向 3.2.57,md5 fa7169428795e8ca7414321612fec9b9。

升级方式:后台「外观 → 主题」点更新即可。升级完四个开关都是关的,行为和你现在完全一样;想用哪个再进去勾。


一、你问的核心问题:要不要装插件

不用。主题自己就能做。 四个功能全部在主题内置,没有引入任何第三方插件,也没有把文章内容发到任何第三方站点(wp-autotags 那种行为本版完全没有)。


二、四个引擎分别在哪儿、怎么工作的

功能 触发条件 用什么算 外部服务
英文别名 别名是空的、或不是纯 ASCII 翻译 API 译标题 → 转 slug 可选,默认 MyMemory(免注册)
标签 只在标签为空时 703 个标签当词典,正向最大匹配 + 频次 不用
分类 只在还挂「未分类」时 朴素贝叶斯(已按你的决策不排除小样本) 不用
摘要 只在 post_excerpt 为空时 取正文开头 不用

三、本版修掉的一个真 bug

做发布前验证时发现的:apply() 里原来写的是

list($suggest, $_) = yang_writing_make_tags($title, $body);

make_tags() 返回两段——词典里已命中的标签和词典外值得新建的候选。$_ 把第二段整个丢掉了;接着又拿 $suggest 去 get_term_by 做二次分类,而 $suggest 里每个词都来自词典、必然全部命中 ⇒ 「新建标签」那个分支永远进不去。

也就是说:你第 1 条决策(「标签需要新增,如果不能新增,一个新的博客永远无法使用这个功能」)在真实发布流程里一次都没生效过。改成 list($exist_hits, $new_cands) 后实测「上限=3 时真的新建 3 个」。

这个 bug 只有走端到端(真发一篇文章、再查数据库)才看得见——光看源码,make_tags() 本身是对的,, $_ 在 PHP 里也完全合法。


四、验证数据

项目 结果
写作端到端(真实 WP 里真发文章、真查库) 71 通过 / 0 失败
后台设置页渲染(伪装管理员走框架真实渲染入口) 21 通过 / 0 失败
密钥保存路径(走真实 of_sanitize_password) 9 通过 / 0 失败
反证:端到端 W1~W16 16 组全部符合预期
反证:后台 A1~A6 6 组全部符合预期
反证:发布包注入 R1~R14 14 组全部让失败数从 0 增加
测试自身不污染 跑完标签库仍是 703、已发布文章仍是 967
线上主题目录 仍是 3.2.56,未被覆盖(红线)

反证的意思是:故意把产品改坏,确认判据会红。比如把钩子从 transition_post_status 改成 save_post、把 slug 改成一律覆盖、把 append 改成 false、把 list($exist_hits, $new_cands) 退回 list($suggest, $_)——每一组都精确红在对那条判据上。

这一步不是形式主义:本轮我自己就写出过两条永远绿的假判据,都是靠反证揪出来的(详见下方"踩坑")。


五、过程中值得记下来的坑

  1. GROUPS 是 bash 只读内建数组(存用户组 ID)。我拿它当计数器,赋值被静默忽略,最后输出「1008 组」——恰好是本机主组 ID。
  2. list($a, $_) 丢返回值:语法完全合法,静态检查全过,只有端到端能抓。
  3. count=0 不是"可删除"的判据:反查 term_relationships 发现有的标签 count=0 但仍有 1 条关系,只是计数器漂移。按 count=0 清孤儿会误删真实标签。
  4. 判据撞自己写的注释:源码注释里写着「74.9% 不可复现」「不许覆盖」这些反面教材原文,判据不先剥注释就会在正确代码上报红。
  5. array( 是 in_array( 的子串:判"有没有内联字数组"时写 strpos($ln,'array('),在 in_array($tail, yang_writing_seps(), true) 这种正确代码上也命中,假红。
  6. PHP 单引号里 \$ 不转义:'#in_array\(\s*\\\$tail#' 得到的是「字面反斜杠 + 行尾锚点」,永远匹配不到 $tail。教训不是记反斜杠个数,而是别在多层转义里手写正则。
  7. 设置页文案里的 74.9% 已清除:换成本轮实测的 49.2%(Top8 命中人工标签)/ 59.0%(只算字面出现在正文里的人工标签),并加了守卫防复发。工单正文里那段历史记录保留(那是过程记录,不是给用户看的界面文案)。

六、需要你在后台确认的

  1. 后台「外观 → 主题设置」,左侧应该出现**「写作增强」**独立标签页。
  2. 勾上「自动生成英文别名」,下面那组「服务商 / 百度 APPID / 百度密钥 / 有道 …」才显示;不勾时是藏着的。
  3. 勾上「自动生成标签」,「每篇最多新增几个」才显示,默认 3。
  4. 填一个密钥保存,刷新页面——密钥框应该是空的,但下面有一行「当前已保存:****abcd」。
  5. 真发一篇测试文章,看四项有没有按预期生成(或者按预期不动)。

确认没问题我再关闭工单。有任何一项不符合,直接在这个单里说。

## 已发布 3.2.57,等你确认后关闭 代码已提交并推送:`0a5486e`(commit 信息带 #25)。 发布包已落到 `theme-update/`,`update.json` 已指向 3.2.57,md5 `fa7169428795e8ca7414321612fec9b9`。 **升级方式**:后台「外观 → 主题」点更新即可。升级完四个开关都是关的,行为和你现在完全一样;想用哪个再进去勾。 --- ### 一、你问的核心问题:要不要装插件 **不用。主题自己就能做。** 四个功能全部在主题内置,没有引入任何第三方插件,也没有把文章内容发到任何第三方站点(`wp-autotags` 那种行为本版完全没有)。 --- ### 二、四个引擎分别在哪儿、怎么工作的 | 功能 | 触发条件 | 用什么算 | 外部服务 | |---|---|---|---| | 英文别名 | 别名是空的、或不是纯 ASCII | 翻译 API 译标题 → 转 slug | **可选**,默认 MyMemory(免注册) | | 标签 | 只在标签为空时 | 703 个标签当词典,正向最大匹配 + 频次 | 不用 | | 分类 | 只在还挂「未分类」时 | 朴素贝叶斯(已按你的决策不排除小样本) | 不用 | | 摘要 | 只在 `post_excerpt` 为空时 | 取正文开头 | 不用 | --- ### 三、本版修掉的一个真 bug 做发布前验证时发现的:`apply()` 里原来写的是 ```php list($suggest, $_) = yang_writing_make_tags($title, $body); ``` `make_tags()` 返回两段——**词典里已命中的标签**和**词典外值得新建的候选**。`$_` 把第二段整个丢掉了;接着又拿 `$suggest` 去 `get_term_by` 做二次分类,而 `$suggest` 里每个词都来自词典、必然全部命中 ⇒ **「新建标签」那个分支永远进不去**。 也就是说:你第 1 条决策(「标签需要新增,如果不能新增,一个新的博客永远无法使用这个功能」)在真实发布流程里**一次都没生效过**。改成 `list($exist_hits, $new_cands)` 后实测「上限=3 时真的新建 3 个」。 这个 bug 只有走端到端(真发一篇文章、再查数据库)才看得见——光看源码,`make_tags()` 本身是对的,`, $_` 在 PHP 里也完全合法。 --- ### 四、验证数据 | 项目 | 结果 | |---|---| | 写作端到端(真实 WP 里真发文章、真查库) | **71 通过 / 0 失败** | | 后台设置页渲染(伪装管理员走框架真实渲染入口) | **21 通过 / 0 失败** | | 密钥保存路径(走真实 `of_sanitize_password`) | **9 通过 / 0 失败** | | 反证:端到端 W1~W16 | **16 组全部符合预期** | | 反证:后台 A1~A6 | **6 组全部符合预期** | | 反证:发布包注入 R1~R14 | **14 组全部让失败数从 0 增加** | | 测试自身不污染 | 跑完标签库仍是 **703**、已发布文章仍是 **967** | | 线上主题目录 | 仍是 3.2.56,**未被覆盖**(红线) | 反证的意思是:**故意把产品改坏,确认判据会红**。比如把钩子从 `transition_post_status` 改成 `save_post`、把 slug 改成一律覆盖、把 `append` 改成 `false`、把 `list($exist_hits, $new_cands)` 退回 `list($suggest, $_)`——每一组都精确红在对那条判据上。 这一步不是形式主义:本轮我自己就写出过**两条永远绿的假判据**,都是靠反证揪出来的(详见下方"踩坑")。 --- ### 五、过程中值得记下来的坑 1. **`GROUPS` 是 bash 只读内建数组**(存用户组 ID)。我拿它当计数器,赋值被静默忽略,最后输出「1008 组」——恰好是本机主组 ID。 2. **`list($a, $_)` 丢返回值**:语法完全合法,静态检查全过,只有端到端能抓。 3. **`count=0` 不是"可删除"的判据**:反查 `term_relationships` 发现有的标签 `count=0` 但仍有 1 条关系,只是计数器漂移。按 `count=0` 清孤儿会误删真实标签。 4. **判据撞自己写的注释**:源码注释里写着「74.9% 不可复现」「不许覆盖」这些反面教材原文,判据不先剥注释就会在**正确代码**上报红。 5. **`array(` 是 `in_array(` 的子串**:判"有没有内联字数组"时写 `strpos($ln,'array(')`,在 `in_array($tail, yang_writing_seps(), true)` 这种正确代码上也命中,假红。 6. **PHP 单引号里 `\$` 不转义**:`'#in_array\(\s*\\\$tail#'` 得到的是「字面反斜杠 + 行尾锚点」,永远匹配不到 `$tail`。教训不是记反斜杠个数,而是别在多层转义里手写正则。 7. **设置页文案里的 74.9% 已清除**:换成本轮实测的 49.2%(Top8 命中人工标签)/ 59.0%(只算字面出现在正文里的人工标签),并加了守卫防复发。工单正文里那段历史记录保留(那是过程记录,不是给用户看的界面文案)。 --- ### 六、需要你在后台确认的 1. 后台「外观 → 主题设置」,左侧应该出现**「写作增强」**独立标签页。 2. 勾上「自动生成英文别名」,下面那组「服务商 / 百度 APPID / 百度密钥 / 有道 …」才显示;不勾时是藏着的。 3. 勾上「自动生成标签」,「每篇最多新增几个」才显示,默认 3。 4. 填一个密钥保存,刷新页面——密钥框应该是**空的**,但下面有一行「当前已保存:`****abcd`」。 5. 真发一篇测试文章,看四项有没有按预期生成(或者按预期**不动**)。 确认没问题我再关闭工单。有任何一项不符合,直接在这个单里说。
Author
Owner

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

交付版本:3.2.57(写作增强四个引擎 + 设置页,零插件依赖)

用户已验收,关闭本单。

按用户确认收尾,交付见上条发布评论。 交付版本:3.2.57(写作增强四个引擎 + 设置页,零插件依赖) 用户已验收,关闭本单。
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#25