Register 我的会员 panel, zibll-style status/equity cards, sidebar deep-link, and slim credits-tab entry so VIP is no longer nested only under assets.
418 lines
14 KiB
PHP
418 lines
14 KiB
PHP
<?php
|
||
/**
|
||
* AKSRT VIP Helper — VIP 会员等级与展示辅助函数
|
||
*
|
||
* @package AKSRT
|
||
*/
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
/**
|
||
* 解析用户 VIP 等级:0=非 VIP,1=VIP1,2=VIP2。
|
||
*
|
||
* 判定顺序:
|
||
* 1. VIP 功能未启用 → 0
|
||
* 2. 角色在 vip_exempt_roles 白名单 → 当前启用的最高档
|
||
* 3. user meta aksrt_vip_level + aksrt_vip_expires(过期 lazy 清 0;expires=0 且 level>0 视为永久)
|
||
* 4. 有效 VIP 订单兜底 → 1(套餐无 level 列,订单无法区分 VIP2)
|
||
* 5. 按 pay_user_vip_1/2 开关钳制
|
||
*
|
||
* @param int $user_id 用户 ID,0 时取当前用户。
|
||
* @return int 0|1|2
|
||
*/
|
||
function aksrt_get_user_vip_level(int $user_id = 0): int {
|
||
if ($user_id <= 0) {
|
||
$user_id = get_current_user_id();
|
||
}
|
||
if ($user_id <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||
return 0;
|
||
}
|
||
|
||
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||
if (empty($vip_settings['enabled'])) {
|
||
return 0;
|
||
}
|
||
|
||
$levels = is_array($vip_settings['levels'] ?? null) ? $vip_settings['levels'] : [];
|
||
$vip1_enabled = !empty($levels[1]['enabled']);
|
||
$vip2_enabled = !empty($levels[2]['enabled']);
|
||
|
||
// 1. 角色豁免:特权角色取当前启用最高档。
|
||
$paid_settings = \AKSRT\Settings\Settings::get_paid_content_settings();
|
||
$exempt_roles = $paid_settings['vip_exempt_roles'] ?? [];
|
||
if (!empty($exempt_roles)) {
|
||
$user = get_userdata($user_id);
|
||
if ($user && !empty(array_intersect((array) $user->roles, $exempt_roles))) {
|
||
if ($vip2_enabled) {
|
||
return 2;
|
||
}
|
||
if ($vip1_enabled) {
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
$candidate = 0;
|
||
|
||
// 2. meta 主路径。
|
||
$meta_level = (int) get_user_meta($user_id, 'aksrt_vip_level', true);
|
||
$expires = (int) get_user_meta($user_id, 'aksrt_vip_expires', true);
|
||
|
||
if ($meta_level > 0) {
|
||
if ($expires > 0 && $expires < time()) {
|
||
// 过期:lazy 清 level,记录上次等级(zibll vip_level_expired)。
|
||
update_user_meta($user_id, 'aksrt_vip_level_expired', min(2, max(1, $meta_level)));
|
||
update_user_meta($user_id, 'aksrt_vip_level', 0);
|
||
$candidate = 0;
|
||
} else {
|
||
// expires === 0 视为永久;expires >= now 视为有效。
|
||
$candidate = min(2, max(0, $meta_level));
|
||
}
|
||
}
|
||
|
||
// 3. 订单兜底(meta 无有效等级时)。
|
||
if ($candidate <= 0 && class_exists('\AKSRT\Payment\Order')) {
|
||
$order_mgr = \AKSRT\Payment\Order::get_instance();
|
||
$valid_order = method_exists($order_mgr, 'get_valid_vip_order')
|
||
? $order_mgr->get_valid_vip_order($user_id)
|
||
: null;
|
||
|
||
if (is_array($valid_order) && !empty($valid_order)) {
|
||
// Order.expires_at is payment checkout deadline, not VIP membership expiry.
|
||
// Prefer note/meta_payload level + aksrt_vip_expires if present.
|
||
$meta = [];
|
||
if (!empty($valid_order['meta_payload']) && is_array($valid_order['meta_payload'])) {
|
||
$meta = $valid_order['meta_payload'];
|
||
} elseif (!empty($valid_order['note']) && is_string($valid_order['note'])) {
|
||
$decoded = json_decode($valid_order['note'], true);
|
||
if (is_array($decoded)) {
|
||
$meta = $decoded;
|
||
}
|
||
}
|
||
|
||
$order_level = (int) ($meta['level'] ?? 0);
|
||
if ($order_level <= 0 && !empty($valid_order['product_id']) && class_exists('\AKSRT\Content\VipService')) {
|
||
// package_id encodes level: decode if possible via service helpers.
|
||
$pkg_id = (int) $valid_order['product_id'];
|
||
// Encode scheme: level * 1000 + index (see VipServicePackages).
|
||
if ($pkg_id >= 1000) {
|
||
$order_level = (int) floor($pkg_id / 1000);
|
||
} elseif ($pkg_id > 0) {
|
||
$order_level = 1;
|
||
}
|
||
}
|
||
$order_level = max(0, min(2, $order_level));
|
||
|
||
// Membership still valid if user meta expires is in future or permanent (0 with prior grant).
|
||
// Without restored meta, treat paid VIP order as at least the ordered level.
|
||
if ($order_level > 0) {
|
||
$candidate = $order_level;
|
||
} else {
|
||
$candidate = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. 按档位开关钳制。
|
||
if ($candidate >= 2) {
|
||
if ($vip2_enabled) {
|
||
return 2;
|
||
}
|
||
if ($vip1_enabled) {
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
if ($candidate === 1) {
|
||
return $vip1_enabled ? 1 : 0;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* 检查用户是否为 VIP(level >= 1)。
|
||
*
|
||
* @param int $user_id
|
||
* @return bool
|
||
*/
|
||
function aksrt_is_vip(int $user_id = 0): bool {
|
||
return aksrt_get_user_vip_level($user_id) >= 1;
|
||
}
|
||
|
||
/**
|
||
* 检查用户是否为 VIP2(level >= 2)。
|
||
*
|
||
* @param int $user_id
|
||
* @return bool
|
||
*/
|
||
function aksrt_is_vip2(int $user_id = 0): bool {
|
||
return aksrt_get_user_vip_level($user_id) >= 2;
|
||
}
|
||
|
||
/**
|
||
* 获取 VIP 徽标文本
|
||
*
|
||
* @return string
|
||
*/
|
||
function aksrt_get_vip_badge_text(int $user_id = 0): string {
|
||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||
return 'VIP';
|
||
}
|
||
|
||
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||
$level = $user_id > 0 ? aksrt_get_user_vip_level($user_id) : 0;
|
||
if ($level >= 1 && !empty($vip_settings['levels'][$level]['name'])) {
|
||
return sanitize_text_field((string) $vip_settings['levels'][$level]['name']);
|
||
}
|
||
return sanitize_text_field($vip_settings['badge_text'] ?? 'VIP');
|
||
}
|
||
|
||
/**
|
||
* Daily free-download limit for a user (0 = unlimited).
|
||
* Aligns with zibll pay_download_limit / pay_download_limit_vip_{n}.
|
||
*/
|
||
if (!function_exists('aksrt_get_user_free_download_limit')) {
|
||
function aksrt_get_user_free_download_limit(int $user_id = 0): int {
|
||
if ($user_id <= 0) {
|
||
$user_id = get_current_user_id();
|
||
}
|
||
if ($user_id <= 0 || !class_exists('\AKSRT\Settings\Settings')) {
|
||
return 0;
|
||
}
|
||
|
||
$vip = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||
$level = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0;
|
||
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
|
||
$benefit = is_array($vip['benefit'] ?? null) ? $vip['benefit'] : [];
|
||
|
||
if ($level >= 1 && !empty($levels[$level]['enabled'])) {
|
||
return max(0, (int) ($levels[$level]['download_limit'] ?? 0));
|
||
}
|
||
|
||
return max(0, (int) ($benefit['download_limit'] ?? 0));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Download speed cap in KB/s for a user (0 = unlimited).
|
||
*/
|
||
if (!function_exists('aksrt_get_user_download_speed')) {
|
||
function aksrt_get_user_download_speed(int $user_id = 0): int {
|
||
if ($user_id <= 0) {
|
||
$user_id = get_current_user_id();
|
||
}
|
||
if ($user_id <= 0 || !class_exists('\AKSRT\Settings\Settings')) {
|
||
return 0;
|
||
}
|
||
|
||
$vip = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||
$level = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0;
|
||
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
|
||
$benefit = is_array($vip['benefit'] ?? null) ? $vip['benefit'] : [];
|
||
|
||
if ($level >= 1 && !empty($levels[$level]['enabled'])) {
|
||
return max(0, (int) ($levels[$level]['download_speed'] ?? 0));
|
||
}
|
||
|
||
return max(0, (int) ($benefit['download_speed'] ?? 0));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Count free downloads used today for a user (zibll pay_down_number).
|
||
*/
|
||
if (!function_exists('aksrt_get_user_free_downloaded_number')) {
|
||
function aksrt_get_user_free_downloaded_number(int $user_id = 0): int {
|
||
if ($user_id <= 0) {
|
||
$user_id = get_current_user_id();
|
||
}
|
||
if ($user_id <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
$day = current_time('Y-m-d');
|
||
$mate = get_user_meta($user_id, 'aksrt_pay_down_number', true);
|
||
if (!is_array($mate) || empty($mate[$day]) || !is_array($mate[$day])) {
|
||
return 0;
|
||
}
|
||
return count($mate[$day]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Record a free download for daily quota (zibll pay_down_number).
|
||
*/
|
||
if (!function_exists('aksrt_record_user_free_download')) {
|
||
function aksrt_record_user_free_download(int $user_id, int $post_id): void {
|
||
if ($user_id <= 0 || $post_id <= 0) {
|
||
return;
|
||
}
|
||
$day = current_time('Y-m-d');
|
||
$mate = get_user_meta($user_id, 'aksrt_pay_down_number', true);
|
||
if (!is_array($mate)) {
|
||
$mate = [];
|
||
}
|
||
// Drop old days to keep meta small.
|
||
foreach (array_keys($mate) as $k) {
|
||
if ($k !== $day) {
|
||
unset($mate[$k]);
|
||
}
|
||
}
|
||
if (!isset($mate[$day]) || !is_array($mate[$day])) {
|
||
$mate[$day] = [];
|
||
}
|
||
$mate[$day][(string) $post_id] = 1;
|
||
update_user_meta($user_id, 'aksrt_pay_down_number', $mate);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Whether a free download is allowed under daily quota.
|
||
* Paid downloads (cost > 0) always pass.
|
||
*
|
||
* @return true|WP_Error
|
||
*/
|
||
if (!function_exists('aksrt_check_free_download_quota')) {
|
||
function aksrt_check_free_download_quota(int $user_id, int $cost = 0) {
|
||
if ($cost > 0) {
|
||
return true;
|
||
}
|
||
if ($user_id <= 0) {
|
||
return true; // login gate handled elsewhere
|
||
}
|
||
|
||
$limit = aksrt_get_user_free_download_limit($user_id);
|
||
if ($limit <= 0) {
|
||
return true; // unlimited
|
||
}
|
||
|
||
$used = aksrt_get_user_free_downloaded_number($user_id);
|
||
if ($used >= $limit) {
|
||
$data = [
|
||
'status' => 429,
|
||
'limit' => $limit,
|
||
'used' => $used,
|
||
];
|
||
// Optional overbuy price for free-quota exhausted (Zibll download_limit_over_price).
|
||
// Callers may pass post context via filter.
|
||
$over = apply_filters('aksrt_download_limit_over_price', 0.0, $user_id);
|
||
if ($over > 0) {
|
||
$data['download_limit_over_price'] = $over;
|
||
$data['allow_limit_over'] = true;
|
||
}
|
||
return new WP_Error(
|
||
'aksrt_download_limit',
|
||
__('您今日下载免费资源个数已超限,请明日再下载。', 'aksrt'),
|
||
$data
|
||
);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Remaining free downloads today (null = unlimited).
|
||
*/
|
||
if (!function_exists('aksrt_get_free_download_surplus')) {
|
||
function aksrt_get_free_download_surplus(int $user_id = 0): ?int {
|
||
if ($user_id <= 0) {
|
||
$user_id = get_current_user_id();
|
||
}
|
||
$limit = aksrt_get_user_free_download_limit($user_id);
|
||
if ($limit <= 0) {
|
||
return null;
|
||
}
|
||
$used = aksrt_get_user_free_downloaded_number($user_id);
|
||
return max(0, $limit - $used);
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 渲染 VIP 徽标 HTML
|
||
*
|
||
* @param int $user_id
|
||
* @param string $size sm | md | lg
|
||
* @return string
|
||
*/
|
||
function aksrt_render_vip_badge(int $user_id = 0, string $size = 'sm'): string {
|
||
if (!aksrt_is_vip($user_id)) {
|
||
return '';
|
||
}
|
||
|
||
$level = aksrt_get_user_vip_level($user_id);
|
||
$badge_text = aksrt_get_vip_badge_text($user_id);
|
||
$size_class = 'aksrt-vip-badge--' . $size;
|
||
$level_class = $level >= 2 ? 'aksrt-vip-badge--vip2' : 'aksrt-vip-badge--vip1';
|
||
|
||
return '<span class="aksrt-vip-badge aksrt-vip-badge--gold ' . esc_attr($size_class . ' ' . $level_class) . '"><i class="fas fa-crown"></i> ' . esc_html($badge_text) . '</span>';
|
||
}
|
||
|
||
/**
|
||
* 获取用户 VIP 等级信息(用于展示)
|
||
*
|
||
* @param int $user_id
|
||
* @return array{is_vip: bool, level: int, badge_text: string, badge_html: string, name: string, expires: int, expires_label: string}
|
||
*/
|
||
function aksrt_get_vip_info(int $user_id = 0): array {
|
||
$level = aksrt_get_user_vip_level($user_id);
|
||
$is_vip = $level >= 1;
|
||
$badge_text = aksrt_get_vip_badge_text($user_id);
|
||
$expires = $user_id > 0 ? (int) get_user_meta($user_id, 'aksrt_vip_expires', true) : 0;
|
||
$expires_label = __('未开通', 'aksrt');
|
||
if ($level > 0) {
|
||
if ($expires <= 0) {
|
||
$expires_label = __('永久有效', 'aksrt');
|
||
} elseif ($expires < time()) {
|
||
$expires_label = __('已过期', 'aksrt');
|
||
} else {
|
||
$expires_label = sprintf(
|
||
/* translators: %s: date */
|
||
__('到期时间:%s', 'aksrt'),
|
||
wp_date('Y-m-d H:i', $expires)
|
||
);
|
||
}
|
||
}
|
||
|
||
$name = __('普通用户', 'aksrt');
|
||
if ($is_vip && class_exists('\AKSRT\Settings\Settings')) {
|
||
$vip = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||
$name = (string) ($vip['levels'][$level]['name'] ?? ($badge_text . __('会员', 'aksrt')));
|
||
} elseif ($is_vip) {
|
||
$name = $badge_text . __('会员', 'aksrt');
|
||
}
|
||
|
||
return [
|
||
'is_vip' => $is_vip,
|
||
'level' => $level,
|
||
'badge_text' => $badge_text,
|
||
'badge_html' => $is_vip ? aksrt_render_vip_badge($user_id) : '',
|
||
'name' => $name,
|
||
'expires' => $expires,
|
||
'expires_label' => $expires_label,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* VIP 开通页入口(用户中心 credits 面板)。
|
||
*/
|
||
if (!function_exists('aksrt_get_vip_purchase_url')) {
|
||
function aksrt_get_vip_purchase_url(): string {
|
||
if (function_exists('aksrt_get_user_center_tab_url')) {
|
||
return aksrt_get_user_center_tab_url('vip');
|
||
}
|
||
if (function_exists('aksrt_get_user_center_url')) {
|
||
return aksrt_get_user_center_url('vip');
|
||
}
|
||
return home_url('/');
|
||
}
|
||
}
|