328 lines
14 KiB
PHP
328 lines
14 KiB
PHP
<?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_get_message_center_paginate_type(): string {
|
|
$settings = class_exists('\AKSRT\Settings\Settings')
|
|
? \AKSRT\Settings\Settings::notification_settings()
|
|
: [];
|
|
$type = sanitize_key((string) ($settings['paginate_type'] ?? 'ajax_lists'));
|
|
return in_array($type, ['ajax_lists', 'default'], true) ? $type : 'ajax_lists';
|
|
}
|
|
|
|
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));
|
|
$paginate_type = aksrt_get_message_center_paginate_type();
|
|
$items = $paginate_type === 'ajax_lists'
|
|
? array_slice($items, 0, $page * $per_page)
|
|
: 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) : ?>
|
|
<?php if ($paginate_type === 'ajax_lists' && $page < $total_pages) : ?>
|
|
<nav class="messages-pagination messages-pagination--load-more" aria-label="<?php esc_attr_e('消息分页', 'aksrt'); ?>">
|
|
<button type="button" class="messages-pagination__more" data-message-next-page="<?php echo esc_attr((string) ($page + 1)); ?>">
|
|
<span><?php esc_html_e('加载更多消息', 'aksrt'); ?></span>
|
|
<small><?php echo esc_html(sprintf(__('%1$d / %2$d 页', 'aksrt'), $page, $total_pages)); ?></small>
|
|
</button>
|
|
</nav>
|
|
<?php elseif ($paginate_type === 'default') : ?>
|
|
<?php
|
|
$numbers = array_values(array_unique(array_filter([
|
|
1,
|
|
$page - 2,
|
|
$page - 1,
|
|
$page,
|
|
$page + 1,
|
|
$page + 2,
|
|
$total_pages,
|
|
], static fn(int $number): bool => $number >= 1 && $number <= $total_pages)));
|
|
sort($numbers);
|
|
$previous_number = 0;
|
|
?>
|
|
<nav class="messages-pagination messages-pagination--numbers" aria-label="<?php esc_attr_e('消息分页', 'aksrt'); ?>">
|
|
<button type="button" class="messages-pagination__button messages-pagination__button--edge" data-message-page="<?php echo esc_attr((string) max(1, $page - 1)); ?>" aria-label="<?php esc_attr_e('上一页', 'aksrt'); ?>" <?php disabled($page, 1); ?>><i class="fa fas fa-angle-left" aria-hidden="true"></i></button>
|
|
<?php foreach ($numbers as $number) : ?>
|
|
<?php if ($previous_number > 0 && $number - $previous_number > 1) : ?><span class="messages-pagination__ellipsis" aria-hidden="true">…</span><?php endif; ?>
|
|
<button type="button" class="messages-pagination__button<?php echo $number === $page ? ' is-current' : ''; ?>" data-message-page="<?php echo esc_attr((string) $number); ?>" <?php echo $number === $page ? 'aria-current="page"' : ''; ?> <?php disabled($number, $page); ?>><?php echo esc_html((string) $number); ?></button>
|
|
<?php $previous_number = $number; ?>
|
|
<?php endforeach; ?>
|
|
<button type="button" class="messages-pagination__button messages-pagination__button--edge" data-message-page="<?php echo esc_attr((string) min($total_pages, $page + 1)); ?>" aria-label="<?php esc_attr_e('下一页', 'aksrt'); ?>" <?php disabled($page, $total_pages); ?>><i class="fa fas fa-angle-right" aria-hidden="true"></i></button>
|
|
</nav>
|
|
<?php endif; ?>
|
|
<?php endif;
|
|
}
|
|
|
|
function aksrt_render_message_center_detail(array $item, string $view): void {
|
|
$type = (string) ($item['type'] ?? 'notice');
|
|
?>
|
|
<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_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">
|
|
<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,
|
|
'paginate_type' => aksrt_get_message_center_paginate_type(),
|
|
'nav' => $nav_html,
|
|
'content' => $content_html,
|
|
'unread' => $counts['unread'],
|
|
]);
|
|
});
|