修复了一些问题

This commit is contained in:
2026-08-25 17:57:20 +08:00
parent 8eb0079bc4
commit dbb1f776f2
19 changed files with 785 additions and 634 deletions
+1
View File
@@ -76,6 +76,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-shortcodes.php';
require_once AKSRT_DIR . '/inc/modules/captcha.php';
require_once AKSRT_DIR . '/inc/modules/user-hub.php';
require_once AKSRT_DIR . '/inc/modules/user-notifications.php';
require_once AKSRT_DIR . '/inc/modules/message-center.php';
require_once AKSRT_DIR . '/inc/modules/creator-revenue.php';
// Stage 3: core hooks
+291
View File
@@ -0,0 +1,291 @@
<?php
/**
* Message center query and server-side fragment renderers.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
/** @return array<string,string> */
function aksrt_message_center_views(): array {
return [
'unread' => __('未读消息', 'aksrt'),
'posts' => __('文章评论', 'aksrt'),
'like' => __('点赞喜欢', 'aksrt'),
'system' => __('系统通知', 'aksrt'),
];
}
function aksrt_normalize_message_view(string $view): string {
$view = sanitize_key($view);
return array_key_exists($view, aksrt_message_center_views()) ? $view : 'unread';
}
function aksrt_message_category_for_type(string $type): string {
$type = sanitize_key($type);
if (str_starts_with($type, 'asset_')) {
return '';
}
if (
in_array($type, ['like', 'post_like', 'comment_like', 'follow', 'followed', 'hot'], true)
|| str_starts_with($type, 'like_')
|| str_starts_with($type, 'follow_')
|| str_starts_with($type, 'followed_')
) {
return 'like';
}
if (
in_array($type, ['posts', 'post', 'comment', 'comment_reply', 'comment_approved', 'pending_comments', 'favorite', 'post_favorite'], true)
|| str_starts_with($type, 'comment_')
|| str_starts_with($type, 'favorite_')
) {
return 'posts';
}
return 'system';
}
/** @return array<int,array<string,mixed>> */
function aksrt_get_message_center_items(int $user_id, string $view): array {
$view = aksrt_normalize_message_view($view);
$items = function_exists('aksrt_get_user_all_notifications')
? aksrt_get_user_all_notifications($user_id)
: [];
return array_values(array_filter($items, static function (array $item) use ($view): bool {
$category = aksrt_message_category_for_type((string) ($item['type'] ?? 'notice'));
if ($category === '') {
return false;
}
if ($view === 'unread') {
return empty($item['read']);
}
return $category === $view;
}));
}
/** @return array{unread:int,posts:int,like:int,system:int} */
function aksrt_get_message_center_counts(int $user_id): array {
$counts = ['unread' => 0, 'posts' => 0, 'like' => 0, 'system' => 0];
$items = function_exists('aksrt_get_user_all_notifications')
? aksrt_get_user_all_notifications($user_id)
: [];
foreach ($items as $item) {
$category = aksrt_message_category_for_type((string) ($item['type'] ?? 'notice'));
if ($category === '' || !empty($item['read'])) {
continue;
}
$counts['unread']++;
$counts[$category]++;
}
return $counts;
}
/** @return array<string,mixed>|null */
function aksrt_get_message_center_item(int $user_id, int $notification_id): ?array {
if ($user_id <= 0 || $notification_id <= 0) {
return null;
}
global $wpdb;
$table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('notification') : '';
if ($table === '') return null;
$row = $wpdb->get_row($wpdb->prepare(
"SELECT id, type, title, description, target_url, is_read, created_at, read_at FROM {$table} WHERE id = %d AND user_id = %d",
$notification_id,
$user_id
), ARRAY_A);
if (!is_array($row) || aksrt_message_category_for_type((string) ($row['type'] ?? 'notice')) === '') return null;
return [
'id' => (string) $row['id'],
'type' => (string) $row['type'],
'title' => (string) $row['title'],
'desc' => (string) ($row['description'] ?? ''),
'url' => (string) ($row['target_url'] ?? ''),
'read' => !empty($row['is_read']) ? 1 : 0,
'created_at' => (string) ($row['created_at'] ?? ''),
'read_at' => (string) ($row['read_at'] ?? ''),
];
}
function aksrt_message_icon_class(string $type): string {
$icons = [
'comment_reply' => 'fa fas fa-comment-dots',
'comment_approved' => 'fa fas fa-check-circle',
'pending_comments' => 'fa fas fa-clock',
'post_favorite' => 'fa fas fa-star',
'post_like' => 'fa fas fa-heart',
'comment_like' => 'fa fas fa-thumbs-up',
'followed' => 'fa fas fa-user-plus',
'download_ready' => 'fa fas fa-download',
'upload_moderation'=> 'fa fas fa-image',
'verify_email' => 'fa fas fa-envelope-open',
'pay' => 'fa fas fa-file-alt',
'order_paid' => 'fa fas fa-file-alt',
'order_refund' => 'fa fas fa-undo',
'vip' => 'fa fas fa-certificate',
'pay_vip' => 'fa fas fa-certificate',
'medal' => 'fa fas fa-certificate',
'badge' => 'fa fas fa-certificate',
'ban_appeal' => 'fa fas fa-gavel',
'user_ban' => 'fa fas fa-ban',
'auth_apply' => 'fa fas fa-id-badge',
'auth_apply_reply' => 'fa fas fa-id-badge',
];
return $icons[$type] ?? 'fa fas fa-bell';
}
function aksrt_message_time(array $item): string {
if (!empty($item['stateful'])) {
return __('状态提醒', 'aksrt');
}
$created_at = (string) ($item['created_at'] ?? '');
return $created_at !== '' ? get_date_from_gmt($created_at, 'Y-m-d H:i') : '';
}
function aksrt_render_message_center_nav(string $view, array $counts): void {
$view = aksrt_normalize_message_view($view);
?>
<ul class="messages-nav__list">
<?php foreach (aksrt_message_center_views() as $key => $label) : ?>
<li>
<a href="<?php echo esc_url(aksrt_get_messages_page_url($key)); ?>"
class="messages-nav__item<?php echo $view === $key ? ' is-active' : ''; ?>"
data-message-view="<?php echo esc_attr($key); ?>"
<?php echo $view === $key ? 'aria-current="page"' : ''; ?>>
<span><?php echo esc_html($label); ?></span>
<span class="messages-nav__badge" data-type="<?php echo esc_attr($key); ?>" data-count="<?php echo esc_attr((string) ($counts[$key] ?? 0)); ?>"><?php echo esc_html((string) ($counts[$key] ?? 0)); ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
<div class="messages-nav__actions">
<button type="button" class="messages-nav__action-btn js-msg-mark-all-read"><?php esc_html_e('全部已读', 'aksrt'); ?></button>
<button type="button" class="messages-nav__action-btn js-msg-clear-read"><?php esc_html_e('清除已读', 'aksrt'); ?></button>
</div>
<?php
}
function aksrt_render_message_item(array $item, string $view): void {
$type = (string) ($item['type'] ?? 'notice');
$id = (string) ($item['id'] ?? '');
$title = (string) ($item['title'] ?? '');
$description = (string) ($item['desc'] ?? '');
$target_url = (string) ($item['url'] ?? '');
$is_read = !empty($item['read']);
$is_stateful = !empty($item['stateful']);
?>
<article class="message-item<?php echo $is_read ? '' : ' is-unread'; ?>" data-notification-id="<?php echo esc_attr($id); ?>">
<span class="message-item__icon"><i class="<?php echo esc_attr(aksrt_message_icon_class($type)); ?>" aria-hidden="true"></i></span>
<div class="message-item__body">
<?php if (!$is_stateful) : ?>
<button type="button" class="message-item__title js-msg-detail" data-id="<?php echo esc_attr($id); ?>" data-view="<?php echo esc_attr($view); ?>">
<?php echo esc_html($title); ?>
<?php if (!$is_read) : ?><span class="message-item__new-badge"><?php esc_html_e('新', 'aksrt'); ?></span><?php endif; ?>
</button>
<?php else : ?>
<div class="message-item__title"><?php echo esc_html($title); ?></div>
<?php endif; ?>
<?php if ($description !== '') : ?><div class="message-item__desc"><?php echo esc_html($description); ?></div><?php endif; ?>
<div class="message-item__time"><?php echo esc_html(aksrt_message_time($item)); ?></div>
</div>
<div class="message-item__actions">
<?php if ($is_stateful && $target_url !== '') : ?><a href="<?php echo esc_url($target_url); ?>" class="message-item__action-btn"><?php esc_html_e('立即处理', 'aksrt'); ?></a><?php endif; ?>
<?php if (!$is_stateful) : ?><button type="button" class="message-item__action-btn js-msg-detail" data-id="<?php echo esc_attr($id); ?>" data-view="<?php echo esc_attr($view); ?>"><?php esc_html_e('详情', 'aksrt'); ?></button><?php endif; ?>
<?php if (!$is_stateful && !$is_read) : ?><button type="button" class="message-item__action-btn js-msg-mark-read" data-id="<?php echo esc_attr($id); ?>"><?php esc_html_e('已读', 'aksrt'); ?></button><?php endif; ?>
<?php if (!$is_stateful) : ?><button type="button" class="message-item__action-btn message-item__action-btn--danger js-msg-delete" data-id="<?php echo esc_attr($id); ?>"><?php esc_html_e('删除', 'aksrt'); ?></button><?php endif; ?>
</div>
</article>
<?php
}
function aksrt_render_message_center_list(int $user_id, string $view, int $page = 1): void {
$items = aksrt_get_message_center_items($user_id, $view);
$per_page = 20;
$total_pages = max(1, (int) ceil(count($items) / $per_page));
$page = max(1, min($total_pages, $page));
$items = array_slice($items, ($page - 1) * $per_page, $per_page);
if (!$items) {
?>
<div class="messages-empty"><span class="messages-empty__icon"><i class="fa fas fa-bell-slash"></i></span><p class="messages-empty__text"><?php esc_html_e('暂无消息', 'aksrt'); ?></p></div>
<?php
return;
}
?>
<div class="messages-list">
<?php foreach ($items as $item) { aksrt_render_message_item($item, $view); } ?>
</div>
<?php if ($total_pages > 1) : ?>
<nav class="messages-pagination" aria-label="<?php esc_attr_e('消息分页', 'aksrt'); ?>">
<?php for ($number = 1; $number <= $total_pages; $number++) : ?>
<button type="button" class="profile-status-chip<?php echo $number === $page ? ' is-active' : ''; ?>" data-message-page="<?php echo esc_attr((string) $number); ?>" <?php disabled($number, $page); ?>><?php echo esc_html((string) $number); ?></button>
<?php endfor; ?>
</nav>
<?php endif;
}
function aksrt_render_message_center_detail(array $item, string $view): void {
$type = (string) ($item['type'] ?? 'notice');
$target_url = (string) ($item['url'] ?? '');
?>
<article class="message-detail" data-notification-id="<?php echo esc_attr((string) ($item['id'] ?? '')); ?>">
<header class="message-detail__header">
<button type="button" class="message-detail__back" data-message-back data-view="<?php echo esc_attr($view); ?>"><i class="fa fas fa-arrow-left" aria-hidden="true"></i> <?php esc_html_e('返回消息列表', 'aksrt'); ?></button>
<span class="message-detail__icon"><i class="<?php echo esc_attr(aksrt_message_icon_class($type)); ?>" aria-hidden="true"></i></span>
<h2 class="message-detail__title"><?php echo esc_html((string) ($item['title'] ?? '')); ?></h2>
<p class="message-detail__meta"><?php echo esc_html(aksrt_notification_type_label($type)); ?> · <?php echo esc_html(aksrt_message_time($item)); ?></p>
</header>
<div class="message-detail__content"><?php echo wp_kses_post(wpautop(esc_html((string) ($item['desc'] ?? '')))); ?></div>
<footer class="message-detail__actions">
<?php if ($target_url !== '') : ?><a href="<?php echo esc_url($target_url); ?>" class="message-detail__primary"><?php esc_html_e('查看相关内容', 'aksrt'); ?></a><?php endif; ?>
<button type="button" class="message-item__action-btn message-item__action-btn--danger js-msg-delete" data-id="<?php echo esc_attr((string) ($item['id'] ?? '')); ?>" data-detail="1"><?php esc_html_e('删除消息', 'aksrt'); ?></button>
</footer>
</article>
<?php
}
add_action('wp_ajax_aksrt_load_message_center', function (): void {
if (!is_user_logged_in()) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')], 401);
}
$nonce = isset($_POST['nonce']) ? (string) wp_unslash($_POST['nonce']) : '';
if (!wp_verify_nonce($nonce, 'aksrt_public_nonce')) {
wp_send_json_error(['message' => __('安全验证失败', 'aksrt')], 403);
}
$user_id = get_current_user_id();
$view = aksrt_normalize_message_view((string) ($_POST['view'] ?? 'unread'));
$page = max(1, absint($_POST['page'] ?? 1));
$notification_id = absint($_POST['notification_id'] ?? 0);
$detail = null;
if ($notification_id > 0) {
$detail = aksrt_get_message_center_item($user_id, $notification_id);
if (!$detail) {
wp_send_json_error(['message' => __('消息不存在或已删除', 'aksrt')], 404);
}
aksrt_mark_user_notification_read($user_id, (string) $notification_id);
$detail['read'] = 1;
}
$counts = aksrt_get_message_center_counts($user_id);
ob_start();
aksrt_render_message_center_nav($view, $counts);
$nav_html = (string) ob_get_clean();
ob_start();
if ($detail) {
aksrt_render_message_center_detail($detail, $view);
} else {
aksrt_render_message_center_list($user_id, $view, $page);
}
$content_html = (string) ob_get_clean();
wp_send_json_success([
'view' => $view,
'page' => $page,
'detail' => $notification_id,
'nav' => $nav_html,
'content' => $content_html,
'unread' => $counts['unread'],
]);
});
+85 -134
View File
@@ -56,11 +56,12 @@ function aksrt_notification_type_label(string $type): string {
$map = [
'comment_reply' => __('评论回复', 'aksrt'),
'comment_approved' => __('评论审核', 'aksrt'),
'comment' => __('文章评论', 'aksrt'),
'download_ready' => __('下载就绪', 'aksrt'),
'asset_points_increase' => __('积分增加', 'aksrt'),
'asset_points_decrease' => __('积分扣减', 'aksrt'),
'asset_balance_increase' => __('余额增加', 'aksrt'),
'asset_balance_decrease' => __('余额扣减', 'aksrt'),
'post_favorite' => __('文章收藏', 'aksrt'),
'post_like' => __('文章点赞', 'aksrt'),
'comment_like' => __('评论点赞', 'aksrt'),
'followed' => __('新增关注', 'aksrt'),
'pending_comments' => __('待审核评论', 'aksrt'),
'notice' => __('通知', 'aksrt'),
];
@@ -88,38 +89,18 @@ function aksrt_get_user_inbox_notifications(int $user_id = 0): array {
global $wpdb;
$table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('notification') : '';
if ($table === '') {
$items = get_user_meta($user_id, 'aksrt_notifications', true);
return is_array($items) ? array_values($items) : [];
}
static $migrated = [];
if (empty($migrated[$user_id])) {
$legacy = get_user_meta($user_id, 'aksrt_notifications', true);
if (is_array($legacy) && !empty($legacy)) {
foreach ($legacy as $item) {
aksrt_add_user_notification($user_id, [
'id' => $item['id'] ?? '',
'type' => $item['type'] ?? 'notice',
'title' => $item['title'] ?? '',
'desc' => $item['desc'] ?? '',
'url' => $item['url'] ?? '',
'read' => !empty($item['read']),
'created_at' => $item['created_at'] ?? current_time('mysql', true),
]);
}
delete_user_meta($user_id, 'aksrt_notifications');
}
$migrated[$user_id] = true;
}
if ($table === '') return [];
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_notification_settings() : [];
$limit = max(20, min(5000, (int) ($settings['max_per_user'] ?? 200)));
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT id, type, title, description, target_url, is_read, created_at, read_at
FROM {$table}
WHERE user_id = %d
ORDER BY created_at DESC, id DESC
LIMIT 50",
$user_id
LIMIT %d",
$user_id,
$limit
), ARRAY_A);
if (!is_array($rows)) return [];
@@ -220,34 +201,6 @@ endif;
add_action('wp', 'aksrt_schedule_notification_cleanup');
add_action('aksrt_notification_cleanup', 'aksrt_cleanup_notifications');
if (!function_exists('aksrt_write_user_inbox_notifications')) :
/** 写入用户通知收件箱 */
function aksrt_write_user_inbox_notifications(int $user_id, array $items): void {
if ($user_id <= 0) return;
global $wpdb;
$table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('notification') : '';
if ($table === '') {
update_user_meta($user_id, 'aksrt_notifications', array_values(array_slice($items, 0, 50)));
return;
}
$wpdb->delete($table, ['user_id' => $user_id], ['%d']);
$items = array_values(array_slice($items, 0, 50));
foreach ($items as $item) {
$wpdb->insert($table, [
'user_id' => $user_id,
'type' => sanitize_key((string) ($item['type'] ?? 'notice')),
'title' => sanitize_text_field((string) ($item['title'] ?? '')),
'description' => sanitize_text_field((string) ($item['desc'] ?? '')),
'target_url' => esc_url_raw((string) ($item['url'] ?? '')),
'is_read' => !empty($item['read']) ? 1 : 0,
'created_at' => (string) ($item['created_at'] ?? current_time('mysql', true)),
'read_at' => !empty($item['read']) ? current_time('mysql', true) : null,
], ['%d', '%s', '%s', '%s', '%s', '%d', '%s', '%s']);
}
}
endif;
if (!function_exists('aksrt_add_user_notification')) :
/** 添加一条用户通知 */
function aksrt_add_user_notification(int $user_id, array $payload): void {
@@ -264,20 +217,7 @@ function aksrt_add_user_notification(int $user_id, array $payload): void {
$table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('notification') : '';
$created_at = (string) ($payload['created_at'] ?? current_time('mysql', true));
if ($table === '') {
$items = aksrt_get_user_inbox_notifications($user_id);
array_unshift($items, [
'id' => isset($payload['id']) && $payload['id'] !== '' ? sanitize_text_field((string) $payload['id']) : uniqid('notif_', true),
'type' => $type,
'title' => sanitize_text_field((string) ($payload['title'] ?? '')),
'desc' => sanitize_text_field((string) ($payload['desc'] ?? '')),
'url' => esc_url_raw((string) ($payload['url'] ?? '')),
'read' => !empty($payload['read']) ? 1 : 0,
'created_at' => $created_at,
]);
aksrt_write_user_inbox_notifications($user_id, $items);
return;
}
if ($table === '') return;
$wpdb->insert($table, [
'user_id' => $user_id,
@@ -298,17 +238,7 @@ function aksrt_mark_user_notification_read(int $user_id, string $notification_id
if ($user_id <= 0) return;
global $wpdb;
$table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('notification') : '';
if ($table === '') {
$items = aksrt_get_user_inbox_notifications($user_id);
foreach ($items as &$item) {
if ($mark_all || (($item['id'] ?? '') === $notification_id)) {
$item['read'] = 1;
}
}
unset($item);
aksrt_write_user_inbox_notifications($user_id, $items);
return;
}
if ($table === '') return;
if ($mark_all) {
$wpdb->update(
@@ -337,14 +267,7 @@ function aksrt_delete_user_notification(int $user_id, string $notification_id):
if ($user_id <= 0 || $notification_id === '') return;
global $wpdb;
$table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('notification') : '';
if ($table === '') {
$items = aksrt_get_user_inbox_notifications($user_id);
$items = array_values(array_filter($items, static function ($item) use ($notification_id) {
return (string) ($item['id'] ?? '') !== $notification_id;
}));
aksrt_write_user_inbox_notifications($user_id, $items);
return;
}
if ($table === '') return;
$wpdb->delete($table, ['id' => absint($notification_id), 'user_id' => $user_id], ['%d', '%d']);
}
endif;
@@ -358,14 +281,7 @@ function aksrt_delete_user_notifications(int $user_id, array $notification_ids):
global $wpdb;
$table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('notification') : '';
if ($table === '') {
$items = aksrt_get_user_inbox_notifications($user_id);
$items = array_values(array_filter($items, static function ($item) use ($ids) {
return !in_array(absint($item['id'] ?? 0), $ids, true);
}));
aksrt_write_user_inbox_notifications($user_id, $items);
return;
}
if ($table === '') return;
$wpdb->query(
"DELETE FROM {$table} WHERE user_id = " . (int) $user_id . " AND id IN (" . implode(',', $ids) . ")"
@@ -379,7 +295,7 @@ function aksrt_get_user_unread_notification_count(int $user_id = 0): int {
$items = aksrt_get_user_inbox_notifications($user_id);
$count = 0;
foreach ($items as $item) {
if (empty($item['read'])) $count++;
if (empty($item['read']) && !str_starts_with((string) ($item['type'] ?? ''), 'asset_')) $count++;
}
return $count;
}
@@ -498,16 +414,33 @@ add_action('wp_ajax_aksrt_check_new_notifications', function () {
wp_send_json_error(['message' => __('安全验证失败', 'aksrt')], 403);
}
$unread = aksrt_get_user_unread_notification_count(get_current_user_id());
$counts = function_exists('aksrt_get_message_center_counts') ? aksrt_get_message_center_counts(get_current_user_id()) : [];
$unread = isset($counts['unread']) ? (int) $counts['unread'] : aksrt_get_user_unread_notification_count(get_current_user_id());
wp_send_json_success([
'count' => $unread,
'unread' => $unread,
]);
});
function aksrt_notify_post_author_of_comment(WP_Comment $comment): void {
if ((int) $comment->comment_parent > 0 || get_comment_meta($comment->comment_ID, 'aksrt_post_comment_notified', true)) return;
$post = get_post((int) $comment->comment_post_ID);
$owner_id = $post instanceof WP_Post ? (int) $post->post_author : 0;
if ($owner_id <= 0 || $owner_id === (int) $comment->user_id) return;
aksrt_add_user_notification($owner_id, [
'type' => 'comment',
'title' => __('文章收到评论', 'aksrt'),
'desc' => sprintf(__('%1$s 评论了你的文章《%2$s》。', 'aksrt'), get_comment_author($comment), get_the_title($post)),
'url' => get_comment_link($comment),
]);
update_comment_meta($comment->comment_ID, 'aksrt_post_comment_notified', 1);
}
add_action('wp_insert_comment', function ($comment_id, $comment) {
if (!$comment instanceof WP_Comment) return;
if ((int) $comment->comment_parent <= 0) return;
if ((int) $comment->comment_approved !== 1) return;
aksrt_notify_post_author_of_comment($comment);
if ((int) $comment->comment_parent <= 0) return;
if (get_comment_meta($comment_id, 'aksrt_reply_notified', true)) return;
$parent = get_comment((int) $comment->comment_parent);
@@ -540,6 +473,7 @@ add_action('transition_comment_status', function ($new_status, $old_status, $com
]);
update_comment_meta($comment->comment_ID, 'aksrt_approved_notified', 1);
}
aksrt_notify_post_author_of_comment($comment);
if ((int) $comment->comment_parent <= 0) return;
if (get_comment_meta($comment->comment_ID, 'aksrt_reply_notified', true)) return;
@@ -558,38 +492,55 @@ add_action('transition_comment_status', function ($new_status, $old_status, $com
update_comment_meta($comment->comment_ID, 'aksrt_reply_notified', 1);
}, 20, 3);
add_action('aksrt_asset_transaction_committed', function ($result, $command): void {
if (!$result instanceof \AKSRT\Ledger\LedgerResult || !$command instanceof \AKSRT\Ledger\LedgerCommand) {
return;
}
$changes = [];
foreach ($result->entries as $entry) {
$user_id = (int) ($entry['user_id'] ?? 0);
$asset = (string) ($entry['asset'] ?? '');
if ($user_id <= 0 || !in_array($asset, ['points', 'balance'], true)) continue;
$key = $user_id . ':' . $asset;
$changes[$key] ??= ['user_id' => $user_id, 'asset' => $asset, 'delta' => 0, 'balance' => 0];
$changes[$key]['delta'] += (int) ($entry['delta_minor'] ?? 0);
$changes[$key]['balance'] = (int) ($entry['balance_after_minor'] ?? 0);
}
foreach ($changes as $change) {
if ((int) $change['delta'] === 0) continue;
$is_points = $change['asset'] === 'points';
$positive = $change['delta'] > 0;
$amount = $is_points
? (string) abs((int) $change['delta'])
: \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, abs((int) $change['delta']))->decimal();
$balance = $is_points
? (string) $change['balance']
: \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, (int) $change['balance'])->decimal();
aksrt_add_user_notification((int) $change['user_id'], [
'type' => 'asset_' . $change['asset'] . '_' . ($positive ? 'increase' : 'decrease'),
'title' => $is_points ? __('积分变动', 'aksrt') : __('余额变动', 'aksrt'),
'desc' => sprintf(__('%1$s:%2$s%3$s,当前余额 %4$s。', 'aksrt'), \AKSRT\Ledger\LedgerPresentation::reason_label($command->reason), $positive ? '+' : '-', $amount, $balance),
'url' => aksrt_get_user_center_url('credits'),
]);
}
}, 10, 2);
add_action('aksrt_post_favorited', function (int $post_id, int $actor_id, bool $active): void {
$post = $active ? get_post($post_id) : null;
$owner_id = $post instanceof WP_Post ? (int) $post->post_author : 0;
if ($owner_id <= 0 || $owner_id === $actor_id) return;
$actor = get_userdata($actor_id);
aksrt_add_user_notification($owner_id, [
'type' => 'post_favorite',
'title' => __('文章被收藏', 'aksrt'),
'desc' => sprintf(__('%1$s 收藏了你的文章《%2$s》。', 'aksrt'), $actor instanceof WP_User ? $actor->display_name : __('一位用户', 'aksrt'), get_the_title($post)),
'url' => get_permalink($post),
]);
}, 30, 3);
add_action('aksrt_post_liked', function (int $post_id, int $actor_id, bool $active): void {
$post = $active ? get_post($post_id) : null;
$owner_id = $post instanceof WP_Post ? (int) $post->post_author : 0;
if ($owner_id <= 0 || $owner_id === $actor_id) return;
$actor = get_userdata($actor_id);
aksrt_add_user_notification($owner_id, [
'type' => 'post_like',
'title' => __('文章收到点赞', 'aksrt'),
'desc' => sprintf(__('%1$s 点赞了你的文章《%2$s》。', 'aksrt'), $actor instanceof WP_User ? $actor->display_name : __('一位用户', 'aksrt'), get_the_title($post)),
'url' => get_permalink($post),
]);
}, 30, 3);
add_action('aksrt_comment_liked', function (int $comment_id, int $actor_id, bool $active): void {
$comment = $active ? get_comment($comment_id) : null;
$owner_id = $comment instanceof WP_Comment ? (int) $comment->user_id : 0;
if ($owner_id <= 0 || $owner_id === $actor_id) return;
$actor = get_userdata($actor_id);
aksrt_add_user_notification($owner_id, [
'type' => 'comment_like',
'title' => __('评论收到点赞', 'aksrt'),
'desc' => sprintf(__('%s 点赞了你的评论。', 'aksrt'), $actor instanceof WP_User ? $actor->display_name : __('一位用户', 'aksrt')),
'url' => get_comment_link($comment),
]);
}, 30, 3);
add_action('aksrt_user_followed', function (int $follower_id, int $following_id): void {
if ($following_id <= 0 || $following_id === $follower_id) return;
$follower = get_userdata($follower_id);
aksrt_add_user_notification($following_id, [
'type' => 'followed',
'title' => __('收到新的关注', 'aksrt'),
'desc' => sprintf(__('%s 关注了你。', 'aksrt'), $follower instanceof WP_User ? $follower->display_name : __('一位用户', 'aksrt')),
'url' => function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($follower_id) : '',
]);
}, 30, 2);
+12 -1
View File
@@ -16,6 +16,7 @@ add_filter('query_vars', function ($vars) {
$vars[] = 'aksrt_user_center';
$vars[] = 'aksrt_user_center_tab';
$vars[] = 'aksrt_author_identifier';
$vars[] = 'aksrt_message_view';
return $vars;
});
@@ -31,6 +32,13 @@ add_action('template_redirect', function () {
nocache_headers();
return;
}
if (!get_query_var('aksrt_message_view') && is_page() && get_page_template_slug(get_queried_object_id()) === 'templates/pages/page-messages.php') {
global $wp_query;
if ($wp_query instanceof WP_Query) { $wp_query->set_404(); }
status_header(404);
nocache_headers();
return;
}
$resolved_path = aksrt_get_current_special_template_path();
if ($resolved_path === '') {
@@ -68,6 +76,9 @@ add_action('init', function () {
add_rewrite_tag('%aksrt_user_center%', '([^&]+)');
add_rewrite_tag('%aksrt_user_center_tab%', '([^/]+)');
add_rewrite_tag('%aksrt_author_identifier%', '([^/]+)');
add_rewrite_tag('%aksrt_message_view%', '(unread|posts|like|system)');
add_rewrite_rule('^message/?$', 'index.php?aksrt_message_view=unread', 'top');
add_rewrite_rule('^message/(posts|like|system)/?$', 'index.php?aksrt_message_view=$matches[1]', 'top');
add_rewrite_rule('^user/([a-z0-9_-]+)/?$', 'index.php?aksrt_user_center=1&aksrt_user_center_tab=$matches[1]', 'top');
add_rewrite_rule('^author/([^/]+)/?$', 'index.php?aksrt_author_identifier=$matches[1]', 'top');
});
@@ -177,7 +188,7 @@ add_action('init', function () {
}
// 统一 flush 检查 — bump when user-home rewrite / author override changes
$rewrite_version = AKSRT_VERSION . ':user-center-author-routing-v6';
$rewrite_version = AKSRT_VERSION . ':message-routing-v1';
$flushed = get_option('aksrt_user_rules_flushed');
$rules = get_option('rewrite_rules');
$need_flush = ($flushed !== $rewrite_version)
+9 -7
View File
@@ -37,12 +37,6 @@ $_aksrt_special_templates = [
'title' => '个人中心',
'fallback' => '/profile/',
],
'messages' => [
'file' => 'templates/pages/page-messages.php',
'slug' => 'messages',
'title' => '消息中心',
'fallback' => '/messages/',
],
'submit' => [
'file' => 'templates/pages/page-submit.php',
'slug' => 'submit',
@@ -135,7 +129,10 @@ function aksrt_resolve_page_template_file(string $template_file = '', string $po
$post_slug = sanitize_title($post_slug);
if ($template_file !== '') {
if ($template_file === 'templates/pages/page-sign.php') {
if ($template_file === 'templates/pages/page-messages.php') {
return '';
}
if ($template_file === 'templates/pages/page-sign.php') {
return $template_file;
}
@@ -223,6 +220,11 @@ function aksrt_get_user_home_template_path(): string {
* Resolve the active special-page template path for the current request.
*/
function aksrt_get_current_special_template_path(): string {
if (get_query_var('aksrt_message_view')) {
$message_template = aksrt_locate_template_file('templates/pages/page-messages.php');
return $message_template ?: '';
}
if (get_query_var('aksrt_author_identifier')) {
return aksrt_get_user_home_template_path();
}
+10 -7
View File
@@ -55,16 +55,19 @@ function aksrt_get_new_post_url(int $edit_id = 0): string {
endif;
if (!function_exists('aksrt_get_messages_page_url')) :
/**
* 消息中心页面 URL(模板 templates/pages/page-messages.php)。
*/
function aksrt_get_messages_page_url(): string {
/** 消息中心 URL。 */
function aksrt_get_messages_page_url(string $view = 'unread'): string {
if (class_exists('\AKSRT\Settings\Settings') && !\AKSRT\Settings\Settings::message_enabled()) {
return '';
}
return function_exists('aksrt_get_template_url')
? aksrt_get_template_url('messages')
: home_url('/messages/');
$paths = [
'unread' => '/message/',
'posts' => '/message/posts/',
'like' => '/message/like/',
'system' => '/message/system/',
];
$view = sanitize_key($view);
return home_url($paths[$view] ?? $paths['unread']);
}
endif;