2022-01-07 11:15:42 +08:00
<? php
/**
* 主题选项
2026-10-23 22:56:10 +08:00
*
* ── 分类原则(3.2.15 重排)────────────────────────────────────
* 原来九个头部分类里有几处明显错位:
* · 哀悼功能挂在「首页配置」下面 —— 它影响的是整站(首页、文章页、说明页),
* 跟首页没有关系;
* · 国旗、备案、版权拆在「页脚」但「404 图片」在全站,都是零碎的;
* · 「收录配置」把 robots.txt 一起塞进来,其实那是另一件事。
*
* 现在的顺序按「访客看到的顺序」排:整站基础 → 顶部 → 首页 → 文章 →
* 站长 → 页脚 → 专项功能 → 收录 → 邮件 → 自定义代码。
* 每一项只出现在它真正生效的地方,不再有"在首页配置里改整站行为"这种事。
*
2026-10-23 12:55:41 +08:00
* @author Yongquan Yang <yangyq.net>
2022-01-07 11:15:42 +08:00
* @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 ;
}
2026-10-23 19:50:07 +08:00
/**
* 图片字段的默认值(后台预填用)。
*
* ── 为什么不直接调 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 )
: '' ;
}
}
2026-10-23 22:56:10 +08:00
/**
* 哀悼相关辅助函数的安全访问
*
* 同样的道理: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 ()
: '' ;
}
}
2026-10-24 01:03:36 +08:00
/**
* 今年清明的月日,给后台说明文字用(如「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 );
}
}
2026-10-23 22:56:10 +08:00
/**
* 自定义 CSS 的默认值(来自 inc/theme-custom-code.php)
*
* 同样是加载顺序问题:这个函数在 theme-custom-code.php 里,
* 而 kratos_options() 可能被更早触发。缺了就返回空串,
* 后台的「自定义 CSS」框是空的,但能打开、能填、能保存。
2026-10-25 10:36:29 +08:00
*
* 3.2.29 起该默认值恒为空:原来的那批规则(段落缩进、h2 蓝条、列表缩进)
* 已并入主题公共样式 assets/css/site.css,配置项只留给站长做临时覆盖。
2026-10-23 22:56:10 +08:00
*/
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 ()
: '' ;
}
}
2022-01-07 11:15:42 +08:00
function kratos_options ()
{
$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 ();
2026-10-23 22:56:10 +08:00
/* ══════════════════════════════════════════════════════════
* 一、全站配置 —— 影响每一个页面的东西
* ══════════════════════════════════════════════════════════ */
2022-01-07 11:15:42 +08:00
$options [] = array (
'name' => __ ( '全站配置' , 'kratos' ),
'type' => 'heading' ,
);
$options [] = array (
'name' => __ ( '图片 Logo' , 'kratos' ),
'desc' => __ ( '不选择图片则显示文字标题' , 'kratos' ),
'id' => 'g_logo' ,
'type' => 'upload' ,
2026-10-23 19:50:07 +08:00
/* 有意不设 std:这里空着时显示站点名称文字,比塞一张通用占位
logo 好看。其它图片字段见 inc/theme-option-defaults.php。 */
2022-01-07 11:15:42 +08:00
);
$options [] = array (
'name' => __ ( 'Favicon 图片' , 'kratos' ),
2026-10-23 19:50:07 +08:00
'desc' => __ ( '浏览器收藏夹和地址栏中显示的图标。<b>留空则自动使用主题自带的默认图标</b>,不会出现空白。' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'id' => 'g_icon' ,
2026-10-23 19:50:07 +08:00
/* 有意不写 std。
*
* std 里存的是**绝对 URL**,一旦用户保存表单就会被写进数据库。
* 数据库里躺着一个写死的主题目录地址是隐患:主题目录改名、
2026-10-23 22:56:10 +08:00
* 换 CDN、或站点迁移域名之后,那条记录就指向不存在的文件,
* 页面上是破图,而用户在后台看到的仍是"已设置",很难自己发现。
2026-10-23 19:50:07 +08:00
*
* 所以默认值一律由运行时提供(yang_option_image()),库里保持空。
* 这样默认图地址永远由当前请求现算,改目录也不用改数据。
*/
2022-01-07 11:15:42 +08:00
'type' => 'upload' ,
);
2026-10-23 22:56:10 +08:00
2022-01-07 11:15:42 +08:00
$options [] = array (
'name' => __ ( '背景颜色' , 'kratos' ),
'desc' => __ ( '全站页面的背景颜色,需填写十六进制颜色码' , 'kratos' ),
'id' => 'g_background' ,
'std' => '#f5f5f5' ,
'type' => 'color' ,
);
$options [] = array (
'name' => __ ( 'Chrome 导航栏颜色' , 'kratos' ),
2026-10-23 22:56:10 +08:00
'desc' => __ ( 'Chrome 移动端浏览器地址栏的颜色' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'id' => 'g_chrome' ,
'std' => '#282a2c' ,
'type' => 'color' ,
);
$options [] = array (
'name' => __ ( '404 页面图片' , 'kratos' ),
2026-10-23 22:56:10 +08:00
'desc' => __ ( '访客打开不存在的地址时显示的配图' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'id' => 'g_404' ,
2026-10-23 19:50:07 +08:00
'std' => yang_options_default_img ( 'g_404' ),
2022-01-07 11:15:42 +08:00
'type' => 'upload' ,
);
$options [] = array (
2026-10-23 22:56:10 +08:00
'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' ),
2026-10-25 18:38:11 +08:00
'desc' => __ ( '把评论头像的请求转发到国内可访问的镜像,改善加载速度。若开启了下面的「自动头像」,未上传头像的访客不再需要这张图。' , 'kratos' ),
2026-10-23 22:56:10 +08:00
'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' ,
);
/* ══════════════════════════════════════════════════════════
2026-10-25 19:18:21 +08:00
* 二、自动头像 —— 独立一块
*
* 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' ,
);
2026-10-25 20:19:27 +08:00
/* ⚠️ 这个开关必须排在「头像风格」**之前**。
风格列表是 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' ,
);
2026-10-25 19:18:21 +08:00
$options [] = array (
'name' => __ ( '头像风格' , 'kratos' ),
2026-10-25 19:41:49 +08:00
'desc' => __ ( '勾选上面的「启用自动头像」后,这里才会出现风格列表。所有风格都可自由使用与商用,且不要求署名。点一下即时预览,保存后生效。' , 'kratos' ),
2026-10-25 19:18:21 +08:00
'std' => 'lorelei' ,
'id' => 'a_auto_style' ,
'type' => 'avatar_style' ,
/* 只有开关打开时才显示这个控件;关着的时候看不到它,
避免用户在关闭状态下改了风格却不知道为什么没生效。 */
'requires' => 'a_auto_enable' ,
);
/* ══════════════════════════════════════════════════════════
* 三、顶部导航 —— 页头那一条
2026-10-23 22:56:10 +08:00
* ══════════════════════════════════════════════════════════ */
$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' ,
);
/* ══════════════════════════════════════════════════════════
2026-10-25 19:18:21 +08:00
* 四、首页配置 —— 只管首页列表长什么样
2026-10-23 22:56:10 +08:00
* ══════════════════════════════════════════════════════════ */
$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' ,
);
/* ══════════════════════════════════════════════════════════
2026-10-25 19:18:21 +08:00
* 五、文章配置 —— 打开单篇文章才看得到的东西
2026-10-23 22:56:10 +08:00
* ══════════════════════════════════════════════════════════ */
$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' ,
);
/* ══════════════════════════════════════════════════════════
2026-10-25 19:18:21 +08:00
* 六、自动目录 —— 把长文切成能跳的章节
2026-10-25 12:18:52 +08:00
*
* 与「文章配置」分开成独立标签页,是刻意的:目录的选项有六项,
* 塞进文章配置里会把「页面布局 / 打赏」这些一眼要看到的东西挤下去。
*
* ⚠️ 这里的 std 必须与 inc/theme-toc.php 的 yang_toc_defaults() 一致。
* 前者只服务「后台预填」和「恢复默认」,运行时一律走
* kratos_option($id, 默认值) —— 两处不一致会出现
* 「页面照 A 渲染、后台框里显示 B」的错位,而且极难发现。
* ══════════════════════════════════════════════════════════ */
$options [] = array (
'name' => __ ( '自动目录' , 'kratos' ),
'type' => 'heading' ,
);
2026-10-25 14:36:19 +08:00
/* ── 分组标题:正文里的那一份 ──────────────────────────────
*
* 用 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' ,
);
2026-10-25 12:18:52 +08:00
$options [] = array (
2026-10-25 13:37:26 +08:00
'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' ,
2026-10-25 12:18:52 +08:00
'type' => 'select' ,
'options' => array (
2026-10-25 13:37:26 +08:00
'left' => __ ( '靠左' , 'kratos' ),
'right' => __ ( '靠右' , 'kratos' ),
),
);
2026-10-25 14:36:19 +08:00
$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' ),
),
);
2026-10-25 13:37:26 +08:00
$options [] = array (
'name' => __ ( '自动插入位置' , 'kratos' ),
2026-10-25 14:36:19 +08:00
'desc' => __ ( '只在「添加方式 = 自动」且勾选了「在正文中显示」时起作用;手动方式下插入点由你自己写的 <b>[TOC]</b> 决定。<br><b>第一个标题之前</b>取正文里最先出现的 h1 或 h2 —— 文章大标题在正文之外,不会被算进去;一个 h1/h2 都没有时退回插在文章开头。' , 'kratos' ),
2026-10-25 13:37:26 +08:00
'id' => 'toc_anchor' ,
'std' => 'first' ,
'type' => 'select' ,
'options' => array (
'start' => __ ( '文章开头' , 'kratos' ),
'first' => __ ( '第一个标题之前(H1 / H2) ' , 'kratos' ),
'end' => __ ( '文章结尾' , 'kratos' ),
),
);
2026-10-27 22:31:26 +08:00
/* 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' ),
),
);
2026-10-25 14:36:19 +08:00
$options [] = array (
'name' => __ ( '浮动目录' , 'kratos' ),
2026-10-27 22:31:26 +08:00
'desc' => __ ( '下面几项只影响<b>浮在页面边上</b>那一份 —— 它不占正文位置,和正文里的目录互不相干。' , 'kratos' ),
2026-10-25 14:36:19 +08:00
'id' => 'toc_group_float' ,
'type' => 'info' ,
);
2026-10-25 13:37:26 +08:00
$options [] = array (
'name' => __ ( '浮动显示' , 'kratos' ),
2026-10-25 14:36:19 +08:00
'desc' => __ ( '勾选后,目录变成贴在页面边缘的浮动面板,点边上的小标签滑出,<b>不占正文位置</b>。窄屏(手机)上是从边缘滑出的抽屉,不会挤占正文。<br>与「在正文中显示」互相独立,可以同时勾选。<b>它没有手动/自动之分</b>:勾了就一直在,不勾就不出现。' , 'kratos' ),
2026-10-25 13:37:26 +08:00
'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' ),
2026-10-25 12:18:52 +08:00
),
);
$options [] = array (
2026-10-27 22:31:26 +08:00
'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' ,
2026-10-25 12:18:52 +08:00
'std' => 'show' ,
'type' => 'select' ,
'options' => array (
2026-10-27 22:31:26 +08:00
'show' => __ ( '默认显示(面板展开)' , 'kratos' ),
'hide' => __ ( '默认隐藏(只留侧边标签)' , 'kratos' ),
2026-10-25 12:18:52 +08:00
),
);
2026-10-27 22:31:26 +08:00
$options [] = array (
'name' => __ ( '两份目录共用的设置' , 'kratos' ),
'desc' => __ ( '下面几项对正文里和浮动的那一份<b>同时生效</b> —— 它们的标题文字、收录哪些级别、要不要加序号,两份保持一致。<br><b>「默认状态」不在这里了</b>:正文那份和浮层那份现在各自单独设(见上面两个分组),因为「正文展开、浮层收起」是很常见的搭配。' , 'kratos' ),
'id' => 'toc_group_common' ,
'type' => 'info' ,
);
2026-10-25 12:18:52 +08:00
$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' ,
);
/* ══════════════════════════════════════════════════════════
2026-10-25 19:18:21 +08:00
* 七、站长信息 —— 文章页页脚那张名片
2026-10-23 22:56:10 +08:00
* ══════════════════════════════════════════════════════════ */
$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' ,
);
/* ══════════════════════════════════════════════════════════
2026-10-25 19:18:21 +08:00
* 八、页脚配置 —— 页面最底下那几行
2026-10-23 22:56:10 +08:00
* ══════════════════════════════════════════════════════════ */
$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' ,
);
/* ══════════════════════════════════════════════════════════
2026-10-25 19:18:21 +08:00
* 九、哀悼模式 —— 整站级功能,与首页无关
2026-10-23 22:56:10 +08:00
*
* 这一组原来错放在「首页配置」下面。它影响的是整站配色
* (首页、文章页、说明页都变黑白),放首页下面会让人以为
* 只改首页,而且想找它的人第一反应不会去点「首页配置」。
* 单独成组,名字直说它是什么。
* ══════════════════════════════════════════════════════════ */
$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 ();
2026-10-24 01:03:36 +08:00
foreach ( yang_opt_mourning_presets () as $key => $info ) {
/* 键有两种:静态日期(`09-03`)和动态日期(`qingming`)。
动态日期的键是内部标识,不能直接摆在用户面前 ——
所以看 key_label,没有就回退到键本身(静态日期就是 MM-DD,
显示出来正好是用户认得的日期)。 */
$label = isset ( $info [ 'key_label' ]) ? $info [ 'key_label' ] : $key ;
2026-10-23 22:56:10 +08:00
$entry = array (
2026-10-24 01:03:36 +08:00
'name' => $label . ' ' . $info [ 'name' ],
2026-10-23 22:56:10 +08:00
'note' => $info [ 'note' ],
);
if ( isset ( $info [ 'group' ]) && $info [ 'group' ] === 'legal' ) {
2026-10-24 01:03:36 +08:00
$rip_legal [ $key ] = $entry ;
2026-10-23 22:56:10 +08:00
} else {
2026-10-24 01:03:36 +08:00
$rip_custom [ $key ] = $entry ;
2026-10-23 22:56:10 +08:00
}
}
$rip_default = array ();
2026-10-24 01:03:36 +08:00
foreach ( yang_opt_mourning_presets () as $key => $info ) {
$rip_default [ $key ] = ! empty ( $info [ 'default' ]) ? 1 : 0 ;
2026-10-23 22:56:10 +08:00
}
$options [] = array (
'name' => __ ( '纪念日清单' , 'kratos' ),
2026-10-24 01:03:36 +08:00
'desc' => sprintf (
__ ( '勾选需要自动黑白的日期。<b>默认一个都不勾</b>,请按自己的意愿挑选。前三个是法定纪念日,后面几个是民间通行做法。日期按站点时区判断,只比对月和日,每年循环。<br><b>清明节</b>是节气,日期每年浮动(4 月 4~6 日之间),已脱离固定日期、按天文历逐年换算 —— 今年是 <b>%s</b>。' , 'kratos' ),
esc_html ( yang_opt_qingming_label ())
),
2026-10-23 22:56:10 +08:00
'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' ,
);
/* ══════════════════════════════════════════════════════════
2026-10-25 19:18:21 +08:00
* 十、收录与统计 —— 给搜索引擎看的
2026-10-23 22:56:10 +08:00
* ══════════════════════════════════════════════════════════ */
$options [] = array (
'name' => __ ( '收录与统计' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'type' => 'heading' ,
);
$options [] = array (
'name' => __ ( '分享图片' , 'kratos' ),
2026-10-23 22:56:10 +08:00
'desc' => __ ( '搜索引擎或者社交工具分享首页时抓取的图片。<b>留空则自动使用主题自带的默认图片</b>。' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'id' => 'seo_shareimg' ,
2026-10-23 19:50:07 +08:00
'std' => yang_options_default_img ( 'seo_shareimg' ),
2022-01-07 11:15:42 +08:00
'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' ),
2026-10-23 22:56:10 +08:00
'desc' => __ ( '填在页脚的统计脚本(如站点访问量统计)。注意:输入 HTML/JS 代码时请注意辨别代码安全!' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'id' => 'seo_statistical' ,
'type' => 'textarea' ,
);
2026-10-28 16:33:41 +08:00
/* ══════════════════════════════════════════════════════════
* 访问统计面板(百度统计 API)
*
* 这一整段都是「附加功能」:不填任何东西时,本页其余部分
* 完全不受影响。面板本身由 inc/theme-stat.php 负责渲染,
* 这里只负责把控件摆出来。
* ══════════════════════════════════════════════════════════ */
$options [] = array (
'name' => __ ( '启用统计埋点' , 'kratos' ),
2026-10-28 17:18:54 +08:00
'desc' => __ ( '关掉后前台不再输出上面那段「统计代码」,但代码本身<b>不会被删除</b> —— '
2026-10-28 16:33:41 +08:00
. '想恢复时勾回来即可,不用再去百度后台找那串 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' ,
);
2022-01-07 11:15:42 +08:00
$options [] = array (
2026-10-23 22:56:10 +08:00
'name' => __ ( 'robots.txt 内容' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'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' ,
);
2026-10-23 22:56:10 +08:00
/* ══════════════════════════════════════════════════════════
2026-10-25 19:18:21 +08:00
* 十一、邮件通知 —— SMTP 与评论提醒
2026-10-23 22:56:10 +08:00
* ══════════════════════════════════════════════════════════ */
2022-01-07 11:15:42 +08:00
$options [] = array (
2026-10-23 22:56:10 +08:00
'name' => __ ( '邮件通知' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'type' => 'heading' ,
);
$options [] = array (
'name' => __ ( 'SMTP 服务' , 'kratos' ),
2026-10-23 22:56:10 +08:00
'desc' => __ ( '用第三方邮箱发信。不开启时走服务器本地发信,很多主机上会被判定为垃圾邮件。' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'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' ),
2026-10-23 22:56:10 +08:00
'desc' => __ ( '填写邮箱密码或授权码' , 'kratos' ),
2022-01-07 11:15:42 +08:00
'id' => 'm_passwd' ,
'class' => 'hidden' ,
'type' => 'password' ,
);
$options [] = array (
'id' => 'm_sendmail' ,
'class' => 'hidden' ,
'type' => 'sendmail' ,
);
2026-10-24 01:38:25 +08:00
/* ── 评论提醒的开关 ──────────────────────────────────────
*
* ⚠️ 这三个开关原先**只有代码在读取、后台没有界面**:
* `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' ,
);
2026-10-23 22:56:10 +08:00
/* ══════════════════════════════════════════════════════════
2026-10-25 17:10:06 +08:00
* 十二、CDN 加速 —— 一个开关 + 两个名单
2026-10-25 16:15:09 +08:00
*
2026-10-25 17:10:06 +08:00
* 3.2.34 原设计是「地址留空即关闭」,没有开关。本版(3.2.35)
* 按用户要求补了三件事:
* ① 「启用 CDN 加速」复选框,作为总开关
* ② 白名单预填 13 类静态资源后缀(用户可改)
* ③ 黑名单预填安全相关后缀(用户可改)
*
* ── 预制内容为什么放在输入框里而不是硬编码 ──────────────────
* 用户明确选了这条路:「预制的这些就放在白名单的输入框里,
* 这样用户就可以直接修改;修改完之后,我们再把它保存下来,
* 按照用户修改的来执行。」
*
* 所以预制内容写在 std 里 —— 它只服务两件事:
* · 后台首次渲染时预填
* · 点「恢复默认」时还原成这份内容
* 一旦用户保存过,库里就有明确的值,运行时读的是库里的值,
* 主题**不会**再去覆盖用户改过的内容。
2026-10-25 16:15:09 +08:00
*
* 匹配逻辑(写进说明里,让用户看得到):
2026-10-25 17:10:06 +08:00
* 开关没勾 → 整个功能关闭,两套名单都不参与
2026-10-25 16:15:09 +08:00
* 先看黑名单 → 命中就保持原地址
* 黑名单没命中 → 再看白名单
* 黑名单优先级高于白名单
* ══════════════════════════════════════════════════════════ */
$options [] = array (
'name' => __ ( 'CDN 加速' , 'kratos' ),
'type' => 'heading' ,
);
2026-10-25 17:10:06 +08:00
/*
* 开关必须是本段的第一个控件。
*
* 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' ,
);
2026-10-25 16:15:09 +08:00
$options [] = array (
'name' => __ ( 'CDN 地址' , 'kratos' ),
2026-10-25 17:10:06 +08:00
'desc' => __ ( '资源的加速前缀,例如 <code>https://cdn.example.com</code>。可以不带协议(自动按 https 补全)。'
. '<b>勾选了上面的开关,这一项就必须填</b>。' , 'kratos' ),
2026-10-25 16:15:09 +08:00
'id' => 'cdn_url' ,
'std' => '' ,
'type' => 'text' ,
'placeholder' => 'https://cdn.example.com' ,
);
2026-10-25 17:10:06 +08:00
/*
* 白名单的 std 就是预制清单。
*
* ⚠️ std 在运行时**完全不生效**( kratos_option 只查库),所以
* 这里只负责「后台预填」与「恢复默认」。运行时若库里没有这个键,
* 由 yang_cdn_defaults() 提供同一份内容 —— 两处必须一致,
* 否则会出现"后台显示 13 项、运行时按 0 项算"的错位。
*/
2026-10-25 16:15:09 +08:00
$options [] = array (
'name' => __ ( '白名单' , 'kratos' ),
2026-10-25 17:10:06 +08:00
'desc' => __ ( '命中这些规则的文件<b>才</b>走 CDN。一行一条,<code>#</code> 或 <code>;</code> 开头的行是注释。'
. '<br><b>这里已经预填了 13 类常用静态资源</b>,你可以直接改、加、删,保存后按你改的执行。'
. '<br>⚠️ 把它全部删空 = <b>所有静态文件都走 CDN</b>(不只是下面这 13 类)。'
. '不想让字体、图标字体之类的走 CDN,就把 <code>*</code> 换成具体后缀。' , 'kratos' ),
2026-10-25 16:15:09 +08:00
'id' => 'cdn_include' ,
2026-10-25 17:10:06 +08:00
'std' => function_exists ( 'yang_cdn_preset_text' ) ? yang_cdn_preset_text ( yang_cdn_preset_include ()) : '' ,
2026-10-25 16:15:09 +08:00
'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> —— 路径里含这几个字(子串)'
2026-10-25 17:10:06 +08:00
. '<br><code>~\/20\d\d\/~</code> —— 正则,对完整地址匹配'
. '<br><br>扩展到字体也很常见:加一行 <code>.woff2 .woff .ttf .eot .otf</code> 即可。' ,
2026-10-25 16:15:09 +08:00
'type' => 'info' ,
);
$options [] = array (
'name' => __ ( '黑名单' , 'kratos' ),
2026-10-25 17:10:06 +08:00
'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' ),
2026-10-25 16:15:09 +08:00
'id' => 'cdn_exclude' ,
2026-10-25 17:10:06 +08:00
'std' => function_exists ( 'yang_cdn_preset_text' ) ? yang_cdn_preset_text ( yang_cdn_preset_exclude ()) : '' ,
2026-10-25 16:15:09 +08:00
'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' ,
);
2026-10-25 22:05:24 +08:00
/* ══════════════════════════════════════════════════════════
* 十二、图片灯箱 —— 正文图片点击在当前页放大
*
* 放在 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' ,
);
2026-10-26 09:00:17 +08:00
/* ══════════════════════════════════════════════════════════
* 十二·五、代码高亮
*
* 自研替换(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' ,
);
2026-10-26 11:37:42 +08:00
/* 配色预览:紧跟在配色下拉框后面
*
* 为什么需要: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' ,
);
}
2026-10-26 09:00:17 +08:00
$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' ,
);
2026-10-25 16:15:09 +08:00
/* ══════════════════════════════════════════════════════════
2026-10-27 16:05:43 +08:00
* 十三、写作增强 —— 发布时自动生成别名/标签/分类/摘要
*
* 四个引擎各自独立开关,互不依赖。分工是按「能不能纯本地做」
* 划的,不是按功能好坏:
* · 分类、摘要 → 纯本地(分类 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' ];
}
2026-10-27 21:40:00 +08:00
/* ── 首帧显隐:在服务端就把它算对 ──────────────────────────
*
* 不这么做的后果:进设置页时四个密钥框**全部先显示**,等
* 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 ;
};
2026-10-27 16:05:43 +08:00
$options [] = array (
'name' => __ ( '翻译服务商' , 'kratos' ),
'desc' => __ ( '别名生成用的翻译服务。<b>MyMemory 无需密钥,开箱可用</b>, '
2026-10-27 21:40:00 +08:00
. '但它是国外服务、有每日字符限额;被限流时换成百度即可。'
. '<br>选定之后,<b>下方只出现这一家的账号输入框</b> —— 用不上的不会摆在那里碍事。' , 'kratos' ),
2026-10-27 16:05:43 +08:00
'std' => 'mymemory' ,
'id' => 'w_provider' ,
'type' => 'select' ,
'options' => $prov_sel ,
2026-10-27 21:40:00 +08:00
'class' => $yang_prov_class (),
2026-10-27 16:05:43 +08:00
);
2026-10-27 21:40:00 +08:00
/* ── 按服务商分组的账号字段 ────────────────────────────────
*
* 需求(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:无需任何账号,说明只有一句。 */
2026-10-27 16:05:43 +08:00
$options [] = array (
'name' => __ ( '服务商账号填写说明' , 'kratos' ),
2026-10-27 21:40:00 +08:00
'id' => 'w_provider_help_mymemory' ,
'desc' => __ ( '<b>MyMemory 不用填任何东西</b>,选它就已经可用。'
. '<br>它是免费公共服务,每日有字符限额;某天开始报错、别名生成不出来,'
. '多半是限额用完了 —— 换百度即可。' , 'kratos' ),
2026-10-27 16:05:43 +08:00
'type' => 'info' ,
2026-10-27 21:40:00 +08:00
'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' ),
2026-10-27 16:05:43 +08:00
);
$options [] = array (
'name' => __ ( '百度 APPID' , 'kratos' ),
2026-10-27 21:40:00 +08:00
'desc' => __ ( '在百度控制台的「开发者信息」里,形如 <code>20240xxx</code> 的一串数字。' , 'kratos' ),
2026-10-27 16:05:43 +08:00
'id' => 'w_baidu_appid' ,
'type' => 'text' ,
2026-10-27 21:40:00 +08:00
'class' => $yang_prov_class ( 'baidu' ),
2026-10-27 16:05:43 +08:00
);
/* 密钥字段的特殊处理(只对这两项生效)。
*
* 框架的 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' ),
2026-10-27 21:40:00 +08:00
'desc' => __ ( '<b>留空表示不修改</b>已保存的密钥。' , 'kratos' )
2026-10-27 16:05:43 +08:00
. ( $yang_baidu_hint !== '' ? '<br>当前已保存:<code>' . esc_html ( $yang_baidu_hint ) . '</code>' : '' ),
'id' => 'w_baidu_key' ,
'type' => 'password' ,
'std' => '' ,
2026-10-27 21:40:00 +08:00
'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' ),
2026-10-27 16:05:43 +08:00
);
$options [] = array (
'name' => __ ( '有道应用 ID' , 'kratos' ),
2026-10-27 21:40:00 +08:00
'desc' => __ ( '在「应用管理」里查。' , 'kratos' ),
2026-10-27 16:05:43 +08:00
'id' => 'w_youdao_appid' ,
'type' => 'text' ,
2026-10-27 21:40:00 +08:00
'class' => $yang_prov_class ( 'youdao' ),
2026-10-27 16:05:43 +08:00
);
$options [] = array (
'name' => __ ( '有道应用密钥' , 'kratos' ),
2026-10-27 21:40:00 +08:00
'desc' => __ ( '<b>留空表示不修改</b>已保存的密钥。' , 'kratos' )
2026-10-27 16:05:43 +08:00
. ( $yang_youdao_hint !== '' ? '<br>当前已保存:<code>' . esc_html ( $yang_youdao_hint ) . '</code>' : '' ),
'id' => 'w_youdao_key' ,
'type' => 'password' ,
'std' => '' ,
2026-10-27 21:40:00 +08:00
'class' => $yang_prov_class ( 'youdao' ),
2026-10-27 16:05:43 +08:00
);
2026-10-27 21:40:00 +08:00
/* 超时对所有服务商通用,所以只跟第一层(w_on_slug)走,不分服务商分组。 */
2026-10-27 16:05:43 +08:00
$options [] = array (
'name' => __ ( '接口超时(秒)' , 'kratos' ),
'desc' => __ ( '调用翻译接口的最长等待时间,建议 8 秒。'
. '设太大时接口挂了会拖慢发布;设太小则容易误判超时。' , 'kratos' ),
'std' => '8' ,
'id' => 'w_timeout' ,
'type' => 'text' ,
2026-10-27 21:40:00 +08:00
'class' => $yang_prov_class (),
2026-10-27 16:05:43 +08:00
);
/* ══════════════════════════════════════════════════════════
* 十四、自定义代码 —— 兜底用,放最后
2026-10-25 10:36:29 +08:00
*
* 3.2.29 起这两项默认都为空:站点自己的通用样式
* (段落缩进、h2 蓝条、列表缩进)已并入 assets/css/site.css,
* 跟着主题走、升级不丢,也不再需要在这里重复一份。
2026-10-23 22:56:10 +08:00
* ══════════════════════════════════════════════════════════ */
2026-10-23 10:35:56 +08:00
$options [] = array (
'name' => __ ( '自定义代码' , 'kratos' ),
'type' => 'heading' ,
);
$options [] = array (
'name' => __ ( '自定义 CSS' , 'kratos' ),
2026-10-25 10:36:29 +08:00
'desc' => __ ( '附加样式,排在主题公共样式之后加载,用于临时覆盖。站点通用样式已并入主题的 assets/css/site.css,这里<b>留空即可</b>。保存后立即生效,随主题升级保留。' , 'kratos' ),
2026-10-23 10:35:56 +08:00
'id' => 'g_custom_css' ,
2026-10-23 22:56:10 +08:00
'std' => yang_opt_default_custom_css (),
2026-10-23 10:35:56 +08:00
'type' => 'code' ,
'settings' => array ( 'rows' => 14 ),
);
$options [] = array (
'name' => __ ( '自定义 JS' , 'kratos' ),
'desc' => __ ( '附加脚本,在页面底部加载,不需要写 <script> 标签。' , 'kratos' ),
'id' => 'g_custom_js' ,
'std' => '' ,
'type' => 'code' ,
'settings' => array ( 'rows' => 14 ),
);
2022-01-07 11:15:42 +08:00
return $options ;
2026-10-23 22:56:10 +08:00
}