- aksrt_ticket gains urge_count / last_urge_time (schema 3.1.0) - cooldown moves from transient to an atomic conditional UPDATE - urge inserts a system reply rendered as a centered notice (not quotable) - admin list badge + info card row for urge count / last time - new ticket_urge_admin mail event via aksrt_ticket_urged Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
259 lines
14 KiB
PHP
259 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Registers all non-forum email notifications.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Settings;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
final class MailNotifications {
|
|
public static function init(): void {
|
|
add_action('aksrt_order_paid', [self::class, 'order_paid'], 20, 2);
|
|
add_action('aksrt_order_status_changed', [self::class, 'order_status'], 20, 3);
|
|
add_action('aksrt_asset_transaction_committed', [self::class, 'asset_transaction'], 20, 2);
|
|
add_action('aksrt_notification_sent', [self::class, 'notification'], 20, 3);
|
|
add_action('aksrt_withdrawal_submitted', [self::class, 'withdrawal_submitted'], 20, 2);
|
|
add_action('aksrt_withdrawal_status_changed', [self::class, 'withdrawal_status'], 20, 4);
|
|
add_action('aksrt_ban_appeal_submitted', [self::class, 'ban_appeal_submitted'], 20, 2);
|
|
add_action('aksrt_ban_appeal_processed', [self::class, 'ban_appeal_processed'], 20, 3);
|
|
add_action('aksrt_new_posts_pending', [self::class, 'post_pending'], 20, 1);
|
|
add_action('transition_post_status', [self::class, 'post_status_changed'], 20, 3);
|
|
add_action('aksrt_report_submitted', [self::class, 'report_submitted'], 20, 2);
|
|
add_action('aksrt_report_status_changed', [self::class, 'report_status_changed'], 20, 4);
|
|
add_action('aksrt_friend_link_submitted', [self::class, 'friend_link_submitted'], 20, 1);
|
|
add_action('aksrt_user_phone_changed', [self::class, 'phone_changed'], 20, 2);
|
|
add_action('aksrt_user_restriction_changed', [self::class, 'restriction_changed'], 20, 4);
|
|
add_action('aksrt_ticket_urged', [self::class, 'ticket_urged'], 20, 1);
|
|
}
|
|
|
|
/**
|
|
* 催单邮件:已指派跟进人时只发给跟进人,否则发给全部管理员(与站内通知同一路由)。
|
|
*
|
|
* @param array<string,mixed> $ticket
|
|
*/
|
|
public static function ticket_urged(array $ticket): void {
|
|
$vars = [
|
|
'username' => (string) ($ticket['user_name'] ?? ''),
|
|
'count' => (int) ($ticket['urge_count'] ?? 0),
|
|
'ticket_num' => (string) ($ticket['ticket_num'] ?? ''),
|
|
'title' => (string) ($ticket['title'] ?? ''),
|
|
'url' => admin_url('users.php?page=aksrt_tickets&id=' . (int) ($ticket['id'] ?? 0)),
|
|
];
|
|
$assignee = get_userdata((int) ($ticket['admin_id'] ?? 0));
|
|
if ($assignee instanceof \WP_User && is_email($assignee->user_email)) {
|
|
self::send('ticket_urge_admin', $assignee->user_email, $vars);
|
|
return;
|
|
}
|
|
self::send_admin('ticket_urge_admin', $vars);
|
|
}
|
|
|
|
public static function restriction_changed(int $user_id, string $type, string $action, string $reason): void {
|
|
$user = get_userdata($user_id);
|
|
if (!$user instanceof \WP_User) return;
|
|
self::send('user_ban', $user->user_email, [
|
|
'username' => $user->display_name,
|
|
'status' => $action === 'removed' ? __('限制已解除', 'aksrt') : $type,
|
|
'note' => $reason,
|
|
]);
|
|
}
|
|
|
|
public static function phone_changed(int $user_id, string $phone): void {
|
|
$user = get_userdata($user_id);
|
|
if ($user instanceof \WP_User) self::send('phone_changed', $user->user_email, ['username' => $user->display_name, 'site_name' => get_bloginfo('name')]);
|
|
}
|
|
|
|
public static function withdrawal_submitted(int $user_id, array $row): void {
|
|
$user = get_userdata($user_id);
|
|
if (!$user instanceof \WP_User) return;
|
|
$vars = [
|
|
'username' => $user->display_name,
|
|
'withdrawal_number' => (string) ($row['withdrawal_number'] ?? ''),
|
|
'amount' => (string) ($row['gross_amount'] ?? $row['amount'] ?? ''),
|
|
'method' => (string) ($row['method'] ?? ''),
|
|
];
|
|
self::send('withdraw_submitted', $user->user_email, $vars);
|
|
self::send_admin('withdraw_submitted_admin', $vars);
|
|
}
|
|
|
|
public static function withdrawal_status(int $user_id, array $row, string $status, string $note): void {
|
|
$user = get_userdata($user_id);
|
|
if (!$user instanceof \WP_User) return;
|
|
self::send('withdraw_completed', $user->user_email, [
|
|
'username' => $user->display_name,
|
|
'withdrawal_number' => (string) ($row['withdrawal_number'] ?? ''),
|
|
'status' => $status,
|
|
'amount' => (string) ($row['gross_amount'] ?? $row['amount'] ?? ''),
|
|
'note' => $note,
|
|
]);
|
|
}
|
|
|
|
public static function order_paid(string $order_number, array $order): void {
|
|
$user = get_userdata((int) ($order['user_id'] ?? 0));
|
|
if (!$user instanceof \WP_User) return;
|
|
$vars = self::order_vars($order_number, $order, $user);
|
|
self::send('order_paid', $user->user_email, $vars);
|
|
self::send_admin('order_paid_admin', $vars);
|
|
}
|
|
|
|
|
|
public static function order_status(string $order_number, string $status, array $order): void {
|
|
if ($status === 'paid') return;
|
|
$user = get_userdata((int) ($order['user_id'] ?? 0));
|
|
if (!$user instanceof \WP_User) return;
|
|
self::send('order_status', $user->user_email, self::order_vars($order_number, $order, $user) + ['status' => $status]);
|
|
}
|
|
|
|
public static function asset_transaction($result, $command): void {
|
|
if (!$result instanceof \AKSRT\Ledger\LedgerResult || !$command instanceof \AKSRT\Ledger\LedgerCommand || $result->replayed) return;
|
|
$reason = $command->reason;
|
|
if (!in_array($reason, ['affiliate_commission', 'creator_income', 'transfer', 'tip', 'referral'], true)) return;
|
|
foreach ($result->entries as $entry) {
|
|
if ((int) ($entry['delta_minor'] ?? 0) <= 0) continue;
|
|
$user = get_userdata((int) ($entry['user_id'] ?? 0));
|
|
if (!$user instanceof \WP_User) continue;
|
|
$amount = self::minor_amount((string) ($entry['asset'] ?? 'balance'), (int) $entry['delta_minor']);
|
|
$url = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('credits') : home_url('/');
|
|
if ($reason === 'affiliate_commission') {
|
|
self::send('affiliate_commission', $user->user_email, ['username' => $user->display_name, 'amount' => $amount, 'order_number' => $command->ref_key, 'url' => $url]);
|
|
} elseif ($reason === 'creator_income') {
|
|
self::send('creator_income', $user->user_email, ['username' => $user->display_name, 'amount' => $amount, 'order_number' => $command->ref_key, 'url' => $url]);
|
|
} elseif ($reason === 'referral') {
|
|
self::send('referral_reward', $user->user_email, ['username' => $user->display_name, 'amount' => $amount, 'url' => $url]);
|
|
} elseif ($reason === 'transfer' && (string) ($entry['entry_type'] ?? '') === 'transfer_in') {
|
|
$sender = get_userdata((int) ($entry['counterparty_user_id'] ?? 0));
|
|
self::send('transfer_received', $user->user_email, [
|
|
'username' => $user->display_name,
|
|
'sender' => $sender instanceof \WP_User ? $sender->display_name : __('其他用户', 'aksrt'),
|
|
'asset' => ((string) ($entry['asset'] ?? '') === 'points') ? __('积分', 'aksrt') : __('余额', 'aksrt'),
|
|
'amount' => $amount,
|
|
'note' => (string) ($entry['remark'] ?? ''),
|
|
'url' => $url,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function notification(int $user_id, string $type, array $data): void {
|
|
$user = get_userdata($user_id);
|
|
if (!$user instanceof \WP_User) return;
|
|
$url = (string) ($data['target_url'] ?? $data['url'] ?? home_url('/'));
|
|
$title = (string) ($data['title'] ?? '');
|
|
$content = (string) ($data['description'] ?? $data['desc'] ?? '');
|
|
$map = [
|
|
'comment_reply' => 'comment_reply',
|
|
'comment_approved' => 'comment_approved',
|
|
];
|
|
if (!isset($map[$type])) return;
|
|
$event = $map[$type];
|
|
$vars = ['username' => $user->display_name, 'site_name' => get_bloginfo('name'), 'title' => $title, 'content' => $content, 'status' => $title, 'note' => $content, 'url' => $url, 'amount' => '', 'withdrawal_number' => ''];
|
|
self::send($event, $user->user_email, $vars);
|
|
}
|
|
|
|
public static function auth_submitted(int $user_id, array $application): void {
|
|
$user = get_userdata($user_id);
|
|
self::send_admin('auth_apply_admin', [
|
|
'username' => $user instanceof \WP_User ? $user->display_name : (string) ($application['public_name'] ?? __('用户', 'aksrt')),
|
|
'url' => admin_url('users.php?page=aksrt_users_verify'),
|
|
]);
|
|
}
|
|
|
|
public static function auth_reviewed(int $user_id, string $status, string $note, int $reviewer_id): void {
|
|
$user = get_userdata($user_id);
|
|
if ($user instanceof \WP_User) self::send('auth_apply_result', $user->user_email, ['username' => $user->display_name, 'status' => $status, 'note' => $note, 'url' => function_exists('aksrt_get_user_center_tab_url') ? aksrt_get_user_center_tab_url('auth') : home_url('/')]);
|
|
}
|
|
|
|
public static function ban_appeal_submitted(int $user_id, array $payload): void {
|
|
$user = get_userdata($user_id);
|
|
if ($user instanceof \WP_User) self::send_admin('ban_appeal_admin', ['username' => $user->display_name, 'url' => admin_url('users.php?page=aksrt_user_ban&type=appeal&status=pending')]);
|
|
}
|
|
|
|
public static function ban_appeal_processed(int $user_id, string $decision, string $note): void {
|
|
$user = get_userdata($user_id);
|
|
if ($user instanceof \WP_User) self::send('user_ban', $user->user_email, ['username' => $user->display_name, 'status' => $decision, 'note' => $note]);
|
|
}
|
|
|
|
public static function post_pending($post): void {
|
|
if (!$post instanceof \WP_Post) return;
|
|
$author = get_userdata((int) $post->post_author);
|
|
if ($author instanceof \WP_User) self::send_admin('post_pending_admin', ['username' => $author->display_name, 'title' => get_the_title($post), 'url' => get_edit_post_link($post->ID, '') ?: admin_url('edit.php')]);
|
|
}
|
|
|
|
public static function post_status_changed(string $new_status, string $old_status, \WP_Post $post): void {
|
|
if ($old_status === $new_status || !in_array($new_status, ['publish', 'draft', 'pending'], true)) return;
|
|
$author = get_userdata((int) $post->post_author);
|
|
if ($author instanceof \WP_User) {
|
|
self::send('post_result', $author->user_email, [
|
|
'username' => $author->display_name,
|
|
'title' => get_the_title($post),
|
|
'status' => $new_status,
|
|
'url' => get_permalink($post),
|
|
]);
|
|
}
|
|
}
|
|
|
|
public static function report_submitted(int $report_id, array $report): void {
|
|
$reporter = get_userdata((int) ($report['reporter_id'] ?? 0));
|
|
self::send_admin('report_admin', ['username' => $reporter instanceof \WP_User ? $reporter->display_name : __('用户', 'aksrt'), 'title' => (string) ($report['target_type'] ?? ''), 'url' => admin_url('users.php?page=aksrt_user_ban')]);
|
|
}
|
|
|
|
public static function report_status_changed(int $report_id, int $reporter_id, string $status, string $note = ''): void {
|
|
$reporter = get_userdata($reporter_id);
|
|
if (!$reporter instanceof \WP_User) {
|
|
return;
|
|
}
|
|
// 状态值中文化后进入邮件;处理说明来自后台“处理说明”输入(钩子第 4 参)。
|
|
$labels = [
|
|
'pending' => __('待处理', 'aksrt'),
|
|
'reviewing' => __('审核中', 'aksrt'),
|
|
'resolved' => __('已解决', 'aksrt'),
|
|
'dismissed' => __('已驳回', 'aksrt'),
|
|
];
|
|
self::send('report_result', $reporter->user_email, [
|
|
'username' => $reporter->display_name,
|
|
'status' => $labels[$status] ?? $status,
|
|
'note' => $note !== '' ? $note : __('—', 'aksrt'),
|
|
'url' => home_url('/'),
|
|
]);
|
|
}
|
|
|
|
public static function friend_link_submitted(array $link): void {
|
|
self::send_admin('links_submit_admin', ['title' => (string) ($link['name'] ?? ''), 'username' => __('访客', 'aksrt'), 'url' => (string) ($link['url'] ?? '')]);
|
|
}
|
|
|
|
/** @param array<string,mixed> $vars */
|
|
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);
|
|
}
|
|
|
|
/** @param array<string,mixed> $vars */
|
|
private static function send_admin(string $event, array $vars): void {
|
|
foreach (self::admin_emails() as $email) self::send($event, $email, $vars);
|
|
}
|
|
|
|
/** @return list<string> */
|
|
private static function admin_emails(): array {
|
|
$emails = [];
|
|
foreach (get_users(['role' => 'administrator', 'fields' => ['user_email'], 'number' => 100]) as $user) {
|
|
$email = sanitize_email((string) ($user->user_email ?? ''));
|
|
if ($email !== '' && is_email($email)) $emails[$email] = true;
|
|
}
|
|
return array_keys($emails);
|
|
}
|
|
|
|
/** @return array<string,string> */
|
|
private static function order_vars(string $order_number, array $order, \WP_User $user): array {
|
|
$product = (string) ($order['product_type'] ?? 'order');
|
|
return ['username' => $user->display_name, 'order_number' => $order_number, 'product' => $product, 'amount' => (string) ($order['amount'] ?? ''), 'currency' => (string) ($order['currency'] ?? 'CNY'), 'payment_method' => (string) ($order['payment_method'] ?? ''), 'url' => function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('orders') : home_url('/')];
|
|
}
|
|
|
|
private static function minor_amount(string $asset, int $minor): string {
|
|
if ($asset === 'points') return (string) $minor;
|
|
return number_format($minor / 100, 2, '.', '');
|
|
}
|
|
}
|