From 6c7cef8d7e3093cb9c2d6678eae4974680356e1a Mon Sep 17 00:00:00 2001 From: XIGE <710062962@qq.com> Date: Mon, 23 Feb 2026 17:26:10 +0800 Subject: [PATCH] 1.0 --- Plugin.php | 943 +++++++++++++++++++++++++++++++++++++++++++++++ manage-panel.php | 603 ++++++++++++++++++++++++++++++ 2 files changed, 1546 insertions(+) create mode 100644 Plugin.php create mode 100644 manage-panel.php diff --git a/Plugin.php b/Plugin.php new file mode 100644 index 0000000..05efc02 --- /dev/null +++ b/Plugin.php @@ -0,0 +1,943 @@ +write = array(__CLASS__, 'saveEditHistory'); + Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array(__CLASS__, 'parseContent'); + Typecho_Plugin::factory('Widget_Archive')->header = array(__CLASS__, 'outputHeader'); + + // 🔥 新增:文章删除时同时删除编辑历史 + Typecho_Plugin::factory('Widget_Contents_Post_Edit')->delete = array(__CLASS__, 'deletePostHistory'); + + // 添加后台管理菜单 - 使用正确的Helper方法 + Helper::addPanel(1, 'EditHistory/manage-panel.php', '历史管理', '查看编辑历史', 'administrator'); + + return '编录插件已激活'; + } + + public static function deactivate() + { + // 使用Helper::removePanel()方法正确移除菜单 + try { + Helper::removePanel(1, 'EditHistory/manage-panel.php'); + } catch (Exception $e) { + // 如果Helper方法失败,尝试直接删除数据库记录 + try { + $db = Typecho_Db::get(); + $db->query($db->delete('table.options') + ->where('name = ?', 'panel:EditHistory/manage-panel.php')); + } catch (Exception $e2) { + // 静默失败 + } + } + + return '编辑记录插件已禁用'; + } + + public static function config(Typecho_Widget_Helper_Form $form) + { + $position = new Typecho_Widget_Helper_Form_Element_Radio( + 'position', + array('auto' => '自动在文章末尾显示', 'manual' => '手动调用', 'both' => '两者都显示'), + 'auto', + '显示位置' + ); + $form->addInput($position); + + $maxRecords = new Typecho_Widget_Helper_Form_Element_Text('maxRecords', NULL, '10', '最大显示记录数'); + $maxRecords->input->setAttribute('type', 'number'); + $form->addInput($maxRecords); + + $showPublishDays = new Typecho_Widget_Helper_Form_Element_Radio( + 'showPublishDays', + array('1' => '显示', '0' => '不显示'), + '1', + '显示发布天数' + ); + $form->addInput($showPublishDays); + + $timeFormat = new Typecho_Widget_Helper_Form_Element_Select( + 'timeFormat', + array('detail' => '详细格式', 'simple' => '简洁格式', 'relative' => '相对时间'), + 'detail', + '时间显示格式' + ); + $form->addInput($timeFormat); + + $showSummary = new Typecho_Widget_Helper_Form_Element_Radio( + 'showSummary', + array('1' => '显示', '0' => '不显示'), + '1', + '显示编辑摘要' + ); + $form->addInput($showSummary); + + $summaryLength = new Typecho_Widget_Helper_Form_Element_Text('summaryLength', NULL, '100', '摘要长度'); + $form->addInput($summaryLength); + + $requireEditSummary = new Typecho_Widget_Helper_Form_Element_Radio( + 'requireEditSummary', + array('1' => '启用(必填)', '0' => '禁用(可选)'), + '0', + '编辑摘要必填' + ); + $form->addInput($requireEditSummary); + + $editSummaryPlaceholder = new Typecho_Widget_Helper_Form_Element_Text( + 'editSummaryPlaceholder', + NULL, + '请简要描述本次编辑的内容(例如:修正错别字、更新数据、补充说明等)', + '编辑摘要提示文字' + ); + $form->addInput($editSummaryPlaceholder); + + $defaultCollapsed = new Typecho_Widget_Helper_Form_Element_Radio( + 'defaultCollapsed', + array('1' => '默认收起', '0' => '默认展开'), + '1', + '默认显示状态' + ); + $form->addInput($defaultCollapsed); + } + + public static function personalConfig(Typecho_Widget_Helper_Form $form) {} + + private static function createTable() + { + try { + $db = Typecho_Db::get(); + $prefix = $db->getPrefix(); + $sql = "CREATE TABLE IF NOT EXISTS `{$prefix}edit_history` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `cid` int(10) unsigned NOT NULL, + `editor` int(10) unsigned DEFAULT NULL, + `edit_time` int(10) unsigned NOT NULL, + `edit_content` text, + `edit_type` varchar(20) DEFAULT 'update', + PRIMARY KEY (`id`), + KEY `idx_cid` (`cid`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"; + $db->query($sql); + } catch (Exception $e) { + // 静默失败 + } + } + + /** + * 🔥 新增:删除文章时同时删除编辑历史 + */ + public static function deletePostHistory($cid, $widget) + { + try { + $db = Typecho_Db::get(); + // 删除该文章的所有编辑历史 + $db->query($db->delete('table.' . self::$tableName) + ->where('cid = ?', $cid)); + } catch (Exception $e) { + // 静默失败 + } + + return $cid; + } + + /** + * 保存编辑记录 + */ + public static function saveEditHistory($contents, $widget) + { + try { + $db = Typecho_Db::get(); + + // 只在有文章ID时保存 + if ($widget->cid) { + // 🔥 关键修复:检查是否是真正的发布操作 + // 获取当前文章状态 + $currentStatus = $widget->status; + + // 🔥 重要:草稿状态下的任何操作都不应该记录编辑历史 + if ($currentStatus === 'draft' || $currentStatus === 'waiting' || $currentStatus === 'private') { + // 草稿、待审核、私密文章状态下的任何保存都不记录 + return $contents; + } + + // 🔥 重要:只有已发布的文章才可能记录编辑历史 + if ($currentStatus !== 'publish') { + return $contents; + } + + // 🔥 现在只处理已发布文章的编辑 + // 检查是否是真正的用户编辑操作 + $isRealEdit = false; + + // 方法1:检查是否有编辑摘要(用户主动填写了编辑说明) + if (isset($_POST['fields']['editSummary']) && !empty(trim($_POST['fields']['editSummary']))) { + $isRealEdit = true; + } + + // 方法2:检查是否有明确的发布操作 + if (isset($_POST['do']) && $_POST['do'] === 'publish') { + // 已经是发布状态,检查是否有内容变化 + if (!$isRealEdit) { + // 检查文章内容是否有变化(简单判断) + $oldContent = $widget->text; + $newContent = isset($_POST['text']) ? $_POST['text'] : ''; + if ($oldContent != $newContent) { + $isRealEdit = true; + } + } + } + + // 方法3:检查自动保存标记 - 排除自动保存 + $isAutoSave = false; + if (isset($_POST['_']) || isset($_POST['autoSave']) || isset($_POST['autosave'])) { + $isAutoSave = true; + } + + // 方法4:检查时间间隔 - 短时间内重复提交可能是自动保存 + static $lastSaveTime = 0; + $currentTime = time(); + if ($currentTime - $lastSaveTime < 10) { // 10秒内重复保存 + $isAutoSave = true; + } + + // 方法5:检查是否是保存草稿或预览 + if (isset($_POST['do']) && ($_POST['do'] === 'save' || $_POST['do'] === 'preview')) { + return $contents; // 直接返回,不记录 + } + + // 🔥 最终判断:只有真正的编辑操作才记录 + if (!$isRealEdit || $isAutoSave) { + return $contents; + } + + // 更新最后保存时间 + $lastSaveTime = $currentTime; + + $user = Typecho_Widget::widget('Widget_User'); + $time = time(); + + // 获取编辑摘要 + $editSummary = ''; + + // 从POST数据中获取编辑摘要 + if (isset($_POST['fields']['editSummary'])) { + $editSummary = trim($_POST['fields']['editSummary']); + } + + // 准备编辑内容摘要 + $summaryContent = ''; + + if (!empty($editSummary)) { + $summaryContent = $editSummary; + } else { + $summaryContent = '未填写编辑说明'; + } + + // 保存到数据库 + $data = array( + 'cid' => $widget->cid, + 'editor' => $user->uid ? $user->uid : 1, + 'edit_time' => $time, + 'edit_content' => $summaryContent, + 'edit_type' => 'update' + ); + + // 执行数据库插入 + try { + $result = $db->query($db->insert('table.' . self::$tableName)->rows($data)); + } catch (Exception $e) { + // 静默失败 + } + } + } catch (Exception $e) { + // 静默失败 + } + + return $contents; + } + + /** + * 提供给主题的字段添加方法 + */ + public static function addFieldToLayout($layout) + { + $options = Typecho_Widget::widget('Widget_Options'); + $pluginOptions = $options->plugin('EditHistory'); + + $placeholder = isset($pluginOptions->editSummaryPlaceholder) ? + $pluginOptions->editSummaryPlaceholder : + '请简要描述本次编辑的内容(例如:修正错别字、更新数据、补充说明等)'; + + $required = isset($pluginOptions->requireEditSummary) && $pluginOptions->requireEditSummary == '1'; + + $editSummary = new Typecho_Widget_Helper_Form_Element_Textarea( + 'editSummary', + NULL, + NULL, + '编辑说明', + '此备注会显示在文章的编辑记录中,修改一次填写一次,自动记录修改' + ); + + $editSummary->input->setAttribute('rows', '4'); + $editSummary->input->setAttribute('placeholder', $placeholder); + $editSummary->input->setAttribute('style', 'width: 100%; padding: 10px; border-radius: 4px; resize: vertical;'); + + // 使用 fields[] 数组作为name,这是Typecho自定义字段的标准方式 + $editSummary->input->setAttribute('name', 'fields[editSummary]'); + + $layout->addItem($editSummary); + } + + private static function getContentSummary($content, $length = 100) + { + if (empty($content)) return ''; + $content = strip_tags($content); + $content = trim($content); + return Typecho_Common::subStr($content, 0, $length, '...'); + } + + /** + * 输出CSS样式和JavaScript + */ + public static function outputHeader() + { + echo ''; + } + + public static function parseContent($content, $widget, $lastResult) + { + $content = empty($lastResult) ? $content : $lastResult; + if ($widget->is('single')) { + $options = Typecho_Widget::widget('Widget_Options'); + $pluginOptions = $options->plugin('EditHistory'); + $position = isset($pluginOptions->position) ? $pluginOptions->position : 'auto'; + + // 检查是否有编辑记录 + $hasEditHistory = self::hasEditHistory($widget->cid); + + // 只要有编辑记录就显示 + if ($hasEditHistory) { + if ($position === 'auto' || $position === 'both') { + $editHistory = self::renderEditHistory($widget->cid); + if ($editHistory) { + $content .= $editHistory; + } + } + } + } + return $content; + } + + /** + * 判断文章是否有编辑记录 + */ + private static function hasEditHistory($cid) + { + try { + $db = Typecho_Db::get(); + + // 检查是否有更新记录 + $updateCount = $db->fetchRow($db->select('COUNT(*) as count') + ->from('table.' . self::$tableName) + ->where('cid = ?', $cid)); + + return $updateCount && $updateCount['count'] > 0; + } catch (Exception $e) { + return false; + } + } + + /** + * 渲染编辑记录HTML + */ + public static function renderEditHistory($cid = null) + { + if (!$cid) { + $widget = Typecho_Widget::widget('Widget_Archive'); + if (!$widget->is('single')) return ''; + $cid = $widget->cid; + } + + try { + $db = Typecho_Db::get(); + $options = Typecho_Widget::widget('Widget_Options'); + $pluginOptions = $options->plugin('EditHistory'); + + // 获取文章信息 + $post = $db->fetchRow($db->select('created', 'authorId') + ->from('table.contents') + ->where('cid = ?', $cid) + ->where('type = ?', 'post') + ->limit(1)); + + if (!$post) return ''; + + // 获取所有编辑记录 + $maxRecords = isset($pluginOptions->maxRecords) ? intval($pluginOptions->maxRecords) : 10; + $limit = $maxRecords > 0 ? $maxRecords : 1000; + + // 查询所有编辑记录,按时间倒序排列(最新的在最前面) + $history = $db->fetchAll($db->select( + 'id', + 'edit_time', + 'edit_content', + 'edit_type', + 'editor' + ) + ->from('table.' . self::$tableName) + ->where('cid = ?', $cid) + ->order('edit_time', Typecho_Db::SORT_DESC) + ->limit($limit)); + + if (empty($history)) return ''; + + // 计算正确的编辑次数 + $totalEdits = count($history); + + // 不使用引用传递,创建新数组 + $formattedHistory = array(); + foreach ($history as $index => $record) { + $formattedRecord = $record; // 复制数组,不使用引用 + $formattedRecord['edit_number'] = $totalEdits - $index; + $formattedHistory[] = $formattedRecord; + } + + // 获取用户信息 + $userIds = array(); + foreach ($formattedHistory as $record) { + if ($record['editor']) { + $userIds[$record['editor']] = $record['editor']; + } + } + + $users = array(); + if (!empty($userIds)) { + $userResults = $db->fetchAll($db->select('uid', 'screenName', 'name') + ->from('table.users') + ->where('uid IN (' . implode(',', $userIds) . ')')); + + foreach ($userResults as $user) { + $users[$user['uid']] = $user; + } + } + + // 计算发布天数 + $publishDays = ''; + if (isset($pluginOptions->showPublishDays) && $pluginOptions->showPublishDays == '1') { + $days = self::calculateDaysFromPublish($post['created']); + $publishDays = '
+ 本文发布于' . $days . '前,内容可能有时效性,注意参考阅读 +
'; + } + + // 获取默认收起/展开设置 + $defaultCollapsed = isset($pluginOptions->defaultCollapsed) && $pluginOptions->defaultCollapsed == '1'; + $collapseClass = $defaultCollapsed ? 'collapsed' : ''; + $toggleText = $defaultCollapsed ? '' : ''; + $icon = $defaultCollapsed ? '↓' : '↑'; + + // 生成HTML + $html = '
+
+
+

+ 编辑记录 + ' . count($formattedHistory) . '次 +

+ +
+ +
+ ' . $publishDays . ' +
    '; + + foreach ($formattedHistory as $record) { + $timeFormat = isset($pluginOptions->timeFormat) ? $pluginOptions->timeFormat : 'detail'; + $editTime = self::formatTime($record['edit_time'], $timeFormat); + + // 显示第几次编辑 + $editNumber = $record['edit_number']; + $actionText = "第{$editNumber}次编辑"; + + // 获取作者名称 + $authorName = '系统'; + if ($record['editor'] && isset($users[$record['editor']])) { + $user = $users[$record['editor']]; + if (!empty($user['screenName'])) { + $authorName = $user['screenName']; + } elseif (!empty($user['name'])) { + $authorName = $user['name']; + } + } + + $html .= '
  • +
    🕒 ' . $editTime . '
    +
    + ' . $actionText . ' + 编辑人:' . htmlspecialchars($authorName); + + // 显示编辑摘要 + if (isset($pluginOptions->showSummary) && $pluginOptions->showSummary == '1' && + !empty($record['edit_content'])) { + + $content = $record['edit_content']; + + // 判断是否是用户填写的摘要 + if ($content != '未填写编辑说明') { + $html .= ' 修改摘要:' . htmlspecialchars($content) . ''; + } else { + $html .= ' 修改摘要:' . htmlspecialchars($content) . ''; + } + } + + $html .= '
  • '; + } + + $html .= '
+ +
+
+
+ + '; + + return $html; + + } catch (Exception $e) { + return ''; + } + } + + private static function calculateDaysFromPublish($publishTime) + { + $currentTime = time(); + $diff = $currentTime - $publishTime; + $days = floor($diff / 86400); + + if ($days == 0) { + $hours = floor($diff / 3600); + if ($hours == 0) { + $minutes = floor($diff / 60); + return $minutes > 0 ? $minutes . '分钟' : '刚刚'; + } + return $hours . '小时'; + } elseif ($days == 1) { + return '1天'; + } else { + return $days . '天'; // 🔥 修改:超过1天全部显示为天数 + } + } + + private static function formatTime($timestamp, $format) + { + switch ($format) { + case 'simple': + return date('Y年m月d日', $timestamp); + case 'relative': + return self::getRelativeTime($timestamp); + default: + return date('Y年m月d日 H:i', $timestamp); + } + } + + private static function getRelativeTime($timestamp) + { + $current = time(); + $diff = $current - $timestamp; + + if ($diff < 60) return '刚刚'; + elseif ($diff < 3600) return floor($diff / 60) . '分钟前'; + elseif ($diff < 86400) return floor($diff / 3600) . '小时前'; + elseif ($diff < 2592000) return floor($diff / 86400) . '天前'; + else return floor($diff / 86400) . '天前'; // 🔥 修改:超过30天也显示为天数 + } + + public static function output() + { + return self::renderEditHistory(); + } + + /** + * 获取文章永久链接 + * 修改方法:使用Typecho的标准方法获取文章链接 + */ +public static function getPostPermalink($cid) +{ + try { + $db = Typecho_Db::get(); + $post = $db->fetchRow($db->select('slug', 'type', 'created') + ->from('table.contents') + ->where('cid = ?', $cid) + ->where('type = ?', 'post') + ->limit(1)); + + if (!$post) return '#'; + + // 使用Typecho的标准方法获取文章对象 + $widget = Typecho_Widget::widget('Widget_Archive', array('type' => 'single'), array('cid' => $cid)); + + // 直接返回文章的永久链接 + return $widget->permalink; + + } catch (Exception $e) { + // 如果上述方法失败,尝试简单构造URL + try { + $options = Typecho_Widget::widget('Widget_Options'); + $siteUrl = rtrim($options->siteUrl, '/'); + + // 查询文章slug + $db = Typecho_Db::get(); + $post = $db->fetchRow($db->select('slug') + ->from('table.contents') + ->where('cid = ?', $cid) + ->limit(1)); + + if ($post && !empty($post['slug'])) { + return $siteUrl . '/' . $post['slug'] . '.html'; + } + } catch (Exception $e2) { + return '#'; + } + + return '#'; + } +} +} \ No newline at end of file diff --git a/manage-panel.php b/manage-panel.php new file mode 100644 index 0000000..17afdd8 --- /dev/null +++ b/manage-panel.php @@ -0,0 +1,603 @@ + +pass('administrator')) { + exit; +} + +// 获取数据库连接 +$db = Typecho_Db::get(); + +// 分页设置 +$page = isset($_GET['page']) ? intval($_GET['page']) : 1; +if ($page < 1) $page = 1; +$pageSize = 20; +$offset = ($page - 1) * $pageSize; + +// 获取总记录数 +$totalCount = $db->fetchRow($db->select('COUNT(*) as count')->from('table.edit_history'))['count']; +$totalPages = ceil($totalCount / $pageSize); +if ($totalPages < 1) $totalPages = 1; +if ($page > $totalPages) $page = $totalPages; + +// 获取编辑记录,按时间倒序排列 +$records = $db->fetchAll($db->select() + ->from('table.edit_history') + ->order('edit_time', Typecho_Db::SORT_DESC) + ->limit($pageSize) + ->offset($offset)); + +// 获取用户信息 +$userIds = []; +$postIds = []; +foreach ($records as $record) { + if ($record['editor']) { + $userIds[$record['editor']] = $record['editor']; + } + $postIds[$record['cid']] = $record['cid']; +} + +$users = []; +if (!empty($userIds)) { + $userResults = $db->fetchAll($db->select('uid', 'screenName', 'name') + ->from('table.users') + ->where('uid IN (' . implode(',', $userIds) . ')')); + + foreach ($userResults as $user) { + $users[$user['uid']] = $user; + } +} + +// 获取文章标题和slug +$posts = []; +if (!empty($postIds)) { + $postResults = $db->fetchAll($db->select('cid', 'title', 'slug') + ->from('table.contents') + ->where('cid IN (' . implode(',', $postIds) . ')')); + + foreach ($postResults as $post) { + $posts[$post['cid']] = $post; + } +} + +// 获取选项 +$options = Typecho_Widget::widget('Widget_Options'); +$adminUrl = rtrim($options->adminUrl, '/'); +$siteUrl = rtrim($options->siteUrl, '/'); +?> + + + + + + 编辑记录管理 - <?php echo $options->title; ?> + + + + + + +
+
+
+

+
+
+ +
+
+
+ 总记录数: + 当前页数: / +
+ + +
+ 📄 +

暂无编辑记录

+ 返回文章管理 +
+ +
+ +
+
编辑时间
+
编辑用户
+
文章标题
+
编辑备注
+
常用操作
+
+ + + + ' . $summary . ''; + ?> + +
+
+ 编辑时间: + +
+
+ 用户: + +
+
+ 文章: + + + + + + + +
+
+ 编辑备注: + +
+ +
+ +
+ + 1): ?> +
+
+ 第 页 / 共 页 +
+
+ +
+
+ + + + +
+
+
+ + \ No newline at end of file