Files
MyTrack/Plugin.php

1855 lines
66 KiB
PHP
Raw Permalink Normal View History

2026-02-23 19:45:59 +08:00
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* 地点卡片&足迹地图
*
* @package MyTrack
* @author 石头厝
* @version 1.1.3
* @link https://www.shitoucuo.com/
*/
class MyTrack_Plugin implements Typecho_Plugin_Interface
{
/**
* 数据库文件路径
*/
private static $dbPath;
/**
* 是否已添加CSS样式
*/
private static $cssAdded = false;
/**
* 是否已添加JS脚本
*/
private static $jsAdded = false;
/**
* 地图配置数组
*/
private static $mapConfigs = array();
/**
* 获取插件版本号
*
* @access public
* @return string 版本号
*/
public static function getVersion()
{
// 读取插件文件头部注释中的版本信息
$pluginFile = __FILE__;
$content = file_get_contents($pluginFile);
// 使用正则表达式匹配@version标签
if (preg_match('/@version\s+([0-9]+\.[0-9]+\.[0-9]+)/', $content, $matches)) {
return $matches[1];
}
// 如果无法从注释中获取,返回默认版本
return '1.1.3';
}
/**
* 激活插件方法
*
* @access public
* @return void
* @throws Typecho_Plugin_Exception
*/
public static function activate()
{
// 初始化数据库路径
self::initDbPath();
// 创建数据库表
self::initDatabase();
// 检查并执行数据库迁移
self::migrateDatabase();
// 添加后台管理菜单 - 使用类型1管理菜单与UserCard插件保持一致
Helper::addPanel(3, 'MyTrack/Manage.php', '足迹管理', '足迹管理', 'administrator');
// 添加动作处理
Helper::addAction('track', 'MyTrack_Action');
// 注册路由
Helper::addRoute('track_action', '/action/track', 'MyTrack_Action', 'action');
// 注册短代码解析
Typecho_Plugin::factory('Widget_Abstract_Contents')->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 '<style>' . self::getMapCardStyles() . '</style>';
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('/<img[^>]+src=[\'"]([^\'"]+)[\'"][^>]*>/i', $text, $matches1);
if (isset($matches1[1]) && !empty($matches1[1])) {
foreach($matches1[1] as $imageUrl) {
$images[] = self::processImageUrl($imageUrl);
}
}
// 模式2: markdown图片语法 ![alt](url)
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], '<div class="mytrack-error">足迹ID ' . $mapId . ' 不存在</div>', $content);
}
$mapIndex++;
}
}
return $content;
}
/**
* 渲染单个地图卡片HTML
*
* @access private
* @param integer $mapId 足迹ID
* @param integer $index 索引
* @return array [HTML字符串, 配置数组]
*/
private static function renderSingleMapCard($mapId, $index)
{
// 获取足迹信息
$footprint = self::getFootprintById($mapId);
if (!$footprint) {
return array(null, null);
}
// 获取插件配置
$options = Typecho_Widget::widget('Widget_Options')->plugin('MyTrack');
// 获取配置
$cardMapWidth = isset($options->cardMapWidth) ? $options->cardMapWidth : '40%';
$cardMapHeight = isset($options->cardMapHeight) ? $options->cardMapHeight : '350px';
// 处理分类显示
$categoriesHtml = '';
if (!empty($footprint['categories'])) {
foreach ($footprint['categories'] as $category) {
$category = trim($category);
$class = '';
$text = '';
switch($category) {
case 'visited':
$class = 'mytrack-categories-visited-badge';
$text = '已玩';
break;
case 'want':
$class = 'mytrack-categories-want-badge';
$text = '向往';
break;
case 'plan':
$class = 'mytrack-categories-plan-badge';
$text = '计划';
break;
default:
$class = 'mytrack-categories-badge';
$text = $category;
}
$categoriesHtml .= '<span class="mytrack-categories-badge ' . $class . '">' . $text . '</span>';
}
}
// 处理星级显示
$ratingHtml = '';
if (!empty($footprint['rating_level']) && $footprint['rating_level'] > 0) {
$ratingHtml = '<div class="mytrack-rating-display">';
for ($i = 1; $i <= 5; $i++) {
if ($i <= $footprint['rating_level']) {
$ratingHtml .= '<span class="mytrack-rating-star">★</span>';
} else {
$ratingHtml .= '<span class="mytrack-rating-empty-star">☆</span>';
}
}
$ratingHtml .= '</div>';
}
// 处理亮点显示
$highlightsHtml = '';
if (!empty($footprint['highlights'])) {
$highlightsHtml = '<div class="mytrack-card-highlights"><strong>亮点:</strong>';
foreach ($footprint['highlights'] as $highlight) {
$highlight = trim($highlight);
if ($highlight) {
$highlightsHtml .= '<span class="mytrack-highlight-badge">' . htmlspecialchars($highlight) . '</span> ';
}
}
$highlightsHtml .= '</div>';
}
// 处理关联文章显示
$relatedArticlesHtml = '';
if (!empty($footprint['related_articles_info'])) {
$relatedArticlesHtml .= '<div class="mytrack-related-articles-section">';
$relatedArticlesHtml .= '<strong>这些文章也提到了本地点:</strong><div class="mytrack-related-articles-list">';
foreach ($footprint['related_articles_info'] as $articleInfo) {
if (!empty($articleInfo['title']) && !empty($articleInfo['link'])) {
$relatedArticlesHtml .= '<div class="mytrack-related-article-item"><a href="' . $articleInfo['link'] . '" target="_blank">' . htmlspecialchars($articleInfo['title']) . '</a></div>';
}
}
$relatedArticlesHtml .= '</div></div>';
}
// 生成唯一ID
$uniqueId = 'map_' . $mapId . '_' . $index;
$mapCardId = 'mytrack-card-' . $uniqueId;
$mapContainerId = $mapCardId . '-map';
// 构建HTML
$html = '
<div class="mytrack-card" id="' . $mapCardId . '">
<div class="mytrack-card-content">
<div class="mytrack-card-info">
<div class="mytrack-card-header">
<h3 class="mytrack-card-title">' . htmlspecialchars($footprint['name']) . '</h3>
</div>';
if ($footprint['address']) {
$html .= '<div class="mytrack-card-address"><strong>地址:</strong>' . htmlspecialchars($footprint['address']) . '</div>';
}
if ($footprint['location_type']) {
$html .= '<div class="mytrack-card-type"><strong>类型:</strong>' . htmlspecialchars($footprint['location_type']) . '</div>';
}
if ($categoriesHtml) {
$html .= '<div class="mytrack-card-categories"><strong>分类:</strong>' . $categoriesHtml . '</div>';
}
if ($ratingHtml) {
$html .= '<div class="mytrack-card-rating"><strong>推荐:</strong>' . $ratingHtml . '</div>';
}
$html .= $highlightsHtml;
if ($footprint['review']) {
$html .= '<div class="mytrack-card-review"><strong>简评:</strong>' . nl2br(htmlspecialchars($footprint['review'])) . '</div>';
}
$html .= $relatedArticlesHtml;
$html .= '</div>
<div class="mytrack-card-map" id="' . $mapContainerId . '" data-map-id="' . $uniqueId . '" style="width: ' . $cardMapWidth . '; height: ' . $cardMapHeight . ';"></div>
</div>
</div>';
// 准备地图配置
$apiKey = isset($options->jsApiKey) ? $options->jsApiKey : '';
$cardMapZoom = isset($options->cardMapZoom) ? intval($options->cardMapZoom) : 16;
$mapTheme = isset($options->mapTheme) ? $options->mapTheme : 'normal';
// 修复正确获取markerColor和对应的颜色值
$markerColor = isset($footprint['markerColor']) && !empty($footprint['markerColor']) ? trim($footprint['markerColor']) : '';
$colorValue = self::getMarkerColorValue($markerColor);
$config = array(
'containerId' => $mapContainerId,
'mapId' => $mapId,
'name' => $footprint['name'],
'latitude' => floatval($footprint['latitude']),
'longitude' => floatval($footprint['longitude']),
'markerColor' => $markerColor,
'markerColorValue' => $colorValue,
'zoom' => $cardMapZoom,
'theme' => $mapTheme,
'apiKey' => $apiKey
);
return array($html, $config);
}
/**
* 根据markerColor标识获取具体的颜色值
*
* @access private
* @param string $markerColor 标记颜色标识
* @return string 具体的颜色值
*/
private static function getMarkerColorValue($markerColor)
{
// 如果markerColor为空返回默认红色
if (empty($markerColor) || !is_string($markerColor)) {
return '#ff0000';
}
$markerColor = trim($markerColor);
if ($markerColor === '') {
return '#ff0000';
}
// 颜色映射表
$colorMap = array(
'sunset' => 'linear-gradient(135deg, rgb(255, 179, 71), rgb(255, 111, 97))',
'ocean' => 'linear-gradient(135deg, rgb(6, 190, 182), rgb(72, 177, 191))',
'forest' => 'linear-gradient(135deg, rgb(94, 231, 223), rgb(57, 163, 124))',
'amber' => 'linear-gradient(135deg, rgb(246, 211, 101), rgb(253, 160, 133))',
'violet' => 'linear-gradient(135deg, rgb(161, 140, 209), rgb(251, 194, 235))',
'citrus' => 'linear-gradient(135deg, rgb(253, 251, 143), rgb(161, 255, 206))'
);
// 如果找到对应的颜色,返回具体的颜色值,否则返回默认的红色
if (isset($colorMap[$markerColor])) {
return $colorMap[$markerColor];
}
return '#ff0000'; // 默认红色
}
/**
* 获取地图卡片CSS样式 - 修复标点颜色问题
*
* @access private
* @return string CSS样式
*/
private static function getMapCardStyles()
{
return '
/* MyTrack 插件专用样式 - 防止与其他插件冲突 */
.mytrack-card {
border: 1px solid #e8e8e8;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
background: white;
margin: 20px 0;
}
.mytrack-card-content {
display: flex;
}
.mytrack-card-info {
flex: 1;
padding: 20px;
min-width: 0;
}
.mytrack-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 15px;
}
.mytrack-card-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333 !important; /* 修复标题颜色,确保显示 */
}
.mytrack-card-categories {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
}
.mytrack-categories-badge {
font-size: 14px;
background: #f0f0f0;
color: #666;
}
.mytrack-categories-visited-badge {
background: #fff;
color: #666;
}
.mytrack-categories-want-badge {
background: #e3f2fd;
color: #1565c0;
}
.mytrack-categories-plan-badge {
background: #fff3e0;
color: #ef6c00;
}
.mytrack-card-info > div {
margin-bottom: 10px;
font-size: 14px;
line-height: 1.5;
}
.mytrack-card-info strong {
color: #555;
font-weight: 600;
min-width: 40px;
display: inline-block;
}
.mytrack-card-address,
.mytrack-card-type,
.mytrack-card-review {
color: #666;
}
.mytrack-card-highlights {
color: #666;
}
.mytrack-highlight-badge {
background-color: #e8f5e9;
color: #2e7d32;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
margin-right: 5px;
display: inline-block;
}
.mytrack-card-rating {
display: flex;
align-items: center;
}
.mytrack-rating-display {
display: inline-flex;
align-items: center;
}
.mytrack-rating-star {
color: #ffc107;
font-size: 14px;
margin-right: 2px;
}
.mytrack-rating-empty-star {
color: #ddd;
font-size: 14px;
margin-right: 2px;
}
.mytrack-related-articles-section {
margin-top: 15px;
padding-top: 10px;
border-top: 1px dashed #eee;
}
.mytrack-related-articles-list {
margin: 5px 0 0 0;
padding: 0;
}
.mytrack-related-article-item {
margin-bottom: 5px;
font-size: 13px;
}
.mytrack-related-article-item a {
color: #3b82f6;
text-decoration: none;
}
.mytrack-related-article-item a:hover {
text-decoration: underline;
}
.mytrack-card-map {
position: relative;
background: #f5f5f5;
}
/* 修复地图标记点样式 - 移除有问题的inherit规则 */
.mytrack-marker-custom {
width: 20px !important;
height: 20px !important;
border-radius: 50% !important;
box-shadow: 0 2px 5px rgba(0,0,0,0.3) !important;
cursor: pointer !important;
transition: transform 0.2s ease-out !important;
border: 2px solid #fff !important;
position: relative !important;
z-index: 100 !important;
display: block !important;
background: #ff0000 !important; /* 默认红色会被JS内联样式覆盖 */
}
.mytrack-marker-custom:hover {
transform: scale(1.05) !important;
}
/* 自定义标记颜色类 */
.mytrack-marker-sunset { background: linear-gradient(135deg, rgb(255, 179, 71), rgb(255, 111, 97)) !important; }
.mytrack-marker-ocean { background: linear-gradient(135deg, rgb(6, 190, 182), rgb(72, 177, 191)) !important; }
.mytrack-marker-forest { background: linear-gradient(135deg, rgb(94, 231, 223), rgb(57, 163, 124)) !important; }
.mytrack-marker-amber { background: linear-gradient(135deg, rgb(246, 211, 101), rgb(253, 160, 133)) !important; }
.mytrack-marker-violet { background: linear-gradient(135deg, rgb(161, 140, 209), rgb(251, 194, 235)) !important; }
.mytrack-marker-citrus { background: linear-gradient(135deg, rgb(253, 251, 143), rgb(161, 255, 206)) !important; }
/* 隐藏高德地图版权信息 */
.mytrack-card-map .amap-logo,
.mytrack-card-map .amap-copyright {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
}
/* 确保所有地图容器中的版权信息都被隐藏 */
.mytrack-card-map div[style*="amap"] .amap-logo,
.mytrack-card-map div[style*="amap"] .amap-copyright,
.mytrack-card-map .amap-container .amap-logo,
.mytrack-card-map .amap-container .amap-copyright {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
}
.mytrack-error {
padding: 20px;
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
border-radius: 4px;
text-align: center;
}
/* 深色模式适配 */
.dark .mytrack-card {
background: #1a1a1a;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.dark .mytrack-card-title {
color: #fff !important; /* 修复深色模式标题颜色 */
}
.dark .mytrack-card-info strong {
color: #b5b5b5;
}
.dark .mytrack-card-address,
.dark .mytrack-card-type,
.dark .mytrack-card-review,
.dark .mytrack-card-highlights {
color: #ccc;
}
.dark .mytrack-categories-badge {
background: #1a1a1a;
color: #d1d5db;
}
.dark .mytrack-highlight-badge {
color: #d1d5db;
background:#dc2626;
}
.dark .mytrack-related-articles-section {
border-top-color: rgba(255, 255, 255, 0.1);
}
/* 深色模式下标记点边框调整为深色 */
.dark .mytrack-marker-custom {
border: 2px solid #fff !important;
}
/* 响应式设计 */
@media (max-width: 768px) {
.mytrack-card-content {
flex-direction: column;
}
.mytrack-card-map {
width: 100% !important;
height: 300px !important;
border-top: 1px solid #e8e8e8;
}
.dark .mytrack-card-map {
border-top-color: rgba(255, 255, 255, 0.1);
}
}
';
}
/**
* 获取地图卡片JS脚本 - 修复标点颜色问题
*
* @access private
* @param array $mapConfigs 地图配置数组
* @return string JS脚本
*/
private static function getMapCardScripts($mapConfigs)
{
if (empty($mapConfigs)) {
return '';
}
$configsJson = json_encode($mapConfigs);
$apiKey = !empty($mapConfigs[0]['apiKey']) ? $mapConfigs[0]['apiKey'] : '';
// 如果API密钥为空直接返回错误提示
if (empty($apiKey)) {
return '
<script>
(function() {
var configs = ' . $configsJson . ';
for (var i = 0; i < configs.length; i++) {
var mapElement = document.getElementById(configs[i].containerId);
if (mapElement) {
mapElement.innerHTML = \'<div style="padding:20px;color:#721c24;background:#f8d7da;text-align:center;">请在高德地图官网申请API密钥并配置</div>\';
}
}
})();
</script>';
}
return '
<script>
(function() {
// 等待页面完全加载
function initMyTrackMaps() {
var configs = ' . $configsJson . ';
if (!configs || configs.length === 0) return;
// 加载高德地图API
if (typeof AMap === "undefined") {
var script = document.createElement("script");
script.src = "https://webapi.amap.com/maps?v=2.0&key=' . $apiKey . '&plugin=AMap.Scale";
script.onload = function() {
setTimeout(createMaps, 100);
};
script.onerror = function() {
showAllErrors("地图API加载失败请检查API密钥");
};
document.head.appendChild(script);
} else {
setTimeout(createMaps, 100);
}
function createMaps() {
if (typeof AMap === "undefined") {
showAllErrors("地图API未加载");
return;
}
for (var i = 0; i < configs.length; i++) {
createMap(configs[i]);
}
}
function createMap(config) {
var mapElement = document.getElementById(config.containerId);
if (!mapElement) return;
try {
// 检查容器尺寸
if (mapElement.offsetWidth === 0 || mapElement.offsetHeight === 0) {
setTimeout(function() { createMap(config); }, 100);
return;
}
// 确定主题
var isDark = document.documentElement.classList.contains("dark");
var mapStyle = isDark ? "amap://styles/dark" : "amap://styles/" + (config.theme || "normal");
// 创建地图时隐藏logo和版权信息
var map = new AMap.Map(mapElement, {
zoom: config.zoom || 16,
center: [config.longitude, config.latitude],
mapStyle: mapStyle,
resizeEnable: true,
logo: false // 禁用logo
});
// 添加标记点 - 修复优先使用后台设置的markerColor
var markerContent = \'\';
if (config.markerColor && config.markerColor.trim() !== \'\') {
// 有自定义标记颜色,使用对应的颜色类
markerContent = \'<div class="mytrack-marker-custom mytrack-marker-\' + config.markerColor + \'"></div>\';
} else {
// 没有自定义颜色,使用默认红色
markerContent = \'<div class="mytrack-marker-custom" style="background: \' + (config.markerColorValue || \'#ff0000\') + \' !important;"></div>\';
}
var marker = new AMap.Marker({
position: [config.longitude, config.latitude],
content: markerContent,
title: config.name || "地点",
offset: new AMap.Pixel(-10, -10)
});
map.add(marker);
// 为深色模式调整标记点边框
if (isDark) {
try {
var markerElement = mapElement.querySelector(".mytrack-marker-custom");
if (markerElement) {
markerElement.style.border = "2px solid #333 !important";
}
} catch (e) {}
}
// 创建后强制隐藏版权信息
hideCopyrightInfo(mapElement);
// 定期检查并隐藏版权信息
var hideInterval = setInterval(function() {
hideCopyrightInfo(mapElement);
}, 500);
// 5秒后停止检查
setTimeout(function() {
clearInterval(hideInterval);
}, 5000);
// 监听窗口大小变化
window.addEventListener("resize", function() {
setTimeout(function() {
map && map.resize();
}, 100);
});
// 监听主题变化
if (typeof MutationObserver !== "undefined") {
var observer = new MutationObserver(function() {
var isDarkNow = document.documentElement.classList.contains("dark");
var newMapStyle = isDarkNow ? "amap://styles/dark" : "amap://styles/" + (config.theme || "normal");
try {
map.setMapStyle(newMapStyle);
// 主题变化时调整标记点边框
var markerElement = mapElement.querySelector(".mytrack-marker-custom");
if (markerElement) {
markerElement.style.border = isDarkNow ? "2px solid #333 !important" : "2px solid #fff !important";
}
} catch (e) {}
});
observer.observe(document.documentElement, { attributes: true });
}
} catch (e) {
console.error("MyTrack: 创建地图失败", e);
if (mapElement) {
mapElement.innerHTML = \'<div style="padding:20px;color:#721c24;background:#f8d7da;text-align:center;">地图加载失败</div>\';
}
}
}
// 隐藏版权信息的函数
function hideCopyrightInfo(mapElement) {
if (!mapElement) return;
try {
// 方法1: 直接查找并隐藏
var logos = mapElement.querySelectorAll(".amap-logo, .amap-copyright");
logos.forEach(function(logo) {
if (logo) {
logo.style.display = "none";
logo.style.visibility = "hidden";
logo.style.opacity = "0";
}
});
// 方法2: 查找所有可能包含版权信息的元素
var allElements = mapElement.querySelectorAll("*");
allElements.forEach(function(el) {
if (el.innerHTML && (el.innerHTML.includes("高德") || el.innerHTML.includes("Amap"))) {
el.style.display = "none";
el.style.visibility = "hidden";
el.style.opacity = "0";
}
});
// 方法3: 查找具有特定样式的元素
var styledElements = mapElement.querySelectorAll("[style*=\'position: absolute\']");
styledElements.forEach(function(el) {
var style = el.getAttribute("style") || "";
if (style.includes("bottom") && (style.includes("0px") || style.includes("right"))) {
el.style.display = "none";
el.style.visibility = "hidden";
el.style.opacity = "0";
}
});
} catch (e) {
// 忽略错误
}
}
function showAllErrors(message) {
for (var i = 0; i < configs.length; i++) {
var mapElement = document.getElementById(configs[i].containerId);
if (mapElement) {
mapElement.innerHTML = \'<div style="padding:20px;color:#721c24;background:#f8d7da;text-align:center;">\' + message + \'</div>\';
}
}
}
}
// 页面加载完成后初始化
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initMyTrackMaps);
} else {
setTimeout(initMyTrackMaps, 300);
}
})();
</script>';
}
/**
* 原来的renderMapCard方法保持兼容性- 修复主题模板调用时的标记颜色问题
*
* @access public
* @param integer $mapId 足迹ID
* @return string 地图卡片HTML
*/
public static function renderMapCard($mapId)
{
static $index = 0;
$currentIndex = $index++;
// 直接调用renderSingleMapCard来确保配置正确
list($mapHtml, $config) = self::renderSingleMapCard($mapId, $currentIndex);
if (!$mapHtml) {
return '<div class="mytrack-error">足迹ID ' . $mapId . ' 不存在</div>';
}
// 确保配置正确添加到mapConfigs数组中
if ($config) {
self::$mapConfigs[] = $config;
}
return $mapHtml;
}
/**
* 同步关联文章
*
* @access private
* @param int $footprintId 足迹ID
* @param int $articleCid 文章CID
* @return bool 是否成功
*/
private static function syncRelatedArticles($footprintId, $articleCid)
{
try {
$db = self::getDbConnection();
// 获取当前的关联文章
$stmt = $db->prepare("SELECT related_articles FROM plugin_track_footprint WHERE id = ?");
$stmt->execute(array($footprintId));
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
$currentRelatedArticles = $result['related_articles'];
$relatedArticlesArray = array();
// 解析当前的关联文章
if (!empty($currentRelatedArticles)) {
$relatedArticlesArray = explode(',', $currentRelatedArticles);
$relatedArticlesArray = array_map('trim', $relatedArticlesArray);
$relatedArticlesArray = array_filter($relatedArticlesArray);
}
// 添加新的文章CID如果不存在
if (!in_array($articleCid, $relatedArticlesArray)) {
$relatedArticlesArray[] = $articleCid;
$newRelatedArticles = implode(',', $relatedArticlesArray);
// 更新数据库
$updateStmt = $db->prepare("UPDATE plugin_track_footprint SET related_articles = ? WHERE id = ?");
$updateStmt->execute(array($newRelatedArticles, $footprintId));
return true;
}
}
} catch (Exception $e) {
error_log('MyTrack: 同步关联文章失败: ' . $e->getMessage());
}
return false;
}
/**
* 文章保存时的处理
*
* @access public
* @param array $content 文章数据
* @return array
*/
public static function onPostSave($content)
{
// 获取当前文章CID
$cid = isset($content['cid']) ? $content['cid'] : 0;
if ($cid && isset($content['text'])) {
// 解析文章内容中的地图短代码并验证足迹ID是否存在
$pattern = '/\{map-(\d+)\}/i';
if (preg_match_all($pattern, $content['text'], $matches)) {
foreach ($matches[1] as $mapId) {
$footprint = self::getFootprintById($mapId);
if ($footprint) {
// 更新足迹的关联文章字段
self::syncRelatedArticles($mapId, $cid);
}
}
}
}
return $content;
}
/**
* 文章写入时的处理
*
* @access public
* @param array $content 文章数据
* @return array
*/
public static function onPostWrite($content)
{
return self::onPostSave($content);
}
}