From 4299b8de55373ec84c45103469d3d5cc2d89639d Mon Sep 17 00:00:00 2001 From: LinRuiqi Date: Sun, 3 Aug 2025 10:16:48 +0800 Subject: [PATCH] commit --- assets/css/style.css | 165 ++++++++++++++ assets/images/default-icon.png | Bin 0 -> 8108 bytes assets/js/admin.js | 45 ++++ friend-links-manager.php | 99 +++++++++ includes/admin-page.php | 378 +++++++++++++++++++++++++++++++++ includes/shortcode.php | 37 ++++ 6 files changed, 724 insertions(+) create mode 100644 assets/css/style.css create mode 100644 assets/images/default-icon.png create mode 100644 assets/js/admin.js create mode 100644 friend-links-manager.php create mode 100644 includes/admin-page.php create mode 100644 includes/shortcode.php diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..33bca5d --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,165 @@ +/* 后台样式 */ +.flm-admin-container { + display: flex; + gap: 2rem; + margin-top: 20px; +} + +.flm-add-form { + flex: 1; + max-width: 400px; + background: #fff; + padding: 20px; + border-radius: 4px; + box-shadow: 0 1px 1px rgba(0,0,0,0.04); +} + +.flm-links-list { + flex: 2; + background: #fff; + padding: 20px; + border-radius: 4px; + box-shadow: 0 1px 1px rgba(0,0,0,0.04); +} + +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 600; +} + +.form-group input[type="text"], +.form-group input[type="url"] { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; +} + +#flm-sortable-links { + list-style: none; + padding: 0; + margin: 20px 0; +} + +.flm-link-item { + display: flex; + align-items: center; + gap: 15px; + padding: 15px; + margin-bottom: 15px; + background: #f9f9f9; + border-radius: 4px; + border-left: 4px solid #6667ab; +} + +.flm-link-preview { + display: flex; + align-items: center; + gap: 10px; + min-width: 200px; +} + +.flm-link-icon { + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; +} + +.flm-link-fields { + flex: 1; +} + +.flm-link-actions { + min-width: 100px; +} + +.flm-import-export { + margin-top: 30px; + padding-top: 20px; + border-top: 1px solid #eee; +} + +.flm-import-export h3 { + margin-bottom: 15px; +} + +.flm-export, .flm-import { + margin-bottom: 15px; +} + +/* 前端展示样式 */ +.flm-links-container { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + margin: 20px 0; +} + +.flm-link-card { + border: 1px solid #6667ab; + border-radius: 14px; + padding: 20px; + text-align: center; + transition: all 0.3s ease; +} + +.flm-link-card:hover { + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + transform: translateY(-2px); +} + +.flm-link-icon-container { + margin-bottom: 10px; +} + +.flm-link-icon-container img { + width: 64px; + height: 64px; + border-radius: 50%; + object-fit: cover; + border: 1px solid #eee; +} + +.flm-link-name { + font-weight: bold; + color: inherit; /* 使用主题默认颜色 */ + margin-top: 10px; +} + +.flm-link-card a { + text-decoration: none; + color: inherit; + display: block; +} + +/* 响应式设计 */ +@media (max-width: 1024px) { + .flm-links-container { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 600px) { + .flm-admin-container { + flex-direction: column; + } + + .flm-add-form { + max-width: 100%; + } + + .flm-links-container { + grid-template-columns: 1fr; + } + + .flm-link-item { + flex-direction: column; + align-items: flex-start; + } +} \ No newline at end of file diff --git a/assets/images/default-icon.png b/assets/images/default-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3afa636d0efbde72f1881a7f2f04f3ea264d0b9d GIT binary patch literal 8108 zcmV;dA5-9oP)C00093P)t-s8A{3j z|NsC0`~Uy_{Qm#_|NR+C%K!cU|NZ^@|NZ^``~Cm_8cN6g{QVhA$^ZTP8cWOl{Qvs> z{r~^?{Qvv>{QLU<`yETm8cE3*OUD{X%>4cN|NQ^_{rwk8%o|F>97oL;OvCyA`ThU* z{QdS7M#>#X#uZG)6imq-M$i{Y(g7;A9!kjn`}-VC%@s_{8A!_&OUnQJ_x}C!0x!7% zGPwEs{v1uw9!bFiG{PK8z4!b36-v(?N6H#c!U8X}0yMlGOVkuY$sSF`7)j0hTRa!3jUT6jR0tM7;?#xdA!20VT5Z_xten_{-et9ZSs;PRR;3!U{9V2}QvHA-4e< zr_tc-#nR%j#NH80(*!xh6Gy=SC$<0_umvls|M>n9PR0s9#tA#RH)XjRPPZCH*aj@6 z0~n#L$Jh}~&JIM%6hXZKD7z3vx&<(-4LPUm^Z4fN^c6|hBuB?+kHQjB!5T)t03NIl zFQoYX^VZ<-8$;m>KgmIAzzs{kPjkHi8@L!!vH}^JpSj!)JJ*J%))Y{@3^}hHL#MRK z<*LKuOLN8!EV?pXvn@}n!PVoJuHOVY$XI^22QZ($)anpL#6WGg1vaV)C5)rN=C#k- zk+RcMcG6XT&55MTbD7E&HLn;qp#dbH^!)dClGrzB!3#jP$kywFoz)aV)eAYzNNB+w zQob`@y)9I?7df>VLbM4mveMS>z02W~qS*l+$~jQS11rV>AHgYDyFp>G5HOkg^z!HE z?dRX($;;UZL(v{o&{ulJVuiwVn81~uzE5hoWQ({1E7Vbhz(!@ZN?)er@a)*%=5B}7 zIA72vPRb}tyCy@O0uF|7lh&)Z�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 . 'friend_links'; + + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE $table_name ( + id mediumint(9) NOT NULL AUTO_INCREMENT, + name varchar(100) NOT NULL, + url varchar(255) NOT NULL, + icon varchar(255) DEFAULT '', + sort_order int(11) DEFAULT 0, + PRIMARY KEY (id) + ) $charset_collate;"; + + require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); + dbDelta($sql); + + // 初始化插件版本 + update_option('flm_version', FLM_VERSION); +} + +// 动态卸载逻辑(替代 register_uninstall_hook) +register_deactivation_hook(__FILE__, 'flm_cleanup'); +function flm_cleanup() { + global $wpdb; + + // 删除数据库表 + $table_name = $wpdb->prefix . 'friend_links'; + if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name) { + $wpdb->query("DROP TABLE IF EXISTS $table_name"); + } + + // 清理插件选项 + delete_option('flm_version'); + + // 强制刷新缓存(针对某些缓存插件) + wp_cache_flush(); +} + +// 加载插件功能文件 +require_once FLM_PLUGIN_DIR . 'includes/admin-page.php'; +require_once FLM_PLUGIN_DIR . 'includes/shortcode.php'; + +// 加载样式和脚本 +add_action('wp_enqueue_scripts', 'flm_enqueue_scripts'); +function flm_enqueue_scripts() { + wp_enqueue_style( + 'flm-style', + FLM_PLUGIN_URL . 'assets/css/style.css', + array(), + FLM_VERSION + ); +} + +// 后台脚本和样式 +add_action('admin_enqueue_scripts', 'flm_admin_enqueue_scripts'); +function flm_admin_enqueue_scripts($hook) { + if ('toplevel_page_friend-links-manager' === $hook) { + wp_enqueue_style( + 'flm-admin-style', + FLM_PLUGIN_URL . 'assets/css/style.css', + array(), + FLM_VERSION + ); + + wp_enqueue_script( + 'flm-admin-js', + FLM_PLUGIN_URL . 'assets/js/admin.js', + array('jquery', 'jquery-ui-sortable'), + FLM_VERSION, + true + ); + + wp_localize_script('flm-admin-js', 'flm_vars', array( + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('flm_nonce') + )); + } +} \ No newline at end of file diff --git a/includes/admin-page.php b/includes/admin-page.php new file mode 100644 index 0000000..c530005 --- /dev/null +++ b/includes/admin-page.php @@ -0,0 +1,378 @@ +"), '', $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 '

警告:禁用该插件将删除所有链接数据,请在禁用前导出包含链接的CSV文件!

'; + + // 处理表单提交 + 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 '

链接添加成功!

'; + } else { + echo '

该URL的链接已存在!

'; + } + } + 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 '

链接更新成功!

'; + } + break; + + case 'delete_link': + if (!empty($_POST['link_id'])) { + $wpdb->delete($table_name, array('id' => intval($_POST['link_id']))); + echo '

链接删除成功!

'; + } + 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 '

' . $message . '

'; + } + break; + } + } + + // 获取所有链接 + $links = $wpdb->get_results("SELECT * FROM $table_name ORDER BY sort_order ASC"); + ?> +
+

友情链接管理

+ +
+ +
+

添加新链接

+
+ + + +
+ + +
+ +
+ + +
+ +
+ + +

留空将自动获取favicon

+
+ + +
+
+ + + +
+
+ + + prefix . 'friend_links'; + $wpdb->delete($table_name, array('id' => intval($_POST['link_id']))); + wp_send_json_success(); + } + + wp_send_json_error(); +} \ No newline at end of file diff --git a/includes/shortcode.php b/includes/shortcode.php new file mode 100644 index 0000000..22929d7 --- /dev/null +++ b/includes/shortcode.php @@ -0,0 +1,37 @@ +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(); + ?> + +