143 lines
6.8 KiB
PHP
143 lines
6.8 KiB
PHP
<?php
|
|
/**
|
|
* Canonical email settings and delivery service.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Settings;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
final class Mail {
|
|
/** @return array<string, mixed> */
|
|
public static function get_smtp(): array {
|
|
$s = Settings::get_all();
|
|
$username = trim((string) ($s['mail_smtp_name'] ?? '')) ?: sanitize_email((string) ($s['mail_name'] ?? ''));
|
|
$password = (string) ($s['mail_passwd'] ?? '');
|
|
return [
|
|
'enabled' => !empty($s['mail_smtps']),
|
|
'host' => trim((string) ($s['mail_host'] ?? '')),
|
|
'port' => max(1, min(65535, (int) ($s['mail_port'] ?? 465))),
|
|
'username' => $username,
|
|
'password' => $password,
|
|
'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' => $username !== '' && $password !== '',
|
|
];
|
|
}
|
|
|
|
/** Send a fixed catalog event. */
|
|
public static function send_event_email(string $event, string $to, array $placeholders = []): bool {
|
|
$definitions = MailTemplates::definitions();
|
|
$to = sanitize_email($to);
|
|
if ($to === '' || !is_email($to) || !isset($definitions[$event]) || !self::is_email_event_enabled($event)) {
|
|
return false;
|
|
}
|
|
$definition = $definitions[$event];
|
|
$subject = self::replace_placeholders((string) $definition['title'], $placeholders);
|
|
$content = self::replace_placeholders((string) $definition['content'], $placeholders);
|
|
$content = self::wrap_html($content, self::get_global_config());
|
|
$headers = ['Content-Type: text/html; charset=UTF-8'];
|
|
return (bool) wp_mail($to, $subject, $content, $headers);
|
|
}
|
|
|
|
public static function send_test_email(string $to): bool|\WP_Error {
|
|
$to = sanitize_email($to);
|
|
if ($to === '' || !is_email($to)) {
|
|
return new \WP_Error('aksrt_invalid_test_email', __('请输入有效的邮箱地址', 'aksrt'));
|
|
}
|
|
|
|
$failure = null;
|
|
$capture_failure = static function (\WP_Error $error) use (&$failure): void {
|
|
$failure = $error;
|
|
};
|
|
add_action('wp_mail_failed', $capture_failure);
|
|
|
|
$site = esc_html((string) get_bloginfo('name'));
|
|
$subject = '邮件配置测试';
|
|
$content = "您好,\n\n这是一封来自 {$site} 的邮件配置测试。";
|
|
$content = self::wrap_html($content, [
|
|
'description' => '此信为系统邮件,请不要直接回复。',
|
|
'more_content' => '',
|
|
]);
|
|
|
|
try {
|
|
$sent = (bool) wp_mail($to, $subject, $content, ['Content-Type: text/html; charset=UTF-8']);
|
|
} finally {
|
|
remove_action('wp_mail_failed', $capture_failure);
|
|
}
|
|
|
|
if ($sent) {
|
|
return true;
|
|
}
|
|
|
|
$error = $failure instanceof \WP_Error
|
|
? $failure
|
|
: new \WP_Error('aksrt_mail_failed', __('wp_mail 返回失败,但未提供详细错误。', 'aksrt'));
|
|
self::log_test_failure($error);
|
|
return $error;
|
|
}
|
|
|
|
private static function is_email_event_enabled(string $event): bool {
|
|
$settings = Settings::get_all();
|
|
return !array_key_exists("email_{$event}_enabled", $settings) || !empty($settings["email_{$event}_enabled"]);
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
private static function get_global_config(): array {
|
|
$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]);
|
|
}
|
|
}
|
|
return $global;
|
|
}
|
|
|
|
private static function replace_placeholders(string $value, array $placeholders): string {
|
|
$replace = [];
|
|
foreach ($placeholders as $key => $replacement) {
|
|
$replace[$key] = wp_strip_all_tags((string) $replacement);
|
|
}
|
|
return strtr($value, $replace);
|
|
}
|
|
|
|
private static function log_test_failure(\WP_Error $error): void {
|
|
$message = sanitize_text_field($error->get_error_message());
|
|
$message = preg_replace('/[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}/i', '[email]', $message);
|
|
$message = preg_replace('/\b(?:[a-z0-9-]+\.)+[a-z]{2,}\b/i', '[host]', (string) $message);
|
|
$message = preg_replace('/\b(?:\d{1,3}\.){3}\d{1,3}\b/', '[ip]', (string) $message);
|
|
$message = preg_replace('/\b(?:password|passwd|username|user|login|credential)\s*[:=]\s*[^\s,;]+/i', '$1=[redacted]', (string) $message);
|
|
error_log('[AKSRT] SMTP 测试失败:' . substr((string) $message, 0, 500));
|
|
}
|
|
|
|
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>';
|
|
}
|
|
}
|