Files
wordpress-theme-aksrt/inc/helpers/aksrt-ip-geo.php
T
kate522 3bb7fb0ba6 refactor(ip-geo): drop dual-read gates; keep single meta and four surfaces
Canonical keys only (ip_geo_display defaults to all four, aksrt_user_addr /
aksrt_comment_addr only). Self-view still resolves current IP on read.
2026-07-25 15:14:29 +08:00

321 lines
9.8 KiB
PHP

<?php
/**
* IP 归属地 — storage, format, and display (no dual-read / no legacy keys).
*
* Gates:
* master: ip_geo_enabled
* surface: ip_geo_display ∈ {comment, user_home, user_center, post}
* users: user_city_s (+ user_city_type)
* comments: comment_city_s (+ comment_city_type) via comments/geo.php
*
* Meta:
* comment → aksrt_comment_addr {country,province,city}
* user → aksrt_user_addr, aksrt_register_ip
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_get_ip_geo_config')) :
/**
* @return array<string,mixed>
*/
function aksrt_get_ip_geo_config(): array {
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_ip_geo_settings')) {
return \AKSRT\Settings\Settings::get_ip_geo_settings();
}
return [
'ip_geo_enabled' => false,
'ip_geo_display' => ['comment', 'user_home', 'user_center', 'post'],
'ip_geo_format' => 'province_city',
'ip_geo_fallback_text' => '',
];
}
endif;
if (!function_exists('aksrt_ip_geo_master_enabled')) :
function aksrt_ip_geo_master_enabled(): bool {
return !empty(aksrt_get_ip_geo_config()['ip_geo_enabled']);
}
endif;
if (!function_exists('aksrt_ip_geo_display_list')) :
/**
* @return list<string>
*/
function aksrt_ip_geo_display_list(): array {
$c = aksrt_get_ip_geo_config();
$display = $c['ip_geo_display'] ?? [];
if (!is_array($display)) {
$display = [];
}
$allowed = ['comment', 'user_home', 'user_center', 'post'];
$out = [];
foreach ($display as $item) {
$key = sanitize_key((string) $item);
if (in_array($key, $allowed, true) && !in_array($key, $out, true)) {
$out[] = $key;
}
}
return $out;
}
endif;
if (!function_exists('aksrt_ip_geo_display_enabled')) :
/**
* @param string $location comment|user_home|user_center|post
*/
function aksrt_ip_geo_display_enabled(string $location): bool {
if (!aksrt_ip_geo_master_enabled()) {
return false;
}
$location = sanitize_key($location);
if (!in_array($location, aksrt_ip_geo_display_list(), true)) {
return false;
}
if (in_array($location, ['user_home', 'user_center'], true) && !aksrt_user_city_enabled()) {
return false;
}
return true;
}
endif;
if (!function_exists('aksrt_ip_geo_format_key')) :
function aksrt_ip_geo_format_key(): string {
$format = sanitize_key((string) (aksrt_get_ip_geo_config()['ip_geo_format'] ?? 'province_city'));
return in_array($format, ['province_city', 'province', 'country_province'], true)
? $format
: 'province_city';
}
endif;
if (!function_exists('aksrt_user_city_enabled')) :
/**
* Users feature: show city on user home / user center.
* Default true when key absent (zibll parity for new installs).
*/
function aksrt_user_city_enabled(): bool {
if (!class_exists('\AKSRT\Settings\Settings')) {
return true;
}
$all = \AKSRT\Settings\Settings::get_all();
if (!array_key_exists('user_city_s', $all)) {
return true;
}
return !empty($all['user_city_s']);
}
endif;
if (!function_exists('aksrt_user_city_type')) :
function aksrt_user_city_type(): string {
if (!class_exists('\AKSRT\Settings\Settings')) {
return aksrt_ip_geo_format_key();
}
$all = \AKSRT\Settings\Settings::get_all();
$type = sanitize_key((string) ($all['user_city_type'] ?? ''));
if (in_array($type, ['province_city', 'province', 'country_province'], true)) {
return $type;
}
return aksrt_ip_geo_format_key();
}
endif;
if (!function_exists('aksrt_normalize_geo_addr')) :
/**
* @param mixed $raw Meta value.
* @return array{country:string,province:string,city:string}
*/
function aksrt_normalize_geo_addr($raw): array {
if (!is_array($raw)) {
return ['country' => '', 'province' => '', 'city' => ''];
}
return [
'country' => trim((string) ($raw['country'] ?? '')),
'province' => trim((string) ($raw['province'] ?? '')),
'city' => trim((string) ($raw['city'] ?? '')),
];
}
endif;
if (!function_exists('aksrt_format_geo_label')) :
/**
* @param array<string,string> $addr Addr.
* @param string $type Format key.
*/
function aksrt_format_geo_label(array $addr, string $type = 'province_city'): string {
$addr = aksrt_normalize_geo_addr($addr);
if (class_exists('\AKSRT\Extensions\IPGeo')) {
$label = \AKSRT\Extensions\IPGeo::get_instance()->format($addr, $type);
if (is_string($label) && $label !== '') {
return $label;
}
}
switch ($type) {
case 'province':
return $addr['province'] !== '' ? $addr['province'] : $addr['city'];
case 'country_province':
return trim($addr['country'] . ' ' . $addr['province']);
case 'province_city':
default:
$parts = array_filter([$addr['province'], $addr['city']]);
if (empty($parts) && $addr['country'] !== '') {
return $addr['country'];
}
return implode(' ', $parts);
}
}
endif;
if (!function_exists('aksrt_get_geo_badge_html')) :
/**
* @param array<string,string>|mixed $addr Addr meta.
* @param string $type Format.
* @param string $class CSS classes.
* @param string $title Title attr.
*/
function aksrt_get_geo_badge_html($addr, string $type = 'province_city', string $class = 'badg badg-sm', string $title = ''): string {
$label = aksrt_format_geo_label(is_array($addr) ? $addr : [], $type);
if ($label === '') {
$fallback = (string) (aksrt_get_ip_geo_config()['ip_geo_fallback_text'] ?? '');
if ($fallback === '') {
return '';
}
$label = $fallback;
}
if ($title === '') {
$title = __('IP 属地', 'aksrt');
}
$class = trim($class . ' aksrt-ip-geo-badge');
return '<span class="' . esc_attr($class) . '" title="' . esc_attr($title) . '">' . esc_html($label) . '</span>';
}
endif;
if (!function_exists('aksrt_lookup_geo_for_ip')) :
/**
* @return array{country:string,province:string,city:string}
*/
function aksrt_lookup_geo_for_ip(string $ip): array {
$ip = trim($ip);
if ($ip === '' || !class_exists('\AKSRT\Extensions\IPGeo')) {
return ['country' => '', 'province' => '', 'city' => ''];
}
$geo = \AKSRT\Extensions\IPGeo::get_instance()->lookup($ip);
return aksrt_normalize_geo_addr(is_array($geo) ? $geo : []);
}
endif;
if (!function_exists('aksrt_store_user_addr')) :
/**
* Resolve IP and write aksrt_user_addr (login / register / own-page refresh).
*/
function aksrt_store_user_addr(int $user_id, ?string $ip = null): void {
if ($user_id <= 0 || !aksrt_ip_geo_master_enabled()) {
return;
}
$need = aksrt_user_city_enabled()
|| in_array('post', aksrt_ip_geo_display_list(), true)
|| in_array('user_home', aksrt_ip_geo_display_list(), true)
|| in_array('user_center', aksrt_ip_geo_display_list(), true);
if (!$need) {
return;
}
if ($ip === null || $ip === '') {
$ip = function_exists('aksrt_get_client_ip') ? (string) aksrt_get_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? '');
}
$ip = trim($ip);
if ($ip === '') {
return;
}
$addr = aksrt_lookup_geo_for_ip($ip);
if ($addr['country'] === '' && $addr['province'] === '' && $addr['city'] === '') {
return;
}
update_user_meta($user_id, 'aksrt_user_addr', $addr);
}
endif;
if (!function_exists('aksrt_get_user_addr')) :
/**
* Read aksrt_user_addr; if empty and viewing self, resolve current client IP once.
*
* @return array{country:string,province:string,city:string}
*/
function aksrt_get_user_addr(int $user_id): array {
if ($user_id <= 0) {
return ['country' => '', 'province' => '', 'city' => ''];
}
$addr = aksrt_normalize_geo_addr(get_user_meta($user_id, 'aksrt_user_addr', true));
if ($addr['country'] !== '' || $addr['province'] !== '' || $addr['city'] !== '') {
return $addr;
}
// Self view: write-on-read from current IP (same outcome as next login).
if ((int) get_current_user_id() === $user_id && aksrt_ip_geo_master_enabled()) {
aksrt_store_user_addr($user_id);
return aksrt_normalize_geo_addr(get_user_meta($user_id, 'aksrt_user_addr', true));
}
return $addr;
}
endif;
if (!function_exists('aksrt_get_user_city_badge_html')) :
/**
* @param int $user_id User.
* @param string $location user_home|user_center
* @param string $class CSS.
*/
function aksrt_get_user_city_badge_html(int $user_id, string $location = 'user_home', string $class = 'badg'): string {
if ($user_id <= 0 || !aksrt_ip_geo_display_enabled($location)) {
return '';
}
return aksrt_get_geo_badge_html(aksrt_get_user_addr($user_id), aksrt_user_city_type(), $class);
}
endif;
if (!function_exists('aksrt_get_post_author_city_badge_html')) :
/**
* Article author IP 属地 (author aksrt_user_addr).
*/
function aksrt_get_post_author_city_badge_html(int $author_id = 0, string $class = 'badg badg-sm'): string {
if (!aksrt_ip_geo_display_enabled('post')) {
return '';
}
if ($author_id <= 0) {
$author_id = (int) get_the_author_meta('ID');
}
if ($author_id <= 0) {
return '';
}
$type = aksrt_user_city_enabled() ? aksrt_user_city_type() : aksrt_ip_geo_format_key();
return aksrt_get_geo_badge_html(aksrt_get_user_addr($author_id), $type, $class);
}
endif;
add_action('wp_login', static function ($user_login, $user = null): void {
$user_id = 0;
if ($user instanceof WP_User) {
$user_id = (int) $user->ID;
} elseif (is_string($user_login) && $user_login !== '') {
$u = get_user_by('login', $user_login);
$user_id = $u instanceof WP_User ? (int) $u->ID : 0;
}
if ($user_id > 0) {
aksrt_store_user_addr($user_id);
}
}, 20, 2);
add_action('user_register', static function ($user_id): void {
$user_id = (int) $user_id;
if ($user_id <= 0) {
return;
}
$ip = function_exists('aksrt_get_client_ip') ? (string) aksrt_get_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? '');
$ip = trim($ip);
if ($ip !== '') {
update_user_meta($user_id, 'aksrt_register_ip', $ip);
}
aksrt_store_user_addr($user_id, $ip !== '' ? $ip : null);
}, 20, 1);