fix(email): lock templates to built-in catalog #32
@@ -152,7 +152,8 @@ add_action('wp_ajax_aksrt_send_email_code', function () {
|
||||
|
||||
$site_name = get_bloginfo( 'name' );
|
||||
$sent = \AKSRT\Settings\Settings::send_event_email(
|
||||
'change_code', $target_email, '', '',
|
||||
'change_code',
|
||||
$target_email,
|
||||
[ '{username}' => $current_user->display_name, '{site_name}' => $site_name, '{code}' => $code ]
|
||||
);
|
||||
|
||||
@@ -294,7 +295,8 @@ add_action('wp_ajax_aksrt_change_email', function () {
|
||||
if ( is_email( $old_email ) ) {
|
||||
$site_name = get_bloginfo( 'name' );
|
||||
\AKSRT\Settings\Settings::send_event_email(
|
||||
'change_notice', $old_email, '', '',
|
||||
'change_notice',
|
||||
$old_email,
|
||||
[ '{username}' => $current_user->display_name, '{site_name}' => $site_name, '{new_email}' => $new_email ]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -540,16 +540,12 @@ class SchemaProvider {
|
||||
|
||||
$data = $this->collect_defaults($schema);
|
||||
foreach ($this->get_all_fields($schema) as $field) {
|
||||
if (array_key_exists($field['key'], $stored)) {
|
||||
$stored_value = $stored[$field['key']];
|
||||
$is_mail_template = $option_key === 'aksrt_settings'
|
||||
&& preg_match('/^email_.+_(subject|content)$/', (string) $field['key']);
|
||||
if (!$is_mail_template || trim((string) $stored_value) !== '') {
|
||||
$data[$field['key']] = $stored_value;
|
||||
}
|
||||
}
|
||||
if (array_key_exists($field['key'], $stored)) {
|
||||
$data[$field['key']] = $stored[$field['key']];
|
||||
}
|
||||
|
||||
$stored_value = array_key_exists($field['key'], $stored) ? $stored[$field['key']] : ($data[$field['key']] ?? '');
|
||||
|
||||
if (!empty($field['secret'])) {
|
||||
$data[$field['key']] = trim((string) $stored_value) !== '' ? '***' : '';
|
||||
}
|
||||
|
||||
@@ -205,9 +205,9 @@ trait UserControllerAuthPhoneOperations {
|
||||
'{code}' => $code,
|
||||
];
|
||||
$sent = $scene_event !== '' && class_exists('\\AKSRT\\Settings\\Settings')
|
||||
? \AKSRT\Settings\Settings::send_event_email($scene_event, $target, '', '', $placeholders)
|
||||
? \AKSRT\Settings\Settings::send_event_email($scene_event, $target, $placeholders)
|
||||
: false;
|
||||
|
||||
: false;
|
||||
if (!$sent) {
|
||||
delete_transient($this->auth_code_key($scene, $target));
|
||||
if ($scene !== 'register') {
|
||||
|
||||
@@ -136,7 +136,7 @@ trait ServicePart1a {
|
||||
return ['success' => false, 'error' => __('注册成功,但读取用户信息失败,请联系管理员', 'aksrt')];
|
||||
}
|
||||
if ($user instanceof \WP_User && is_email($user->user_email)) {
|
||||
\AKSRT\Settings\Mail::send_event_email('reg_success', $user->user_email, '', '', [
|
||||
\AKSRT\Settings\Mail::send_event_email('reg_success', $user->user_email, [
|
||||
'{username}' => $user->display_name,
|
||||
'{site_name}' => get_bloginfo('name'),
|
||||
'{url}' => function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/'),
|
||||
|
||||
@@ -193,7 +193,6 @@ trait DisplayPageGroups {
|
||||
'trackcode' => self::trackcode(),
|
||||
'seo' => self::get_seo_settings(),
|
||||
'mail' => self::get_smtp(),
|
||||
'email_events' => self::get_email_event_config(),
|
||||
'footer' => self::get_footer_settings(),
|
||||
'tabbar' => self::get_footer_tabbar_settings(),
|
||||
'floating_buttons' => self::get_floating_buttons_settings(),
|
||||
|
||||
@@ -40,34 +40,11 @@ trait DomainGroups {
|
||||
return 'template_1';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邮件事件配置
|
||||
*
|
||||
* @param string $event 事件类型,留空返回全部
|
||||
* @return array 单个事件配置或全部事件数组
|
||||
*/
|
||||
public static function get_email_event_config(string $event = ''): array {
|
||||
return Mail::get_email_event_config($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮件事件是否启用
|
||||
*/
|
||||
public static function is_email_event_enabled(string $event): bool {
|
||||
return Mail::is_email_event_enabled($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过邮件事件体系发送邮件
|
||||
*/
|
||||
public static function send_event_email(
|
||||
string $event,
|
||||
string $to,
|
||||
string $default_subject,
|
||||
string $default_content,
|
||||
array $placeholders = []
|
||||
): bool {
|
||||
return Mail::send_event_email($event, $to, $default_subject, $default_content, $placeholders);
|
||||
public static function send_event_email(string $event, string $to, array $placeholders = []): bool {
|
||||
return Mail::send_event_email($event, $to, $placeholders);
|
||||
}
|
||||
|
||||
/** 禁封设置 */
|
||||
|
||||
+27
-66
@@ -28,66 +28,17 @@ final class Mail {
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public static function get_email_event_config(string $event = ''): array {
|
||||
/** Send a fixed catalog event. */
|
||||
public static function send_event_email(string $event, string $to, array $placeholders = []): bool {
|
||||
$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 (array_keys($definitions) as $key) {
|
||||
$events[$key] = self::event_config($key, $settings, $defaults);
|
||||
}
|
||||
return ['global' => $global, 'events' => $events];
|
||||
}
|
||||
if (!isset($definitions[$event])) {
|
||||
return ['enabled' => false, 'subject' => '', 'content' => '', 'placeholders' => [], 'global' => $global];
|
||||
}
|
||||
return array_merge(self::event_config($event, $settings, $defaults), ['global' => $global]);
|
||||
}
|
||||
|
||||
public static function is_email_event_enabled(string $event): bool {
|
||||
$definitions = MailTemplates::definitions();
|
||||
if (!isset($definitions[$event])) {
|
||||
return false;
|
||||
}
|
||||
$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 = '',
|
||||
array $placeholders = []
|
||||
): bool {
|
||||
$to = sanitize_email($to);
|
||||
if ($to === '' || !is_email($to) || !isset(MailTemplates::definitions()[$event]) || !self::is_email_event_enabled($event)) {
|
||||
if ($to === '' || !is_email($to) || !isset($definitions[$event]) || !self::is_email_event_enabled($event)) {
|
||||
return false;
|
||||
}
|
||||
$cfg = self::get_email_event_config($event);
|
||||
$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);
|
||||
$content = self::replace_placeholders($content, $placeholders);
|
||||
$content = self::wrap_html($content, $cfg['global'] ?? []);
|
||||
$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);
|
||||
}
|
||||
@@ -129,17 +80,27 @@ final class Mail {
|
||||
return $error;
|
||||
}
|
||||
|
||||
/** @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' => !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 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 {
|
||||
|
||||
@@ -192,7 +192,7 @@ final class MailNotifications {
|
||||
private static function send(string $event, string $to, array $vars): void {
|
||||
$placeholders = [];
|
||||
foreach ($vars as $key => $value) $placeholders['{' . $key . '}'] = (string) $value;
|
||||
Mail::send_event_email($event, $to, '', '', $placeholders);
|
||||
Mail::send_event_email($event, $to, $placeholders);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $vars */
|
||||
|
||||
@@ -70,8 +70,6 @@ final class MailTemplates {
|
||||
];
|
||||
foreach ($definitions as $event => $definition) {
|
||||
$defaults["email_{$event}_enabled"] = (bool) $definition['enabled'];
|
||||
$defaults["email_{$event}_subject"] = $definition['title'];
|
||||
$defaults["email_{$event}_content"] = $definition['content'];
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
@@ -114,8 +112,6 @@ final class MailTemplates {
|
||||
foreach ($events as $event) {
|
||||
$definition = $definitions[$event];
|
||||
$fields[] = ['key' => "email_{$event}_enabled", 'label' => "{$definition['title']}:启用", 'type' => 'switch', 'default' => $defaults["email_{$event}_enabled"]];
|
||||
$fields[] = ['key' => "email_{$event}_subject", 'label' => "{$definition['title']}:主题模板", 'type' => 'input', 'default' => $defaults["email_{$event}_subject"]];
|
||||
$fields[] = ['key' => "email_{$event}_content", 'label' => "{$definition['title']}:正文模板", 'type' => 'textarea', 'rows' => 6, 'sanitize_type' => 'html', 'default' => $defaults["email_{$event}_content"], 'help' => '可用占位符:' . implode('、', array_map(static fn(string $p): string => '{' . $p . '}', $definition['placeholders']))];
|
||||
}
|
||||
return ['key' => $key, 'title' => $title, 'fields' => $fields];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const read = (file) => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
|
||||
const templates = read('src/Settings/MailTemplates.php')
|
||||
const mail = read('src/Settings/Mail.php')
|
||||
const schemaProvider = read('src/Admin/SchemaProvider.php')
|
||||
const notifications = read('src/Settings/MailNotifications.php')
|
||||
const coreHooks = read('inc/core-hooks.php')
|
||||
const globalController = read('src/Admin/GlobalController.php')
|
||||
@@ -34,9 +35,10 @@ test('forum-only events stay out of the email catalog', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('email templates and SMTP use one aksrt_settings schema', () => {
|
||||
test('email settings keep one canonical SMTP schema', () => {
|
||||
assert.match(templates, /'option_key'\s*=>\s*'aksrt_settings'/)
|
||||
assert.match(templates, /'mail_passwd'.*'secret'\s*=>\s*true/)
|
||||
assert.match(templates, /'email_test_send'.*'ui_only'\s*=>\s*true/)
|
||||
assert.match(mail, /Settings::get_all\(\)/)
|
||||
assert.match(mail, /'auth'\s*=>\s*\$username\s*!==\s*''\s*&&\s*\$password\s*!==\s*''/)
|
||||
assert.doesNotMatch(templates, /mail_smtpauth/)
|
||||
@@ -46,6 +48,23 @@ test('email templates and SMTP use one aksrt_settings schema', () => {
|
||||
assert.doesNotMatch(userController, /aksrt_mail_templates|mail-templates/)
|
||||
})
|
||||
|
||||
test('email event schema exposes only notification switches', () => {
|
||||
assert.match(templates, /private static function event_group\([^)]*\): array/)
|
||||
assert.match(templates, /\$fields\[\] = \['key' => "email_\{\$event\}_enabled"/)
|
||||
assert.doesNotMatch(templates, /email_\{\$event\}_subject/)
|
||||
assert.doesNotMatch(templates, /email_\{\$event\}_content/)
|
||||
assert.doesNotMatch(templates, /email_[A-Za-z0-9_]+_(subject|content)/)
|
||||
assert.doesNotMatch(templates, /主题模板|正文模板|可用占位符/)
|
||||
assert.doesNotMatch(schemaProvider, /is_mail_template|email_.+_\(subject\|content\)/)
|
||||
})
|
||||
|
||||
test('mail runtime always uses the built-in event catalog', () => {
|
||||
assert.match(mail, /replace_placeholders\(\(string\) \$definition\['title'\], \$placeholders\)/)
|
||||
assert.match(mail, /replace_placeholders\(\(string\) \$definition\['content'\], \$placeholders\)/)
|
||||
assert.doesNotMatch(mail, /\$subject_key|\$content_key|default_subject|default_content/)
|
||||
assert.doesNotMatch(mail, /get_email_event_config\(\)/)
|
||||
})
|
||||
|
||||
test('SMTP authentication and failures use the canonical mail service', () => {
|
||||
assert.match(coreHooks, /add_filter\('wp_mail_from',/)
|
||||
assert.match(coreHooks, /add_filter\('wp_mail_from_name',/)
|
||||
|
||||
Reference in New Issue
Block a user