Files
laoyang 51c16209a0 4.1.1 修设置页链接无法新窗口打开(target 被 wp_kses 剥掉) #33
用户反馈:设置页「数据导出服务」那个链接点下去在本页跳走了,
把正在改的设置页顶掉。

根因不是链接写错 —— 源码里 target="_blank" 一直在。是框架渲染 desc 时
走 wp_kses($explain_value, $allowedtags)
(inc/options-framework/includes/class-options-interface.php:210/315),
而 WordPress 全局 $allowedtags['a'] 只有 href + title,target 和 rel
被静默剥掉。实测:

  输入 <a href="..." target="_blank" rel="noopener">数据导出服务</a>
  输出 <a href="...">数据导出服务</a>          ← target/rel 消失

修法:在 kratos_options() 开头给 $allowedtags['a'] 补上 target / rel。
只放宽这两个属性,不放宽别的。时机是对的 —— _optionsframework_options()
在第 47 行就调 kratos_options(),早于所有 wp_kses 调用。

影响的不止这一个链接:robots.txt、设置-阅读、两个翻译 API 的链接
全都是同样情况,一并修好(共 5 处)。

线上真实环境验证:
  ALLOWED_BEFORE=href,title
  ALLOWED_AFTER=href,title,target,rel
  HAS_TARGET=YES

版本号 4.1.0 → 4.1.1:同一版本号重发 PUC 认不出(它用 version_compare
判严格大于),已装 4.1.0 的站点永远拿不到这次修复。readme 按用户确认
**并入现有 V4.1.0 节**,不新增一节(节数仍 57)。

补的判据(都做了注入反证):
- _mkzip410.py 11c 段 5 条:白名单补了 target/rel、补的位置在
  kratos_options() 内、「数据导出服务」链接带 target、没拿 window.open 绕过
- _mkzip410_falsify.py 17/18 组:删白名单必须变红,18/18 抓到
- _readme410.py 改造成替换模式 + 反证(不换首节必须红,42/0)

顺带修两个已过期的地方:
- _mkzip410_falsify.py 的「版本号没改」注入锚点写死 "Version: 4.1.0",
  升 4.1.1 后锚点未命中、反证报"没抓到"(其实是注入脚本自己过期)
  ⇒ 改成动态读当前版本号
- _publish410.sh 的 readme 首节判据要求 == 本版号,但 4.1.1 并入
  V4.1.0 节 ⇒ 改成「首节是 4.1.x」+「本版修复说明真在里面」两头都验

线上已发布:md5 bd12b67f2c4ca5bec5053a02e49f2fe6
PUC 实测 HAS_UPDATE=YES(已装 4.1.0 → 新 4.1.1)
2026-10-28 17:45:19 +08:00

1698 lines
85 KiB
PHP
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 主题选项
*
* ── 分类原则(3.2.15 重排)────────────────────────────────────
* 原来九个头部分类里有几处明显错位:
* · 哀悼功能挂在「首页配置」下面 —— 它影响的是整站(首页、文章页、说明页),
* 跟首页没有关系;
* · 国旗、备案、版权拆在「页脚」但「404 图片」在全站,都是零碎的;
* · 「收录配置」把 robots.txt 一起塞进来,其实那是另一件事。
*
* 现在的顺序按「访客看到的顺序」排:整站基础 → 顶部 → 首页 → 文章 →
* 站长 → 页脚 → 专项功能 → 收录 → 邮件 → 自定义代码。
* 每一项只出现在它真正生效的地方,不再有"在首页配置里改整站行为"这种事。
*
* @author Yongquan Yang <yangyq.net>
* @license MIT License
* @version 2020.09.21
*/
function getrobots()
{
$site_url = parse_url(site_url());
$path = (!empty($site_url['path'])) ? $site_url['path'] : '';
$robots = "User-agent: *\n\n";
$robots .= "Disallow: $path/wp-admin/\n";
$robots .= "Disallow: $path/wp-includes/\n";
$robots .= "Disallow: $path/wp-content/plugins/\n";
$robots .= "Disallow: $path/wp-content/themes/\n";
return $robots;
}
/**
* 图片字段的默认值(后台预填用)。
*
* ── 为什么不直接调 yang_image_default() ──────────────────────
* 本文件由 optionsframework_init() 在 init 钩子上 require,
* 而 yang_image_default() 来自 inc/theme-option-defaults.php。
* 正常时序下后者先加载,但**加载顺序被打乱时**(子主题、外部脚本
* 直接 include 本文件、插件在更早的钩子里触发本函数),
* 直接调用就会 Fatal —— 后台整个白屏,比"少一个预填值"严重得多。
*
* 这里先用 function_exists() 探一下,缺了就返回空串:
* 后台退化成没有预填默认值,但页面能打开、能保存、能干活。
* 降级必须降到"还能用",不能降到"打不开"。
*/
if (!function_exists('yang_options_default_img')) {
function yang_options_default_img($id)
{
return function_exists('yang_image_default')
? yang_image_default($id)
: '';
}
}
/**
* 哀悼相关辅助函数的安全访问
*
* 同样的道理:yang_mourning_presets() / yang_mourning_status_text() /
* yang_mourning_extra_raw() 都在 inc/theme-mourning.php 里,由
* functions.php 按顺序 require。只要有人(子主题、迁移脚本、别的插件)
* 在 theme-mourning.php 之前触发了 kratos_options(),这里就会 Fatal,
* 整个后台白屏,且报错指向本文件,很难联想到是加载顺序问题。
*
* 降级策略:缺函数时按"没有纪念日 / 没有状态说明"降级 ——
* 后台那几个控件会变空,但页面能打开、能保存、能继续用。
*/
if (!function_exists('yang_opt_mourning_presets')) {
function yang_opt_mourning_presets()
{
return function_exists('yang_mourning_presets')
? yang_mourning_presets()
: array();
}
}
if (!function_exists('yang_opt_mourning_status')) {
function yang_opt_mourning_status()
{
return function_exists('yang_mourning_status_text')
? yang_mourning_status_text()
: '';
}
}
if (!function_exists('yang_opt_mourning_extra_raw')) {
function yang_opt_mourning_extra_raw()
{
return function_exists('yang_mourning_extra_raw')
? yang_mourning_extra_raw()
: '';
}
}
/**
* 今年清明的月日,给后台说明文字用(如「4 月 5 日」)
*
* 换算不出来(超出年份表范围)时返回一句明确的说明,
* **不要**退化成「4 月 4 日」—— 那正是用户要求删掉的近似值。
*/
if (!function_exists('yang_opt_qingming_label')) {
function yang_opt_qingming_label()
{
if (!function_exists('yang_qingming_md')) {
return __('换算不可用', 'kratos');
}
$md = yang_qingming_md();
if ($md === null) {
return sprintf(
__('今年(%s 年)超出可换算范围,清明不会自动生效', 'kratos'),
current_time('Y')
);
}
list($mo, $da) = explode('-', $md);
return sprintf(__('%d 月 %d 日', 'kratos'), (int) $mo, (int) $da);
}
}
/**
* 自定义 CSS 的默认值(来自 inc/theme-custom-code.php)
*
* 同样是加载顺序问题:这个函数在 theme-custom-code.php 里,
* 而 kratos_options() 可能被更早触发。缺了就返回空串,
* 后台的「自定义 CSS」框是空的,但能打开、能填、能保存。
*
* 3.2.29 起该默认值恒为空:原来的那批规则(段落缩进、h2 蓝条、列表缩进)
* 已并入主题公共样式 assets/css/site.css,配置项只留给站长做临时覆盖。
*/
if (!function_exists('yang_opt_default_custom_css')) {
function yang_opt_default_custom_css()
{
return function_exists('yang_default_custom_css')
? yang_default_custom_css()
: '';
}
}
function kratos_options()
{
/* 🔴 让 desc 里的链接能新窗口打开。
框架渲染 desc 时走 wp_kses($explain_value, $allowedtags)
(inc/options-framework/includes/class-options-interface.php:210/315),
而 WordPress 的全局 $allowedtags['a'] 只有 href + title ——
**target / rel 会被静默剥掉**,代码里写着 target="_blank" 也不生效。
实测:wp_kses 输出里 target 消失,页面点链接在本页跳走。
在构建选项数组时补齐白名单(只补这两个属性,不放宽别的)。 */
global $allowedtags;
if (!isset($allowedtags['a']) || !is_array($allowedtags['a'])) {
$allowedtags['a'] = array();
}
$allowedtags['a']['target'] = true;
$allowedtags['a']['rel'] = true;
$sitename = get_bloginfo('name');
$imagepath = ASSET_PATH . '/assets/img/options/';
$seorobots = '<a href="' . home_url() . '/robots.txt" target="_blank">robots.txt</a>';
$seoreading = '<a href="' . admin_url('options-reading.php') . '" target="_blank">' . __('设置-阅读-对搜索引擎的可见性', 'kratos') . '</a>';
$top_array = array(
'banner' => __( '图片导航', 'kratos' ),
'color' => __( '颜色导航', 'kratos' ),
);
$options = array();
/* ══════════════════════════════════════════════════════════
* 一、全站配置 —— 影响每一个页面的东西
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('全站配置', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('图片 Logo', 'kratos'),
'desc' => __('不选择图片则显示文字标题', 'kratos'),
'id' => 'g_logo',
'type' => 'upload',
/* 有意不设 std:这里空着时显示站点名称文字,比塞一张通用占位
logo 好看。其它图片字段见 inc/theme-option-defaults.php。 */
);
$options[] = array(
'name' => __('Favicon 图片', 'kratos'),
'desc' => __('浏览器收藏夹和地址栏中显示的图标。<b>留空则自动使用主题自带的默认图标</b>,不会出现空白。', 'kratos'),
'id' => 'g_icon',
/* 有意不写 std。
*
* std 里存的是**绝对 URL**,一旦用户保存表单就会被写进数据库。
* 数据库里躺着一个写死的主题目录地址是隐患:主题目录改名、
* 换 CDN、或站点迁移域名之后,那条记录就指向不存在的文件,
* 页面上是破图,而用户在后台看到的仍是"已设置",很难自己发现。
*
* 所以默认值一律由运行时提供(yang_option_image()),库里保持空。
* 这样默认图地址永远由当前请求现算,改目录也不用改数据。
*/
'type' => 'upload',
);
$options[] = array(
'name' => __('背景颜色', 'kratos'),
'desc' => __('全站页面的背景颜色,需填写十六进制颜色码', 'kratos'),
'id' => 'g_background',
'std' => '#f5f5f5',
'type' => 'color',
);
$options[] = array(
'name' => __('Chrome 导航栏颜色', 'kratos'),
'desc' => __('Chrome 移动端浏览器地址栏的颜色', 'kratos'),
'id' => 'g_chrome',
'std' => '#282a2c',
'type' => 'color',
);
$options[] = array(
'name' => __('404 页面图片', 'kratos'),
'desc' => __('访客打开不存在的地址时显示的配图', 'kratos'),
'id' => 'g_404',
'std' => yang_options_default_img('g_404'),
'type' => 'upload',
);
$options[] = array(
'name' => __('前台管理员导航', 'kratos'),
'desc' => __('登录管理员后,页面顶部显示 WordPress 管理工具条', 'kratos'),
'std' => '1',
'id' => 'g_adminbar',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('CSS 动画库', 'kratos'),
'desc' => __('开启 animate.css 效果。会增加一个约 60KB 的样式文件,不需要就别开。', 'kratos'),
'std' => '0',
'id' => 'g_animate',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('Gravatar 加速', 'kratos'),
'desc' => __('把评论头像的请求转发到国内可访问的镜像,改善加载速度。若开启了下面的「自动头像」,未上传头像的访客不再需要这张图。', 'kratos'),
'std' => '0',
'id' => 'g_gravatar',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('禁止生成缩略图', 'kratos'),
'desc' => __('上传图片时不再额外生成多种尺寸的副本,节省磁盘空间', 'kratos'),
'id' => 'g_removeimgsize',
'std' => '0',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('Gutenberg 编辑器', 'kratos'),
'desc' => __('开启 WordPress 区块编辑器。关闭则使用经典编辑器。', 'kratos'),
'std' => '0',
'id' => 'g_gutenberg',
'type' => 'checkbox',
);
/* ══════════════════════════════════════════════════════════
* 二、自动头像 —— 独立一块
*
* 3.2.36 里这两项原本塞在「全站配置」内部(Gravatar 加速之后)。
* 上线后发现**根本找不到**:它排在第 9 位,纵坐标 1058px,而
* 浏览器视口才 1000px 高 —— 前 8 项(其中「404 页面图片」一项
* 就占 301px)把它顶到首屏之外,不往下滚看不见。左侧标签栏也没
* 有「头像」字样,光扫标签栏同样摸不着。
*
* 所以提成独立标签页:标签栏直接能看见,进去第一项就是开关。
* 这也更贴合原始需求「最上面的一个复选框,勾完之后出现下面这些
* 列表」——独立成页后,这个「上面/下面」的关系才真的成立。
*
* ── 为什么开关与风格拆成两个字段 ──────────────────────────
* 若用一个字段直接存风格名(留空=关闭),用户「主动关闭」和
* 「还没选过风格」在库里会变成同一种状态(都空),以后想恢复
* 默认值时分不清该填什么。两个字段各管一件事更清楚。
*
* ⚠️ std 只在后台预填和「恢复默认」时起作用,运行时 kratos_option()
* 完全不看它。所以运行时默认值必须同步给一份:
* 开关默认关('0'),风格默认 lorelei(见 yang_avatar_style())。
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('自动头像', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('启用自动头像', 'kratos'),
'desc' => __('访客没注册、也没上传头像时,按他的邮箱生成一张头像。<b>全程本地生成,不请求任何外部服务</b>,同一个邮箱永远得到同一张。若访客已经上传过真实头像,不受影响。', 'kratos'),
'std' => '0',
'id' => 'a_auto_enable',
'type' => 'checkbox',
);
/* ⚠️ 这个开关必须排在「头像风格」**之前**。
风格列表是 18 张带缩略图的卡片,实测高约 2100px;
排在它后面的控件实际落到 y≈2327px,而设置页视口约 1000px
—— 用户不滚三屏根本看不到。3.2.36 就是栽在「塞在长列表末尾」
这个坑里,别再犯。 */
$options[] = array(
'name' => __('注册用户保留原头像', 'kratos'),
'desc' => __('<b>建议开启。</b>开启后只给「邮箱在用户表里查不到」的匿名访客生成头像;本站注册用户一律用他自己的头像,不会被替换。<br>关掉则所有 Gravatar 头像都会被换掉 —— 站内作者、管理员也会变成卡通脸。<br><b>说明:</b>注册用户若自己也没上传过头像,Gravatar 上就是一张默认灰人像。要分辨这种情况必须向 gravatar.com 发请求,而本功能坚持不访问任何外部服务,所以这类用户会保持灰人像,不会自动生成。', 'kratos'),
'std' => '1',
'id' => 'a_auto_keep_member',
'type' => 'checkbox',
'requires' => 'a_auto_enable',
);
$options[] = array(
'name' => __('头像风格', 'kratos'),
'desc' => __('勾选上面的「启用自动头像」后,这里才会出现风格列表。所有风格都可自由使用与商用,且不要求署名。点一下即时预览,保存后生效。', 'kratos'),
'std' => 'lorelei',
'id' => 'a_auto_style',
'type' => 'avatar_style',
/* 只有开关打开时才显示这个控件;关着的时候看不到它,
避免用户在关闭状态下改了风格却不知道为什么没生效。 */
'requires' => 'a_auto_enable',
);
/* ══════════════════════════════════════════════════════════
* 三、顶部导航 —— 页头那一条
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('顶部导航', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __( '顶部样式', 'kratos' ),
'desc' => __('请选择顶部样式(图片导航或颜色导航)', 'kratos'),
'id' => 'top_select',
'std' => 'banner',
'type' => 'select',
'options' => $top_array
);
$options[] = array(
'name' => __('图片导航背景', 'kratos'),
'desc' => __('顶部样式选「图片导航」时使用。<b>留空则自动使用主题自带的默认图片</b>。', 'kratos'),
'id' => 'top_img',
'std' => yang_options_default_img('top_img'),
'class' => 'hidden',
'type' => 'upload',
);
$options[] = array(
'name' => __('颜色导航背景', 'kratos'),
'desc' => __('顶部样式选「颜色导航」时使用', 'kratos'),
'id' => 'top_color',
'std' => '#24292e',
'class' => 'hidden',
'type' => 'color',
);
$options[] = array(
'name' => __('副标题', 'kratos'),
'desc' => __('显示在站点标题旁边的一句短话', 'kratos'),
'id' => 'top_title',
'std' => 'Kratos',
'class' => 'hidden',
'type' => 'text',
);
$options[] = array(
'name' => __('标题描述', 'kratos'),
'std' => __('一款专注于用户阅读体验的响应式博客主题', 'kratos'),
'id' => 'top_describe',
'class' => 'hidden',
'type' => 'text',
);
/* ══════════════════════════════════════════════════════════
* 四、首页配置 —— 只管首页列表长什么样
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('首页配置', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('特色图片', 'kratos'),
'desc' => __('在首页文章列表的每篇标题上方显示一张配图', 'kratos'),
'std' => '1',
'id' => 'g_thumbnail',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('默认特色图', 'kratos'),
'desc' => __('文章既没有正文图片、也没有设置特色图时,用这张顶上。<b>留空则自动使用主题自带的默认图片</b>。', 'kratos'),
'id' => 'g_postthumbnail',
'class' => 'hidden',
'std' => yang_options_default_img('g_postthumbnail'),
'type' => 'upload',
);
$options[] = array(
'name' => __('无内容图片', 'kratos'),
'desc' => __('搜索不到结果、或分类下还没有文章时显示的插画。<b>留空则自动使用主题自带的默认图片</b>。', 'kratos'),
'id' => 'g_nothing',
'std' => yang_options_default_img('g_nothing'),
'type' => 'upload',
);
/* ══════════════════════════════════════════════════════════
* 五、文章配置 —— 打开单篇文章才看得到的东西
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('文章配置', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('页面布局', 'kratos'),
'desc' => __('文章页面是否显示侧边栏。仅在文章详情页生效。', 'kratos'),
'id' => "g_article_widgets",
'std' => "two_side",
'type' => "images",
'options' => array(
'one_side' => $imagepath . 'col-12.png',
'two_side' => $imagepath . 'col-8.png')
);
$options[] = array(
'name' => __('文章打赏', 'kratos'),
'desc' => __('在文章底部显示打赏按钮,点开是收款二维码', 'kratos'),
'id' => 'g_donate',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('支付宝收款码', 'kratos'),
'desc' => __('<b>留空则自动使用主题自带的默认图片</b>。', 'kratos'),
'id' => 'g_donate_alipay',
'std' => yang_options_default_img('g_donate_alipay'),
'type' => 'upload',
'class' => 'hidden',
);
$options[] = array(
'name' => __('微信收款码', 'kratos'),
'desc' => __('<b>留空则自动使用主题自带的默认图片</b>。', 'kratos'),
'id' => 'g_donate_wechat',
'std' => yang_options_default_img('g_donate_wechat'),
'type' => 'upload',
'class' => 'hidden',
);
/* ══════════════════════════════════════════════════════════
* 六、自动目录 —— 把长文切成能跳的章节
*
* 与「文章配置」分开成独立标签页,是刻意的:目录的选项有六项,
* 塞进文章配置里会把「页面布局 / 打赏」这些一眼要看到的东西挤下去。
*
* ⚠️ 这里的 std 必须与 inc/theme-toc.php 的 yang_toc_defaults() 一致。
* 前者只服务「后台预填」和「恢复默认」,运行时一律走
* kratos_option($id, 默认值) —— 两处不一致会出现
* 「页面照 A 渲染、后台框里显示 B」的错位,而且极难发现。
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('自动目录', 'kratos'),
'type' => 'heading',
);
/* ── 分组标题:正文里的那一份 ──────────────────────────────
*
* 用 info 类型而不是 heading:heading 会另起一个标签页
* (framework 里 `case "heading"` 会 $counter++ 并新建
* #options-group-N),info 只渲染成一行 <h4> + 说明文字,
* 既不进标签页菜单,也不生成表单字段(见
* class-options-interface.php 的 :24 / :59 / :91)。
*
* 加这两条小标题的原因:浮动目录和正文目录是两件互不相干的事,
* 但原来的选项排成一长列,「添加方式」「自动插入位置」跟在
* 「浮动显示」后面,看着像是两种目录共用的设置,容易让人以为
* 浮动目录也分手动/自动。 */
$options[] = array(
'name' => __('正文里的目录', 'kratos'),
'desc' => __('下面这几项只影响<b>正文流里</b>那一份目录 —— 它跟着文章一起排版,会占正文位置。', 'kratos'),
'id' => 'toc_group_inline',
'type' => 'info',
);
$options[] = array(
'name' => __('在正文中显示', 'kratos'),
'desc' => __('勾选后,在文章正文里插一份目录。<br>它和下面的「浮动显示」<b>互不影响</b>:两个都勾就同时出现两份(正文一份、边上浮一份),只勾一个就只出现一种。', 'kratos'),
'id' => 'toc_inline',
'std' => '1',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('正文中的对齐', 'kratos'),
'desc' => __('正文里那份目录在<b>文章内容区域内</b>靠左还是靠右。这里不是「浮到页面边上」—— 目录仍然在正文流里,上下文的文字会绕开它,只是整体贴向一侧。<br>只在勾选「在正文中显示」时才有意义。', 'kratos'),
'id' => 'toc_inline_align',
'std' => 'left',
'type' => 'select',
'options' => array(
'left' => __('靠左', 'kratos'),
'right' => __('靠右', 'kratos'),
),
);
$options[] = array(
'name' => __('添加方式', 'kratos'),
'desc' => __('<b>手动</b>:不自动加,只有正文里手写了 <b>[TOC]</b> 的地方才显示(老文章的行为,升级后不会突然多出目录)。<br><b>自动</b>:由主题自己在「自动插入位置」处加一份,不用改任何文章。<br>本项<b>只管正文里的那一份</b>;浮动目录不受它影响(浮动目录没有地方写 <b>[TOC]</b>,勾了就一直在)。', 'kratos'),
'id' => 'toc_mode',
'std' => 'manual',
'type' => 'select',
'options' => array(
'manual' => __('手动(只在写 [TOC] 的地方显示)', 'kratos'),
'auto' => __('自动添加', 'kratos'),
),
);
$options[] = array(
'name' => __('自动插入位置', 'kratos'),
'desc' => __('只在「添加方式 = 自动」且勾选了「在正文中显示」时起作用;手动方式下插入点由你自己写的 <b>[TOC]</b> 决定。<br><b>第一个标题之前</b>取正文里最先出现的 h1 或 h2 —— 文章大标题在正文之外,不会被算进去;一个 h1/h2 都没有时退回插在文章开头。', 'kratos'),
'id' => 'toc_anchor',
'std' => 'first',
'type' => 'select',
'options' => array(
'start' => __('文章开头', 'kratos'),
'first' => __('第一个标题之前(H1 / H2)', 'kratos'),
'end' => __('文章结尾', 'kratos'),
),
);
/* 3.2.59:从「共用」分组搬到这里。它只作用于正文那份,
摆在「正文里的目录」分组下才不会让人以为它也管浮层。 */
$options[] = array(
'name' => __('正文目录的默认状态', 'kratos'),
'desc' => __('<b>默认显示</b>:进来就能看到全部条目。<b>默认隐藏</b>:先只露出标题栏,读者自己点开。<b>较长或标题很多的文章建议默认隐藏</b>,否则目录会占掉一屏。<br>这一项<b>只管正文里那一份</b>;浮动目录的默认状态在下面「浮动目录」分组里单独设,<b>两者互不影响</b>。<br>注:这里只管「默认长什么样」,读者点开后仍然可以正常使用;正文里手写的 <b>[TOC]</b> 还能用 <b>open="0"</b> 单独指定,且只影响正文这份。', 'kratos'),
'id' => 'toc_inline_default',
'std' => 'show',
'type' => 'select',
'options' => array(
'show' => __('默认显示', 'kratos'),
'hide' => __('默认隐藏', 'kratos'),
),
);
$options[] = array(
'name' => __('浮动目录', 'kratos'),
'desc' => __('下面几项只影响<b>浮在页面边上</b>那一份 —— 它不占正文位置,和正文里的目录互不相干。', 'kratos'),
'id' => 'toc_group_float',
'type' => 'info',
);
$options[] = array(
'name' => __('浮动显示', 'kratos'),
'desc' => __('勾选后,目录变成贴在页面边缘的浮动面板,点边上的小标签滑出,<b>不占正文位置</b>。窄屏(手机)上是从边缘滑出的抽屉,不会挤占正文。<br>与「在正文中显示」互相独立,可以同时勾选。<b>它没有手动/自动之分</b>:勾了就一直在,不勾就不出现。', 'kratos'),
'id' => 'toc_float',
'std' => '',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('浮动方向', 'kratos'),
'desc' => __('浮动面板贴页面左边还是右边。只在勾选「浮动显示」时才有意义。', 'kratos'),
'id' => 'toc_float_side',
'std' => 'left',
'type' => 'select',
'options' => array(
'left' => __('页面左侧', 'kratos'),
'right' => __('页面右侧', 'kratos'),
),
);
$options[] = array(
'name' => __('浮层目录的默认状态', 'kratos'),
'desc' => __('<b>默认显示</b>:一进文章,面板就已经滑出展开。<b>默认隐藏</b>:只留边上那片竖向小标签,读者点一下才滑出 —— <b>长文建议用这个</b>,不然面板会一直挡着正文。<br>这一项<b>只管浮动那一份</b>,和正文目录的默认状态<b>各设各的</b>:想让正文展开、浮层收起,就这里选「默认隐藏」、上面选「默认显示」。<br>注:展开态直接写在页面 HTML 的 class 上,<b>浏览器禁用 JS 也能看到正确的默认状态</b>。', 'kratos'),
'id' => 'toc_float_default',
'std' => 'show',
'type' => 'select',
'options' => array(
'show' => __('默认显示(面板展开)', 'kratos'),
'hide' => __('默认隐藏(只留侧边标签)', 'kratos'),
),
);
$options[] = array(
'name' => __('两份目录共用的设置', 'kratos'),
'desc' => __('下面几项对正文里和浮动的那一份<b>同时生效</b> —— 它们的标题文字、收录哪些级别、要不要加序号,两份保持一致。<br><b>「默认状态」不在这里了</b>:正文那份和浮层那份现在各自单独设(见上面两个分组),因为「正文展开、浮层收起」是很常见的搭配。', 'kratos'),
'id' => 'toc_group_common',
'type' => 'info',
);
$options[] = array(
'name' => __('目录标题', 'kratos'),
'desc' => __('显示在目录顶部的那行字。留空则用「目录」。', 'kratos'),
'id' => 'toc_title',
'std' => __('目录', 'kratos'),
'type' => 'text',
);
$options[] = array(
'name' => __('收录标题级别', 'kratos'),
'desc' => __('勾选要进目录的标题级别。<b>正文里的 h1 是作者自己写的章节标题</b>(文章大标题在正文之外,不会被收进来),所以要用 h1 分章的文章也建议勾上 h1。<br>h4 以下通常是细节,勾上会让目录变得很长。', 'kratos'),
'id' => 'toc_levels',
'std' => array('1' => 1, '2' => 1, '3' => 1, '4' => 0),
'type' => 'multicheck',
'options' => array(
'1' => array('name' => 'H1', 'note' => __('一级标题', 'kratos')),
'2' => array('name' => 'H2', 'note' => __('二级标题', 'kratos')),
'3' => array('name' => 'H3', 'note' => __('三级标题', 'kratos')),
'4' => array('name' => 'H4', 'note' => __('四级标题', 'kratos')),
),
);
$options[] = array(
'name' => __('显示序号', 'kratos'),
'desc' => __('在条目前面加 1 / 1.1 / 1.1.1 这样的序号。', 'kratos'),
'id' => 'toc_numbering',
'std' => '0',
'type' => 'checkbox',
);
/* ══════════════════════════════════════════════════════════
* 七、站长信息 —— 文章页页脚那张名片
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('站长信息', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('个人头像', 'kratos'),
'desc' => __('显示在文章底部作者名片里。<b>留空则自动使用主题自带的默认头像</b>。', 'kratos'),
'id' => 'a_gravatar',
'std' => yang_options_default_img('a_gravatar'),
'type' => 'upload',
);
$options[] = array(
'name' => __('个人昵称', 'kratos'),
'id' => 'a_nickname',
'std' => 'Kratos',
'type' => 'text',
);
$options[] = array(
'name' => __('个人简介', 'kratos'),
'std' => __('保持饥渴的专注,追求最佳的品质', 'kratos'),
'id' => 'a_about',
'type' => 'textarea',
);
/* ══════════════════════════════════════════════════════════
* 八、页脚配置 —— 页面最底下那几行
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('页脚配置', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('版权信息', 'kratos'),
'desc' => __('页脚显示的那行版权声明。<b>留空则自动用「COPYRIGHT © 当前年份 站点名称 版权所有」</b>。', 'kratos'),
'id' => 's_copyright',
/* 有意不写 std:std 里写死的年份过一年就变成旧年份,
而且一旦保存就永久留在库里。改由 footer.php 每次现算。 */
'type' => 'textarea',
);
$options[] = array(
'name' => __('是否显示国旗', 'kratos'),
'desc' => __('在页脚显示一面国旗', 'kratos'),
'std' => '0',
'id' => 'g_flag',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('国旗图片', 'kratos'),
'desc' => __('<b>留空则自动使用主题自带的默认国旗</b>。', 'kratos'),
'id' => 'g_flag_url',
/* 不写 std —— 理由同 g_icon:默认值由运行时给,库里不存绝对地址。 */
'type' => 'upload',
);
$options[] = array(
'name' => __('工信部备案信息', 'kratos'),
'id' => 's_icp',
'placeholder' => __('例如:京ICP证xxxxxx号', 'kratos'),
'type' => 'text',
);
$options[] = array(
'name' => __('公安网备案信息', 'kratos'),
'id' => 's_gov',
'placeholder' => __('例如:京公网安备 xxxxxxxxxxxx号', 'kratos'),
'type' => 'text',
);
$options[] = array(
'name' => __('公安网备案链接', 'kratos'),
'id' => 's_gov_link',
'placeholder' => __('例如:http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=xxxxx', 'kratos'),
'type' => 'text',
);
/* ══════════════════════════════════════════════════════════
* 九、哀悼模式 —— 整站级功能,与首页无关
*
* 这一组原来错放在「首页配置」下面。它影响的是整站配色
* (首页、文章页、说明页都变黑白),放首页下面会让人以为
* 只改首页,而且想找它的人第一反应不会去点「首页配置」。
* 单独成组,名字直说它是什么。
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('哀悼模式', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('手动常开', 'kratos'),
'desc' => __('勾上就一直黑白,与日期无关。这是老的开关,日常建议用下面的「按纪念日自动哀悼」。', 'kratos'),
'id' => 'g_rip',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('按纪念日自动哀悼', 'kratos'),
'desc' => __('勾选后,只有在下表勾选的那些日期才会自动黑白,其余日子正常显示。日期按站点时区判断,只比对月和日,每年循环。', 'kratos'),
'id' => 'g_rip_auto',
'type' => 'checkbox',
);
// 把内置清单转成 multicheck 需要的格式
$rip_legal = array();
$rip_custom = array();
foreach (yang_opt_mourning_presets() as $key => $info) {
/* 键有两种:静态日期(`09-03`)和动态日期(`qingming`)。
动态日期的键是内部标识,不能直接摆在用户面前 ——
所以看 key_label,没有就回退到键本身(静态日期就是 MM-DD,
显示出来正好是用户认得的日期)。 */
$label = isset($info['key_label']) ? $info['key_label'] : $key;
$entry = array(
'name' => $label . ' ' . $info['name'],
'note' => $info['note'],
);
if (isset($info['group']) && $info['group'] === 'legal') {
$rip_legal[$key] = $entry;
} else {
$rip_custom[$key] = $entry;
}
}
$rip_default = array();
foreach (yang_opt_mourning_presets() as $key => $info) {
$rip_default[$key] = !empty($info['default']) ? 1 : 0;
}
$options[] = array(
'name' => __('纪念日清单', 'kratos'),
'desc' => sprintf(
__('勾选需要自动黑白的日期。<b>默认一个都不勾</b>,请按自己的意愿挑选。前三个是法定纪念日,后面几个是民间通行做法。日期按站点时区判断,只比对月和日,每年循环。<br><b>清明节</b>是节气,日期每年浮动(4 月 4~6 日之间),已脱离固定日期、按天文历逐年换算 —— 今年是 <b>%s</b>。', 'kratos'),
esc_html(yang_opt_qingming_label())
),
'id' => 'g_rip_days',
'std' => $rip_default,
'type' => 'multicheck',
'options' => array_merge($rip_legal, $rip_custom),
);
$options[] = array(
'name' => __('附加日期', 'kratos'),
'desc' => sprintf(
__('清单里没有的日期填这里。<br><b>%1$s</b> 表示每年循环(如 05-12);<b>%2$s</b> 表示只在当年生效(如 2020-04-04,用于历次全国哀悼日这类一次性事件)。<br>多个日期用空格或逗号分隔。当前填的是 <b>%3$s</b>。', 'kratos'),
'MM-DD',
'YYYY-MM-DD',
esc_html(yang_opt_mourning_extra_raw() === '' ? __('(空)', 'kratos') : yang_opt_mourning_extra_raw())
),
'id' => 'g_rip_extra',
'type' => 'text',
);
$options[] = array(
'name' => __('说明页路径', 'kratos'),
'desc' => sprintf(
/* 注意:desc 会过 wp_kses($allowedtags),那是 WordPress 的默认白名单,
只放行 a/abbr/b/br/em/i/strong 等,**不放行 <code>** ——
写 <code> 会被静默剥掉,页面上只剩裸文字。所以这里用 <b> 强调。 */
__('变黑白时,页面顶部会出现一条提示,点进去是一个说明页面(彩色),讲清楚为什么变黑白、哪几天会变。<br>这里可以改那个页面的路径。留空默认 <b>memory</b>,即 %s<br><b>注意:</b>不要和已有文章或页面的路径重名。', 'kratos'),
esc_url(home_url('/memory/'))
),
'id' => 'g_rip_page_slug',
'std' => 'memory',
'type' => 'text',
);
$options[] = array(
'desc' => yang_opt_mourning_status(),
'type' => 'info',
);
/* ══════════════════════════════════════════════════════════
* 十、收录与统计 —— 给搜索引擎看的
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('收录与统计', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('分享图片', 'kratos'),
'desc' => __('搜索引擎或者社交工具分享首页时抓取的图片。<b>留空则自动使用主题自带的默认图片</b>。', 'kratos'),
'id' => 'seo_shareimg',
'std' => yang_options_default_img('seo_shareimg'),
'type' => 'upload',
);
$options[] = array(
'name' => __('关键词', 'kratos'),
'desc' => __('每个关键词之间需要用「英文逗号」分割', 'kratos'),
'id' => 'seo_keywords',
'type' => 'text',
);
$options[] = array(
'name' => __('站点描述', 'kratos'),
'id' => 'seo_description',
'type' => 'textarea',
);
$options[] = array(
'name' => __('统计代码', 'kratos'),
'desc' => __('填在页脚的统计脚本(如站点访问量统计)。注意:输入 HTML/JS 代码时请注意辨别代码安全!', 'kratos'),
'id' => 'seo_statistical',
'type' => 'textarea',
);
/* ══════════════════════════════════════════════════════════
* 访问统计面板(百度统计 API)
*
* 这一整段都是「附加功能」:不填任何东西时,本页其余部分
* 完全不受影响。面板本身由 inc/theme-stat.php 负责渲染,
* 这里只负责把控件摆出来。
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('启用统计埋点', 'kratos'),
'desc' => __('关掉后前台不再输出上面那段「统计代码」,但代码本身<b>不会被删除</b> —— '
. '想恢复时勾回来即可,不用再去百度后台找那串 hash。', 'kratos'),
'id' => 'stat_track_enable',
'std' => '1',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('访问统计面板', 'kratos'),
'desc' => __('下面的面板会读百度统计的接口,把数据直接显示在这里。'
. '需要先在百度统计开通「数据导出服务」拿到 API Key。', 'kratos'),
'type' => 'info',
);
$options[] = array(
'name' => __('API Key', 'kratos'),
'desc' => __('百度统计 → 管理 → <a href="https://tongji.baidu.com/sc-web/home/dataapi" target="_blank" rel="noopener">数据导出服务</a>'
. ' 开通后可见,形如 <code>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</code>。<br>'
. '<b>留空表示不修改</b>已保存的值。', 'kratos'),
'id' => 'stat_api_key',
'type' => 'password',
'std' => '',
);
$options[] = array(
'name' => __('Secret Key', 'kratos'),
'desc' => __('与 API Key 在同一页面。'
. '<b>留空表示不修改</b>。这一项只存在服务器的独立文件里(权限 600),'
. '不会写进设置表,也不会在表单里回显。', 'kratos'),
'id' => 'stat_api_secret',
'type' => 'password',
'std' => '',
);
$options[] = array(
'name' => __('站点 ID', 'kratos'),
'desc' => __('百度统计「管理 → 网站列表」点进站点,地址栏里 <code>siteId=</code> 后面那串数字。'
. '直接粘整条地址也行,会自动抠出数字。', 'kratos'),
'id' => 'stat_site_id',
'type' => 'text',
);
$options[] = array(
'name' => __('授权码', 'kratos'),
'desc' => __('在面板里点「授权」拿到 code 之后,粘到这里并保存 —— '
. '主题会立刻用它换 refresh_token,之后自动续期,不用再管。<br>'
. '授权码只能用一次,保存后这一格会自动清空。', 'kratos'),
'id' => 'stat_auth_code',
'type' => 'text',
'std' => '',
);
$options[] = array(
'name' => __('refresh_token(高级)', 'kratos'),
'desc' => __('一般不需要手动填 —— 走上面的「授权码」流程会自动得到。'
. '只有在别处已经拿过 token、要迁移过来时才用。<br>'
. '<b>留空表示不修改</b>。', 'kratos'),
'id' => 'stat_refresh',
'type' => 'password',
'std' => '',
);
/* 指标多选:全做,但让用户决定显示哪些。
理由见 inc/theme-stat.php 的 yang_stat_metric_defs() ——
每个指标对应一次 API 调用,而配额是按次计的。 */
$yang_stat_metric_opts = array();
if (function_exists('yang_stat_metric_defs')) {
foreach (yang_stat_metric_defs() as $k => $def) {
$yang_stat_metric_opts[$k] = array('name' => $def['label']);
}
}
$yang_stat_metric_all = array();
foreach ($yang_stat_metric_opts as $k => $_) {
$yang_stat_metric_all[$k] = '1';
}
$options[] = array(
'name' => __('面板显示哪些指标', 'kratos'),
'desc' => __('全部取消勾选 = 面板只显示"已接通"不显示数字。'
. '勾得越多,每次刷新后台时调用的接口越多(配额按次计)。'
. '默认全选。', 'kratos'),
'id' => 'stat_metrics',
'std' => $yang_stat_metric_all,
'type' => 'multicheck',
'options' => $yang_stat_metric_opts,
);
$options[] = array(
'name' => __('数据概览', 'kratos'),
'desc' => function_exists('yang_stat_render_shell')
? yang_stat_render_shell()
: '<em>统计模块未加载</em>',
'type' => 'info',
);
$options[] = array(
'name' => __('robots.txt 内容', 'kratos'),
'desc' => __('- 需要 ', 'kratos') . $seoreading . __(' 是开启的状态,下面的配置才会生效', 'kratos'),
'type' => 'info',
);
$options[] = array(
'desc' => __('- 如果网站根目录下已经有 robots.txt 文件,下面的配置不会生效', 'kratos'),
'type' => 'info',
);
$options[] = array(
'desc' => __('- 点击 ', 'kratos') . $seorobots . __(' 查看配置是否生效,如果网站开启了 CDN,可能需要刷新缓存才会生效', 'kratos'),
'type' => 'info',
);
$options[] = array(
'id' => 'seo_robots',
'std' => getrobots(),
'type' => 'textarea',
);
/* ══════════════════════════════════════════════════════════
* 十一、邮件通知 —— SMTP 与评论提醒
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('邮件通知', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('SMTP 服务', 'kratos'),
'desc' => __('用第三方邮箱发信。不开启时走服务器本地发信,很多主机上会被判定为垃圾邮件。', 'kratos'),
'id' => 'm_smtp',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('邮件服务器', 'kratos'),
'desc' => __('填写发件服务器地址', 'kratos'),
'id' => 'm_host',
'class' => 'hidden',
'type' => 'text',
);
$options[] = array(
'name' => __('服务器端口', 'kratos'),
'desc' => __('填写发件服务器端口', 'kratos'),
'id' => 'm_port',
'class' => 'hidden',
'type' => 'text',
);
$options[] = array(
'name' => __('授权方式', 'kratos'),
'desc' => __('填写登录鉴权的方式,ssl 或 tls', 'kratos'),
'id' => 'm_sec',
'class' => 'hidden',
'type' => 'text',
);
$options[] = array(
'name' => __('邮箱帐号', 'kratos'),
'desc' => __('填写邮箱账号', 'kratos'),
'id' => 'm_username',
'class' => 'hidden',
'type' => 'text',
);
$options[] = array(
'name' => __('邮箱密码', 'kratos'),
'desc' => __('填写邮箱密码或授权码', 'kratos'),
'id' => 'm_passwd',
'class' => 'hidden',
'type' => 'password',
);
$options[] = array(
'id' => 'm_sendmail',
'class' => 'hidden',
'type' => 'sendmail',
);
/* ── 评论提醒的开关 ──────────────────────────────────────
*
* ⚠️ 这三个开关原先**只有代码在读取、后台没有界面**:
* `m_comment_notify` —— 有人回复时通知被回复者
* `m_comment_approved_notify` —— 评论通过审核时通知评论者
* `m_keep_core_notify` —— 保留 WordPress 自带的作者通知
* 也就是说用户无法关掉它们,只能改代码。
* 静态契约检查(`_static_315.php`)会报「模板读取的选项都有界面」失败,
* 那一项正是靠这个抓出来的。
*
* 默认值取 true 与代码里的 `kratos_option('m_comment_notify', true)` 一致:
* 评论提醒默认开着(这是博客的基本礼貌),想关的人自己能关。
*/
$options[] = array(
'name' => __('有人回复时发邮件通知', 'kratos'),
'desc' => __('评论收到回复时,给被回复的人发一封提醒。需要先在下面配好发信方式,否则发不出去。', 'kratos'),
'id' => 'm_comment_notify',
'std' => true,
'type' => 'checkbox',
);
$options[] = array(
'name' => __('评论通过审核时通知评论者', 'kratos'),
'desc' => __('评论从「待审核」变为「已通过」时,告诉评论者他的评论已显示。适合开了先审后发的站点。', 'kratos'),
'id' => 'm_comment_approved_notify',
'std' => true,
'type' => 'checkbox',
);
$options[] = array(
'name' => __('保留 WordPress 自带的作者通知', 'kratos'),
'desc' => __('主题在装了评论插件时会关掉 WordPress 自带的「有新评论通知站长」,避免和插件重复发信。<b>只有当插件在场时勾这一项才有意义</b>;插件不在场时主题一定会保留自带通知,不会让你收不到信。', 'kratos'),
'id' => 'm_keep_core_notify',
'std' => false,
'type' => 'checkbox',
);
/* ══════════════════════════════════════════════════════════
* 十二、CDN 加速 —— 一个开关 + 两个名单
*
* 3.2.34 原设计是「地址留空即关闭」,没有开关。本版(3.2.35)
* 按用户要求补了三件事:
* ① 「启用 CDN 加速」复选框,作为总开关
* ② 白名单预填 13 类静态资源后缀(用户可改)
* ③ 黑名单预填安全相关后缀(用户可改)
*
* ── 预制内容为什么放在输入框里而不是硬编码 ──────────────────
* 用户明确选了这条路:「预制的这些就放在白名单的输入框里,
* 这样用户就可以直接修改;修改完之后,我们再把它保存下来,
* 按照用户修改的来执行。」
*
* 所以预制内容写在 std 里 —— 它只服务两件事:
* · 后台首次渲染时预填
* · 点「恢复默认」时还原成这份内容
* 一旦用户保存过,库里就有明确的值,运行时读的是库里的值,
* 主题**不会**再去覆盖用户改过的内容。
*
* 匹配逻辑(写进说明里,让用户看得到):
* 开关没勾 → 整个功能关闭,两套名单都不参与
* 先看黑名单 → 命中就保持原地址
* 黑名单没命中 → 再看白名单
* 黑名单优先级高于白名单
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('CDN 加速', 'kratos'),
'type' => 'heading',
);
/*
* 开关必须是本段的第一个控件。
*
* std 用 yang_cdn_enable_default()(地址非空 ⇒ 默认勾选),
* 目的是**老站点升级不丢加速**:3.2.34 的站点只要填过地址就
* 一直在加速,升级后若默认不勾,它会静默失去加速,而用户不会
* 去查一个从没动过的复选框。
*/
$options[] = array(
'name' => __('启用 CDN 加速', 'kratos'),
'desc' => __('勾选后功能才生效。<b>没勾时,下面填什么都不产生作用</b>,前台与不装这个功能完全一致。'
. '<br>勾了但 CDN 地址留空是<b>不允许</b>的:点保存会被拦下来并提示必须填地址。', 'kratos'),
'id' => 'cdn_enable',
'std' => function_exists('yang_cdn_enable_default') ? yang_cdn_enable_default() : false,
'type' => 'checkbox',
);
$options[] = array(
'name' => __('CDN 地址', 'kratos'),
'desc' => __('资源的加速前缀,例如 <code>https://cdn.example.com</code>。可以不带协议(自动按 https 补全)。'
. '<b>勾选了上面的开关,这一项就必须填</b>。', 'kratos'),
'id' => 'cdn_url',
'std' => '',
'type' => 'text',
'placeholder' => 'https://cdn.example.com',
);
/*
* 白名单的 std 就是预制清单。
*
* ⚠️ std 在运行时**完全不生效**(kratos_option 只查库),所以
* 这里只负责「后台预填」与「恢复默认」。运行时若库里没有这个键,
* 由 yang_cdn_defaults() 提供同一份内容 —— 两处必须一致,
* 否则会出现"后台显示 13 项、运行时按 0 项算"的错位。
*/
$options[] = array(
'name' => __('白名单', 'kratos'),
'desc' => __('命中这些规则的文件<b>才</b>走 CDN。一行一条,<code>#</code> 或 <code>;</code> 开头的行是注释。'
. '<br><b>这里已经预填了 13 类常用静态资源</b>,你可以直接改、加、删,保存后按你改的执行。'
. '<br>⚠️ 把它全部删空 = <b>所有静态文件都走 CDN</b>(不只是下面这 13 类)。'
. '不想让字体、图标字体之类的走 CDN,就把 <code>*</code> 换成具体后缀。', 'kratos'),
'id' => 'cdn_include',
'std' => function_exists('yang_cdn_preset_text') ? yang_cdn_preset_text(yang_cdn_preset_include()) : '',
'type' => 'code',
'settings' => array('rows' => 10),
);
$options[] = array(
'name' => __('白名单写法', 'kratos'),
'desc' => __('按<b>路径</b>匹配(正则规则除外,它对完整地址匹配)。下面每种写法都可以直接抄:', 'kratos')
. '<br><code>*</code> —— 全部静态文件'
. '<br><code>jpg</code> 或 <code>.jpg</code> 或 <code>*.jpg</code> —— 扩展名'
. '<br><code>jpg,png,webp</code> 或 <code>.jpg .png</code> —— 一串扩展名,命中任一'
. '<br><code>/wp-content/uploads/</code> —— 以这个路径开头(以斜杠开头、不含星号)'
. '<br><code>*/uploads/*</code> —— 通配符,<code>*</code> 可以跨斜杠'
. '<br><code>uploads</code> —— 路径里含这几个字(子串)'
. '<br><code>~\/20\d\d\/~</code> —— 正则,对完整地址匹配'
. '<br><br>扩展到字体也很常见:加一行 <code>.woff2 .woff .ttf .eot .otf</code> 即可。',
'type' => 'info',
);
$options[] = array(
'name' => __('黑名单', 'kratos'),
'desc' => __('命中的文件<b>即使符合白名单也不走 CDN</b>,保持原地址。一行一条,写法与白名单完全相同。'
. '<b>黑名单优先于白名单</b>:两边都命中时按黑名单处理。'
. '<br><b>这里已经预填了安全相关的后缀</b>(<code>.php</code> <code>.env</code> <code>.sql</code> <code>.bak</code> <code>.log</code> 等)—— '
. '它们一旦被推到第三方 CDN 节点上,等于把配置、数据库备份、错误日志公开了。'
. '<b>建议保留这行</b>,即使白名单写的是 <code>*</code>。', 'kratos'),
'id' => 'cdn_exclude',
'std' => function_exists('yang_cdn_preset_text') ? yang_cdn_preset_text(yang_cdn_preset_exclude()) : '',
'type' => 'code',
'settings' => array('rows' => 10),
);
$options[] = array(
'name' => __('这些地址永远不走 CDN', 'kratos'),
'desc' => __('下面这些不受两套名单影响,是硬性排除 —— 把它们改到 CDN 会让站点直接坏掉,所以不给开关:'
. '<br>· <b>以斜杠结尾</b>的地址(页面、栏目页、导航链接):改了会让访客跳去 CDN 域名,全站 404'
. '<br>· <b>页面与动态后缀</b>:<code>php</code>、<code>html</code>、<code>htm</code>、<code>asp</code> 等'
. '<br>· <b>没有后缀</b>的地址:通常是接口、短链、伪静态页'
. '<br>· <b>后台与接口路径</b>:<code>wp-admin</code>、<code>wp-login.php</code>、<code>wp-json</code>、<code>wp-cron.php</code>、<code>xmlrpc.php</code>、<code>/feed/</code>'
. '<br>· <b>非本站域名</b>:外链图片、别人家的 CDN'
. '<br>· <b>非 http(s) 协议</b>:<code>data:</code>、<code>mailto:</code>、<code>tel:</code>、<code>javascript:</code>', 'kratos'),
'type' => 'info',
);
$options[] = array(
'name' => __('后台与接口里不改写', 'kratos'),
'desc' => __('为了不影响编辑与调试,下面这些地方<b>保持原地址</b>:后台页面、REST 接口、AJAX 请求、feed 订阅、WP-Cron、定制器预览、命令行。只有访客看到的前台页面才改写。', 'kratos'),
'type' => 'info',
);
/* ══════════════════════════════════════════════════════════
* 十二、图片灯箱 —— 正文图片点击在当前页放大
*
* 放在 CDN 之后:灯箱里加载的原图地址会被 CDN 一起改写,
* 两者是相关的,放近一点方便对照着调。
*
* ⚠️ 这里的 std 必须与 inc/theme-lightbox.php 的运行时默认值一致
* (yang_lightbox_active() / yang_lightbox_theme())。
* `std` 只服务「后台预填」和「恢复默认」,**运行时完全不读**;
* 两处不一致会出现「后台看着勾上了、前台却没生效」。
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('图片灯箱', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('启用图片灯箱', 'kratos'),
'desc' => __('点击正文里的图片,<b>在当前页面</b>覆盖展示原图,不再跳新标签页。'
. '一篇文章有多张图时可以左右切换,也支持键盘方向键。'
. '<br><b>说明:</b>这些图在正文里通常是缩略图,点开后加载的是<b>原图</b>;'
. '若开启了 CDN 加速,原图地址同样走 CDN。'
. '<br>关掉则完全退回原有行为(点击在新标签页打开图片)。', 'kratos'),
'std' => '1',
'id' => 'a_lightbox_enable',
'type' => 'checkbox',
);
/* 遮罩配色的两个单选项
*
* 用户先要「黑底也行、不黑也行」,看了两套演示后明确选了**白色**,
* 所以 std 从 dark 改成 light。深色仍然保留:看照片时它更合适
* (周围全黑对颜色判断干扰最小),换站/换内容时可能需要。
* 说明文字里写清各自适合什么图,避免用户反复切换试错。 */
$options[] = array(
'name' => __('遮罩配色', 'kratos'),
'desc' => __('<b>白色</b>(默认)适合以截图、文档、白底图为主的内容,'
. '不会让白底图的边缘发灰。'
. '<br><b>深色</b>适合看照片:周围全黑时对图里颜色的判断更准。', 'kratos'),
'std' => 'light',
'id' => 'a_lightbox_theme',
'type' => 'radio',
'options' => array(
'light' => __('白色遮罩(浅底,看截图与文档)', 'kratos'),
'dark' => __('深色遮罩(黑底,看照片)', 'kratos'),
),
/* 只有灯箱开着时才有意义;关着的时候藏起来,
避免用户改了配色却不知道为什么没变化。 */
'requires' => 'a_lightbox_enable',
);
/* 遮罩不透明度
*
* 用户要求「不一定要把内容全盖住一点都看不见,只突出这张照片就行,
* 透明度 80% 左右」。做成可调而不是写死 80:
* · 各人屏幕亮度不同,80 在别人站上可能偏透或偏实;
* · 以后想临时更实一点(确认底下文字)或更透一点,
* 改一个数字就行,不用等主题更新。
*
* ⚠️ 用 select 而不是文本输入:文本输入让用户自己敲数字,
* 敲出 0 或 300 都是灾难(0 = 完全看不见遮罩,300 = 非法值)。
* 给固定的几档,每个档都验证过可读性。 */
$options[] = array(
'name' => __('遮罩不透明度', 'kratos'),
'desc' => __('数值越小,底下正文透出来越多 —— 能看出自己还停在哪一页,'
. '但正文也越容易干扰看图的注意力。'
. '<br><b>80%(默认)</b>是「隐约可见、照片仍是主角」的平衡点。'
. '低于 70% 时请在深色图片较多的页面上确认一下底部文字是否还看得清。'
. '<br><b>说明:</b>这里改的是<b>遮罩底色</b>的透明度,'
. '照片本身不会被弄淡。', 'kratos'),
'std' => '80',
'id' => 'a_lightbox_opacity',
'type' => 'select',
'options' => array(
'100' => __('100% —— 完全盖住(不透)', 'kratos'),
'95' => __('95% —— 几乎不透明', 'kratos'),
'90' => __('90% —— 很实', 'kratos'),
'85' => __('85%', 'kratos'),
'80' => __('80% —— 默认,隐约可见', 'kratos'),
'75' => __('75%', 'kratos'),
'70' => __('70% —— 能明显看到底下', 'kratos'),
'60' => __('60% —— 偏透', 'kratos'),
'50' => __('50% —— 最透(下限)', 'kratos'),
),
'requires' => 'a_lightbox_enable',
);
/* ══════════════════════════════════════════════════════════
* 十二·五、代码高亮
*
* 自研替换(highlight.js 自托管),不再依赖 Enlighter / Prism 插件。
*
* ⚠️ 这里的 std 必须与 inc/theme-highlight.php 的运行时默认值一致
* (yang_hl_enabled() / yang_hl_js_config())。
* `std` 只服务「后台预填」和「恢复默认」,**运行时完全不读**;
* 两处不一致会出现「后台看着勾上了、前台却没生效」。
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('代码高亮', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('启用代码高亮', 'kratos'),
'desc' => __('接管文章与评论里的所有代码块:配色、语言标签、复制按钮、行号。'
. '<br>关掉则退回插件高亮(若插件也没开,代码块就是纯文本)。', 'kratos'),
'std' => '1',
'id' => 'a_highlight_enable',
'type' => 'checkbox',
);
/* 配色:20 套 = 10 浅 + 10 深
*
* 深色组不是装饰。两个实际用途:
* ① 技术文章里把代码块做成深色,能压住浅色的正文,突出代码本身;
* ② 一篇文章并列给 Python / Java 两种实现时,配色差异帮读者分清段落。
* 站内实测有 19 篇文章含 ≥2 种语言、38 处相邻代码块语言不同 —— 确有场景。
*
* 只加载选中的那一套:20 套全加载约 50KB,没必要,
* 而且多套同时在场时会互相压制,切换时容易"看起来没变"。 */
$yang_hl_opts = array();
if (function_exists('yang_hl_themes')) {
foreach (yang_hl_themes() as $k => $v) {
$yang_hl_opts[$k] = $v[1] === 'dark'
? sprintf(__('%s(深色)', 'kratos'), $v[0])
: sprintf(__('%s(浅色)', 'kratos'), $v[0]);
}
}
$options[] = array(
'name' => __('代码配色', 'kratos'),
'desc' => __('共 20 套,<b>10 套浅色 + 10 套深色</b>。'
. '<br><b>浅色</b>与站点底色接近,整体更统一;'
. '<b>深色</b>能压住浅色正文、让代码更突出,技术文章里常用。'
. '<br><b>说明:</b>深色配色会连带代码块的顶栏与底色一起变深,'
. '不会出现"浅色页面上印浅色字"。', 'kratos'),
'std' => 'github',
'id' => 'a_highlight_theme',
'type' => 'select',
'options' => $yang_hl_opts,
'requires' => 'a_highlight_enable',
);
/* 配色预览:紧跟在配色下拉框后面
*
* 为什么需要:20 套配色只有名字(GitHub / Monokai / Night Owl…),
* 光看名字不知道长什么样。原来的流程是
* 选一个 → 保存 → 去前台刷新 → 不满意 → 回来再选,
* 来回几次就放弃、最后一直用默认那套。
*
* 这里放一张**真代码块卡片**,切换下拉框立刻换色,不用保存。
* 用的是前台同一份 CSS(配色 min.css + highlight.css),不另写一套 ——
* 另写一套会导致预览和前台不一致,比没有预览更误导人。 */
if (function_exists('yang_hl_admin_preview_html')) {
$options[] = array(
'name' => '',
'desc' => yang_hl_admin_preview_html(),
'type' => 'info',
'class' => 'yang-hl-preview-wrap',
);
}
$options[] = array(
'name' => __('显示行号', 'kratos'),
'desc' => __('<b>默认关闭。</b>'
. '<br>行号画在代码左侧的独立区域里,<b>不会进入复制内容</b> ——'
. '复制出去的永远是干净的代码,可以直接粘到编辑器里跑。'
. '<br><b>说明:</b>如果是要逐行对比两段代码,建议复制到编辑器里用'
. '它自带的行号或对比功能,那样更准确。', 'kratos'),
'std' => '0',
'id' => 'a_highlight_line_numbers',
'type' => 'checkbox',
'requires' => 'a_highlight_enable',
);
$options[] = array(
'name' => __('显示复制按钮', 'kratos'),
'desc' => __('<b>默认开启。</b>代码块右上角一个「复制」,点一下把纯代码放进剪贴板。'
. '<br>桌面端鼠标移到代码块上才显形(不占视觉),'
. '触屏设备上常驻可见。', 'kratos'),
'std' => '1',
'id' => 'a_highlight_copy_button',
'type' => 'checkbox',
'requires' => 'a_highlight_enable',
);
/* 自动识别必须做成选项,不能强行开着
*
* 站内实测:18 个 ≥4 行、作者没标语言的代码块,用 highlightAuto 只对了 4 个
* (22%)。典型错误:Java 被认成 Kotlin 或 C#、shell 输出被认成 Swift、
* 配置文件被认成 Go/Ruby、140 行 LaTeX 被认成 Ruby。
* 所以它默认开着当兜底(老文章大量没标语言,不开就全是白板),
* 但必须让用户能关掉 —— 否则等于把 78% 的错误结果当成事实贴在页面上。
* 识别出来的标签会画成虚线边框 + 斜体,与作者手标的实线区分开。 */
$options[] = array(
'name' => __('自动识别代码语言', 'kratos'),
'desc' => __('<b>默认开启。</b>对<b>没写语言标记</b>的代码块自动判断语言并上色;'
. '作者标了语言的块永远按标记来,不受影响。'
. '<br><b>识别结果会画成虚线边框 + 斜体</b>,提醒读者这是猜的、可能不准。'
. '<br><b>为什么要做成开关:</b>自动识别并不总对 —— 已用站内代码实测,'
. '像 Java 可能被认成 Kotlin/C#、命令行输出可能被认成 Swift。'
. '如果你发现某些代码块颜色明显不对,可以关掉它,全部按纯文本显示。', 'kratos'),
'std' => '1',
'id' => 'a_highlight_autodetect',
'type' => 'checkbox',
'requires' => 'a_highlight_enable',
);
/* ══════════════════════════════════════════════════════════
* 十三、写作增强 —— 发布时自动生成别名/标签/分类/摘要
*
* 四个引擎各自独立开关,互不依赖。分工是按「能不能纯本地做」
* 划的,不是按功能好坏:
* · 分类、摘要 → 纯本地(分类 90% 准确率、摘要 4ms)
* · 标签 → 本地优先,外部可选(Top8 召回 49.2%,见下方说明)
* · 别名 → 必须外部(纯 PHP 做不出中译英)
*
* 用户明确要求「能不用外部就不用,为了安全着想」,所以默认状态是:
* 四个开关**全关**,需要用户主动打开 —— 新装主题不该偷偷往文章里写东西。
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('写作增强', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('总开关说明', 'kratos'),
'desc' => __('下面的功能全部只在<b>「文章第一次发布」</b>那一下生效,'
. '并且<b>只填空白、绝不覆盖已有内容</b>。'
. '<br>已发布的旧文章<b>完全不动</b> —— 再次编辑保存也不会改任何字段。'
. '<br>这样设计是因为别名就是 URL:一旦被改,旧链接全部 404。', 'kratos'),
'type' => 'info',
);
/* ── ① 摘要 ────────────────────────────────────────────────
* 放第一个:最安全、最便宜、纯本地、离线可用。
* 站点现在 967 篇文章**没有一篇有摘要**,这一项立刻有收益。 */
$options[] = array(
'name' => __('自动生成摘要', 'kratos'),
'desc' => __('发布时从正文里抽取 150 字左右的摘要,写入「摘要」字段。'
. '<b>纯本地计算,不联网</b>,耗时约 4 毫秒。'
. '<br>只有摘要为空时才写;你自己写过的摘要不会被改动。', 'kratos'),
'std' => '0',
'id' => 'w_on_excerpt',
'type' => 'checkbox',
);
/* ── ② 分类 ────────────────────────────────────────────────
* 同样纯本地。只在新文章还挂在「默认分类」上时才出手。 */
$options[] = array(
'name' => __('自动选择分类', 'kratos'),
'desc' => __('发布时按标签与已发布文章的历史共现关系,推荐最接近的一个分类。'
. '<b>纯本地计算,不联网。</b>'
. '<br>只对<b>仍挂在「默认分类」</b>的新文章生效;你已经选过分类的不动。'
. '<br>线上实测准确率约 90%(前三名命中 99%)。', 'kratos'),
'std' => '0',
'id' => 'w_on_category',
'type' => 'checkbox',
);
/* ── ③ 标签 ────────────────────────────────────────────────
* 本地引擎:703 个已有标签当词典,最大匹配 + TF-IDF 排序。
* 外部引擎:本地匹配不到新词时,由服务商提关键词 —— 这是唯一
* 能"发现新词"的路(服务器没有任何中文分词扩展)。 */
$options[] = array(
'name' => __('自动生成标签', 'kratos'),
'desc' => __('发布时自动打标签。<b>优先从你已有的标签里挑</b>'
. '(实测:Top8 能命中人工标签的 49.2%,'
. '只算字面出现在正文里的人工标签则 59.0%),'
. '匹配不到才考虑新增。'
. '<br>你自己手工打的标签<b>不会被删除</b>,只会追加。', 'kratos'),
'std' => '0',
'id' => 'w_on_tag',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('每篇最多新增几个标签', 'kratos'),
'desc' => __('只限制<b>新建</b>的标签数量;匹配到已有标签不受此限制。'
. '<br>设为 0 表示<b>只从已有标签里挑,永不新建</b>。'
. '<br>为什么要限制:站内 703 个标签里已有 338 个只用过 1 次,'
. '放开新建会让标签库迅速变成几千个长尾词。', 'kratos'),
'std' => '3',
'id' => 'w_tag_new_limit',
'type' => 'text',
);
/* ── ④ 别名 ────────────────────────────────────────────────
* 唯一必须联网的一项。放最后:万一接口超时,前面三项已经落库。 */
$options[] = array(
'name' => __('自动生成英文别名', 'kratos'),
'desc' => __('发布时把中文标题翻译成英文,作为 URL 别名(slug)。'
. '<b>这一项必须联网调用翻译服务</b> —— 纯 PHP 做不出中译英,'
. '拼音方案(xin-xi-jiao-lu…)又长又不可读,所以没有本地引擎。'
. '<br>只有别名为空、或仍是 <code>%e4%bf%a1</code> 这种乱码时才写;'
. '<b>你手工填过的别名绝不动</b>。'
. '<br>翻译失败(超时、限额用尽)时<b>安静跳过</b>,不影响发布。', 'kratos'),
'std' => '0',
'id' => 'w_on_slug',
'type' => 'checkbox',
);
/* ── 翻译服务商 ────────────────────────────────────────────
* 只用服务器实测可达的三家。Google 翻译返回 000、LibreTranslate 超时,
* 已排除 —— 列出来只会让用户白填密钥。 */
$providers = function_exists('yang_writing_providers') ? yang_writing_providers() : array();
$prov_sel = array();
foreach ($providers as $k => $v) {
$prov_sel[$k] = $v['label'];
}
/* ── 首帧显隐:在服务端就把它算对 ──────────────────────────
*
* 不这么做的后果:进设置页时四个密钥框**全部先显示**,等
* options-custom.js 跑到 yq_toggle_writing_slug_fields() 才被藏起来 ——
* 每次进页面、每次保存后重载,都闪一下。
*
* inc/theme-avatar.php 那边踩过同一个坑,结论一样:
* 「首帧的显隐由 PHP 打 class 上(不闪),JS 只管‘点了之后’。」
*
* ⚠️ 这里读的是**当前已保存值**,不是 std。用户没保存过时
* kratos_option 会给回传的默认值,正好也是表单上显示的值,一致。 */
$yang_prov_now = (string) kratos_option('w_provider', 'mymemory');
if (!isset($prov_sel[$yang_prov_now])) {
/* 库里的值不在清单里(配置被改坏 / 服务商下架)时,按第一项算,
与 select 实际会选中的那项保持一致 —— 否则首帧和 JS 修正后不一样。
🔴 别用 key($prov_sel) 取「第一项」:那读的是数组**内部指针**,
而指针位置取决于前面有没有跑过 foreach / reset,属于隐式状态。
PHP 7 起 foreach 按值遍历不动指针,所以现在恰好对;但这是巧合,
不是保证 —— 中间插一句 reset() 或改成按引用遍历就会变。
线上硬基线是 PHP 7.4,用 array_key_first()(7.3+)才是显式的。 */
$yang_first = function_exists('array_key_first') ? array_key_first($prov_sel) : null;
if ($yang_first === null && !empty($prov_sel)) {
$yang_keys = array_keys($prov_sel);
$yang_first = $yang_keys[0];
}
$yang_prov_now = (string) $yang_first;
}
$yang_on_slug = function_exists('yang_writing_on') ? yang_writing_on('w_on_slug') : false;
/* 生成某个字段的 class。$key 为 null 表示「不按服务商分组、只看总开关」。 */
$yang_prov_class = function ($key = null) use ($yang_on_slug, $yang_prov_now) {
if ($key === null) {
return $yang_on_slug ? '' : 'yq-cond-hide';
}
$cls = 'yang-prov yang-prov-' . $key;
if (!$yang_on_slug || $key !== $yang_prov_now) {
$cls .= ' yq-cond-hide';
}
return $cls;
};
$options[] = array(
'name' => __('翻译服务商', 'kratos'),
'desc' => __('别名生成用的翻译服务。<b>MyMemory 无需密钥,开箱可用</b>,'
. '但它是国外服务、有每日字符限额;被限流时换成百度即可。'
. '<br>选定之后,<b>下方只出现这一家的账号输入框</b> —— 用不上的不会摆在那里碍事。', 'kratos'),
'std' => 'mymemory',
'id' => 'w_provider',
'type' => 'select',
'options' => $prov_sel,
'class' => $yang_prov_class(),
);
/* ── 按服务商分组的账号字段 ────────────────────────────────
*
* 需求(3.2.58):选了谁才显示谁的输入框,别把四家的密钥框全摆着。
*
* 实现要点 —— `class` 里那两个勾子不是装饰,是**唯一的分组声明处**:
* yang-prov 「我说哪家就显示哪家」的成员标记
* yang-prov-<key> 属于哪一家,key 必须与 yang_writing_providers() 一致
*
* 为什么把分组写在 PHP 而不是 JS 里写死一张映射表:
* 服务商清单本来就只有 theme-writing.php 一份(yang_writing_providers())。
* 若 JS 里再抄一份 {baidu:[...], youdao:[...]},将来加一家就得改两处,
* 而且两边不一致时**不会报错**,只会表现成「新服务商的框永远不出现」。
* 现在 JS 只做一件事:读元素上的 class,和下拉框当前值比对。
*
* ⚠️ 框架确实会把这里的 class 拼到包裹 div 上(见
* class-options-interface.php:70 的 `$class .= ' ' . $value['class']`),
* 所以 text/password 会得到 `section section-text yang-prov ...`。
*
* ⚠️ info 类型走的是另一条分支(:272),它拼出来的 class 是
* `section section-info yang-prov ...` —— 注意它**也有 .section 类**。
* 本文件下面那段 CSS 注释里写的「info 不生成 .section 包裹层」只对
* `id="section-xxx"` 这个 id 成立,class 是有的。别再被那句话误导。
*/
/* MyMemory:无需任何账号,说明只有一句。 */
$options[] = array(
'name' => __('服务商账号填写说明', 'kratos'),
'id' => 'w_provider_help_mymemory',
'desc' => __('<b>MyMemory 不用填任何东西</b>,选它就已经可用。'
. '<br>它是免费公共服务,每日有字符限额;某天开始报错、别名生成不出来,'
. '多半是限额用完了 —— 换百度即可。', 'kratos'),
'type' => 'info',
'class' => $yang_prov_class('mymemory'),
);
$options[] = array(
'name' => __('百度翻译 · 账号怎么拿', 'kratos'),
'id' => 'w_provider_help_baidu',
'desc' => __('访问 <a href="https://fanyi-api.baidu.com/" target="_blank" rel="noopener">fanyi-api.baidu.com</a> '
. '→ 注册登录 → 进入「管理控制台」→ 在「开发者信息」里可看到 <b>APPID</b> 与 <b>密钥</b>。'
. '<br>免费版每月 5 万字符,对个人博客够用。', 'kratos'),
'type' => 'info',
'class' => $yang_prov_class('baidu'),
);
$options[] = array(
'name' => __('百度 APPID', 'kratos'),
'desc' => __('在百度控制台的「开发者信息」里,形如 <code>20240xxx</code> 的一串数字。', 'kratos'),
'id' => 'w_baidu_appid',
'type' => 'text',
'class' => $yang_prov_class('baidu'),
);
/* 密钥字段的特殊处理(只对这两项生效)。
*
* 框架的 password 类型会把**真实值**写进 HTML 的 value 属性,页面上看着
* 是圆点,F12 一看就是明文(主题里的 SMTP 密码现在就是这样,那是既有
* 问题,不在本次范围)。这个新功能没有照抄一遍的理由。
*
* 做法:submit 前先用 '' 覆盖掉 std,页面上永远不出现真实密钥;
* 真实值存进独立选项 yang_writing_secrets(见 theme-writing.php 的
* yang_writing_capture_secret)。已有值时在下方提示末 4 位,
* 留空提交 = 不修改。 */
$yang_baidu_hint = function_exists('yang_writing_secret_hint') ? yang_writing_secret_hint('w_baidu_key') : '';
$yang_youdao_hint = function_exists('yang_writing_secret_hint') ? yang_writing_secret_hint('w_youdao_key') : '';
$options[] = array(
'name' => __('百度密钥', 'kratos'),
'desc' => __('<b>留空表示不修改</b>已保存的密钥。', 'kratos')
. ($yang_baidu_hint !== '' ? '<br>当前已保存:<code>' . esc_html($yang_baidu_hint) . '</code>' : ''),
'id' => 'w_baidu_key',
'type' => 'password',
'std' => '',
'class' => $yang_prov_class('baidu'),
);
$options[] = array(
'name' => __('有道翻译 · 账号怎么拿', 'kratos'),
'id' => 'w_provider_help_youdao',
'desc' => __('访问 <a href="https://ai.youdao.com/" target="_blank" rel="noopener">ai.youdao.com</a> '
. '→ 注册登录 → 「创建应用」选「文本翻译」→ 在「应用管理」里可看到 <b>应用 ID</b> 与 <b>应用密钥</b>。', 'kratos'),
'type' => 'info',
'class' => $yang_prov_class('youdao'),
);
$options[] = array(
'name' => __('有道应用 ID', 'kratos'),
'desc' => __('在「应用管理」里查。', 'kratos'),
'id' => 'w_youdao_appid',
'type' => 'text',
'class' => $yang_prov_class('youdao'),
);
$options[] = array(
'name' => __('有道应用密钥', 'kratos'),
'desc' => __('<b>留空表示不修改</b>已保存的密钥。', 'kratos')
. ($yang_youdao_hint !== '' ? '<br>当前已保存:<code>' . esc_html($yang_youdao_hint) . '</code>' : ''),
'id' => 'w_youdao_key',
'type' => 'password',
'std' => '',
'class' => $yang_prov_class('youdao'),
);
/* 超时对所有服务商通用,所以只跟第一层(w_on_slug)走,不分服务商分组。 */
$options[] = array(
'name' => __('接口超时(秒)', 'kratos'),
'desc' => __('调用翻译接口的最长等待时间,建议 8 秒。'
. '设太大时接口挂了会拖慢发布;设太小则容易误判超时。', 'kratos'),
'std' => '8',
'id' => 'w_timeout',
'type' => 'text',
'class' => $yang_prov_class(),
);
/* ══════════════════════════════════════════════════════════
* 十四、自定义代码 —— 兜底用,放最后
*
* 3.2.29 起这两项默认都为空:站点自己的通用样式
* (段落缩进、h2 蓝条、列表缩进)已并入 assets/css/site.css,
* 跟着主题走、升级不丢,也不再需要在这里重复一份。
* ══════════════════════════════════════════════════════════ */
$options[] = array(
'name' => __('自定义代码', 'kratos'),
'type' => 'heading',
);
$options[] = array(
'name' => __('自定义 CSS', 'kratos'),
'desc' => __('附加样式,排在主题公共样式之后加载,用于临时覆盖。站点通用样式已并入主题的 assets/css/site.css,这里<b>留空即可</b>。保存后立即生效,随主题升级保留。', 'kratos'),
'id' => 'g_custom_css',
'std' => yang_opt_default_custom_css(),
'type' => 'code',
'settings' => array('rows' => 14),
);
$options[] = array(
'name' => __('自定义 JS', 'kratos'),
'desc' => __('附加脚本,在页面底部加载,不需要写 &lt;script&gt; 标签。', 'kratos'),
'id' => 'g_custom_js',
'std' => '',
'type' => 'code',
'settings' => array('rows' => 14),
);
return $options;
}