220 lines
6.9 KiB
PHP
220 lines
6.9 KiB
PHP
<?php
|
|
/**
|
|
* AKSRT Message — 站内通知系统
|
|
*
|
|
* 提供通知持久化(写入 aksrt_notification 表)、未读计数、标记已读。
|
|
* 与 AKSRT_Private_Message 联动:私信发送时自动创建通知记录。
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class AKSRT_Message {
|
|
|
|
private static $instance = null;
|
|
|
|
public static function get_instance(): self {
|
|
if (null === self::$instance) self::$instance = new self();
|
|
return self::$instance;
|
|
}
|
|
|
|
public function init(): void {
|
|
add_action('wp_ajax_aksrt_mark_message_read', [$this, 'ajax_mark_read']);
|
|
add_action('wp_ajax_aksrt_get_unread_count', [$this, 'ajax_get_unread_count']);
|
|
}
|
|
|
|
/**
|
|
* 获取用户未读消息数
|
|
*/
|
|
public function get_unread_count(int $user_id): int {
|
|
if ($user_id <= 0) return 0;
|
|
|
|
// 优先从表中实时统计
|
|
$table = $this->table();
|
|
if ($table !== '') {
|
|
global $wpdb;
|
|
$count = (int) $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$table} WHERE user_id = %d AND is_read = 0",
|
|
$user_id
|
|
));
|
|
// 同步到 user_meta 以兼容旧读取方
|
|
update_user_meta($user_id, 'aksrt_unread_message_count', $count);
|
|
return $count;
|
|
}
|
|
|
|
// 降级:从 user_meta 读取
|
|
$count = (int) get_user_meta($user_id, 'aksrt_unread_message_count', true);
|
|
return max(0, $count);
|
|
}
|
|
|
|
/**
|
|
* 标记单条通知为已读
|
|
*/
|
|
public function mark_read(int $user_id, string $message_id): bool {
|
|
if ($user_id <= 0 || empty($message_id)) return false;
|
|
|
|
$table = $this->table();
|
|
if ($table !== '') {
|
|
global $wpdb;
|
|
$result = $wpdb->update(
|
|
$table,
|
|
['is_read' => 1, 'read_at' => current_time('mysql')],
|
|
['id' => (int) $message_id, 'user_id' => $user_id, 'is_read' => 0],
|
|
['%d', '%s'],
|
|
['%d', '%d', '%d']
|
|
);
|
|
return $result !== false;
|
|
}
|
|
|
|
// 降级:仅更新计数
|
|
$current = $this->get_unread_count($user_id);
|
|
if ($current > 0) {
|
|
update_user_meta($user_id, 'aksrt_unread_message_count', max(0, $current - 1));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 标记用户所有通知为已读
|
|
*/
|
|
public function mark_all_read(int $user_id): bool {
|
|
if ($user_id <= 0) return false;
|
|
|
|
$table = $this->table();
|
|
if ($table !== '') {
|
|
global $wpdb;
|
|
$wpdb->update(
|
|
$table,
|
|
['is_read' => 1, 'read_at' => current_time('mysql')],
|
|
['user_id' => $user_id, 'is_read' => 0],
|
|
['%d', '%s'],
|
|
['%d', '%d']
|
|
);
|
|
}
|
|
|
|
update_user_meta($user_id, 'aksrt_unread_message_count', 0);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 发送站内通知(持久化到 aksrt_notification 表)
|
|
*
|
|
* @param int $to_user_id 接收人 ID
|
|
* @param string $type 通知类型 (comment_reply, download_ready, conversation, notice, ...)
|
|
* @param string $content 通知内容摘要
|
|
* @param array $data 附加数据 (title, target_url, ...)
|
|
* @return bool
|
|
*/
|
|
public function send(int $to_user_id, string $type, string $content, array $data = []): bool {
|
|
if ($to_user_id <= 0 || empty($content)) return false;
|
|
|
|
$table = $this->table();
|
|
if ($table !== '') {
|
|
global $wpdb;
|
|
$wpdb->insert($table, [
|
|
'user_id' => $to_user_id,
|
|
'type' => sanitize_key($type),
|
|
'title' => sanitize_text_field((string) ($data['title'] ?? $content)),
|
|
'description' => sanitize_textarea_field($content),
|
|
'target_url' => esc_url_raw((string) ($data['target_url'] ?? '')),
|
|
'is_read' => 0,
|
|
'created_at' => current_time('mysql'),
|
|
], ['%d', '%s', '%s', '%s', '%s', '%d', '%s']);
|
|
}
|
|
|
|
// 同步更新 user_meta 未读计数
|
|
$current = (int) get_user_meta($to_user_id, 'aksrt_unread_message_count', true);
|
|
update_user_meta($to_user_id, 'aksrt_unread_message_count', max(0, $current) + 1);
|
|
|
|
// 触发通知钩子
|
|
do_action('aksrt_notification_sent', $to_user_id, $type, $data);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 获取用户通知列表
|
|
*/
|
|
public function get_notifications(int $user_id, array $args = []): array {
|
|
if ($user_id <= 0) return [];
|
|
|
|
$table = $this->table();
|
|
if ($table !== '') {
|
|
global $wpdb;
|
|
|
|
$where = ['user_id = %d'];
|
|
$params = [$user_id];
|
|
|
|
$type = sanitize_key((string) ($args['type'] ?? ''));
|
|
if ($type !== '' && $type !== 'all') {
|
|
if ($type === 'unread') {
|
|
$where[] = 'is_read = 0';
|
|
} else {
|
|
$where[] = 'type = %s';
|
|
$params[] = $type;
|
|
}
|
|
}
|
|
|
|
$limit = max(1, min(100, (int) ($args['limit'] ?? 20)));
|
|
$offset = max(0, (int) ($args['offset'] ?? 0));
|
|
|
|
$where_sql = implode(' AND ', $where);
|
|
$sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d";
|
|
$params[] = $limit;
|
|
$params[] = $offset;
|
|
|
|
return (array) $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* 获取通知收件箱完整视图(供 page-messages.php 使用)
|
|
*/
|
|
public function get_all_notifications(int $user_id): array {
|
|
return $this->get_notifications($user_id, ['limit' => 200]);
|
|
}
|
|
|
|
private function table(): string {
|
|
if (class_exists('AKSRT_DB_Migrator')) {
|
|
return AKSRT_DB_Migrator::table('notification');
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* AJAX 标记已读
|
|
*/
|
|
public function ajax_mark_read(): void {
|
|
check_ajax_referer('aksrt_message', '_nonce');
|
|
|
|
$user_id = get_current_user_id();
|
|
if ($user_id <= 0) {
|
|
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
|
|
}
|
|
|
|
$message_id = sanitize_text_field((string) ($_POST['message_id'] ?? ''));
|
|
if ($this->mark_read($user_id, $message_id)) {
|
|
wp_send_json_success(['unread' => $this->get_unread_count($user_id)]);
|
|
}
|
|
|
|
wp_send_json_error(['message' => __('操作失败', 'aksrt')]);
|
|
}
|
|
|
|
/**
|
|
* AJAX 获取未读数
|
|
*/
|
|
public function ajax_get_unread_count(): void {
|
|
check_ajax_referer('aksrt_message', '_nonce');
|
|
|
|
$user_id = get_current_user_id();
|
|
if ($user_id <= 0) {
|
|
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
|
|
}
|
|
|
|
wp_send_json_success(['unread' => $this->get_unread_count($user_id)]);
|
|
}
|
|
}
|