版本号 3.2.6 -> 3.2.7(纪念日全部默认不勾 + 彩色说明页 + 邮件彩色 + 双引用块)

哀悼默认值全部改为不勾选
- 九个纪念日的 default 全改成 0。之前法定那三个是 1,导致全新安装的站点
  一到日子就自己变黑白,而站主根本没同意过这件事。
- 后台多选组件的 std 全为 0,打开设置页一个都不勾。
- 「从未保存过」与「保存过但全不勾」两条路径都返回空数组,
  不能因为结果为空就回退到默认 —— 那等于用户取消勾选无效。

新增彩色说明页(inc/theme-mourning-page.php + pages/page-mourning.php)
- 站点变黑白后必须有个地方解释清楚,否则访客只看到一片灰、以为站点坏了。
- 三处入口:顶部提示条(wp_body_open,首屏可见)、页脚常驻链接、
  [yang_mourning] 短代码。
- 页面内容由真实配置驱动,不是写死的文案:没勾的日期不会出现,
  今天在列表里带「今天」标记,一个都没勾时明说「不会生效」。
- 页面必须保持彩色,否则解释「为什么是灰的」的页面自己也是灰的。
  取消灰度做了两层:filter:none 与 grayscale(0%) 都写,且带 !important;
  优先级放在 wp_head 的 2,晚于灰度样式的 1。
- 路由走 parse_request 优先级 5 而非 template_redirect:
  避免先走一遍 404 判定与 404 模板。带子目录安装时先剥掉前缀再比对。
- 不抢占用户自己建的页面:如果站里已有同名 slug 的页面就放手。
  否则用户会觉得「我建的页面坏了」,且没有任何提示。

邮件不受影响,且回复邮件同时给出双方内容
- 邮件是独立 HTML,不走 wp_head,结构上就不可能被灰度,已断言验证。
- theme-mail-template 的单个 quote 改成 quotes 列表,一个模板同时渲染
  「被回复的那条」与「新的回复」,最后一块用主色高亮(回复是重点),
  前面的用弱化色。只有一块时退化为主色,与旧行为一致。
- 通过 wpdiscuz_email_content 重建整封邮件,不需要改插件。
  审核通过通知用 wpdiscuz_comment_approved_email_content 走同一模板,
  但它只给 post_id、不给评论对象,能放的内容受此限制。

两个必须记下来的坑
- wpDiscuz 在过滤器之后还会做一次 html_entity_decode 再发信。
  esc_html 的输出会被那一步还原成真标签,评论里的 <script> 直接生效。
  所以返回前先把 & 编码一次。本地 stub 没有这一步,测试看不出来。
- header.php 从来没有调用 wp_body_open,挂在上面的东西全部静默失效。
  不调用不会报错,只会「什么都没发生」。已补在 <body> 之后。

yang_mail_text 保留换行
- 原来用 wp_strip_all_tags($t, true),第二个参数 remove_breaks 会把换行
  压成空格;而 strip_tags 遇到 <br> 是直接删掉不补换行的。
  结果整条评论被拍成一行,模板里的 nl2br 成了死代码。
- 改为先把块级边界降级成 \n,再去标签(remove_breaks=false)。
  收行内空白用 [^\S\n] 而不是 [ \t\f\v]:PCRE 的 \v 是垂直空白类,
  会把 \n 一起匹配掉。

说明页对比度修正(WCAG AA)
- 金色 #b8860b 在白底只有 3.06:1,当装饰色没问题,当 12-15px 文字偏虚。
  拆成 --ym-gold(装饰)与 --ym-gold-text(#946600,4.75:1,仍属金色)。
- 分组小标题 #9aa5b1 只有 2.36:1,改用 #5f6b78(5.12:1)。
- 「今天」标签白字压金底 3.25:1,改用 #8a6100(5.54:1)。
This commit is contained in:
2026-10-23 14:43:47 +08:00
parent 37c52948c4
commit 269364bed4
9 changed files with 833 additions and 21 deletions
+144 -1
View File
@@ -306,4 +306,147 @@ function yang_mail_send_test($to = null)
));
return wp_mail($to, sprintf(__('[%s] 邮件配置测试', 'kratos'), get_bloginfo('name')), $body);
}
}
/**
* 抵消 wpDiscuz 在过滤器之后又做的那一次 html_entity_decode
*
* ── 为什么必须要这一步 ────────────────────────────────────────
* `wpdiscuz_email_content` 这个过滤器**不是最后一站**。插件拿到返回值后
* 还会执行:
*
* $message = html_entity_decode($message, ENT_QUOTES);
* wp_mail($email, $subject, $message, $headers, $attachments);
*
* (utils/class.WpdiscuzHelperEmail.php:494 附近)
*
* 于是我们在模板里用 esc_html() 做的转义会被整体还原:
* 用户评论里的 `<script>alert(1)</script>` 会变回可执行标签,
* 未闭合的 `<div>` 会吃掉整封邮件的布局。
*
* 这**在本地用桩环境测不出来** —— 桩环境没有插件那一步解码,
* 断言只会看到"已转义"而全绿。只有读插件源码才发现。
*
* 解法:把 `&` 全部预编码成 `&amp;`。这样插件解码一次之后,
* 恰好还原成我们想要的转义状态(decode("&amp;lt;") === "&lt;")。
* 只处理 `&` 就够,因为其它实体的风险都源于 `&` 被还原。
*/
function yang_mail_escape_for_wpdiscuz($html)
{
return str_replace('&', '&amp;', $html);
}
/**
* 把 wpDiscuz 的通知邮件接管成主题模板
*
* ── 为什么要接管 ──────────────────────────────────────────────
* wpDiscuz 自己的模板只印「新评论内容」,收件人看到一句陌生人说的话,
* 完全想不起自己在哪篇文章下说过什么。要理解上下文,必须**同时给出
* 被回复的那条评论**。
*
* 它的过滤器 `wpdiscuz_email_content` 能拿到 `$comment`(新评论对象)
* 和 `$emailData`(订阅记录),足以重建整封邮件,所以不需要改插件。
*
* ── 两个模板还是一个模板 ──────────────────────────────────────
* 用一个。评论通知和「审核通过」通知的差别只有标题、徽标、引言和
* 上面那两段引用,正文骨架完全一样。做成两套模板会立刻分叉,
* 以后改一次配色要改两处。
*
* @param string $message 插件生成的 HTML(丢弃)
* @param WP_Comment $comment 新评论
* @param array $email_data 订阅记录
* @return string
*/
function yang_mail_render_comment_notify($message, $comment, $email_data = array())
{
if (!yang_mail_smtp_ready() || !($comment instanceof WP_Comment)) {
return $message;
}
$site = get_bloginfo('name');
$post_id = (int) $comment->comment_post_ID;
$post = get_post($post_id);
$title = $post ? get_the_title($post) : __('(文章已删除)', 'kratos');
$link = get_comment_link($comment->comment_ID);
$author = $comment->comment_author ? $comment->comment_author : __('匿名访客', 'kratos');
// 新评论这一段(一定是最后一块,模板会把它高亮成主色)
$quotes = array();
// 被回复的原评论:有父评论才取,取不到就不放,宁缺毋滥
$parent_id = (int) $comment->comment_parent;
if ($parent_id > 0) {
$parent = get_comment($parent_id);
if ($parent) {
$p_author = $parent->comment_author ? $parent->comment_author : __('匿名访客', 'kratos');
$quotes[] = array(
'label' => sprintf(__('你之前说的(%s)', 'kratos'), $p_author),
'content' => yang_mail_text($parent->comment_content, 1200),
'meta' => mysql2date('Y-m-d H:i', $parent->comment_date),
);
}
}
$quotes[] = array(
'label' => sprintf(__('%s 回复说', 'kratos'), $author),
'content' => yang_mail_text($comment->comment_content, 1200),
'meta' => mysql2date('Y-m-d H:i', $comment->comment_date),
);
// 引言随场景变化:有父评论是「有人回复你」,没有是「有新评论」
$intro = $parent_id > 0
? sprintf(__('你在《%s》下的评论收到了一条回复。', 'kratos'), $title)
: sprintf(__('《%s》下有新的评论。', 'kratos'), $title);
$rows = array(
array('label' => __('文章', 'kratos'), 'value' => $title),
array('label' => __('评论者', 'kratos'), 'value' => $author, 'strong' => true),
array('label' => __('时间', 'kratos'), 'value' => mysql2date('Y-m-d H:i', $comment->comment_date)),
);
$body = yang_mail_render(array(
'title' => sprintf(__('%s · 新回复', 'kratos'), $site),
'preheader' => sprintf(__('%s 在《%s》下回复了评论。', 'kratos'), $author, $title),
'badge' => $parent_id > 0 ? __('新回复', 'kratos') : __('新评论', 'kratos'),
'heading' => $parent_id > 0 ? __('有人回复了你的评论', 'kratos') : __('文章有新评论', 'kratos'),
'intro' => $intro,
'quotes' => $quotes,
'rows' => $rows,
'cta_text' => __('查看完整讨论', 'kratos'),
'cta_url' => $link,
'note' => __('你收到这封邮件,是因为你订阅了这篇文章的评论,或你的评论被回复了。', 'kratos'),
));
return yang_mail_escape_for_wpdiscuz($body);
}
add_filter('wpdiscuz_email_content', 'yang_mail_render_comment_notify', 20, 3);
/**
* 审核通过通知也套同一套模板
*
* 这个过滤器的参数是 ($message, $post_id, $email),拿不到评论对象,
* 所以只能给「你的评论已通过审核」这种通用内容 —— 这是插件接口的限制,
* 不是这里偷懒。
*/
function yang_mail_render_approved_notify($message, $post_id, $email)
{
if (!yang_mail_smtp_ready()) {
return $message;
}
$title = get_the_title($post_id);
return yang_mail_escape_for_wpdiscuz(yang_mail_render(array(
'title' => sprintf(__('%s · 评论已通过审核', 'kratos'), get_bloginfo('name')),
'preheader' => sprintf(__('你在《%s》下的评论已通过审核并公开显示。', 'kratos'), $title),
'badge' => __('已通过', 'kratos'),
'heading' => __('你的评论已通过审核', 'kratos'),
'intro' => sprintf(__('你在《%s》下提交的评论已经审核通过,现在其他读者可以看到它了。', 'kratos'), $title),
'rows' => array(
array('label' => __('文章', 'kratos'), 'value' => $title),
),
'cta_text' => __('去看看', 'kratos'),
'cta_url' => get_permalink($post_id),
'note' => __('这是评论审核结果通知,无需回复。', 'kratos'),
)));
}
add_filter('wpdiscuz_comment_approved_email_content', 'yang_mail_render_approved_notify', 20, 3);