Files
Memo/Action.php

1357 lines
44 KiB
PHP
Raw Permalink Normal View History

2026-02-23 17:31:07 +08:00
<?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()
);
}
}
}