版本号 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:
+24
-119
@@ -1,130 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* SMTP 配置
|
||||
*
|
||||
* ⚠️ 这个文件过去自己发过两类邮件(回复通知、审核通过通知),
|
||||
* 与 wpDiscuz 插件的通知**完全重复**,是「一条评论收到多封邮件」的
|
||||
* 直接原因之一。现已全部移除。
|
||||
*
|
||||
* 发信通道的接管逻辑统一在 inc/theme-mail.php,
|
||||
* 邮件模板在 inc/theme-mail-template.php。
|
||||
* 本文件只保留 SMTP 连通性相关的部分。
|
||||
*
|
||||
* @author Yongquan Yang <yangyq.net>
|
||||
* @license MIT License
|
||||
* @version 2020.02.15
|
||||
*/
|
||||
|
||||
if (kratos_option('m_smtp', false)) {
|
||||
function mail_smtp($phpmailer)
|
||||
{
|
||||
$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');
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Debug
|
||||
function wp_mail_debug($wp_error)
|
||||
{
|
||||
return error_log(print_r($wp_error, true));
|
||||
}
|
||||
// add_action('wp_mail_failed', 'wp_mail_debug', 10, 1);
|
||||
// 兼容旧版本:早期这里是 mail_smtp(),挂 phpmailer_init(优先级默认 10)。
|
||||
// 现在由 inc/theme-mail.php 的 yang_mail_configure_phpmailer(优先级 9999)接管,
|
||||
// 因为它还要处理「配置不完整不接管」「清掉别的插件残留配置」等情形。
|
||||
// 这里刻意不再注册任何 phpmailer_init,避免两套配置互相覆盖。
|
||||
|
||||
function comment_approved($comment)
|
||||
{
|
||||
if (is_email($comment->comment_author_email)) {
|
||||
$wp_email = kratos_option('m_username');
|
||||
$to = trim($comment->comment_author_email);
|
||||
$post_link = get_permalink($comment->comment_post_ID);
|
||||
$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($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');
|
||||
// Debug:只在 WP_DEBUG 且显式开启时记录
|
||||
if (defined('WP_DEBUG') && WP_DEBUG && kratos_option('m_debug_log', false)) {
|
||||
add_action('wp_mail_failed', function ($error) {
|
||||
$data = $error->get_error_data();
|
||||
error_log('[yang_mail] 发送失败: ' . $error->get_error_message()
|
||||
. ' | to=' . (isset($data['to']) && is_array($data['to']) ? implode(',', $data['to']) : '')
|
||||
. ' | subject=' . (isset($data['subject']) ? $data['subject'] : ''));
|
||||
}, 10, 1);
|
||||
}
|
||||
Reference in New Issue
Block a user