Files
Memo/Action.php
2026-02-23 17:31:07 +08:00

1357 lines
44 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
class Memo_Action
{
/**
* 数据库对象
*/
private $db;
private $prefix;
/**
* 请求对象
*/
private $request;
/**
* 响应对象
*/
private $response;
/**
* 构造函数
*/
public function __construct($request = null, $response = null)
{
$this->db = Typecho_Db::get();
$this->prefix = $this->db->getPrefix();
$this->request = $request ?: Typecho_Request::getInstance();
$this->response = $response ?: Typecho_Response::getInstance();
}
/**
* 添加备忘录 - 修复SQL错误
*/
public function addMemo($data)
{
try {
$now = new Typecho_Date(Typecho_Date::gmtTime());
$insert = array(
'content' => $data['content'],
'category' => isset($data['category']) ? $data['category'] : '默认',
'event_date' => !empty($data['event_date']) ? $data['event_date'] : null,
'post_cids' => isset($data['post_cids']) ? $this->cleanCids($data['post_cids']) : '',
'original_url' => isset($data['original_url']) ? trim($data['original_url']) : '', // 新增:原文链接
'created' => $now->format('Y-m-d H:i:s'),
'modified' => $now->format('Y-m-d H:i:s'),
'status' => 1
);
$this->validateInsertData($insert);
// 修复直接使用insert方法不调用query()两次
$insertId = $this->db->query($this->db->insert($this->prefix . 'memo')->rows($insert));
// 获取最后插入的ID
$row = $this->db->fetchRow($this->db->select('LAST_INSERT_ID() as id'));
return $row ? $row['id'] : $insertId;
} catch (Exception $e) {
error_log("addMemo Error: " . $e->getMessage());
throw new Exception("发布失败: " . $e->getMessage());
}
}
/**
* 验证插入数据
*/
private function validateInsertData(&$data)
{
if (empty($data['content'])) {
throw new Exception("内容不能为空");
}
if (!empty($data['event_date']) && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['event_date'])) {
throw new Exception("日期格式错误应为YYYY-MM-DD");
}
$data['content'] = trim($data['content']);
if (empty($data['content'])) {
throw new Exception("内容不能为空");
}
// 验证原文链接格式(如果提供)
if (!empty($data['original_url']) && !filter_var($data['original_url'], FILTER_VALIDATE_URL)) {
throw new Exception("原文链接格式不正确请输入有效的URL");
}
// 确保分类不为空
if (empty($data['category'])) {
$data['category'] = '默认';
}
}
/**
* 更新备忘录
*/
public function updateMemo($data)
{
try {
$now = new Typecho_Date(Typecho_Date::gmtTime());
$update = array(
'content' => $data['edit_content'],
'category' => isset($data['edit_category']) ? $data['edit_category'] : '默认',
'event_date' => !empty($data['edit_event_date']) ? $data['edit_event_date'] : null,
'post_cids' => isset($data['edit_post_cids']) ? $this->cleanCids($data['edit_post_cids']) : '',
'original_url' => isset($data['edit_original_url']) ? trim($data['edit_original_url']) : '', // 新增:原文链接
'modified' => $now->format('Y-m-d H:i:s')
);
// 验证原文链接格式(如果提供)
if (!empty($update['original_url']) && !filter_var($update['original_url'], FILTER_VALIDATE_URL)) {
throw new Exception("原文链接格式不正确请输入有效的URL");
}
// 修复直接使用update方法
$this->db->query($this->db->update($this->prefix . 'memo')
->rows($update)
->where('id = ?', intval($data['edit_id'])));
} catch (Exception $e) {
error_log("updateMemo Error: " . $e->getMessage());
throw new Exception("更新失败: " . $e->getMessage());
}
}
/**
* 删除备忘录
*/
public function deleteMemo($id)
{
$this->db->query($this->db->delete($this->prefix . 'memo')->where('id = ?', intval($id)));
}
/**
* 批量删除
*/
public function deleteMemos($ids)
{
foreach ($ids as $id) {
$this->deleteMemo(intval($id));
}
}
/**
* 获取备忘录列表(带搜索和筛选)
*/
public function getMemos($page = 1, $perPage = 10, $search = '', $category = '', $orderBy = 'id', $order = 'DESC')
{
$offset = ($page - 1) * $perPage;
$query = $this->db->select()
->from($this->prefix . 'memo')
->where('status = ?', 1);
// 搜索条件
if (!empty($search)) {
$query->where('content LIKE ?', '%' . $search . '%');
}
// 分类筛选
if (!empty($category)) {
$query->where('category = ?', $category);
}
// 排序
$query->order($orderBy, $order);
// 分页
$query->limit($perPage)->offset($offset);
return $this->db->fetchAll($query);
}
/**
* 获取所有备忘录(用于导出)
*/
public function getAllMemos()
{
$query = $this->db->select()
->from($this->prefix . 'memo')
->where('status = ?', 1)
->order('id', Typecho_Db::SORT_DESC);
return $this->db->fetchAll($query);
}
/**
* 获取按分类分组的备忘录用于MD导出
*/
public function getMemosGroupedByCategory()
{
$query = $this->db->select()
->from($this->prefix . 'memo')
->where('status = ?', 1)
->order('category', Typecho_Db::SORT_ASC)
->order('event_date', Typecho_Db::SORT_DESC)
->order('id', Typecho_Db::SORT_DESC);
$memos = $this->db->fetchAll($query);
$grouped = array();
foreach ($memos as $memo) {
$category = $memo['category'] ?: '未分类';
if (!isset($grouped[$category])) {
$grouped[$category] = array();
}
$grouped[$category][] = $memo;
}
return $grouped;
}
/**
* 获取总记录数(带搜索和筛选)
*/
public function getTotalCount($search = '', $category = '')
{
$query = $this->db->select('COUNT(*) as count')
->from($this->prefix . 'memo')
->where('status = ?', 1);
if (!empty($search)) {
$query->where('content LIKE ?', '%' . $search . '%');
}
if (!empty($category)) {
$query->where('category = ?', $category);
}
$result = $this->db->fetchRow($query);
return $result['count'];
}
/**
* 获取单条记录
*/
public function getMemo($id)
{
$query = $this->db->select()
->from($this->prefix . 'memo')
->where('id = ?', $id)
->limit(1);
return $this->db->fetchRow($query);
}
/**
* 获取所有分类
*/
public function getAllCategories()
{
$query = $this->db->select('DISTINCT category')
->from($this->prefix . 'memo')
->where('status = ?', 1)
->order('category', Typecho_Db::SORT_ASC);
$results = $this->db->fetchAll($query);
$categories = array();
foreach ($results as $row) {
if (!empty($row['category'])) {
$categories[] = $row['category'];
}
}
return $categories;
}
/**
* 获取各分类的记录数
*/
public function getCategoryCounts()
{
$query = $this->db->select('category, COUNT(*) as count')
->from($this->prefix . 'memo')
->where('status = ?', 1)
->group('category')
->order('count', Typecho_Db::SORT_DESC);
$results = $this->db->fetchAll($query);
$counts = array();
foreach ($results as $row) {
$counts[$row['category']] = $row['count'];
}
return $counts;
}
/**
* 根据文章CID获取文章信息
*/
public function getPostByCid($cid)
{
try {
$query = $this->db->select('cid, title, slug, created')
->from($this->prefix . 'contents')
->where('cid = ?', intval($cid))
->where('type = ?', 'post')
->where('status = ?', 'publish')
->limit(1);
$post = $this->db->fetchRow($query);
if ($post) {
$options = Typecho_Widget::widget('Widget_Options');
// 根据伪静态规则生成文章URL
if (!empty($post['slug'])) {
// 假设伪静态规则为 /{slug}.html
$post['url'] = Typecho_Common::url($post['slug'] . '.html', $options->index);
} else {
// 如果没有slug使用默认格式
$post['url'] = Typecho_Common::url('archives/' . $post['cid'], $options->index);
}
}
return $post;
} catch (Exception $e) {
return null;
}
}
/**
* 清理CID字符串
*/
private function cleanCids($cids)
{
if (empty($cids)) {
return '';
}
$cidsArray = array_filter(array_map('trim', explode(',', $cids)));
$validCids = array();
foreach ($cidsArray as $cid) {
if (is_numeric($cid) && $cid > 0) {
$validCids[] = intval($cid);
}
}
return implode(',', array_unique($validCids));
}
/**
* 导出数据为文本格式
*/
public function exportData()
{
$memos = $this->getAllMemos();
$content = '';
foreach ($memos as $memo) {
$line = '';
// 添加分类
if (!empty($memo['category'])) {
$line .= '[' . $memo['category'] . '] ';
}
// 添加日期
if (!empty($memo['event_date'])) {
$date = date('Y.m.d', strtotime($memo['event_date']));
$line .= $date . ' ';
}
// 添加内容
$text = str_replace(array("\r\n", "\r", "\n"), " ", $memo['content']);
$line .= $text;
// 添加原文链接(如果有)
if (!empty($memo['original_url'])) {
$line .= ' [' . $memo['original_url'] . ']';
}
$line .= "\n";
$content .= $line;
}
return $content;
}
/**
* 导出数据为Markdown格式
*/
public function exportMdData()
{
$groupedMemos = $this->getMemosGroupedByCategory();
$content = "# 知识备忘录\n\n";
$content .= "> 导出时间:" . date('Y-m-d H:i:s') . "\n\n";
foreach ($groupedMemos as $category => $memos) {
$content .= "## " . $category . "\n\n";
foreach ($memos as $memo) {
$content .= "- ";
// 添加日期
if (!empty($memo['event_date'])) {
$date = date('Y-m-d', strtotime($memo['event_date']));
$content .= "**" . $date . "** ";
}
// 添加内容
$text = $memo['content'];
// 清理换行符但保留URL
$text = preg_replace('/\r\n|\r|\n/', ' ', $text);
$content .= $text;
// 添加原文链接(如果有)
if (!empty($memo['original_url'])) {
$content .= " [原文链接](" . $memo['original_url'] . ")";
}
// 添加关联文章(如果有)
if (!empty($memo['post_cids'])) {
$cids = array_filter(array_map('trim', explode(',', $memo['post_cids'])));
if (count($cids) > 0) {
$content .= " (关联文章: " . implode(', ', $cids) . ")";
}
}
$content .= "\n";
}
$content .= "\n";
}
// 添加统计信息
$totalCount = $this->getTotalCount();
$categoryCounts = $this->getCategoryCounts();
$content .= "---\n\n";
$content .= "## 统计信息\n\n";
$content .= "- 总记录数: " . $totalCount . "\n";
foreach ($categoryCounts as $cat => $count) {
$content .= "- " . $cat . ": " . $count . "\n";
}
return $content;
}
/**
* 导出选中的备忘录数据
*/
public function exportSelectedData($ids, $format = 'txt')
{
if (empty($ids)) {
throw new Exception("未选择要导出的记录");
}
// 获取选中的记录
$ids = array_map('intval', $ids);
$idsStr = implode(',', $ids);
$query = $this->db->select()
->from($this->prefix . 'memo')
->where('id IN (' . $idsStr . ')')
->where('status = ?', 1)
->order('id', Typecho_Db::SORT_DESC);
$memos = $this->db->fetchAll($query);
if ($format === 'md') {
return $this->exportSelectedAsMd($memos);
} else {
return $this->exportSelectedAsTxt($memos);
}
}
/**
* 导出选中记录为TXT格式
*/
private function exportSelectedAsTxt($memos)
{
$content = '';
$content .= '=== 选中的备忘录记录 ===' . "\n";
$content .= '导出时间:' . date('Y-m-d H:i:s') . "\n";
$content .= '记录数量:' . count($memos) . "\n";
$content .= '=======================' . "\n\n";
foreach ($memos as $memo) {
$line = '';
// 添加ID
$line .= 'ID:' . $memo['id'] . ' ';
// 添加分类
if (!empty($memo['category'])) {
$line .= '[' . $memo['category'] . '] ';
}
// 添加日期
if (!empty($memo['event_date'])) {
$date = date('Y.m.d', strtotime($memo['event_date']));
$line .= $date . ' ';
}
// 添加内容
$text = str_replace(array("\r\n", "\r", "\n"), " ", $memo['content']);
$line .= $text;
// 添加原文链接(如果有)
if (!empty($memo['original_url'])) {
$line .= ' [' . $memo['original_url'] . ']';
}
// 添加创建时间
$line .= ' (创建:' . date('Y-m-d', strtotime($memo['created'])) . ')';
$line .= "\n";
$content .= $line;
}
// 添加统计信息
$content .= "\n" . '=== 导出完成 ===' . "\n";
$content .= '总记录数:' . count($memos) . "\n";
$content .= '导出时间:' . date('Y-m-d H:i:s');
return $content;
}
/**
* 导出选中记录为MD格式
*/
private function exportSelectedAsMd($memos)
{
// 按分类分组
$grouped = array();
foreach ($memos as $memo) {
$category = $memo['category'] ?: '未分类';
if (!isset($grouped[$category])) {
$grouped[$category] = array();
}
$grouped[$category][] = $memo;
}
// 按分类名称排序
ksort($grouped);
$content = "# 选中的备忘录记录\n\n";
$content .= "> 导出时间:" . date('Y-m-d H:i:s') . "\n";
$content .= "> 记录数量:" . count($memos) . "\n\n";
foreach ($grouped as $category => $categoryMemos) {
$content .= "## " . $category . "\n\n";
foreach ($categoryMemos as $memo) {
$content .= "### ID:" . $memo['id'] . "\n\n";
$content .= "- **创建时间**" . date('Y-m-d H:i:s', strtotime($memo['created'])) . "\n";
if (!empty($memo['event_date'])) {
$content .= "- **事件日期**" . date('Y-m-d', strtotime($memo['event_date'])) . "\n";
}
if (!empty($memo['original_url'])) {
$content .= "- **原文链接**[点击查看](" . $memo['original_url'] . ")\n";
}
if (!empty($memo['post_cids'])) {
$cids = array_filter(array_map('trim', explode(',', $memo['post_cids'])));
if (count($cids) > 0) {
$content .= "- **关联文章**" . implode(', ', $cids) . "\n";
}
}
$content .= "\n**内容**\n\n";
$content .= $memo['content'] . "\n\n";
$content .= "---\n\n";
}
}
// 添加统计信息
$content .= "## 统计信息\n\n";
$content .= "- **总记录数**" . count($memos) . "\n";
$content .= "- **分类数量**" . count($grouped) . "\n";
foreach ($grouped as $category => $categoryMemos) {
$content .= " - " . $category . "" . count($categoryMemos) . "\n";
}
$content .= "- **导出时间**" . date('Y-m-d H:i:s') . "\n";
return $content;
}
/**
* 导入数据(修复版)
*/
public function importData($text)
{
$text = str_replace("\r\n", "\n", $text);
$text = str_replace("\r", "\n", $text);
$lines = explode("\n", $text);
$imported = 0;
$failed = 0;
$failReasons = array();
// 检查是否是Markdown格式
$isMarkdown = false;
foreach ($lines as $line) {
if (strpos($line, '## ') === 0 || strpos($line, '# ') === 0) {
$isMarkdown = true;
break;
}
}
if ($isMarkdown) {
return $this->importMdData($text);
}
// 原始TXT格式导入
foreach ($lines as $lineNum => $line) {
$line = trim($line);
if (empty($line)) continue;
// 解析分类
$category = '默认';
$event_date = null;
$original_url = '';
$content = $line;
// 匹配分类 [分类]
if (preg_match('/^\[([^\]]+)\]\s*(.*)$/', $line, $matches)) {
$category = trim($matches[1]);
$content = trim($matches[2]);
}
// 匹配日期 YYYY.MM.DD 或 YYYY.M.D
$datePattern = '/^(\d{4})\.(\d{1,2})\.(\d{1,2})\s+(.*)$/';
if (preg_match($datePattern, $content, $matches)) {
$year = intval($matches[1]);
$month = intval($matches[2]);
$day = intval($matches[3]);
if (checkdate($month, $day, $year)) {
$event_date = sprintf('%04d-%02d-%02d', $year, $month, $day);
$content = trim($matches[4]);
}
}
// 匹配原文链接 [URL]
if (preg_match('/^(.*)\s+\[(https?:\/\/[^\]]+)\]$/', $content, $matches)) {
$content = trim($matches[1]);
$original_url = trim($matches[2]);
}
if (empty($content)) {
$failed++;
$failReasons[] = "" . ($lineNum + 1) . " 行:内容为空";
continue;
}
try {
$now = new Typecho_Date(Typecho_Date::gmtTime());
$insert = array(
'content' => $content,
'category' => $category,
'event_date' => $event_date,
'post_cids' => '',
'original_url' => $original_url,
'created' => $now->format('Y-m-d H:i:s'),
'modified' => $now->format('Y-m-d H:i:s'),
'status' => 1
);
$this->db->query($this->db->insert($this->prefix . 'memo')->rows($insert));
$imported++;
} catch (Exception $e) {
$failed++;
$failReasons[] = "" . ($lineNum + 1) . " 行:导入失败 - " . $e->getMessage();
}
}
return array(
'imported' => $imported,
'failed' => $failed,
'fail_reasons' => $failReasons
);
}
/**
* 导入Markdown格式数据
*/
private function importMdData($text)
{
$text = str_replace("\r\n", "\n", $text);
$text = str_replace("\r", "\n", $text);
$lines = explode("\n", $text);
$imported = 0;
$failed = 0;
$failReasons = array();
$currentCategory = '默认';
$inCategory = false;
foreach ($lines as $lineNum => $line) {
$line = trim($line);
if (empty($line)) continue;
// 检测分类标题 (## 分类名)
if (strpos($line, '## ') === 0) {
$currentCategory = trim(substr($line, 3));
$inCategory = true;
continue;
}
// 跳过其他标题和元信息
if (strpos($line, '# ') === 0 || strpos($line, '> ') === 0 || strpos($line, '---') === 0) {
continue;
}
// 检测列表项 (- 内容)
if (strpos($line, '- ') === 0) {
$content = trim(substr($line, 2));
// 解析日期 (**日期**)
$event_date = null;
if (preg_match('/^\*\*(\d{4}-\d{2}-\d{2})\*\*\s*(.*)$/', $content, $matches)) {
$event_date = $matches[1];
$content = trim($matches[2]);
}
// 解析原文链接 [原文链接](URL)
$original_url = '';
if (preg_match('/^(.*)\s+\[原文链接\]\((https?:\/\/[^)]+)\)/', $content, $matches)) {
$content = trim($matches[1]);
$original_url = trim($matches[2]);
}
// 解析关联文章 (关联文章: CID列表)
$post_cids = '';
if (preg_match('/^(.*)\s+\(关联文章:\s*([^)]+)\)/', $content, $matches)) {
$content = trim($matches[1]);
$post_cids = preg_replace('/[^0-9,]/', '', $matches[2]);
}
if (empty($content)) {
$failed++;
$failReasons[] = "" . ($lineNum + 1) . " 行:内容为空";
continue;
}
try {
$now = new Typecho_Date(Typecho_Date::gmtTime());
$insert = array(
'content' => $content,
'category' => $currentCategory,
'event_date' => $event_date,
'post_cids' => $post_cids,
'original_url' => $original_url,
'created' => $now->format('Y-m-d H:i:s'),
'modified' => $now->format('Y-m-d H:i:s'),
'status' => 1
);
$this->db->query($this->db->insert($this->prefix . 'memo')->rows($insert));
$imported++;
} catch (Exception $e) {
$failed++;
$failReasons[] = "" . ($lineNum + 1) . " 行:导入失败 - " . $e->getMessage();
}
}
}
return array(
'imported' => $imported,
'failed' => $failed,
'fail_reasons' => $failReasons
);
}
/**
* 导入SQL文件数据从Typecho文章SQL导入- 简化修复版
*/
public function importSqlData($sqlContent, $category = '文章导入')
{
try {
$imported = 0;
$failed = 0;
$failReasons = array();
// 方法:使用最简单直接的字符串处理
$lines = explode("\n", $sqlContent);
$inInsert = false;
$insertBuffer = '';
$insertCount = 0;
error_log("开始导入SQL总行数: " . count($lines));
foreach ($lines as $lineNum => $line) {
$line = trim($line);
// 跳过空行和注释
if (empty($line) || strpos($line, '--') === 0) {
continue;
}
// 检查是否是INSERT语句开始
if (!$inInsert && preg_match('/INSERT\s+INTO\s+`?(\w+contents)`?\s+\(/i', $line)) {
$inInsert = true;
$insertBuffer = $line;
error_log("{$lineNum} 行: 开始INSERT语句");
continue;
}
// 如果在INSERT语句中
if ($inInsert) {
$insertBuffer .= ' ' . $line;
// 检查是否结束(以分号结尾)
if (substr($line, -1) === ';') {
$insertCount++;
error_log("{$lineNum} 行: 结束INSERT语句 #{$insertCount}");
// 处理这个INSERT语句
$result = $this->parseAndImportInsert($insertBuffer, $category, $imported + 1);
$imported += $result['imported'];
$failed += $result['failed'];
$failReasons = array_merge($failReasons, $result['fail_reasons']);
$inInsert = false;
$insertBuffer = '';
// 每处理10个INSERT输出一次进度
if ($insertCount % 10 == 0) {
error_log("已处理 {$insertCount} 个INSERT语句导入 {$imported} 条记录");
}
}
}
}
// 处理最后一个可能的未完成INSERT
if ($inInsert && !empty($insertBuffer)) {
$insertCount++;
error_log("处理最后一个未完成的INSERT语句 #{$insertCount}");
$result = $this->parseAndImportInsert($insertBuffer . ';', $category, $imported + 1);
$imported += $result['imported'];
$failed += $result['failed'];
$failReasons = array_merge($failReasons, $result['fail_reasons']);
}
error_log("导入完成: 找到 {$insertCount} 个INSERT语句成功 {$imported} 条,失败 {$failed}");
return array(
'imported' => $imported,
'failed' => $failed,
'fail_reasons' => $failReasons
);
} catch (Exception $e) {
error_log("importSqlData 异常: " . $e->getMessage());
return array(
'imported' => 0,
'failed' => 1,
'fail_reasons' => array('导入失败: ' . $e->getMessage())
);
}
}
/**
* 解析并导入单个INSERT语句
*/
private function parseAndImportInsert($insertSql, $category, $startIndex)
{
$imported = 0;
$failed = 0;
$failReasons = array();
try {
// 移除末尾的分号
$insertSql = rtrim($insertSql, ';');
// 尝试多种方式解析INSERT语句
$records = $this->parseInsertStatement($insertSql);
foreach ($records as $recordIndex => $record) {
$currentIndex = $startIndex + $imported + $failed;
try {
// 检查是否为文章
if (!isset($record['type']) || $record['type'] != 'post') {
continue; // 跳过非文章类型
}
// 获取标题和内容
$title = isset($record['title']) ? trim($record['title']) : '';
$text = isset($record['text']) ? trim($record['text']) : '';
$created = isset($record['created']) ? intval($record['created']) : time();
// 跳过空内容
if (empty($text) && empty($title)) {
$failed++;
continue;
}
// 处理内容
$text = $this->cleanContent($text);
// 组合内容
$content = '';
if (!empty($title)) {
$content = $title;
if (!empty($text)) {
$content .= "\n\n" . $text;
}
} else {
$content = $text;
}
// 截断过长的内容
if (mb_strlen($content, 'UTF-8') > 800) {
$content = mb_substr($content, 0, 800, 'UTF-8') . '...';
}
if (empty($content)) {
$failed++;
continue;
}
// 准备插入数据
$now = new Typecho_Date(Typecho_Date::gmtTime());
$createdDate = new Typecho_Date($created);
$insert = array(
'content' => $content,
'category' => $category,
'event_date' => $createdDate->format('Y-m-d'),
'post_cids' => '', // SQL导入不关联CID
'original_url' => '',
'created' => $now->format('Y-m-d H:i:s'),
'modified' => $now->format('Y-m-d H:i:s'),
'status' => 1
);
// 执行插入
$this->db->query($this->db->insert($this->prefix . 'memo')->rows($insert));
$imported++;
} catch (Exception $e) {
$failed++;
error_log("导入记录 {$currentIndex} 失败: " . $e->getMessage());
}
}
} catch (Exception $e) {
$failed++;
$failReasons[] = "解析INSERT语句失败: " . $e->getMessage();
error_log("解析INSERT语句失败: " . $e->getMessage());
}
return array(
'imported' => $imported,
'failed' => $failed,
'fail_reasons' => $failReasons
);
}
/**
* 解析INSERT语句 - 多种方法尝试
*/
private function parseInsertStatement($insertSql)
{
$records = array();
// 方法1: 使用正则提取VALUES部分
if (preg_match('/VALUES\s*(.+)$/is', $insertSql, $match)) {
$valuesPart = trim($match[1]);
// 提取所有括号内的值集
$valueSets = $this->extractValueSetsFromString($valuesPart);
foreach ($valueSets as $valueSet) {
$record = $this->parseValueSet($valueSet);
if (!empty($record)) {
$records[] = $record;
}
}
}
// 如果方法1失败尝试方法2
if (empty($records)) {
$records = $this->fallbackParseInsert($insertSql);
}
return $records;
}
/**
* 从字符串提取值集
*/
private function extractValueSetsFromString($str)
{
$sets = array();
$current = '';
$depth = 0;
$inString = false;
$quoteChar = '';
for ($i = 0; $i < strlen($str); $i++) {
$char = $str[$i];
// 处理字符串内的字符
if ($inString) {
if ($char === '\\') {
$current .= $char;
if ($i + 1 < strlen($str)) {
$current .= $str[$i + 1];
$i++;
}
continue;
}
if ($char === $quoteChar) {
$inString = false;
}
$current .= $char;
continue;
}
// 处理引号
if ($char === "'" || $char === '"') {
$inString = true;
$quoteChar = $char;
$current .= $char;
continue;
}
// 处理括号
if ($char === '(') {
if ($depth === 0) {
$current = '(';
} else {
$current .= $char;
}
$depth++;
continue;
}
if ($char === ')') {
$depth--;
if ($depth === 0) {
$current .= ')';
$sets[] = $current;
$current = '';
continue;
}
$current .= $char;
continue;
}
// 其他字符
if ($depth > 0) {
$current .= $char;
}
}
return $sets;
}
/**
* 解析值集
*/
private function parseValueSet($valueSet)
{
$record = array();
// 移除括号
$valueSet = trim($valueSet, '()');
// 分割值
$values = $this->splitValues($valueSet);
// 映射到常见的Typecho字段根据您的SQL结构调整
$fieldMap = array(
0 => 'cid',
1 => 'title',
2 => 'slug',
3 => 'created',
4 => 'modified',
5 => 'text',
6 => 'order',
7 => 'authorId',
8 => 'template',
9 => 'type',
10 => 'status',
11 => 'password',
12 => 'commentsNum',
13 => 'allowComment',
14 => 'allowPing',
15 => 'allowFeed',
16 => 'parent',
17 => 'views'
);
foreach ($values as $index => $value) {
if (isset($fieldMap[$index])) {
$field = $fieldMap[$index];
$record[$field] = $this->cleanValue($value);
}
}
return $record;
}
/**
* 分割值
*/
private function splitValues($str)
{
$values = array();
$current = '';
$depth = 0;
$inString = false;
$quoteChar = '';
for ($i = 0; $i < strlen($str); $i++) {
$char = $str[$i];
// 处理转义字符
if ($char === '\\' && $inString) {
$current .= $char;
if ($i + 1 < strlen($str)) {
$current .= $str[$i + 1];
$i++;
}
continue;
}
// 处理引号
if (($char === "'" || $char === '"') && ($i === 0 || $str[$i-1] !== '\\')) {
if (!$inString) {
$inString = true;
$quoteChar = $char;
} elseif ($char === $quoteChar) {
$inString = false;
}
$current .= $char;
continue;
}
// 处理逗号分隔
if ($char === ',' && !$inString && $depth === 0) {
$values[] = $current;
$current = '';
continue;
}
$current .= $char;
}
// 添加最后一个值
if (!empty($current)) {
$values[] = $current;
}
return $values;
}
/**
* 清理值
*/
private function cleanValue($value)
{
$value = trim($value);
// 移除引号
if ((strpos($value, "'") === 0 && substr($value, -1) === "'") ||
(strpos($value, '"') === 0 && substr($value, -1) === '"')) {
$value = substr($value, 1, -1);
}
// 处理转义字符
$value = str_replace("\\'", "'", $value);
$value = str_replace('\\"', '"', $value);
$value = str_replace('\\\\', '\\', $value);
return $value;
}
/**
* 备用解析方法
*/
private function fallbackParseInsert($insertSql)
{
$records = array();
// 尝试更简单的正则匹配
if (preg_match_all('/\(([^)]+)\)/', $insertSql, $matches)) {
foreach ($matches[1] as $match) {
// 假设这是值部分
$values = explode(',', $match);
if (count($values) >= 10) { // 确保有足够字段
$record = array(
'cid' => $this->cleanValue($values[0] ?? ''),
'title' => $this->cleanValue($values[1] ?? ''),
'slug' => $this->cleanValue($values[2] ?? ''),
'created' => $this->cleanValue($values[3] ?? ''),
'modified' => $this->cleanValue($values[4] ?? ''),
'text' => $this->cleanValue($values[5] ?? ''),
'type' => $this->cleanValue($values[9] ?? ''),
'status' => $this->cleanValue($values[10] ?? '')
);
$records[] = $record;
}
}
}
return $records;
}
/**
* 清理内容
*/
private function cleanContent($text)
{
if (empty($text)) return '';
// 移除HTML标签
$text = strip_tags($text);
// 解码HTML实体
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// 移除多余空白
$text = preg_replace('/\s+/', ' ', $text);
return trim($text);
}
/**
* 将URL转换为链接静态方法
* 修复只匹配准确的URL不包括前后文字
*/
public static function makeLinksClickable($text)
{
if (empty($text)) {
return $text;
}
// 改进的URL正则表达式 - 只匹配完整的URL
// 匹配格式http://example.com 或 https://example.com/path
// 使用单词边界确保只匹配完整的URL
$urlPattern = '/(?<![\w@])(https?:\/\/(?:www\.)?[a-zA-Z0-9][-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*))(?![\w@])/';
// 使用preg_replace_callback进行更精确的处理
$result = preg_replace_callback($urlPattern, function($matches) {
$url = trim($matches[1]);
// 清理URL末尾的标点符号如果存在
$punctuation = array('.', ',', ';', ':', '!', '?', ')', ']', '}');
$lastChar = substr($url, -1);
if (in_array($lastChar, $punctuation)) {
$url = substr($url, 0, -1);
$suffix = $lastChar;
} else {
$suffix = '';
}
// 创建链接
$link = '<a href="' . htmlspecialchars($url) . '" class="url-link" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($url) . '</a>';
return $link . $suffix;
}, $text);
return $result ? $result : $text;
}
/**
* 简单的URL检测函数 - 更保守的版本
*/
public static function simpleMakeLinksClickable($text)
{
if (empty($text)) {
return $text;
}
// 更简单的正则只匹配明显的URL
$urlPattern = '/(https?:\/\/[^\s<>"\'\)\(]+)/';
$result = preg_replace_callback($urlPattern, function($matches) {
$url = $matches[1];
// 移除URL末尾可能错误包含的标点
$url = rtrim($url, '.,;:!?)');
return '<a href="' . htmlspecialchars($url) . '" class="url-link" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($url) . '</a>';
}, $text);
return $result ? $result : $text;
}
/**
* 获取统计信息 - 修复使用event_date字段
*/
public function getStatistics()
{
$stats = array();
try {
// 总记录数
$totalResult = $this->db->fetchRow($this->db->select('COUNT(*) as count')
->from($this->prefix . 'memo')
->where('status = ?', 1));
$stats['total_count'] = $totalResult ? $totalResult['count'] : 0;
// 使用原生SQL查询避免复杂的where条件
$sql = "SELECT
MIN(event_date) as oldest_date,
MAX(event_date) as newest_date,
COUNT(CASE WHEN event_date IS NOT NULL AND event_date != '' AND event_date != '0000-00-00' THEN 1 END) as dated_count
FROM {$this->prefix}memo
WHERE status = 1";
$dateResult = $this->db->fetchRow($sql);
if ($dateResult) {
$stats['oldest_date'] = !empty($dateResult['oldest_date']) && $dateResult['oldest_date'] != '0000-00-00' ? $dateResult['oldest_date'] : null;
$stats['newest_date'] = !empty($dateResult['newest_date']) && $dateResult['newest_date'] != '0000-00-00' ? $dateResult['newest_date'] : null;
$stats['dated_count'] = $dateResult['dated_count'] ? intval($dateResult['dated_count']) : 0;
} else {
$stats['oldest_date'] = null;
$stats['newest_date'] = null;
$stats['dated_count'] = 0;
}
$stats['undated_count'] = $stats['total_count'] - $stats['dated_count'];
// 分类统计
$categoryQuery = $this->db->select('category, COUNT(*) as count')
->from($this->prefix . 'memo')
->where('status = ?', 1)
->group('category')
->order('count', Typecho_Db::SORT_DESC);
$categoryResults = $this->db->fetchAll($categoryQuery);
$stats['categories'] = array();
foreach ($categoryResults as $row) {
$stats['categories'][$row['category']] = $row['count'];
}
return $stats;
} catch (Exception $e) {
error_log("getStatistics Error: " . $e->getMessage());
// 返回默认值避免页面崩溃
return array(
'total_count' => 0,
'oldest_date' => null,
'newest_date' => null,
'dated_count' => 0,
'undated_count' => 0,
'categories' => array()
);
}
}
}