contentEx = array('MyTrack_Plugin', 'parseMapShortcode');
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('MyTrack_Plugin', 'parseMapShortcode');
// 注册文章保存时的处理
Typecho_Plugin::factory('Widget_Abstract_Contents')->save = array('MyTrack_Plugin', 'onPostSave');
Typecho_Plugin::factory('Widget_Abstract_Contents')->write = array('MyTrack_Plugin', 'onPostWrite');
// 注册footer钩子来输出JS
Typecho_Plugin::factory('Widget_Archive')->footer = array('MyTrack_Plugin', 'footer');
// 注册header钩子来输出CSS
Typecho_Plugin::factory('Widget_Archive')->header = array('MyTrack_Plugin', 'header');
return _t('插件已激活');
}
/**
* 禁用插件方法
*
* @access public
* @return void
*/
public static function deactivate()
{
// 移除管理菜单 - 使用与activate()相同的索引
Helper::removePanel(3, 'MyTrack/Manage.php');
// 同时从数据库中清理相关记录
try {
$db = Typecho_Db::get();
// 清理options表中的面板记录
$db->query($db->delete('table.options')
->where('name = ?', 'panelTable:MyTrack/Manage.php'));
} catch (Exception $e) {
// 忽略错误
}
Helper::removeAction('track');
Helper::removeRoute('track_action');
return _t('插件已禁用');
}
/**
* 获取插件配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form 配置面板
* @return void
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
// 高德地图JS API密钥
$jsApiKey = new Typecho_Widget_Helper_Form_Element_Text('jsApiKey', null, '',
_t('高德地图JS API密钥 *'), _t('用于前端地图显示的API密钥,必填项'));
$jsApiKey->addRule('required', _t('JS API密钥不能为空'));
$form->addInput($jsApiKey);
// 高德地图Web服务API密钥
$webApiKey = new Typecho_Widget_Helper_Form_Element_Text('webApiKey', null, '',
_t('高德地图Web服务API密钥 *'), _t('用于后台地址搜索的API密钥,必填项'));
$webApiKey->addRule('required', _t('Web服务API密钥不能为空'));
$form->addInput($webApiKey);
// 默认缩放级别
$zoomLevel = new Typecho_Widget_Helper_Form_Element_Text('zoomLevel', null, '15',
_t('默认缩放级别 *'), _t('设置地图默认缩放级别,范围3-18,默认为15,必填项'));
$zoomLevel->addRule('required', _t('缩放级别不能为空'))
->addRule(function($value) {
return is_numeric($value) && $value >= 3 && $value <= 18;
}, _t('缩放级别必须是3-18之间的数字'));
$form->addInput($zoomLevel);
// 默认视图模式
$viewMode = new Typecho_Widget_Helper_Form_Element_Radio('viewMode', array(
'2D' => '2D视图',
'3D' => '3D视图'
), '2D', _t('默认视图模式'));
$form->addInput($viewMode);
// 默认地图主题
$mapTheme = new Typecho_Widget_Helper_Form_Element_Select('mapTheme', array(
'normal' => '标准',
'dark' => '幻影黑',
'light' => '月光银',
'whitesmoke' => '远山黛',
'fresh' => '草色青',
'grey' => '雅士灰',
'graffiti' => '涂鸦',
'macaron' => '马卡龙',
'blue' => '靛青蓝',
'darkblue' => '极夜蓝',
'wine' => '酱籽'
), 'normal', _t('默认地图主题'));
$form->addInput($mapTheme);
// 前台显示设置
$enableDisplay = new Typecho_Widget_Helper_Form_Element_Radio('enableDisplay', array(
1 => '是',
0 => '否'
), 1, _t('显示前台足迹地图'), _t('关闭后前台将不显示足迹地图,但后台管理功能仍可使用'));
$form->addInput($enableDisplay);
// 缓存时间设置
$cacheExpire = new Typecho_Widget_Helper_Form_Element_Text('cacheExpire', null, '7',
_t('缓存时间(天)*'), _t('设置数据缓存的有效期,单位为天,默认为7天,0表示关闭缓存'));
$cacheExpire->addRule('required', _t('缓存时间不能为空'))
->addRule(function($value) {
return is_numeric($value) && $value >= 0 && is_int($value + 0);
}, _t('缓存时间必须是一个大于等于0的整数'));
$form->addInput($cacheExpire);
// 点聚合功能设置
$enableCluster = new Typecho_Widget_Helper_Form_Element_Radio('enableCluster', array(
1 => '是',
0 => '否'
), 0, _t('启用点聚合功能'), _t('启用后,当地图上标记点较多时会自动聚合,提高性能和可读性'));
$form->addInput($enableCluster);
// 空内容标记点颜色设置
$emptyMarkerColor = new Typecho_Widget_Helper_Form_Element_Text('emptyMarkerColor', null, '#2196F3',
_t('空内容标记点颜色'), _t('设置空内容标记点的颜色'));
$emptyMarkerColor->input->setAttribute('type', 'color');
$form->addInput($emptyMarkerColor);
// 有内容标记点颜色设置
$contentMarkerColor = new Typecho_Widget_Helper_Form_Element_Text('contentMarkerColor', null, '#4CAF50',
_t('有内容标记点颜色'), _t('设置有内容标记点的颜色'));
$contentMarkerColor->input->setAttribute('type', 'color');
$form->addInput($contentMarkerColor);
// 聚合标记点颜色设置
$clusterMarkerColor1 = new Typecho_Widget_Helper_Form_Element_Text('clusterMarkerColor1', null, '#2196F3',
_t('聚合标记点颜色1'), _t('设置小规模聚合标记点的颜色'));
$clusterMarkerColor1->input->setAttribute('type', 'color');
$form->addInput($clusterMarkerColor1);
$clusterMarkerColor2 = new Typecho_Widget_Helper_Form_Element_Text('clusterMarkerColor2', null, '#FF9800',
_t('聚合标记点颜色2'), _t('设置中等规模聚合标记点的颜色'));
$clusterMarkerColor2->input->setAttribute('type', 'color');
$form->addInput($clusterMarkerColor2);
$clusterMarkerColor3 = new Typecho_Widget_Helper_Form_Element_Text('clusterMarkerColor3', null, '#FF5722',
_t('聚合标记点颜色3'), _t('设置大规模聚合标记点的颜色'));
$clusterMarkerColor3->input->setAttribute('type', 'color');
$form->addInput($clusterMarkerColor3);
// 短代码地图卡片样式设置
$cardMapWidth = new Typecho_Widget_Helper_Form_Element_Text('cardMapWidth', null, '40%',
_t('短代码地图宽度'), _t('设置短代码地图卡片的宽度,例如:40% 或 300px'));
$form->addInput($cardMapWidth);
$cardMapHeight = new Typecho_Widget_Helper_Form_Element_Text('cardMapHeight', null, '320px',
_t('短代码地图高度'), _t('设置短代码地图卡片的高度'));
$form->addInput($cardMapHeight);
$cardMapZoom = new Typecho_Widget_Helper_Form_Element_Text('cardMapZoom', null, '16',
_t('短代码地图缩放级别'), _t('设置短代码地图的缩放级别,范围3-18'));
$form->addInput($cardMapZoom);
}
/**
* 个人用户的配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form
* @return void
*/
public static function personalConfig(Typecho_Widget_Helper_Form $form) {}
/**
* 输出CSS到页面头部
*
* @access public
*/
public static function header()
{
if (!self::$cssAdded) {
echo '';
self::$cssAdded = true;
}
}
/**
* 输出JS到页面底部
*
* @access public
*/
public static function footer()
{
if (!empty(self::$mapConfigs) && !self::$jsAdded) {
echo self::getMapCardScripts(self::$mapConfigs);
self::$jsAdded = true;
}
}
/**
* 初始化数据库路径
* 优先使用已存在的数据库文件,如果没有则生成新的随机名称
*
* @access private
* @return void
*/
private static function initDbPath()
{
$dbDir = __DIR__ . '/db';
// 确保目录存在
if (!is_dir($dbDir)) {
mkdir($dbDir, 0755, true);
}
// 检查是否已存在.db文件(优先使用已存在的)
$dbFiles = glob($dbDir . '/track_*.db');
if (!empty($dbFiles)) {
// 使用第一个找到的数据库文件(保持一致性)
self::$dbPath = $dbFiles[0];
} else {
// 没有找到现有数据库文件,生成新的随机名称
$randomStr = substr(md5(uniqid(rand(), true)), 0, 10);
self::$dbPath = $dbDir . '/track_' . $randomStr . '.db';
}
}
/**
* 初始化数据库
*
* @access private
* @return void
*/
private static function initDatabase()
{
// 确保数据库路径已初始化
if (empty(self::$dbPath)) {
self::initDbPath();
}
try {
// 创建SQLite数据库连接
$db = new PDO('sqlite:' . self::$dbPath);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 读取SQL初始化文件
$sqlFile = __DIR__ . '/db/init.sql';
if (file_exists($sqlFile)) {
$sql = file_get_contents($sqlFile);
$db->exec($sql);
} else {
// 如果没有SQL文件,手动创建表
$db->exec("CREATE TABLE IF NOT EXISTS plugin_track_footprint (
id INTEGER PRIMARY KEY AUTOINCREMENT,
latitude REAL NOT NULL,
longitude REAL NOT NULL,
name TEXT NOT NULL,
address TEXT,
location_type TEXT,
rating_level INTEGER,
categories TEXT,
review TEXT,
description TEXT,
article_cid INTEGER,
urlLabel TEXT,
url TEXT,
photos TEXT,
tags TEXT,
date DATETIME,
markerColor TEXT,
related_articles TEXT,
highlights TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)");
// 创建更新时间触发器
$db->exec("CREATE TRIGGER IF NOT EXISTS update_footprint_time
AFTER UPDATE ON plugin_track_footprint
BEGIN
UPDATE plugin_track_footprint SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END");
}
$db = null;
} catch (PDOException $e) {
throw new Typecho_Plugin_Exception('数据库初始化失败: ' . $e->getMessage());
}
}
/**
* 获取数据库连接
*
* @access public
* @return PDO
*/
public static function getDbConnection()
{
// 确保数据库路径已初始化
if (empty(self::$dbPath)) {
self::initDbPath();
}
// 如果数据库文件不存在,初始化数据库
if (!file_exists(self::$dbPath)) {
self::initDatabase();
}
try {
$db = new PDO('sqlite:' . self::$dbPath);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $db;
} catch (PDOException $e) {
throw new Typecho_Plugin_Exception('数据库连接失败: ' . $e->getMessage());
}
}
/**
* 通过文章CID获取文章信息和图片
*
* @access public
* @param integer $cid 文章CID
* @return array
*/
public static function getArticleInfo($cid)
{
$result = array(
'title' => '',
'link' => '',
'text' => '',
'images' => array(),
'tags' => array(),
'created' => ''
);
if (empty($cid)) {
return $result;
}
try {
// 获取文章信息,包括内容
$db = Typecho_Db::get();
$article = $db->fetchRow($db->select('title', 'slug', 'created', 'text')
->from('table.contents')
->where('cid = ?', $cid)
->where('type = ?', 'post')
->where('status = ?', 'publish')
->limit(1));
if ($article) {
$result['title'] = $article['title'];
$result['link'] = self::getPostUrlByCid($cid);
$result['text'] = $article['text'];
$result['created'] = $article['created'];
// 从文章内容中提取图片(限制4张)
if (!empty($article['text'])) {
$result['images'] = self::getPostImagesByCid($cid);
}
// 获取文章标签
$result['tags'] = self::getPostTagsByCid($cid);
}
} catch (Exception $e) {
error_log('MyTrack: 获取文章信息失败: ' . $e->getMessage());
}
return $result;
}
/**
* 通过文章CID获取文章URL
*
* @access public
* @param integer $cid 文章CID
* @return string|false
*/
public static function getPostUrlByCid($cid)
{
try {
// 获取文章信息,包括日期和分类
$db = Typecho_Db::get();
$row = $db->fetchRow($db->select('slug', 'type', 'created')
->from('table.contents')
->where('cid = ?', $cid)
->where('status = ?', 'publish'));
if (!$row) {
return false;
}
// 获取文章分类
$category = '';
$categories = $db->fetchAll($db->select('slug')
->from('table.metas')
->join('table.relationships', 'table.metas.mid = table.relationships.mid')
->where('table.relationships.cid = ?', $cid)
->where('table.metas.type = ?', 'category')
->order('table.metas.order', Typecho_Db::SORT_ASC));
if (!empty($categories)) {
$category = $categories[0]['slug'];
}
// 准备URL参数
$date = getdate($row['created']);
$params = array(
'cid' => $cid,
'slug' => $row['slug'],
'category' => $category,
'directory' => $category,
'year' => $date['year'],
'month' => str_pad($date['mon'], 2, '0', STR_PAD_LEFT),
'day' => str_pad($date['mday'], 2, '0', STR_PAD_LEFT)
);
// 获取自定义路径设置
$options = Typecho_Widget::widget('Widget_Options');
// 尝试获取不同的永久链接设置
$permalinkStructure = '';
// 方法1: 直接从options获取
if (isset($options->permalink)) {
$permalinkStructure = $options->permalink;
}
// 方法2: 尝试从routingTable获取
if (empty($permalinkStructure) && isset($options->routingTable)) {
$routingTable = $options->routingTable;
if (isset($routingTable['post'])) {
$permalinkStructure = $routingTable['post']['url'];
}
}
// 方法3: 尝试从Typecho_Router获取
if (empty($permalinkStructure)) {
$router = Typecho_Router::get();
$routes = $router->getRoutes();
if (isset($routes['post'])) {
$permalinkStructure = $routes['post']['url'];
}
}
// 如果没有找到自定义路径设置,使用默认方式
if (empty($permalinkStructure)) {
return Typecho_Router::url($row['type'], $row, $options->index);
}
// 替换路径中的占位符
$url = $permalinkStructure;
// 处理Typecho特殊格式的占位符
$url = preg_replace('/\[year:digital:4\]/', $params['year'], $url);
$url = preg_replace('/\[month:digital:2\]/', $params['month'], $url);
$url = preg_replace('/\[day:digital:2\]/', $params['day'], $url);
$url = preg_replace('/\[slug\]/', $params['slug'], $url);
$url = preg_replace('/\[cid\]/', $params['cid'], $url);
$url = preg_replace('/\[category\]/', $params['category'], $url);
$url = preg_replace('/\[directory\]/', $params['directory'], $url);
// 替换标准占位符
foreach ($params as $key => $value) {
$url = str_replace('{' . $key . '}', $value, $url);
}
// 确保URL以/开头
if (strpos($url, '/') !== 0) {
$url = '/' . $url;
}
// 拼接完整URL
$fullUrl = rtrim($options->siteUrl, '/') . $url;
return $fullUrl;
} catch (Exception $e) {
error_log("MyTrack: 获取文章 URL 失败: " . $e->getMessage());
}
return false;
}
/**
* 通过文章CID获取文章中的图片
*
* @access public
* @param integer $cid 文章CID
* @return array
*/
public static function getPostImagesByCid($cid)
{
$db = Typecho_Db::get();
$content = $db->fetchRow($db->select('text')
->from('table.contents')
->where('cid = ?', $cid)
->where('type = ?', 'post')
->where('status = ?', 'publish'));
if (!$content) {
return array();
}
// 尝试多种图片匹配模式
$images = array();
$text = $content['text'];
// 模式1: 标准img标签
preg_match_all('/]+src=[\'"]([^\'"]+)[\'"][^>]*>/i', $text, $matches1);
if (isset($matches1[1]) && !empty($matches1[1])) {
foreach($matches1[1] as $imageUrl) {
$images[] = self::processImageUrl($imageUrl);
}
}
// 模式2: markdown图片语法 
preg_match_all('/!\[[^\]]*\]\(([^)]+)\)/i', $text, $matches2);
if (isset($matches2[1]) && !empty($matches2[1])) {
foreach($matches2[1] as $imageUrl) {
$images[] = self::processImageUrl($imageUrl);
}
}
// 模式3: 直接匹配图片URL
preg_match_all('/https?:\/\/[^\s]+\.(jpg|jpeg|png|gif|webp|svg)(\?[^\s]*)?/i', $text, $matches3);
if (isset($matches3[0]) && !empty($matches3[0])) {
foreach($matches3[0] as $imageUrl) {
$images[] = self::processImageUrl($imageUrl);
}
}
// 模式4: 匹配相对路径的图片URL
preg_match_all('/[^\s]+\.(jpg|jpeg|png|gif|webp|svg)(\?[^\s]*)?/i', $text, $matches4);
if (isset($matches4[0]) && !empty($matches4[0])) {
foreach($matches4[0] as $imageUrl) {
if (strpos($imageUrl, 'http') !== 0) {
$images[] = self::processImageUrl($imageUrl);
}
}
}
// 去重并过滤空值
$images = array_filter(array_unique($images));
// 限制最多4张图片
$images = array_slice($images, 0, 4);
return $images;
}
/**
* 通过文章CID获取文章标签
*
* @access public
* @param integer $cid 文章CID
* @return array 标签数组
*/
public static function getPostTagsByCid($cid)
{
try {
$db = Typecho_Db::get();
// 查询文章的标签信息
$tags = $db->fetchAll($db->select('table.metas.name', 'table.metas.slug')
->from('table.metas')
->join('table.relationships', 'table.metas.mid = table.relationships.mid')
->where('table.relationships.cid = ?', $cid)
->where('table.metas.type = ?', 'tag')
->order('table.metas.order', Typecho_Db::SORT_ASC));
$tagNames = array();
if (!empty($tags)) {
foreach ($tags as $tag) {
$tagNames[] = $tag['name'];
}
}
return $tagNames;
} catch (Exception $e) {
error_log('MyTrack: 获取文章标签失败: ' . $e->getMessage());
return array();
}
}
/**
* 处理图片URL,将相对路径转换为绝对路径
*
* @access private
* @param string $imageUrl 原始图片URL
* @return string 处理后的图片URL
*/
private static function processImageUrl($imageUrl)
{
// 处理相对路径
if (strpos($imageUrl, 'http') !== 0) {
$options = Typecho_Widget::widget('Widget_Options');
$imageUrl = Typecho_Common::url($imageUrl, $options->siteUrl);
}
return $imageUrl;
}
/**
* 数据库迁移
* 更新数据库字段,添加新字段
*
* @access private
* @return void
*/
private static function migrateDatabase()
{
try {
// 确保数据库路径已初始化
if (empty(self::$dbPath)) {
self::initDbPath();
}
$db = new PDO('sqlite:' . self::$dbPath);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 检查表结构
$stmt = $db->prepare("PRAGMA table_info(plugin_track_footprint)");
$stmt->execute();
$columns = $stmt->fetchAll(PDO::FETCH_ASSOC);
$hasName = false;
$hasDate = false;
$hasUrl = false;
$hasUrlLabel = false;
$hasPhotos = false;
$hasCategories = false;
$hasMarkerColor = false;
$hasRelatedArticles = false;
$hasHighlights = false;
// 检查字段是否存在
foreach ($columns as $column) {
if ($column['name'] === 'name') {
$hasName = true;
}
if ($column['name'] === 'date') {
$hasDate = true;
}
if ($column['name'] === 'url') {
$hasUrl = true;
}
if ($column['name'] === 'urlLabel') {
$hasUrlLabel = true;
}
if ($column['name'] === 'photos') {
$hasPhotos = true;
}
if ($column['name'] === 'categories') {
$hasCategories = true;
}
if ($column['name'] === 'markerColor') {
$hasMarkerColor = true;
}
if ($column['name'] === 'related_articles') {
$hasRelatedArticles = true;
}
if ($column['name'] === 'highlights') {
$hasHighlights = true;
}
}
// 重命名字段:location 改为 name
if (!$hasName) {
// 检查是否存在 location 字段
$hasLocation = false;
foreach ($columns as $column) {
if ($column['name'] === 'location') {
$hasLocation = true;
break;
}
}
if ($hasLocation) {
// 重命名 location 为 name
$db->exec("ALTER TABLE plugin_track_footprint RENAME COLUMN location TO name");
} else {
// 直接添加 name 字段
$db->exec("ALTER TABLE plugin_track_footprint ADD COLUMN name TEXT");
}
}
// 重命名字段:travel_time 改为 date
if (!$hasDate) {
// 检查是否存在 travel_time 字段
$hasTravelTime = false;
foreach ($columns as $column) {
if ($column['name'] === 'travel_time') {
$hasTravelTime = true;
break;
}
}
if ($hasTravelTime) {
// 重命名 travel_time 为 date
$db->exec("ALTER TABLE plugin_track_footprint RENAME COLUMN travel_time TO date");
} else {
// 直接添加 date 字段
$db->exec("ALTER TABLE plugin_track_footprint ADD COLUMN date DATETIME");
}
}
// 重命名字段:article_link 改为 url
if (!$hasUrl) {
// 检查是否存在 article_link 字段
$hasArticleLink = false;
foreach ($columns as $column) {
if ($column['name'] === 'article_link') {
$hasArticleLink = true;
break;
}
}
if ($hasArticleLink) {
// 重命名 article_link 为 url
$db->exec("ALTER TABLE plugin_track_footprint RENAME COLUMN article_link TO url");
} else {
// 直接添加 url 字段
$db->exec("ALTER TABLE plugin_track_footprint ADD COLUMN url TEXT");
}
}
// 重命名字段:article_title 改为 urlLabel
if (!$hasUrlLabel) {
// 检查是否存在 article_title 字段
$hasArticleTitle = false;
foreach ($columns as $column) {
if ($column['name'] === 'article_title') {
$hasArticleTitle = true;
break;
}
}
if ($hasArticleTitle) {
// 重命名 article_title 为 urlLabel
$db->exec("ALTER TABLE plugin_track_footprint RENAME COLUMN article_title TO urlLabel");
} else {
// 直接添加 urlLabel 字段
$db->exec("ALTER TABLE plugin_track_footprint ADD COLUMN urlLabel TEXT");
}
}
// 重命名字段:image_links 改为 photos
if (!$hasPhotos) {
// 检查是否存在 image_links 字段
$hasImageLinks = false;
foreach ($columns as $column) {
if ($column['name'] === 'image_links') {
$hasImageLinks = true;
break;
}
}
if ($hasImageLinks) {
// 重命名 image_links 为 photos
$db->exec("ALTER TABLE plugin_track_footprint RENAME COLUMN image_links TO photos");
} else {
// 直接添加 photos 字段
$db->exec("ALTER TABLE plugin_track_footprint ADD COLUMN photos TEXT");
}
}
// 重命名字段:status 改为 categories
if (!$hasCategories) {
// 检查是否存在 status 字段
$hasStatus = false;
foreach ($columns as $column) {
if ($column['name'] === 'status') {
$hasStatus = true;
break;
}
}
if ($hasStatus) {
// 重命名 status 为 categories
$db->exec("ALTER TABLE plugin_track_footprint RENAME COLUMN status TO categories");
} else {
// 直接添加 categories 字段
$db->exec("ALTER TABLE plugin_track_footprint ADD COLUMN categories TEXT");
}
}
// 添加新字段:markerColor
if (!$hasMarkerColor) {
$db->exec("ALTER TABLE plugin_track_footprint ADD COLUMN markerColor TEXT");
}
// 添加新字段:related_articles
if (!$hasRelatedArticles) {
$db->exec("ALTER TABLE plugin_track_footprint ADD COLUMN related_articles TEXT");
}
// 添加新字段:highlights
if (!$hasHighlights) {
$db->exec("ALTER TABLE plugin_track_footprint ADD COLUMN highlights TEXT");
}
$db = null;
} catch (PDOException $e) {
error_log('MyTrack数据库迁移失败: ' . $e->getMessage());
}
}
/**
* 获取数据库文件路径
*
* @access public
* @return string
*/
public static function getDbPath()
{
if (empty(self::$dbPath)) {
self::initDbPath();
}
return self::$dbPath;
}
/**
* 通过足迹ID获取足迹信息
*
* @access public
* @param integer $id 足迹ID
* @return array|null 足迹信息或null
*/
public static function getFootprintById($id)
{
if (empty($id)) {
return null;
}
try {
$db = self::getDbConnection();
$stmt = $db->prepare("SELECT * FROM plugin_track_footprint WHERE id = ?");
$stmt->execute(array($id));
$footprint = $stmt->fetch(PDO::FETCH_ASSOC);
if ($footprint) {
// 处理分类字段
if (!empty($footprint['categories'])) {
$footprint['categories'] = explode(',', $footprint['categories']);
} else {
$footprint['categories'] = array();
}
// 处理亮点字段
if (!empty($footprint['highlights'])) {
$footprint['highlights'] = explode(',', $footprint['highlights']);
} else {
$footprint['highlights'] = array();
}
// 获取文章信息(如果有关联文章)
if (!empty($footprint['article_cid'])) {
$articleInfo = self::getArticleInfo($footprint['article_cid']);
if (!empty($articleInfo['title']) && empty($footprint['urlLabel'])) {
$footprint['urlLabel'] = $articleInfo['title'];
}
if (!empty($articleInfo['link']) && empty($footprint['url'])) {
$footprint['url'] = $articleInfo['link'];
}
}
// 处理关联文章信息
if (!empty($footprint['related_articles'])) {
$footprint['related_articles_info'] = self::getRelatedArticlesInfo($footprint['related_articles']);
}
return $footprint;
}
} catch (Exception $e) {
error_log('MyTrack: 获取足迹信息失败: ' . $e->getMessage());
}
return null;
}
/**
* 获取关联文章信息
*
* @access public
* @param string $relatedArticles 关联文章ID字符串(逗号分隔)
* @return array 关联文章信息数组
*/
public static function getRelatedArticlesInfo($relatedArticles)
{
$result = array();
if (empty($relatedArticles)) {
return $result;
}
$articleIds = explode(',', $relatedArticles);
$articleIds = array_map('trim', $articleIds);
$articleIds = array_filter($articleIds);
foreach ($articleIds as $cid) {
if (is_numeric($cid)) {
$articleInfo = self::getArticleInfo($cid);
if ($articleInfo) {
$result[$cid] = array(
'title' => $articleInfo['title'],
'link' => $articleInfo['link'],
'cid' => $cid
);
}
}
}
return $result;
}
/**
* 解析文章内容中的地图短代码
*
* @access public
* @param string $content 文章内容
* @param Widget_Abstract_Contents $widget 小部件对象
* @param array $lastResult 上次解析结果
* @return string 处理后的内容
*/
public static function parseMapShortcode($content, $widget, $lastResult)
{
$content = empty($lastResult) ? $content : $lastResult;
// 匹配 {map-数字} 格式的短代码
$pattern = '/\{map-(\d+)\}/i';
if (preg_match_all($pattern, $content, $matches)) {
// 获取当前文章的CID
$currentCid = isset($widget->cid) ? $widget->cid : 0;
// 收集所有需要渲染的地图ID
$mapIds = $matches[1];
$mapIndex = 0;
foreach ($mapIds as $index => $mapId) {
// 渲染单个地图卡片
list($mapHtml, $config) = self::renderSingleMapCard($mapId, $mapIndex);
if ($mapHtml) {
$content = str_replace($matches[0][$index], $mapHtml, $content);
if ($config) {
self::$mapConfigs[] = $config;
}
// 在短代码解析时同步更新关联文章
if ($currentCid > 0) {
self::syncRelatedArticles($mapId, $currentCid);
}
} else {
$content = str_replace($matches[0][$index], '