Files
wordpress-theme-aksrt/inc/helpers/aksrt-vip.php
T
kate522 69836154fd fix(vip): Batch 0 wiring for VIP settings, URLs, and order meta
Wire get_vip_settings alias, tab aliases for vip/assets, expired-level meta,
VIP2 order-level fallback, JSON-safe order notes, and header/float pay-vip CTAs.
2026-07-21 16:16:43 +08:00

412 lines
13 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
/**
* 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(): string {
if (!class_exists('\AKSRT\Settings\Settings')) {
return 'VIP';
}
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
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 '';
}
$badge_text = aksrt_get_vip_badge_text();
$size_class = 'aksrt-vip-badge--' . $size;
return '<span class="aksrt-vip-badge aksrt-vip-badge--gold ' . esc_attr($size_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();
$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('credits');
}
if (function_exists('aksrt_get_user_center_url')) {
return aksrt_get_user_center_url();
}
return home_url('/');
}
}