功能简介
这是一个为WordPress网站开发的用户在线状态管理系统,提供了类似社交平台的用户状态功能。
主要功能
- 多状态支持 :在线、离开、忙碌、离线四种状态
- 智能监测 :自动检测用户活动状态并更新
- 实时交互 :AJAX异步更新状态,无需刷新页面
- 视觉反馈 :头像上显示状态指示器,直观明了
- 后台管理 :完整的后台管理界面,支持批量操作
- 响应式设计 :适配各种屏幕尺寸
- 性能优化 :缓存机制减少数据库查询
技术实现
- 用户在线状态表 :存储用户ID、状态、最后更新时间和手动设置标志
- 用户元数据 :使用WordPress用户元数据存储活动时间和登录状态
- 状态管理函数 :处理状态的获取、更新和缓存
- 活动检测函数 :判断用户是否处于活动状态
- 定时任务函数 :定期检查和更新用户状态
- 缓存管理函数 :处理状态缓存的读写和清除
- AJAX交互 :使用jQuery AJAX实现无刷新状态更新
- 响应式CSS :适配不同屏幕尺寸的样式设计
- 头像集成 :通过过滤器在头像上添加状态指示器
- 用户体验优化 :添加加载状态、错误处理和成功反馈
效果图

使用教程
进入目录zibll\functions.php
//星空知-用户在线状态
// 创建用户在线状态表
function xk_zhplugs_create_user_online_status_table()
{
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
user_id bigint(20) NOT NULL,
status varchar(20) DEFAULT 'online',
last_update datetime DEFAULT CURRENT_TIMESTAMP,
manual_set tinyint(1) DEFAULT 0,
PRIMARY KEY (user_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
set_transient('xk_zhplugs_manual_set_field_checked', 1, DAY_IN_SECONDS);
}
add_action('init', 'xk_zhplugs_create_user_online_status_table');
function xk_zhplugs_get_status_monitor_time()
{
static $time = null;
if ($time === null) {
$time = 300;// 默认5分钟
}
return $time;
}
function xk_zhplugs_is_user_active($user_id)
{
$last_activity = get_user_meta($user_id, 'last_activity', true);
if (!$last_activity) {
return false;
}
$monitor_time = xk_zhplugs_get_status_monitor_time();
return (time() - (int) $last_activity) <= $monitor_time;
}
function xk_zhplugs_update_user_activity()
{
if (is_user_logged_in()) {
$user_id = get_current_user_id();
$current_time = time();
update_user_meta($user_id, 'last_activity', $current_time);
update_user_meta($user_id, 'is_logged_in', '1');
error_log('更新了用户ID ' . $user_id . ' 的活动时间和登录状态');
$current_status = xk_zhplugs_get_user_online_status($user_id);
error_log('用户ID ' . $user_id . ' 当前状态: ' . $current_status);
if ($current_status == 'offline') {
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
$result = $wpdb->query(
$wpdb->prepare(
"REPLACE INTO $table_name (user_id, status, last_update, manual_set) VALUES (%d, %s, %s, %d)",
$user_id,
'online',
current_time('mysql'),
0
)
);
error_log('用户ID ' . $user_id . ' 状态从离线更新为在线,结果: ' . ($result !== false ? '成功' : '失败'));
if ($result === false) {
error_log('数据库更新失败: ' . $wpdb->last_error);
}
xk_zhplugs_clear_user_status_cache($user_id);
error_log('用户ID ' . $user_id . ' 状态已更新为在线并清除了缓存');
}
}
}
add_action('init', 'xk_zhplugs_update_user_activity');
function xk_zhplugs_check_user_activity_status()
{
error_log('开始检查用户活动状态');
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
$users = $wpdb->get_results("SELECT user_id, status FROM $table_name");
error_log('获取到 ' . count($users) . ' 个用户的状态信息');
foreach ($users as $user) {
$is_logged_in = get_user_meta($user->user_id, 'is_logged_in', true);
$is_active = xk_zhplugs_is_user_active($user->user_id);
error_log("用户ID {$user->user_id} 当前状态: {$user->status}, 登录状态: {$is_logged_in}, 活动状态: " . ($is_active ? '是' : '否'));
if ($user->status != 'offline' && (!$is_active || $is_logged_in != '1')) {
$result = $wpdb->query(
$wpdb->prepare(
"REPLACE INTO $table_name (user_id, status, last_update, manual_set) VALUES (%d, %s, %s, %d)",
$user->user_id,
'offline',
current_time('mysql'),
0
)
);
error_log("更新用户ID {$user->user_id} 状态为离线,结果: " . ($result !== false ? '成功' : '失败'));
if ($result === false) {
error_log('数据库更新失败: ' . $wpdb->last_error);
}
update_user_meta($user->user_id, 'is_logged_in', '0');
xk_zhplugs_clear_user_status_cache($user->user_id);
error_log("用户状态切换:用户ID {$user->user_id} 因" . (!$is_active ? '不活动' : '未登录') . "从 {$user->status} 状态切换为离线状态");
}
}
error_log('用户活动状态检查完成');
}
function xk_zhplugs_schedule_activity_check()
{
if (!wp_next_scheduled('xk_zhplugs_check_user_activity')) {
wp_schedule_event(time(), 'every_minute', 'xk_zhplugs_check_user_activity');
}
}
add_action('wp', 'xk_zhplugs_schedule_activity_check');
add_action('admin_init', 'xk_zhplugs_schedule_activity_check');
add_action('init', 'xk_zhplugs_schedule_activity_check');
add_action('xk_zhplugs_check_user_activity', 'xk_zhplugs_check_user_activity_status');
// 测试
function xk_zhplugs_manual_check_activity_status()
{
xk_zhplugs_check_user_activity_status();
error_log('手动触发了用户活动状态检查');
}
add_action('wp_ajax_check_activity_status', 'xk_zhplugs_manual_check_activity_status');
add_action('wp_ajax_nopriv_check_activity_status', 'xk_zhplugs_manual_check_activity_status');
function xk_zhplugs_add_cron_intervals($schedules)
{
$schedules['every_minute'] = array(
'interval' => 60, // 60秒
'display' => __('每分钟')
);
return $schedules;
}
add_filter('cron_schedules', 'xk_zhplugs_add_cron_intervals');
function xk_zhplugs_set_online_on_login($user_login, $user)
{
update_user_meta($user->ID, 'last_activity', time());
update_user_meta($user->ID, 'is_logged_in', '1');
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
$wpdb->query(
$wpdb->prepare(
"REPLACE INTO $table_name (user_id, status, last_update, manual_set) VALUES (%d, %s, %s, %d)",
$user->ID,
'online',
current_time('mysql'),
0
)
);
xk_zhplugs_clear_user_status_cache($user->ID);
}
add_action('wp_login', 'xk_zhplugs_set_online_on_login', 10, 2);
function xk_zhplugs_set_status_on_logout()
{
error_log('开始处理用户登出状态更新');
$user_id = get_current_user_id();
error_log('登出用户ID: ' . $user_id);
if (!$user_id) {
error_log('无法获取用户ID,跳过登出状态更新');
return;
}
update_user_meta($user_id, 'is_logged_in', '0');
error_log('更新了用户ID ' . $user_id . ' 的登录状态为离线');
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
$result = $wpdb->query(
$wpdb->prepare(
"REPLACE INTO $table_name (user_id, status, last_update, manual_set) VALUES (%d, %s, %s, %d)",
$user_id,
'offline',
current_time('mysql'),
0
)
);
error_log('更新用户ID ' . $user_id . ' 状态为离线,结果: ' . ($result !== false ? '成功' : '失败'));
if ($result === false) {
error_log('数据库更新失败: ' . $wpdb->last_error);
}
xk_zhplugs_clear_user_status_cache($user_id);
error_log('清除了用户ID ' . $user_id . ' 的状态缓存');
}
add_action('wp_logout', 'xk_zhplugs_set_status_on_logout');
add_action('clear_auth_cookie', 'xk_zhplugs_set_status_on_logout', 100);
add_action('wp_login_failed', 'xk_zhplugs_set_status_on_logout');
add_action('wp_ajax_user_online_status_switch', 'xk_zhplugs_user_online_status_switch');
function xk_zhplugs_user_online_status_switch()
{
header('Content-Type: application/json');
if (!is_user_logged_in()) {
echo json_encode(array('success' => false, 'data' => array('msg' => '请先登录')));
exit;
}
$user_id = get_current_user_id();
$status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : '';
$nonce = isset($_POST['nonce']) ? sanitize_text_field($_POST['nonce']) : '';
if (empty($nonce) || !wp_verify_nonce($nonce, 'xk_zhplugs_user_online_status')) {
echo json_encode(array('success' => false, 'data' => array('msg' => '安全验证失败')));
exit;
}
$allowed_statuses = array('online', 'away', 'busy', 'offline');
if (!in_array($status, $allowed_statuses)) {
echo json_encode(array('success' => false, 'data' => array('msg' => '无效的状态')));
exit;
}
if ($status != 'offline' && !xk_zhplugs_is_user_active($user_id)) {
echo json_encode(array('success' => false, 'data' => array('msg' => '您的会话已超时,请刷新页面后再试')));
exit;
}
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
$result = $wpdb->query(
$wpdb->prepare(
"REPLACE INTO $table_name (user_id, status, last_update, manual_set) VALUES (%d, %s, %s, %d)",
$user_id,
$status,
current_time('mysql'),
1
)
);
if ($result !== false) {
if ($status === 'offline') {
update_user_meta($user_id, 'is_logged_in', '0');
} else {
update_user_meta($user_id, 'is_logged_in', '1');
update_user_meta($user_id, 'last_activity', time());
}
if (function_exists('wp_cache_delete')) {
wp_cache_delete($user_id, 'user_avatar');
}
if (function_exists('xk_zhplugs_clear_user_status_cache')) {
xk_zhplugs_clear_user_status_cache($user_id);
}
$status_map = array('online' => '在线', 'away' => '离开', 'busy' => '忙碌', 'offline' => '离线');
echo json_encode(array('success' => true, 'data' => array('msg' => '当前在线状态为:' . ($status_map[$status] ?? '在线'))));
} else {
error_log('在线状态更新失败: ' . $wpdb->last_error);
echo json_encode(array('success' => false, 'data' => array('msg' => '在线状态更新失败')));
}
exit;
}
$xk_zhplugs_user_status_cache = array();
$xk_zhplugs_status_cache_time = array();
function xk_zhplugs_clear_user_status_cache($user_id = null)
{
global $xk_zhplugs_user_status_cache, $xk_zhplugs_status_cache_time;
error_log('开始清除用户状态缓存,用户ID: ' . ($user_id ? $user_id : '所有'));
if ($user_id) {
if (isset($xk_zhplugs_user_status_cache[$user_id])) {
unset($xk_zhplugs_user_status_cache[$user_id]);
error_log('清除了用户ID ' . $user_id . ' 的基础状态缓存');
}
if (isset($xk_zhplugs_status_cache_time[$user_id])) {
unset($xk_zhplugs_status_cache_time[$user_id]);
}
$full_cache_key = 'user_status_full_' . $user_id;
if (isset($xk_zhplugs_user_status_cache[$full_cache_key])) {
unset($xk_zhplugs_user_status_cache[$full_cache_key]);
error_log('清除了用户ID ' . $user_id . ' 的完整状态数据缓存');
}
if (isset($xk_zhplugs_status_cache_time[$full_cache_key])) {
unset($xk_zhplugs_status_cache_time[$full_cache_key]);
}
if (function_exists('wp_cache_delete')) {
wp_cache_delete($user_id, 'user_avatar');
wp_cache_delete('user_status_' . $user_id);
error_log('清除了用户ID ' . $user_id . ' 的WordPress对象缓存');
}
clean_user_cache(get_userdata($user_id));
error_log('清除了用户ID ' . $user_id . ' 的用户元数据缓存');
} else {
$xk_zhplugs_user_status_cache = array();
$xk_zhplugs_status_cache_time = array();
error_log('清除了所有内存缓存');
if (function_exists('wp_cache_flush_group')) {
wp_cache_flush_group('user_avatar');
error_log('清除了所有用户的头像缓存');
}
wp_cache_flush();
error_log('清除了所有WordPress缓存');
}
error_log('用户状态缓存清除完成');
}
function xk_zhplugs_get_user_online_status($user_id = null)
{
global $xk_zhplugs_user_status_cache, $xk_zhplugs_status_cache_time;
if (!$user_id) {
$user_id = get_current_user_id();
}
if (!$user_id) {
return 'offline';
}
$cache_valid = isset($xk_zhplugs_user_status_cache[$user_id]) &&
isset($xk_zhplugs_status_cache_time[$user_id]) &&
(time() - $xk_zhplugs_status_cache_time[$user_id] < 60);
if ($cache_valid) {
$cached_status = $xk_zhplugs_user_status_cache[$user_id];
if ($cached_status != 'offline' && !xk_zhplugs_is_user_active($user_id)) {
return 'offline';
}
return $cached_status;
}
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
$status_data = $wpdb->get_row($wpdb->prepare(
"SELECT status FROM $table_name WHERE user_id = %d",
$user_id
));
$status = $status_data ? $status_data->status : 'offline';
if ($status != 'offline' && !xk_zhplugs_is_user_active($user_id)) {
$status = 'offline';
}
$xk_zhplugs_user_status_cache[$user_id] = $status;
$xk_zhplugs_status_cache_time[$user_id] = time();
return $status;
}
// 用户在线状态小工具
class XK_Zhplugs_Online_Status_Widget extends WP_Widget
{
public function __construct()
{
parent::__construct(
'xk_zhplugs_online_status_widget',
'星空知-在线状态',
array('description' => '显示和切换用户在线状态')
);
}
public function widget($args, $instance)
{
if (!is_user_logged_in()) {
return;
}
echo $args['before_widget'];
$title = !empty($instance['title']) ? $instance['title'] : '星空知-在线状态';
echo $args['before_title'] . $title . $args['after_title'];
$current_status = xk_zhplugs_get_user_online_status();
$nonce = wp_create_nonce('xk_zhplugs_user_online_status');
?>
<div class="fetl-user-online-status mt5">
<div class="but <?php echo $current_status == 'online' ? 'fetl-active' : ''; ?>" data-sta="online">
<span></span><span class="fetl-wd-k-all">在线</span>
</div>
<div class="but <?php echo $current_status == 'away' ? 'fetl-active' : ''; ?>" data-sta="away">
<span></span><span class="fetl-wd-k-all">离开</span>
</div>
<div class="but <?php echo $current_status == 'busy' ? 'fetl-active' : ''; ?>" data-sta="busy">
<span></span><span class="fetl-wd-k-all">忙碌</span>
</div>
<div class="but <?php echo $current_status == 'offline' ? 'fetl-active' : ''; ?>" data-sta="offline">
<span></span><span class="fetl-wd-k-all">离线</span>
</div>
<input type="hidden" class="xk_zhplugs_user_online_status_nonce" value="<?php echo esc_attr($nonce); ?>">
</div>
<?php
echo $args['after_widget'];
}
public function form($instance)
{
$title = !empty($instance['title']) ? $instance['title'] : '星空知-在线状态';
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>">标题:</label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<?php
}
public function update($new_instance, $old_instance)
{
$instance = array();
$instance['title'] = !empty($new_instance['title']) ? strip_tags($new_instance['title']) : '';
return $instance;
}
}
// 注册小工具
add_action('widgets_init', function () {
register_widget('XK_Zhplugs_Online_Status_Widget');
});
// 添加顶级后台管理菜单
add_action('admin_menu', 'xk_zhplugs_add_user_status_admin_menu', 20);
function xk_zhplugs_add_user_status_admin_menu()
{
// 检查功能是否启用
add_menu_page(
'用户在线状态管理',
'用户在线状态管理',
'manage_options',
'xk-user-status',
'xk_zhplugs_user_status_admin_page',
'dashicons-heart',
25
);
}
add_action('admin_init', 'xk_zhplugs_handle_status_form_submissions');
function xk_zhplugs_handle_status_form_submissions()
{
global $pagenow;
if ($pagenow === 'options-general.php' && isset($_GET['page']) && $_GET['page'] === 'xk-user-status') {
wp_safe_redirect(admin_url('admin.php?page=xk-user-status'));
exit;
}
if (isset($_POST['update_user_status'])) {
xk_zhplugs_handle_update_user_status();
}
if (isset($_POST['bulk_update_status'])) {
xk_zhplugs_handle_bulk_update_status();
}
}
function xk_zhplugs_user_status_admin_page()
{
global $title;
$title = '用户在线状态管理';
$search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
$status_filter = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '';
$valid_statuses = array('', 'online', 'away', 'busy', 'offline');
if (!in_array($status_filter, $valid_statuses)) {
$status_filter = '';
}
$users = xk_zhplugs_get_users_with_status($search, $status_filter);
// 状态选项
$status_options = array(
'online' => '在线',
'away' => '离开',
'busy' => '忙碌',
'offline' => '离线'
);
?>
<div class="wrap">
<h1>用户在线状态管理</h1>
<!-- 组合搜索和批量操作在同一行 -->
<div class="tablenav top" style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
<!-- 搜索和状态筛选表单 -->
<form method="get" action="" style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
<input type="hidden" name="page" value="xk-user-status">
<label class="screen-reader-text" for="user-search-input">搜索用户:</label>
<input type="search" id="user-search-input" name="s" value="<?php echo esc_attr($search); ?>"
placeholder="搜索用户名或昵称" style="min-width: 200px;">
<!-- 状态筛选下拉框 -->
<select name="status" id="status-filter" style="min-width: 120px;">
<option value="">所有状态</option>
<?php foreach ($status_options as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($status_filter, $value); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
<input type="submit" id="search-submit" class="button" value="筛选">
<?php if (!empty($search) || !empty($status_filter)): ?>
<a href="<?php echo admin_url('admin.php?page=xk-user-status'); ?>" rel="external nofollow" rel="external nofollow" rel="external nofollow"
class="button">清除筛选</a>
<?php endif; ?>
</form>
</div>
<!-- 批量操作表单 -->
<form method="post" action="">
<?php wp_nonce_field('xk_user_status_bulk', 'xk_user_status_nonce'); ?>
<div class="tablenav top" style="margin-top: 10px;">
<!-- 批量操作 -->
<div class="alignleft actions bulkactions">
<label for="bulk-action-selector-top" class="screen-reader-text">选择批量操作</label>
<select name="bulk_status" id="bulk-action-selector-top">
<option value="">更改状态为...</option>
<?php foreach ($status_options as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>"><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
<input type="submit" name="bulk_update_status" id="doaction" class="button action" value="应用">
</div>
<br class="clear">
</div>
<!-- 用户状态列表 -->
<table class="wp-list-table widefat fixed striped users">
<thead>
<tr>
<th scope="col" class="manage-column column-cb check-column">
<input type="checkbox" id="cb-select-all-1">
</th>
<th scope="col" class="manage-column column-username">用户名</th>
<th scope="col" class="manage-column column-name">昵称</th>
<th scope="col" class="manage-column column-status">当前状态</th>
<th scope="col" class="manage-column column-last_activity">最后活动时间</th>
<th scope="col" class="manage-column column-actions">操作</th>
</tr>
</thead>
<tbody id="the-list">
<?php if (!empty($users)): ?>
<?php foreach ($users as $user): ?>
<tr id="user-<?php echo $user->ID; ?>">
<th scope="row" class="check-column">
<input type="checkbox" name="user_ids[]" value="<?php echo $user->ID; ?>">
</th>
<td class="username column-username">
<strong><?php echo esc_html($user->user_login); ?></strong>
</td>
<td class="name column-name">
<?php echo esc_html($user->display_name); ?>
</td>
<td class="status column-status">
<span class="status-badge status-<?php echo $user->status; ?>">
<?php echo $status_options[$user->status]; ?>
</span>
</td>
<td class="last_activity column-last_activity">
<?php echo $user->last_activity ? date('Y-m-d H:i:s', $user->last_activity) : '从未活动'; ?>
</td>
<td class="actions column-actions">
<!-- 使用独立的表单,不嵌套在批量操作表单中 -->
<form method="post" action="" style="display: inline;"
id="update-form-<?php echo $user->ID; ?>">
<?php wp_nonce_field('xk_user_status_update', 'xk_user_status_update_nonce'); ?>
<input type="hidden" name="user_id" value="<?php echo $user->ID; ?>">
<select name="new_status" style="margin-right: 5px;">
<?php foreach ($status_options as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($user->status, $value); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
<input type="submit" name="update_user_status" class="button button-small" value="更新">
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="colspanchange">
<?php if (!empty($search) || !empty($status_filter)): ?>
没有找到匹配条件的用户。
<?php else: ?>
没有找到用户。
<?php endif; ?>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</form>
<!-- 样式 -->
<style>
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.status-online {
background-color: #c8e6c9;
color: #2e7d32;
}
.status-away {
background-color: #fff3cd;
color: #856404;
}
.status-busy {
background-color: #f8d7da;
color: #721c24;
}
.status-offline {
background-color: #e2e3e5;
color: #383d41;
}
</style>
</div>
<?php
}
function xk_zhplugs_get_users_with_status($search = '', $status_filter = '')
{
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
$sql = "SELECT DISTINCT u.ID, u.user_login, u.display_name,
uos.status, uos.manual_set,
um.meta_value as last_activity
FROM $wpdb->users u
LEFT JOIN $table_name uos ON u.ID = uos.user_id
LEFT JOIN $wpdb->usermeta um ON u.ID = um.user_id AND um.meta_key = 'last_activity'";
$where_conditions = array();
$prepare_args = array();
if (!empty($search)) {
$search = '%' . $wpdb->esc_like($search) . '%';
$where_conditions[] = "(u.user_login LIKE %s OR u.display_name LIKE %s)";
$prepare_args[] = $search;
$prepare_args[] = $search;
}
if (!empty($status_filter)) {
$where_conditions[] = "(uos.status = %s OR (uos.status IS NULL AND %s = 'offline'))";
$prepare_args[] = $status_filter;
$prepare_args[] = $status_filter;
}
if (!empty($where_conditions)) {
$sql .= " WHERE " . implode(" AND ", $where_conditions);
}
$sql .= " ORDER BY uos.status ASC, u.user_login ASC";
if (!empty($prepare_args)) {
$users = $wpdb->get_results($wpdb->prepare($sql, $prepare_args));
} else {
$users = $wpdb->get_results($sql);
}
foreach ($users as $user) {
if (empty($user->status)) {
$user->status = 'offline';
}
if (!empty($user->last_activity)) {
$user->last_activity = (int) $user->last_activity;
}
}
return $users;
}
function xk_zhplugs_handle_update_user_status()
{
if (!isset($_POST['xk_user_status_update_nonce']) || !wp_verify_nonce($_POST['xk_user_status_update_nonce'], 'xk_user_status_update')) {
wp_die('安全验证失败');
}
if (!current_user_can('manage_options')) {
wp_die('您没有权限执行此操作');
}
$user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0;
$new_status = isset($_POST['new_status']) ? sanitize_text_field($_POST['new_status']) : '';
$allowed_statuses = array('online', 'away', 'busy', 'offline');
if (!in_array($new_status, $allowed_statuses)) {
wp_die('无效的状态值');
}
// 更新用户状态
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
// 输出日志
error_log("用户状态切换:管理员更新用户ID {$user_id} 为 {$new_status} 状态");
$wpdb->query(
$wpdb->prepare(
"REPLACE INTO $table_name (user_id, status, last_update, manual_set) VALUES (%d, %s, %s, %d)",
$user_id,
$new_status,
current_time('mysql'),
1
)
);
// 更新登录状态
if ($new_status === 'offline') {
update_user_meta($user_id, 'is_logged_in', '0');
} else {
update_user_meta($user_id, 'is_logged_in', '1');
update_user_meta($user_id, 'last_activity', time());
}
// 清除缓存
xk_zhplugs_clear_user_status_cache($user_id);
// 重定向到原页面
wp_redirect(admin_url('options-general.php?page=xk-user-status'));
exit;
}
// 处理批量更新状态
function xk_zhplugs_handle_bulk_update_status()
{
// 验证nonce
if (!isset($_POST['xk_user_status_nonce']) || !wp_verify_nonce($_POST['xk_user_status_nonce'], 'xk_user_status_bulk')) {
wp_die('安全验证失败');
}
// 检查权限
if (!current_user_can('manage_options')) {
wp_die('您没有权限执行此操作');
}
$user_ids = isset($_POST['user_ids']) ? array_map('intval', $_POST['user_ids']) : array();
$bulk_status = isset($_POST['bulk_status']) ? sanitize_text_field($_POST['bulk_status']) : '';
// 验证状态值
$allowed_statuses = array('online', 'away', 'busy', 'offline');
if (!in_array($bulk_status, $allowed_statuses) || empty($user_ids)) {
wp_die('无效的操作参数');
}
// 批量更新用户状态
global $wpdb;
$table_name = $wpdb->prefix . 'user_online_status';
$now = current_time('mysql');
foreach ($user_ids as $user_id) {
// 输出日志
error_log("用户状态切换:管理员批量更新用户ID {$user_id} 为 {$bulk_status} 状态");
$wpdb->query(
$wpdb->prepare(
"REPLACE INTO $table_name (user_id, status, last_update, manual_set) VALUES (%d, %s, %s, %d)",
$user_id,
$bulk_status,
$now,
1
)
);
// 更新登录状态
if ($bulk_status === 'offline') {
update_user_meta($user_id, 'is_logged_in', '0');
} else {
update_user_meta($user_id, 'is_logged_in', '1');
update_user_meta($user_id, 'last_activity', time());
}
// 清除缓存
xk_zhplugs_clear_user_status_cache($user_id);
}
// 重定向到原页面
wp_redirect(admin_url('options-general.php?page=xk-user-status'));
exit;
}
// 在头像上添加在线状态指示器 - 只在前端生效
add_filter('get_avatar', 'xk_zhplugs_add_online_status_to_avatar', 99, 5);
function xk_zhplugs_add_online_status_to_avatar($avatar, $id_or_email, $size, $default, $alt)
{
// 不在后台显示在线状态指示器,避免影响后台布局
if (is_admin()) {
return $avatar;
}
$user_id = 0;
if (is_numeric($id_or_email)) {
$user_id = $id_or_email;
} elseif (is_object($id_or_email) && !empty($id_or_email->user_id)) {
$user_id = $id_or_email->user_id;
} elseif (is_email($id_or_email)) {
$user = get_user_by('email', $id_or_email);
$user_id = $user ? $user->ID : 0;
}
if ($user_id) {
$status = xk_zhplugs_get_user_online_status($user_id);
$avatar = '<div class="fetl-avatar-status-box" style="display:inline-block;position:relative;">' . $avatar .
'<span class="fetl-online-status-dot fetl-status-' . esc_attr($status) . ' user-uid-' . esc_attr($user_id) . '"></span>' .
'</div>';
}
return $avatar;
}
// 子比主题头像适配 - 优化版本
add_filter('zib_get_data_avatar', 'xk_zhplugs_zib_avatar_with_status', 10, 3);
function xk_zhplugs_zib_avatar_with_status($avatar, $user_id, $size)
{
// 不在后台显示在线状态指示器,避免影响后台布局
if (is_admin() || !$user_id) {
return $avatar;
}
$status = xk_zhplugs_get_user_online_status($user_id);
$status_config = array(
'online' => array('color' => '#72e128', 'text' => '在线'),
'away' => array('color' => '#faad14', 'text' => '离开'),
'busy' => array('color' => '#f5222d', 'text' => '忙碌'),
'offline' => array('color' => '#d9d9d9', 'text' => '离线')
);
$config = $status_config[$status] ?? $status_config['offline'];
$dot_size = $size <= 40 ? 7 : ($size <= 60 ? 8 : ($size <= 100 ? 9 : 10));
// 创建状态指示器HTML,包含状态类
$dot_html = sprintf(
'<span class="fetl-online-status-dot user-uid-%d fetl-status-%s"
style="position:absolute;bottom:1px;left:1px;width:%dpx;height:%dpx;border-radius:100%%;box-shadow:0 0 0 2px #fff;background-color:%s;z-index:2;"
title="%s">
</span>',
$user_id,
$status,
$dot_size,
$dot_size,
$config['color'],
$config['text']
);
// 检查头像是否已经有容器,避免重复添加
if (strpos($avatar, 'fetl-avatar-status-box') !== false) {
// 如果已经有容器,只更新状态指示器
$avatar = preg_replace(
'/<span class="fetl-online-status-dot user-uid-' . $user_id . '.*?<\/span>/',
$dot_html,
$avatar
);
} else {
// 否则,添加容器和状态指示器
$avatar = sprintf(
'<div class="fetl-avatar-status-box" style="position:relative;display:inline-block;vertical-align:middle;">
%s
%s
</div>',
$avatar,
$dot_html
);
}
return $avatar;
}
// 注入 JavaScript 代码
add_action('wp_footer', 'xk_zhplugs_inject_javascript');
function xk_zhplugs_inject_javascript()
{
if (!is_user_logged_in())
return;
$nonce = wp_create_nonce('xk_zhplugs_user_online_status');
$user_id = get_current_user_id();
?>
<script type="text/javascript">
jQuery(document).on('click', '.fetl-user-online-status .but', function () {
var $this = jQuery(this);
var $container = $this.closest('.fetl-user-online-status');
var status = $this.data('sta');
var nonce = $container.find('.xk_zhplugs_user_online_status_nonce').val() || window
.xk_zhplugs_user_online_status_nonce || '<?php echo $nonce; ?>';
var $allButtons = $container.find('.but');
// 防止重复点击
if ($this.data('processing')) return;
$this.data('processing', 1);
// 立即更新UI,给用户即时反馈
$allButtons.removeClass('fetl-active');
$this.addClass('fetl-active');
// 添加加载状态类
$this.addClass('status-updating');
// 优化的AJAX请求,添加超时设置
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'user_online_status_switch',
status: status,
nonce: nonce
},
timeout: 5000, // 设置5秒超时
cache: false,
success: function (response) {
if (response.success) {
var user_id = '<?php echo $user_id; ?>';
if (user_id) {
// 使用更高效的选择器
jQuery('.fetl-online-status-dot.user-uid-' + user_id)
.removeClass(
'fetl-status-online fetl-status-away fetl-status-busy fetl-status-offline'
)
.addClass('fetl-status-' + status);
}
if (typeof notyf === 'function') {
notyf(response.data.msg, 'success', 1000);
}
} else {
// 失败时恢复原状态
$allButtons.removeClass('fetl-active');
// 显示错误信息
if (typeof notyf === 'function') {
notyf(response.data.msg || '状态更新失败', "danger");
}
}
},
error: function (xhr, status, error) {
// 错误处理,恢复原状态
$allButtons.removeClass('fetl-active');
// 显示错误信息
var errorMsg = '网络请求异常';
if (status === 'timeout') {
errorMsg = '请求超时,请重试';
} else if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.msg) {
errorMsg = xhr.responseJSON.data.msg;
}
if (typeof notyf === 'function') {
notyf(errorMsg, "danger");
}
},
complete: function () {
// 清除加载状态
$this.removeClass('status-updating');
$this.data('processing', 0);
}
});
});
jQuery(document).on('click', "a[href*='action=logout']", function (e) {
var nonce = window.xk_zhplugs_user_online_status_nonce || '<?php echo $nonce; ?>';
var user_id = '<?php echo $user_id; ?>';
if (user_id && nonce) {
e.preventDefault();
var logoutUrl = jQuery(this).attr('href');
var $this = jQuery(this);
// 添加加载状态
$this.addClass('logging-out');
// 优化的AJAX请求,添加超时设置
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'user_online_status_switch',
status: 'offline',
nonce: nonce
},
timeout: 3000, // 3秒超时
cache: false,
error: function (xhr, status, error) {
// 即使请求失败,也执行登出
console.warn('登出状态更新失败,但仍将继续登出:', error);
},
complete: function () {
// 清除加载状态并执行登出
$this.removeClass('logging-out');
window.location.href = logoutUrl;
}
});
}
});
window.xk_zhplugs_user_online_status_nonce = '<?php echo $nonce; ?>';
window.current_user_id = '<?php echo $user_id; ?>';
</script>
<?php
}
add_action('wp_head', 'xk_zhplugs_user_online_status_styles');
function xk_zhplugs_user_online_status_styles()
{
?>
<style>
.fetl-user-online-status {
display: grid !important;
grid-template-columns: repeat(4, 1fr);
gap: calc(1.938rem - 5px);
margin-bottom: calc(1.938rem - 5px);
}
.fetl-user-online-status>div {
position: relative;
background: var(--muted-border-color);
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 6px;
min-width: 50px;
min-height: 50px;
border-radius: var(--main-radius);
border: 2px dashed transparent;
cursor: pointer !important;
transition: .15s;
text-align: center;
font-weight: 400;
text-shadow: 0 0 0;
line-height: 1.44;
}
.fetl-user-online-status>div:hover {
border-color: var(--theme-color);
}
.fetl-user-online-status>div.fetl-active {
border: 2px solid var(--theme-color) !important;
}
.fetl-user-online-status>div.fetl-active:before {
position: absolute;
top: -1px;
text-align: center;
font-size: 0.725rem;
content: "";
font-family: 'remixicon' !important;
width: 16px;
height: 16px;
line-height: 14px;
opacity: 0.9;
color: #fff;
right: -1px;
left: auto;
font-weight: 600;
background-color: var(--theme-color);
border-radius: 0 var(--main-radius) 0 5px;
transition: all 0.2s;
z-index: 1;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='12' height='12'%3E%3Cpath fill='white' d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: 10px;
}
.fetl-wd-k-all {
word-break: keep-all;
}
.fetl-user-online-status>div span:first-child {
width: 13px;
height: 13px;
border-radius: 100%;
box-shadow: 0 0 0 2px #fff;
}
.fetl-user-online-status>div span:last-child {
font-size: .95rem;
color: var(--muted-color);
}
[data-sta="online"] span:first-child {
background-color: #36c76c !important;
}
[data-sta="away"] span:first-child {
background-color: #ffd648 !important;
}
[data-sta="busy"] span:first-child {
background-color: #ff6692 !important;
}
[data-sta="offline"] span:first-child {
background-color: #526b7a !important;
}
.fetl-avatar-status-box {
position: relative;
display: inline-block;
}
.fetl-online-status-dot {
position: absolute;
bottom: 1px;
left: 1px;
width: 7px;
height: 7px;
border-radius: 100%;
box-shadow: 0 0 0 2px #fff;
z-index: 2;
}
.fetl-status-online {
background-color: #36c76c !important;
}
.fetl-status-away {
background-color: #ffd648 !important;
}
.fetl-status-busy {
background-color: #ff6692 !important;
}
.fetl-status-offline {
background-color: #526b7a !important;
}
@media (max-width:991px) {
.fetl-user-online-status>div {
min-width: auto;
min-height: auto;
}
}
@media (max-width:768px) {
.fetl-user-online-status {
gap: 10px;
}
.fetl-user-online-status>div {
min-width: 40px;
min-height: 40px;
padding: 4px;
}
}
.zib-widget .fetl-user-online-status {
width: 100%;
}
</style>
<?php
}
将以下代码添加到 zibll/inc/functions/zib-header.php 文件中:
查找位置: 先搜索以下代码:
$con .= $payvip ? '<div class="mt10 em09" style="padding:2px;">' . $payvip . '</div>' : '';
添加位置: 将以下代码插入到上述代码的 上面
$current_status = xk_zhplugs_get_user_online_status();
$nonce = wp_create_nonce('xk_zhplugs_user_online_status');
$con .= '<div class="mt10 fetl-user-online-status" style="padding:2px;">';
$con .= '<div class="but ' . ($current_status == 'online' ? 'fetl-active' : '') . '" data-sta="online">';
$con .= '<span></span><span class="fetl-wd-k-all">在线</span>';
$con .= '</div>';
$con .= '<div class="but ' . ($current_status == 'away' ? 'fetl-active' : '') . '" data-sta="away">';
$con .= '<span></span><span class="fetl-wd-k-all">离开</span>';
$con .= '</div>';
$con .= '<div class="but ' . ($current_status == 'busy' ? 'fetl-active' : '') . '" data-sta="busy">';
$con .= '<span></span><span class="fetl-wd-k-all">忙碌</span>';
$con .= '</div>';
$con .= '<div class="but ' . ($current_status == 'offline' ? 'fetl-active' : '') . '" data-sta="offline">';
$con .= '<span></span><span class="fetl-wd-k-all">离线</span>';
$con .= '</div>';
$con .= '<input type="hidden" class="xk_zhplugs_user_online_status_nonce" value="' . esc_attr($nonce) . '">';
$con .= '</div>';
添加头像状态过滤器
将以下代码添加到 zibll/inc/functions/zib-theme.php 文件中:
查找位置: 先搜索以下代码:
add_action('user_save_custom_avatar', function ($user_id) {
添加位置: 在 return $avatar; 代码的 上面 添加以下代码:
$avatar = apply_filters('zib_get_data_avatar', $avatar, $user_id, $size, $alt);
