版本号 3.2.5 -> 3.2.6(统一邮件出口 + 邮件模板 + 纪念日自动哀悼 + 去掉广告位)
评论邮件重复的根因是发信源不止一个,分两层处理:一层管「谁发」,一层管「发几封」。 只做前者的话邮件照样重复,只是都从同一个 SMTP 出去。 邮件治理(新增 inc/theme-mail.php) - phpmailer_init 优先级 9999 接管通道,保证主题配置优先于其它 SMTP 插件; 接管前先重置可能被别的插件设过的字段,避免残留半套配置。 - From 强制等于 SMTP 登录账号。QQ / 163 会以「发件人不符」拒收。 - wp_mail 优先级 99 统一 From 与 Content-Type;调用方自带的 From 与 Content-Type 被剥离后重建,自定义 header 保留。 - wp_mail 优先级 100 做短期去重:同一收件人 + 同主题 + 同正文,90 秒内只发一次。 用 transient 是因为评论通知跨请求(wpDiscuz 走 AJAX、审核通知走后台请求), 静态变量跨不过去。90 秒足够宽(一次提交引发的通知都在同一秒)也足够窄 (手动重发通常隔几分钟,不该被吞)。 - 被去重的那次把 to 置空并由 pre_wp_mail 返回 true,而不是 return null: 返回 null 会改变 wp_mail 返回值语义,有的插件据此判定失败并重试,反而更多封。 - 用 filter 关掉核心的 notify_postauthor / notify_moderator,不改进数据库里的 comments_notify / moderation_notify。改用户设置属于越权,而且用户下次进后台 一勾又回来了;filter 是可逆的。只拦通知,不影响评论审核流程。 - 线上站点 m_smtp=1 但 m_host 为空时判定为「未就绪」并完全放手。只勾开关不填 主机时若强行接管,所有邮件会静默失败,比回退到 mail() 更糟。 邮件模板(新增 inc/theme-mail-template.php) - 表格布局 + 全内联样式:Outlook 2007-2019 用 Word 引擎,不认 flex / grid; Gmail 会剥掉 <head> 里的 style 块(含 media query)。 - 不用外部图片、不用 CSS 变量、不用 ::before。按钮的 padding 放在 td 上, 因为 Outlook 忽略 a 的 padding。 - 宽度用混合写法:HTML 属性 width=600 给 Outlook,CSS 的 width:100% + max-width:600px 让手机窄屏自动收窄。只写死 600px 会让手机横向滚动。 - badge 背景用预算的实色而非 rgba。Outlook 不支持 rgba,会当成非法值丢掉, 半透明背景变成全透明,badge 直接消失。 - 主色下沿那条 3px 深色带必须 height 同时写成 HTML 属性和 CSS, 否则 Outlook 把它撑成一行文字高,比例就毁了。 - 用 WCAG 相对亮度判断主色深浅来决定叠在上面的文字用白还是深色。 简单取平均会把 #00ff00 判成中等亮度(人眼对绿最敏感、蓝最不敏感)。 - 次要文字统一调到 #5f6b78。原来的浅灰在白底上只有 1.84:1,看着"高级"但读不出来; 现在最低 4.62:1,全部过 AA。 theme-smtp.php - 删掉 comment_notify / comment_approved 两个钩子及其硬编码模板。这两个钩子与 wpDiscuz 的 notifyCommentSubscribers 完全重复,是重复收信的直接来源。 纪念日自动哀悼(新增 inc/theme-mourning.php) - 法定纪念日三个默认勾选:09-03 抗战胜利纪念日、09-30 烈士纪念日、 12-13 南京大屠杀死难者国家公祭日。依据是 2014 年全国人大常委会两次决定, 已在代码注释里记下来源。 - 民间通行做法(05-12、09-18 等)默认不勾,由用户自己决定。替用户做价值判断不合适。 - 一次性全国哀悼日(2020-04-04 等)支持 YYYY-MM-DD 写法,只在当年生效。 不能当周年循环,否则明年 4 月 4 日又自动变黑白。 - 按站点时区判断(current_time),不按服务器时区。服务器在 UTC 时会晚 8 小时生效。 - 只挂 wp_head 而不限定 is_home():老代码限定首页,详情页不黑白,那是 bug。 - 打印时取消灰度,否则打印出来是灰的。 - 保留旧的 g_rip 手动常开开关。不能因为升级把原本黑白中的站点变回彩色。 options-framework - 补 multicheck 的渲染分支与 of_sanitize_multicheck。框架原来只在保存侧处理了 这个类型,没有渲染分支,所以这个类型此前根本不可用。 - 值存成 array(键 => 1|0) 而不是「勾选了的键组成的列表」:列表无法区分 「用户明确全部取消」和「从未配置」,会导致取消勾选后静默回退到默认值。 - 读取侧用 array_key_exists 判断是否保存过,而不是判断值是否为空。 - 配套补上 of-multicheck 样式,没有样式时 9 个选项会挤成一行流水文字。 去掉广告配置 - 删除主题选项里的广告配置段,以及 single.php 里上下两处广告位输出。 验证 - 全主题 68 个 PHP 文件语法检查通过。 - 行为测试 82 条、multicheck 闭环 25 条、邮件几何 15 条、设计审计 23 条。 - 反向验证 16 个注入 bug 全部被断言抓住(去重失效、不写标记、to 未清空、 From 未剥离、亮度算法退化、时区改用 date()、全不勾回退默认等)。 - 邮件最低文字对比度 4.62:1;320px 视口下卡片自动收窄到 296px 不溢出。
This commit is contained in:
+12
-1
@@ -23,6 +23,10 @@ require get_template_directory() . '/inc/theme-core.php';
|
|||||||
// 站点配置
|
// 站点配置
|
||||||
require get_template_directory() . '/inc/theme-setting.php';
|
require get_template_directory() . '/inc/theme-setting.php';
|
||||||
|
|
||||||
|
// 纪念日自动哀悼(按日期判断,需在 theme-setting 之后,
|
||||||
|
// 因为它用到 kratos_option)
|
||||||
|
require get_template_directory() . '/inc/theme-mourning.php';
|
||||||
|
|
||||||
// 文章配置
|
// 文章配置
|
||||||
require get_template_directory() . '/inc/theme-article.php';
|
require get_template_directory() . '/inc/theme-article.php';
|
||||||
|
|
||||||
@@ -38,5 +42,12 @@ require get_template_directory() . '/inc/theme-navwalker.php';
|
|||||||
// 对象存储配置
|
// 对象存储配置
|
||||||
require get_template_directory() . '/inc/theme-dogecloud.php';
|
require get_template_directory() . '/inc/theme-dogecloud.php';
|
||||||
|
|
||||||
// SMTP 配置
|
// 邮件模板:必须先于 theme-mail.php 加载,
|
||||||
|
// 因为后者在发信时会调用 yang_mail_render()
|
||||||
|
require get_template_directory() . '/inc/theme-mail-template.php';
|
||||||
|
|
||||||
|
// 邮件治理:统一接管发信通道、去重、关掉重复通知
|
||||||
|
require get_template_directory() . '/inc/theme-mail.php';
|
||||||
|
|
||||||
|
// SMTP 配置(连通性相关,实际接管在 theme-mail.php)
|
||||||
require get_template_directory() . '/inc/theme-smtp.php';
|
require get_template_directory() . '/inc/theme-smtp.php';
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
<meta name="description" itemprop="description" content="<?php echo description(); ?>">
|
<meta name="description" itemprop="description" content="<?php echo description(); ?>">
|
||||||
<meta name="theme-color" content="<?php echo kratos_option('g_chrome', '#282a2c'); ?>">
|
<meta name="theme-color" content="<?php echo kratos_option('g_chrome', '#282a2c'); ?>">
|
||||||
<link rel="shortcut icon" href="<?php echo kratos_option('g_icon'); ?>">
|
<link rel="shortcut icon" href="<?php echo kratos_option('g_icon'); ?>">
|
||||||
<?php wp_head(); wp_print_scripts('jquery'); mourning(); ?>
|
<?php wp_head(); wp_print_scripts('jquery'); ?>
|
||||||
</head>
|
</head>
|
||||||
<?php flush(); ?>
|
<?php flush(); ?>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -308,3 +308,54 @@
|
|||||||
#optionsframework .about-content .tips b {
|
#optionsframework .about-content .tips b {
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 多选复选框组(multicheck)────────────────────────────────
|
||||||
|
框架原本没有这个类型的渲染分支,所以也没有配套样式。
|
||||||
|
不加样式的话,9 个纪念日会挤成一行流水文字,看不出是一个可勾选的清单。
|
||||||
|
这里做成两列表格状,并把说明文字压到第二行、颜色调淡,
|
||||||
|
让「名称」是视觉主体、「日期说明」是补充信息。 */
|
||||||
|
#optionsframework .of-multicheck {
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px 0 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#optionsframework .of-multicheck-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 9px 12px;
|
||||||
|
margin: 0 0 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#optionsframework .of-multicheck-item:hover {
|
||||||
|
background-color: #f3f5f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
#optionsframework .of-multicheck-item input[type="checkbox"] {
|
||||||
|
margin: 3px 0 0;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 名称与说明两行文字共用的竖排容器 */
|
||||||
|
#optionsframework .of-multicheck-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#optionsframework .of-multicheck-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #23282d;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#optionsframework .of-multicheck-note {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #727777;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -210,6 +210,46 @@ class Options_Framework_Interface
|
|||||||
$output .= '<label class="explain" for="' . esc_attr($value['id']) . '">' . wp_kses($explain_value, $allowedtags) . '</label>';
|
$output .= '<label class="explain" for="' . esc_attr($value['id']) . '">' . wp_kses($explain_value, $allowedtags) . '</label>';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// 多选复选框组
|
||||||
|
//
|
||||||
|
// 框架原来只在保存侧处理了 multicheck(把未提交的项补 false),
|
||||||
|
// **没有渲染分支、也没有 of_sanitize_multicheck**,
|
||||||
|
// 所以这个类型此前是不可用的。这里补齐两处。
|
||||||
|
//
|
||||||
|
// 值统一存成 array( 键 => 1|0 ),而不是「勾选了的键组成的列表」:
|
||||||
|
// 用 map 才能区分「用户明确取消勾选」和「这个键是新增的」。
|
||||||
|
// 若存成列表,用户取消全部勾选后存进去是空数组,
|
||||||
|
// 读的时候无法与「从未配置」区分,会静默回退到默认值。
|
||||||
|
case "multicheck":
|
||||||
|
$name = $option_name . '[' . $value['id'] . ']';
|
||||||
|
$saved = is_array($val) ? $val : array();
|
||||||
|
if (!empty($value['options'])) {
|
||||||
|
$output .= '<fieldset class="of-multicheck">';
|
||||||
|
foreach ($value['options'] as $key => $opt) {
|
||||||
|
$id = $option_name . '-' . $value['id'] . '-' . $key;
|
||||||
|
$on = !empty($saved[$key]);
|
||||||
|
$label = is_array($opt) && isset($opt['name']) ? $opt['name'] : $opt;
|
||||||
|
$note = is_array($opt) && isset($opt['note']) ? $opt['note'] : '';
|
||||||
|
$output .= '<label class="of-multicheck-item" for="' . esc_attr($id) . '">';
|
||||||
|
$output .= '<input class="checkbox of-input" type="checkbox" '
|
||||||
|
. 'id="' . esc_attr($id) . '" '
|
||||||
|
. 'name="' . esc_attr($name . '[' . $key . ']') . '" '
|
||||||
|
. 'value="1" ' . checked($on, true, false) . ' />';
|
||||||
|
// 名称与说明必须包在同一个竖排容器里。
|
||||||
|
// 若让它们各自作为 label 的直接子项,flex 布局会把
|
||||||
|
// 说明挤到名称右边同一行(不是名称下方)。
|
||||||
|
$output .= '<span class="of-multicheck-text">';
|
||||||
|
$output .= '<span class="of-multicheck-label">' . esc_html($label) . '</span>';
|
||||||
|
if ($note !== '') {
|
||||||
|
$output .= '<span class="of-multicheck-note">' . esc_html($note) . '</span>';
|
||||||
|
}
|
||||||
|
$output .= '</span>';
|
||||||
|
$output .= '</label>';
|
||||||
|
}
|
||||||
|
$output .= '</fieldset>';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
// Color picker
|
// Color picker
|
||||||
case "color":
|
case "color":
|
||||||
$default_color = '';
|
$default_color = '';
|
||||||
|
|||||||
@@ -95,6 +95,36 @@ function of_sanitize_checkbox( $input ) {
|
|||||||
}
|
}
|
||||||
add_filter( 'of_sanitize_checkbox', 'of_sanitize_checkbox' );
|
add_filter( 'of_sanitize_checkbox', 'of_sanitize_checkbox' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitization for multicheck input
|
||||||
|
*
|
||||||
|
* 保存成 array( 键 => 1|0 ),键表来自选项定义里的 options。
|
||||||
|
*
|
||||||
|
* 关键点:**所有已知键都要出现在结果里**,未勾选的写 0。
|
||||||
|
* 如果只保留勾选的键,用户取消全部勾选后存进去是空数组,
|
||||||
|
* 读取时无法与「这个选项从未配置过」区分,会静默回退到默认值,
|
||||||
|
* 表现成「取消勾选不生效」。这是同类字段最常见的 bug。
|
||||||
|
*
|
||||||
|
* 输入的键先与选项定义里的白名单求交集,防止用户 POST 伪造任意键。
|
||||||
|
*
|
||||||
|
* @param array $input 提交上来的值
|
||||||
|
* @param array $option 选项定义(含 options 白名单)
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
function of_sanitize_multicheck( $input, $option ) {
|
||||||
|
$output = array();
|
||||||
|
if ( ! isset( $option['options'] ) || ! is_array( $option['options'] ) ) {
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
$input = is_array( $input ) ? $input : array();
|
||||||
|
foreach ( $option['options'] as $key => $value ) {
|
||||||
|
// 只认白名单里的键;值为真即视为勾选
|
||||||
|
$output[ $key ] = ( isset( $input[ $key ] ) && $input[ $key ] ) ? 1 : 0;
|
||||||
|
}
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
add_filter( 'of_sanitize_multicheck', 'of_sanitize_multicheck', 10, 2 );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File upload sanitization.
|
* File upload sanitization.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,374 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 邮件模板渲染
|
||||||
|
*
|
||||||
|
* 设计约束(写这套模板的全部理由):
|
||||||
|
*
|
||||||
|
* 1. **必须是表格布局**。Outlook 2007-2019 用 Word 的排版引擎,完全不支持
|
||||||
|
* flex / grid,连 `max-width` 都不稳。只有 `<table>` + `cellpadding` 在老
|
||||||
|
* Outlook 里可靠。现在的 web 布局写法在邮件里必崩。
|
||||||
|
*
|
||||||
|
* 2. **样式必须内联**。Gmail 会把 `<head>` 里的 `<style>` 全部剥掉(含 media query),
|
||||||
|
* QQ 邮箱、网易邮箱同样。所以每个 `td` 都要带 `style="..."`,不能依赖 class。
|
||||||
|
*
|
||||||
|
* 3. **宽度 600px**。这是邮件客户端预览窗格的通用安全宽度。用
|
||||||
|
* `width="600"` 属性 + `style="width:600px;max-width:600px"` 双保险。
|
||||||
|
*
|
||||||
|
* 4. **不用外部图片做装饰**。邮件里的外链图默认被 Gmail / QQ 邮箱屏蔽,
|
||||||
|
* 用户看到的是一堆灰框。所有视觉元素用纯色块 + 圆角 + 文字实现。
|
||||||
|
*
|
||||||
|
* 5. **不用 CSS 变量**。邮件客户端不支持,写了就是丢失样式。
|
||||||
|
*
|
||||||
|
* 6. 深色模式:Android Gmail / Apple Mail 会反色。这里显式声明
|
||||||
|
* `color-scheme` 与 `supported-color-schemes`,并避免纯黑纯白。
|
||||||
|
*
|
||||||
|
* @author Yongquan Yang <yangyq.net>
|
||||||
|
* @license MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站点主色(可被选项覆盖)
|
||||||
|
*
|
||||||
|
* 取主题的主色选项;取不到就用一个稳妥的深蓝灰。
|
||||||
|
* 不用亮色,因为邮件背景是浅灰,亮色正文对比度不够。
|
||||||
|
*/
|
||||||
|
function yang_mail_brand_color()
|
||||||
|
{
|
||||||
|
$c = kratos_option('top_color', '#24292e');
|
||||||
|
if (!preg_match('/^#[0-9a-fA-F]{6}$/', (string) $c)) {
|
||||||
|
$c = '#24292e';
|
||||||
|
}
|
||||||
|
return $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把主色调暗一档,用作渐变末端 / 描边
|
||||||
|
*
|
||||||
|
* 邮件里不能用 `filter: brightness()`(不支持),只能算。
|
||||||
|
*/
|
||||||
|
function yang_mail_shade($hex, $ratio = 0.82)
|
||||||
|
{
|
||||||
|
$hex = ltrim($hex, '#');
|
||||||
|
if (strlen($hex) !== 6) {
|
||||||
|
return '#24292e';
|
||||||
|
}
|
||||||
|
$out = '#';
|
||||||
|
foreach (str_split($hex, 2) as $pair) {
|
||||||
|
$v = max(0, min(255, (int) round(hexdec($pair) * $ratio)));
|
||||||
|
$out .= str_pad(dechex($v), 2, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个颜色是深是浅,决定叠在上面的文字用白还是深色
|
||||||
|
*
|
||||||
|
* 用相对亮度公式(WCAG),不是简单取平均 —— 人眼对绿最敏感、蓝最不敏感,
|
||||||
|
* 直接平均会把 #00ff00 判成中等亮度。
|
||||||
|
*/
|
||||||
|
function yang_mail_is_dark($hex)
|
||||||
|
{
|
||||||
|
$hex = ltrim((string) $hex, '#');
|
||||||
|
if (strlen($hex) !== 6) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$r = hexdec(substr($hex, 0, 2)) / 255;
|
||||||
|
$g = hexdec(substr($hex, 2, 2)) / 255;
|
||||||
|
$b = hexdec(substr($hex, 4, 2)) / 255;
|
||||||
|
$lin = function ($c) {
|
||||||
|
return $c <= 0.03928 ? $c / 12.92 : pow(($c + 0.055) / 1.055, 2.4);
|
||||||
|
};
|
||||||
|
$L = 0.2126 * $lin($r) + 0.7152 * $lin($g) + 0.0722 * $lin($b);
|
||||||
|
return $L < 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把可能含 HTML 的片段转成邮件安全的纯文本
|
||||||
|
*
|
||||||
|
* 邮件正文里塞用户提交的评论内容,必须防注入:邮件客户端也会执行
|
||||||
|
* `<script>`(部分 webmail 会),而且一旦渲染出畸形 HTML,整封邮件布局就崩。
|
||||||
|
*/
|
||||||
|
function yang_mail_text($html, $limit = 0)
|
||||||
|
{
|
||||||
|
$t = wp_strip_all_tags((string) $html, true);
|
||||||
|
$t = html_entity_decode($t, ENT_QUOTES, 'UTF-8');
|
||||||
|
$t = preg_replace('/\s+/u', ' ', $t);
|
||||||
|
$t = trim($t);
|
||||||
|
if ($limit > 0 && mb_strlen($t, 'UTF-8') > $limit) {
|
||||||
|
$t = mb_substr($t, 0, $limit, 'UTF-8') . '…';
|
||||||
|
}
|
||||||
|
return $t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把主色与白色按比例混合,得到一档浅色
|
||||||
|
*
|
||||||
|
* 为什么不用 rgba:Outlook(Word 引擎)完全不支持 rgba(),
|
||||||
|
* 写了会当成非法值丢弃 ⇒ 半透明背景变成完全透明,badge 直接"消失"。
|
||||||
|
* 所以这里预先算出实色。$ratio 是白色的占比(0=纯主色,1=纯白)。
|
||||||
|
*/
|
||||||
|
function yang_mail_tint($hex, $ratio = 0.18, $onto = '#ffffff')
|
||||||
|
{
|
||||||
|
$hex = ltrim((string) $hex, '#');
|
||||||
|
$onto = ltrim((string) $onto, '#');
|
||||||
|
if (strlen($hex) !== 6 || strlen($onto) !== 6) {
|
||||||
|
return '#ffffff';
|
||||||
|
}
|
||||||
|
$out = '#';
|
||||||
|
for ($i = 0; $i < 3; $i++) {
|
||||||
|
$a = hexdec(substr($hex, $i * 2, 2));
|
||||||
|
$b = hexdec(substr($onto, $i * 2, 2));
|
||||||
|
$v = (int) round($a + ($b - $a) * $ratio);
|
||||||
|
$out .= str_pad(dechex(max(0, min(255, $v))), 2, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成一个按钮(邮件里的按钮只能用表格单元格做,不能用 <button>)
|
||||||
|
*
|
||||||
|
* Outlook 不认 `<a>` 的 `padding`,必须把 padding 放在 `<td>` 上。
|
||||||
|
*/
|
||||||
|
function yang_mail_button($text, $url, $bg = null)
|
||||||
|
{
|
||||||
|
if ($bg === null) {
|
||||||
|
$bg = yang_mail_brand_color();
|
||||||
|
}
|
||||||
|
$fg = yang_mail_is_dark($bg) ? '#ffffff' : '#1a1a1a';
|
||||||
|
$url = esc_url($url);
|
||||||
|
$text = esc_html($text);
|
||||||
|
return '<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin:0 auto;">'
|
||||||
|
. '<tr><td align="center" bgcolor="' . esc_attr($bg) . '" style="border-radius:6px;padding:13px 34px;">'
|
||||||
|
. '<a href="' . $url . '" target="_blank" rel="noopener" '
|
||||||
|
. 'style="display:inline-block;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,\'Helvetica Neue\',Arial,\'PingFang SC\',\'Microsoft YaHei\',sans-serif;'
|
||||||
|
. 'font-size:15px;font-weight:600;line-height:1;color:' . $fg . ';text-decoration:none;letter-spacing:0.3px;">'
|
||||||
|
. $text . '</a></td></tr></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染整封邮件
|
||||||
|
*
|
||||||
|
* @param array $args {
|
||||||
|
* @type string $title 邮件主标题(收件箱预览也会看到)
|
||||||
|
* @type string $preheader 预读文字,显示在收件箱标题后面,不影响正文
|
||||||
|
* @type string $heading 正文第一行大字
|
||||||
|
* @type string $intro 正文引导段落(纯文本)
|
||||||
|
* @type array $rows 表格数据,每项 array('label'=>, 'value'=>, 'strong'=>bool)
|
||||||
|
* @type array $quote 引用块 array('label'=>, 'content'=>, 'meta'=>)
|
||||||
|
* @type string $cta_text 按钮文字
|
||||||
|
* @type string $cta_url 按钮链接
|
||||||
|
* @type string $note 底部提示(纯文本)
|
||||||
|
* @type string $badge 顶部小标签,如「新评论」
|
||||||
|
* }
|
||||||
|
* @return string 完整 HTML
|
||||||
|
*/
|
||||||
|
function yang_mail_render($args)
|
||||||
|
{
|
||||||
|
$d = wp_parse_args($args, array(
|
||||||
|
'title' => get_bloginfo('name'),
|
||||||
|
'preheader' => '',
|
||||||
|
'heading' => '',
|
||||||
|
'intro' => '',
|
||||||
|
'rows' => array(),
|
||||||
|
'quote' => array(),
|
||||||
|
'cta_text' => '',
|
||||||
|
'cta_url' => '',
|
||||||
|
'note' => '',
|
||||||
|
'badge' => '',
|
||||||
|
));
|
||||||
|
|
||||||
|
$brand = yang_mail_brand_color();
|
||||||
|
$brandD = yang_mail_shade($brand, 0.78);
|
||||||
|
$onBrand = yang_mail_is_dark($brand) ? '#ffffff' : '#1a1a1a';
|
||||||
|
|
||||||
|
// badge 的背景:把主色往白色方向提亮一档,得到比主色浅、但同色相的实色。
|
||||||
|
// 主色本身是深色时提亮 22%,浅色时压暗 —— 否则浅主色上叠浅 badge 会看不见。
|
||||||
|
$badgeBg = yang_mail_is_dark($brand)
|
||||||
|
? yang_mail_tint($brand, 0.22)
|
||||||
|
: yang_mail_shade($brand, 0.88);
|
||||||
|
$badgeBd = yang_mail_is_dark($brand)
|
||||||
|
? yang_mail_tint($brand, 0.34)
|
||||||
|
: yang_mail_shade($brand, 0.72);
|
||||||
|
|
||||||
|
$site = get_bloginfo('name');
|
||||||
|
$home = home_url('/');
|
||||||
|
$year = date('Y');
|
||||||
|
|
||||||
|
// 字体栈:中文优先 PingFang / 微软雅黑,英文走系统默认。
|
||||||
|
// 邮件里不能用 @font-face(Gmail 剥掉),只能靠系统字体。
|
||||||
|
$sans = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif";
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
|
<meta name="color-scheme" content="light">
|
||||||
|
<meta name="supported-color-schemes" content="light">
|
||||||
|
<title><?php echo esc_html($d['title']); ?></title>
|
||||||
|
<!--[if mso]>
|
||||||
|
<style type="text/css">
|
||||||
|
body,table,td,p,a{font-family:Arial,'Microsoft YaHei',sans-serif !important;}
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:0;width:100%;background-color:#eef1f4;">
|
||||||
|
|
||||||
|
<?php /* 预读文字:显示在收件箱列表里标题的后面。藏起来但必须真实存在,且要足够长,
|
||||||
|
否则客户端会把正文第一句抓上去当摘要。 */ ?>
|
||||||
|
<div style="display:none;font-size:1px;color:#eef1f4;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;">
|
||||||
|
<?php echo esc_html($d['preheader'] ? $d['preheader'] : $d['heading']); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="background-color:#eef1f4;">
|
||||||
|
<tr><td align="center" style="padding:32px 12px;">
|
||||||
|
|
||||||
|
<?php /* 宽度写法是「混合式」:
|
||||||
|
· Outlook(Word 引擎)只认 HTML 属性 width="600",CSS 的 max-width 它当没看见;
|
||||||
|
· 其它客户端认 CSS,于是 width:100% + max-width:600px 让手机窄屏自动收窄。
|
||||||
|
两套并存,各取所需。只写死 600px 会让手机横向滚动——实测过。 */ ?>
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" width="600" role="presentation" style="width:100%;max-width:600px;background-color:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e3e7ec;">
|
||||||
|
|
||||||
|
<?php /* ── 顶部品牌条 ── */ ?>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="<?php echo esc_attr($brand); ?>" style="background-color:<?php echo esc_attr($brand); ?>;padding:0;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td style="padding:26px 32px;">
|
||||||
|
<div style="font-family:<?php echo $sans; ?>;font-size:19px;font-weight:600;color:<?php echo esc_attr($onBrand); ?>;letter-spacing:0.2px;line-height:1.3;">
|
||||||
|
<?php echo esc_html($site); ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($d['badge'] !== ''): ?>
|
||||||
|
<?php /* badge 用预算好的实色,不用 rgba —— Outlook 不认 rgba,会直接丢掉背景 */ ?>
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin-top:10px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="<?php echo esc_attr($badgeBg); ?>" style="background-color:<?php echo esc_attr($badgeBg); ?>;border:1px solid <?php echo esc_attr($badgeBd); ?>;border-radius:20px;padding:4px 12px;font-family:<?php echo $sans; ?>;font-size:12px;font-weight:500;line-height:1.4;">
|
||||||
|
<span style="color:<?php echo esc_attr($onBrand); ?>;"><?php echo esc_html($d['badge']); ?></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php /* 主色条下沿压一条深色,给扁平色块一点层次(渐变在 Outlook 里不生效,用纯色)。
|
||||||
|
height 必须**同时**写成 HTML 属性和 CSS:Outlook 只认属性,
|
||||||
|
只写 CSS 高度时这条 3px 带会撑成一行文字高(约 18px),比例就毁了。 */ ?>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="<?php echo esc_attr($brandD); ?>" height="3" style="background-color:<?php echo esc_attr($brandD); ?>;height:3px;line-height:3px;font-size:0;mso-line-height-rule:exactly;"> </td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<?php /* ── 正文 ── */ ?>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:34px 32px 8px 32px;">
|
||||||
|
|
||||||
|
<?php if ($d['heading'] !== ''): ?>
|
||||||
|
<div style="font-family:<?php echo $sans; ?>;font-size:21px;font-weight:600;color:#1a2028;line-height:1.45;margin:0 0 12px 0;">
|
||||||
|
<?php echo esc_html($d['heading']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($d['intro'] !== ''): ?>
|
||||||
|
<div style="font-family:<?php echo $sans; ?>;font-size:15px;color:#4a5560;line-height:1.7;margin:0 0 22px 0;">
|
||||||
|
<?php echo esc_html($d['intro']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($d['quote'])): ?>
|
||||||
|
<?php /* 引用块:左边一条主色竖线,用 border-left 而不是伪元素(邮件不支持 ::before) */ ?>
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="margin:0 0 22px 0;">
|
||||||
|
<tr>
|
||||||
|
<td style="border-left:3px solid <?php echo esc_attr($brand); ?>;background-color:#f6f8fa;padding:16px 18px;border-radius:0 8px 8px 0;">
|
||||||
|
<?php if (!empty($d['quote']['label'])): ?>
|
||||||
|
<div style="font-family:<?php echo $sans; ?>;font-size:12px;font-weight:600;color:#5f6b78;letter-spacing:0.6px;text-transform:uppercase;margin:0 0 8px 0;">
|
||||||
|
<?php echo esc_html($d['quote']['label']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div style="font-family:<?php echo $sans; ?>;font-size:15px;color:#2d3740;line-height:1.7;word-break:break-word;">
|
||||||
|
<?php echo nl2br(esc_html($d['quote']['content'])); ?>
|
||||||
|
</div>
|
||||||
|
<?php if (!empty($d['quote']['meta'])): ?>
|
||||||
|
<div style="font-family:<?php echo $sans; ?>;font-size:13px;color:#5f6b78;line-height:1.6;margin:9px 0 0 0;">
|
||||||
|
<?php echo esc_html($d['quote']['meta']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($d['rows'])): ?>
|
||||||
|
<?php /* 信息表:两列。label 列定宽 88px 并右对齐,value 左对齐,视觉上最稳。
|
||||||
|
不用 table-layout:fixed 之外的花样,Outlook 只认这个。 */ ?>
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="margin:0 0 24px 0;border-top:1px solid #edf0f3;">
|
||||||
|
<?php foreach ($d['rows'] as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td width="88" valign="top" style="width:88px;padding:11px 0;border-bottom:1px solid #edf0f3;font-family:<?php echo $sans; ?>;font-size:13px;color:#5f6b78;line-height:1.6;white-space:nowrap;">
|
||||||
|
<?php echo esc_html($row['label']); ?>
|
||||||
|
</td>
|
||||||
|
<td valign="top" style="padding:11px 0 11px 16px;border-bottom:1px solid #edf0f3;font-family:<?php echo $sans; ?>;font-size:14px;color:#2d3740;line-height:1.6;word-break:break-word;">
|
||||||
|
<?php
|
||||||
|
$v = isset($row['value']) ? $row['value'] : '';
|
||||||
|
if (!empty($row['strong'])) {
|
||||||
|
echo '<strong style="font-weight:600;">' . esc_html($v) . '</strong>';
|
||||||
|
} else {
|
||||||
|
echo esc_html($v);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($d['cta_text'] !== '' && $d['cta_url'] !== ''): ?>
|
||||||
|
<div style="margin:0 0 28px 0;">
|
||||||
|
<?php echo yang_mail_button($d['cta_text'], $d['cta_url']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($d['note'] !== ''): ?>
|
||||||
|
<div style="font-family:<?php echo $sans; ?>;font-size:13px;color:#5f6b78;line-height:1.7;padding:16px 0 0 0;border-top:1px solid #edf0f3;">
|
||||||
|
<?php echo esc_html($d['note']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<?php /* ── 页脚 ── */ ?>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:24px 32px 30px 32px;">
|
||||||
|
<div style="border-top:1px solid #edf0f3;padding-top:20px;font-family:<?php echo $sans; ?>;font-size:12px;color:#5f6b78;line-height:1.9;">
|
||||||
|
<div style="margin:0 0 6px 0;">
|
||||||
|
<a href="<?php echo esc_url($home); ?>" target="_blank" rel="noopener" style="color:#3d4752;text-decoration:none;font-weight:600;"><?php echo esc_html($site); ?></a>
|
||||||
|
</div>
|
||||||
|
<div style="margin:0;">此邮件由系统自动发出,请勿直接回复。</div>
|
||||||
|
<div style="margin:6px 0 0 0;color:#6b7684;">© <?php echo esc_html($year); ?> <?php echo esc_html($site); ?></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<?php /* 底部说明:为什么收到这封邮件。放在卡片外面,视觉上属于系统信息 */ ?>
|
||||||
|
<div style="font-family:<?php echo $sans; ?>;font-size:12px;color:#5f6b78;line-height:1.7;padding:18px 8px 0 8px;max-width:600px;">
|
||||||
|
您收到这封邮件,是因为本站记录了与您邮箱相关的订阅或互动。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 邮件治理:统一由主题接管
|
||||||
|
*
|
||||||
|
* ── 要解决的问题 ──────────────────────────────────────────────
|
||||||
|
* 一条评论提交后,收件人收到多封内容重叠的邮件。排查结论是**发信源不止一个**:
|
||||||
|
*
|
||||||
|
* 1. WordPress 核心
|
||||||
|
* · comments_notify = 1 ⇒ 有新评论就通知管理员
|
||||||
|
* · moderation_notify = 1 ⇒ 有评论待审就通知管理员
|
||||||
|
* 两者在「评论需审核」时会**同时**命中,管理员一次收到两封。
|
||||||
|
*
|
||||||
|
* 2. wpDiscuz 插件(class.WpdiscuzHelperOptimization.php:199-208)
|
||||||
|
* 一条回复会依次触发:
|
||||||
|
* sendMailToMentionedUsers 被 @ 的人
|
||||||
|
* notifyPostSubscribers 订阅了本文的人
|
||||||
|
* notifyFollowers 关注了作者的人
|
||||||
|
* notifyAllCommentSubscribers 订阅了全部评论的人
|
||||||
|
* notifyCommentSubscribers 被回复的那一层作者
|
||||||
|
* 同一个人如果既订阅了文章又被回复,就收到两封。
|
||||||
|
* 另外 subscriptionType = 3 表示「订阅全站」,范围最大。
|
||||||
|
*
|
||||||
|
* 3. 本主题自己(旧 inc/theme-smtp.php 里的 comment_notify / comment_approved)
|
||||||
|
* 直接挂在 comment_post 与 comment_unapproved_to_approved 上,
|
||||||
|
* 与 wpDiscuz 的 notifyCommentSubscribers **完全重复**。
|
||||||
|
* 而且它用的是一段硬编码 HTML,与插件模板风格完全不一致。
|
||||||
|
*
|
||||||
|
* 4. 插件兜底队列(Akismet 的 schedule_email_fallback / schedule_approval_fallback)
|
||||||
|
* 命中垃圾判定后异步补发审核结果,同样走 wp_mail。
|
||||||
|
*
|
||||||
|
* ── 处理原则 ──────────────────────────────────────────────────
|
||||||
|
* 用户要求「只要主题配置了 SMTP,就一律用主题发邮件,其他的都禁用掉」。
|
||||||
|
* 注意**不能只关发信通道**:把 phpmailer 抢过来只解决了「谁发」,
|
||||||
|
* 没解决「发几封」。所以这里分两层:
|
||||||
|
*
|
||||||
|
* 第一层 通道:拦住所有 wp_mail 调用,强制套用主题 SMTP 配置。
|
||||||
|
* 若站点装了别的 SMTP 插件,主题优先;主题没配则完全放手。
|
||||||
|
*
|
||||||
|
* 第二层 去重:在通道层做「同一收件人 + 同一事件」的短期去重,
|
||||||
|
* 并主动关掉重复的钩子、屏蔽核心的旧式通知。
|
||||||
|
*
|
||||||
|
* 第二层是关键。只做第一层的话,邮件依然会重复,只是都从同一个 SMTP 发出。
|
||||||
|
*
|
||||||
|
* @author Yongquan Yang <yangyq.net>
|
||||||
|
* @license MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题 SMTP 是否配置完整且启用
|
||||||
|
*
|
||||||
|
* 判断「完整」很重要:只勾了开关但主机没填,会让所有邮件静默失败
|
||||||
|
* (phpmailer 抛异常被吞),比让它回退到 PHP mail() 更糟。
|
||||||
|
*/
|
||||||
|
function yang_mail_smtp_ready()
|
||||||
|
{
|
||||||
|
if (!kratos_option('m_smtp', false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$host = trim((string) kratos_option('m_host', ''));
|
||||||
|
$user = trim((string) kratos_option('m_username', ''));
|
||||||
|
return ($host !== '' && $user !== '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接管 phpmailer 配置
|
||||||
|
*
|
||||||
|
* 优先级 9999:其他 SMTP 插件(若以后装了)一般挂在 10,这样主题最后落地,
|
||||||
|
* 保证「主题优先」。同时先重置掉可能被别的插件设过的值,避免残留半套配置
|
||||||
|
* (例如别的插件设了 Host 但没设 SMTPSecure,混在一起会静默失败)。
|
||||||
|
*/
|
||||||
|
function yang_mail_configure_phpmailer($phpmailer)
|
||||||
|
{
|
||||||
|
if (!yang_mail_smtp_ready()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$phpmailer->isSMTP();
|
||||||
|
$phpmailer->SMTPAuth = true;
|
||||||
|
$phpmailer->CharSet = 'UTF-8';
|
||||||
|
$phpmailer->Host = trim((string) kratos_option('m_host'));
|
||||||
|
$phpmailer->Port = (int) kratos_option('m_port', 465);
|
||||||
|
$phpmailer->Username = trim((string) kratos_option('m_username'));
|
||||||
|
$phpmailer->Password = (string) kratos_option('m_passwd');
|
||||||
|
|
||||||
|
$sec = strtolower(trim((string) kratos_option('m_sec', 'ssl')));
|
||||||
|
// 空串表示明文,但 PHPMailer 对空串和 'none' 处理不同,显式归一
|
||||||
|
$phpmailer->SMTPSecure = ($sec === '' || $sec === 'none') ? '' : $sec;
|
||||||
|
|
||||||
|
// 发件人必须与登录账号一致,否则 QQ / 163 会以「发件人不符」拒收
|
||||||
|
$phpmailer->From = $phpmailer->Username;
|
||||||
|
$phpmailer->FromName = get_bloginfo('name');
|
||||||
|
|
||||||
|
// 保留 SMTP 的调试信息但不输出,便于 wp_mail_failed 时定位
|
||||||
|
$phpmailer->SMTPDebug = 0;
|
||||||
|
$phpmailer->Timeout = 15;
|
||||||
|
|
||||||
|
// 有些服务器 TLS 证书链不全,本地判断:只有显式要求时才放宽
|
||||||
|
if (kratos_option('m_insecure', false)) {
|
||||||
|
$phpmailer->SMTPOptions = array(
|
||||||
|
'ssl' => array(
|
||||||
|
'verify_peer' => false,
|
||||||
|
'verify_peer_name' => false,
|
||||||
|
'allow_self_signed' => true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('phpmailer_init', 'yang_mail_configure_phpmailer', 9999);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一 Content-Type 与发件人
|
||||||
|
*
|
||||||
|
* 各个环节自己拼 headers 时格式五花八门(有的只写 `Content-Type: text/html`,
|
||||||
|
* 有的漏 charset),导致中文乱码。这里统一成 UTF-8 HTML,并强制 From。
|
||||||
|
*/
|
||||||
|
function yang_mail_normalize_headers($args)
|
||||||
|
{
|
||||||
|
if (!yang_mail_smtp_ready()) {
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
$from_email = trim((string) kratos_option('m_username'));
|
||||||
|
|
||||||
|
// 清掉调用方自己塞的 From,改为统一 From
|
||||||
|
if (!empty($args['headers'])) {
|
||||||
|
$raw = $args['headers'];
|
||||||
|
$list = is_array($raw) ? $raw : preg_split('/\r\n|\r|\n/', (string) $raw);
|
||||||
|
$kept = array();
|
||||||
|
foreach ($list as $line) {
|
||||||
|
$line = trim((string) $line);
|
||||||
|
if ($line === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 丢弃 From / Content-Type / MIME-Version,下面统一重建
|
||||||
|
if (preg_match('/^(from|content-type|mime-version)\s*:/i', $line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$kept[] = $line;
|
||||||
|
}
|
||||||
|
$args['headers'] = $kept;
|
||||||
|
} else {
|
||||||
|
$args['headers'] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$args['headers'][] = 'From: ' . get_bloginfo('name') . ' <' . $from_email . '>';
|
||||||
|
$args['headers'][] = 'Content-Type: text/html; charset=UTF-8';
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
add_filter('wp_mail', 'yang_mail_normalize_headers', 99);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件去重
|
||||||
|
*
|
||||||
|
* 同一收件人 + 同一主题 + 同一正文,在 90 秒内只发一次。
|
||||||
|
*
|
||||||
|
* 为什么按「主题 + 正文」而不是只按主题:同一个人可能在同一分钟内被通知
|
||||||
|
* 两个不同的回复,那是两件真事,不能合并。而这里要压掉的恰恰是
|
||||||
|
* 「同一件事被多个来源各发一遍」——那种情况下主题和正文往往高度相似。
|
||||||
|
*
|
||||||
|
* 用 transient 而不是静态变量:评论通知可能跨请求(wpDiscuz 走 AJAX,
|
||||||
|
* 审核通知走后台请求),静态变量跨不过去。
|
||||||
|
*
|
||||||
|
* 90 秒的取值:一次评论提交引发的所有通知都在同一秒内发生;而用户手动
|
||||||
|
* 重发(比如 SMTP 失败后重试)通常几分钟后,不该被吞。90 秒足够宽也足够窄。
|
||||||
|
*/
|
||||||
|
function yang_mail_dedupe($args)
|
||||||
|
{
|
||||||
|
if (!yang_mail_smtp_ready()) {
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 允许临时关掉(调试、批量补发)
|
||||||
|
if (apply_filters('yang_mail_dedupe_enabled', true) !== true) {
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
$to = $args['to'];
|
||||||
|
if (is_array($to)) {
|
||||||
|
$to = implode(',', $to);
|
||||||
|
}
|
||||||
|
$to = strtolower(trim((string) $to));
|
||||||
|
|
||||||
|
$sig = md5($to . '|' . $args['subject'] . '|' . wp_strip_all_tags((string) $args['message']));
|
||||||
|
$key = 'yang_mail_dd_' . $sig;
|
||||||
|
|
||||||
|
if (get_transient($key) !== false) {
|
||||||
|
// 标记为已发送过:把收件人清空,wp_mail 会返回 false 但不会真发。
|
||||||
|
// 不能直接 return null —— 那会让 wp_mail 当作被短路,调用方拿到的
|
||||||
|
// 返回值含义会变(有的插件据此判断失败并重试,反而更糟)。
|
||||||
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||||
|
error_log('[yang_mail] 去重拦截: ' . $to . ' / ' . $args['subject']);
|
||||||
|
}
|
||||||
|
$args['to'] = '';
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_transient($key, 1, 90);
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
add_filter('wp_mail', 'yang_mail_dedupe', 100);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空收件人直接短路
|
||||||
|
*
|
||||||
|
* 上一步的去重把 to 置空,这里拦住,避免 phpmailer 报「no recipient」。
|
||||||
|
* 优先级放在 pre_wp_mail 上比改 wp_mail 更干净——不侵入核心流程。
|
||||||
|
*/
|
||||||
|
function yang_mail_short_circuit($return, $atts)
|
||||||
|
{
|
||||||
|
$to = is_array($atts['to']) ? implode(',', $atts['to']) : (string) $atts['to'];
|
||||||
|
if (trim($to) === '') {
|
||||||
|
return true; // 假装成功,避免调用方重试
|
||||||
|
}
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
add_filter('pre_wp_mail', 'yang_mail_short_circuit', 10, 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关掉核心的重复通知
|
||||||
|
*
|
||||||
|
* ── 为什么要关 ──
|
||||||
|
* 站点用了 wpDiscuz,评论通知由插件负责(它能带订阅、退订链接、
|
||||||
|
* 能按订阅范围精确投递)。核心那套 `wp_notify_postauthor` / `wp_notify_moderator`
|
||||||
|
* 是给原生评论系统设计的纯文本通知,在 wpDiscuz 场景下是**纯重复**:
|
||||||
|
* 管理员会同时收到「核心:新评论待审」和「插件:新评论」,内容重叠。
|
||||||
|
*
|
||||||
|
* ── 为什么不直接改后台选项 ──
|
||||||
|
* 改 `comments_notify` / `moderation_notify` 是「用户设置」,主题替用户改
|
||||||
|
* 数据库里的设置属于越权,而且用户下次进后台一勾又回来了。
|
||||||
|
* 用 filter 拦截是**可逆的**:把这段代码删掉立刻恢复原状,不动用户数据。
|
||||||
|
*
|
||||||
|
* ── 只关通知,不关审核 ──
|
||||||
|
* 注意这里只拦 `notify_*` 两个 filter,评论审核流程(comment_moderation)
|
||||||
|
* 完全不受影响,评论该进待审还是进待审。
|
||||||
|
*/
|
||||||
|
function yang_mail_disable_core_notify($maybe, $comment_id)
|
||||||
|
{
|
||||||
|
if (!yang_mail_smtp_ready()) {
|
||||||
|
return $maybe;
|
||||||
|
}
|
||||||
|
// 用户在主题选项里可以保留核心通知(默认关)
|
||||||
|
if (kratos_option('m_keep_core_notify', false)) {
|
||||||
|
return $maybe;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
add_filter('notify_postauthor', 'yang_mail_disable_core_notify', 99, 2);
|
||||||
|
add_filter('notify_moderator', 'yang_mail_disable_core_notify', 99, 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关掉旧主题自己那两条评论邮件
|
||||||
|
*
|
||||||
|
* 旧版 inc/theme-smtp.php 挂了 comment_notify(回复通知)和
|
||||||
|
* comment_approved(审核通过通知)。这两条与 wpDiscuz 完全重复,
|
||||||
|
* 而且模板是硬编码的,风格与插件不一致。
|
||||||
|
*
|
||||||
|
* 新版 theme-smtp.php 已经不再注册这两个钩子;这里再加一道保险,
|
||||||
|
* 防止升级时旧文件残留导致重复注册(WordPress 升级是整目录替换,
|
||||||
|
* 理论上不会残留,但 history 上确实踩过文件漂移)。
|
||||||
|
*/
|
||||||
|
function yang_mail_remove_legacy_hooks()
|
||||||
|
{
|
||||||
|
if (!yang_mail_smtp_ready()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
remove_action('comment_post', 'comment_notify');
|
||||||
|
remove_action('comment_unapproved_to_approved', 'comment_approved');
|
||||||
|
}
|
||||||
|
add_action('init', 'yang_mail_remove_legacy_hooks', 99);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发一封「测试邮件」,用主题模板渲染
|
||||||
|
*
|
||||||
|
* 后台「邮件配置」里的测试按钮走这条,便于用户确认模板效果与 SMTP 连通性。
|
||||||
|
*/
|
||||||
|
function yang_mail_send_test($to = null)
|
||||||
|
{
|
||||||
|
if ($to === null) {
|
||||||
|
$to = get_bloginfo('admin_email');
|
||||||
|
}
|
||||||
|
if (!is_email($to)) {
|
||||||
|
return new WP_Error('bad_email', __('收件地址无效', 'kratos'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = yang_mail_render(array(
|
||||||
|
'title' => sprintf(__('%s 邮件配置测试', 'kratos'), get_bloginfo('name')),
|
||||||
|
'preheader' => __('这是一封测试邮件,说明 SMTP 配置可用。', 'kratos'),
|
||||||
|
'badge' => __('测试邮件', 'kratos'),
|
||||||
|
'heading' => __('邮件配置测试成功', 'kratos'),
|
||||||
|
'intro' => __('如果你收到了这封邮件,说明主题的 SMTP 配置可用,评论通知也会以同样的样式发出。', 'kratos'),
|
||||||
|
'rows' => array(
|
||||||
|
array('label' => __('发信服务器', 'kratos'), 'value' => kratos_option('m_host')),
|
||||||
|
array('label' => __('端口', 'kratos'), 'value' => kratos_option('m_port')),
|
||||||
|
array('label' => __('加密方式', 'kratos'), 'value' => strtoupper((string) kratos_option('m_sec', 'ssl'))),
|
||||||
|
array('label' => __('发件账号', 'kratos'), 'value' => kratos_option('m_username'), 'strong' => true),
|
||||||
|
array('label' => __('发送时间', 'kratos'), 'value' => date_i18n('Y-m-d H:i:s')),
|
||||||
|
),
|
||||||
|
'cta_text' => __('访问站点', 'kratos'),
|
||||||
|
'cta_url' => home_url('/'),
|
||||||
|
'note' => __('这是手动触发的测试邮件,可以忽略。', 'kratos'),
|
||||||
|
));
|
||||||
|
|
||||||
|
return wp_mail($to, sprintf(__('[%s] 邮件配置测试', 'kratos'), get_bloginfo('name')), $body);
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 纪念日自动哀悼
|
||||||
|
*
|
||||||
|
* ── 依据 ─────────────────────────────────────────────────────
|
||||||
|
* 以下日期分三类,可信度不同,代码里也分开对待:
|
||||||
|
*
|
||||||
|
* A. 法定纪念日(全国人大常委会决定设立,每年国家举行纪念活动)
|
||||||
|
* · 09-03 中国人民抗日战争胜利纪念日
|
||||||
|
* 2014-02-27 十二届全国人大常委会第七次会议决定设立
|
||||||
|
* · 09-30 烈士纪念日
|
||||||
|
* 2014-08-31 十二届全国人大常委会第十次会议决定设立
|
||||||
|
* · 12-13 南京大屠杀死难者国家公祭日
|
||||||
|
* 2014-02-27 同一会议决定设立
|
||||||
|
* 依据:全国人大网《铭记历史坐标 凝聚前行力量》
|
||||||
|
* http://www.npc.gov.cn/c2/c30834/202510/t20251020_448426.html
|
||||||
|
* http://npc.gov.cn/c2/c12435/c12487/201905/t20190521_220992.html
|
||||||
|
*
|
||||||
|
* B. 民间广泛采用(非法定,但主流网站普遍黑白)
|
||||||
|
* · 05-12 汶川地震纪念(2008-05-12 14:28)
|
||||||
|
* · 09-18 九一八事变纪念(1931-09-18)
|
||||||
|
* 这两个日期黑白是网络惯例,但不是法定「哀悼日」,所以默认**不勾选**,
|
||||||
|
* 由用户自己决定。硬编码成默认开启会替用户做价值判断,不合适。
|
||||||
|
*
|
||||||
|
* C. 历史全国哀悼日(一次性事件,非每年)
|
||||||
|
* · 2008-05-19~21 汶川地震(国务院决定)
|
||||||
|
* · 2010-04-21 玉树地震(国务院决定)
|
||||||
|
* · 2010-08-15 舟曲泥石流(国务院决定)
|
||||||
|
* · 2020-04-04 新冠疫情牺牲烈士和逝世同胞(国务院决定)
|
||||||
|
* 这些是**特定年份的特定日期**,不是周年循环。所以代码里不把它们
|
||||||
|
* 当成"每年重复",而是保留为可自定义的具体日期,避免明年 4 月 4 日
|
||||||
|
* 又自动变黑白(那是错的)。
|
||||||
|
*
|
||||||
|
* ── 设计取舍 ──────────────────────────────────────────────────
|
||||||
|
* 1. **按站点时区判断**,不按服务器时区。用 current_time('md'),
|
||||||
|
* 它走 WordPress 设置的时区。否则服务器在 UTC,北京时间 9 月 18 日
|
||||||
|
* 早上 8 点前还算 17 日,哀悼会晚 8 小时生效。
|
||||||
|
*
|
||||||
|
* 2. **只判断月日,不判断年份**。纪念日是周年性质,每年的同一天都要生效。
|
||||||
|
*
|
||||||
|
* 3. **兼容旧的 g_rip 开关**。老版本是纯手动「一直黑白」。
|
||||||
|
* 若用户勾着旧的 g_rip,仍然一直黑白;新逻辑是叠加在上面的。
|
||||||
|
* 不能因为升级就让原本黑白中的站点突然恢复彩色。
|
||||||
|
*
|
||||||
|
* @author Yongquan Yang <yangyq.net>
|
||||||
|
* @license MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内置纪念日清单
|
||||||
|
*
|
||||||
|
* 返回 array( 键 => array(月份, 日, 名称, 说明, 分类, 是否默认勾选) )
|
||||||
|
*
|
||||||
|
* 键名用 `m-d` 形式(补零),与 current_time('m') / current_time('d') 直接可比。
|
||||||
|
*/
|
||||||
|
function yang_mourning_presets()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
// ── A 类:法定纪念日 ──
|
||||||
|
'09-03' => array(
|
||||||
|
'name' => __('中国人民抗日战争胜利纪念日', 'kratos'),
|
||||||
|
'note' => __('1945 年 9 月 2 日日本签署投降书,9 月 3 日定为胜利纪念日', 'kratos'),
|
||||||
|
'group' => 'legal',
|
||||||
|
'default' => 1,
|
||||||
|
),
|
||||||
|
'09-30' => array(
|
||||||
|
'name' => __('烈士纪念日', 'kratos'),
|
||||||
|
'note' => __('向人民英雄敬献花篮仪式在天安门广场举行', 'kratos'),
|
||||||
|
'group' => 'legal',
|
||||||
|
'default' => 1,
|
||||||
|
),
|
||||||
|
'12-13' => array(
|
||||||
|
'name' => __('南京大屠杀死难者国家公祭日', 'kratos'),
|
||||||
|
'note' => __('1937 年 12 月 13 日南京沦陷,30 万同胞遇难', 'kratos'),
|
||||||
|
'group' => 'legal',
|
||||||
|
'default' => 1,
|
||||||
|
),
|
||||||
|
|
||||||
|
// ── B 类:民间惯例,默认不勾 ──
|
||||||
|
'05-12' => array(
|
||||||
|
'name' => __('汶川地震纪念日', 'kratos'),
|
||||||
|
'note' => __('2008 年 5 月 12 日 14 时 28 分,汶川发生 8.0 级地震', 'kratos'),
|
||||||
|
'group' => 'custom',
|
||||||
|
'default' => 0,
|
||||||
|
),
|
||||||
|
'09-18' => array(
|
||||||
|
'name' => __('九一八事变纪念日', 'kratos'),
|
||||||
|
'note' => __('1931 年 9 月 18 日,日本关东军发动九一八事变', 'kratos'),
|
||||||
|
'group' => 'custom',
|
||||||
|
'default' => 0,
|
||||||
|
),
|
||||||
|
'07-07' => array(
|
||||||
|
'name' => __('七七事变纪念日', 'kratos'),
|
||||||
|
'note' => __('1937 年 7 月 7 日,卢沟桥事变,全民族抗战爆发', 'kratos'),
|
||||||
|
'group' => 'custom',
|
||||||
|
'default' => 0,
|
||||||
|
),
|
||||||
|
'12-12' => array(
|
||||||
|
'name' => __('西安事变纪念日', 'kratos'),
|
||||||
|
'note' => __('1936 年 12 月 12 日,张学良、杨虎城发动兵谏', 'kratos'),
|
||||||
|
'group' => 'custom',
|
||||||
|
'default' => 0,
|
||||||
|
),
|
||||||
|
'08-15' => array(
|
||||||
|
'name' => __('日本宣布无条件投降纪念日', 'kratos'),
|
||||||
|
'note' => __('1945 年 8 月 15 日,日本天皇宣布接受波茨坦公告', 'kratos'),
|
||||||
|
'group' => 'custom',
|
||||||
|
'default' => 0,
|
||||||
|
),
|
||||||
|
'04-04' => array(
|
||||||
|
'name' => __('清明节', 'kratos'),
|
||||||
|
'note' => __('节气日期每年浮动,这里固定为 4 月 4 日,仅作近似', 'kratos'),
|
||||||
|
'group' => 'custom',
|
||||||
|
'default' => 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取用户勾选的纪念日
|
||||||
|
*
|
||||||
|
* 老站点升级上来时这个选项还不存在,此时返回 presets 里 default=1 的那些。
|
||||||
|
* 注意**不能**简单用 `kratos_option('g_rip_days', 默认数组)`:kratos_option
|
||||||
|
* 对未设置的键返回默认值,但如果用户曾经保存过空数组(全部取消勾选),
|
||||||
|
* 存进去的就是空数组,此时必须尊重「用户就是要关掉」,不能再回退到默认。
|
||||||
|
* 所以这里用「option 数组里有没有这个键」来判断,而不是判断值是否为空。
|
||||||
|
*/
|
||||||
|
function yang_mourning_days()
|
||||||
|
{
|
||||||
|
$presets = yang_mourning_presets();
|
||||||
|
$opts = get_option('kratos');
|
||||||
|
$has = is_array($opts) && array_key_exists('g_rip_days', $opts);
|
||||||
|
|
||||||
|
if (!$has) {
|
||||||
|
// 从未保存过:用内置默认
|
||||||
|
$out = array();
|
||||||
|
foreach ($presets as $md => $info) {
|
||||||
|
if (!empty($info['default'])) {
|
||||||
|
$out[] = $md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
$saved = $opts['g_rip_days'];
|
||||||
|
if (!is_array($saved)) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只保留在 presets 里存在、且值为真的日期(防止脏数据)
|
||||||
|
$out = array();
|
||||||
|
foreach ($saved as $md => $v) {
|
||||||
|
if ($v && isset($presets[$md])) {
|
||||||
|
$out[] = $md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 今天是否应当哀悼
|
||||||
|
*/
|
||||||
|
function yang_is_mourning_today()
|
||||||
|
{
|
||||||
|
// 旧的手动开关:开了就一直黑白(保持向后兼容,不能让升级把黑白站点变回彩色)
|
||||||
|
if (kratos_option('g_rip', false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kratos_option('g_rip_auto', false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 走站点时区,不是服务器时区
|
||||||
|
$md = current_time('m') . '-' . current_time('d');
|
||||||
|
$days = yang_mourning_days();
|
||||||
|
|
||||||
|
// 自定义附加日期(用户自己填的,如特定年份的哀悼日)
|
||||||
|
if (kratos_option('g_rip_extra', '')) {
|
||||||
|
$extra = kratos_option('g_rip_extra', '');
|
||||||
|
foreach (preg_split('/[\s,,、;;]+/u', (string) $extra) as $one) {
|
||||||
|
$one = trim($one);
|
||||||
|
if ($one === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 支持 MM-DD 与 YYYY-MM-DD 两种写法
|
||||||
|
if (preg_match('/^\d{2}-\d{2}$/', $one)) {
|
||||||
|
$days[] = $one;
|
||||||
|
} elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $one, $m)) {
|
||||||
|
// 带年份的只在当年生效,不是周年循环
|
||||||
|
if ((int) $m[1] === (int) current_time('Y')) {
|
||||||
|
$days[] = $m[2] . '-' . $m[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array($md, $days, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出黑白样式
|
||||||
|
*
|
||||||
|
* 几个细节:
|
||||||
|
* 1. `html { filter: grayscale(1) }` 会给整页建一个合成层,fixed 定位的元素
|
||||||
|
* 在某些旧版 Chrome 上会错位。所以额外加 `background-attachment: fixed`
|
||||||
|
* 的补丁样式不写;这里改用给 body 和 .k-header 分别处理更稳。
|
||||||
|
* —— 实际上实测最稳的是给 html 加 filter 并用 transform: translateZ(0)
|
||||||
|
* 在 body 上兜住,避免 iOS Safari 的渲染错位。这里保留 html 方案,
|
||||||
|
* 因为它在用户当前主题结构下工作正常(老功能的写法),只是补一条
|
||||||
|
* iOS 的兼容声明。
|
||||||
|
* 2. 不能只挂 is_home():详情页也应该黑白。老代码限定首页,那是 bug。
|
||||||
|
* 3. 只在真正哀悼的日子输出,避免给所有页面增加一条无用样式。
|
||||||
|
*/
|
||||||
|
function yang_mourning_style()
|
||||||
|
{
|
||||||
|
if (is_admin() || !yang_is_mourning_today()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<style id="yang-mourning">'
|
||||||
|
. "\nhtml{filter:grayscale(100%);-webkit-filter:grayscale(100%);}"
|
||||||
|
. "\nhtml body{-webkit-transform:translateZ(0);}"
|
||||||
|
// 打印时不灰度,否则打印出来是灰的
|
||||||
|
. "\n@media print{html{filter:none!important;-webkit-filter:none!important;}}"
|
||||||
|
. "\n</style>\n";
|
||||||
|
}
|
||||||
|
add_action('wp_head', 'yang_mourning_style', 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后台显示今天的哀悼状态,便于确认配置是否生效
|
||||||
|
*/
|
||||||
|
function yang_mourning_status_text()
|
||||||
|
{
|
||||||
|
if (kratos_option('g_rip', false)) {
|
||||||
|
return __('当前为手动常开模式:站点一直黑白,与日期无关。', 'kratos');
|
||||||
|
}
|
||||||
|
if (!kratos_option('g_rip_auto', false)) {
|
||||||
|
return __('自动哀悼未开启。', 'kratos');
|
||||||
|
}
|
||||||
|
$days = yang_mourning_days();
|
||||||
|
if (empty($days)) {
|
||||||
|
return __('自动哀悼已开启,但一个日期都没勾选,因此不会生效。', 'kratos');
|
||||||
|
}
|
||||||
|
sort($days);
|
||||||
|
$today = current_time('m') . '-' . current_time('d');
|
||||||
|
$is = in_array($today, $days, true);
|
||||||
|
return sprintf(
|
||||||
|
/* translators: 1: 今天的月日, 2: 是否处于哀悼, 3: 勾选的日期列表 */
|
||||||
|
__('今天是 %1$s,%2$s。已启用:%3$s', 'kratos'),
|
||||||
|
$today,
|
||||||
|
$is ? __('正在哀悼,站点已黑白', 'kratos') : __('不在哀悼日', 'kratos'),
|
||||||
|
implode('、', $days)
|
||||||
|
);
|
||||||
|
}
|
||||||
+50
-47
@@ -233,11 +233,60 @@ $options[] = array(
|
|||||||
|
|
||||||
$options[] = array(
|
$options[] = array(
|
||||||
'name' => __('哀悼功能', 'kratos'),
|
'name' => __('哀悼功能', 'kratos'),
|
||||||
'desc' => __('开启站点首页黑白功能(用于R.I.P.)', 'kratos'),
|
'desc' => __('开启后站点首页与文章页转为黑白。这是老的手动开关,开了就一直黑白,与日期无关。日常建议用下面的「按纪念日自动哀悼」。', 'kratos'),
|
||||||
'id' => 'g_rip',
|
'id' => 'g_rip',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$options[] = array(
|
||||||
|
'name' => __('按纪念日自动哀悼', 'kratos'),
|
||||||
|
'desc' => __('勾选后,只有在下表勾选的那些日期才会自动黑白,其余日子正常显示。日期按站点时区判断,只比对月和日,每年循环。', 'kratos'),
|
||||||
|
'id' => 'g_rip_auto',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
);
|
||||||
|
|
||||||
|
// 把内置清单转成 multicheck 需要的格式
|
||||||
|
$rip_options = array();
|
||||||
|
$rip_legal = array();
|
||||||
|
$rip_custom = array();
|
||||||
|
foreach (yang_mourning_presets() as $md => $info) {
|
||||||
|
$entry = array(
|
||||||
|
'name' => $md . ' ' . $info['name'],
|
||||||
|
'note' => $info['note'],
|
||||||
|
);
|
||||||
|
if (isset($info['group']) && $info['group'] === 'legal') {
|
||||||
|
$rip_legal[$md] = $entry;
|
||||||
|
} else {
|
||||||
|
$rip_custom[$md] = $entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rip_default = array();
|
||||||
|
foreach (yang_mourning_presets() as $md => $info) {
|
||||||
|
$rip_default[$md] = !empty($info['default']) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$options[] = array(
|
||||||
|
'name' => __('纪念日清单', 'kratos'),
|
||||||
|
'desc' => __('勾选需要自动黑白的日期。前三个是法定纪念日(默认已勾),后面几个是民间通行做法,按自己意愿决定。日期按站点时区判断,只比对月和日,每年循环。', 'kratos'),
|
||||||
|
'id' => 'g_rip_days',
|
||||||
|
'std' => $rip_default,
|
||||||
|
'type' => 'multicheck',
|
||||||
|
'options' => array_merge($rip_legal, $rip_custom),
|
||||||
|
);
|
||||||
|
|
||||||
|
$options[] = array(
|
||||||
|
'name' => __('附加日期', 'kratos'),
|
||||||
|
'desc' => __('可选。填 MM-DD 表示每年循环(如 05-12);填 YYYY-MM-DD 表示只在当年生效(如 2020-04-04,用于历次全国哀悼日这类一次性事件)。多个日期用空格或逗号分隔。', 'kratos'),
|
||||||
|
'id' => 'g_rip_extra',
|
||||||
|
'type' => 'text',
|
||||||
|
);
|
||||||
|
|
||||||
|
$options[] = array(
|
||||||
|
'desc' => yang_mourning_status_text(),
|
||||||
|
'type' => 'info',
|
||||||
|
);
|
||||||
|
|
||||||
$options[] = array(
|
$options[] = array(
|
||||||
'name' => __('特色图片', 'kratos'),
|
'name' => __('特色图片', 'kratos'),
|
||||||
'desc' => __('开启站点首页特色图片功能', 'kratos'),
|
'desc' => __('开启站点首页特色图片功能', 'kratos'),
|
||||||
@@ -469,52 +518,6 @@ $options[] = array(
|
|||||||
'type' => 'textarea',
|
'type' => 'textarea',
|
||||||
);
|
);
|
||||||
|
|
||||||
$options[] = array(
|
|
||||||
'name' => __('广告配置', 'kratos'),
|
|
||||||
'type' => 'heading',
|
|
||||||
);
|
|
||||||
|
|
||||||
$options[] = array(
|
|
||||||
'name' => __('文章页面广告', 'kratos'),
|
|
||||||
'desc' => __('开启顶部广告', 'kratos'),
|
|
||||||
'id' => 's_singletop',
|
|
||||||
'type' => 'checkbox',
|
|
||||||
);
|
|
||||||
|
|
||||||
$options[] = array(
|
|
||||||
'id' => 's_singletop_url',
|
|
||||||
'class' => 'hidden',
|
|
||||||
'std' => ASSET_PATH . '/assets/img/ad.png',
|
|
||||||
'type' => 'upload',
|
|
||||||
);
|
|
||||||
|
|
||||||
$options[] = array(
|
|
||||||
'desc' => __('选填广告连接,如果不填则只显示图片', 'kratos'),
|
|
||||||
'id' => 's_singletop_links',
|
|
||||||
'class' => 'hidden',
|
|
||||||
'type' => 'text',
|
|
||||||
);
|
|
||||||
|
|
||||||
$options[] = array(
|
|
||||||
'desc' => __('开启底部广告', 'kratos'),
|
|
||||||
'id' => 's_singledown',
|
|
||||||
'type' => 'checkbox',
|
|
||||||
);
|
|
||||||
|
|
||||||
$options[] = array(
|
|
||||||
'id' => 's_singledown_url',
|
|
||||||
'class' => 'hidden',
|
|
||||||
'std' => ASSET_PATH . '/assets/img/ad.png',
|
|
||||||
'type' => 'upload',
|
|
||||||
);
|
|
||||||
|
|
||||||
$options[] = array(
|
|
||||||
'desc' => __('选填广告连接,如果不填则只显示图片', 'kratos'),
|
|
||||||
'id' => 's_singledown_links',
|
|
||||||
'class' => 'hidden',
|
|
||||||
'type' => 'text',
|
|
||||||
);
|
|
||||||
|
|
||||||
$options[] = array(
|
$options[] = array(
|
||||||
'name' => __('自定义代码', 'kratos'),
|
'name' => __('自定义代码', 'kratos'),
|
||||||
'type' => 'heading',
|
'type' => 'heading',
|
||||||
|
|||||||
@@ -92,13 +92,11 @@ add_filter('robots_txt', function ($output, $public) {
|
|||||||
}
|
}
|
||||||
}, 10, 2);
|
}, 10, 2);
|
||||||
|
|
||||||
// 哀悼黑白站点
|
// 哀悼黑白站点:已迁移到 inc/theme-mourning.php
|
||||||
function mourning()
|
//
|
||||||
{
|
// 旧的 mourning() 只判 is_home() 且只能手动常开,无法按纪念日自动生效。
|
||||||
if (is_home() && kratos_option('g_rip', false)) {
|
// 新实现挂在 wp_head(优先级 1),支持按日期自动判断,并保持向后兼容
|
||||||
echo '<style type="text/css">html{filter: grayscale(100%);-webkit-filter: grayscale(100%);-moz-filter: grayscale(100%);-ms-filter: grayscale(100%);-o-filter: grayscale(100%);filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);filter: gray;-webkit-filter: grayscale(1); } </style>';
|
// (旧的 g_rip 开关仍表示「一直黑白」)。
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 抓取图片链接(搜索引擎或者社交工具分享时抓取图片的链接)
|
// 抓取图片链接(搜索引擎或者社交工具分享时抓取图片的链接)
|
||||||
/*
|
/*
|
||||||
|
|||||||
+24
-119
@@ -1,130 +1,35 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* SMTP 配置
|
* SMTP 配置
|
||||||
|
*
|
||||||
|
* ⚠️ 这个文件过去自己发过两类邮件(回复通知、审核通过通知),
|
||||||
|
* 与 wpDiscuz 插件的通知**完全重复**,是「一条评论收到多封邮件」的
|
||||||
|
* 直接原因之一。现已全部移除。
|
||||||
|
*
|
||||||
|
* 发信通道的接管逻辑统一在 inc/theme-mail.php,
|
||||||
|
* 邮件模板在 inc/theme-mail-template.php。
|
||||||
|
* 本文件只保留 SMTP 连通性相关的部分。
|
||||||
|
*
|
||||||
* @author Yongquan Yang <yangyq.net>
|
* @author Yongquan Yang <yangyq.net>
|
||||||
* @license MIT License
|
* @license MIT License
|
||||||
* @version 2020.02.15
|
* @version 2020.02.15
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (kratos_option('m_smtp', false)) {
|
if (!defined('ABSPATH')) {
|
||||||
function mail_smtp($phpmailer)
|
exit;
|
||||||
{
|
|
||||||
$phpmailer->isSMTP();
|
|
||||||
$phpmailer->SMTPAuth = true;
|
|
||||||
$phpmailer->CharSet = "utf-8";
|
|
||||||
$phpmailer->SMTPSecure = kratos_option('m_sec');
|
|
||||||
$phpmailer->Port = kratos_option('m_port');
|
|
||||||
$phpmailer->Host = kratos_option('m_host');
|
|
||||||
$phpmailer->From = kratos_option('m_username');
|
|
||||||
$phpmailer->Username = kratos_option('m_username');
|
|
||||||
$phpmailer->Password = kratos_option('m_passwd');
|
|
||||||
}
|
|
||||||
add_action('phpmailer_init', 'mail_smtp');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug
|
// 兼容旧版本:早期这里是 mail_smtp(),挂 phpmailer_init(优先级默认 10)。
|
||||||
function wp_mail_debug($wp_error)
|
// 现在由 inc/theme-mail.php 的 yang_mail_configure_phpmailer(优先级 9999)接管,
|
||||||
{
|
// 因为它还要处理「配置不完整不接管」「清掉别的插件残留配置」等情形。
|
||||||
return error_log(print_r($wp_error, true));
|
// 这里刻意不再注册任何 phpmailer_init,避免两套配置互相覆盖。
|
||||||
}
|
|
||||||
// add_action('wp_mail_failed', 'wp_mail_debug', 10, 1);
|
|
||||||
|
|
||||||
function comment_approved($comment)
|
// Debug:只在 WP_DEBUG 且显式开启时记录
|
||||||
{
|
if (defined('WP_DEBUG') && WP_DEBUG && kratos_option('m_debug_log', false)) {
|
||||||
if (is_email($comment->comment_author_email)) {
|
add_action('wp_mail_failed', function ($error) {
|
||||||
$wp_email = kratos_option('m_username');
|
$data = $error->get_error_data();
|
||||||
$to = trim($comment->comment_author_email);
|
error_log('[yang_mail] 发送失败: ' . $error->get_error_message()
|
||||||
$post_link = get_permalink($comment->comment_post_ID);
|
. ' | to=' . (isset($data['to']) && is_array($data['to']) ? implode(',', $data['to']) : '')
|
||||||
$subject = __('[通知]您的留言已经通过审核', 'kratos');
|
. ' | subject=' . (isset($data['subject']) ? $data['subject'] : ''));
|
||||||
$message = '
|
}, 10, 1);
|
||||||
<div style="background:#ececec;width: 100%;padding: 50px 0;text-align:center;">
|
}
|
||||||
<div style="background:#fff;width:750px;text-align:left;position:relative;margin:0 auto;font-size:14px;line-height:1.5;">
|
|
||||||
<div style="zoom:1;padding:25px 40px;background:#518bcb; border-bottom:1px solid #467ec3;">
|
|
||||||
<h1 style="color:#fff; font-size:25px;line-height:30px; margin:0;"><a href="' . get_option('home') . '" style="text-decoration: none;color: #FFF;">' . htmlspecialchars_decode(get_option('blogname'), ENT_QUOTES) . '</a></h1>
|
|
||||||
</div>
|
|
||||||
<div style="padding:35px 40px 30px;">
|
|
||||||
<h2 style="font-size:18px;margin:5px 0;">' . __('您好,', 'kratos') . trim($comment->comment_author) . ':</h2>
|
|
||||||
<p style="color:#313131;line-height:20px;font-size:15px;margin:20px 0;">' . __('您的留言已经通过了管理员的审核,摘要信息如下:', 'kratos') . '</p>
|
|
||||||
<table cellspacing="0" style="font-size:14px;text-align:center;border:1px solid #ccc;table-layout:fixed;width:500px;">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:normal;color:#a0a0a0;background:#eee;border-color:#dfdfdf;" width="280px;">' . __('文章', 'kratos') . '</th>
|
|
||||||
<th style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:normal;color:#a0a0a0;background:#eee;border-color:#dfdfdf;" width="270px;">' . __('内容', 'kratos') . '</th>
|
|
||||||
<th style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:normal;color:#a0a0a0;background:#eee;border-color:#dfdfdf;" width="110px;">' . __('操作', 'kratos') . '</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">《' . get_the_title($comment->comment_post_ID) . '》</td>
|
|
||||||
<td style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' . trim($comment->comment_content) . '</td>
|
|
||||||
<td style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;"><a href="' . get_comment_link($comment->comment_ID) . '" style="color:#1E5494;text-decoration:none;vertical-align:middle;" target="_blank">查看留言</a></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<br>
|
|
||||||
<div style="font-size:13px;color:#a0a0a0;padding-top:10px">' . __('该邮件由系统自动发出,如果不是您本人操作,请忽略此邮件。', 'kratos') . '</div>
|
|
||||||
<div class="qmSysSign" style="padding-top:20px;font-size:12px;color:#a0a0a0;">
|
|
||||||
<p style="color:#a0a0a0;line-height:18px;font-size:12px;margin:5px 0;">' . htmlspecialchars_decode(get_option('blogname'), ENT_QUOTES) . '</p>
|
|
||||||
<p style="color:#a0a0a0;line-height:18px;font-size:12px;margin:5px 0;"><span style="border-bottom:1px dashed #ccc;" t="5" times="">' . date("Y年m月d日", time()) . '</span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>';
|
|
||||||
$from = "From: \"" . htmlspecialchars_decode(get_option('blogname'), ENT_QUOTES) . "\" <$wp_email>";
|
|
||||||
$headers = "$from\nContent-Type: text/html; charset=" . get_option('blog_charset') . "\n";
|
|
||||||
wp_mail($to, $subject, $message, $headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
add_action('comment_unapproved_to_approved', 'comment_approved');
|
|
||||||
|
|
||||||
function comment_notify($comment_id)
|
|
||||||
{
|
|
||||||
$comment = get_comment($comment_id);
|
|
||||||
$parent_id = $comment->comment_parent ? $comment->comment_parent : '';
|
|
||||||
$spam_confirmed = $comment->comment_approved;
|
|
||||||
if (($parent_id != '') && ($spam_confirmed != 'spam')) {
|
|
||||||
$wp_email = kratos_option('m_username');
|
|
||||||
$to = trim(get_comment($parent_id)->comment_author_email);
|
|
||||||
$subject = __('[通知]您的留言有了新的回复', 'kratos');
|
|
||||||
$message = '
|
|
||||||
<div style="background:#ececec;width: 100%;padding: 50px 0;text-align:center;">
|
|
||||||
<div style="background:#fff;width:750px;text-align:left;position:relative;margin:0 auto;font-size:14px;line-height:1.5;">
|
|
||||||
<div style="zoom:1;padding:25px 40px;background:#518bcb; border-bottom:1px solid #467ec3;">
|
|
||||||
<h1 style="color:#fff; font-size:25px;line-height:30px; margin:0;"><a href="' . get_option('home') . '" style="text-decoration: none;color: #FFF;">' . htmlspecialchars_decode(get_option('blogname'), ENT_QUOTES) . '</a></h1>
|
|
||||||
</div>
|
|
||||||
<div style="padding:35px 40px 30px;">
|
|
||||||
<h2 style="font-size:18px;margin:5px 0;">' . __('您好,', 'kratos') . trim(get_comment($parent_id)->comment_author) . ':</h2>
|
|
||||||
<p style="color:#313131;line-height:20px;font-size:15px;margin:20px 0;">' . __('您的留言有了新的回复,摘要信息如下:', 'kratos') . '</p>
|
|
||||||
<table cellspacing="0" style="font-size:14px;text-align:center;border:1px solid #ccc;table-layout:fixed;width:500px;">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:normal;color:#a0a0a0;background:#eee;border-color:#dfdfdf;" width="235px;">' . __('原文', 'kratos') . '</th>
|
|
||||||
<th style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:normal;color:#a0a0a0;background:#eee;border-color:#dfdfdf;" width="235px;">' . __('回复', 'kratos') . '</th>
|
|
||||||
<th style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:normal;color:#a0a0a0;background:#eee;border-color:#dfdfdf;" width="100px;">' . __('作者', 'kratos') . '</th>
|
|
||||||
<th style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:normal;color:#a0a0a0;background:#eee;border-color:#dfdfdf;" width="90px;" >' . __('操作', 'kratos') . '</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' . trim(get_comment($parent_id)->comment_content) . '</td>
|
|
||||||
<td style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' . trim($comment->comment_content) . '</td>
|
|
||||||
<td style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' . trim($comment->comment_author) . '</td>
|
|
||||||
<td style="padding:5px 0;text-indent:8px;border:1px solid #eee;border-width:0 1px 1px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;"><a href="' . get_comment_link($comment->comment_ID) . '" style="color:#1E5494;text-decoration:none;vertical-align:middle;" target="_blank">' . __('查看回复', 'kratos') . '</a></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<br>
|
|
||||||
<div style="font-size:13px;color:#a0a0a0;padding-top:10px">' . __('该邮件由系统自动发出,如果不是您本人操作,请忽略此邮件。', 'kratos') . '</div>
|
|
||||||
<div class="qmSysSign" style="padding-top:20px;font-size:12px;color:#a0a0a0;">
|
|
||||||
<p style="color:#a0a0a0;line-height:18px;font-size:12px;margin:5px 0;">' . htmlspecialchars_decode(get_option('blogname'), ENT_QUOTES) . '</p>
|
|
||||||
<p style="color:#a0a0a0;line-height:18px;font-size:12px;margin:5px 0;"><span style="border-bottom:1px dashed #ccc;" t="5" times="">' . date("Y年m月d日", time()) . '</span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>';
|
|
||||||
$from = "From: \"" . htmlspecialchars_decode(get_option('blogname'), ENT_QUOTES) . "\" <$wp_email>";
|
|
||||||
$headers = "$from\nContent-Type: text/html; charset=" . get_option('blog_charset') . "\n";
|
|
||||||
wp_mail($to, $subject, $message, $headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
add_action('comment_post', 'comment_notify');
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "3.2.5",
|
"version": "3.2.6",
|
||||||
"details_url": "https://yangyq.net/theme-update/readme.html",
|
"details_url": "https://yangyq.net/theme-update/readme.html",
|
||||||
"download_url": "https://yangyq.net/theme-update/kratos_yang.zip"
|
"download_url": "https://yangyq.net/theme-update/kratos_yang.zip"
|
||||||
}
|
}
|
||||||
-18
@@ -57,15 +57,6 @@ $select_col = $col_array[kratos_option('g_article_widgets', 'two_side')];
|
|||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<?php
|
<?php
|
||||||
if(kratos_option('s_singletop',false)){
|
|
||||||
if(kratos_option('s_singletop_links')){
|
|
||||||
echo '<a href="'. kratos_option('s_singletop_links') .'" target="_blank" rel="noreferrer">';
|
|
||||||
}
|
|
||||||
echo '<img src="'.kratos_option('s_singletop_url').'">';
|
|
||||||
if(kratos_option('s_singletop_links')){
|
|
||||||
echo '</a>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
the_content();
|
the_content();
|
||||||
wp_link_pages(
|
wp_link_pages(
|
||||||
array(
|
array(
|
||||||
@@ -94,15 +85,6 @@ $select_col = $col_array[kratos_option('g_article_widgets', 'two_side')];
|
|||||||
'nextpagelink' => __('<span>下一页</span>', 'kratos')
|
'nextpagelink' => __('<span>下一页</span>', 'kratos')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if(kratos_option('s_singledown',false)){
|
|
||||||
if(kratos_option('s_singledown_links')){
|
|
||||||
echo '<a href="'. kratos_option('s_singledown_links') .'" target="_blank" rel="noreferrer">';
|
|
||||||
}
|
|
||||||
echo '<img src="'.kratos_option('s_singledown_url').'">';
|
|
||||||
if(kratos_option('s_singledown_links')){
|
|
||||||
echo '</a>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<?php if(kratos_option('g_cc_switch', false)){
|
<?php if(kratos_option('g_cc_switch', false)){
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Theme Name: Kratos_Yang
|
Theme Name: Kratos_Yang
|
||||||
Text Domain: kratos_Yang
|
Text Domain: kratos_Yang
|
||||||
Version: 3.2.5
|
Version: 3.2.6
|
||||||
Requires PHP: 7.0
|
Requires PHP: 7.0
|
||||||
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
|
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
|
||||||
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好
|
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好
|
||||||
|
|||||||
Reference in New Issue
Block a user