Files
laoyang 37c52948c4 版本号 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 不溢出。
2026-10-23 13:55:23 +08:00

249 lines
6.7 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* @package Options_Framework
* @author Devin Price <devin@wptheming.com>
* @license GPL-2.0+
* @link http://wptheming.com
* @copyright 2010-2014 WP Theming
*/
/**
* Sanitization for text input
*
* @link http://developer.wordpress.org/reference/functions/sanitize_text_field/
*/
add_filter( 'of_sanitize_text', 'sanitize_text_field' );
/**
* Sanitization for password input
*
* @link http://developer.wordpress.org/reference/functions/sanitize_text_field/
*/
add_filter( 'of_sanitize_password', 'sanitize_text_field' );
/**
* Sanitization for select input
*
* Validates that the selected option is a valid option.
*/
add_filter( 'of_sanitize_select', 'of_sanitize_enum', 10, 2 );
/**
* Sanitization for radio input
*
* Validates that the selected option is a valid option.
*/
add_filter( 'of_sanitize_radio', 'of_sanitize_enum', 10, 2 );
/**
* Sanitization for image selector
*
* Validates that the selected option is a valid option.
*/
add_filter( 'of_sanitize_images', 'of_sanitize_enum', 10, 2 );
/**
* Sanitization for textarea field
*
* @param $input string
* @return $output sanitized string
*/
function of_sanitize_textarea( $input ) {
global $allowedposttags;
$output = wp_kses( $input, $allowedposttags );
return $output;
}
add_filter( 'of_sanitize_textarea', 'of_sanitize_textarea' );
/**
* Sanitization for raw code fields (theme's own and user's CSS / JS).
*
* 为什么不能复用 textarea:textarea 走 wp_kses(),CSS 里的子选择器
* `>`、`+`、`~` 以及 JS 里的比较运算符 `<`、`>` 都会被当成 HTML 标签
* 剥掉。例:`a > b { }` 存进库会变成 `a b { }`,样式静默失效。
* 所以这里只做三件必要的事:
* 1. 去掉 PHP 标签,避免这段文本将来被 eval / include 时执行代码;
* 2. 把 `</style>`、`</script>` 打断,避免提前闭合输出标签;
* 3. 其余内容原样返回(含反斜杠转义,如 CSS 的 `\f101`)。
*
* @param $input string
* @return $output sanitized string
*/
function of_sanitize_code( $input ) {
if ( ! is_string( $input ) ) {
return '';
}
$output = str_replace( array( '<?', '?>' ), '', $input );
$output = preg_replace( '#</\s*(style|script)#i', '<\\/$1', $output );
return $output;
}
add_filter( 'of_sanitize_code', 'of_sanitize_code' );
/**
* Sanitization for checkbox input
*
* @param $input string (1 or empty) checkbox state
* @return $output '1' or false
*/
function of_sanitize_checkbox( $input ) {
if ( $input ) {
$output = '1';
} else {
$output = false;
}
return $output;
}
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.
*
* Returns a sanitized filepath if it has a valid extension.
*
* @param string $input filepath
* @returns string $output filepath
*/
function of_sanitize_upload( $input ) {
$output = '';
$filetype = wp_check_filetype( $input );
if ( $filetype["ext"] ) {
$output = esc_url( $input );
}
return $output;
}
add_filter( 'of_sanitize_upload', 'of_sanitize_upload' );
/**
* Sanitization of input with allowed tags and wpautotop.
*
* Allows allowed tags in html input and ensures tags close properly.
*
* @param string $input
* @returns string $output
*/
function of_sanitize_allowedtags( $input ) {
global $allowedtags;
$output = wpautop( wp_kses( $input, $allowedtags ) );
return $output;
}
/**
* Sanitization of input with allowed post tags and wpautotop.
*
* Allows allowed post tags in html input and ensures tags close properly.
*
* @param string $input
* @returns string $output
*/
function of_sanitize_allowedposttags( $input ) {
global $allowedposttags;
$output = wpautop( wp_kses( $input, $allowedposttags) );
return $output;
}
/**
* Validates that the $input is one of the avilable choices
* for that specific option.
*
* @param string $input
* @returns string $output
*/
function of_sanitize_enum( $input, $option ) {
$output = '';
if ( array_key_exists( $input, $option['options'] ) ) {
$output = $input;
}
return $output;
}
/**
* Sanitization for background option.
*
* @returns array $output
*/
function of_sanitize_background( $input ) {
$output = wp_parse_args( $input, array(
'color' => '',
'image' => '',
'repeat' => 'repeat',
'position' => 'top center',
'attachment' => 'scroll'
) );
$output['color'] = apply_filters( 'of_sanitize_hex', $input['color'] );
$output['image'] = apply_filters( 'of_sanitize_upload', $input['image'] );
return $output;
}
add_filter( 'of_sanitize_background', 'of_sanitize_background' );
/**
* Sanitize a color represented in hexidecimal notation.
*
* @param string Color in hexidecimal notation. "#" may or may not be prepended to the string.
* @param string The value that this function should return if it cannot be recognized as a color.
* @return string
*/
function of_sanitize_hex( $hex, $default = '' ) {
if ( of_validate_hex( $hex ) ) {
return $hex;
}
return $default;
}
add_filter( 'of_sanitize_color', 'of_sanitize_hex' );
/**
* Is a given string a color formatted in hexidecimal notation?
*
* @param string Color in hexidecimal notation. "#" may or may not be prepended to the string.
* @return bool
*/
function of_validate_hex( $hex ) {
$hex = trim( $hex );
/* Strip recognized prefixes. */
if ( 0 === strpos( $hex, '#' ) ) {
$hex = substr( $hex, 1 );
}
elseif ( 0 === strpos( $hex, '%23' ) ) {
$hex = substr( $hex, 3 );
}
/* Regex match. */
if ( 0 === preg_match( '/^[0-9a-fA-F]{6}$/', $hex ) ) {
return false;
}
else {
return true;
}
}