HIGH 级别修复: - WebhookHandler: 添加 SSRF 防护 (Validator::is_valid_url) - AIGateway: 自定义端点添加 SSRF 验证 - AIGateway: 白名单域名匹配改为大小写不敏感 - CommercialManager: 文件读取添加路径遍历防护 (realpath) - GroupModel: from_array() 添加输入类型验证和清理 - GroupManager: add/remove_source_to_group 添加权限检查 MEDIUM 级别修复: - NotificationManager: 添加 5 分钟速率限制防止通知轰炸 - NotificationManager: 允许第三方扩展通知处理器 - EmailHandler: 添加收件人邮箱格式验证 - EmailHandler: 移除自定义 From 头避免 SPF/DKIM 问题 - CommercialManager: lock/unlock_version 添加权限检查 - CommercialManager: 商业插件列表支持过滤器扩展 - GroupManager: toggle 方法改进原子性,先更新分组再更新源 - AbstractAdapter: 正则匹配添加错误处理 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
181 lines
5.1 KiB
PHP
181 lines
5.1 KiB
PHP
<?php
|
|
/**
|
|
* 邮件通知处理器
|
|
*
|
|
* @package WPBridge
|
|
*/
|
|
|
|
namespace WPBridge\Notification;
|
|
|
|
use WPBridge\Core\Settings;
|
|
|
|
// 防止直接访问
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* 邮件通知处理器类
|
|
*/
|
|
class EmailHandler implements HandlerInterface {
|
|
|
|
/**
|
|
* 设置实例
|
|
*
|
|
* @var Settings
|
|
*/
|
|
private Settings $settings;
|
|
|
|
/**
|
|
* 支持的通知类型
|
|
*
|
|
* @var array
|
|
*/
|
|
private array $supported_types = [ 'update', 'error', 'recovery' ];
|
|
|
|
/**
|
|
* 构造函数
|
|
*
|
|
* @param Settings $settings 设置实例
|
|
*/
|
|
public function __construct( Settings $settings ) {
|
|
$this->settings = $settings;
|
|
}
|
|
|
|
/**
|
|
* 获取处理器名称
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_name(): string {
|
|
return 'email';
|
|
}
|
|
|
|
/**
|
|
* 是否启用
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_enabled(): bool {
|
|
$notification_settings = $this->settings->get( 'notifications', [] );
|
|
return ! empty( $notification_settings['email']['enabled'] );
|
|
}
|
|
|
|
/**
|
|
* 是否支持该通知类型
|
|
*
|
|
* @param string $type 通知类型
|
|
* @return bool
|
|
*/
|
|
public function supports_type( string $type ): bool {
|
|
$notification_settings = $this->settings->get( 'notifications', [] );
|
|
$enabled_types = $notification_settings['email']['types'] ?? $this->supported_types;
|
|
|
|
return in_array( $type, $enabled_types, true );
|
|
}
|
|
|
|
/**
|
|
* 发送通知
|
|
*
|
|
* @param string $subject 主题
|
|
* @param string $message 消息
|
|
* @param array $data 附加数据
|
|
* @throws \Exception 发送失败时抛出异常
|
|
*/
|
|
public function send( string $subject, string $message, array $data = [] ): void {
|
|
$notification_settings = $this->settings->get( 'notifications', [] );
|
|
$recipients = $notification_settings['email']['recipients'] ?? [];
|
|
|
|
if ( empty( $recipients ) ) {
|
|
// 默认发送给管理员
|
|
$recipients = [ get_option( 'admin_email' ) ];
|
|
}
|
|
|
|
// 验证收件人邮箱格式
|
|
$valid_recipients = array_filter( $recipients, function ( $email ) {
|
|
return is_email( $email );
|
|
} );
|
|
|
|
if ( empty( $valid_recipients ) ) {
|
|
throw new \Exception( __( '没有有效的收件人邮箱', 'wpbridge' ) );
|
|
}
|
|
|
|
// 构建 HTML 邮件
|
|
$html_message = $this->build_html_message( $subject, $message, $data );
|
|
|
|
// 使用 WordPress 默认发件人,避免 SPF/DKIM 问题
|
|
$headers = [
|
|
'Content-Type: text/html; charset=UTF-8',
|
|
];
|
|
|
|
$sent = wp_mail( $valid_recipients, $subject, $html_message, $headers );
|
|
|
|
if ( ! $sent ) {
|
|
throw new \Exception( __( '邮件发送失败', 'wpbridge' ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 构建 HTML 邮件内容
|
|
*
|
|
* @param string $subject 主题
|
|
* @param string $message 消息
|
|
* @param array $data 附加数据
|
|
* @return string
|
|
*/
|
|
private function build_html_message( string $subject, string $message, array $data ): string {
|
|
$site_name = get_bloginfo( 'name' );
|
|
$site_url = get_site_url();
|
|
|
|
$html = '<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>' . esc_html( $subject ) . '</title>
|
|
<style>
|
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; line-height: 1.6; color: #333; }
|
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
.header { background: #0073aa; color: white; padding: 20px; text-align: center; }
|
|
.content { padding: 20px; background: #f9f9f9; }
|
|
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
|
|
.data-table { width: 100%; border-collapse: collapse; margin-top: 15px; }
|
|
.data-table th, .data-table td { padding: 8px; border: 1px solid #ddd; text-align: left; }
|
|
.data-table th { background: #f0f0f0; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>WPBridge</h1>
|
|
</div>
|
|
<div class="content">
|
|
<p>' . nl2br( esc_html( $message ) ) . '</p>';
|
|
|
|
// 添加数据表格
|
|
if ( ! empty( $data ) && ! isset( $data['test'] ) ) {
|
|
$html .= '<table class="data-table">';
|
|
foreach ( $data as $key => $value ) {
|
|
if ( is_scalar( $value ) ) {
|
|
$html .= '<tr><th>' . esc_html( $key ) . '</th><td>' . esc_html( $value ) . '</td></tr>';
|
|
}
|
|
}
|
|
$html .= '</table>';
|
|
}
|
|
|
|
$html .= '
|
|
</div>
|
|
<div class="footer">
|
|
<p>' . sprintf(
|
|
/* translators: %s: site name */
|
|
esc_html__( '此邮件由 %s 的 WPBridge 插件发送', 'wpbridge' ),
|
|
esc_html( $site_name )
|
|
) . '</p>
|
|
<p><a href="' . esc_url( $site_url ) . '">' . esc_html( $site_url ) . '</a></p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>';
|
|
|
|
return $html;
|
|
}
|
|
}
|