From 3576359fb331f7b1ba9b155363ae73e653fea04b Mon Sep 17 00:00:00 2001
From: XIGE <710062962@qq.com>
Date: Mon, 23 Feb 2026 19:55:05 +0800
Subject: [PATCH] 1.0
---
Plugin.php | 2267 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 2267 insertions(+)
create mode 100644 Plugin.php
diff --git a/Plugin.php b/Plugin.php
new file mode 100644
index 0000000..147a79e
--- /dev/null
+++ b/Plugin.php
@@ -0,0 +1,2267 @@
+header = array('RuiPing_Plugin', 'header');
+ Typecho_Plugin::factory('Widget_Archive')->footer = array('RuiPing_Plugin', 'footer');
+
+ // 在后台页面添加代码
+ Typecho_Plugin::factory('admin/header.php')->header = array('RuiPing_Plugin', 'addAdminHeader');
+ Typecho_Plugin::factory('admin/footer.php')->end = array('RuiPing_Plugin', 'addAdminFooter');
+
+ // 使用Typecho标准的方式处理AJAX
+ Typecho_Plugin::factory('Widget_Archive')->handleInit = array('RuiPing_Plugin', 'handleInit');
+
+ // 添加前端评论标识钩子
+ Typecho_Plugin::factory('Widget_Archive')->beforeRender = array('RuiPing_Plugin', 'beforeRender');
+
+ return _t('锐评插件已激活');
+ }
+
+ /**
+ * 禁用插件
+ */
+ public static function deactivate()
+ {
+ return _t('锐评插件已禁用');
+ }
+
+ /**
+ * 插件配置面板
+ */
+ public static function config(Typecho_Widget_Helper_Form $form)
+ {
+ // 锐评ID设置
+ $commentIds = new Typecho_Widget_Helper_Form_Element_Textarea('commentIds',
+ NULL,
+ '',
+ _t('锐评ID'),
+ _t('每行一个评论ID,不限制数量。可以在评论管理页面找到评论ID'));
+ $form->addInput($commentIds);
+
+ // 每页显示数量
+ $limit = new Typecho_Widget_Helper_Form_Element_Text('limit',
+ NULL,
+ '10',
+ _t('每页数量'),
+ _t('每页显示的评论数量,超过此数量将分页显示'));
+ $form->addInput($limit);
+
+ // 显示样式设置
+ $showAuthor = new Typecho_Widget_Helper_Form_Element_Radio('showAuthor',
+ array(
+ '1' => _t('显示'),
+ '0' => _t('不显示')
+ ),
+ '1',
+ _t('显示评论人'));
+ $form->addInput($showAuthor);
+
+ $showDate = new Typecho_Widget_Helper_Form_Element_Radio('showDate',
+ array(
+ '1' => _t('显示'),
+ '0' => _t('不显示')
+ ),
+ '1',
+ _t('显示评论时间'));
+ $form->addInput($showDate);
+
+ $showContent = new Typecho_Widget_Helper_Form_Element_Radio('showContent',
+ array(
+ '1' => _t('显示'),
+ '0' => _t('不显示')
+ ),
+ '1',
+ _t('显示评论内容'));
+ $form->addInput($showContent);
+
+ $showSource = new Typecho_Widget_Helper_Form_Element_Radio('showSource',
+ array(
+ '1' => _t('显示'),
+ '0' => _t('不显示')
+ ),
+ '1',
+ _t('显示来源文章'));
+ $form->addInput($showSource);
+
+ // 新增:显示原文设置
+ $showOriginal = new Typecho_Widget_Helper_Form_Element_Radio('showOriginal',
+ array(
+ '1' => _t('显示'),
+ '0' => _t('不显示')
+ ),
+ '1',
+ _t('显示原文链接'));
+ $form->addInput($showOriginal);
+
+ // 链接设置
+ $authorLink = new Typecho_Widget_Helper_Form_Element_Radio('authorLink',
+ array(
+ '1' => _t('新窗口打开'),
+ '0' => _t('当前窗口打开')
+ ),
+ '1',
+ _t('作者链接打开方式'));
+ $form->addInput($authorLink);
+
+ $commentLink = new Typecho_Widget_Helper_Form_Element_Radio('commentLink',
+ array(
+ '1' => _t('新窗口打开'),
+ '0' => _t('当前窗口打开')
+ ),
+ '1',
+ _t('评论链接打开方式'));
+ $form->addInput($commentLink);
+
+ // 分页设置
+ $showPagination = new Typecho_Widget_Helper_Form_Element_Radio('showPagination',
+ array(
+ '1' => _t('显示'),
+ '0' => _t('不显示')
+ ),
+ '1',
+ _t('显示分页'));
+ $form->addInput($showPagination);
+
+ // 调试模式
+ $debugMode = new Typecho_Widget_Helper_Form_Element_Radio('debugMode',
+ array(
+ '1' => _t('开启'),
+ '0' => _t('关闭')
+ ),
+ '0',
+ _t('调试模式'),
+ _t('开启后显示调试信息,用于排查问题'));
+ $form->addInput($debugMode);
+
+ // 新增:前端锐评标识设置
+ $showFrontendBadge = new Typecho_Widget_Helper_Form_Element_Radio('showFrontendBadge',
+ array(
+ '1' => _t('显示'),
+ '0' => _t('不显示')
+ ),
+ '1',
+ _t('前端锐评标识'),
+ _t('在文章评论区中为锐评评论显示标识'));
+ $form->addInput($showFrontendBadge);
+
+ $frontendBadgePosition = new Typecho_Widget_Helper_Form_Element_Select('frontendBadgePosition',
+ array(
+ 'content-top-right' => _t('内容右上角'),
+ 'content-top-left' => _t('内容左上角'),
+ 'comment-top-right' => _t('评论整体右上角'),
+ 'after-author' => _t('作者名后')
+ ),
+ 'content-top-right',
+ _t('标识位置'));
+ $form->addInput($frontendBadgePosition);
+
+ $frontendBadgeText = new Typecho_Widget_Helper_Form_Element_Text('frontendBadgeText',
+ NULL,
+ '❤ 锐评',
+ _t('标识文字'));
+ $form->addInput($frontendBadgeText);
+
+ $frontendBadgeColor = new Typecho_Widget_Helper_Form_Element_Text('frontendBadgeColor',
+ NULL,
+ '#d32f2f',
+ _t('标识颜色'));
+ $form->addInput($frontendBadgeColor);
+
+ // 备份和恢复设置
+ echo '
+
锐评插件 - 设置备份与恢复
+ ';
+
+ // 备份设置
+ $backupBtn = new Typecho_Widget_Helper_Form_Element_Submit('backupBtn', NULL, _t('备份当前设置'),
+ _t('将当前插件设置备份到服务器'));
+ $backupBtn->input->setAttribute('class', 'btn primary');
+ $backupBtn->input->setAttribute('onclick', 'heartfeltBackupSettings()');
+ $form->addItem($backupBtn);
+
+ // 恢复设置
+ $restoreBtn = new Typecho_Widget_Helper_Form_Element_Submit('restoreBtn', NULL, _t('从备份恢复'),
+ _t('从最近的备份恢复插件设置'));
+ $restoreBtn->input->setAttribute('class', 'btn');
+ $restoreBtn->input->setAttribute('onclick', 'heartfeltRestoreSettings()');
+ $form->addItem($restoreBtn);
+
+ // 下载备份
+ $downloadBtn = new Typecho_Widget_Helper_Form_Element_Submit('downloadBtn', NULL, _t('下载备份文件'),
+ _t('下载当前设置的备份文件'));
+ $downloadBtn->input->setAttribute('class', 'btn');
+ $downloadBtn->input->setAttribute('onclick', 'heartfeltDownloadBackup()');
+ $form->addItem($downloadBtn);
+
+ // 上传备份
+ $uploadArea = new Typecho_Widget_Helper_Form_Element_Textarea('uploadBackup', NULL, '',
+ _t('上传备份文件'),
+ _t('粘贴备份文件内容或上传备份文件进行恢复'));
+ $form->addInput($uploadArea);
+
+ // 添加JavaScript
+ self::addBackupScript();
+ }
+
+ /**
+ * 个人用户配置面板
+ */
+ public static function personalConfig(Typecho_Widget_Helper_Form $form) {}
+
+ /**
+ * 处理初始化
+ */
+ public static function handleInit()
+ {
+ // 检查是否是我们的AJAX请求
+ if (isset($_GET['action']) && $_GET['action'] == 'heartfelt-comments') {
+ self::handleAjax();
+ exit;
+ }
+
+ // 处理获取锐评ID列表的请求(前端使用)
+ if (isset($_GET['action']) && $_GET['action'] == 'ruiping-get-ids') {
+ self::getHeartfeltIds();
+ exit;
+ }
+
+ // 处理备份相关的AJAX请求
+ if (isset($_POST['heartfelt_backup_action'])) {
+ self::handleBackupAjax();
+ exit;
+ }
+
+ // 处理下载备份请求
+ if (isset($_GET['heartfelt_backup_action']) && $_GET['heartfelt_backup_action'] == 'download') {
+ self::downloadBackup();
+ exit;
+ }
+
+ // 处理批量操作请求
+ if (isset($_POST['heartfelt_batch_action'])) {
+ self::handleBatchAction();
+ exit;
+ }
+
+ // 处理锐评筛选页面
+ if (isset($_GET['heartfelt_filter'])) {
+ self::handleHeartfeltFilter();
+ exit;
+ }
+ }
+/**
+ * 获取锐评ID列表(前端使用)
+ */
+private static function getHeartfeltIds()
+{
+ header('Content-Type: application/json; charset=utf-8');
+
+ try {
+ $options = Typecho_Widget::widget('Widget_Options');
+ $config = $options->plugin('RuiPing');
+
+ if (!$config) {
+ echo json_encode(array('success' => true, 'ids' => array()));
+ exit;
+ }
+
+ $ids = self::getCommentIds($config->commentIds);
+
+ echo json_encode(array(
+ 'success' => true,
+ 'ids' => $ids,
+ 'showFrontendBadge' => isset($config->showFrontendBadge) ? $config->showFrontendBadge : '1',
+ 'badgeText' => isset($config->frontendBadgeText) ? $config->frontendBadgeText : '❤ 锐评',
+ 'badgePosition' => isset($config->frontendBadgePosition) ? $config->frontendBadgePosition : 'content-top-right',
+ 'badgeColor' => isset($config->frontendBadgeColor) ? $config->frontendBadgeColor : '#d32f2f',
+ // 新增:跳转链接和新窗口配置
+ 'badgeLink' => 'https://www.shitoucuo.com/rp.html', // 固定跳转链接
+ 'openInNewWindow' => true // 固定新窗口打开
+ ));
+
+ } catch (Exception $e) {
+ echo json_encode(array(
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ 'ids' => array()
+ ));
+ }
+ exit;
+}
+
+/**
+ * 页面渲染前执行(添加前端标识)
+ */
+public static function beforeRender()
+{
+ $options = Typecho_Widget::widget('Widget_Options');
+ $config = $options->plugin('RuiPing');
+
+ // 检查是否启用前端标识
+ if (!$config || (isset($config->showFrontendBadge) && $config->showFrontendBadge == '0')) {
+ return;
+ }
+
+ // 只在前台文章页面添加
+ if (Typecho_Widget::widget('Widget_Archive')->is('single')) {
+ // 添加JavaScript到页面
+ $ajaxUrl = Typecho_Common::url('index.php?action=ruiping-get-ids', $options->siteUrl);
+ $badgeText = isset($config->frontendBadgeText) ? htmlspecialchars($config->frontendBadgeText) : '❤ 锐评';
+ $badgePosition = isset($config->frontendBadgePosition) ? $config->frontendBadgePosition : 'content-top-right';
+ $badgeColor = isset($config->frontendBadgeColor) ? $config->frontendBadgeColor : '#d32f2f';
+ $badgeLink = 'https://www.shitoucuo.com/rp.html'; // 固定跳转链接
+ $openInNewWindow = true; // 固定新窗口打开
+
+ $js = <<
+(function() {
+ // 等待页面加载完成
+ document.addEventListener('DOMContentLoaded', function() {
+ // 获取锐评ID列表
+ fetch('{$ajaxUrl}')
+ .then(response => response.json())
+ .then(data => {
+ if (data.success && data.ids && data.ids.length > 0) {
+ // 标记锐评评论
+ markHeartfeltComments(data.ids, {
+ badgeText: data.badgeText || '{$badgeText}',
+ badgePosition: data.badgePosition || '{$badgePosition}',
+ badgeColor: data.badgeColor || '{$badgeColor}',
+ badgeLink: data.badgeLink || '{$badgeLink}',
+ openInNewWindow: data.openInNewWindow || {$openInNewWindow}
+ });
+ }
+ })
+ .catch(error => {
+ console.log('锐评插件:获取ID失败', error);
+ });
+ });
+
+ /**
+ * 标记锐评评论
+ * @param {Array} heartfeltIds 锐评ID数组
+ * @param {Object} options 配置选项
+ */
+ function markHeartfeltComments(heartfeltIds, options) {
+ if (!heartfeltIds || heartfeltIds.length === 0) return;
+
+ heartfeltIds = heartfeltIds.map(id => parseInt(id));
+
+ // 查找所有评论项
+ const commentItems = document.querySelectorAll('li[id^="comment-"], li.comment-body, .comment-body');
+
+ commentItems.forEach(item => {
+ // 从ID或数据属性中提取评论ID
+ let commentId = null;
+
+ // 尝试从id属性获取
+ const idAttr = item.getAttribute('id');
+ if (idAttr) {
+ const match = idAttr.match(/comment-(\d+)/);
+ if (match && match[1]) {
+ commentId = parseInt(match[1]);
+ }
+ }
+
+ // 如果找到了评论ID且是锐评,添加标识
+ if (commentId && heartfeltIds.includes(commentId)) {
+ addHeartfeltBadge(item, options, commentId);
+ }
+ });
+ }
+
+ /**
+ * 添加锐评标识
+ * @param {HTMLElement} commentElement 评论元素
+ * @param {Object} options 配置选项
+ * @param {Number} commentId 评论ID
+ */
+ function addHeartfeltBadge(commentElement, options, commentId) {
+ // 检查是否已经添加过标识
+ if (document.querySelector('.heartfelt-frontend-badge[data-comment-id="' + commentId + '"]')) {
+ return;
+ }
+
+ // 根据位置选择目标元素
+ const badgePosition = options.badgePosition || 'content-top-right';
+ let targetElement = null;
+ let isContentTarget = false;
+
+ if (badgePosition.includes('content')) {
+ // 找评论内容容器 - 针对你的评论模板
+ const contentElements = commentElement.querySelectorAll('.comment-content, .cm');
+ if (contentElements.length > 0) {
+ targetElement = contentElements[0];
+ isContentTarget = true;
+ } else {
+ // 如果没有找到特定的内容容器,使用整个评论元素
+ targetElement = commentElement;
+ }
+ } else {
+ targetElement = commentElement;
+ }
+
+ // 创建标识容器
+ const badge = document.createElement('span');
+ badge.className = 'heartfelt-frontend-badge';
+ badge.setAttribute('data-comment-id', commentId);
+
+ // 判断是否有链接,如果有则创建a标签,否则直接显示文本
+ if (options.badgeLink) {
+ // 创建可点击的链接
+ const link = document.createElement('a');
+ link.href = options.badgeLink;
+ if (options.openInNewWindow) {
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ }
+ link.textContent = options.badgeText || '❤ 锐评';
+ link.style.color = '#fff';
+ link.style.textDecoration = 'none'; // 移除下划线装饰
+ link.style.display = 'block';
+ link.title = '点击可以跳转锐评汇总页面喔'; // 添加title提示
+
+ badge.appendChild(link);
+ badge.style.cursor = 'pointer'; // 显示手型光标
+ } else {
+ // 没有链接,直接显示文本
+ badge.textContent = options.badgeText || '❤ 锐评';
+ badge.style.cursor = 'default';
+ }
+
+ badge.style.backgroundColor = options.badgeColor || '#d32f2f';
+
+ // 设置标识位置
+ switch (badgePosition) {
+ case 'content-top-right':
+ badge.style.position = 'absolute';
+ badge.style.top = '8px';
+ badge.style.right = '8px';
+ badge.style.zIndex = '100';
+ // 确保目标元素有相对定位
+ if (window.getComputedStyle(targetElement).position === 'static') {
+ targetElement.style.position = 'relative';
+ }
+ // 添加一些内边距避免标识覆盖内容
+ const currentPadding = window.getComputedStyle(targetElement).paddingRight;
+ if (currentPadding && currentPadding !== '0px') {
+ const paddingValue = parseFloat(currentPadding);
+ targetElement.style.paddingRight = (paddingValue + 50) + 'px';
+ } else {
+ targetElement.style.paddingRight = '50px';
+ }
+ break;
+
+ case 'content-top-left':
+ badge.style.position = 'absolute';
+ badge.style.top = '8px';
+ badge.style.left = '8px';
+ badge.style.zIndex = '100';
+ if (window.getComputedStyle(targetElement).position === 'static') {
+ targetElement.style.position = 'relative';
+ }
+ const currentPaddingLeft = window.getComputedStyle(targetElement).paddingLeft;
+ if (currentPaddingLeft && currentPaddingLeft !== '0px') {
+ const paddingValue = parseFloat(currentPaddingLeft);
+ targetElement.style.paddingLeft = (paddingValue + 50) + 'px';
+ } else {
+ targetElement.style.paddingLeft = '50px';
+ }
+ break;
+
+ case 'comment-top-right':
+ badge.style.position = 'absolute';
+ badge.style.top = '10px';
+ badge.style.right = '10px';
+ badge.style.zIndex = '100';
+ if (window.getComputedStyle(commentElement).position === 'static') {
+ commentElement.style.position = 'relative';
+ }
+ break;
+
+ case 'after-author':
+ // 找到作者名元素,添加到后面
+ const authorElements = commentElement.querySelectorAll('.author, .author-name, .comment-author, .auhui .author-name');
+ if (authorElements.length > 0) {
+ targetElement = authorElements[0];
+ badge.style.marginLeft = '8px';
+ badge.style.display = 'inline-block';
+ badge.style.verticalAlign = 'middle';
+ } else {
+ badge.style.position = 'relative';
+ badge.style.display = 'inline-block';
+ badge.style.marginBottom = '5px';
+ }
+ break;
+ }
+
+ // 通用样式
+ badge.style.color = '#fff';
+ badge.style.padding = '3px 8px';
+ badge.style.borderRadius = '4px';
+ badge.style.fontSize = '12px';
+ badge.style.fontWeight = '600';
+ badge.style.lineHeight = '1.2';
+ badge.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
+ badge.style.transition = 'all 0.3s ease';
+ badge.style.textShadow = '1px 1px 1px rgba(0,0,0,0.2)';
+ badge.style.whiteSpace = 'nowrap';
+
+ // 添加悬停效果
+ badge.addEventListener('mouseenter', function() {
+ this.style.transform = 'scale(1.05)';
+ this.style.boxShadow = '0 4px 10px rgba(0,0,0,0.3)';
+ });
+
+ badge.addEventListener('mouseleave', function() {
+ this.style.transform = 'scale(1)';
+ this.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
+ });
+
+ // 添加到元素
+ targetElement.appendChild(badge);
+
+ // 添加淡入动画
+ badge.style.opacity = '0';
+ badge.style.transform = 'translateY(-5px)';
+ setTimeout(() => {
+ badge.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
+ badge.style.opacity = '1';
+ badge.style.transform = 'translateY(0)';
+ }, 10);
+ }
+})();
+
+JS;
+
+ // 输出JavaScript
+ echo $js;
+ }
+}
+
+ /**
+ * 处理锐评筛选
+ */
+ public static function handleHeartfeltFilter()
+ {
+ $filter = isset($_GET['heartfelt_filter']) ? $_GET['heartfelt_filter'] : 'all';
+
+ if ($filter == 'heartfelt') {
+ // 重定向到锐评专用页面
+ $options = Typecho_Widget::widget('Widget_Options');
+ $config = $options->plugin('RuiPing');
+
+ if ($config) {
+ $commentIds = self::getCommentIds($config->commentIds);
+ if (!empty($commentIds)) {
+ // 构建评论ID参数
+ $coidParams = '';
+ foreach ($commentIds as $id) {
+ $coidParams .= '&coid[]=' . $id;
+ }
+
+ $redirectUrl = Typecho_Common::url('manage-comments.php', $options->adminUrl) . '?' . $coidParams;
+ header('Location: ' . $redirectUrl);
+ exit;
+ } else {
+ // 如果没有锐评,显示提示信息
+ $options = Typecho_Widget::widget('Widget_Options');
+ $redirectUrl = Typecho_Common::url('manage-comments.php', $options->adminUrl);
+ header('Location: ' . $redirectUrl);
+ exit;
+ }
+ }
+ }
+
+ // 默认返回评论管理页面
+ $options = Typecho_Widget::widget('Widget_Options');
+ $redirectUrl = Typecho_Common::url('manage-comments.php', $options->adminUrl);
+ header('Location: ' . $redirectUrl);
+ exit;
+ }
+
+ /**
+ * 处理批量操作
+ */
+ public static function handleBatchAction()
+ {
+ // 验证用户权限
+ $user = Typecho_Widget::widget('Widget_User');
+ if (!$user->hasLogin() || !$user->pass('administrator', true)) {
+ self::jsonResponse(false, '权限不足');
+ }
+
+ $action = $_POST['heartfelt_batch_action'];
+ $commentIds = isset($_POST['comment_ids']) ? $_POST['comment_ids'] : array();
+
+ if (empty($commentIds)) {
+ self::jsonResponse(false, '请选择要操作的评论');
+ }
+
+ try {
+ $db = Typecho_Db::get();
+
+ // 获取当前插件配置
+ $options = Typecho_Widget::widget('Widget_Options');
+ $config = $options->plugin('RuiPing');
+
+ if (!$config) {
+ $config = new stdClass();
+ $config->commentIds = '';
+ }
+
+ $currentIds = self::getCommentIds($config->commentIds);
+
+ switch ($action) {
+ case 'add':
+ foreach ($commentIds as $commentId) {
+ $commentId = intval($commentId);
+ if ($commentId > 0 && !in_array($commentId, $currentIds)) {
+ $currentIds[] = $commentId;
+ }
+ }
+ $message = '已批量设置为锐评';
+ break;
+
+ case 'remove':
+ $currentIds = array_filter($currentIds, function($id) use ($commentIds) {
+ return !in_array($id, $commentIds);
+ });
+ $message = '已批量取消锐评';
+ break;
+
+ default:
+ self::jsonResponse(false, '未知操作');
+ return;
+ }
+
+ // 更新配置
+ $newValue = implode("\n", $currentIds);
+ $existingConfig = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'plugin:RuiPing'));
+
+ if ($existingConfig) {
+ $existingConfigArray = unserialize($existingConfig['value']);
+ $existingConfigArray['commentIds'] = $newValue;
+
+ $updateResult = $db->query($db->update('table.options')
+ ->rows(array('value' => serialize($existingConfigArray)))
+ ->where('name = ?', 'plugin:RuiPing'));
+ }
+
+ self::jsonResponse(true, $message, array(
+ 'action' => $action,
+ 'count' => count($commentIds)
+ ));
+
+ } catch (Exception $e) {
+ self::jsonResponse(false, '操作失败: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 处理备份相关的AJAX请求
+ */
+ public static function handleBackupAjax()
+ {
+ // 验证用户权限
+ $user = Typecho_Widget::widget('Widget_User');
+ if (!$user->hasLogin() || !$user->pass('administrator', true)) {
+ self::jsonResponse(false, '权限不足');
+ }
+
+ $action = $_POST['heartfelt_backup_action'];
+
+ switch ($action) {
+ case 'backup':
+ self::backupSettings();
+ break;
+ case 'restore':
+ self::restoreSettings();
+ break;
+ case 'upload':
+ self::uploadBackup();
+ break;
+ default:
+ self::jsonResponse(false, '未知操作');
+ }
+ }
+
+ /**
+ * 备份设置
+ */
+ private static function backupSettings()
+ {
+ try {
+ $db = Typecho_Db::get();
+
+ // 获取当前插件设置
+ $config = $db->fetchRow($db->select()->from('table.options')
+ ->where('name = ?', 'plugin:RuiPing'));
+
+ if (!$config) {
+ self::jsonResponse(false, '没有找到插件设置');
+ return;
+ }
+
+ // 创建备份
+ $backupData = array(
+ 'plugin_config' => $config['value'],
+ 'backup_time' => time(),
+ 'backup_version' => '2.5.1',
+ 'site_url' => Helper::options()->siteUrl
+ );
+
+ $backupContent = serialize($backupData);
+
+ // 使用更短的名字避免字段长度限制
+ $backupName = 'heartfelt_bak_' . date('Ymd_His');
+
+ // 保存备份到数据库
+ $existingBackup = $db->fetchRow($db->select()->from('table.options')
+ ->where('name = ?', $backupName));
+
+ if ($existingBackup) {
+ $db->query($db->update('table.options')
+ ->rows(array('value' => $backupContent))
+ ->where('name = ?', $backupName));
+ } else {
+ $db->query($db->insert('table.options')
+ ->rows(array(
+ 'name' => $backupName,
+ 'user' => '0',
+ 'value' => $backupContent
+ )));
+ }
+
+ // 清理旧的备份,只保留最近的5个
+ self::cleanOldBackups();
+
+ self::jsonResponse(true, '设置备份成功', array('backup_name' => $backupName));
+
+ } catch (Exception $e) {
+ self::jsonResponse(false, '备份失败: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 恢复设置
+ */
+ private static function restoreSettings()
+ {
+ try {
+ $db = Typecho_Db::get();
+
+ // 获取最新的备份
+ $backups = $db->fetchAll($db->select()->from('table.options')
+ ->where('name LIKE ?', 'heartfelt_bak_%')
+ ->order('name', Typecho_Db::SORT_DESC)
+ ->limit(1));
+
+ if (empty($backups)) {
+ self::jsonResponse(false, '没有找到备份文件');
+ return;
+ }
+
+ $backup = $backups[0];
+ $backupData = unserialize($backup['value']);
+
+ if (!$backupData || !isset($backupData['plugin_config'])) {
+ self::jsonResponse(false, '备份文件格式错误');
+ return;
+ }
+
+ // 恢复插件设置
+ $existingConfig = $db->fetchRow($db->select()->from('table.options')
+ ->where('name = ?', 'plugin:RuiPing'));
+
+ if ($existingConfig) {
+ $db->query($db->update('table.options')
+ ->rows(array('value' => $backupData['plugin_config']))
+ ->where('name = ?', 'plugin:RuiPing'));
+ } else {
+ $db->query($db->insert('table.options')
+ ->rows(array(
+ 'name' => 'plugin:RuiPing',
+ 'user' => '0',
+ 'value' => $backupData['plugin_config']
+ )));
+ }
+
+ self::jsonResponse(true, '设置恢复成功', array(
+ 'backup_time' => date('Y-m-d H:i:s', $backupData['backup_time'])
+ ));
+
+ } catch (Exception $e) {
+ self::jsonResponse(false, '恢复失败: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 下载备份
+ */
+ private static function downloadBackup()
+ {
+ try {
+ // 验证用户权限
+ $user = Typecho_Widget::widget('Widget_User');
+ if (!$user->hasLogin() || !$user->pass('administrator', true)) {
+ die('权限不足');
+ }
+
+ $db = Typecho_Db::get();
+
+ // 获取最新的备份
+ $backups = $db->fetchAll($db->select()->from('table.options')
+ ->where('name LIKE ?', 'heartfelt_bak_%')
+ ->order('name', Typecho_Db::SORT_DESC)
+ ->limit(1));
+
+ if (empty($backups)) {
+ die('没有找到备份文件');
+ }
+
+ $backup = $backups[0];
+ $backupData = unserialize($backup['value']);
+
+ if (!$backupData || !isset($backupData['backup_time'])) {
+ die('备份文件格式错误');
+ }
+
+ $filename = "heartfelt_backup_" . date('Y-m-d_H-i-s', $backupData['backup_time']) . ".dat";
+
+ header('Content-Type: application/octet-stream');
+ header('Content-Disposition: attachment; filename="' . $filename . '"');
+ header('Content-Length: ' . strlen($backup['value']));
+ header('Cache-Control: no-cache, must-revalidate');
+ header('Pragma: no-cache');
+ header('Expires: 0');
+
+ echo $backup['value'];
+ exit;
+
+ } catch (Exception $e) {
+ die('下载失败: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 上传备份
+ */
+ private static function uploadBackup()
+ {
+ try {
+ $backupContent = isset($_POST['backup_content']) ? $_POST['backup_content'] : '';
+
+ if (empty($backupContent)) {
+ self::jsonResponse(false, '备份内容为空');
+ return;
+ }
+
+ // 验证备份内容
+ $backupData = @unserialize($backupContent);
+ if (!$backupData || !isset($backupData['plugin_config'])) {
+ self::jsonResponse(false, '备份文件格式错误');
+ return;
+ }
+
+ $db = Typecho_Db::get();
+
+ // 保存备份
+ $backupName = 'heartfelt_upload_' . date('Ymd_His');
+ $db->query($db->insert('table.options')
+ ->rows(array(
+ 'name' => $backupName,
+ 'user' => '0',
+ 'value' => $backupContent
+ )));
+
+ // 恢复设置
+ $existingConfig = $db->fetchRow($db->select()->from('table.options')
+ ->where('name = ?', 'plugin:RuiPing'));
+
+ if ($existingConfig) {
+ $db->query($db->update('table.options')
+ ->rows(array('value' => $backupData['plugin_config']))
+ ->where('name = ?', 'plugin:RuiPing'));
+ } else {
+ $db->query($db->insert('table.options')
+ ->rows(array(
+ 'name' => 'plugin:RuiPing',
+ 'user' => '0',
+ 'value' => $backupData['plugin_config']
+ )));
+ }
+
+ self::jsonResponse(true, '备份上传并恢复成功');
+
+ } catch (Exception $e) {
+ self::jsonResponse(false, '上传失败: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 清理旧的备份
+ */
+ private static function cleanOldBackups()
+ {
+ try {
+ $db = Typecho_Db::get();
+
+ $backups = $db->fetchAll($db->select()->from('table.options')
+ ->where('name LIKE ?', 'heartfelt_bak_%')
+ ->order('name', Typecho_Db::SORT_DESC));
+
+ if (count($backups) > 5) {
+ for ($i = 5; $i < count($backups); $i++) {
+ $db->query($db->delete('table.options')
+ ->where('name = ?', $backups[$i]['name']));
+ }
+ }
+
+ } catch (Exception $e) {
+ // 忽略清理错误
+ }
+ }
+
+ /**
+ * 添加备份功能相关的JavaScript
+ */
+ private static function addBackupScript()
+ {
+ $ajaxUrl = Helper::options()->index . '/action/ruiping-backup';
+ ?>
+
+ hasLogin() || !$user->pass('administrator', true)) {
+ self::jsonResponse(false, '权限不足');
+ }
+
+ $db = Typecho_Db::get();
+ $commentId = isset($_POST['commentId']) ? intval($_POST['commentId']) : 0;
+ $action = isset($_POST['action']) ? $_POST['action'] : '';
+
+ if ($commentId <= 0 || !in_array($action, array('set', 'unset'))) {
+ self::jsonResponse(false, '参数错误');
+ }
+
+ // 验证评论是否存在
+ $comment = $db->fetchRow($db->select()->from('table.comments')->where('coid = ?', $commentId));
+ if (!$comment) {
+ self::jsonResponse(false, '评论不存在');
+ }
+
+ // 获取当前插件配置
+ $options = Typecho_Widget::widget('Widget_Options');
+ $config = $options->plugin('RuiPing');
+
+ if (!$config) {
+ $config = new stdClass();
+ $config->commentIds = '';
+ $config->limit = '10';
+ $config->showAuthor = '1';
+ $config->showDate = '1';
+ $config->showContent = '1';
+ $config->showSource = '1';
+ $config->showOriginal = '1';
+ $config->authorLink = '1';
+ $config->commentLink = '1';
+ $config->showPagination = '1';
+ $config->debugMode = '0';
+ }
+
+ $currentIds = self::getCommentIds($config->commentIds);
+
+ if ($action == 'set') {
+ if (!in_array($commentId, $currentIds)) {
+ $currentIds[] = $commentId;
+ }
+ } else {
+ $currentIds = array_filter($currentIds, function($id) use ($commentId) {
+ return $id != $commentId;
+ });
+ }
+
+ $newValue = implode("\n", $currentIds);
+ $existingConfig = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'plugin:RuiPing'));
+
+ if ($existingConfig) {
+ $existingConfigArray = unserialize($existingConfig['value']);
+ $existingConfigArray['commentIds'] = $newValue;
+
+ $updateResult = $db->query($db->update('table.options')
+ ->rows(array('value' => serialize($existingConfigArray)))
+ ->where('name = ?', 'plugin:RuiPing'));
+ } else {
+ $newConfig = array(
+ 'commentIds' => $newValue,
+ 'limit' => $config->limit ?: '10',
+ 'showAuthor' => $config->showAuthor ?: '1',
+ 'showDate' => $config->showDate ?: '1',
+ 'showContent' => $config->showContent ?: '1',
+ 'showSource' => $config->showSource ?: '1',
+ 'showOriginal' => $config->showOriginal ?: '1',
+ 'authorLink' => $config->authorLink ?: '1',
+ 'commentLink' => $config->commentLink ?: '1',
+ 'showPagination' => $config->showPagination ?: '1',
+ 'debugMode' => $config->debugMode ?: '0',
+ 'showFrontendBadge' => isset($config->showFrontendBadge) ? $config->showFrontendBadge : '1',
+ 'frontendBadgePosition' => isset($config->frontendBadgePosition) ? $config->frontendBadgePosition : 'content-top-right',
+ 'frontendBadgeText' => isset($config->frontendBadgeText) ? $config->frontendBadgeText : '❤ 锐评',
+ 'frontendBadgeColor' => isset($config->frontendBadgeColor) ? $config->frontendBadgeColor : '#d32f2f'
+ );
+
+ $updateResult = $db->query($db->insert('table.options')
+ ->rows(array(
+ 'name' => 'plugin:RuiPing',
+ 'user' => '0',
+ 'value' => serialize($newConfig)
+ )));
+ }
+
+ if ($updateResult) {
+ self::jsonResponse(true, $action == 'set' ? '已设置为锐评' : '已取消锐评', array(
+ 'commentId' => $commentId,
+ 'action' => $action,
+ 'isHeartfelt' => $action == 'set'
+ ));
+ } else {
+ self::jsonResponse(false, '更新配置失败');
+ }
+ }
+
+ /**
+ * 添加管理员头部
+ */
+ public static function addAdminHeader()
+ {
+ if (self::isCommentsManagementPage()) {
+ echo '';
+ }
+ }
+
+ /**
+ * 添加管理员底部
+ */
+ public static function addAdminFooter()
+ {
+ if (!self::isCommentsManagementPage()) {
+ return;
+ }
+
+ $options = Typecho_Widget::widget('Widget_Options');
+ $config = $options->plugin('RuiPing');
+
+ if (!$config) {
+ $currentIds = array();
+ } else {
+ $currentIds = self::getCommentIds($config->commentIds);
+ }
+
+ $currentIdsStr = implode(',', $currentIds);
+ $ajaxUrl = Typecho_Common::url('index.php', $options->siteUrl) . '?action=heartfelt-comments';
+ $batchAjaxUrl = Typecho_Common::url('index.php', $options->siteUrl);
+ $heartfeltUrl = Typecho_Common::url('index.php', $options->siteUrl) . '?heartfelt_filter=heartfelt';
+
+ echo <<
+// 锐评插件 - 后台功能
+var heartfeltMenuAdded = false;
+
+function initRuiPing() {
+ var buttonCount = 0;
+ var badgeCount = 0;
+
+ // 添加锐评菜单到现有筛选菜单(只添加一次)
+ if (!heartfeltMenuAdded) {
+ addRuiPingMenu();
+ heartfeltMenuAdded = true;
+ }
+
+ // 添加锐评操作到默认批量操作菜单
+ addRuiPingBatchActions();
+
+ jQuery('tr[id^="comment-"]').each(function() {
+ var \$tr = jQuery(this);
+ var trId = \$tr.attr('id');
+ var commentId = trId.replace('comment-', '');
+
+ if (commentId && !isNaN(commentId)) {
+ var isHeartfelt = [{$currentIdsStr}].indexOf(parseInt(commentId)) > -1;
+
+ var operationDiv = \$tr.find('.operate');
+ if (operationDiv.length === 0) {
+ operationDiv = \$tr.find('.comment-action');
+ }
+ if (operationDiv.length === 0) {
+ operationDiv = \$tr.find('td:last-child');
+ }
+
+ if (operationDiv.length > 0) {
+ if (!operationDiv.find('.heartfelt-btn').length) {
+ var heartfeltBtn = isHeartfelt ?
+ '❤ 取消锐评' :
+ '🤍 设为锐评';
+
+ operationDiv.append(heartfeltBtn);
+ buttonCount++;
+
+ bindSingleButton(operationDiv.find('.heartfelt-btn').last());
+ }
+ }
+
+ if (isHeartfelt) {
+ var firstTd = \$tr.find('td:first-child');
+ if (firstTd.length > 0 && !firstTd.find('.heartfelt-badge').length) {
+ firstTd.css('position', 'relative');
+ firstTd.append('❤ 锐评');
+ badgeCount++;
+ }
+ }
+ }
+ });
+}
+
+// 添加锐评菜单到现有筛选菜单区域
+function addRuiPingMenu() {
+ // 查找现有的筛选菜单 - 左边的菜单
+ var \$typechoTabs = jQuery('.typecho-option-tabs').first();
+ if (\$typechoTabs.length === 0) {
+ console.log('未找到筛选菜单容器');
+ return;
+ }
+
+ // 检查是否已经添加过锐评菜单
+ if (\$typechoTabs.find('.ruiping-menu-trigger').length > 0) {
+ return;
+ }
+
+ // 创建锐评菜单项 - 添加到垃圾菜单后面
+ var ruiPingMenuItem = jQuery('');
+
+ // 找到垃圾菜单并插入到它后面
+ var \$spamMenu = \$typechoTabs.find('a[href*="spam"]').parent();
+ if (\$spamMenu.length > 0) {
+ \$spamMenu.after(ruiPingMenuItem);
+ } else {
+ // 如果找不到垃圾菜单,添加到末尾
+ \$typechoTabs.append(ruiPingMenuItem);
+ }
+
+ // 检查当前是否在锐评页面
+ var currentUrl = window.location.href;
+ if (currentUrl.indexOf('heartfelt_filter=heartfelt') > -1 || currentUrl.indexOf('coid[]') > -1) {
+ ruiPingMenuItem.addClass('current');
+ }
+}
+
+// 添加锐评操作到默认批量操作菜单
+function addRuiPingBatchActions() {
+ var \$operateDiv = jQuery('.operate');
+ if (\$operateDiv.length === 0) {
+ console.log('未找到操作容器');
+ return;
+ }
+
+ var \$dropdownMenu = \$operateDiv.find('.dropdown-menu');
+ if (\$dropdownMenu.length === 0) {
+ console.log('未找到下拉菜单');
+ return;
+ }
+
+ // 检查是否已经添加过锐评操作
+ if (\$dropdownMenu.find('.heartfelt-batch-action').length > 0) {
+ return;
+ }
+
+ // 在删除操作前添加锐评操作
+ var heartfeltActions = '';
+ heartfeltActions += '设为锐评';
+ heartfeltActions += '取消锐评';
+
+ \$dropdownMenu.find('a[href*="delete"]').parent().before(heartfeltActions);
+}
+
+// 批量操作
+function heartfeltBatchAction(action) {
+ var selectedComments = [];
+ jQuery('input[name="coid[]"]:checked').each(function() {
+ var commentId = jQuery(this).val();
+ if (commentId && !isNaN(commentId)) {
+ selectedComments.push(parseInt(commentId));
+ }
+ });
+
+ if (selectedComments.length === 0) {
+ alert('请先选择要操作的评论');
+ return;
+ }
+
+ var confirmMessage = '';
+ switch (action) {
+ case 'add':
+ confirmMessage = '确定要将选中的 ' + selectedComments.length + ' 条评论设为锐评吗?';
+ break;
+ case 'remove':
+ confirmMessage = '确定要取消选中的 ' + selectedComments.length + ' 条评论的锐评状态吗?';
+ break;
+ }
+
+ if (!confirm(confirmMessage)) {
+ return;
+ }
+
+ jQuery.ajax({
+ url: '{$batchAjaxUrl}',
+ type: 'POST',
+ dataType: 'json',
+ data: {
+ heartfelt_batch_action: action,
+ comment_ids: selectedComments
+ },
+ success: function(response) {
+ if (response && response.success) {
+ alert(response.message || '操作成功');
+ window.location.reload();
+ } else {
+ var message = response && response.message ? response.message : '操作失败';
+ alert('操作失败: ' + message);
+ }
+ },
+ error: function(xhr, status, error) {
+ var errorMsg = '网络错误,请重试。状态: ' + status;
+ if (xhr.status === 404) {
+ errorMsg = '请求的接口不存在 (404),请检查插件路由配置';
+ } else if (xhr.status === 403) {
+ errorMsg = '权限不足 (403),请重新登录';
+ } else if (xhr.status === 500) {
+ errorMsg = '服务器内部错误 (500)';
+ }
+ alert(errorMsg);
+ }
+ });
+}
+
+function bindSingleButton(btn) {
+ btn.off('click').on('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ var \$btn = jQuery(this);
+ var commentId = \$btn.data('comment');
+ var action = \$btn.data('action');
+
+ if (!commentId) {
+ alert('错误: 未找到评论ID');
+ return;
+ }
+
+ var originalHtml = \$btn.html();
+ \$btn.html('处理中...').prop('disabled', true).css('opacity', '0.6');
+
+ jQuery.ajax({
+ url: '{$ajaxUrl}',
+ type: 'POST',
+ dataType: 'json',
+ data: {
+ commentId: commentId,
+ action: action
+ },
+ success: function(response) {
+ if (response && response.success) {
+ alert(response.message || '操作成功');
+ window.location.reload();
+ } else {
+ var message = response && response.message ? response.message : '操作失败';
+ alert('操作失败: ' + message);
+ \$btn.prop('disabled', false).css('opacity', '1').html(originalHtml);
+ }
+ },
+ error: function(xhr, status, error) {
+ var errorMsg = '网络错误,请重试。状态: ' + status;
+ if (xhr.status === 404) {
+ errorMsg = '请求的接口不存在 (404),请检查插件路由配置';
+ } else if (xhr.status === 403) {
+ errorMsg = '权限不足 (403),请重新登录';
+ } else if (xhr.status === 500) {
+ errorMsg = '服务器内部错误 (500)';
+ }
+ alert(errorMsg);
+ \$btn.prop('disabled', false).css('opacity', '1').html(originalHtml);
+ },
+ timeout: 10000
+ });
+ });
+}
+
+function heartfeltMainInit() {
+ if (!window.jQuery) {
+ var checkJQuery = setInterval(function() {
+ if (window.jQuery) {
+ clearInterval(checkJQuery);
+ heartfeltMainInit();
+ }
+ }, 100);
+ return;
+ }
+
+ jQuery(document).ready(function($) {
+ initRuiPing();
+
+ // 只初始化一次,避免重复
+ setTimeout(function() {
+ if (!heartfeltMenuAdded) {
+ initRuiPing();
+ }
+ }, 500);
+ });
+}
+
+heartfeltMainInit();
+
+
+HTML;
+ }
+
+ /**
+ * 检查是否为评论管理页面
+ */
+ private static function isCommentsManagementPage()
+ {
+ $pathInfo = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
+ $requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
+
+ if (strpos($pathInfo, 'comments') !== false ||
+ strpos($requestUri, 'comments') !== false ||
+ strpos($requestUri, 'comment') !== false ||
+ (isset($_GET['__typecho_page']) && $_GET['__typecho_page'] === 'comments')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 插件实现方法
+ */
+ public static function render()
+ {
+ $options = Typecho_Widget::widget('Widget_Options');
+ $config = $options->plugin('RuiPing');
+
+ if (!$config) {
+ return '插件配置未找到,请重新保存插件设置
';
+ }
+
+ $currentPage = isset($_GET['heartfelt_page']) ? max(1, intval($_GET['heartfelt_page'])) : 1;
+
+ if ($config->debugMode && empty($config->commentIds)) {
+ return '调试:插件设置中未配置评论ID
';
+ }
+
+ $commentIds = self::getCommentIds($config->commentIds);
+ if (empty($commentIds)) {
+ if ($config->debugMode) {
+ return '调试:解析后的评论ID为空,请检查ID格式(每行一个数字ID)
';
+ }
+ return '暂无锐评
';
+ }
+
+ $limit = intval($config->limit) ?: 10;
+
+ return self::getRuiPing($commentIds, $limit, $currentPage, $config);
+ }
+
+ /**
+ * JSON响应
+ */
+ private static function jsonResponse($success, $message = '', $data = array())
+ {
+ header('Content-Type: application/json; charset=utf-8');
+ echo json_encode(array(
+ 'success' => $success,
+ 'message' => $message,
+ 'data' => $data
+ ));
+ exit;
+ }
+
+ /**
+ * 获取评论ID数组
+ */
+ private static function getCommentIds($commentIdsText)
+ {
+ if (empty($commentIdsText)) {
+ return array();
+ }
+
+ $ids = explode("\n", $commentIdsText);
+ $ids = array_map('trim', $ids);
+ $ids = array_filter($ids, function($id) {
+ return is_numeric($id) && $id > 0;
+ });
+
+ return $ids;
+ }
+
+ /**
+ * 获取锐评数据(带分页)- 修复排序问题
+ */
+ private static function getRuiPing($commentIds, $limit, $currentPage, $config)
+ {
+ $db = Typecho_Db::get();
+
+ $offset = ($currentPage - 1) * $limit;
+ $total = count($commentIds);
+ $totalPages = ceil($total / $limit);
+
+ // 修复:先获取所有评论数据,按时间排序,然后再分页
+ $select = $db->select()
+ ->from('table.comments')
+ ->where('coid IN ?', $commentIds)
+ ->order('created', Typecho_Db::SORT_DESC); // 先全局按时间排序
+
+ $allComments = $db->fetchAll($select);
+
+ // 如果获取的评论数量与ID数量不一致,重新匹配
+ $allCommentsMap = array();
+ foreach ($allComments as $comment) {
+ $allCommentsMap[$comment['coid']] = $comment;
+ }
+
+ // 确保所有ID都有对应的评论数据
+ $validComments = array();
+ foreach ($commentIds as $commentId) {
+ if (isset($allCommentsMap[$commentId])) {
+ $validComments[$commentId] = $allCommentsMap[$commentId];
+ }
+ }
+
+ // 按时间排序(最新的在前面)
+ uasort($validComments, function($a, $b) {
+ return $b['created'] - $a['created'];
+ });
+
+ // 分页:取当前页的数据
+ $currentPageData = array_slice($validComments, $offset, $limit, true);
+
+ $debugInfo = '';
+ if ($config->debugMode) {
+ $debugInfo = '';
+ $debugInfo .= '
调试信息:
';
+ $debugInfo .= '
总评论数: ' . $total . '
';
+ $debugInfo .= '
每页数量: ' . $limit . '
';
+ $debugInfo .= '
总页数: ' . $totalPages . '
';
+ $debugInfo .= '
当前页: ' . $currentPage . '
';
+ $debugInfo .= '
当前页评论数量: ' . count($currentPageData) . '
';
+ $debugInfo .= '
排序方式: 按时间倒序(最新的在最前面)
';
+ $debugInfo .= '
';
+ }
+
+ $html = $debugInfo;
+ $html .= self::generateHtml($currentPageData, $config);
+
+ if ($config->showPagination && $totalPages > 1) {
+ $html .= self::generatePagination($currentPage, $totalPages);
+ }
+
+ return $html;
+ }
+
+ /**
+ * 生成分页HTML
+ */
+ private static function generatePagination($currentPage, $totalPages)
+ {
+ $html = '';
+
+ return $html;
+ }
+
+ /**
+ * 构建分页查询参数
+ */
+ private static function buildPageQuery($page)
+ {
+ $params = $_GET;
+ $params['heartfelt_page'] = $page;
+
+ return http_build_query($params);
+ }
+
+ /**
+ * 生成HTML输出
+ */
+ private static function generateHtml($comments, $config)
+ {
+ if (empty($comments)) {
+ return '暂无锐评
';
+ }
+
+ $html = '';
+
+ return $html;
+ }
+
+ /**
+ * 获取文章永久链接
+ */
+ private static function getPostPermalink($cid)
+ {
+ try {
+ $db = Typecho_Db::get();
+ $post = $db->fetchRow($db->select('slug', 'type')
+ ->from('table.contents')
+ ->where('cid = ?', $cid)
+ ->where('type = ?', 'post')
+ ->limit(1));
+
+ if ($post) {
+ $options = Typecho_Widget::widget('Widget_Options');
+ $permalink = Typecho_Router::url('post', $post, $options->index);
+ return $permalink;
+ }
+ } catch (Exception $e) {
+ return '';
+ }
+ return '';
+ }
+
+ /**
+ * 获取评论链接
+ */
+ private static function getCommentLink($comment, $config)
+ {
+ $postUrl = self::getPostPermalink($comment['cid']);
+ if (!$postUrl) {
+ return '';
+ }
+
+ $commentUrl = $postUrl . '#comment-' . $comment['coid'];
+
+ return $commentUrl;
+ }
+
+ /**
+ * 获取作者链接
+ */
+ private static function getAuthorLink($comment, $config)
+ {
+ if (!empty($comment['url'])) {
+ $authorUrl = $comment['url'];
+ if (!preg_match('/^https?:\/\//', $authorUrl)) {
+ $authorUrl = 'http://' . $authorUrl;
+ }
+
+ return array('url' => $authorUrl, 'target' => $config->authorLink ? '_blank' : '');
+ }
+
+ return array('url' => '', 'target' => '');
+ }
+
+ /**
+ * 生成单个评论项HTML
+ */
+ private static function generateCommentItem($comment, $config)
+ {
+ $html = '';
+
+ return $html;
+ }
+
+ /**
+ * 头部输出
+ */
+ public static function header()
+ {
+ $css = self::getCss();
+ $options = Typecho_Widget::widget('Widget_Options');
+ $config = $options->plugin('RuiPing');
+
+ // 添加前端标识的CSS
+ if ($config && (!isset($config->showFrontendBadge) || $config->showFrontendBadge == '1')) {
+ $badgeColor = isset($config->frontendBadgeColor) ? $config->frontendBadgeColor : '#d32f2f';
+
+ $frontendCss = <<
+/* 竖排锐评标识样式 */
+.heartfelt-frontend-badge {
+ background-color: #d32f2f;
+ color: #fff;
+ padding: 3px 8px;
+ border-radius: 20px;
+ font-size: 12px;
+ font-weight: 600;
+ line-height: 1.2;
+ z-index: 100;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
+ transition: all 0.3s ease;
+ cursor: default;
+ text-shadow: 1px 1px 1px rgba(0,0,0,0.2);
+ white-space: nowrap;
+ pointer-events: auto;
+
+ /* 竖排文字 */
+ writing-mode: vertical-rl;
+ text-orientation: mixed;
+ height: auto;
+ padding: 8px 3px !important; /* 调整padding,竖排时宽高互换 */
+ letter-spacing: 1px;
+ margin-top:15px;
+}
+
+.heartfelt-frontend-badge:hover {
+ box-shadow: 0 4px 10px rgba(0,0,0,0.3);
+}
+
+/* 针对你的评论模板的特殊处理 */
+.comment-content .heartfelt-frontend-badge,
+.cm .heartfelt-frontend-badge {
+ position: absolute !important;
+ top: 25px !important;
+ right: 15px !important;
+}
+
+/* 确保内容区域有相对定位 */
+.comment-content,
+.cm {
+ position: relative !important;
+}
+
+/* 为内容区域添加padding,避免标识覆盖文字 - 调整为竖排时的宽度 */
+.comment-content[style*="padding-right"],
+.cm[style*="padding-right"] {
+ padding-right: 30px !important; /* 竖排标识宽度更小 */
+}
+
+.comment-content[style*="padding-left"],
+.cm[style*="padding-left"] {
+ padding-left: 30px !important; /* 竖排标识宽度更小 */
+}
+
+/* 暗色模式适配 */
+@media (prefers-color-scheme: dark) {
+ .heartfelt-frontend-badge {
+ box-shadow: 0 2px 5px rgba(0,0,0,0.4);
+ }
+}
+
+/* 修复嵌套评论的标识位置 */
+.comment-child .heartfelt-frontend-badge {
+ font-size: 11px;
+ padding: 6px 2px !important; /* 竖排调整 */
+ top: 5px !important;
+ right: 5px !important;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+ .heartfelt-frontend-badge {
+ font-size: 11px;
+ padding: 6px 2px !important; /* 竖排调整 */
+ }
+
+ /* 移动端调整位置,避免太靠边 */
+ .comment-content .heartfelt-frontend-badge,
+ .cm .heartfelt-frontend-badge {
+ top: 5px !important;
+ right: 5px !important;
+ }
+
+ /* 调整内容区域的padding */
+ .comment-content[style*="padding-right"],
+ .cm[style*="padding-right"] {
+ padding-right: 25px !important; /* 移动端竖排标识更窄 */
+ }
+
+ .comment-content[style*="padding-left"],
+ .cm[style*="padding-left"] {
+ padding-left: 25px !important; /* 移动端竖排标识更窄 */
+ }
+}
+
+/* 动画效果 */
+@keyframes heartfelt-badge-appear {
+ 0% {
+ opacity: 0;
+ transform: translateY(-10px) scale(0.8);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+.heartfelt-frontend-badge {
+ animation: heartfelt-badge-appear 0.5s ease-out;
+}
+
+/* 针对其他位置的竖排调整 */
+.heartfelt-frontend-badge[style*="content-top-left"] {
+ writing-mode: vertical-rl;
+ text-orientation: mixed;
+ padding: 8px 3px !important;
+}
+
+.heartfelt-frontend-badge[style*="comment-top-right"],
+.heartfelt-frontend-badge[style*="comment-top-left"] {
+ writing-mode: vertical-rl;
+ text-orientation: mixed;
+ padding: 8px 3px !important;
+}
+
+/* 作者名后的标识保持横排 */
+.heartfelt-frontend-badge[style*="display: inline-block"] {
+ writing-mode: horizontal-tb !important;
+ padding: 3px 8px !important;
+ vertical-align: middle;
+}
+
+CSS;
+
+ $css .= $frontendCss;
+ }
+
+ echo $css;
+ }
+
+ /**
+ * 底部输出
+ */
+ public static function footer()
+ {
+ // 可以在这里添加其他页脚内容
+ }
+
+ /**
+ * 获取CSS样式
+ */
+ private static function getCss()
+ {
+ return <<
+/* 锐评插件核心样式 */
+.heartfelt-comments {
+ background: #f8f9fa;
+ border-radius: 6px;
+ margin: 20px 0;
+}
+
+.heartfelt-title {
+ color: #d32f2f;
+ margin-bottom: 12px;
+ font-size: 1.1em;
+ text-align:center;
+ padding: 30px;
+ border-radius:10px;
+}
+
+.heartfelt-comments-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.heartfelt-comment-item {
+ background:#eee;
+ padding: 16px;
+ border-radius: 10px;
+ position: relative;
+ border: 1px solid rgba(0, 0, 0, 0);
+ transition: all 0.3s ease;
+}
+
+.heartfelt-comment-item:hover {
+ border:1px solid #f15a22;
+}
+
+.heartfelt-comment-content {
+ color: #333;
+ line-height: 1.6;
+ margin-bottom: 12px;
+ font-size: 15px;
+}
+
+.heartfelt-comment-link {
+ color: #333;
+ text-decoration: none;
+ display: block;
+ padding: 8px;
+ margin: -8px;
+ border-radius: 4px;
+ transition: background-color 0.15s ease;
+}
+
+.heartfelt-comment-link:hover {
+ background-color: #f8f9fa;
+ color: #d32f2f;
+}
+
+.heartfelt-comment-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ font-size: 0.85em;
+ color: #666;
+ padding-top: 12px;
+ border-top: 1px solid #faf8f1;
+}
+.dark .heartfelt-comment-meta{border-top: 1px solid hsl(0 0% 15%);}
+.heartfelt-comment-author,
+.heartfelt-comment-date,
+.heartfelt-comment-source {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.heartfelt-author-link,
+.heartfelt-source-link,
+.heartfelt-origin-link {
+ color: #666;
+ text-decoration: none;
+ transition: color 0.15s ease;
+}
+
+.heartfelt-author-link:hover,
+.heartfelt-source-link:hover,
+.heartfelt-origin-link:hover {
+ color: #d32f2f;
+ text-decoration: none;
+}
+
+.heartfelt-origin-link {
+ padding: 2px 6px;
+ border-radius: 3px;
+ background: #f0f0f0;
+ margin-left: 8px;
+ font-size: 0.8em;
+ border: 1px solid #ddd;
+}
+
+.heartfelt-origin-link:hover {
+ background: #f8f0f5;
+ border-color: #d32f2f;
+}
+
+.heartfelt-pagination {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+ margin-top: 24px;
+}
+
+.heartfelt-page-link,
+.heartfelt-page-current {
+ padding: 6px 12px;
+ border-radius: 50px;
+ text-decoration: none;
+ transition: all 0.15s ease;
+ font-size: 0.9em;
+ min-width: 36px;
+ text-align: center;
+}
+.dark .heartfelt-page-link{
+ background-color:rgb(10 12 25 / 1)!important;
+}
+.heartfelt-page-link {
+ color: #495057;
+ background: white;
+ border: 1px solid #f15a22;
+}
+
+.heartfelt-page-link:hover {
+ color: #d32f2f;
+ background: #f8f0f5;
+ border-color: #d32f2f;
+ text-decoration: none;
+}
+
+.heartfelt-page-current {
+ color: white;
+ background: #d32f2f;
+ border: 1px solid #d32f2f;
+ font-weight: 600;
+}
+
+.heartfelt-debug {
+ background: #fff3cd;
+ border: 1px solid #ffeaa7;
+ padding: 12px;
+ border-radius: 4px;
+ color: #856404;
+ font-size: 0.9em;
+ margin-bottom: 12px;
+}
+
+.heartfelt-debug p {
+ margin: 4px 0;
+}
+
+.heartfelt-debug strong {
+ color: #d32f2f;
+}
+
+.heartfelt-comment-link::after {
+ content: " →";
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ font-size: 0.85em;
+ margin-left: 4px;
+}
+
+.heartfelt-comment-link:hover::after {
+ opacity: 1;
+}
+
+@media (max-width: 768px) {
+ .heartfelt-comments {
+ padding: 12px;
+ margin: 12px 0;
+ }
+
+ .heartfelt-comment-meta {
+ flex-direction: column;
+ gap: 6px;
+ }
+
+ .heartfelt-comment-content {
+ font-size: 0.9em;
+ }
+
+ .heartfelt-comment-link {
+ padding: 6px;
+ margin: -6px;
+ }
+
+ .heartfelt-pagination {
+ flex-wrap: wrap;
+ gap: 4px;
+ }
+
+ .heartfelt-page-link,
+ .heartfelt-page-current {
+ padding: 4px 8px;
+ font-size: 0.8em;
+ min-width: 30px;
+ }
+}
+
+CSS;
+ }
+}
\ No newline at end of file