fix(email): complete mail settings and templates

This commit is contained in:
2026-09-04 12:06:42 +08:00
parent 4f8ab94fac
commit 4833053a5c
29 changed files with 716 additions and 426 deletions
+116 -77
View File
@@ -1,5 +1,7 @@
<?php
/**
* Canonical email settings and delivery service.
*
* @package AKSRT
*/
@@ -7,112 +9,149 @@ namespace AKSRT\Settings;
defined('ABSPATH') || exit;
class Mail {
final class Mail {
/** @return list<string> */
public static function events(): array {
return array_keys(MailTemplates::definitions());
}
/** 所有邮件事件类型常量 */
public const EMAIL_EVENTS = [
'reg_verify_link',
'reg_verify_code',
'reg_success',
'password_reset',
'change_code',
'change_notice',
];
/**
* 获取邮件事件配置
*
* @param string $event 事件类型,留空返回全部
* @return array
*/
public static function get_email_event_config(string $event = ''): array {
/** @return array<string, mixed> */
public static function get_smtp(): array {
$s = Settings::get_all();
$global = [
'footer_text' => sanitize_text_field((string) ($s['email_footer_text'] ?? '')),
'site_branding' => !isset($s['email_site_branding']) || !empty($s['email_site_branding']),
'template_mode' => in_array($s['email_template_mode'] ?? 'plain', ['plain', 'html'], true)
? ($s['email_template_mode'] ?? 'plain') : 'plain',
return [
'enabled' => !empty($s['mail_smtps']),
'host' => trim((string) ($s['mail_host'] ?? '')),
'port' => max(1, min(65535, (int) ($s['mail_port'] ?? 465))),
'username' => trim((string) ($s['mail_smtp_name'] ?? '')) ?: sanitize_email((string) ($s['mail_name'] ?? '')),
'password' => (string) ($s['mail_passwd'] ?? ''),
'encryption' => in_array(($s['mail_smtpsecure'] ?? 'ssl'), ['ssl', 'tls', ''], true) ? (string) ($s['mail_smtpsecure'] ?? 'ssl') : 'ssl',
'from_name' => trim((string) ($s['mail_showname'] ?? '')) ?: get_bloginfo('name'),
'from_address' => sanitize_email((string) ($s['mail_name'] ?? '')),
'auth' => !empty($s['mail_smtpauth']),
];
}
/** @return array<string, mixed> */
public static function get_email_event_config(string $event = ''): array {
$definitions = MailTemplates::definitions();
$defaults = MailTemplates::defaults();
$settings = Settings::get_all();
$global = [
'description' => (string) ($defaults['mail_description'] ?? ''),
'more_content' => (string) ($defaults['mail_more_content'] ?? ''),
];
foreach (['mail_description', 'mail_more_content'] as $key) {
if (array_key_exists($key, $settings) && trim((string) $settings[$key]) !== '') {
$global[str_replace('mail_', '', $key)] = $key === 'mail_more_content'
? wp_kses_post((string) $settings[$key])
: sanitize_textarea_field((string) $settings[$key]);
}
}
if ($event === '') {
$events = [];
foreach (self::EMAIL_EVENTS as $ev) {
$events[$ev] = self::build_email_event_entry($s, $ev);
foreach (array_keys($definitions) as $key) {
$events[$key] = self::event_config($key, $settings, $defaults);
}
return ['global' => $global, 'events' => $events];
}
if (!in_array($event, self::EMAIL_EVENTS, true)) {
return ['enabled' => true, 'subject' => '', 'content' => '', 'html' => false, 'global' => $global];
if (!isset($definitions[$event])) {
return ['enabled' => false, 'subject' => '', 'content' => '', 'placeholders' => [], 'global' => $global];
}
return array_merge(self::build_email_event_entry($s, $event), ['global' => $global]);
return array_merge(self::event_config($event, $settings, $defaults), ['global' => $global]);
}
/**
* 邮件事件是否启用
*/
public static function is_email_event_enabled(string $event): bool {
if (!in_array($event, self::EMAIL_EVENTS, true)) {
return true;
$definitions = MailTemplates::definitions();
if (!isset($definitions[$event])) {
return false;
}
$s = Settings::get_all();
return !isset($s["email_{$event}_enabled"]) || !empty($s["email_{$event}_enabled"]);
$settings = Settings::get_all();
return !array_key_exists("email_{$event}_enabled", $settings) || !empty($settings["email_{$event}_enabled"]);
}
/**
* 通过邮件事件体系发送邮件
* Send a configured event. The optional defaults keep existing callers source-compatible.
*/
public static function send_event_email(
string $event,
string $to,
string $default_subject,
string $default_content,
string $default_subject = '',
string $default_content = '',
array $placeholders = []
): bool {
if (!self::is_email_event_enabled($event)) {
$to = sanitize_email($to);
if ($to === '' || !is_email($to) || !isset(MailTemplates::definitions()[$event]) || !self::is_email_event_enabled($event)) {
return false;
}
$cfg = self::get_email_event_config($event);
$subject = ($cfg['subject'] !== '') ? $cfg['subject'] : $default_subject;
$subject = strtr($subject, $placeholders);
$content = ($cfg['content'] !== '') ? $cfg['content'] : $default_content;
$content = strtr($content, $placeholders);
$branding = Settings::get_mail_branding();
$footer = $cfg['global']['footer_text'];
if ($footer === '' && $branding['description'] !== '') {
$footer = $branding['description'];
}
if ($footer === '') {
$footer = get_bloginfo('name');
}
if ($footer !== '') {
$content .= "\n\n— " . $footer;
if ($branding['description'] !== '' && $footer !== $branding['description']) {
$content .= "\n" . $branding['description'];
}
}
$is_html = $cfg['html'] || ($cfg['global']['template_mode'] === 'html');
$headers = [$is_html ? 'Content-Type: text/html; charset=UTF-8' : 'Content-Type: text/plain; charset=UTF-8'];
return wp_mail($to, $subject, $content, $headers);
$definition = MailTemplates::definitions()[$event];
$subject = (string) ($cfg['subject'] ?? $default_subject ?: $definition['title']);
$content = (string) ($cfg['content'] ?? $default_content ?: $definition['content']);
$subject = self::replace_placeholders($subject, $placeholders, false);
$content = self::replace_placeholders($content, $placeholders, false);
$content = self::wrap_html($content, $cfg['global'] ?? []);
$headers = ['Content-Type: text/html; charset=UTF-8'];
return (bool) wp_mail($to, $subject, $content, $headers);
}
/**
* 构建单个邮件事件条目
*/
private static function build_email_event_entry(array $s, string $event): array {
public static function send_test_email(string $to): bool {
$to = sanitize_email($to);
if ($to === '' || !is_email($to)) return false;
$site = esc_html((string) get_bloginfo('name'));
$subject = '邮件配置测试';
$content = "您好,\n\n这是一封来自 {$site} 的邮件配置测试。";
$content = self::wrap_html($content, [
'description' => '此信为系统邮件,请不要直接回复。',
'more_content' => '',
]);
return (bool) wp_mail($to, $subject, $content, ['Content-Type: text/html; charset=UTF-8']);
}
/** @return array<string, mixed> */
private static function event_config(string $event, array $settings, array $defaults): array {
$definition = MailTemplates::definitions()[$event];
$subject_key = "email_{$event}_subject";
$content_key = "email_{$event}_content";
return [
'enabled' => !isset($s["email_{$event}_enabled"]) || !empty($s["email_{$event}_enabled"]),
'subject' => sanitize_text_field((string) ($s["email_{$event}_subject"] ?? '')),
'content' => sanitize_textarea_field((string) ($s["email_{$event}_content"] ?? '')),
'html' => !empty($s["email_{$event}_html"]),
'enabled' => !array_key_exists("email_{$event}_enabled", $settings) || !empty($settings["email_{$event}_enabled"]),
'subject' => trim((string) ($settings[$subject_key] ?? '')) ?: (string) ($defaults[$subject_key] ?? $definition['title']),
'content' => (string) ($settings[$content_key] ?? '') !== '' ? (string) $settings[$content_key] : (string) ($defaults[$content_key] ?? $definition['content']),
'placeholders' => $definition['placeholders'],
];
}
private static function replace_placeholders(string $value, array $placeholders, bool $html): string {
$replace = [];
foreach ($placeholders as $key => $replacement) {
$replacement = (string) $replacement;
if ($html) {
$replacement = str_contains($key, 'url') ? esc_url($replacement) : esc_html($replacement);
} else {
$replacement = wp_strip_all_tags($replacement);
}
$replace[$key] = $replacement;
}
return strtr($value, $replace);
}
private static function wrap_html(string $content, array $global): string {
$logo = method_exists(Settings::class, 'logo_url') ? Settings::logo_url() : '';
$background = esc_url(AKSRT_URI . '/assets/img/mail-bg.png');
$body = preg_match('/<[^>]+>/', $content) ? wp_kses_post($content) : nl2br(esc_html($content));
$description = esc_html((string) ($global['description'] ?? ''));
$more = wp_kses_post((string) ($global['more_content'] ?? ''));
$site = esc_html((string) get_bloginfo('name'));
$logo_html = $logo !== '' ? '<img src="' . esc_url($logo) . '" alt="' . $site . '" style="max-width:220px;height:auto;">' : '<strong style="font-size:24px;">' . $site . '</strong>';
return '<div style="background:#ecf1f3;padding:2%;font-family:Arial,sans-serif;color:#55798d;line-height:1.7;">'
. '<div style="max-width:701px;margin:0 auto;background:#fff;box-shadow:0 3px 12px rgba(0,0,0,.08);">'
. '<div style="padding:42px 10%;text-align:center;">' . $logo_html . '</div>'
. '<div style="padding:0 10% 42px;overflow-wrap:break-word;">' . $body . '</div>'
. '<div style="border-top:2px dashed rgba(125,136,140,.15);padding:34px 10% 0;">'
. '<strong style="font-size:20px;">' . $site . '</strong>'
. '<div style="margin:16px 0;white-space:pre-wrap;">' . $description . '</div>'
. '<div>' . $more . '</div></div>'
. '<img src="' . $background . '" alt="" style="display:block;width:100%;height:auto;margin-top:26px;">'
. '</div></div>';
}
}