Files
RecentlyActive/Plugin.php
2026-02-23 19:51:00 +08:00

1335 lines
45 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 最近活跃时间、楼层(子父级独立)、用户等级
*
* @package RecentlyActive
* @author 石头厝
* @version 2.2.0
* @link https://www.shitoucuo.com/
*/
class RecentlyActive_Plugin implements Typecho_Plugin_Interface
{
/**
* 用户等级配置 - 更新为旅行主题
*/
private static $defaultLevels = array(
array('name' => '偶遇', 'min_comments' => 0, 'class' => 'vip1', 'color' => '#c0c0c0'),
array('name' => '同程', 'min_comments' => 1, 'class' => 'vip2', 'color' => '#a0a0a0'),
array('name' => '涉溪', 'min_comments' => 20, 'class' => 'vip3', 'color' => '#9e7a5d'),
array('name' => '穿林', 'min_comments' => 60, 'class' => 'vip4', 'color' => '#b87333'),
array('name' => '览峰', 'min_comments' => 150, 'class' => 'vip5', 'color' => '#cb6d1e'),
array('name' => '渡川', 'min_comments' => 300, 'class' => 'vip6', 'color' => '#cc8400'),
array('name' => '聆泉', 'min_comments' => 600, 'class' => 'vip7', 'color' => '#d4af37'),
array('name' => '沐霞', 'min_comments' => 1200, 'class' => 'vip8', 'color' => '#ffb800'),
array('name' => '共云', 'min_comments' => 2400, 'class' => 'vip9', 'color' => '#ffa500'),
array('name' => '印雪', 'min_comments' => 4800, 'class' => 'vip10', 'color' => '#ff8c00'),
array('name' => '望星', 'min_comments' => 9600, 'class' => 'vip11', 'color' => '#da70d6'),
array('name' => '归真', 'min_comments' => 19200, 'class' => 'vip12', 'color' => '#a42be2'),
);
/**
* 楼层名称默认配置
*/
private static $defaultFloorNames = array('沙发', '板凳', '地板');
/**
* 子楼层名称默认配置
*/
private static $defaultSubFloorNames = array('B1', 'B2', 'B3');
/**
* 激活插件
*/
public static function activate()
{
// 前端样式
Typecho_Plugin::factory('Widget_Archive')->header = array('RecentlyActive_Plugin', 'outputHeader');
// 在评论列表添加用户等级
Typecho_Plugin::factory('Widget_Comments_Archive')->contentEx = array('RecentlyActive_Plugin', 'addUserLevelToComment');
// 在评论列表添加楼层显示
Typecho_Plugin::factory('Widget_Comments_Archive')->contentEx = array('RecentlyActive_Plugin', 'addFloorNumberToComment');
return _t('插件已激活,使用 RecentlyActive_Plugin::show() 显示用户活跃时间');
}
/**
* 禁用插件
*/
public static function deactivate()
{
return _t('插件已禁用');
}
/**
* 插件配置面板
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
// 显示模式
$displayMode = new Typecho_Widget_Helper_Form_Element_Radio('display_mode',
array(
'relative' => '相对时间3小时前',
'absolute' => '绝对时间2023-01-01 12:00',
'smart' => '智能模式1天内用相对时间更早用绝对时间'
),
'smart',
_t('时间显示模式'));
$form->addInput($displayMode);
// 时间格式
$dateFormat = new Typecho_Widget_Helper_Form_Element_Text('date_format',
NULL,
'Y-m-d H:i',
_t('时间格式'),
_t('绝对时间格式Y-m-d H:i'));
$form->addInput($dateFormat);
// 在线状态阈值(分钟)
$onlineThreshold = new Typecho_Widget_Helper_Form_Element_Text('online_threshold',
NULL,
'10',
_t('在线状态阈值(分钟)'),
_t('多少分钟内显示为"在线"状态'));
$form->addInput($onlineThreshold->addRule('isInteger', _t('必须是整数')));
// 是否显示状态点
$showDot = new Typecho_Widget_Helper_Form_Element_Radio('show_dot',
array(
'1' => '显示状态点 ●',
'0' => '不显示状态点'
),
'1',
_t('状态点显示'));
$form->addInput($showDot);
// 默认文字
$defaultText = new Typecho_Widget_Helper_Form_Element_Text('default_text',
NULL,
'从未活跃',
_t('默认显示文字'),
_t('当用户从未活跃时显示的文字'));
$form->addInput($defaultText);
// 楼层显示配置区域
echo '<div class="typecho-page-title"><h2>楼层显示配置</h2></div>';
// 启用楼层显示
$enableFloor = new Typecho_Widget_Helper_Form_Element_Radio('enable_floor',
array(
'1' => '启用',
'0' => '禁用'
),
'1',
_t('启用楼层显示'));
$form->addInput($enableFloor);
// 前三楼名称
$floorNames = new Typecho_Widget_Helper_Form_Element_Text('floor_names',
NULL,
'沙发,板凳,地板',
_t('父评论前三楼名称'),
_t('父评论前三楼显示的名称,用英文逗号分隔,例如:沙发,板凳,地板'));
$form->addInput($floorNames);
// 父评论显示格式
$parentFloorFormat = new Typecho_Widget_Helper_Form_Element_Text('parent_floor_format',
NULL,
'#楼层',
_t('父评论楼层显示格式'),
_t('父评论楼层显示格式,#楼层 会被替换为实际楼层,例如:#楼层楼 会显示为 1楼'));
$form->addInput($parentFloorFormat);
// 新增:子评论前三楼名称
$subFloorNames = new Typecho_Widget_Helper_Form_Element_Text('sub_floor_names',
NULL,
'B1,B2,B3',
_t('子评论前三楼名称'),
_t('子评论前三楼显示的名称用英文逗号分隔例如B1,B2,B3'));
$form->addInput($subFloorNames);
// 新增:子评论显示格式
$subFloorFormat = new Typecho_Widget_Helper_Form_Element_Text('sub_floor_format',
NULL,
'B#楼层',
_t('子评论楼层显示格式'),
_t('子评论楼层显示格式,#楼层 会被替换为实际楼层例如B#楼层 会显示为 B1'));
$form->addInput($subFloorFormat);
// 用户等级配置区域
echo '<div class="typecho-page-title"><h2>用户等级配置</h2></div>';
// 启用用户等级显示
$enableUserLevel = new Typecho_Widget_Helper_Form_Element_Radio('enable_user_level',
array(
'1' => '启用',
'0' => '禁用'
),
'1',
_t('启用用户等级显示'));
$form->addInput($enableUserLevel);
// 管理员标识
$adminLabel = new Typecho_Widget_Helper_Form_Element_Text('admin_label',
NULL,
'博主',
_t('管理员标识'),
_t('管理员在评论中显示的文字'));
$form->addInput($adminLabel);
// 等级配置说明
echo '<div class="message notice"><p>等级配置格式:等级名称|所需评论数|CSS类名|颜色值<br>例如:偶遇|0|vip1|#c0c0c0<br>每行一个等级,按评论数升序排列</p></div>';
// 等级配置
$levelConfig = new Typecho_Widget_Helper_Form_Element_Textarea('level_config',
NULL,
self::getDefaultLevelConfig(),
_t('等级配置'),
_t('每行一个等级:等级名称|所需评论数|CSS类名|颜色值'));
$form->addInput($levelConfig);
// 图标字体CSS
$iconFontCss = new Typecho_Widget_Helper_Form_Element_Textarea('iconfont_css',
NULL,
self::getDefaultIconFontCss(),
_t('图标字体CSS'),
_t('用户等级图标的CSS样式'));
$form->addInput($iconFontCss);
}
/**
* 获取默认等级配置文本
*/
private static function getDefaultLevelConfig()
{
$lines = array();
foreach (self::$defaultLevels as $level) {
$lines[] = "{$level['name']}|{$level['min_comments']}|{$level['class']}|{$level['color']}";
}
return implode("\n", $lines);
}
/**
* 获取默认图标字体CSS - 更新为提供的CSS
*/
private static function getDefaultIconFontCss()
{
$css = '.vipicon {
font-family: "FontAwesome", "iconfont";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
text-align: center;
font-variant: normal;
text-transform: none;
line-height: 1em;
}
.vipicon:before {
content: "\e66a";
font-size: 14px;
}
.com-level { display: inline-block; margin-left: 3px; }
.com-level sub {
font-size: 11px;
vertical-align: baseline;
position: relative;
top: -0.5em;
font-weight: bold;
}
/* 等级颜色定义 - 同时应用到图标和数字 */
.com-level.vip1 .vipicon,
.com-level.vip1 sub { color: #999; }
.com-level.vip2 .vipicon,
.com-level.vip2 sub { color: #8c8c8c; }
.com-level.vip3 .vipicon,
.com-level.vip3 sub { color: #666; }
.com-level.vip4 .vipicon,
.com-level.vip4 sub { color: #52c41a; }
.com-level.vip5 .vipicon,
.com-level.vip5 sub { color: #1890ff; }
.com-level.vip6 .vipicon,
.com-level.vip6 sub { color: #722ed1; }
.com-level.vip7 .vipicon,
.com-level.vip7 sub { color: #faad14; }
.com-level.vip8 .vipicon,
.com-level.vip8 sub { color: #f5222d; }
.com-level.vip9 .vipicon,
.com-level.vip9 sub { color: #eb2f96; }
.com-level.vip10 .vipicon,
.com-level.vip10 sub { color: #fa541c; }
.com-level.vip11 .vipicon,
.com-level.vip11 sub { color: #13c2c2; }
.com-level.vip12 .vipicon,
.com-level.vip12 sub { color: #000; }
/* 动态CSS - 确保用户自定义的颜色通过内联样式生效 */
.com-level .vipicon,
.com-level sub {
color: inherit !important;
}
/* 博主颜色 */
.com-level.blogger .vipicon,
.com-level.blogger sub { color: #f5222d !important; }';
return $css;
}
/**
* 个人用户配置(不需要)
*/
public static function personalConfig(Typecho_Widget_Helper_Form $form) {}
/**
* 输出前端样式
*/
public static function outputHeader()
{
try {
$options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
$enableUserLevel = isset($options->enable_user_level) ? $options->enable_user_level : '1';
} catch (Exception $e) {
$enableUserLevel = '1';
}
// 基础CSS总是输出用于活跃时间显示
$css = <<<CSS
<style>
.recently-active {
display: inline-block;
font-size: 15px;
color: #666;
margin-left: 5px;
}
.recently-active.online {
color: #52c41a;
}
.recently-active.online:before {
font-size: 13px;
vertical-align: middle;
}
.recently-active.online.no-dot:before {
content: "";
}
.recently-active.offline {
color: #999;
}
.recently-active.offline:before {
font-size: 10px;
}
.recently-active.offline.no-dot:before {
content: "";
}
.recently-active-tooltip {
cursor: help;
}
</style>
CSS;
// 只有启用等级显示时才输出等级CSS
if ($enableUserLevel == '1') {
try {
$iconFontCss = isset($options->iconfont_css) ? $options->iconfont_css : self::getDefaultIconFontCss();
$css .= "<style>{$iconFontCss}</style>";
} catch (Exception $e) {
$css .= "<style>" . self::getDefaultIconFontCss() . "</style>";
}
}
echo $css;
}
/**
* 添加楼层号到评论 - 通过钩子自动处理
*/
public static function addFloorNumberToComment($content, $widget, $lastResult)
{
$content = empty($lastResult) ? $content : $lastResult;
try {
// 获取插件配置
$options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
$enableFloor = isset($options->enable_floor) ? $options->enable_floor : '1';
// 如果禁用楼层显示,直接返回原内容
if ($enableFloor == '0') {
return $content;
}
// 判断是父评论还是子评论
$isParentComment = ($widget->parent == 0);
if ($isParentComment) {
// 父评论:统计全部父评论的楼层
$floorNumber = self::getParentCommentFloorNumber($widget->coid, $widget->cid);
$floorHtml = self::generateParentFloorHtml($floorNumber, $options);
return $floorHtml . $content;
} else {
// 子评论:使用新的计数逻辑
$subFloorNumber = self::getSubCommentFloorNumberCorrect($widget->coid, $widget->parent, $widget->cid);
$floorHtml = self::generateSubFloorHtml($subFloorNumber, $options);
return $floorHtml . $content;
}
} catch (Exception $e) {
// 出错时返回原内容,确保评论不消失
return $content;
}
}
/**
* 生成父评论楼层HTML
*/
private static function generateParentFloorHtml($floorNumber, $options)
{
// 获取父评论楼层显示格式
$parentFloorFormat = isset($options->parent_floor_format) ? $options->parent_floor_format : '#楼层';
// 获取父评论楼层名称配置
$floorNames = array();
if (isset($options->floor_names) && !empty($options->floor_names)) {
$floorNames = explode(',', $options->floor_names);
}
// 如果配置为空,使用默认名称
if (empty($floorNames)) {
$floorNames = self::$defaultFloorNames;
}
// 根据楼层数获取显示文本
if ($floorNumber <= count($floorNames) && $floorNumber > 0) {
$floorText = $floorNames[$floorNumber - 1];
} else {
// 使用格式替换
$floorText = str_replace('#楼层', $floorNumber, $parentFloorFormat);
}
// 生成楼层HTML
return '<span class="comment-floor parent-floor" style="margin-right: 5px; font-weight: bold; color: #666;">' . htmlspecialchars($floorText) . '</span>';
}
/**
* 生成子评论楼层HTML
*/
private static function generateSubFloorHtml($subFloorNumber, $options)
{
// 获取子评论楼层显示格式
$subFloorFormat = isset($options->sub_floor_format) ? $options->sub_floor_format : 'B#楼层';
// 获取子评论楼层名称配置
$subFloorNames = array();
if (isset($options->sub_floor_names) && !empty($options->sub_floor_names)) {
$subFloorNames = explode(',', $options->sub_floor_names);
}
// 如果配置为空,使用默认名称
if (empty($subFloorNames)) {
$subFloorNames = self::$defaultSubFloorNames;
}
// 根据楼层数获取显示文本
if ($subFloorNumber <= count($subFloorNames) && $subFloorNumber > 0) {
$floorText = $subFloorNames[$subFloorNumber - 1];
} else {
// 使用格式替换
$floorText = str_replace('#楼层', $subFloorNumber, $subFloorFormat);
}
// 生成楼层HTML
return '<span class="comment-floor sub-floor" style="margin-right: 5px; font-weight: bold; color: #888;">' . htmlspecialchars($floorText) . '</span>';
}
/**
* 获取父评论的楼层号(统计全部父评论)
*/
private static function getParentCommentFloorNumber($currentCoid, $cid)
{
try {
$db = Typecho_Db::get();
// 查询所有父评论parent=0且属于当前文章按时间升序排列
$query = $db->select('coid')
->from('table.comments')
->where('cid = ?', $cid)
->where('parent = ?', 0)
->where('status = ?', 'approved')
->order('coid', Typecho_Db::SORT_ASC);
$comments = $db->fetchAll($query);
// 查找当前评论在数组中的位置从1开始
foreach ($comments as $index => $comment) {
if ($comment['coid'] == $currentCoid) {
return $index + 1;
}
}
// 如果没找到返回1
return 1;
} catch (Exception $e) {
return 1;
}
}
/**
* 获取子评论的楼层号 - 正确版本:统计同一父评论下的所有子评论
*/
private static function getSubCommentFloorNumberCorrect($currentCoid, $parentCoid, $cid)
{
try {
$db = Typecho_Db::get();
// 首先,我们需要找到当前评论的根父评论
// 对于嵌套回复,根父评论是最顶层的父评论
$rootParentId = self::findRootParent($currentCoid, $parentCoid, $cid);
// 如果找到的根父评论不是直接父评论,使用根父评论
if ($rootParentId != $parentCoid) {
$parentCoid = $rootParentId;
}
// 现在获取该父评论下的所有子评论按coid排序
$allChildren = self::getAllDirectAndNestedChildren($parentCoid, $cid);
// 按coid排序所有子评论
usort($allChildren, function($a, $b) {
return $a['coid'] - $b['coid'];
});
// 查找当前评论在所有子评论中的位置
foreach ($allChildren as $index => $comment) {
if ($comment['coid'] == $currentCoid) {
return $index + 1; // 返回楼层号
}
}
// 如果没找到,可能是查询有问题,尝试简单查询
$simpleQuery = $db->select('coid')
->from('table.comments')
->where('cid = ?', $cid)
->where('parent = ?', $parentCoid)
->where('status = ?', 'approved')
->order('coid', Typecho_Db::SORT_ASC);
$directChildren = $db->fetchAll($simpleQuery);
foreach ($directChildren as $index => $comment) {
if ($comment['coid'] == $currentCoid) {
return $index + 1;
}
}
return 1; // 默认返回1
} catch (Exception $e) {
return 1;
}
}
/**
* 查找评论的根父评论
*/
private static function findRootParent($currentCoid, $parentCoid, $cid)
{
try {
$db = Typecho_Db::get();
// 如果当前评论的父评论是0说明它自己就是父评论
if ($parentCoid == 0) {
return $currentCoid;
}
// 检查父评论的父评论
$currentParentId = $parentCoid;
$visited = array($currentCoid); // 防止循环
for ($i = 0; $i < 20; $i++) { // 最多追踪20层
if (in_array($currentParentId, $visited)) {
break; // 避免循环
}
$visited[] = $currentParentId;
$parentComment = $db->fetchRow($db->select('parent')
->from('table.comments')
->where('coid = ?', $currentParentId)
->where('cid = ?', $cid)
->limit(1));
if (!$parentComment || $parentComment['parent'] == 0) {
// 找到了根父评论
return $currentParentId;
}
$currentParentId = $parentComment['parent'];
}
// 如果循环结束还没找到,返回直接父评论
return $parentCoid;
} catch (Exception $e) {
return $parentCoid;
}
}
/**
* 获取父评论下的所有子评论(包括嵌套的)
*/
private static function getAllDirectAndNestedChildren($parentCoid, $cid)
{
$db = Typecho_Db::get();
$allChildren = array();
// 递归获取所有子评论
self::collectChildrenRecursively($parentCoid, $cid, $db, $allChildren);
return $allChildren;
}
/**
* 递归收集子评论
*/
private static function collectChildrenRecursively($parentCoid, $cid, $db, &$allChildren)
{
// 获取当前父评论的直接子评论
$children = $db->fetchAll($db->select('coid', 'parent')
->from('table.comments')
->where('cid = ?', $cid)
->where('parent = ?', $parentCoid)
->where('status = ?', 'approved')
->order('coid', Typecho_Db::SORT_ASC));
foreach ($children as $child) {
$allChildren[] = $child;
// 递归获取子评论的子评论
self::collectChildrenRecursively($child['coid'], $cid, $db, $allChildren);
}
}
/**
* 添加用户等级到评论
*/
public static function addUserLevelToComment($content, $widget, $lastResult)
{
$content = empty($lastResult) ? $content : $lastResult;
try {
// 获取插件配置
$options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
$enableUserLevel = isset($options->enable_user_level) ? $options->enable_user_level : '1';
// 如果禁用等级显示,直接返回原内容
if ($enableUserLevel == '0') {
return $content;
}
// 获取评论者邮箱
$email = $widget->mail;
// 如果是游客评论,不显示等级
if (!$email) {
return $content;
}
// 通过邮箱获取用户信息
$userInfo = self::getUserInfoByEmail($email);
// 获取用户组信息
$isAdmin = false;
if ($userInfo && isset($userInfo['group'])) {
// Typecho中管理员用户组是 'administrator'
$isAdmin = ($userInfo['group'] == 'administrator');
}
// 如果是管理员,显示博主标识
if ($isAdmin) {
$adminLabel = isset($options->admin_label) ? $options->admin_label : '';
return $content . '<!--<span class="com-level blogger" title="' . htmlspecialchars($adminLabel) . '" style="color: #f5222d !important;"><span class="iconfont vipicon"></span><sub>博主</sub></span>-->';
}
// 获取用户评论数
$commentCount = self::getUserCommentCountByEmail($email);
// 获取用户等级
$levelInfo = self::getUserLevel($commentCount, $options);
// 生成等级HTML
$levelHtml = self::generateLevelHtml($levelInfo, $commentCount);
return $content . $levelHtml;
} catch (Exception $e) {
// 出错时返回原内容,确保评论不消失
return $content;
}
}
/**
* 根据邮箱获取用户信息
*/
private static function getUserInfoByEmail($email)
{
if (!$email) return null;
try {
$db = Typecho_Db::get();
$user = $db->fetchRow($db->select('uid', 'name', 'mail', 'group', 'url')
->from('table.users')
->where('mail = ?', $email)
->limit(1));
return $user;
} catch (Exception $e) {
return null;
}
}
/**
* 根据邮箱获取用户评论数
*/
private static function getUserCommentCountByEmail($email)
{
if (!$email) return 0;
try {
$db = Typecho_Db::get();
$result = $db->fetchAll($db->select(array('COUNT(cid)' => 'commentNum'))
->from('table.comments')
->where('mail = ?', $email));
return $result && isset($result[0]['commentNum']) ? intval($result[0]['commentNum']) : 0;
} catch (Exception $e) {
return 0;
}
}
/**
* 获取用户等级信息
*/
private static function getUserLevel($commentCount, $options)
{
// 解析等级配置
$levels = self::parseLevelConfig($options);
$currentLevel = null;
$nextLevel = null;
// 查找当前等级
for ($i = 0; $i < count($levels); $i++) {
if ($commentCount >= $levels[$i]['min_comments']) {
$currentLevel = $levels[$i];
$currentLevel['index'] = $i + 1;
// 获取下一等级
if (isset($levels[$i + 1])) {
$nextLevel = $levels[$i + 1];
}
} else {
break;
}
}
// 如果没有找到等级,使用第一个等级
if (!$currentLevel && count($levels) > 0) {
$currentLevel = $levels[0];
$currentLevel['index'] = 1;
if (isset($levels[1])) {
$nextLevel = $levels[1];
}
}
return array(
'current' => $currentLevel,
'next' => $nextLevel,
'comment_count' => $commentCount
);
}
/**
* 解析等级配置
*/
private static function parseLevelConfig($options)
{
$levels = array();
if (isset($options->level_config) && !empty($options->level_config)) {
$lines = explode("\n", $options->level_config);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
$parts = explode('|', $line);
if (count($parts) >= 3) {
$level = array(
'name' => trim($parts[0]),
'min_comments' => intval(trim($parts[1])),
'class' => trim($parts[2])
);
// 修复:正确处理颜色值,包括空值情况
if (count($parts) >= 4) {
$color = trim($parts[3]);
if (!empty($color)) {
$level['color'] = $color;
} else {
// 如果颜色为空,使用默认等级对应的颜色
$level['color'] = self::getDefaultColorByClass($level['class']);
}
} else {
// 如果没有颜色配置,使用默认等级对应的颜色
$level['color'] = self::getDefaultColorByClass($level['class']);
}
$levels[] = $level;
}
}
}
// 如果没有配置,使用默认配置
if (empty($levels)) {
$levels = self::$defaultLevels;
}
// 按评论数排序
usort($levels, function($a, $b) {
return $a['min_comments'] - $b['min_comments'];
});
return $levels;
}
/**
* 根据等级类名获取默认颜色
*/
private static function getDefaultColorByClass($levelClass)
{
foreach (self::$defaultLevels as $level) {
if ($level['class'] == $levelClass) {
return $level['color'];
}
}
return '#666'; // 默认颜色
}
/**
* 生成等级HTML - 修复:使用内联样式确保颜色生效
*/
private static function generateLevelHtml($levelInfo, $commentCount)
{
if (!$levelInfo['current']) {
return '';
}
$current = $levelInfo['current'];
$next = $levelInfo['next'];
// 构建title提示文本
$title = htmlspecialchars($current['name']);
$title .= " · {$commentCount}";
if ($next) {
$needed = $next['min_comments'] - $commentCount;
if ($needed > 0) {
$title .= " · 再{$needed}评升至" . htmlspecialchars($next['name']);
}
}
// 生成HTML - 使用行内样式确保颜色生效
$html = sprintf(
'<span class="com-level %s" title="%s" style="color: %s !important;">',
$current['class'],
$title,
$current['color']
);
$html .= '<span style="margin-left:-5px;" class="iconfont vipicon"></span>';
$html .= sprintf('<sub>%d</sub>', $current['index']);
$html .= '</span>';
return $html;
}
/**
* 获取用户活跃时间
*/
public static function getActiveTime($userId)
{
if (!$userId) return 0;
try {
$db = Typecho_Db::get();
$user = $db->fetchRow($db->select('activated')
->from('table.users')
->where('uid = ?', $userId));
return $user && isset($user['activated']) ? intval($user['activated']) : 0;
} catch (Exception $e) {
return 0;
}
}
/**
* 格式化时间显示
*/
public static function formatTime($timestamp, $options = null)
{
// 如果未提供options尝试获取插件配置
if (!$options) {
try {
$options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
} catch (Exception $e) {
$options = (object)[
'display_mode' => 'smart',
'date_format' => 'Y-m-d H:i',
'default_text' => '从未活跃'
];
}
}
if (!$timestamp || $timestamp == 0) {
return isset($options->default_text) ? $options->default_text : '从未活跃';
}
$currentTime = time();
$diff = $currentTime - $timestamp;
$displayMode = isset($options->display_mode) ? $options->display_mode : 'smart';
// 相对时间模式 - 始终返回相对时间
if ($displayMode == 'relative') {
return self::getRelativeTime($diff);
}
// 智能模式
if ($displayMode == 'smart') {
if ($diff < 86400) { // 24小时内
return self::getRelativeTime($diff);
} else {
$format = isset($options->date_format) ? $options->date_format : 'Y-m-d H:i';
return date($format, $timestamp);
}
}
// 绝对时间模式
$format = isset($options->date_format) ? $options->date_format : 'Y-m-d H:i';
return date($format, $timestamp);
}
/**
* 获取相对时间文本
*/
private static function getRelativeTime($diff)
{
if ($diff < 60) {
return '刚刚';
} elseif ($diff < 3600) {
$minutes = floor($diff / 60);
return $minutes . '分钟前';
} elseif ($diff < 86400) {
$hours = floor($diff / 3600);
return $hours . '小时前';
} elseif ($diff < 2592000) { // 30天
$days = floor($diff / 86400);
return $days . '天前';
} elseif ($diff < 31536000) { // 365天
$months = floor($diff / 2592000);
return $months . '个月前';
} else {
$years = floor($diff / 31536000);
return $years . '年前';
}
}
/**
* 判断是否在线
*/
private static function isOnline($timestamp, $threshold)
{
if (!$timestamp) return false;
$currentTime = time();
$diff = $currentTime - $timestamp;
$thresholdSeconds = $threshold * 60; // 转换为秒
return $diff < $thresholdSeconds;
}
/**
* 前端显示函数 - 核心方法
*/
public static function show($userId = null, $customOptions = array())
{
if (!$userId) {
$user = Typecho_Widget::widget('Widget_User');
$userId = $user->uid;
}
if (!$userId) {
return '';
}
$activeTime = self::getActiveTime($userId);
// 获取插件配置
$options = null;
try {
$optionsObj = Typecho_Widget::widget('Widget_Options');
if ($optionsObj) {
$options = $optionsObj->plugin('RecentlyActive');
}
} catch (Exception $e) {
// 忽略异常,使用默认配置
}
if (!$options) {
$options = (object)[
'display_mode' => 'smart',
'date_format' => 'Y-m-d H:i',
'default_text' => '从未活跃',
'online_threshold' => '10',
'show_dot' => '1'
];
}
// 合并自定义选项
if (!empty($customOptions)) {
foreach ($customOptions as $key => $value) {
$options->$key = $value;
}
}
$formattedTime = self::formatTime($activeTime, $options);
// 构建CSS类
$classes = array('recently-active');
$onlineThreshold = isset($options->online_threshold) ? intval($options->online_threshold) : 10;
if (self::isOnline($activeTime, $onlineThreshold)) {
$classes[] = 'online';
} else {
$classes[] = 'offline';
}
// 是否显示状态点
$showDot = isset($options->show_dot) ? $options->show_dot : '1';
if ($showDot == '0') {
$classes[] = 'no-dot';
}
$classStr = implode(' ', $classes);
// 完整时间用于title提示
$fullTime = $activeTime ? date('Y-m-d H:i:s', $activeTime) : '从未活跃';
return sprintf(
'<span class="%s recently-active-tooltip" title="最后活跃: %s">%s</span>',
$classStr,
htmlspecialchars($fullTime),
htmlspecialchars($formattedTime)
);
}
/**
* 获取最近活跃用户列表(侧边栏小工具)
*/
public static function getActiveUsers($limit = 5)
{
try {
$db = Typecho_Db::get();
$users = $db->fetchAll($db->select('uid', 'name', 'mail', 'url', 'activated')
->from('table.users')
->where('activated > 0')
->order('activated', Typecho_Db::SORT_DESC)
->limit($limit));
$result = array();
foreach ($users as $user) {
$result[] = array(
'uid' => $user['uid'],
'name' => $user['name'],
'mail' => $user['mail'],
'url' => $user['url'],
'activated' => $user['activated'],
'avatar' => self::getGravatar($user['mail']),
'timeText' => self::formatTime($user['activated'])
);
}
return $result;
} catch (Exception $e) {
return array();
}
}
/**
* 获取Gravatar头像
*/
private static function getGravatar($email, $size = 40)
{
$hash = md5(strtolower(trim($email)));
return "https://www.gravatar.com/avatar/{$hash}?s={$size}&d=identicon&r=g";
}
/**
* 获取用户的等级信息(公共方法,可在主题中使用)
*/
public static function getUserLevelInfo($userId = null)
{
if (!$userId) {
$user = Typecho_Widget::widget('Widget_User');
$userId = $user->uid;
}
if (!$userId) {
return null;
}
try {
$options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
$commentCount = self::getUserCommentCount($userId);
return self::getUserLevel($commentCount, $options);
} catch (Exception $e) {
return null;
}
}
/**
* 显示用户等级(公共方法,可在主题中使用)
*/
public static function showUserLevel($userId = null)
{
if (!$userId) {
return '';
}
try {
// 获取插件配置
$options = Typecho_Widget::widget('Widget_Options')->plugin('RecentlyActive');
$enableUserLevel = isset($options->enable_user_level) ? $options->enable_user_level : '1';
// 如果禁用等级显示,直接返回空
if ($enableUserLevel == '0') {
return '';
}
// 获取用户信息
$userInfo = self::getUserInfoById($userId);
// 判断是否是管理员
$isAdmin = false;
if ($userInfo && isset($userInfo['group'])) {
$isAdmin = ($userInfo['group'] == 'administrator');
}
// 如果是管理员,显示博主标识
if ($isAdmin) {
$adminLabel = isset($options->admin_label) ? $options->admin_label : '';
return '<!--<span class="com-level blogger" title="' . htmlspecialchars($adminLabel) . '" style="color: #f5222d !important;"><span class="iconfont vipicon"></span><sub>博主</sub></span>-->';
}
// 获取用户评论数
$commentCount = self::getUserCommentCount($userId);
// 获取用户等级
$levelInfo = self::getUserLevel($commentCount, $options);
// 生成等级HTML
return self::generateLevelHtml($levelInfo, $commentCount);
} catch (Exception $e) {
// 出错时返回空,不影响页面显示
return '';
}
}
/**
* 根据用户ID获取用户信息
*/
private static function getUserInfoById($userId)
{
if (!$userId) return null;
try {
$db = Typecho_Db::get();
$user = $db->fetchRow($db->select('uid', 'name', 'mail', 'group', 'url')
->from('table.users')
->where('uid = ?', $userId)
->limit(1));
return $user;
} catch (Exception $e) {
return null;
}
}
/**
* 获取用户评论数通过用户ID
*/
private static function getUserCommentCount($userId)
{
if (!$userId) return 0;
try {
$db = Typecho_Db::get();
$result = $db->fetchRow($db->select('COUNT(*) as cnt')
->from('table.comments')
->where('authorId = ?', $userId)
->where('status = ?', 'approved'));
return $result ? intval($result['cnt']) : 0;
} catch (Exception $e) {
return 0;
}
}
/**
* 前端调用:显示楼层号(静态方法,可在主题中调用)- 修复版本
*/
public static function showFloorNumber($coid = null, $cid = null, $customOptions = array())
{
if (!$coid || !$cid) {
return '';
}
try {
// 获取插件配置
$options = null;
try {
$optionsObj = Typecho_Widget::widget('Widget_Options');
if ($optionsObj) {
$options = $optionsObj->plugin('RecentlyActive');
}
} catch (Exception $e) {
// 忽略异常,使用默认配置
}
if (!$options) {
$options = (object)[
'enable_floor' => '1',
'floor_names' => '沙发,板凳,地板',
'parent_floor_format' => '#楼层',
'sub_floor_names' => 'B1,B2,B3',
'sub_floor_format' => 'B#楼层'
];
}
// 合并自定义选项
if (!empty($customOptions)) {
foreach ($customOptions as $key => $value) {
$options->$key = $value;
}
}
$enableFloor = isset($options->enable_floor) ? $options->enable_floor : '1';
// 如果禁用楼层显示,直接返回空
if ($enableFloor == '0') {
return '';
}
// 判断评论类型(需要查询数据库获取评论信息)
$commentInfo = self::getCommentInfo($coid);
if (!$commentInfo) {
return '';
}
$isParentComment = ($commentInfo['parent'] == 0);
if ($isParentComment) {
// 父评论楼层
$floorNumber = self::getParentCommentFloorNumber($coid, $cid);
$floorText = self::getParentFloorText($floorNumber, $options);
$class = 'parent-floor';
$style = 'margin-right: 5px; font-weight: bold; color: #666;';
} else {
// 子评论楼层 - 使用新的正确计数方法
$parentId = $commentInfo['parent'];
$subFloorNumber = self::getSubCommentFloorNumberCorrect($coid, $parentId, $cid);
$floorText = self::getSubFloorText($subFloorNumber, $options);
$class = 'sub-floor';
$style = 'margin-right: 5px; font-weight: bold; color: #888;';
}
// 生成楼层HTML
return '<span class="comment-floor ' . $class . '" style="' . $style . '">' . htmlspecialchars($floorText) . '</span>';
} catch (Exception $e) {
// 出错时返回空
return '';
}
}
/**
* 获取父评论楼层文本
*/
private static function getParentFloorText($floorNumber, $options)
{
// 获取父评论楼层名称配置
$floorNames = array();
if (isset($options->floor_names) && !empty($options->floor_names)) {
$floorNames = explode(',', $options->floor_names);
}
// 如果配置为空,使用默认名称
if (empty($floorNames)) {
$floorNames = self::$defaultFloorNames;
}
// 根据楼层数获取显示文本
if ($floorNumber <= count($floorNames) && $floorNumber > 0) {
return $floorNames[$floorNumber - 1];
} else {
// 使用格式替换
$parentFloorFormat = isset($options->parent_floor_format) ? $options->parent_floor_format : '#楼层';
return str_replace('#楼层', $floorNumber, $parentFloorFormat);
}
}
/**
* 获取子评论楼层文本
*/
private static function getSubFloorText($subFloorNumber, $options)
{
// 获取子评论楼层名称配置
$subFloorNames = array();
if (isset($options->sub_floor_names) && !empty($options->sub_floor_names)) {
$subFloorNames = explode(',', $options->sub_floor_names);
}
// 如果配置为空,使用默认名称
if (empty($subFloorNames)) {
$subFloorNames = self::$defaultSubFloorNames;
}
// 根据楼层数获取显示文本
if ($subFloorNumber <= count($subFloorNames) && $subFloorNumber > 0) {
return $subFloorNames[$subFloorNumber - 1];
} else {
// 使用格式替换
$subFloorFormat = isset($options->sub_floor_format) ? $options->sub_floor_format : 'B#楼层';
return str_replace('#楼层', $subFloorNumber, $subFloorFormat);
}
}
/**
* 获取评论信息
*/
private static function getCommentInfo($coid)
{
try {
$db = Typecho_Db::get();
$comment = $db->fetchRow($db->select('coid', 'parent', 'cid')
->from('table.comments')
->where('coid = ?', $coid)
->limit(1));
return $comment;
} catch (Exception $e) {
return null;
}
}
}