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 = '/(?' . htmlspecialchars($url) . ''; 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 '' . htmlspecialchars($url) . ''; }, $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() ); } } }