Files
MyTrack/Plugin.php
2026-02-23 19:45:59 +08:00

1855 lines
66 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}
}