Files
friend-links-manager/includes/admin-page.php
2025-11-22 09:37:46 +08:00

614 lines
27 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
// 添加管理菜单
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
);
}
// 处理导出请求
add_action('admin_init', 'flm_handle_export');
function flm_handle_export() {
if (isset($_GET['page']) && $_GET['page'] === 'friend-links-manager' &&
isset($_GET['action']) && $_GET['action'] === 'export' &&
isset($_GET['nonce']) && wp_verify_nonce($_GET['nonce'], 'flm_export_nonce')) {
global $wpdb;
$table_name = $wpdb->prefix . 'friend_links';
// 检查表是否存在
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
wp_die('友情链接表不存在,请先添加一些链接。');
}
$links = $wpdb->get_results("SELECT * FROM $table_name ORDER BY sort_order ASC");
// 检查是否有数据
if (empty($links)) {
wp_die('没有找到任何链接数据,请先添加一些链接。');
}
// 清除所有输出缓冲
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');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Transfer-Encoding: binary');
$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),
flm_sanitize_export_text($link->description)
));
}
fclose($output);
exit;
}
}
// 自动获取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-info"><p><strong>💡 提示:</strong>卸载插件时会删除数据,禁用插件不会丢失数据。</p></div>';
// 显示数据库修复结果
if (isset($_GET['flm_fix_success'])) {
echo '<div class="notice notice-success"><p>✅ 数据库修复成功description字段已添加现在可以正常保存链接信息了。</p></div>';
} elseif (isset($_GET['flm_fix_error'])) {
echo '<div class="notice notice-error"><p>❌ 数据库修复失败,请检查数据库权限。</p></div>';
} elseif (isset($_GET['flm_fix_exists'])) {
echo '<div class="notice notice-info"><p> 数据库表结构正常,无需修复。</p></div>';
}
// 数据库修复按钮
echo '<div class="notice notice-warning"><p><strong>⚠️ 数据库修复:</strong><a href="' . wp_nonce_url(admin_url('admin.php?page=friend-links-manager&flm_fix_db=1'), 'flm_fix_db') . '" class="button">点击修复数据库表结构</a>(如果保存功能不工作)</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']);
// 验证URL格式
if (!filter_var($url, FILTER_VALIDATE_URL)) {
echo '<div class="notice notice-error"><p>请输入有效的URL地址</p></div>';
break;
}
$auto_get_icon = isset($_POST['auto_get_icon']) ? true : false;
$icon = '';
if ($auto_get_icon) {
$icon = flm_get_favicon($url);
} elseif (!empty($_POST['icon'])) {
$icon = esc_url_raw($_POST['icon']);
}
$description = !empty($_POST['description']) ? sanitize_textarea_field($_POST['description']) : '';
$existing = $wpdb->get_row($wpdb->prepare(
"SELECT id FROM $table_name WHERE url = %s",
$url
));
if (!$existing) {
// 获取当前最大的sort_order值
$max_sort_order = $wpdb->get_var("SELECT MAX(sort_order) FROM $table_name");
$new_sort_order = $max_sort_order ? intval($max_sort_order) + 1 : 0;
$wpdb->insert($table_name, array(
'name' => $name,
'url' => $url,
'icon' => $icon,
'description' => $description,
'sort_order' => $new_sort_order
));
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'])) {
$update_errors = 0;
$update_count = 0;
// 直接使用数组格式
$link_ids = array_values($_POST['link_ids']);
$link_names = isset($_POST['link_names']) ? array_values($_POST['link_names']) : array();
$link_urls = isset($_POST['link_urls']) ? array_values($_POST['link_urls']) : array();
$link_icons = isset($_POST['link_icons']) ? array_values($_POST['link_icons']) : array();
$link_descriptions = isset($_POST['link_descriptions']) ? array_values($_POST['link_descriptions']) : array();
foreach ($link_ids as $index => $id) {
// 确保数组索引存在
if (!isset($link_names[$index]) || !isset($link_urls[$index])) {
$update_errors++;
continue;
}
$name = sanitize_text_field($link_names[$index]);
$url = esc_url_raw($link_urls[$index]);
// 验证URL格式
if (!filter_var($url, FILTER_VALIDATE_URL)) {
$update_errors++;
continue;
}
$icon = isset($link_icons[$index]) ? esc_url_raw($link_icons[$index]) : '';
$description = isset($link_descriptions[$index]) ? sanitize_textarea_field($link_descriptions[$index]) : '';
$update_data = array(
'name' => $name,
'url' => $url,
'icon' => $icon,
'description' => $description,
'sort_order' => $index
);
$result = $wpdb->update($table_name, $update_data, array('id' => intval($id)));
if ($result === false) {
echo '<div class="notice notice-error"><p><strong>数据库错误:</strong>ID ' . $id . ' 更新失败: ' . $wpdb->last_error . '</p></div>';
$update_errors++;
} else {
$update_count++;
}
}
if ($update_errors > 0) {
echo '<div class="notice notice-warning"><p>更新完成!成功更新 ' . $update_count . ' 条链接,跳过 ' . $update_errors . ' 条记录</p></div>';
} else {
echo '<div class="notice notice-success"><p>链接更新成功!共更新 ' . $update_count . ' 条记录</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':
// 使用WordPress的admin_url进行重定向导出
$export_url = admin_url('admin.php?page=friend-links-manager&action=export&nonce=' . wp_create_nonce('flm_export_nonce'));
wp_redirect($export_url);
exit;
break;
case 'save_settings':
$desktop_columns = isset($_POST['desktop_columns']) ? intval($_POST['desktop_columns']) : 3;
$random_display = isset($_POST['random_display']) ? 1 : 0;
$show_descriptions = isset($_POST['show_descriptions']) ? 1 : 0;
update_option('flm_desktop_columns', $desktop_columns);
update_option('flm_random_display', $random_display);
update_option('flm_show_descriptions', $show_descriptions);
echo '<div class="notice notice-success"><p>设置保存成功!</p></div>';
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);
$sort_order = 0;
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]);
$description = isset($data[3]) ? sanitize_textarea_field($data[3]) : '';
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,
'description' => $description
), array('id' => $existing->id));
$update_count++;
} else {
$wpdb->insert($table_name, array(
'name' => $name,
'url' => $url,
'icon' => $icon,
'description' => $description,
'sort_order' => $sort_order
));
$import_count++;
}
$sort_order++;
}
}
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;
case 'save_settings':
$desktop_columns = isset($_POST['desktop_columns']) ? intval($_POST['desktop_columns']) : 3;
$random_display = isset($_POST['random_display']) ? 1 : 0;
$show_descriptions = isset($_POST['show_descriptions']) ? 1 : 0;
update_option('flm_desktop_columns', $desktop_columns);
update_option('flm_random_display', $random_display);
update_option('flm_show_descriptions', $show_descriptions);
echo '<div class="notice notice-success"><p>设置保存成功!</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>
<input type="checkbox" id="auto_get_icon" name="auto_get_icon" checked>
自动获取网站头像
</label>
</div>
<div class="form-group" id="icon_field" style="display:none;">
<label for="icon">网站图标URL (可选)</label>
<input type="url" id="icon" name="icon">
<p class="description">手动指定图标URL</p>
</div>
<div class="form-group">
<label for="description">链接描述 (可选)</label>
<textarea id="description" name="description" rows="3" placeholder="一句话描述这个网站"></textarea>
</div>
<button type="submit" class="button button-primary">添加链接</button>
</form>
</div>
<!-- 链接列表 -->
<div class="flm-links-list">
<h2>链接列表</h2>
<!-- 显示设置 -->
<div class="flm-top-section flm-display-settings">
<h3>显示设置</h3>
<form method="post">
<?php wp_nonce_field('flm_nonce'); ?>
<input type="hidden" name="flm_action" value="save_settings">
<div class="form-group">
<label for="desktop_columns">电脑端每行显示链接数</label>
<select id="desktop_columns" name="desktop_columns">
<option value="2" <?php selected(get_option('flm_desktop_columns', 3), 2); ?>>2个</option>
<option value="3" <?php selected(get_option('flm_desktop_columns', 3), 3); ?>>3个</option>
<option value="4" <?php selected(get_option('flm_desktop_columns', 3), 4); ?>>4个</option>
<option value="5" <?php selected(get_option('flm_desktop_columns', 3), 5); ?>>5个</option>
<option value="6" <?php selected(get_option('flm_desktop_columns', 3), 6); ?>>6个</option>
</select>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="random_display" <?php checked(get_option('flm_random_display', 1), 1); ?>>
随机显示友情链接
</label>
<p class="description">关闭后按照链接添加顺序或CSV文件中的顺序显示</p>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="show_descriptions" <?php checked(get_option('flm_show_descriptions', 1), 1); ?>>
显示链接描述
</label>
<p class="description">关闭后,即使链接有描述也不会在前台显示</p>
</div>
<button type="submit" class="button button-primary">保存设置</button>
</form>
</div>
<form method="post" id="flm-links-form">
<?php wp_nonce_field('flm_nonce'); ?>
<input type="hidden" name="flm_action" value="update_links">
<div class="flm-top-actions">
<button type="submit" class="button button-primary">保存更改</button>
<a href="<?php echo admin_url('admin.php?page=friend-links-manager&action=export&nonce=' . wp_create_nonce('flm_export_nonce')); ?>" class="button">导出为CSV</a>
<button type="button" class="button flm-batch-delete" disabled>批量删除</button>
</div>
<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-checkbox">
<input type="checkbox" class="flm-select-link" value="<?php echo $link->id; ?>">
</div>
<div class="flm-link-preview">
<span class="flm-drag-handle">☰</span>
<?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 class="form-group">
<label>链接描述</label>
<textarea name="link_descriptions[]" rows="2" placeholder="一句话描述这个网站"><?php echo esc_textarea($link->description); ?></textarea>
</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-top-section flm-import">
<h3>导入链接</h3>
<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>
<?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();
}
// 处理AJAX排序更新请求
add_action('wp_ajax_flm_update_sort_order', 'flm_ajax_update_sort_order');
function flm_ajax_update_sort_order() {
check_ajax_referer('flm_nonce', 'nonce');
if (!empty($_POST['link_ids']) && is_array($_POST['link_ids'])) {
global $wpdb;
$table_name = $wpdb->prefix . 'friend_links';
foreach ($_POST['link_ids'] as $index => $link_id) {
$link_id = intval($link_id);
if ($link_id > 0) {
$wpdb->update($table_name,
array('sort_order' => $index),
array('id' => $link_id),
array('%d'),
array('%d')
);
}
}
wp_send_json_success();
}
wp_send_json_error();
}
// 处理AJAX批量删除请求
add_action('wp_ajax_flm_batch_delete_links', 'flm_ajax_batch_delete_links');
function flm_ajax_batch_delete_links() {
check_ajax_referer('flm_nonce', 'nonce');
if (!empty($_POST['link_ids']) && is_array($_POST['link_ids'])) {
global $wpdb;
$table_name = $wpdb->prefix . 'friend_links';
$deleted_count = 0;
$error_count = 0;
foreach ($_POST['link_ids'] as $link_id) {
$link_id = intval($link_id);
if ($link_id > 0) {
$result = $wpdb->delete($table_name, array('id' => $link_id), array('%d'));
if ($result !== false) {
$deleted_count++;
} else {
$error_count++;
}
}
}
if ($deleted_count > 0) {
wp_send_json_success(array(
'deleted_count' => $deleted_count,
'error_count' => $error_count
));
} else {
wp_send_json_error('没有链接被删除');
}
}
wp_send_json_error('无效的请求参数');
}