Merge pull request 'fix(user): show IP city on home/center and force /user + profile templates' (#297) from worktree-ip-location-display into main

Reviewed-on: CYL/wordpress-theme-aksrt#297
This commit is contained in:
2026-07-25 15:04:24 +08:00
6 changed files with 163 additions and 49 deletions
+61 -5
View File
@@ -46,13 +46,35 @@ if (!function_exists('aksrt_ip_geo_display_enabled')) :
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_map('sanitize_key', $display);
return in_array(sanitize_key($location), $display, true);
$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;
@@ -72,9 +94,13 @@ if (!function_exists('aksrt_user_city_enabled')) :
*/
function aksrt_user_city_enabled(): bool {
if (!class_exists('\AKSRT\Settings\Settings')) {
return false;
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;
@@ -238,7 +264,36 @@ if (!function_exists('aksrt_get_user_addr')) :
if ((!is_array($raw) || empty($raw)) && metadata_exists('user', $user_id, 'user_addr')) {
$raw = get_user_meta($user_id, 'user_addr', true);
}
return aksrt_normalize_geo_addr($raw);
$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;
@@ -251,9 +306,10 @@ if (!function_exists('aksrt_get_user_city_badge_html')) :
* @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_user_city_enabled()) {
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 '';
}
+1 -1
View File
@@ -348,7 +348,7 @@ if (!function_exists('aksrt_single_render_author_card')) :
if ($author_id <= 0) {
return;
}
$author_url = get_author_posts_url($author_id);
$author_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($author_id) : get_author_posts_url($author_id);
$author_name = get_the_author();
$bio = trim((string) get_the_author_meta('description'));
$name_html = function_exists('aksrt_get_user_name')
+1 -1
View File
@@ -57,7 +57,7 @@ if (!function_exists('aksrt_get_comments_user_name_html')) :
if ($user_id > 0) {
$user = get_userdata($user_id);
$display = $user instanceof WP_User ? $user->display_name : get_comment_author($comment);
$url = get_author_posts_url($user_id);
$url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
$name = '<a class="text-ellipsis font-bold aksrt-comment__author" href="' . esc_url($url) . '">' . esc_html($display) . '</a>';
if (function_exists('aksrt_render_user_identity_chips')) {
$name .= aksrt_render_user_identity_chips($user_id, 'comment');
+89 -39
View File
@@ -58,37 +58,46 @@ add_filter('template_include', function ($template) {
return $resolved_path;
}
return $template;
global $_aksrt_special_templates;
// A) /user/{昵称} 路由 → 加载公开主页模板
$nicename = get_query_var('user_nicename');
if (!empty($nicename)) {
// A) /user/{昵称} even if special-path helper returned empty (user missing → 404).
$nicename = (string) get_query_var('user_nicename');
if ($nicename !== '') {
$user = get_user_by('slug', $nicename);
if (!$user) return $template; // 不存在的用户 → 默认 404
$tpl = aksrt_locate_template_file('templates/pages/page-user-home.php');
if (!$tpl) {
// 兜底:如果新模板不存在,回退到旧 profile 模板
$tpl = aksrt_locate_template_file($_aksrt_special_templates['profile']['file']);
if ($user instanceof WP_User) {
$tpl = aksrt_locate_template_file('templates/pages/page-user-home.php');
if ($tpl) {
global $aksrt_profile_user;
$aksrt_profile_user = $user;
return $tpl;
}
}
if (!$tpl) return $template;
global $aksrt_profile_user;
$aksrt_profile_user = $user;
return $tpl;
// Unknown nicename: force 404 instead of blank page.
global $wp_query;
if ($wp_query instanceof WP_Query) {
$wp_query->set_404();
status_header(404);
nocache_headers();
}
$four = get_query_template('404');
return $four ?: $template;
}
// B) 普通页面 → 检测是否使用了我们的特殊模板,确保加载正确的模板文件
// (防止 WP 在某些情况下未能正确解析自定义页面模板)
// B) profile slug page without rewrite var (permalink edge).
if (is_page()) {
$page_id = get_queried_object_id();
if ($page_id) {
$slug = (string) get_post_field('post_name', $page_id);
$page_template = get_post_meta($page_id, '_wp_page_template', true);
if ($page_template && strpos($page_template, 'templates/pages/') === 0) {
if ($slug === 'profile' || $page_template === 'templates/pages/page-profile.php') {
$tpl = aksrt_locate_template_file('templates/pages/page-profile.php');
if ($tpl) {
return $tpl;
}
}
if (is_string($page_template) && strpos($page_template, 'templates/pages/') === 0) {
$tpl = aksrt_locate_template_file($page_template);
if ($tpl) return $tpl;
if ($tpl) {
return $tpl;
}
}
}
}
@@ -136,26 +145,28 @@ add_filter('template_include', function ($template) {
add_action('init', function () {
global $_aksrt_special_templates;
// 后台/AJAX:检查并创建缺失页面
// 后台/AJAX:检查并创建缺失页面(含 profile 用户中心)
$pages_bootstrapped = get_option('aksrt_template_pages_bootstrapped');
$requested_template_key = (!is_admin() && !wp_doing_ajax()) ? aksrt_get_requested_template_key() : '';
if (is_admin() || wp_doing_ajax() || $pages_bootstrapped !== AKSRT_VERSION) {
foreach ($_aksrt_special_templates as $key => $cfg) {
if ($key === 'profile') {
continue;
}
aksrt_ensure_template_page($key);
}
if ($pages_bootstrapped !== AKSRT_VERSION) {
update_option('aksrt_template_pages_bootstrapped', AKSRT_VERSION);
}
} elseif ($requested_template_key !== '' && $requested_template_key !== 'profile') {
} elseif ($requested_template_key !== '') {
aksrt_ensure_template_page($requested_template_key);
}
// 统一 flush 检查
$rewrite_version = AKSRT_VERSION . ':orders-cart-revenue-routing-v1';
// Always re-assert profile page template (blank WP page with slug profile is common).
if (function_exists('aksrt_ensure_template_page')) {
aksrt_ensure_template_page('profile');
}
// 统一 flush 检查 — bump when user-home rewrite / author override changes
$rewrite_version = AKSRT_VERSION . ':user-home-profile-routing-v2';
$flushed = get_option('aksrt_user_rules_flushed');
if ($flushed !== $rewrite_version) {
flush_rewrite_rules(false);
@@ -168,24 +179,63 @@ add_action('save_post', function ($pid) { if (get_post_type($pid) === 'page') ak
add_action('delete_post', function ($pid) { if (get_post_type($pid) === 'page') aksrt_clear_template_page_cache(); });
// ============================================================
// 六、作者链接改写
// 六、作者链接改写 — 强制覆盖 WP 默认 /author/{slug}/
// ============================================================
add_filter('author_link', function ($link, $author_id) {
$author_id = (int) $author_id;
if ($author_id > 0 && function_exists('aksrt_get_user_home_url')) {
return aksrt_get_user_home_url($author_id);
}
$user = get_userdata($author_id);
$slug = $user ? $user->user_nicename : 'user-' . $author_id;
$slug = $user ? $user->user_nicename : ('user-' . max(0, $author_id));
return home_url('/user/' . trailingslashit($slug));
}, 10, 2);
}, 99, 2);
// Pretty-permalink author archives → public user home.
add_action('template_redirect', function () {
if (!is_author()) return;
$author_id = get_queried_object_id();
if ($author_id) {
$user = get_userdata($author_id);
$slug = $user ? $user->user_nicename : 'user-' . $author_id;
wp_safe_redirect(home_url('/user/' . trailingslashit($slug)), 301);
if (is_admin() || wp_doing_ajax() || is_feed() || is_embed()) {
return;
}
if (!is_author()) {
return;
}
$author_id = (int) get_queried_object_id();
if ($author_id <= 0) {
$author_id = (int) get_query_var('author');
}
if ($author_id <= 0) {
return;
}
$target = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($author_id)
: home_url('/user/');
if ($target === '' || $target === home_url('/')) {
return;
}
wp_safe_redirect($target, 301);
exit;
}, 1);
// Catch bare ?author=ID when is_author() did not fire.
add_action('template_redirect', function () {
if (is_admin() || wp_doing_ajax()) {
return;
}
if (empty($_GET['author'])) {
return;
}
if (get_query_var('user_nicename') || get_query_var('aksrt_user_center')) {
return;
}
if (is_author()) {
return; // handled above
}
$author_id = (int) $_GET['author'];
if ($author_id > 0 && function_exists('aksrt_get_user_home_url')) {
wp_safe_redirect(aksrt_get_user_home_url($author_id), 301);
exit;
}
});
}, 2);
// ============================================================
// AJAX: 保存用户个人资料(显示名称、简介、联系方式)
+10 -2
View File
@@ -488,8 +488,16 @@ function aksrt_ensure_template_page(string $key): void {
$cfg = $_aksrt_special_templates[$key];
$desired_slug = sanitize_title($cfg['slug'] ?? $key);
// 已有页面使用此模板?
if (aksrt_find_template_page($cfg['file'])) return;
// 已有页面使用此模板?仍校验模板 meta,避免空白默认页。
$found_page = aksrt_find_template_page($cfg['file']);
if ($found_page instanceof WP_Post) {
$current_tpl = get_post_meta((int) $found_page->ID, '_wp_page_template', true);
if ($current_tpl !== $cfg['file']) {
update_post_meta((int) $found_page->ID, '_wp_page_template', $cfg['file']);
aksrt_clear_template_page_cache();
}
return;
}
if ($desired_slug !== '') {
$existing_page = get_page_by_path($desired_slug, OBJECT, 'page');
+1 -1
View File
@@ -30,7 +30,7 @@ $meta_items = is_array($args['meta_items'] ?? null) ? array_values(array_filter(
$author_id = (int) get_the_author_meta('ID');
$author_name = get_the_author();
$author_url = get_author_posts_url($author_id);
$author_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($author_id) : get_author_posts_url($author_id);
$modified_ts = (int) get_the_modified_time('U');
$published_ts = (int) get_the_time('U');
$now = (int) current_time('timestamp');