411 lines
15 KiB
PHP
411 lines
15 KiB
PHP
<?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();
|
||
}
|
||
}
|