1357 lines
44 KiB
PHP
1357 lines
44 KiB
PHP
|
|
<?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()
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|