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 '#'; } } }