评论邮件重复的根因是发信源不止一个,分两层处理:一层管「谁发」,一层管「发几封」。 只做前者的话邮件照样重复,只是都从同一个 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 不溢出。
362 lines
6.6 KiB
CSS
362 lines
6.6 KiB
CSS
#optionsframework {
|
|
max-width: 855px;
|
|
background: #fff;
|
|
}
|
|
|
|
#optionsframework h3 {
|
|
margin: 0;
|
|
padding: 12px 10px;
|
|
border-bottom: 1px solid #ddd;
|
|
background-color: #f1f1f1;
|
|
cursor: default;
|
|
}
|
|
|
|
#optionsframework p {
|
|
margin-bottom: 0;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
#optionsframework .section {
|
|
padding: 10px 10px 0;
|
|
}
|
|
|
|
#optionsframework .group {
|
|
padding-bottom: 40px;
|
|
}
|
|
|
|
#optionsframework .section .controls {
|
|
float: left;
|
|
padding-right: 2%;
|
|
width: 54%;
|
|
min-width: 350px;
|
|
}
|
|
|
|
#optionsframework .section .explain {
|
|
float: left;
|
|
max-width: 38%;
|
|
color: #777;
|
|
font-size: 12px;
|
|
line-height: 16px;
|
|
}
|
|
|
|
#optionsframework .section-checkbox .controls {
|
|
width: 98%;
|
|
}
|
|
|
|
#optionsframework .section-checkbox .explain {
|
|
max-width: 94%;
|
|
}
|
|
|
|
#optionsframework .controls input[type="text"],
|
|
#optionsframework .controls input[type="password"] {
|
|
width: 100%;
|
|
}
|
|
|
|
#optionsframework .controls input[type="text"].wp-color-picker {
|
|
width: 65px;
|
|
}
|
|
|
|
#optionsframework .controls select,
|
|
#optionsframework .controls textarea {
|
|
margin-bottom: 10px;
|
|
width: 100%;
|
|
}
|
|
|
|
#optionsframework .section-radio label,
|
|
#optionsframework .section-multicheck label {
|
|
float: left;
|
|
margin-bottom: 5px;
|
|
max-width: 90%;
|
|
line-height: 16px;
|
|
}
|
|
|
|
#optionsframework input.checkbox,
|
|
#optionsframework input.of-radio {
|
|
float: left;
|
|
clear: both;
|
|
margin: 0 10px 5px 0;
|
|
}
|
|
|
|
#optionsframework .section-typography .controls {
|
|
float: none;
|
|
width: auto;
|
|
}
|
|
|
|
#optionsframework .section-typography .explain {
|
|
float: none;
|
|
width: auto;
|
|
}
|
|
|
|
#optionsframework .controls .of-typography-size {
|
|
float: left;
|
|
width: 80px;
|
|
}
|
|
|
|
#optionsframework .controls .of-typography-unit {
|
|
float: left;
|
|
margin-left: 5px;
|
|
width: 50px;
|
|
}
|
|
|
|
#optionsframework .controls .of-typography-face {
|
|
float: left;
|
|
margin-left: 5px;
|
|
width: 100px;
|
|
}
|
|
|
|
#optionsframework .controls .of-typography-style {
|
|
float: left;
|
|
margin-right: 5px;
|
|
margin-left: 5px;
|
|
width: 80px;
|
|
}
|
|
|
|
#optionsframework .section-typography .wp-picker-container {
|
|
margin-top: 2px;
|
|
}
|
|
|
|
#optionsframework .of-background-properties {
|
|
clear: both;
|
|
margin-top: 18px;
|
|
}
|
|
|
|
#optionsframework .controls .of-background-repeat {
|
|
float: left;
|
|
margin-right: 5px;
|
|
width: 125px;
|
|
}
|
|
|
|
#optionsframework .controls .of-background-position {
|
|
float: left;
|
|
margin-right: 5px;
|
|
width: 125px;
|
|
}
|
|
|
|
#optionsframework .controls .of-background-attachment {
|
|
float: left;
|
|
margin-right: 5px;
|
|
width: 125px;
|
|
}
|
|
|
|
#optionsframework .section-background .wp-picker-container {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
#optionsframework .controls .of-radio-img-img {
|
|
float: left;
|
|
display: none;
|
|
margin: 0 5px 10px 0;
|
|
border: 3px solid #f9f9f9;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#optionsframework .controls .of-radio-img-selected {
|
|
border: 3px solid #ccc;
|
|
}
|
|
|
|
#optionsframework .controls .of-radio-img-img:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
#optionsframework .controls .of-border-width {
|
|
float: left;
|
|
width: 80px;
|
|
}
|
|
|
|
#optionsframework .controls .of-border-style {
|
|
float: left;
|
|
width: 120px;
|
|
}
|
|
|
|
#optionsframework .hide {
|
|
display: none;
|
|
}
|
|
|
|
#optionsframework .of-option-image {
|
|
margin: 3px 0 18px 0;
|
|
max-width: 340px;
|
|
}
|
|
|
|
#optionsframework .mini .controls select,
|
|
#optionsframework .section .mini .controls {
|
|
width: 140px;
|
|
}
|
|
|
|
#optionsframework .mini .controls input,
|
|
#optionsframework .mini .controls {
|
|
width: 140px;
|
|
min-width: 140px;
|
|
}
|
|
|
|
#optionsframework .mini .explain {
|
|
max-width: 74%;
|
|
}
|
|
|
|
#optionsframework .section-info {
|
|
padding-bottom: 7px;
|
|
}
|
|
|
|
#optionsframework .controls input.upload {
|
|
width: 80%;
|
|
}
|
|
|
|
#optionsframework .screenshot {
|
|
position: relative;
|
|
float: left;
|
|
margin-top: 3px;
|
|
margin-left: 1px;
|
|
width: 344px;
|
|
}
|
|
|
|
#optionsframework .screenshot img {
|
|
float: left;
|
|
margin-bottom: 10px;
|
|
padding: 4px;
|
|
max-width: 334px;
|
|
border-color: #ccc #eee #eee #ccc;
|
|
border-style: solid;
|
|
border-width: 1px;
|
|
background: #fafafa;
|
|
}
|
|
|
|
#optionsframework .screenshot .remove-image {
|
|
position: absolute;
|
|
bottom: 4px;
|
|
left: -4px;
|
|
float: left;
|
|
display: block;
|
|
padding: 0;
|
|
width: 16px;
|
|
height: 16px;
|
|
border: medium none;
|
|
background: url("../images/ico-delete.png") no-repeat;
|
|
text-indent: -9999px;
|
|
}
|
|
|
|
#optionsframework .screenshot .no_image .file_link {
|
|
margin-left: 20px;
|
|
}
|
|
|
|
#optionsframework .screenshot .no_image .remove-button {
|
|
bottom: 0px;
|
|
}
|
|
|
|
#optionsframework .reset-button {
|
|
float: left;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#optionsframework-submit {
|
|
padding: 7px 10px;
|
|
border-top: 1px solid #ddd;
|
|
background-color: #f1f1f1;
|
|
}
|
|
|
|
#optionsframework .button-primary {
|
|
float: right;
|
|
}
|
|
|
|
#optionsframework .section:after {
|
|
display: table;
|
|
content: "";
|
|
}
|
|
|
|
#optionsframework .section:after {
|
|
clear: both;
|
|
}
|
|
|
|
#optionsframework .about-content {
|
|
padding: 10px 20px;
|
|
}
|
|
|
|
#optionsframework .about-content img {
|
|
width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
#optionsframework .about-content h4 {
|
|
margin-top: 20px;
|
|
margin-bottom: 16px;
|
|
padding-bottom: 0.3em;
|
|
border-bottom: 1px solid #eaecef;
|
|
font-weight: 600;
|
|
font-size: 18px;
|
|
line-height: 1.25;
|
|
}
|
|
|
|
#optionsframework .about-content ul {
|
|
padding-left: 0;
|
|
list-style: none;
|
|
font-size: 15px;
|
|
}
|
|
|
|
#optionsframework .about-content ul li {
|
|
line-height: 25px;
|
|
}
|
|
|
|
#optionsframework .about-content .notices {
|
|
color: #727777;
|
|
font-size: 14px;
|
|
}
|
|
|
|
#optionsframework .about-content .tips {
|
|
color: #333333;
|
|
text-align: center;
|
|
font-size: 16px;
|
|
}
|
|
|
|
#optionsframework .about-content .tips b {
|
|
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;
|
|
}
|