Files
wordpress-theme-aksrt/src/User/Home.php
T

411 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* @package AKSRT
*/
namespace AKSRT\User;
defined('ABSPATH') || exit;
class Home {
private static $instance = null;
/** @var array Tab 注册表 */
private $tabs = [];
private function __construct() {}
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
$this->register_default_tabs();
add_action('rest_api_init', [$this, 'register_rest_routes']);
}
/**
* Register public user-home panel fragment routes.
*/
public function register_rest_routes(): void {
register_rest_route('aksrt/v1', '/user-home/panel/(?P<user_id>\d+)/(?P<tab>[a-z0-9_-]+)', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'rest_panel'],
'permission_callback' => '__return_true',
'args' => [
'user_id' => [
'required' => true,
'sanitize_callback' => 'absint',
'validate_callback' => static function ($value): bool {
return (int) $value > 0;
},
],
'tab' => [
'required' => true,
'sanitize_callback' => 'sanitize_key',
'validate_callback' => static function ($value): bool {
return preg_match('/^[a-z0-9_-]+$/', (string) $value) === 1;
},
],
'page' => [
'required' => false,
'default' => 1,
'sanitize_callback' => 'absint',
],
'sub' => [
'required' => false,
'default' => 'followers',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => static function ($value): bool {
return in_array((string) $value, ['followers', 'following'], true);
},
],
],
]);
}
/**
* Render a public user-home panel body fragment.
*/
public function rest_panel(\WP_REST_Request $request) {
$user = get_userdata((int) $request->get_param('user_id'));
if (!$user) {
return new \WP_Error('aksrt_user_home_user', __('用户不存在', 'aksrt'), ['status' => 404]);
}
$tab = sanitize_key((string) $request->get_param('tab'));
$page = max(1, (int) $request->get_param('page'));
$sub = (string) $request->get_param('sub') === 'following' ? 'following' : 'followers';
if (!function_exists('aksrt_get_user_home_page_context')) {
require_once AKSRT_DIR . '/inc/helpers/aksrt-user-home.php';
}
$context = aksrt_get_user_home_page_context($user);
if (!in_array($tab, (array) ($context['enabled_tabs'] ?? []), true)) {
return new \WP_Error('aksrt_user_home_panel', __('用户主页面板不存在', 'aksrt'), ['status' => 404]);
}
$context['active_tab'] = $tab;
$context['follow_sub'] = $sub;
$page_keys = [
'posts' => 'posts_page',
'comments' => 'comments_page',
'favorites' => 'favorites_page',
'follow' => 'follow_page',
];
if (isset($page_keys[$tab])) {
$context[$page_keys[$tab]] = $page;
}
$html = aksrt_capture_user_home_panel($tab, $context);
if ($html === '') {
return new \WP_Error('aksrt_user_home_panel_empty', __('用户主页面板暂不可用', 'aksrt'), ['status' => 404]);
}
return rest_ensure_response([
'success' => true,
'data' => [
'tab' => $tab,
'html' => $html,
],
]);
}
/**
* 注册默认公开 Tab
*/
private function register_default_tabs(): void {
$this->tabs = [
'posts' => [
'label' => __('文章', 'aksrt'),
'icon' => 'file-alt',
'priority' => 10,
],
'comments' => [
'label' => __('评论', 'aksrt'),
'icon' => 'comment',
'priority' => 20,
],
'favorites' => [
'label' => __('收藏', 'aksrt'),
'icon' => 'star',
'priority' => 30,
],
'credits' => [
'label' => class_exists('\AKSRT\Settings\Settings')
? sprintf(__('我的%s', 'aksrt'), \AKSRT\Settings\Settings::get_currency_name())
: __('我的积分', 'aksrt'),
'icon' => 'wallet',
'priority' => 40,
],
'follow' => [
'label' => __('粉丝', 'aksrt'),
'icon' => 'user-plus',
'priority' => 80,
],
];
/**
* 允许插件/子主题注册额外公开 Tab
*
* @param array $tabs Tab 注册表
*/
$this->tabs = apply_filters('aksrt_user_home_tabs', $this->tabs);
}
/**
* 获取 Tab 注册表
*/
public function get_tabs(): array {
return $this->tabs;
}
/**
* 获取当前用户主页实际可见的公开 Tab。
*/
public function get_visible_tabs(bool $is_own_profile = false): array {
$settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_user_home_settings()
: [
'tabs' => ['posts', 'comments', 'favorites'],
];
$visible = [];
foreach ((array) ($settings['tabs'] ?? []) as $key) {
if (!isset($this->tabs[$key])) {
continue;
}
if ($key === 'credits' && (!$is_own_profile || !class_exists('\AKSRT\Settings\Settings') || !\AKSRT\Settings\Settings::credits_enabled())) {
continue;
}
$visible[$key] = $this->tabs[$key];
}
if (empty($visible)) {
foreach (['posts', 'comments', 'favorites'] as $fallback) {
if (isset($this->tabs[$fallback])) {
$visible[$fallback] = $this->tabs[$fallback];
}
}
}
return $visible;
}
/**
* 获取公开展示的身份信号(认证 / 等级 / 徽章)。
*
* @return array<int,array{label:string,icon:string,tone:string,icon_url?:string,type?:string,slug?:string,wear?:bool}>
*/
public function get_profile_signals(int $user_id): array {
if ($user_id <= 0 || !class_exists('\AKSRT\Settings\Settings')) {
return [];
}
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
$signals = [];
if (!empty($settings['verify']['enabled']) && !empty($settings['verify']['public_display']) && $this->is_user_verified($user_id, $settings['verify'])) {
$resolved = $this->resolve_verified_type($user_id, $settings['verify']);
$signals[] = [
'type' => 'verify',
'label' => $resolved['label'],
'title' => $resolved['label'],
'icon' => 'check-circle',
'icon_url' => get_theme_file_uri('/assets/img/user/user-certification.svg'),
'tone' => 'success',
];
}
if (!empty($settings['level']['enabled']) && !empty($settings['level']['public_display'])) {
$level = $this->get_user_level($user_id, $settings['level']);
if ($level > 0) {
$signals[] = [
'type' => 'level',
'label' => Level::get_level_name($level),
'title' => Level::get_level_name($level),
'icon' => 'layer-group',
'icon_url' => Level::get_level_icon_url($level),
'tone' => 'info',
];
}
}
if (!empty($settings['medal']['enabled']) && !empty($settings['medal']['public_display'])) {
$limit = max(1, (int) ($settings['medal']['limit'] ?? 3));
if (class_exists('AKSRT\\User\\Badges')) {
$list = Badges::list_for_user($user_id);
$badge_items = [];
$worn_item = null;
foreach ((array) ($list['categories'] ?? []) as $cat) {
foreach ((array) ($cat['items'] ?? []) as $item) {
if (empty($item['owned'])) {
continue;
}
if (!empty($item['wear'])) {
$worn_item = $item;
}
$badge_items[] = $item;
}
}
// Keep the worn badge visible even when it falls after the configured preview limit.
if (is_array($worn_item)) {
$signals[] = [
'type' => 'badge',
'slug' => (string) ($worn_item['slug'] ?? ''),
'label' => (string) ($worn_item['label'] ?? ''),
'icon' => 'award',
'tone' => 'info',
'icon_url' => (string) ($worn_item['icon_url'] ?? ''),
'wear' => true,
];
}
$badge_count = is_array($worn_item) ? 1 : 0;
foreach ($badge_items as $item) {
if (!empty($item['wear'])) {
continue;
}
if ($badge_count >= $limit) {
break;
}
$signals[] = [
'type' => 'badge',
'slug' => (string) ($item['slug'] ?? ''),
'label' => (string) ($item['label'] ?? ''),
'icon' => 'award',
'tone' => 'info',
'icon_url' => (string) ($item['icon_url'] ?? ''),
'wear' => false,
];
$badge_count++;
}
}
}
return $signals;
}
/**
* 解析"更多资料"弹窗用的认证三态标识。
*
* @return array{state:string,label:string} state 为空串表示认证公开展示已关闭;
* 否则为 none|personal|enterprise,label 为对应展示文案(与名字旁认证 chip 同源)。
*/
public function get_verify_state(int $user_id): array {
if ($user_id <= 0 || !class_exists('\AKSRT\Settings\Settings')) {
return ['state' => '', 'label' => ''];
}
$verify = \AKSRT\Settings\Settings::get_user_identity_settings()['verify'] ?? [];
$verify = is_array($verify) ? $verify : [];
if (empty($verify['enabled']) || empty($verify['public_display'])) {
return ['state' => '', 'label' => ''];
}
if (!$this->is_user_verified($user_id, $verify)) {
return ['state' => 'none', 'label' => __('未认证', 'aksrt')];
}
$resolved = $this->resolve_verified_type($user_id, $verify);
// 邮箱核验通过等无申请类型的场景与名字旁 chip 一致,回落到首个已配置类型。
$state = in_array($resolved['state'], ['personal', 'enterprise'], true) ? $resolved['state'] : 'personal';
return ['state' => $state, 'label' => $resolved['label']];
}
/**
* @return array{state:string,label:string} state 为申请类型 personal|enterprise(可为空串),label 为展示文案。
*/
private function resolve_verified_type(int $user_id, array $verify): array {
$info = class_exists('AKSRT\\User\\AuthApply') ? AuthApply::get_auth_info($user_id) : [];
$application = class_exists('AKSRT\\User\\AuthApply') ? AuthApply::get_apply_payload($user_id) : [];
$type = sanitize_key((string) ($info['type'] ?? $application['type'] ?? ''));
$type_settings = is_array($verify['types'][$type] ?? null) ? $verify['types'][$type] : [];
$label = trim((string) ($type_settings['badge_text'] ?? $type_settings['label'] ?? ''));
if ($label === '') {
foreach ((array) ($verify['types'] ?? []) as $configured_type) {
if (is_array($configured_type) && !empty($configured_type['badge_text'])) {
$label = trim((string) $configured_type['badge_text']);
break;
}
}
}
return ['state' => $type, 'label' => $label];
}
/**
* 获取公开治理提示。
*
* @return array{state:string,text:string}|array{}
*/
public function get_profile_notice(int $user_id): array {
if ($user_id <= 0 || !class_exists('\AKSRT\Settings\Settings')) {
return [];
}
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
if (empty($settings['report']['enabled']) || empty($settings['report']['public_notice'])) {
return [];
}
$state = sanitize_key((string) get_user_meta($user_id, 'aksrt_user_ban_state', true));
if (!in_array($state, ['muted', 'restricted', 'banned'], true)) {
return [];
}
return [
'state' => $state,
'text' => (string) $settings['report']['ban_notice_text'],
];
}
/**
* 公开主页是否显示举报入口。
*/
public function show_report_entry(): bool {
if (!class_exists('\AKSRT\Settings\Settings')) {
return false;
}
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
return !empty($settings['report']['enabled']) && !empty($settings['report']['public_entry']);
}
/**
* @param array{accept_email_verified:bool} $verify_settings
*/
private function is_user_verified(int $user_id, array $verify_settings): bool {
if (class_exists('\\AKSRT\\User\\AuthApply') && AuthApply::is_verified($user_id)) {
return true;
}
if (!empty($verify_settings['accept_email_verified']) && class_exists('\AKSRT\Auth\Service')) {
return \AKSRT\Auth\Service::user_is_email_verified($user_id);
}
return false;
}
/**
* @param array{source?:string,thresholds?:array<int,int>} $level_settings
*/
private function get_user_level(int $user_id, array $level_settings): int {
unset($level_settings);
return class_exists('AKSRT\\User\\Level') ? Level::get_level($user_id) : 0;
}
/**
* 判断当前页面是否为公开用户主页
*/
public static function is_page(): bool {
return function_exists('aksrt_is_user_home_page') && aksrt_is_user_home_page();
}
}