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 = '
+
+
+
+
+ ' . $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, '/');
+?>
+
+
+
+
+
+ 编辑记录管理 - title; ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ 总记录数: 条
+ 当前页数: /
+
+
+
+
+
+
+
+
+
+
+
+ ' . $summary . '';
+ ?>
+
+
+
+ 编辑时间:
+
+
+
+ 用户:
+
+
+
+
+ 编辑备注:
+
+
+
+
+
+
+
+ 1): ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file