943 lines
31 KiB
PHP
943 lines
31 KiB
PHP
<?php
|
||
/**
|
||
* 文章编辑历史
|
||
*
|
||
* @package EditHistory
|
||
* @version 5.4.0
|
||
* @author 石头厝
|
||
* @link https://www.shitoucuo.com
|
||
*/
|
||
|
||
class EditHistory_Plugin implements Typecho_Plugin_Interface
|
||
{
|
||
private static $tableName = 'edit_history';
|
||
|
||
public static function activate()
|
||
{
|
||
self::createTable();
|
||
Typecho_Plugin::factory('Widget_Contents_Post_Edit')->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 '<style>
|
||
.edit-history-container {
|
||
margin: 0 0;
|
||
padding: 0;
|
||
width:100%;
|
||
}
|
||
|
||
.edit-history-box {
|
||
border-radius: 10px;
|
||
background: #ffffff;
|
||
overflow: hidden;
|
||
}
|
||
.dark .edit-history-box {
|
||
background: rgb(30 41 59 / var(--tw-bg-opacity));
|
||
}
|
||
|
||
.edit-history-header {
|
||
padding: 18px 24px;
|
||
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
|
||
color: white;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.dark .edit-history-header {
|
||
background: rgb(10 12 25 / var(--tw-bg-opacity));
|
||
}
|
||
|
||
.edit-history-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.edit-history-count {
|
||
background: rgba(255,255,255,0.2);
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.edit-history-toggle {
|
||
background: rgba(255,255,255,0.2);
|
||
border: none;
|
||
color: white;
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.edit-history-toggle:hover {
|
||
background: rgba(255,255,255,0.3);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.edit-history-toggle .icon {
|
||
font-size: 14px;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.edit-history-toggle.collapsed .icon {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
.edit-history-content-wrapper {
|
||
overflow: hidden;
|
||
transition: max-height 0.5s ease;
|
||
border-bottom-left-radius: 10px;
|
||
border-bottom-right-radius: 10px;
|
||
background: #ffffff;
|
||
}
|
||
.dark .edit-history-content-wrapper {
|
||
background: rgb(30 41 59 / var(--tw-bg-opacity));
|
||
}
|
||
|
||
.publish-info {
|
||
padding: 16px 24px;
|
||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||
color: #495057;
|
||
font-size: 14px;
|
||
line-height: 1.6;
|
||
margin:0 auto;
|
||
}
|
||
.dark .publish-info {
|
||
background: rgb(51 65 85 / var(--tw-bg-opacity));
|
||
color: #cbd5e1;
|
||
}
|
||
|
||
.publish-days {
|
||
color: #dc2626;
|
||
background: rgba(106, 17, 203, 0.1);
|
||
padding: 5px 15px;
|
||
border-radius:20px;
|
||
}
|
||
.dark .publish-days {
|
||
background: rgb(10 12 25 / var(--tw-bg-opacity)) !important;
|
||
color: rgb(156 163 175 / var(--tw-text-opacity)) !important;
|
||
}
|
||
|
||
.edit-history-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
.edit-history-item {
|
||
padding: 18px 24px;
|
||
border-bottom: 1px solid #f1f3f5;
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
}
|
||
.dark .edit-history-item {
|
||
border-bottom: 1px solid #334155;
|
||
}
|
||
|
||
.edit-history-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.edit-history-item:hover {
|
||
background-color: #f8f9fa;
|
||
}
|
||
.dark .edit-history-item:hover {
|
||
background-color: rgb(51 65 85 / var(--tw-bg-opacity));
|
||
}
|
||
|
||
.edit-history-item.create-item {
|
||
background-color: #f0f7ff;
|
||
}
|
||
.dark .edit-history-item.create-item {
|
||
background-color: rgb(30 58 138 / 0.2);
|
||
}
|
||
|
||
|
||
.edit-history-time {
|
||
font-size: 13px;
|
||
color: #666;
|
||
margin-bottom: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.dark .edit-history-time {
|
||
color: #cbd5e1;
|
||
}
|
||
|
||
.edit-history-author {
|
||
font-size: 15px;
|
||
color: #333;
|
||
margin-bottom: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.dark .edit-history-author {
|
||
color: #e2e8f0;
|
||
}
|
||
|
||
.edit-history-action {
|
||
display: inline-block;
|
||
padding: 3px 10px;
|
||
border-radius: 15px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.edit-history-action.create {
|
||
background: #1890ff;
|
||
color: white;
|
||
}
|
||
|
||
.edit-history-action.update {
|
||
background: #52c41a;
|
||
color: white;
|
||
}
|
||
|
||
.dark .edit-history-action.update{
|
||
background: rgb(10 12 25 / var(--tw-bg-opacity)) !important;
|
||
color: rgb(156 163 175 /1) !important;
|
||
}
|
||
.edit-history-summary {
|
||
display: inline-block;
|
||
margin-left: 8px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
vertical-align: middle;
|
||
}
|
||
.dark .edit-history-summary {
|
||
color: #cbd5e1;
|
||
}
|
||
|
||
.edit-history-summary-text {
|
||
color: #1890ff;
|
||
font-weight: 500;
|
||
}
|
||
.dark .edit-history-summary-text {
|
||
color: #60a5fa;
|
||
}
|
||
|
||
.edit-history-summary-default {
|
||
color: #999;
|
||
}
|
||
.dark .edit-history-summary-default {
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.edit-history-empty {
|
||
padding: 60px 24px;
|
||
color: #adb5bd;
|
||
font-size: 15px;
|
||
}
|
||
.dark .edit-history-empty {
|
||
color: #64748b;
|
||
}
|
||
|
||
.edit-history-footer {
|
||
padding: 14px 24px;
|
||
background: #f8f9fa;
|
||
font-size: 12px;
|
||
color: #868e96;
|
||
text-align: center;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
border-bottom-left-radius: 10px;
|
||
border-bottom-right-radius: 10px;
|
||
}
|
||
.dark .edit-history-footer {
|
||
background: rgb(51 65 85 / var(--tw-bg-opacity));
|
||
color: #94a3b8;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.edit-history-box {
|
||
border-radius: 8px;
|
||
margin: 30px 0;
|
||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.edit-history-header,
|
||
.edit-history-item,
|
||
.publish-info {
|
||
padding: 14px 16px;
|
||
}
|
||
|
||
.edit-history-footer {
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 12px 16px;
|
||
border-bottom-left-radius: 8px;
|
||
border-bottom-right-radius: 8px;
|
||
}
|
||
|
||
.edit-history-title {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.edit-history-summary {
|
||
margin-left: 0;
|
||
margin-top: 4px;
|
||
display: block;
|
||
width: 100%;
|
||
}
|
||
|
||
.edit-history-content-wrapper {
|
||
border-bottom-left-radius: 8px;
|
||
border-bottom-right-radius: 8px;
|
||
}
|
||
}
|
||
</style>';
|
||
}
|
||
|
||
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 = '<div class="publish-info">
|
||
<span class="publish-days">本文发布于' . $days . '前,内容可能有时效性,注意参考阅读</span>
|
||
</div>';
|
||
}
|
||
|
||
// 获取默认收起/展开设置
|
||
$defaultCollapsed = isset($pluginOptions->defaultCollapsed) && $pluginOptions->defaultCollapsed == '1';
|
||
$collapseClass = $defaultCollapsed ? 'collapsed' : '';
|
||
$toggleText = $defaultCollapsed ? '<!--展开-->' : '<!--收起-->';
|
||
$icon = $defaultCollapsed ? '↓' : '↑';
|
||
|
||
// 生成HTML
|
||
$html = '<div class="edit-history-container">
|
||
<div class="edit-history-box">
|
||
<div class="edit-history-header" onclick="toggleEditHistory(this)">
|
||
<h3 class="edit-history-title">
|
||
<!--📝-->编辑记录
|
||
<span class="edit-history-count">' . count($formattedHistory) . '次</span>
|
||
</h3>
|
||
<button class="editHistory-toggle ' . $collapseClass . '">
|
||
<span class="icon1">' . $icon . '</span>
|
||
<span class="text">' . $toggleText . '</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="edit-history-content-wrapper" style="' . ($defaultCollapsed ? 'max-height: 0;' : 'max-height: 2000px;') . '">
|
||
' . $publishDays . '
|
||
<ul class="edit-history-list">';
|
||
|
||
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 .= '<li class="edit-history-item update-item">
|
||
<div class="edit-history-time">🕒 ' . $editTime . '</div>
|
||
<div class="edit-history-author">
|
||
<span class="edit-history-action update">' . $actionText . '</span>
|
||
编辑人:' . htmlspecialchars($authorName);
|
||
|
||
// 显示编辑摘要
|
||
if (isset($pluginOptions->showSummary) && $pluginOptions->showSummary == '1' &&
|
||
!empty($record['edit_content'])) {
|
||
|
||
$content = $record['edit_content'];
|
||
|
||
// 判断是否是用户填写的摘要
|
||
if ($content != '未填写编辑说明') {
|
||
$html .= '<span class="edit-history-summary"> 修改摘要:<span class="edit-history-summary-text">' . htmlspecialchars($content) . '</span></span>';
|
||
} else {
|
||
$html .= '<span class="edit-history-summary"> 修改摘要:<span class="edit-history-summary-default">' . htmlspecialchars($content) . '</span></span>';
|
||
}
|
||
}
|
||
|
||
$html .= '</div></li>';
|
||
}
|
||
|
||
$html .= '</ul>
|
||
<div class="edit-history-footer">
|
||
<div>本文共有' . count($formattedHistory) . '次编辑修改记录</div>
|
||
<div>最后编辑:' . self::formatTime($formattedHistory[0]['edit_time'], $timeFormat) . '</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function toggleEditHistory(header) {
|
||
var wrapper = header.nextElementSibling;
|
||
var toggleBtn = header.querySelector(".editHistory-toggle");
|
||
var icon = toggleBtn.querySelector(".icon");
|
||
var text = toggleBtn.querySelector(".text");
|
||
|
||
if (wrapper.style.maxHeight && wrapper.style.maxHeight !== "0px") {
|
||
// 收起
|
||
wrapper.style.maxHeight = "0";
|
||
toggleBtn.classList.add("collapsed");
|
||
icon.textContent = "↓";
|
||
text.textContent = "";
|
||
} else {
|
||
// 展开
|
||
wrapper.style.maxHeight = wrapper.scrollHeight + "px";
|
||
toggleBtn.classList.remove("collapsed");
|
||
icon.textContent = "↑";
|
||
text.textContent = "";
|
||
}
|
||
}
|
||
|
||
// 初始设置
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
var wrappers = document.querySelectorAll(".edit-history-content-wrapper");
|
||
for (var i = 0; i < wrappers.length; i++) {
|
||
var wrapper = wrappers[i];
|
||
if (!wrapper.style.maxHeight) {
|
||
var header = wrapper.previousElementSibling;
|
||
var toggleBtn = header.querySelector(".editHistory-toggle");
|
||
if (toggleBtn.classList.contains("collapsed")) {
|
||
wrapper.style.maxHeight = "0";
|
||
} else {
|
||
wrapper.style.maxHeight = wrapper.scrollHeight + "px";
|
||
}
|
||
}
|
||
}
|
||
});
|
||
</script>';
|
||
|
||
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 '#';
|
||
}
|
||
}
|
||
} |