Merge pull request 'fix(ip-geo): harden full-chain IP geolocation reliability' (#313) from worktree-ip-geo-fix into main
Reviewed-on: CYL/wordpress-theme-aksrt#313
This commit is contained in:
@@ -172,9 +172,21 @@ function matchDependsOn(dependsOn) {
|
||||
return !!props.formData[dependsOn]
|
||||
}
|
||||
if (typeof dependsOn === 'object') {
|
||||
const { key, value, not } = dependsOn
|
||||
const { key, value, values, not } = dependsOn
|
||||
const currentVal = props.formData[key]
|
||||
if (not) return currentVal !== value
|
||||
if (not) {
|
||||
if (Array.isArray(values)) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
return !values.some((v) => currentVal == v)
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
return currentVal !== value && currentVal != value
|
||||
}
|
||||
// values: match any of a list (e.g. provider ∈ [qq, polling])
|
||||
if (Array.isArray(values)) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
return values.some((v) => currentVal == v)
|
||||
}
|
||||
// Loose equality so true matches 1 / '1' from some option stores.
|
||||
// eslint-disable-next-line eqeqeq
|
||||
return currentVal == value || (!!value === true && !!currentVal)
|
||||
|
||||
@@ -44,10 +44,10 @@ export const ipSchema = {
|
||||
title: '接口凭据',
|
||||
icon: 'fas fa-key',
|
||||
fields: [
|
||||
{ key: 'ip_addr_sdk_qq_appkey', label: '腾讯 AppKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'qq' } },
|
||||
{ key: 'ip_addr_sdk_qq_secretkey', label: '腾讯 SecretKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'qq' } },
|
||||
{ key: 'ip_addr_sdk_amap_appkey', label: '高德 AppKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'amap' }, help: '必须在高德开放平台创建「Web服务」类型 Key,并开通 IP 定位。选错平台会报 USERKEY_PLAT_NOMATCH。' },
|
||||
{ key: 'ip_addr_sdk_amap_secretkey', label: '高德 SecretKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'amap' }, help: '若控制台启用了「数字签名」,必填安全密钥;未启用可留空。' },
|
||||
{ key: 'ip_addr_sdk_qq_appkey', label: '腾讯 AppKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', values: ['qq', 'polling'] }, help: '「腾讯位置服务」或「轮流查询」时填写;未填则轮询会跳过腾讯。' },
|
||||
{ key: 'ip_addr_sdk_qq_secretkey', label: '腾讯 SecretKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', values: ['qq', 'polling'] }, help: '启用 SK 签名时必填;未启用可留空。' },
|
||||
{ key: 'ip_addr_sdk_amap_appkey', label: '高德 AppKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', values: ['amap', 'polling'] }, help: '必须在高德开放平台创建「Web服务」类型 Key,并开通 IP 定位。选错平台会报 USERKEY_PLAT_NOMATCH。「轮流查询」时未填则跳过高德。' },
|
||||
{ key: 'ip_addr_sdk_amap_secretkey', label: '高德 SecretKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', values: ['amap', 'polling'] }, help: '若控制台启用了「数字签名」,必填安全密钥;未启用可留空。' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -151,18 +151,34 @@ if (!function_exists('aksrt_format_geo_label')) :
|
||||
return $label;
|
||||
}
|
||||
}
|
||||
$country = $addr['country'];
|
||||
$province = $addr['province'];
|
||||
$city = $addr['city'];
|
||||
|
||||
// Drop duplicated province+city when provider returns the same token twice.
|
||||
if ($province !== '' && $city !== '' && $province === $city) {
|
||||
$city = '';
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'province':
|
||||
return $addr['province'] !== '' ? $addr['province'] : $addr['city'];
|
||||
if ($province !== '') {
|
||||
return $province;
|
||||
}
|
||||
return $city !== '' ? $city : $country;
|
||||
case 'country_province':
|
||||
return trim($addr['country'] . ' ' . $addr['province']);
|
||||
$joined = trim($country . ' ' . $province);
|
||||
if ($joined !== '') {
|
||||
return $joined;
|
||||
}
|
||||
return $city;
|
||||
case 'province_city':
|
||||
default:
|
||||
$parts = array_filter([$addr['province'], $addr['city']]);
|
||||
if (empty($parts) && $addr['country'] !== '') {
|
||||
return $addr['country'];
|
||||
$parts = array_filter([$province, $city]);
|
||||
if (!empty($parts)) {
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
return implode(' ', $parts);
|
||||
return $country;
|
||||
}
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -50,6 +50,21 @@ if (!function_exists('aksrt_store_comment_addr')) :
|
||||
$ip = $comment instanceof WP_Comment ? (string) $comment->comment_author_IP : '';
|
||||
}
|
||||
$ip = trim((string) $ip);
|
||||
|
||||
// Private / empty stored IP (CDN edge without proxy headers): resolve live client IP once.
|
||||
$needs_live = ($ip === '' || $ip === '0.0.0.0');
|
||||
if (!$needs_live && function_exists('filter_var')) {
|
||||
$needs_live = !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
|
||||
}
|
||||
if ($needs_live && function_exists('aksrt_get_client_ip')) {
|
||||
$live = trim((string) aksrt_get_client_ip());
|
||||
if ($live !== '' && $live !== '0.0.0.0'
|
||||
&& filter_var($live, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
|
||||
) {
|
||||
$ip = $live;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ip === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
+29
-4
@@ -218,11 +218,29 @@ function aksrt_get_captcha_status_summary(): array {
|
||||
// ============================================================
|
||||
|
||||
function aksrt_get_client_ip(): string {
|
||||
$keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
|
||||
// Prefer CDN / reverse-proxy headers (Cloudflare first), then generic proxies.
|
||||
$keys = [
|
||||
'HTTP_CF_CONNECTING_IP',
|
||||
'HTTP_TRUE_CLIENT_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_REAL_IP',
|
||||
'HTTP_CLIENT_IP',
|
||||
];
|
||||
foreach ($keys as $key) {
|
||||
if (!empty($_SERVER[$key])) {
|
||||
$parts = explode(',', (string) $_SERVER[$key]);
|
||||
$ip = trim($parts[0]);
|
||||
if (empty($_SERVER[$key])) {
|
||||
continue;
|
||||
}
|
||||
// X-Forwarded-For may be a chain: client, proxy1, proxy2 — take first valid.
|
||||
$parts = explode(',', (string) $_SERVER[$key]);
|
||||
foreach ($parts as $part) {
|
||||
$ip = trim($part);
|
||||
// Strip IPv6 brackets / port noise occasionally injected by proxies.
|
||||
if (str_starts_with($ip, '[') && str_contains($ip, ']')) {
|
||||
$ip = substr($ip, 1, strpos($ip, ']') - 1);
|
||||
} elseif (substr_count($ip, ':') === 1 && str_contains($ip, '.')) {
|
||||
// IPv4:port
|
||||
$ip = explode(':', $ip, 2)[0];
|
||||
}
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
return $ip;
|
||||
}
|
||||
@@ -231,6 +249,13 @@ function aksrt_get_client_ip(): string {
|
||||
return isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
|
||||
}
|
||||
|
||||
// Native wp-comments-post + any path that omits comment_author_IP.
|
||||
// Ensures comment_author_IP (and thus aksrt_comment_addr) is the real client IP behind CDN.
|
||||
add_filter('pre_comment_user_ip', static function ($ip) {
|
||||
$resolved = aksrt_get_client_ip();
|
||||
return ($resolved !== '' && $resolved !== '0.0.0.0') ? $resolved : $ip;
|
||||
}, 5);
|
||||
|
||||
// ============================================================
|
||||
// 腾讯云 API 3.0 TC3-HMAC-SHA256 签名
|
||||
// ============================================================
|
||||
|
||||
@@ -310,6 +310,19 @@ class ExtensionController extends ControllerBase {
|
||||
$message = '';
|
||||
$ok = !empty($result['country']) || !empty($result['province']) || !empty($result['city']);
|
||||
|
||||
// Persist "verified" once a live lookup succeeds so overview cards leave "待补全".
|
||||
if ($ok) {
|
||||
$all = \AKSRT\Settings\Settings::get_all();
|
||||
if (empty($all['ip_geo_verified'])) {
|
||||
$all['ip_geo_verified'] = true;
|
||||
update_option('aksrt_settings', $all);
|
||||
if (method_exists('\AKSRT\Settings\Settings', 'flush_settings_cache')) {
|
||||
\AKSRT\Settings\Settings::flush_settings_cache();
|
||||
}
|
||||
$status = $geo->get_status();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ok) {
|
||||
$detail = trim((string) ($result['detail'] ?? ''));
|
||||
switch ($reason) {
|
||||
|
||||
@@ -27,8 +27,8 @@ class SchemaExtensionsIP {
|
||||
['key' => 'ip_geo_cache_ttl', 'label' => '缓存时间 (秒)', 'type' => 'number', 'default' => 86400, 'min' => 3600, 'max' => 604800],
|
||||
]],
|
||||
['key' => 'providers', 'title' => '接口凭据', 'fields' => [
|
||||
['key' => 'ip_addr_sdk_qq_appkey', 'label' => '腾讯 AppKey', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'ip_addr_sdk_qq_secretkey', 'label' => '腾讯 SecretKey', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'ip_addr_sdk_qq_appkey', 'label' => '腾讯 AppKey', 'type' => 'input', 'default' => '', 'help' => '「腾讯位置服务」或「轮流查询」时填写'],
|
||||
['key' => 'ip_addr_sdk_qq_secretkey', 'label' => '腾讯 SecretKey', 'type' => 'input', 'default' => '', 'help' => '启用 SK 签名时必填'],
|
||||
['key' => 'ip_addr_sdk_amap_appkey', 'label' => '高德 AppKey', 'type' => 'input', 'default' => '', 'help' => '须为「Web服务」Key,并开通 IP 定位'],
|
||||
['key' => 'ip_addr_sdk_amap_secretkey', 'label' => '高德 SecretKey', 'type' => 'input', 'default' => '', 'help' => '启用数字签名时必填'],
|
||||
]],
|
||||
|
||||
@@ -92,25 +92,76 @@ trait ModerationPart2 {
|
||||
}
|
||||
|
||||
private function lookup_geoip(string $ip, array $config): ?string {
|
||||
// 优先复用 IP 归属地模块的查询能力(含缓存)
|
||||
if (class_exists('\AKSRT\Extensions\IPGeo')) {
|
||||
$result = \AKSRT\Extensions\IPGeo::get_instance()->lookup($ip);
|
||||
if (!empty($result['country'])) {
|
||||
return $result['country'];
|
||||
// Dedicated ISO country-code path for block lists (CN/US/…).
|
||||
// IPGeo returns localized names ("中国") which are not ISO codes.
|
||||
$provider = sanitize_key((string) ($config['geoip_provider'] ?? 'ipapi'));
|
||||
$api_key = (string) ($config['geoip_api_key'] ?? '');
|
||||
|
||||
if ($provider === 'ipinfo' || $provider === 'ipinfo.io') {
|
||||
$code = $this->geoip_ipinfo($ip, $api_key);
|
||||
if ($code !== null) {
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
|
||||
// 回退到内置查询
|
||||
$ip_geo_config = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_ip_geo_settings()
|
||||
: [];
|
||||
$provider = (string) ($ip_geo_config['ip_geo_provider'] ?? $config['geoip_provider'] ?? 'ip-api.com');
|
||||
$api_key = (string) ($ip_geo_config['ip_geo_api_key'] ?? $config['geoip_api_key'] ?? '');
|
||||
|
||||
if ($provider === 'ipinfo' || $provider === 'ipinfo.io') {
|
||||
return $this->geoip_ipinfo($ip, $api_key);
|
||||
$code = $this->geoip_ipapi($ip);
|
||||
if ($code !== null) {
|
||||
return $code;
|
||||
}
|
||||
return $this->geoip_ipapi($ip);
|
||||
|
||||
// Last resort: map IPGeo country label → ISO when possible.
|
||||
if (class_exists('\AKSRT\Extensions\IPGeo')) {
|
||||
$result = \AKSRT\Extensions\IPGeo::get_instance()->lookup($ip);
|
||||
$label = trim((string) ($result['country'] ?? ''));
|
||||
if ($label !== '') {
|
||||
return $this->country_label_to_iso($label);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map common country labels / ISO aliases to ISO-3166 alpha-2.
|
||||
*/
|
||||
private function country_label_to_iso(string $label): ?string {
|
||||
$raw = trim($label);
|
||||
if ($raw === '') {
|
||||
return null;
|
||||
}
|
||||
if (preg_match('/^[A-Za-z]{2}$/', $raw)) {
|
||||
return strtoupper($raw);
|
||||
}
|
||||
$map = [
|
||||
'中国' => 'CN', '中华人民共和国' => 'CN', 'china' => 'CN', 'cn' => 'CN',
|
||||
'美国' => 'US', '美利坚合众国' => 'US', 'united states' => 'US', 'usa' => 'US', 'us' => 'US',
|
||||
'日本' => 'JP', 'japan' => 'JP', 'jp' => 'JP',
|
||||
'韩国' => 'KR', '大韩民国' => 'KR', 'south korea' => 'KR', 'korea' => 'KR', 'kr' => 'KR',
|
||||
'香港' => 'HK', '中国香港' => 'HK', 'hong kong' => 'HK', 'hk' => 'HK',
|
||||
'澳门' => 'MO', '中国澳门' => 'MO', 'macao' => 'MO', 'macau' => 'MO', 'mo' => 'MO',
|
||||
'台湾' => 'TW', '中国台湾' => 'TW', 'taiwan' => 'TW', 'tw' => 'TW',
|
||||
'新加坡' => 'SG', 'singapore' => 'SG', 'sg' => 'SG',
|
||||
'英国' => 'GB', 'united kingdom' => 'GB', 'uk' => 'GB', 'gb' => 'GB',
|
||||
'德国' => 'DE', 'germany' => 'DE', 'de' => 'DE',
|
||||
'法国' => 'FR', 'france' => 'FR', 'fr' => 'FR',
|
||||
'俄罗斯' => 'RU', 'russia' => 'RU', 'ru' => 'RU',
|
||||
'澳大利亚' => 'AU', 'australia' => 'AU', 'au' => 'AU',
|
||||
'加拿大' => 'CA', 'canada' => 'CA', 'ca' => 'CA',
|
||||
'印度' => 'IN', 'india' => 'IN', 'in' => 'IN',
|
||||
'越南' => 'VN', 'vietnam' => 'VN', 'vn' => 'VN',
|
||||
'泰国' => 'TH', 'thailand' => 'TH', 'th' => 'TH',
|
||||
'马来西亚' => 'MY', 'malaysia' => 'MY', 'my' => 'MY',
|
||||
'菲律宾' => 'PH', 'philippines' => 'PH', 'ph' => 'PH',
|
||||
'印度尼西亚' => 'ID', 'indonesia' => 'ID', 'id' => 'ID',
|
||||
];
|
||||
$key = function_exists('mb_strtolower') ? mb_strtolower($raw, 'UTF-8') : strtolower($raw);
|
||||
if (isset($map[$raw])) {
|
||||
return $map[$raw];
|
||||
}
|
||||
if (isset($map[$key])) {
|
||||
return $map[$key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +205,16 @@ trait ModerationPart2 {
|
||||
// ============================================================
|
||||
|
||||
private function get_client_ip(): string {
|
||||
$keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
|
||||
if (function_exists('aksrt_get_client_ip')) {
|
||||
return (string) aksrt_get_client_ip();
|
||||
}
|
||||
$keys = [
|
||||
'HTTP_CF_CONNECTING_IP',
|
||||
'HTTP_TRUE_CLIENT_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_REAL_IP',
|
||||
'HTTP_CLIENT_IP',
|
||||
];
|
||||
foreach ($keys as $key) {
|
||||
if (!empty($_SERVER[$key])) {
|
||||
$parts = explode(',', (string) $_SERVER[$key]);
|
||||
|
||||
@@ -292,7 +292,10 @@ class CommentActions {
|
||||
'comment_content' => $content,
|
||||
'comment_parent' => $parent,
|
||||
'comment_type' => '',
|
||||
'comment_author_IP' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash((string) $_SERVER['REMOTE_ADDR'])) : '',
|
||||
// Prefer real client IP behind CDN/proxy (REMOTE_ADDR alone is often edge IP).
|
||||
'comment_author_IP' => function_exists('aksrt_get_client_ip')
|
||||
? (string) aksrt_get_client_ip()
|
||||
: (isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash((string) $_SERVER['REMOTE_ADDR'])) : ''),
|
||||
'comment_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash((string) $_SERVER['HTTP_USER_AGENT'])) : '',
|
||||
'comment_date' => current_time('mysql'),
|
||||
'comment_date_gmt' => current_time('mysql', true),
|
||||
|
||||
@@ -127,14 +127,19 @@ class APIAudit {
|
||||
* Get client IP
|
||||
*/
|
||||
private function get_client_ip(): string {
|
||||
if (function_exists('aksrt_get_client_ip')) {
|
||||
return sanitize_text_field((string) aksrt_get_client_ip());
|
||||
}
|
||||
$ip = '';
|
||||
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$parts = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
|
||||
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
|
||||
$ip = (string) $_SERVER['HTTP_CF_CONNECTING_IP'];
|
||||
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$parts = explode(',', (string) $_SERVER['HTTP_X_FORWARDED_FOR']);
|
||||
$ip = trim($parts[0]);
|
||||
} elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
|
||||
$ip = $_SERVER['HTTP_X_REAL_IP'];
|
||||
$ip = (string) $_SERVER['HTTP_X_REAL_IP'];
|
||||
} elseif (!empty($_SERVER['REMOTE_ADDR'])) {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
$ip = (string) $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
return sanitize_text_field($ip);
|
||||
}
|
||||
|
||||
@@ -45,19 +45,31 @@ class IPGeo {
|
||||
|
||||
$cache_key = 'aksrt_ip_geo_' . md5($ip);
|
||||
$ttl = max(3600, (int) ($config['ip_geo_cache_ttl'] ?? 86400));
|
||||
// Negative cache: avoid hammering providers on repeated failures (admin force skips).
|
||||
$neg_key = 'aksrt_ip_geo_neg_' . md5($ip);
|
||||
$neg_ttl = 600;
|
||||
|
||||
if (!$force) {
|
||||
$cached = get_transient($cache_key);
|
||||
if ($cached !== false && is_array($cached)) {
|
||||
return $cached;
|
||||
}
|
||||
$neg = get_transient($neg_key);
|
||||
if ($neg !== false && is_array($neg)) {
|
||||
return $neg;
|
||||
}
|
||||
}
|
||||
|
||||
$provider = sanitize_key((string) ($config['ip_addr_sdk'] ?? 'null'));
|
||||
$result = $this->query_provider($provider, $ip, $config);
|
||||
|
||||
if ($this->has_location($result)) {
|
||||
// Strip bulky raw payloads before caching.
|
||||
$store = $result;
|
||||
unset($store['raw']);
|
||||
if (!$force) {
|
||||
set_transient($cache_key, $result, $ttl);
|
||||
set_transient($cache_key, $store, $ttl);
|
||||
delete_transient($neg_key);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
@@ -66,6 +78,13 @@ class IPGeo {
|
||||
$result['reason'] = 'provider_empty';
|
||||
}
|
||||
|
||||
// Cache miss with reason so front-end doesn't retry every page view.
|
||||
if (!$force) {
|
||||
$store = $result;
|
||||
unset($store['raw']);
|
||||
set_transient($neg_key, $store, $neg_ttl);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -85,11 +104,16 @@ class IPGeo {
|
||||
$missing[] = 'ip_addr_sdk_amap_appkey';
|
||||
}
|
||||
}
|
||||
// polling / null (free): no hard credential requirements.
|
||||
|
||||
// Free + polling are always "configured" once enabled; paid need keys.
|
||||
// verified flips after a successful admin test (or any write of ip_geo_verified).
|
||||
$configured = $enabled && empty($missing);
|
||||
|
||||
return [
|
||||
'enabled' => $enabled,
|
||||
'provider' => $provider,
|
||||
'configured' => $enabled && empty($missing),
|
||||
'configured' => $configured,
|
||||
'available' => $enabled,
|
||||
'verified' => !empty($config['ip_geo_verified']),
|
||||
'transport' => 'http_api',
|
||||
@@ -110,11 +134,19 @@ class IPGeo {
|
||||
$province = $this->string_field($geo['province'] ?? '');
|
||||
$city = $this->string_field($geo['city'] ?? '');
|
||||
|
||||
if ($province !== '' && $city !== '' && $province === $city) {
|
||||
$city = '';
|
||||
}
|
||||
|
||||
switch ($format) {
|
||||
case 'province':
|
||||
return $province !== '' ? $province : $city;
|
||||
if ($province !== '') {
|
||||
return $province;
|
||||
}
|
||||
return $city !== '' ? $city : $country;
|
||||
case 'country_province':
|
||||
return trim($country . ' ' . $province);
|
||||
$joined = trim($country . ' ' . $province);
|
||||
return $joined !== '' ? $joined : $city;
|
||||
case 'province_city':
|
||||
default:
|
||||
$parts = array_filter([$province, $city]);
|
||||
@@ -255,6 +287,19 @@ class IPGeo {
|
||||
]);
|
||||
}
|
||||
|
||||
// Amap IP API is IPv4-only; free path for IPv6.
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$free = $this->query_ip_api($ip);
|
||||
if ($this->has_location($free)) {
|
||||
$free['detail'] = 'amap_ipv6_fallback_ip_api';
|
||||
return $free;
|
||||
}
|
||||
return $this->empty_result('provider_empty', [
|
||||
'provider' => 'amap',
|
||||
'detail' => 'amap_ipv4_only',
|
||||
]);
|
||||
}
|
||||
|
||||
// Amap sign: md5(sorted query without sig + secret) — zibll uses ip=&key= order.
|
||||
$base_query = 'ip=' . $ip . '&key=' . $appkey;
|
||||
$url = 'https://restapi.amap.com/v3/ip?' . $base_query;
|
||||
@@ -374,6 +419,13 @@ class IPGeo {
|
||||
$country = $this->string_field($addr[1] ?? '');
|
||||
|
||||
if ($province === '' && $city_s === '') {
|
||||
// HTTP OK but unusable body (block page / CAPTCHA / empty) → free path.
|
||||
$fallback = $this->query_ip_api($ip);
|
||||
if ($this->has_location($fallback)) {
|
||||
$fallback['provider'] = 'ip-api';
|
||||
$fallback['detail'] = 'pconline_parse_failed_fallback_ip_api';
|
||||
return $fallback;
|
||||
}
|
||||
return $this->empty_result('provider_empty', [
|
||||
'provider' => 'pconline',
|
||||
'detail' => 'parse_failed',
|
||||
|
||||
@@ -53,19 +53,20 @@ class ExtensionRuntime {
|
||||
$provider = 'null';
|
||||
}
|
||||
|
||||
$display_raw = $settings['ip_geo_display'] ?? ['comment', 'user_home', 'user_center', 'post'];
|
||||
$display = is_array($display_raw) ? $display_raw : [];
|
||||
$allowed_display = ['comment', 'user_home', 'user_center', 'post'];
|
||||
$display_norm = [];
|
||||
foreach ($display as $item) {
|
||||
$key = sanitize_key((string) $item);
|
||||
if (in_array($key, $allowed_display, true) && !in_array($key, $display_norm, true)) {
|
||||
$display_norm[] = $key;
|
||||
}
|
||||
}
|
||||
// Empty / missing → full four surfaces (canonical default).
|
||||
if ($display_norm === []) {
|
||||
// Only fill defaults when the key was never saved — empty array means "none".
|
||||
if (!array_key_exists('ip_geo_display', $settings)) {
|
||||
$display_norm = $allowed_display;
|
||||
} else {
|
||||
$display_raw = $settings['ip_geo_display'];
|
||||
$display = is_array($display_raw) ? $display_raw : [];
|
||||
$display_norm = [];
|
||||
foreach ($display as $item) {
|
||||
$key = sanitize_key((string) $item);
|
||||
if (in_array($key, $allowed_display, true) && !in_array($key, $display_norm, true)) {
|
||||
$display_norm[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$format = sanitize_key((string) ($settings['ip_geo_format'] ?? 'province_city'));
|
||||
|
||||
Reference in New Issue
Block a user