文章目录
-
- 在当今全球化的互联网环境中,网站访问者可能来自世界各地。然而,并非所有内容都适合向所有地区的用户展示。基于地理围栏的内容区域化技术能够根据用户的地理位置,智能展示相关性强、合规性高的内容,这已成为现代网站开发的重要趋势。 地理围栏(Geo-fencing)是一种基于位置的服务技术,通过GPS、RFID、Wi-Fi或蜂窝数据等定位方式,在现实世界中创建一个虚拟的边界。当设备进入或离开这个边界时,可以触发预设的操作或通知。在网站开发中,这一技术可以用于根据用户的地理位置展示不同的内容、广告或功能。 对于WordPress网站而言,实现基于地理围栏的内容区域化展示具有多重价值: 提升用户体验:向用户展示与其地理位置相关的内容,如本地新闻、活动、产品等 合规性管理:根据不同地区的法律法规展示合规内容 营销精准化:针对不同地区实施差异化的营销策略 内容优化:根据地区特点优化内容展示,提高转化率 本教程将详细介绍如何通过WordPress代码二次开发,实现一个基于地理围栏的网站内容区域化展示工具,让您的网站具备智能化的区域内容展示能力。
-
- 在开始开发之前,我们需要确保具备以下基础知识: WordPress主题和插件的基本结构 PHP编程基础 JavaScript/jQuery基础 WordPress钩子(Hooks)和过滤器(Filters)的使用 基本的HTML/CSS知识
- 本地开发环境:建议使用XAMPP、MAMP或Local by Flywheel等工具搭建本地WordPress开发环境 代码编辑器:推荐使用VS Code、PHPStorm或Sublime Text 版本控制:使用Git进行代码版本管理 测试工具:浏览器开发者工具、Postman等API测试工具
- 我们将创建一个独立的WordPress插件来实现地理围栏功能,这样可以确保功能的独立性和可移植性。 在WordPress的wp-content/plugins/目录下创建新文件夹geo-fencing-content,并在其中创建主插件文件: <?php /** * Plugin Name: 地理围栏内容区域化工具 * Plugin URI: https://yourwebsite.com/ * Description: 基于用户地理位置实现网站内容区域化展示的WordPress插件 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: geo-fencing-content */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('GFC_VERSION', '1.0.0'); define('GFC_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('GFC_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once GFC_PLUGIN_DIR . 'includes/class-geo-fencing-core.php'; function gfc_init() { $plugin = new Geo_Fencing_Core(); $plugin->run(); } add_action('plugins_loaded', 'gfc_init');
-
- 实现地理围栏功能的第一步是获取用户的准确地理位置。以下是几种常用的技术方案: HTML5 Geolocation API:浏览器原生支持,精度较高,但需要用户授权 IP地址定位:通过用户IP地址推断地理位置,无需用户授权但精度有限 第三方定位服务:如MaxMind、IPinfo等专业服务,精度和可靠性较高
- 我们将首先实现基于IP地址的地理定位功能,作为基础方案。创建includes/class-geo-location.php文件: <?php class Geo_Location { private $api_key; private $cache_time; public function __construct() { // 可以从插件设置中获取API密钥 $this->api_key = get_option('gfc_ipapi_key', ''); $this->cache_time = 24 * 60 * 60; // 缓存24小时 } /** * 获取用户IP地址 */ public function get_user_ip() { $ip_keys = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'); foreach ($ip_keys as $key) { if (array_key_exists($key, $_SERVER) === true) { foreach (explode(',', $_SERVER[$key]) as $ip) { $ip = trim($ip); // 验证IP地址格式 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) { return $ip; } } } } return $_SERVER['REMOTE_ADDR']; } /** * 通过IP地址获取地理位置信息 */ public function get_location_by_ip($ip = '') { if (empty($ip)) { $ip = $this->get_user_ip(); } // 检查缓存 $cache_key = 'gfc_location_' . md5($ip); $cached_data = get_transient($cache_key); if ($cached_data !== false) { return $cached_data; } // 使用ip-api.com免费服务(限制:45次/分钟) $url = "http://ip-api.com/json/{$ip}"; if (!empty($this->api_key)) { // 如果有付费API密钥,可以使用更专业的服务 // $url = "https://api.ipgeolocation.io/ipgeo?apiKey={$this->api_key}&ip={$ip}"; } $response = wp_remote_get($url, array('timeout' => 5)); if (is_wp_error($response)) { // 如果API请求失败,使用备用方法 return $this->get_fallback_location(); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); // 标准化返回数据 $location_data = array( 'country' => isset($data['country']) ? $data['country'] : '', 'country_code' => isset($data['countryCode']) ? $data['countryCode'] : '', 'region' => isset($data['region']) ? $data['region'] : '', 'region_name' => isset($data['regionName']) ? $data['regionName'] : '', 'city' => isset($data['city']) ? $data['city'] : '', 'zip' => isset($data['zip']) ? $data['zip'] : '', 'lat' => isset($data['lat']) ? $data['lat'] : 0, 'lon' => isset($data['lon']) ? $data['lon'] : 0, 'timezone' => isset($data['timezone']) ? $data['timezone'] : '', 'isp' => isset($data['isp']) ? $data['isp'] : '', 'ip' => $ip ); // 缓存结果 set_transient($cache_key, $location_data, $this->cache_time); return $location_data; } /** * 备用定位方法 */ private function get_fallback_location() { // 这里可以添加备用定位逻辑 // 例如使用其他免费API或默认位置 return array( 'country' => '未知', 'country_code' => 'UN', 'region' => '', 'city' => '', 'lat' => 0, 'lon' => 0, 'ip' => $this->get_user_ip() ); } /** * 计算两点之间的距离(用于地理围栏判断) */ public function calculate_distance($lat1, $lon1, $lat2, $lon2, $unit = 'km') { $theta = $lon1 - $lon2; $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta)); $dist = acos($dist); $dist = rad2deg($dist); $miles = $dist * 60 * 1.1515; switch ($unit) { case 'km': return $miles * 1.609344; case 'm': return $miles * 1.609344 * 1000; case 'mile': default: return $miles; } } }
-
- 我们需要设计一个灵活的地理围栏系统,支持多种形状的围栏(圆形、多边形等)。首先在数据库中创建存储地理围栏的表。 创建数据库表的代码可以放在插件激活钩子中。在class-geo-fencing-core.php中添加: class Geo_Fencing_Core { public function __construct() { // 构造函数 } public function run() { // 注册激活钩子 register_activation_hook(__FILE__, array($this, 'activate_plugin')); // 注册其他钩子和过滤器 $this->register_hooks(); } public function activate_plugin() { // 创建数据库表 $this->create_database_tables(); // 设置默认选项 $this->set_default_options(); } private function create_database_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'geo_fences'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, fence_type varchar(20) NOT NULL DEFAULT 'circle', coordinates text NOT NULL, radius float DEFAULT 0, content_rule text, priority int(11) DEFAULT 0, is_active tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY is_active (is_active), KEY priority (priority) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 创建内容关联表 $relation_table = $wpdb->prefix . 'geo_fence_content'; $sql2 = "CREATE TABLE IF NOT EXISTS $relation_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, fence_id mediumint(9) NOT NULL, content_type varchar(50) NOT NULL, content_id bigint(20) NOT NULL, action_type varchar(50) NOT NULL DEFAULT 'show', conditions text, PRIMARY KEY (id), KEY fence_id (fence_id), KEY content_type (content_type, content_id) ) $charset_collate;"; dbDelta($sql2); } }
- 我们需要创建一个管理界面,让网站管理员可以添加、编辑和删除地理围栏。创建admin/class-geo-fencing-admin.php: class Geo_Fencing_Admin { private $plugin_name; private $version; public function __construct($plugin_name, $version) { $this->plugin_name = $plugin_name; $this->version = $version; add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); } public function add_admin_menu() { add_menu_page( '地理围栏管理', '地理围栏', 'manage_options', 'geo-fencing', array($this, 'display_admin_page'), 'dashicons-location-alt', 30 ); add_submenu_page( 'geo-fencing', '地理围栏列表', '围栏列表', 'manage_options', 'geo-fencing', array($this, 'display_admin_page') ); add_submenu_page( 'geo-fencing', '添加新围栏', '添加围栏', 'manage_options', 'geo-fencing-add', array($this, 'display_add_fence_page') ); add_submenu_page( 'geo-fencing', '地理围栏设置', '设置', 'manage_options', 'geo-fencing-settings', array($this, 'display_settings_page') ); } public function display_admin_page() { include GFC_PLUGIN_DIR . 'admin/views/fence-list.php'; } public function display_add_fence_page() { include GFC_PLUGIN_DIR . 'admin/views/fence-edit.php'; } public function display_settings_page() { include GFC_PLUGIN_DIR . 'admin/views/settings.php'; } public function enqueue_admin_scripts($hook) { if (strpos($hook, 'geo-fencing') === false) { return; } // 加载Leaflet地图库 wp_enqueue_style('leaflet-css', 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.css'); wp_enqueue_script('leaflet-js', 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.js', array(), '1.7.1', true); // 加载插件自定义脚本 wp_enqueue_script( $this->plugin_name . '-admin', GFC_PLUGIN_URL . 'admin/js/admin.js', array('jquery', 'leaflet-js'), $this->version, true ); wp_enqueue_style( $this->plugin_name . '-admin', GFC_PLUGIN_URL . 'admin/css/admin.css', array(), $this->version ); // 传递数据到JavaScript wp_localize_script($this->plugin_name . '-admin', 'gfc_admin_data', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('gfc_admin_nonce'), 'default_lat' => get_option('gfc_default_lat', 39.9042), 'default_lng' => get_option('gfc_default_lng', 116.4074) )); } }
- 创建admin/views/fence-edit.php文件,实现围栏编辑界面: <div class="wrap"> <h1><?php echo isset($_GET['action']) && $_GET['action'] == 'edit' ? '编辑地理围栏' : '添加新地理围栏'; ?></h1> <form method="post" action="<?php echo admin_url('admin-post.php'); ?>" id="gfc-fence-form"> <input type="hidden" name="action" value="gfc_save_fence"> <?php wp_nonce_field('gfc_save_fence_action', 'gfc_fence_nonce'); ?> <?php if (isset($_GET['id'])): ?> <input type="hidden" name="fence_id" value="<?php echo intval($_GET['id']); ?>"> <?php endif; ?> <table class="form-table"> <tr> <th scope="row"><label for="fence_name">围栏名称</label></th> <td> <input type="text" name="fence_name" id="fence_name" class="regular-text" required> <p class="description">用于识别此地理围栏的名称</p> </td> </tr> <tr> <th scope="row"><label for="fence_type">围栏类型</label></th> <td> <select name="fence_type" id="fence_type"> <option value="circle">圆形围栏</option> <option value="polygon">多边形围栏</option> <option value="rectangle">矩形围栏</option> </select> </td> </tr> <tr id="circle_fields"> <th scope="row"><label for="center_point">中心点坐标</label></th> <td> <div class="coordinates-input"> <input type="text" name="center_lat" id="center_lat" placeholder="纬度" class="small-text"> <input type="text" name="center_lng" id="center_lng" placeholder="经度" class="small-text"> <button type="button" class="button" id="get_current_location">获取当前位置</button> </div> <p class="description">格式:纬度,经度(例如:39.9042,116.4074)</p> </td> </tr> <tr id="circle_radius_field"> <th scope="row"><label for="radius">半径</label></th> <td> <input type="number" name="radius" id="radius" min="0.1" step="0.1" class="small-text"> <select name="radius_unit"> <option value="km">公里</option> <option value="m">米</option> <option value="mile">英里</option> </select> </td> </tr> <tr id="polygon_fields" style="display:none;"> <tr id="polygon_fields" style="display:none;"> <th scope="row"><label for="polygon_coords">多边形坐标</label></th> <td> <textarea name="polygon_coords" id="polygon_coords" rows="5" cols="50" placeholder="格式:纬度,经度 例如: 39.9042,116.4074 39.9142,116.4174 39.8942,116.4274"></textarea> <p class="description">每行输入一个坐标点,格式:纬度,经度</p> </td> </tr> <tr> <th scope="row"><label for="fence_priority">优先级</label></th> <td> <input type="number" name="fence_priority" id="fence_priority" value="0" min="0" class="small-text"> <p class="description">数值越大优先级越高,当用户位于多个围栏重叠区域时生效</p> </td> </tr> <tr> <th scope="row"><label for="is_active">状态</label></th> <td> <label> <input type="checkbox" name="is_active" id="is_active" value="1" checked> 启用此围栏 </label> </td> </tr> </table> <div class="map-container"> <h3>地图预览</h3> <div id="fence_map" style="height: 400px; width: 100%;"></div> <p class="description">在地图上点击可以设置围栏中心点或添加多边形顶点</p> </div> <h2>内容规则设置</h2> <div id="content_rules"> <div class="content-rule"> <h4>规则 #1</h4> <table class="form-table"> <tr> <th scope="row"><label>内容类型</label></th> <td> <select name="content_type[]" class="content-type-select"> <option value="post">文章</option> <option value="page">页面</option> <option value="category">分类目录</option> <option value="custom">自定义内容</option> </select> </td> </tr> <tr> <th scope="row"><label>内容选择</label></th> <td> <select name="content_id[]" class="content-id-select" style="width: 300px;"> <option value="">请选择内容</option> </select> </td> </tr> <tr> <th scope="row"><label>操作类型</label></th> <td> <select name="action_type[]"> <option value="show">显示内容</option> <option value="hide">隐藏内容</option> <option value="replace">替换内容</option> <option value="redirect">重定向</option> </select> </td> </tr> <tr class="replace-content" style="display:none;"> <th scope="row"><label>替换内容</label></th> <td> <textarea name="replace_content[]" rows="3" cols="50" placeholder="输入替换内容或短代码"></textarea> </td> </tr> <tr class="redirect-url" style="display:none;"> <th scope="row"><label>重定向URL</label></th> <td> <input type="url" name="redirect_url[]" class="regular-text" placeholder="https://"> </td> </tr> </table> <hr> </div> </div> <button type="button" class="button" id="add_content_rule">添加新规则</button> <p class="submit"> <input type="submit" name="submit" id="submit" class="button button-primary" value="保存围栏"> </p> </form> </div> <script>jQuery(document).ready(function($) { // 初始化地图 var map = L.map('fence_map').setView([39.9042, 116.4074], 10); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }).addTo(map); // 地图点击事件 var marker; var polygonPoints = []; var polygonLayer; map.on('click', function(e) { var lat = e.latlng.lat; var lng = e.latlng.lng; $('#center_lat').val(lat.toFixed(6)); $('#center_lng').val(lng.toFixed(6)); // 更新地图标记 if (marker) { map.removeLayer(marker); } marker = L.marker([lat, lng]).addTo(map); // 如果是多边形模式,添加顶点 if ($('#fence_type').val() === 'polygon') { polygonPoints.push([lat, lng]); updatePolygonPreview(); } }); // 围栏类型切换 $('#fence_type').on('change', function() { var type = $(this).val(); if (type === 'circle') { $('#circle_fields, #circle_radius_field').show(); $('#polygon_fields').hide(); } else if (type === 'polygon') { $('#circle_fields, #circle_radius_field').hide(); $('#polygon_fields').show(); polygonPoints = []; if (polygonLayer) { map.removeLayer(polygonLayer); } } }); // 更新多边形预览 function updatePolygonPreview() { if (polygonLayer) { map.removeLayer(polygonLayer); } if (polygonPoints.length >= 3) { polygonLayer = L.polygon(polygonPoints, {color: 'blue'}).addTo(map); map.fitBounds(polygonLayer.getBounds()); // 更新坐标文本 var coordsText = polygonPoints.map(function(point) { return point[0].toFixed(6) + ',' + point[1].toFixed(6); }).join('n'); $('#polygon_coords').val(coordsText); } } // 获取当前位置 $('#get_current_location').on('click', function() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(function(position) { var lat = position.coords.latitude; var lng = position.coords.longitude; $('#center_lat').val(lat.toFixed(6)); $('#center_lng').val(lng.toFixed(6)); // 更新地图 if (marker) { map.removeLayer(marker); } marker = L.marker([lat, lng]).addTo(map); map.setView([lat, lng], 13); }); } else { alert('您的浏览器不支持地理位置功能'); } }); // 操作类型切换 $(document).on('change', 'select[name="action_type[]"]', function() { var actionType = $(this).val(); var ruleDiv = $(this).closest('.content-rule'); ruleDiv.find('.replace-content, .redirect-url').hide(); if (actionType === 'replace') { ruleDiv.find('.replace-content').show(); } else if (actionType === 'redirect') { ruleDiv.find('.redirect-url').show(); } }); // 添加新规则 $('#add_content_rule').on('click', function() { var ruleCount = $('#content_rules .content-rule').length + 1; var newRule = $('#content_rules .content-rule:first').clone(); newRule.find('h4').text('规则 #' + ruleCount); newRule.find('input, textarea, select').val(''); newRule.find('.replace-content, .redirect-url').hide(); $('#content_rules').append(newRule); }); });</script> ## 第四部分:核心功能实现与集成 ### 4.1 地理围栏检测算法 创建`includes/class-geo-fence-detector.php`,实现围栏检测逻辑: <?phpclass Geo_Fence_Detector { private $location_service; public function __construct($location_service) { $this->location_service = $location_service; } /** * 检测用户是否在指定围栏内 */ public function is_user_in_fence($fence_data, $user_location = null) { if (empty($user_location)) { $user_location = $this->location_service->get_location_by_ip(); } if (empty($user_location['lat']) || empty($user_location['lon'])) { return false; } $user_lat = floatval($user_location['lat']); $user_lng = floatval($user_location['lon']); switch ($fence_data['fence_type']) { case 'circle': return $this->check_circle_fence($fence_data, $user_lat, $user_lng); case 'polygon': return $this->check_polygon_fence($fence_data, $user_lat, $user_lng); case 'rectangle': return $this->check_rectangle_fence($fence_data, $user_lat, $user_lng); default: return false; } } /** * 检测圆形围栏 */ private function check_circle_fence($fence_data, $user_lat, $user_lng) { $center_coords = explode(',', $fence_data['coordinates']); if (count($center_coords) !== 2) { return false; } $center_lat = floatval(trim($center_coords[0])); $center_lng = floatval(trim($center_coords[1])); $radius = floatval($fence_data['radius']); // 计算距离 $distance = $this->location_service->calculate_distance( $center_lat, $center_lng, $user_lat, $user_lng, 'km' ); return $distance <= $radius; } /** * 检测多边形围栏(使用射线法) */ private function check_polygon_fence($fence_data, $user_lat, $user_lng) { $coordinates = $this->parse_polygon_coordinates($fence_data['coordinates']); if (count($coordinates) < 3) { return false; } $inside = false; $n = count($coordinates); for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) { $xi = $coordinates[$i][0]; $yi = $coordinates[$i][1]; $xj = $coordinates[$j][0]; $yj = $coordinates[$j][1]; $intersect = (($yi > $user_lng) != ($yj > $user_lng)) && ($user_lat < ($xj - $xi) * ($user_lng - $yi) / ($yj - $yi) + $xi); if ($intersect) { $inside = !$inside; } } return $inside; } /** * 解析多边形坐标 */ private function parse_polygon_coordinates($coords_string) { $coordinates = array(); $lines = explode("n", $coords_string); foreach ($lines as $line) { $line = trim($line); if (empty($line)) continue; $parts = explode(',', $line); if (count($parts) === 2) { $lat = floatval(trim($parts[0])); $lng = floatval(trim($parts[1])); $coordinates[] = array($lat, $lng); } } return $coordinates; } /** * 检测矩形围栏 */ private function check_rectangle_fence($fence_data, $user_lat, $user_lng) { $bounds = explode('|', $fence_data['coordinates']); if (count($bounds) !== 4) { return false; } $min_lat = floatval($bounds[0]); $max_lat = floatval($bounds[1]); $min_lng = floatval($bounds[2]); $max_lng = floatval($bounds[3]); return ($user_lat >= $min_lat && $user_lat <= $max_lat && $user_lng >= $min_lng && $user_lng <= $max_lng); } /** * 获取用户所在的所有围栏 */ public function get_user_fences($user_location = null) { global $wpdb; $table_name = $wpdb->prefix . 'geo_fences'; // 获取所有启用的围栏 $fences = $wpdb->get_results( "SELECT * FROM $table_name WHERE is_active = 1 ORDER BY priority DESC", ARRAY_A ); $user_fences = array(); foreach ($fences as $fence) { if ($this->is_user_in_fence($fence, $user_location)) { $user_fences[] = $fence; } } return $user_fences; } /** * 获取适用于用户的内容规则 */ public function get_content_rules_for_user() { $user_fences = $this->get_user_fences(); $all_rules = array(); foreach ($user_fences as $fence) { $fence_rules = $this->get_fence_content_rules($fence['id']); $all_rules = array_merge($all_rules, $fence_rules); } // 按优先级排序 usort($all_rules, function($a, $b) { return $b['priority'] - $a['priority']; }); return $all_rules; } /** * 获取围栏的内容规则 */ private function get_fence_content_rules($fence_id) { global $wpdb; $table_name = $wpdb->prefix . 'geo_fence_content'; return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name WHERE fence_id = %d", $fence_id ), ARRAY_A ); } } ### 4.2 WordPress内容过滤与替换 创建`includes/class-content-filter.php`,实现内容过滤功能: <?phpclass Geo_Content_Filter { private $fence_detector; private $user_rules; public function __construct($fence_detector) { $this->fence_detector = $fence_detector; $this->user_rules = null; } public function init() { // 只在非管理页面应用规则 if (!is_admin()) { // 文章内容过滤 add_filter('the_content', array($this, 'filter_post_content'), 10, 1); // 小工具过滤 add_filter('widget_display_callback', array($this, 'filter_widget'), 10, 3); // 菜单过滤 add_filter('wp_nav_menu_objects', array($this, 'filter_menu_items'), 10, 2); // 重定向处理 add_action('template_redirect', array($this, 'handle_redirects')); // 短代码支持 add_shortcode('geo_content', array($this, 'geo_content_shortcode')); } } /** * 获取用户规则(懒加载) */ private function get_user_rules() { if ($this->user_rules === null) { $this->user_rules = $this->fence_detector->get_content_rules_for_user(); } return $this->user_rules; } /** * 过滤文章内容 */ public function filter_post_content($content) { global $post; if (empty($post)) { return $content; } $rules = $this->get_user_rules(); foreach ($rules as $rule) { if ($this->rule_applies_to_content($rule, $post)) { $content = $this->apply_rule_to_content($rule, $content, $post); } } return $content; } /** * 检查规则是否适用于当前内容 */ private function rule_applies_to_content($rule, $post) { switch ($rule['content_type']) { case 'post': return ($post->post_type == 'post' && $rule['content_id'] == $post->ID); case 'page': return ($post->post_type == 'page' && $rule['content_id'] == $post->ID); case 'category': return has_category($rule['content_id'], $post); case 'custom': // 自定义规则,可以扩展 return $this->check_custom_rule($rule, $post); default: return false; } } /** * 应用规则到内容 */ private function apply_rule_to_content($rule, $content, $post) { switch ($rule['action_type']) { case 'hide': return ''; // 完全隐藏内容 case 'replace': return $this->get_replacement_content($rule); case 'show': // 默认就是
在当今全球化的互联网环境中,网站访问者可能来自世界各地。然而,并非所有内容都适合向所有地区的用户展示。基于地理围栏的内容区域化技术能够根据用户的地理位置,智能展示相关性强、合规性高的内容,这已成为现代网站开发的重要趋势。
地理围栏(Geo-fencing)是一种基于位置的服务技术,通过GPS、RFID、Wi-Fi或蜂窝数据等定位方式,在现实世界中创建一个虚拟的边界。当设备进入或离开这个边界时,可以触发预设的操作或通知。在网站开发中,这一技术可以用于根据用户的地理位置展示不同的内容、广告或功能。
对于WordPress网站而言,实现基于地理围栏的内容区域化展示具有多重价值:
- 提升用户体验:向用户展示与其地理位置相关的内容,如本地新闻、活动、产品等
- 合规性管理:根据不同地区的法律法规展示合规内容
- 营销精准化:针对不同地区实施差异化的营销策略
- 内容优化:根据地区特点优化内容展示,提高转化率
本教程将详细介绍如何通过WordPress代码二次开发,实现一个基于地理围栏的网站内容区域化展示工具,让您的网站具备智能化的区域内容展示能力。
在开始开发之前,我们需要确保具备以下基础知识:
- WordPress主题和插件的基本结构
- PHP编程基础
- JavaScript/jQuery基础
- WordPress钩子(Hooks)和过滤器(Filters)的使用
- 基本的HTML/CSS知识
- 本地开发环境:建议使用XAMPP、MAMP或Local by Flywheel等工具搭建本地WordPress开发环境
- 代码编辑器:推荐使用VS Code、PHPStorm或Sublime Text
- 版本控制:使用Git进行代码版本管理
- 测试工具:浏览器开发者工具、Postman等API测试工具
我们将创建一个独立的WordPress插件来实现地理围栏功能,这样可以确保功能的独立性和可移植性。
在WordPress的wp-content/plugins/目录下创建新文件夹geo-fencing-content,并在其中创建主插件文件:
<?php
/**
* Plugin Name: 地理围栏内容区域化工具
* Plugin URI: https://yourwebsite.com/
* Description: 基于用户地理位置实现网站内容区域化展示的WordPress插件
* Version: 1.0.0
* Author: 您的名称
* License: GPL v2 or later
* Text Domain: geo-fencing-content
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('GFC_VERSION', '1.0.0');
define('GFC_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('GFC_PLUGIN_URL', plugin_dir_url(__FILE__));
// 初始化插件
require_once GFC_PLUGIN_DIR . 'includes/class-geo-fencing-core.php';
function gfc_init() {
$plugin = new Geo_Fencing_Core();
$plugin->run();
}
add_action('plugins_loaded', 'gfc_init');
实现地理围栏功能的第一步是获取用户的准确地理位置。以下是几种常用的技术方案:
- HTML5 Geolocation API:浏览器原生支持,精度较高,但需要用户授权
- IP地址定位:通过用户IP地址推断地理位置,无需用户授权但精度有限
- 第三方定位服务:如MaxMind、IPinfo等专业服务,精度和可靠性较高
我们将首先实现基于IP地址的地理定位功能,作为基础方案。创建includes/class-geo-location.php文件:
<?php
class Geo_Location {
private $api_key;
private $cache_time;
public function __construct() {
// 可以从插件设置中获取API密钥
$this->api_key = get_option('gfc_ipapi_key', '');
$this->cache_time = 24 * 60 * 60; // 缓存24小时
}
/**
* 获取用户IP地址
*/
public function get_user_ip() {
$ip_keys = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR');
foreach ($ip_keys as $key) {
if (array_key_exists($key, $_SERVER) === true) {
foreach (explode(',', $_SERVER[$key]) as $ip) {
$ip = trim($ip);
// 验证IP地址格式
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'];
}
/**
* 通过IP地址获取地理位置信息
*/
public function get_location_by_ip($ip = '') {
if (empty($ip)) {
$ip = $this->get_user_ip();
}
// 检查缓存
$cache_key = 'gfc_location_' . md5($ip);
$cached_data = get_transient($cache_key);
if ($cached_data !== false) {
return $cached_data;
}
// 使用ip-api.com免费服务(限制:45次/分钟)
$url = "http://ip-api.com/json/{$ip}";
if (!empty($this->api_key)) {
// 如果有付费API密钥,可以使用更专业的服务
// $url = "https://api.ipgeolocation.io/ipgeo?apiKey={$this->api_key}&ip={$ip}";
}
$response = wp_remote_get($url, array('timeout' => 5));
if (is_wp_error($response)) {
// 如果API请求失败,使用备用方法
return $this->get_fallback_location();
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
// 标准化返回数据
$location_data = array(
'country' => isset($data['country']) ? $data['country'] : '',
'country_code' => isset($data['countryCode']) ? $data['countryCode'] : '',
'region' => isset($data['region']) ? $data['region'] : '',
'region_name' => isset($data['regionName']) ? $data['regionName'] : '',
'city' => isset($data['city']) ? $data['city'] : '',
'zip' => isset($data['zip']) ? $data['zip'] : '',
'lat' => isset($data['lat']) ? $data['lat'] : 0,
'lon' => isset($data['lon']) ? $data['lon'] : 0,
'timezone' => isset($data['timezone']) ? $data['timezone'] : '',
'isp' => isset($data['isp']) ? $data['isp'] : '',
'ip' => $ip
);
// 缓存结果
set_transient($cache_key, $location_data, $this->cache_time);
return $location_data;
}
/**
* 备用定位方法
*/
private function get_fallback_location() {
// 这里可以添加备用定位逻辑
// 例如使用其他免费API或默认位置
return array(
'country' => '未知',
'country_code' => 'UN',
'region' => '',
'city' => '',
'lat' => 0,
'lon' => 0,
'ip' => $this->get_user_ip()
);
}
/**
* 计算两点之间的距离(用于地理围栏判断)
*/
public function calculate_distance($lat1, $lon1, $lat2, $lon2, $unit = 'km') {
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) +
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
switch ($unit) {
case 'km':
return $miles * 1.609344;
case 'm':
return $miles * 1.609344 * 1000;
case 'mile':
default:
return $miles;
}
}
}
我们需要设计一个灵活的地理围栏系统,支持多种形状的围栏(圆形、多边形等)。首先在数据库中创建存储地理围栏的表。
创建数据库表的代码可以放在插件激活钩子中。在class-geo-fencing-core.php中添加:
class Geo_Fencing_Core {
public function __construct() {
// 构造函数
}
public function run() {
// 注册激活钩子
register_activation_hook(__FILE__, array($this, 'activate_plugin'));
// 注册其他钩子和过滤器
$this->register_hooks();
}
public function activate_plugin() {
// 创建数据库表
$this->create_database_tables();
// 设置默认选项
$this->set_default_options();
}
private function create_database_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$table_name = $wpdb->prefix . 'geo_fences';
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
fence_type varchar(20) NOT NULL DEFAULT 'circle',
coordinates text NOT NULL,
radius float DEFAULT 0,
content_rule text,
priority int(11) DEFAULT 0,
is_active tinyint(1) DEFAULT 1,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY is_active (is_active),
KEY priority (priority)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
// 创建内容关联表
$relation_table = $wpdb->prefix . 'geo_fence_content';
$sql2 = "CREATE TABLE IF NOT EXISTS $relation_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
fence_id mediumint(9) NOT NULL,
content_type varchar(50) NOT NULL,
content_id bigint(20) NOT NULL,
action_type varchar(50) NOT NULL DEFAULT 'show',
conditions text,
PRIMARY KEY (id),
KEY fence_id (fence_id),
KEY content_type (content_type, content_id)
) $charset_collate;";
dbDelta($sql2);
}
}
我们需要创建一个管理界面,让网站管理员可以添加、编辑和删除地理围栏。创建admin/class-geo-fencing-admin.php:
class Geo_Fencing_Admin {
private $plugin_name;
private $version;
public function __construct($plugin_name, $version) {
$this->plugin_name = $plugin_name;
$this->version = $version;
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
}
public function add_admin_menu() {
add_menu_page(
'地理围栏管理',
'地理围栏',
'manage_options',
'geo-fencing',
array($this, 'display_admin_page'),
'dashicons-location-alt',
30
);
add_submenu_page(
'geo-fencing',
'地理围栏列表',
'围栏列表',
'manage_options',
'geo-fencing',
array($this, 'display_admin_page')
);
add_submenu_page(
'geo-fencing',
'添加新围栏',
'添加围栏',
'manage_options',
'geo-fencing-add',
array($this, 'display_add_fence_page')
);
add_submenu_page(
'geo-fencing',
'地理围栏设置',
'设置',
'manage_options',
'geo-fencing-settings',
array($this, 'display_settings_page')
);
}
public function display_admin_page() {
include GFC_PLUGIN_DIR . 'admin/views/fence-list.php';
}
public function display_add_fence_page() {
include GFC_PLUGIN_DIR . 'admin/views/fence-edit.php';
}
public function display_settings_page() {
include GFC_PLUGIN_DIR . 'admin/views/settings.php';
}
public function enqueue_admin_scripts($hook) {
if (strpos($hook, 'geo-fencing') === false) {
return;
}
// 加载Leaflet地图库
wp_enqueue_style('leaflet-css', 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.css');
wp_enqueue_script('leaflet-js', 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.js', array(), '1.7.1', true);
// 加载插件自定义脚本
wp_enqueue_script(
$this->plugin_name . '-admin',
GFC_PLUGIN_URL . 'admin/js/admin.js',
array('jquery', 'leaflet-js'),
$this->version,
true
);
wp_enqueue_style(
$this->plugin_name . '-admin',
GFC_PLUGIN_URL . 'admin/css/admin.css',
array(),
$this->version
);
// 传递数据到JavaScript
wp_localize_script($this->plugin_name . '-admin', 'gfc_admin_data', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('gfc_admin_nonce'),
'default_lat' => get_option('gfc_default_lat', 39.9042),
'default_lng' => get_option('gfc_default_lng', 116.4074)
));
}
}
创建admin/views/fence-edit.php文件,实现围栏编辑界面:
<div class="wrap">
<h1><?php echo isset($_GET['action']) && $_GET['action'] == 'edit' ? '编辑地理围栏' : '添加新地理围栏'; ?></h1>
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>" id="gfc-fence-form">
<input type="hidden" name="action" value="gfc_save_fence">
<?php wp_nonce_field('gfc_save_fence_action', 'gfc_fence_nonce'); ?>
<?php if (isset($_GET['id'])): ?>
<input type="hidden" name="fence_id" value="<?php echo intval($_GET['id']); ?>">
<?php endif; ?>
<table class="form-table">
<tr>
<th scope="row"><label for="fence_name">围栏名称</label></th>
<td>
<input type="text" name="fence_name" id="fence_name" class="regular-text" required>
<p class="description">用于识别此地理围栏的名称</p>
</td>
</tr>
<tr>
<th scope="row"><label for="fence_type">围栏类型</label></th>
<td>
<select name="fence_type" id="fence_type">
<option value="circle">圆形围栏</option>
<option value="polygon">多边形围栏</option>
<option value="rectangle">矩形围栏</option>
</select>
</td>
</tr>
<tr id="circle_fields">
<th scope="row"><label for="center_point">中心点坐标</label></th>
<td>
<div class="coordinates-input">
<input type="text" name="center_lat" id="center_lat" placeholder="纬度" class="small-text">
<input type="text" name="center_lng" id="center_lng" placeholder="经度" class="small-text">
<button type="button" class="button" id="get_current_location">获取当前位置</button>
</div>
<p class="description">格式:纬度,经度(例如:39.9042,116.4074)</p>
</td>
</tr>
<tr id="circle_radius_field">
<th scope="row"><label for="radius">半径</label></th>
<td>
<input type="number" name="radius" id="radius" min="0.1" step="0.1" class="small-text">
<select name="radius_unit">
<option value="km">公里</option>
<option value="m">米</option>
<option value="mile">英里</option>
</select>
</td>
</tr>
<tr id="polygon_fields" style="display:none;">
<tr id="polygon_fields" style="display:none;">
<th scope="row"><label for="polygon_coords">多边形坐标</label></th>
<td>
<textarea name="polygon_coords" id="polygon_coords" rows="5" cols="50" placeholder="格式:纬度,经度 例如: 39.9042,116.4074 39.9142,116.4174 39.8942,116.4274"></textarea>
<p class="description">每行输入一个坐标点,格式:纬度,经度</p>
</td>
</tr>
<tr>
<th scope="row"><label for="fence_priority">优先级</label></th>
<td>
<input type="number" name="fence_priority" id="fence_priority" value="0" min="0" class="small-text">
<p class="description">数值越大优先级越高,当用户位于多个围栏重叠区域时生效</p>
</td>
</tr>
<tr>
<th scope="row"><label for="is_active">状态</label></th>
<td>
<label>
<input type="checkbox" name="is_active" id="is_active" value="1" checked> 启用此围栏
</label>
</td>
</tr>
</table>
<div class="map-container">
<h3>地图预览</h3>
<div id="fence_map" style="height: 400px; width: 100%;"></div>
<p class="description">在地图上点击可以设置围栏中心点或添加多边形顶点</p>
</div>
<h2>内容规则设置</h2>
<div id="content_rules">
<div class="content-rule">
<h4>规则 #1</h4>
<table class="form-table">
<tr>
<th scope="row"><label>内容类型</label></th>
<td>
<select name="content_type[]" class="content-type-select">
<option value="post">文章</option>
<option value="page">页面</option>
<option value="category">分类目录</option>
<option value="custom">自定义内容</option>
</select>
</td>
</tr>
<tr>
<th scope="row"><label>内容选择</label></th>
<td>
<select name="content_id[]" class="content-id-select" style="width: 300px;">
<option value="">请选择内容</option>
</select>
</td>
</tr>
<tr>
<th scope="row"><label>操作类型</label></th>
<td>
<select name="action_type[]">
<option value="show">显示内容</option>
<option value="hide">隐藏内容</option>
<option value="replace">替换内容</option>
<option value="redirect">重定向</option>
</select>
</td>
</tr>
<tr class="replace-content" style="display:none;">
<th scope="row"><label>替换内容</label></th>
<td>
<textarea name="replace_content[]" rows="3" cols="50" placeholder="输入替换内容或短代码"></textarea>
</td>
</tr>
<tr class="redirect-url" style="display:none;">
<th scope="row"><label>重定向URL</label></th>
<td>
<input type="url" name="redirect_url[]" class="regular-text" placeholder="https://">
</td>
</tr>
</table>
<hr>
</div>
</div>
<button type="button" class="button" id="add_content_rule">添加新规则</button>
<p class="submit">
<input type="submit" name="submit" id="submit" class="button button-primary" value="保存围栏">
</p>
</form>
</div>
<script>
jQuery(document).ready(function($) {
// 初始化地图
var map = L.map('fence_map').setView([39.9042, 116.4074], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// 地图点击事件
var marker;
var polygonPoints = [];
var polygonLayer;
map.on('click', function(e) {
var lat = e.latlng.lat;
var lng = e.latlng.lng;
$('#center_lat').val(lat.toFixed(6));
$('#center_lng').val(lng.toFixed(6));
// 更新地图标记
if (marker) {
map.removeLayer(marker);
}
marker = L.marker([lat, lng]).addTo(map);
// 如果是多边形模式,添加顶点
if ($('#fence_type').val() === 'polygon') {
polygonPoints.push([lat, lng]);
updatePolygonPreview();
}
});
// 围栏类型切换
$('#fence_type').on('change', function() {
var type = $(this).val();
if (type === 'circle') {
$('#circle_fields, #circle_radius_field').show();
$('#polygon_fields').hide();
} else if (type === 'polygon') {
$('#circle_fields, #circle_radius_field').hide();
$('#polygon_fields').show();
polygonPoints = [];
if (polygonLayer) {
map.removeLayer(polygonLayer);
}
}
});
// 更新多边形预览
function updatePolygonPreview() {
if (polygonLayer) {
map.removeLayer(polygonLayer);
}
if (polygonPoints.length >= 3) {
polygonLayer = L.polygon(polygonPoints, {color: 'blue'}).addTo(map);
map.fitBounds(polygonLayer.getBounds());
// 更新坐标文本
var coordsText = polygonPoints.map(function(point) {
return point[0].toFixed(6) + ',' + point[1].toFixed(6);
}).join('n');
$('#polygon_coords').val(coordsText);
}
}
// 获取当前位置
$('#get_current_location').on('click', function() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var lat = position.coords.latitude;
var lng = position.coords.longitude;
$('#center_lat').val(lat.toFixed(6));
$('#center_lng').val(lng.toFixed(6));
// 更新地图
if (marker) {
map.removeLayer(marker);
}
marker = L.marker([lat, lng]).addTo(map);
map.setView([lat, lng], 13);
});
} else {
alert('您的浏览器不支持地理位置功能');
}
});
// 操作类型切换
$(document).on('change', 'select[name="action_type[]"]', function() {
var actionType = $(this).val();
var ruleDiv = $(this).closest('.content-rule');
ruleDiv.find('.replace-content, .redirect-url').hide();
if (actionType === 'replace') {
ruleDiv.find('.replace-content').show();
} else if (actionType === 'redirect') {
ruleDiv.find('.redirect-url').show();
}
});
// 添加新规则
$('#add_content_rule').on('click', function() {
var ruleCount = $('#content_rules .content-rule').length + 1;
var newRule = $('#content_rules .content-rule:first').clone();
newRule.find('h4').text('规则 #' + ruleCount);
newRule.find('input, textarea, select').val('');
newRule.find('.replace-content, .redirect-url').hide();
$('#content_rules').append(newRule);
});
});
</script>
## 第四部分:核心功能实现与集成
### 4.1 地理围栏检测算法
创建`includes/class-geo-fence-detector.php`,实现围栏检测逻辑:
<?php
class Geo_Fence_Detector {
private $location_service;
public function __construct($location_service) {
$this->location_service = $location_service;
}
/**
* 检测用户是否在指定围栏内
*/
public function is_user_in_fence($fence_data, $user_location = null) {
if (empty($user_location)) {
$user_location = $this->location_service->get_location_by_ip();
}
if (empty($user_location['lat']) || empty($user_location['lon'])) {
return false;
}
$user_lat = floatval($user_location['lat']);
$user_lng = floatval($user_location['lon']);
switch ($fence_data['fence_type']) {
case 'circle':
return $this->check_circle_fence($fence_data, $user_lat, $user_lng);
case 'polygon':
return $this->check_polygon_fence($fence_data, $user_lat, $user_lng);
case 'rectangle':
return $this->check_rectangle_fence($fence_data, $user_lat, $user_lng);
default:
return false;
}
}
/**
* 检测圆形围栏
*/
private function check_circle_fence($fence_data, $user_lat, $user_lng) {
$center_coords = explode(',', $fence_data['coordinates']);
if (count($center_coords) !== 2) {
return false;
}
$center_lat = floatval(trim($center_coords[0]));
$center_lng = floatval(trim($center_coords[1]));
$radius = floatval($fence_data['radius']);
// 计算距离
$distance = $this->location_service->calculate_distance(
$center_lat, $center_lng,
$user_lat, $user_lng,
'km'
);
return $distance <= $radius;
}
/**
* 检测多边形围栏(使用射线法)
*/
private function check_polygon_fence($fence_data, $user_lat, $user_lng) {
$coordinates = $this->parse_polygon_coordinates($fence_data['coordinates']);
if (count($coordinates) < 3) {
return false;
}
$inside = false;
$n = count($coordinates);
for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
$xi = $coordinates[$i][0];
$yi = $coordinates[$i][1];
$xj = $coordinates[$j][0];
$yj = $coordinates[$j][1];
$intersect = (($yi > $user_lng) != ($yj > $user_lng))
&& ($user_lat < ($xj - $xi) * ($user_lng - $yi) / ($yj - $yi) + $xi);
if ($intersect) {
$inside = !$inside;
}
}
return $inside;
}
/**
* 解析多边形坐标
*/
private function parse_polygon_coordinates($coords_string) {
$coordinates = array();
$lines = explode("n", $coords_string);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
$parts = explode(',', $line);
if (count($parts) === 2) {
$lat = floatval(trim($parts[0]));
$lng = floatval(trim($parts[1]));
$coordinates[] = array($lat, $lng);
}
}
return $coordinates;
}
/**
* 检测矩形围栏
*/
private function check_rectangle_fence($fence_data, $user_lat, $user_lng) {
$bounds = explode('|', $fence_data['coordinates']);
if (count($bounds) !== 4) {
return false;
}
$min_lat = floatval($bounds[0]);
$max_lat = floatval($bounds[1]);
$min_lng = floatval($bounds[2]);
$max_lng = floatval($bounds[3]);
return ($user_lat >= $min_lat && $user_lat <= $max_lat &&
$user_lng >= $min_lng && $user_lng <= $max_lng);
}
/**
* 获取用户所在的所有围栏
*/
public function get_user_fences($user_location = null) {
global $wpdb;
$table_name = $wpdb->prefix . 'geo_fences';
// 获取所有启用的围栏
$fences = $wpdb->get_results(
"SELECT * FROM $table_name WHERE is_active = 1 ORDER BY priority DESC",
ARRAY_A
);
$user_fences = array();
foreach ($fences as $fence) {
if ($this->is_user_in_fence($fence, $user_location)) {
$user_fences[] = $fence;
}
}
return $user_fences;
}
/**
* 获取适用于用户的内容规则
*/
public function get_content_rules_for_user() {
$user_fences = $this->get_user_fences();
$all_rules = array();
foreach ($user_fences as $fence) {
$fence_rules = $this->get_fence_content_rules($fence['id']);
$all_rules = array_merge($all_rules, $fence_rules);
}
// 按优先级排序
usort($all_rules, function($a, $b) {
return $b['priority'] - $a['priority'];
});
return $all_rules;
}
/**
* 获取围栏的内容规则
*/
private function get_fence_content_rules($fence_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'geo_fence_content';
return $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM $table_name WHERE fence_id = %d",
$fence_id
),
ARRAY_A
);
}
}
### 4.2 WordPress内容过滤与替换
创建`includes/class-content-filter.php`,实现内容过滤功能:
<?php
class Geo_Content_Filter {
private $fence_detector;
private $user_rules;
public function __construct($fence_detector) {
$this->fence_detector = $fence_detector;
$this->user_rules = null;
}
public function init() {
// 只在非管理页面应用规则
if (!is_admin()) {
// 文章内容过滤
add_filter('the_content', array($this, 'filter_post_content'), 10, 1);
// 小工具过滤
add_filter('widget_display_callback', array($this, 'filter_widget'), 10, 3);
// 菜单过滤
add_filter('wp_nav_menu_objects', array($this, 'filter_menu_items'), 10, 2);
// 重定向处理
add_action('template_redirect', array($this, 'handle_redirects'));
// 短代码支持
add_shortcode('geo_content', array($this, 'geo_content_shortcode'));
}
}
/**
* 获取用户规则(懒加载)
*/
private function get_user_rules() {
if ($this->user_rules === null) {
$this->user_rules = $this->fence_detector->get_content_rules_for_user();
}
return $this->user_rules;
}
/**
* 过滤文章内容
*/
public function filter_post_content($content) {
global $post;
if (empty($post)) {
return $content;
}
$rules = $this->get_user_rules();
foreach ($rules as $rule) {
if ($this->rule_applies_to_content($rule, $post)) {
$content = $this->apply_rule_to_content($rule, $content, $post);
}
}
return $content;
}
/**
* 检查规则是否适用于当前内容
*/
private function rule_applies_to_content($rule, $post) {
switch ($rule['content_type']) {
case 'post':
return ($post->post_type == 'post' && $rule['content_id'] == $post->ID);
case 'page':
return ($post->post_type == 'page' && $rule['content_id'] == $post->ID);
case 'category':
return has_category($rule['content_id'], $post);
case 'custom':
// 自定义规则,可以扩展
return $this->check_custom_rule($rule, $post);
default:
return false;
}
}
/**
* 应用规则到内容
*/
private function apply_rule_to_content($rule, $content, $post) {
switch ($rule['action_type']) {
case 'hide':
return ''; // 完全隐藏内容
case 'replace':
return $this->get_replacement_content($rule);
case 'show':
// 默认就是


