Merge pull request 'fix(email): lock templates to built-in catalog' (#32) from worktree/email-config-hard-cut into main

Reviewed-on: #32
This commit was merged in pull request #32.
This commit is contained in:
2026-09-13 12:04:57 +00:00
10 changed files with 61 additions and 111 deletions
+4 -2
View File
@@ -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 ]
);
}
+2 -6
View File
@@ -541,15 +541,11 @@ 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;
}
$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;
if (!$sent) {
delete_transient($this->auth_code_key($scene, $target));
if ($scene !== 'register') {
+1 -1
View File
@@ -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('/'),
-1
View File
@@ -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(),
+2 -25
View File
@@ -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
View File
@@ -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 {
+1 -1
View File
@@ -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 */
-4
View File
@@ -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',/)