*/ 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'], '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 { $c = aksrt_get_ip_geo_config(); return !empty($c['ip_geo_enabled']); } endif; if (!function_exists('aksrt_ip_geo_display_enabled')) : /** * Whether a surface may show IP 归属地 badges. * * @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); $c = aksrt_get_ip_geo_config(); $display = $c['ip_geo_display'] ?? []; if (!is_array($display)) { $display = [$display]; } $display = array_values(array_unique(array_filter(array_map('sanitize_key', $display)))); // Legacy saves only had "comment". When master is on, expand so user/post // surfaces work without forcing a re-save of the checkbox group. if ($display === [] || $display === ['comment']) { $display = ['comment', 'user_home', 'user_center', 'post']; } // Zibll: user home/center are gated by user_city_s, not a separate surface list. // Still honour an explicit opt-out if the admin removed them from the list after upgrade. if (in_array($location, ['user_home', 'user_center'], true)) { if (!aksrt_user_city_enabled()) { return false; } // If list explicitly includes any user surface, require membership; else allow. $has_user_keys = (bool) array_intersect($display, ['user_home', 'user_center']); if ($has_user_keys) { return in_array($location, $display, true); } return true; } return in_array($location, $display, true); } endif; if (!function_exists('aksrt_ip_geo_format_key')) : function aksrt_ip_geo_format_key(): string { $c = aksrt_get_ip_geo_config(); $format = sanitize_key((string) ($c['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 switch (user home / user center), independent of comment city_s. */ function aksrt_user_city_enabled(): bool { if (!class_exists('\AKSRT\Settings\Settings')) { return true; // zibll default on } $all = \AKSRT\Settings\Settings::get_all(); // Unset → on (zibll _pz default true). Explicit false turns off. if (!array_key_exists('user_city_s', $all)) { return true; } return !empty($all['user_city_s']); } endif; if (!function_exists('aksrt_user_city_type')) : /** * User-facing precision (maps to shared format keys). */ 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'] ?? '')); // zibll: pro / city if ($type === 'pro' || $type === 'province') { return 'province'; } if ($type === 'city' || $type === 'province_city') { return 'province_city'; } if ($type === 'country_province') { return 'country_province'; } 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' => '']; } // Accept zibll-shaped nation → country. $country = trim((string) ($raw['country'] ?? $raw['nation'] ?? '')); return [ 'country' => $country, 'province' => trim((string) ($raw['province'] ?? '')), 'city' => trim((string) ($raw['city'] ?? '')), ]; } endif; if (!function_exists('aksrt_format_geo_label')) : /** * @param array $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; } } // Local fallback (mirrors IPGeo::format). 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|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 '' . esc_html($label) . ''; } 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 current client IP and persist user meta. * Zibll: refresh on every login when user_city_s is on. * Also refresh when article (post) display needs author addr. */ 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() || aksrt_ip_geo_display_enabled('post') || aksrt_ip_geo_display_enabled('user_home') || aksrt_ip_geo_display_enabled('user_center'); 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')) : /** * @param int $user_id User ID. * @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' => '']; } $raw = get_user_meta($user_id, 'aksrt_user_addr', true); // Legacy / accidental zibll key. if ((!is_array($raw) || empty($raw)) && metadata_exists('user', $user_id, 'user_addr')) { $raw = get_user_meta($user_id, 'user_addr', true); } $addr = aksrt_normalize_geo_addr($raw); if ($addr['country'] !== '' || $addr['province'] !== '' || $addr['city'] !== '') { return $addr; } // Lazy backfill from stored IPs so pages work before next login. $ips = []; foreach (['aksrt_register_ip', 'register_ip'] as $k) { $v = trim((string) get_user_meta($user_id, $k, true)); if ($v !== '') { $ips[] = $v; } } // Own profile: refresh from current client IP (zibll updates on login; this covers open sessions). if ((int) get_current_user_id() === $user_id) { $cur = function_exists('aksrt_get_client_ip') ? (string) aksrt_get_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''); $cur = trim($cur); if ($cur !== '') { array_unshift($ips, $cur); } } $ips = array_values(array_unique(array_filter($ips))); foreach ($ips as $ip) { $looked = aksrt_lookup_geo_for_ip($ip); if ($looked['country'] !== '' || $looked['province'] !== '' || $looked['city'] !== '') { update_user_meta($user_id, 'aksrt_user_addr', $looked); return $looked; } } return $addr; } endif; if (!function_exists('aksrt_get_user_city_badge_html')) : /** * Badge for user home / user center (requires user_city_s + display location). * * @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) { return ''; } // aksrt_ip_geo_display_enabled already enforces master + user_city for user surfaces. if (!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 detail author IP 属地 (uses author last-login 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 ''; } // Prefer user_city type when feature on; else global format. $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; // ── Hooks: persist on login / register ────────────────────────────────────── 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 && function_exists('aksrt_store_user_addr')) { 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); } if (function_exists('aksrt_store_user_addr')) { aksrt_store_user_addr($user_id, $ip !== '' ? $ip : null); } }, 20, 1);