This commit is contained in:
LinRuiqi
2025-08-03 10:16:48 +08:00
parent 8550605556
commit 4299b8de55
6 changed files with 724 additions and 0 deletions

378
includes/admin-page.php Normal file
View File

@@ -0,0 +1,378 @@
<?php
// 添加管理菜单
add_action('admin_menu', 'flm_add_admin_menu');
function flm_add_admin_menu() {
add_menu_page(
'友情链接管理',
'友情链接',
'manage_options',
'friend-links-manager',
'flm_admin_page',
'dashicons-admin-links',
30
);
}
// 自动获取favicon
function flm_get_favicon($url) {
$domain = parse_url($url, PHP_URL_HOST);
if ($domain) {
$domain = preg_replace('/^www\./', '', $domain);
return 'https://favicon.im/' . urlencode($domain);
}
return '';
}
// 极端严格的URL清理用于导出
function flm_sanitize_export_url($url) {
if (empty($url)) return '';
// 先解码HTML实体
$url = html_entity_decode($url);
// 移除所有HTML标签和特殊字符
$url = strip_tags($url);
$url = str_replace(array("\r", "\n", "\t", "\\", "'", '"', " ", "<", ">"), '', $url);
// 使用正则提取纯URL
if (preg_match('/(https?:\/\/[^\s\"\'<>]+)/i', $url, $matches)) {
$url = $matches[1];
}
// 最终过滤和验证
$url = filter_var($url, FILTER_SANITIZE_URL);
if (!preg_match('/^https?:\/\//i', $url)) {
$url = 'http://' . ltrim($url, '/');
}
return rtrim($url, '/');
}
// 极端严格的文本清理(用于导出)
function flm_sanitize_export_text($text) {
if (empty($text)) return '';
// 彻底移除所有HTML/JavaScript代码
$text = html_entity_decode($text);
$text = strip_tags($text);
$text = str_replace(array("\r", "\n", "\t", "\\", "'", '"', "<", ">"), '', $text);
return sanitize_text_field($text);
}
// 管理页面内容
function flm_admin_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'friend_links';
// 显示警告信息
echo '<div class="notice notice-warning"><p><strong>警告:</strong>禁用该插件将删除所有链接数据请在禁用前导出包含链接的CSV文件</p></div>';
// 处理表单提交
if (isset($_POST['flm_action'])) {
check_admin_referer('flm_nonce');
switch ($_POST['flm_action']) {
case 'add_link':
if (!empty($_POST['name']) && !empty($_POST['url'])) {
$name = sanitize_text_field($_POST['name']);
$url = esc_url_raw($_POST['url']);
$icon = !empty($_POST['icon']) ? esc_url_raw($_POST['icon']) : flm_get_favicon($url);
$existing = $wpdb->get_row($wpdb->prepare(
"SELECT id FROM $table_name WHERE url = %s",
$url
));
if (!$existing) {
$wpdb->insert($table_name, array(
'name' => $name,
'url' => $url,
'icon' => $icon,
'sort_order' => 0
));
echo '<div class="notice notice-success"><p>链接添加成功!</p></div>';
} else {
echo '<div class="notice notice-error"><p>该URL的链接已存在</p></div>';
}
}
break;
case 'update_links':
if (!empty($_POST['link_ids'])) {
foreach ($_POST['link_ids'] as $index => $id) {
$wpdb->update($table_name, array(
'name' => sanitize_text_field($_POST['link_names'][$index]),
'url' => esc_url_raw($_POST['link_urls'][$index]),
'icon' => esc_url_raw($_POST['link_icons'][$index]),
'sort_order' => $index
), array('id' => intval($id)));
}
echo '<div class="notice notice-success"><p>链接更新成功!</p></div>';
}
break;
case 'delete_link':
if (!empty($_POST['link_id'])) {
$wpdb->delete($table_name, array('id' => intval($_POST['link_id'])));
echo '<div class="notice notice-success"><p>链接删除成功!</p></div>';
}
break;
case 'export_links':
$links = $wpdb->get_results("SELECT name, url, icon FROM $table_name ORDER BY sort_order ASC");
// 清除所有输出缓冲
while (ob_get_level()) {
ob_end_clean();
}
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=friend-links-export-' . date('Y-m-d') . '.csv');
header('Pragma: no-cache');
header('Expires: 0');
$output = fopen('php://output', 'w');
// 添加BOM头解决中文乱码
fwrite($output, chr(0xEF).chr(0xBB).chr(0xBF));
// 只写入三列标题
fputcsv($output, array(
'网站名称',
'网站URL',
'图标URL'
));
foreach ($links as $link) {
// 对每列数据应用极端清理
fputcsv($output, array(
flm_sanitize_export_text($link->name),
flm_sanitize_export_url($link->url),
flm_sanitize_export_url($link->icon)
));
}
fclose($output);
exit;
break;
case 'import_links':
if (!empty($_FILES['import_file']['tmp_name'])) {
$file = $_FILES['import_file']['tmp_name'];
$handle = fopen($file, 'r');
$import_count = 0;
$update_count = 0;
$error_count = 0;
// 跳过标题行
fgetcsv($handle);
while (($data = fgetcsv($handle)) !== false) {
if (count($data) < 2 || empty($data[0]) || empty($data[1])) {
$error_count++;
continue;
}
$name = sanitize_text_field($data[0]);
$url = esc_url_raw($data[1]);
$icon = isset($data[2]) ? esc_url_raw($data[2]) : flm_get_favicon($data[1]);
if (!filter_var($url, FILTER_VALIDATE_URL)) {
$error_count++;
continue;
}
if (!empty($name) && !empty($url)) {
$existing = $wpdb->get_row($wpdb->prepare(
"SELECT id FROM $table_name WHERE url = %s",
$url
));
if ($existing) {
$wpdb->update($table_name, array(
'name' => $name,
'icon' => $icon
), array('id' => $existing->id));
$update_count++;
} else {
$wpdb->insert($table_name, array(
'name' => $name,
'url' => $url,
'icon' => $icon,
'sort_order' => 0
));
$import_count++;
}
}
}
fclose($handle);
$message = sprintf(
'导入完成!新增 %d 条链接,更新 %d 条已有链接',
$import_count,
$update_count
);
if ($error_count > 0) {
$message .= sprintf(',跳过 %d 条格式不正确的记录', $error_count);
}
echo '<div class="notice notice-success"><p>' . $message . '</p></div>';
}
break;
}
}
// 获取所有链接
$links = $wpdb->get_results("SELECT * FROM $table_name ORDER BY sort_order ASC");
?>
<div class="wrap">
<h1>友情链接管理</h1>
<div class="flm-admin-container">
<!-- 添加新链接表单 -->
<div class="flm-add-form">
<h2>添加新链接</h2>
<form method="post">
<?php wp_nonce_field('flm_nonce'); ?>
<input type="hidden" name="flm_action" value="add_link">
<div class="form-group">
<label for="name">网站名称 (必填)</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="url">网站URL (必填)</label>
<input type="url" id="url" name="url" required>
</div>
<div class="form-group">
<label for="icon">网站图标URL (可选)</label>
<input type="url" id="icon" name="icon">
<p class="description">留空将自动获取favicon</p>
</div>
<button type="submit" class="button button-primary">添加链接</button>
</form>
</div>
<!-- 链接列表 -->
<div class="flm-links-list">
<h2>链接列表</h2>
<form method="post" id="flm-links-form">
<?php wp_nonce_field('flm_nonce'); ?>
<input type="hidden" name="flm_action" value="update_links">
<ul id="flm-sortable-links">
<?php foreach ($links as $link): ?>
<li class="flm-link-item">
<input type="hidden" name="link_ids[]" value="<?php echo $link->id; ?>">
<div class="flm-link-preview">
<?php if ($link->icon): ?>
<img src="<?php echo esc_url($link->icon); ?>" alt="<?php echo esc_attr($link->name); ?>" class="flm-link-icon">
<?php endif; ?>
<span class="flm-link-name"><?php echo esc_html($link->name); ?></span>
</div>
<div class="flm-link-fields">
<div class="form-group">
<label>网站名称</label>
<input type="text" name="link_names[]" value="<?php echo esc_attr($link->name); ?>" required>
</div>
<div class="form-group">
<label>网站URL</label>
<input type="url" name="link_urls[]" value="<?php echo esc_attr($link->url); ?>" required>
</div>
<div class="form-group">
<label>网站图标URL</label>
<input type="url" name="link_icons[]" value="<?php echo esc_attr($link->icon); ?>">
</div>
</div>
<div class="flm-link-actions">
<button type="button" class="button flm-delete-link" data-link-id="<?php echo $link->id; ?>">删除</button>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php if (!empty($links)): ?>
<button type="submit" class="button button-primary">保存更改</button>
<?php endif; ?>
</form>
<!-- 导入导出 -->
<div class="flm-import-export">
<h3>导入/导出</h3>
<div class="flm-export">
<form method="post">
<?php wp_nonce_field('flm_nonce'); ?>
<input type="hidden" name="flm_action" value="export_links">
<button type="submit" class="button">导出为CSV</button>
<p class="description">导出的CSV文件将只包含三列数据网站名称、网站URL、图标URL</p>
</form>
</div>
<div class="flm-import">
<form method="post" enctype="multipart/form-data">
<?php wp_nonce_field('flm_nonce'); ?>
<input type="hidden" name="flm_action" value="import_links">
<div class="form-group">
<label for="import_file">选择CSV文件</label>
<input type="file" id="import_file" name="import_file" accept=".csv" required>
<p class="description">请选择包含三列数据的CSV文件网站名称、网站URL、图标URL</p>
</div>
<button type="submit" class="button button-primary">导入链接</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
// 使链接可排序
$('#flm-sortable-links').sortable();
// 删除链接
$('.flm-delete-link').on('click', function() {
if (confirm('确定要删除这个链接吗?')) {
var linkId = $(this).data('link-id');
var $form = $('#flm-links-form');
$form.append('<input type="hidden" name="flm_action" value="delete_link">');
$form.append('<input type="hidden" name="link_id" value="' + linkId + '">');
$form.submit();
}
});
});
</script>
<?php
}
// 处理AJAX删除请求
add_action('wp_ajax_flm_delete_link', 'flm_ajax_delete_link');
function flm_ajax_delete_link() {
check_ajax_referer('flm_nonce', 'nonce');
if (!empty($_POST['link_id'])) {
global $wpdb;
$table_name = $wpdb->prefix . 'friend_links';
$wpdb->delete($table_name, array('id' => intval($_POST['link_id'])));
wp_send_json_success();
}
wp_send_json_error();
}

37
includes/shortcode.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
// 注册短代码
add_shortcode('friend_links', 'flm_display_friend_links');
function flm_display_friend_links($atts) {
global $wpdb;
$table_name = $wpdb->prefix . 'friend_links';
$atts = shortcode_atts(array(
'random' => 'true'
), $atts);
$order_by = ($atts['random'] === 'true') ? 'RAND()' : 'sort_order ASC';
$links = $wpdb->get_results("SELECT * FROM $table_name ORDER BY $order_by");
if (empty($links)) {
return '';
}
ob_start();
?>
<div class="flm-links-container">
<?php foreach ($links as $link): ?>
<div class="flm-link-card">
<a href="<?php echo esc_url($link->url); ?>" target="_blank" rel="noopener noreferrer">
<div class="flm-link-icon-container">
<img src="<?php echo $link->icon ?: FLM_PLUGIN_URL . 'assets/images/default-icon.png'; ?>"
alt="<?php echo esc_attr($link->name); ?>"
class="flm-link-icon">
</div>
<div class="flm-link-name"><?php echo esc_html($link->name); ?></div>
</a>
</div>
<?php endforeach; ?>
</div>
<?php
return ob_get_clean();
}