diff --git a/inc/classes/class-aksrt-content-visibility.php b/inc/classes/class-aksrt-content-visibility.php index 35993bf1..1257580d 100644 --- a/inc/classes/class-aksrt-content-visibility.php +++ b/inc/classes/class-aksrt-content-visibility.php @@ -38,7 +38,17 @@ class AKSRT_Content_Visibility { ? aksrt_get_pay_meta($resource_post) : AKSRT_Domain_Model::default_pay_meta(); $pay_type = AKSRT_Domain_Model::normalize_pay_type((string) ($pay_meta['pay_type'] ?? AKSRT_Domain_Model::PAY_TYPE_NO)); - $price = (float) ($pay_meta['price'] ?? 0); + $priced = function_exists('aksrt_get_user_pay_price') + ? aksrt_get_user_pay_price($resource_post, $uid) + : null; + $pay_modo = is_array($priced) + ? ((($priced['modo'] ?? '0') === 'points') ? 'points' : '0') + : ((($pay_meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'); + $price = is_array($priced) + ? (float) ($priced['amount'] ?? 0) + : (($pay_modo === 'points') + ? (float) ($pay_meta['points_price'] ?? 0) + : (float) ($pay_meta['price'] ?? 0)); $paid_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_paid_content_settings() : [ @@ -50,6 +60,7 @@ class AKSRT_Content_Visibility { $state = [ 'post_id' => $resource_post instanceof WP_Post ? (int) $resource_post->ID : 0, 'pay_type' => $pay_type, + 'pay_modo' => $pay_modo, 'price' => $price, 'need_login' => false, 'need_purchase' => false, @@ -59,6 +70,8 @@ class AKSRT_Content_Visibility { 'can_view' => true, 'can_download' => true, 'download_cost' => 0, + 'pay_limit' => 0, + 'user_vip_level' => 0, ]; if (!($resource_post instanceof WP_Post)) { @@ -68,20 +81,16 @@ class AKSRT_Content_Visibility { } if ($pay_type === AKSRT_Domain_Model::PAY_TYPE_NO) { + $state['has_paid_access'] = true; return $state; } $has_free_access = $price <= 0 && $pay_type !== AKSRT_Domain_Model::PAY_TYPE_VIP; - // Prefer points_price when pay_modo=points. - if (is_array($pay_meta) && (($pay_meta['pay_modo'] ?? '0') === 'points')) { - $price = (float) ($pay_meta['points_price'] ?? $price); - $state['price'] = $price; - $has_free_access = $price <= 0 && $pay_type !== AKSRT_Domain_Model::PAY_TYPE_VIP; - } $pay_limit = (int) ($pay_meta['pay_limit'] ?? 0); if (!in_array($pay_limit, [0, 1, 2], true)) { $pay_limit = 0; } + $state['pay_limit'] = $pay_limit; $has_paid_order = false; if ($uid > 0 && class_exists('AKSRT_Order')) { @@ -93,47 +102,60 @@ class AKSRT_Content_Visibility { } $has_vip_access = false; - $user_vip_level = 0; + $user_vip_level = is_array($priced) ? (int) ($priced['vip_level'] ?? 0) : 0; if ($uid > 0) { - if (function_exists('aksrt_get_user_vip_level')) { + if ($user_vip_level <= 0 && function_exists('aksrt_get_user_vip_level')) { $user_vip_level = aksrt_get_user_vip_level($uid); } if (function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) { $has_vip_access = true; $user_vip_level = max($user_vip_level, 2); - } elseif (function_exists('aksrt_is_vip') && ($pay_type === AKSRT_Domain_Model::PAY_TYPE_VIP || $pay_limit > 0)) { + } elseif ($pay_type === AKSRT_Domain_Model::PAY_TYPE_VIP || $pay_limit > 0) { $has_vip_access = $user_vip_level >= max(1, $pay_limit); } elseif ($user_vip_level > 0) { + // VIP tier pricing may zero cost; still track membership. $has_vip_access = $pay_limit <= 0 || $user_vip_level >= $pay_limit; } } + $state['user_vip_level'] = $user_vip_level; // Member-only purchase restriction (Zibll pay_limit). if ($pay_limit > 0 && $pay_type !== AKSRT_Domain_Model::PAY_TYPE_NO) { $state['need_vip'] = true; - $state['pay_limit'] = $pay_limit; - $allowed_member = $has_vip_access && $user_vip_level >= $pay_limit; + $allowed_member = $uid > 0 && $user_vip_level >= $pay_limit; if ($pay_type === AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD) { $download_cost = function_exists('aksrt_resource_download_cost') ? (int) aksrt_resource_download_cost($resource_post) : (int) ceil($price); + // If member is free via VIP price, cost is 0. + if ($allowed_member && $price <= 0) { + $download_cost = 0; + } $state['download_cost'] = max(0, $download_cost); $state['need_login'] = $uid <= 0; $state['need_purchase'] = $allowed_member && $state['download_cost'] > 0 && !$has_paid_order; - $state['has_paid_access'] = $allowed_member && ($has_paid_order || $state['download_cost'] <= 0 || $has_vip_access); + $state['has_paid_access'] = $allowed_member && ($has_paid_order || $state['download_cost'] <= 0); $state['can_view'] = true; - $state['can_download'] = $allowed_member && ($uid > 0) && ($has_paid_order || $state['download_cost'] <= 0 || $has_vip_access); + $state['can_download'] = $allowed_member && ($uid > 0) && ($has_paid_order || $state['download_cost'] <= 0); if (!$allowed_member) { $state['can_download'] = false; $state['has_paid_access'] = false; + $state['need_purchase'] = false; } return $state; } - $state['has_paid_access'] = $allowed_member || $has_paid_order; + $state['has_paid_access'] = ($allowed_member && $price <= 0) || $has_paid_order || ($allowed_member && $has_vip_access && $pay_type === AKSRT_Domain_Model::PAY_TYPE_VIP); + // For reading/gallery/video with pay_limit: members still need to purchase unless free. + if ($allowed_member) { + $state['has_paid_access'] = $has_paid_order || $price <= 0; + $state['need_purchase'] = !$state['has_paid_access']; + } else { + $state['has_paid_access'] = false; + $state['need_purchase'] = false; + } $state['can_view'] = $state['has_paid_access']; $state['can_download'] = false; - $state['need_purchase'] = !$state['has_paid_access']; return $state; } @@ -149,10 +171,23 @@ class AKSRT_Content_Visibility { || !empty($pay_meta['require_login']) || ($free_logged_show && $state['download_cost'] <= 0); $state['require_captcha'] = !empty($paid_settings['download_captcha']) || !empty($pay_meta['require_captcha']); - $state['need_purchase'] = !$has_vip_access && $state['download_cost'] > 0; - $state['has_paid_access'] = $has_vip_access || $has_paid_order || !$state['need_purchase']; + // Cash mode with cost handled by orders: need purchase when no order and base price > 0. + $needs_pay = $state['download_cost'] > 0 || ($pay_modo === '0' && $price > 0 && !$has_paid_order && empty($paid_settings['download_credit'])); + if ($pay_modo === '0' && $price > 0 && empty($paid_settings['download_credit'])) { + $state['need_purchase'] = !$has_paid_order && !($has_vip_access && $price <= 0); + $state['has_paid_access'] = $has_paid_order || $price <= 0; + // Download still gated by login/credit path for token; cash unlock is order-based. + $state['can_view'] = true; + $state['can_download'] = ($has_paid_order || $price <= 0) && (!$state['need_login'] || $uid > 0); + return $state; + } + + $state['need_purchase'] = !$has_paid_order && $state['download_cost'] > 0 && !$has_free_access; + // VIP with free tier amount already reflected in download_cost. + $state['has_paid_access'] = $has_paid_order || $state['download_cost'] <= 0 || $has_free_access; $state['can_view'] = true; - $state['can_download'] = !$state['need_login'] || $uid > 0; + $state['can_download'] = (!$state['need_login'] || $uid > 0) && ($has_paid_order || $state['download_cost'] <= 0 || true); + // can_download true only means "may request token"; rest_request still deducts credits. return $state; } @@ -167,8 +202,13 @@ class AKSRT_Content_Visibility { } $state['need_login'] = !empty($pay_meta['require_login']); - $state['need_purchase'] = !$has_free_access; - $state['has_paid_access'] = $has_free_access || $has_paid_order || $has_vip_access; + $state['need_purchase'] = !$has_free_access && !$has_paid_order; + $state['has_paid_access'] = $has_free_access || $has_paid_order; + // VIP free tier for reading etc. via amount. + if (!$state['has_paid_access'] && $price <= 0 && $pay_type !== AKSRT_Domain_Model::PAY_TYPE_VIP) { + $state['has_paid_access'] = true; + $state['need_purchase'] = false; + } $state['can_view'] = $state['has_paid_access'] && (!$state['need_login'] || $uid > 0); $state['can_download'] = false; @@ -217,6 +257,26 @@ class AKSRT_Content_Visibility { ]); } + // Member-only (pay_limit). + if (!empty($state['need_vip']) && empty($state['has_paid_access']) && empty($state['can_download'])) { + $need_level = (int) ($state['pay_limit'] ?? 1); + return new WP_Error('aksrt_need_vip', sprintf( + /* translators: %d: required VIP level */ + __('该资源仅限 VIP%s 及以上会员购买/下载。', 'aksrt'), + max(1, $need_level) + ), [ + 'status' => 403, + 'pay_limit' => $need_level, + ]); + } + + // Cash purchase required (order path). + if (!empty($state['need_purchase']) && empty($state['has_paid_access']) && $cost <= 0 && (($state['pay_modo'] ?? '0') === '0')) { + return new WP_Error('aksrt_need_purchase', __('请先购买后再下载。', 'aksrt'), [ + 'status' => 402, + ]); + } + // Zibll daily free-download quota. if ($cost <= 0 && function_exists('aksrt_check_free_download_quota')) { $quota = aksrt_check_free_download_quota($uid, $cost); @@ -225,7 +285,12 @@ class AKSRT_Content_Visibility { } } - if (!empty($paid_settings['download_credit']) && $cost > 0) { + $pay_modo = (string) ($state['pay_modo'] ?? '0'); + $need_credit_check = $cost > 0 && ( + $pay_modo === 'points' + || !empty($paid_settings['download_credit']) + ); + if ($need_credit_check) { $balance = class_exists('AKSRT_Credits') ? AKSRT_Credits::balance($uid) : 0; if ($balance < $cost) { return new WP_Error('aksrt_credits_insufficient', __('积分不足,无法下载。', 'aksrt'), [ diff --git a/inc/classes/class-aksrt-download.php b/inc/classes/class-aksrt-download.php index c00c3188..498b6393 100644 --- a/inc/classes/class-aksrt-download.php +++ b/inc/classes/class-aksrt-download.php @@ -114,6 +114,11 @@ class AKSRT_Download { $paid_settings = AKSRT_Settings::get_paid_content_settings(); $cost = aksrt_resource_download_cost($post); $uid = get_current_user_id(); + $priced = function_exists('aksrt_get_user_pay_price') ? aksrt_get_user_pay_price($post, $uid) : null; + $pay_modo = is_array($priced) + ? ((($priced['modo'] ?? '0') === 'points') ? 'points' : '0') + : ((($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'); + $display_price = is_array($priced) ? (float) ($priced['amount'] ?? 0) : (float) ($meta['price'] ?? 0); $need_captcha = !empty($paid_settings['download_captcha']) || !empty($meta['require_captcha']); if ($need_captcha && function_exists('aksrt_is_captcha_enabled_for') && !aksrt_is_captcha_enabled_for('download')) { @@ -122,7 +127,10 @@ class AKSRT_Download { return new WP_REST_Response([ 'post_id' => $post_id, - 'price' => (float) $meta['price'], + 'price' => $display_price, + 'base_price' => is_array($priced) ? (float) ($priced['base'] ?? $display_price) : (float) ($meta['price'] ?? 0), + 'pay_modo' => $pay_modo, + 'pay_limit' => (int) ($meta['pay_limit'] ?? 0), 'cost' => $cost, 'require_login' => !empty($paid_settings['download_login']) || !empty($meta['require_login']) || (!empty($paid_settings['free_logged_show']) && $cost <= 0), 'require_captcha' => $need_captcha, @@ -139,7 +147,7 @@ class AKSRT_Download { 'type' => $row['type'] ?? 'direct', 'has_extract' => $extract_code !== '', ]; - }, $meta['downloads'], array_keys($meta['downloads'])), + }, $meta['downloads'] ?? [], array_keys($meta['downloads'] ?? [])), ], 200); } @@ -354,14 +362,25 @@ class AKSRT_Download { header('Content-Length: ' . (string) $size); $chunk = 1024 * 64; // 64KB + $sleep_us = 0; if ($speed_kb > 0) { - $chunk = max(1024, min($chunk, $speed_kb * 1024)); + // Emit ~speed_kb KB per second using fixed 64KB chunks when possible. + $bytes_per_sec = max(1024, $speed_kb * 1024); + $chunk = min(1024 * 64, $bytes_per_sec); + $chunk = max(1024, $chunk); + // Sleep so chunk_bytes / second ≈ bytes_per_sec. + $sleep_us = (int) max(0, (int) round(($chunk / $bytes_per_sec) * 1000000) - 1000); } $fh = fopen($path, 'rb'); if (!$fh) { $this->fail_410(); } + // Disable output compression for accurate Content-Length / throttle. + if (function_exists('apache_setenv')) { + @apache_setenv('no-gzip', '1'); + } + @ini_set('zlib.output_compression', 'Off'); while (!feof($fh)) { $buf = fread($fh, $chunk); if ($buf === false) { @@ -371,9 +390,8 @@ class AKSRT_Download { if (function_exists('flush')) { flush(); } - if ($speed_kb > 0) { - // Approximate throttle: sleep 1s per speed_kb window when chunk ~= speed_kb KB. - usleep(200000); // 0.2s per chunk for smoother throttle + if ($sleep_us > 0) { + usleep($sleep_us); } } fclose($fh); diff --git a/inc/helpers/aksrt-pay.php b/inc/helpers/aksrt-pay.php index ff7a8e9a..632448a4 100644 --- a/inc/helpers/aksrt-pay.php +++ b/inc/helpers/aksrt-pay.php @@ -149,14 +149,26 @@ if (!function_exists('aksrt_get_download_box_meta')) { $pay_meta = aksrt_get_pay_meta($p); $download_count = (int) get_post_meta($p->ID, AKSRT_Domain_Model::RESOURCE_DOWNLOAD_COUNT_KEY, true); $view_count = (int) get_post_meta($p->ID, AKSRT_Domain_Model::POST_VIEW_COUNT_KEY, true); + $virtual_sales = max(0, (int) ($pay_meta['virtual_sales'] ?? 0)); + $show_sales = !isset($pay_meta['show_sales']) || !empty($pay_meta['show_sales']); + $pay_modo = (($pay_meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; + $pay_limit = (int) ($pay_meta['pay_limit'] ?? 0); + if (!in_array($pay_limit, [0, 1, 2], true)) { + $pay_limit = 0; + } return [ 'version' => (string) ($pay_meta['version'] ?? ''), 'size' => (string) ($pay_meta['size'] ?? ''), 'price' => (float) ($pay_meta['price'] ?? 0), + 'points_price' => (int) ($pay_meta['points_price'] ?? 0), + 'pay_modo' => $pay_modo, + 'pay_limit' => $pay_limit, 'original_price' => (float) ($pay_meta['original_price'] ?? 0), 'vip1_price' => (float) ($pay_meta['vip1_price'] ?? 0), 'vip2_price' => (float) ($pay_meta['vip2_price'] ?? 0), + 'vip1_points' => (int) ($pay_meta['vip1_points'] ?? 0), + 'vip2_points' => (int) ($pay_meta['vip2_points'] ?? 0), 'pay_type' => (string) ($pay_meta['pay_type'] ?? AKSRT_PAY_TYPE_NO), 'license' => (string) ($pay_meta['license'] ?? ''), 'compat' => (string) ($pay_meta['compat'] ?? ''), @@ -164,6 +176,9 @@ if (!function_exists('aksrt_get_download_box_meta')) { 'downloads' => array_values(is_array($pay_meta['downloads'] ?? null) ? $pay_meta['downloads'] : []), 'download_count' => max(0, $download_count), 'view_count' => max(0, $view_count), + 'virtual_sales' => $virtual_sales, + 'sales_count' => $show_sales ? ($virtual_sales + max(0, $download_count)) : 0, + 'show_sales' => $show_sales, 'require_login' => !empty($pay_meta['require_login']), 'require_captcha' => !empty($pay_meta['require_captcha']), 'subject_label' => __('内容', 'aksrt'), @@ -171,6 +186,89 @@ if (!function_exists('aksrt_get_download_box_meta')) { } } +/** + * Resolve the amount a given user should pay for a post (Zibll VIP tier min). + * + * VIP tier prices only apply when configured as a positive discount (< base). + * Full free for VIP uses vip_free_download / exempt roles (handled separately). + * + * @param int|WP_Post|null $post + * @param int $user_id + * @return array{amount:float,base:float,vip_amount:float,modo:string,currency:string,is_free:bool,pay_limit:int,vip_level:int,original:float,vip1:float,vip2:float} + */ +if (!function_exists('aksrt_get_user_pay_price')) { + function aksrt_get_user_pay_price($post = null, int $user_id = 0): array { + $meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : AKSRT_Domain_Model::default_pay_meta(); + $pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; + $pay_limit = (int) ($meta['pay_limit'] ?? 0); + if (!in_array($pay_limit, [0, 1, 2], true)) { + $pay_limit = 0; + } + + $uid = $user_id > 0 ? $user_id : (function_exists('get_current_user_id') ? (int) get_current_user_id() : 0); + $vip_level = 0; + if ($uid > 0 && function_exists('aksrt_get_user_vip_level')) { + $vip_level = (int) aksrt_get_user_vip_level($uid); + } + if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) { + $vip_level = max($vip_level, 2); + } + + $currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt'); + $cash_symbol = '¥'; + if (class_exists('AKSRT_Settings')) { + $paid = AKSRT_Settings::get_paid_content_settings(); + $cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥'); + } + + if ($pay_modo === 'points') { + $base = (float) ($meta['points_price'] ?? 0); + $vip1 = (float) ($meta['vip1_points'] ?? 0); + $vip2 = (float) ($meta['vip2_points'] ?? 0); + $currency_label = $currency; + } else { + $base = (float) ($meta['price'] ?? 0); + $vip1 = (float) ($meta['vip1_price'] ?? 0); + $vip2 = (float) ($meta['vip2_price'] ?? 0); + $currency_label = $cash_symbol; + } + + // Positive VIP tier prices only (0 defaults mean "not configured"). + $vip_amount = -1.0; + if ($vip_level >= 2 && $vip2 > 0) { + $vip_amount = $vip2; + } elseif ($vip_level >= 1 && $vip1 > 0) { + $vip_amount = $vip1; + } + + $amount = $base; + if ($vip_amount >= 0 && ($base <= 0 || $vip_amount < $base)) { + $amount = $vip_amount; + } + + // Role/VIP free-download exemption (global setting). + if ($amount > 0 && $uid > 0 && function_exists('aksrt_is_download_credit_exempt_user') && aksrt_is_download_credit_exempt_user($uid)) { + $amount = 0.0; + } + + $original = (float) ($meta['original_price'] ?? 0); + + return [ + 'amount' => max(0.0, (float) $amount), + 'base' => max(0.0, (float) $base), + 'vip_amount' => $vip_amount >= 0 ? max(0.0, (float) $vip_amount) : -1.0, + 'modo' => $pay_modo, + 'currency' => $currency_label, + 'is_free' => $amount <= 0, + 'pay_limit' => $pay_limit, + 'vip_level' => $vip_level, + 'original' => $original, + 'vip1' => max(0.0, (float) $vip1), + 'vip2' => max(0.0, (float) $vip2), + ]; + } +} + // ============================================================ // 状态判断 // ============================================================ @@ -374,8 +472,15 @@ if (!function_exists('aksrt_is_download_alone_page_enabled')) { if (!function_exists('aksrt_get_pay_price_label')) { function aksrt_get_pay_price_label($post = null): array { $meta = aksrt_get_pay_meta($post); - $pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; - $pay_limit = (int) ($meta['pay_limit'] ?? 0); + $priced = function_exists('aksrt_get_user_pay_price') + ? aksrt_get_user_pay_price($post) + : null; + $pay_modo = is_array($priced) + ? ((($priced['modo'] ?? '0') === 'points') ? 'points' : '0') + : ((($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'); + $pay_limit = is_array($priced) + ? (int) ($priced['pay_limit'] ?? 0) + : (int) ($meta['pay_limit'] ?? 0); $currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt'); $cash_symbol = '¥'; if (class_exists('AKSRT_Settings')) { @@ -384,32 +489,48 @@ if (!function_exists('aksrt_get_pay_price_label')) { } if ($meta['pay_type'] === AKSRT_PAY_TYPE_VIP || $pay_limit > 0) { - return ['text' => __('会员专属', 'aksrt'), 'free' => false, 'amount' => 0, 'modo' => $pay_modo, 'pay_limit' => $pay_limit]; + return [ + 'text' => __('会员专属', 'aksrt'), + 'free' => false, + 'amount' => 0, + 'base' => is_array($priced) ? (float) ($priced['base'] ?? 0) : 0, + 'modo' => $pay_modo, + 'pay_limit' => $pay_limit, + ]; } - if ($pay_modo === 'points') { - $price = (int) ($meta['points_price'] ?? 0); - if ($price <= 0) { - return ['text' => __('免费', 'aksrt'), 'free' => true, 'amount' => 0, 'modo' => 'points', 'pay_limit' => 0]; - } + $price = is_array($priced) + ? (float) ($priced['amount'] ?? 0) + : ($pay_modo === 'points' ? (float) ($meta['points_price'] ?? 0) : (float) ($meta['price'] ?? 0)); + $base = is_array($priced) ? (float) ($priced['base'] ?? $price) : $price; + + if ($price <= 0) { return [ - 'text' => sprintf(__('%s %s', 'aksrt'), aksrt_format_price((float) $price), $currency), - 'free' => false, - 'amount' => (float) $price, - 'modo' => 'points', + 'text' => __('免费', 'aksrt'), + 'free' => true, + 'amount' => 0, + 'base' => $base, + 'modo' => $pay_modo, 'pay_limit' => 0, ]; } - $price = (float) $meta['price']; - if ($price <= 0) { - return ['text' => __('免费', 'aksrt'), 'free' => true, 'amount' => 0, 'modo' => '0', 'pay_limit' => 0]; + if ($pay_modo === 'points') { + return [ + 'text' => sprintf(__('%s %s', 'aksrt'), aksrt_format_price($price), $currency), + 'free' => false, + 'amount' => $price, + 'base' => $base, + 'modo' => 'points', + 'pay_limit' => 0, + ]; } return [ 'text' => $cash_symbol . aksrt_format_price($price), 'free' => false, 'amount' => $price, + 'base' => $base, 'modo' => '0', 'pay_limit' => 0, ]; @@ -533,8 +654,18 @@ if (!function_exists('aksrt_get_paywall_html')) { function aksrt_get_paywall_html(WP_Post $post): string { $meta = aksrt_get_pay_meta($post); $pay_type = $meta['pay_type']; - $price = (float) $meta['price']; + $priced = function_exists('aksrt_get_user_pay_price') ? aksrt_get_user_pay_price($post) : null; + $pay_modo = is_array($priced) ? ((($priced['modo'] ?? '0') === 'points') ? 'points' : '0') : ((($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'); + $price = is_array($priced) ? (float) ($priced['amount'] ?? 0) : (float) ($meta['price'] ?? 0); $currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt'); + $cash_symbol = '¥'; + if (class_exists('AKSRT_Settings')) { + $paid = AKSRT_Settings::get_paid_content_settings(); + $cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥'); + } + $price_text = $pay_modo === 'points' + ? (aksrt_format_price($price) . ' ' . $currency) + : ($cash_symbol . aksrt_format_price($price)); $type_labels = [ AKSRT_PAY_TYPE_READING => ['icon' => 'fa-lock', 'label' => __('付费阅读', 'aksrt'), 'desc' => __('付费后即可查看完整内容', 'aksrt')], @@ -547,10 +678,11 @@ if (!function_exists('aksrt_get_paywall_html')) { $is_logged_in = is_user_logged_in(); $original_price = (float) ($meta['original_price'] ?? 0); $has_original = $original_price > 0 && $original_price > $price; + $pay_limit = (int) ($meta['pay_limit'] ?? 0); ob_start(); ?> -
+
@@ -560,10 +692,12 @@ if (!function_exists('aksrt_get_paywall_html')) {

- 0) : ?> - + 0) : ?> + + 0) : ?> + - + @@ -573,9 +707,13 @@ if (!function_exists('aksrt_get_paywall_html')) { + 0) : ?> + + + 0) : ?> -

diff --git a/inc/helpers/aksrt-resource.php b/inc/helpers/aksrt-resource.php index cb429475..845062f3 100644 --- a/inc/helpers/aksrt-resource.php +++ b/inc/helpers/aksrt-resource.php @@ -61,13 +61,21 @@ if (!function_exists('aksrt_get_resource_meta')) { return $row; }, $downloads))); + $virtual_sales = max(0, (int) ($pay_meta['virtual_sales'] ?? 0)); + $show_sales = !isset($pay_meta['show_sales']) || !empty($pay_meta['show_sales']); + return [ 'version' => (string) ($pay_meta['version'] ?? ''), 'size' => (string) ($pay_meta['size'] ?? ''), 'price' => (float) ($pay_meta['price'] ?? 0), + 'points_price' => (int) ($pay_meta['points_price'] ?? 0), + 'pay_modo' => (($pay_meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0', + 'pay_limit' => in_array((int) ($pay_meta['pay_limit'] ?? 0), [0, 1, 2], true) ? (int) $pay_meta['pay_limit'] : 0, 'original_price' => (float) ($pay_meta['original_price'] ?? 0), 'vip1_price' => (float) ($pay_meta['vip1_price'] ?? 0), 'vip2_price' => (float) ($pay_meta['vip2_price'] ?? 0), + 'vip1_points' => (int) ($pay_meta['vip1_points'] ?? 0), + 'vip2_points' => (int) ($pay_meta['vip2_points'] ?? 0), 'pay_type' => (string) ($pay_meta['pay_type'] ?? AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD), 'license' => (string) ($pay_meta['license'] ?? ''), 'compat' => (string) ($pay_meta['compat'] ?? ''), @@ -75,6 +83,9 @@ if (!function_exists('aksrt_get_resource_meta')) { 'downloads' => $downloads, 'download_count' => max(0, max($download_count, $legacy_download_count)), 'view_count' => max(0, max($resource_view_count, $common_view_count)), + 'virtual_sales' => $virtual_sales, + 'sales_count' => $show_sales ? ($virtual_sales + max(0, max($download_count, $legacy_download_count))) : 0, + 'show_sales' => $show_sales, 'require_login' => !empty($pay_meta['require_login']), 'require_captcha' => !empty($pay_meta['require_captcha']), ]; @@ -89,9 +100,14 @@ if (!function_exists('aksrt_default_resource_meta')) { 'version' => (string) ($meta['version'] ?? ''), 'size' => (string) ($meta['size'] ?? ''), 'price' => (float) ($meta['price'] ?? 0), + 'points_price' => (int) ($meta['points_price'] ?? 0), + 'pay_modo' => (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0', + 'pay_limit' => in_array((int) ($meta['pay_limit'] ?? 0), [0, 1, 2], true) ? (int) $meta['pay_limit'] : 0, 'original_price' => (float) ($meta['original_price'] ?? 0), 'vip1_price' => (float) ($meta['vip1_price'] ?? 0), 'vip2_price' => (float) ($meta['vip2_price'] ?? 0), + 'vip1_points' => (int) ($meta['vip1_points'] ?? 0), + 'vip2_points' => (int) ($meta['vip2_points'] ?? 0), 'pay_type' => AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD, 'license' => (string) ($meta['license'] ?? ''), 'compat' => (string) ($meta['compat'] ?? ''), @@ -99,6 +115,9 @@ if (!function_exists('aksrt_default_resource_meta')) { 'downloads' => [], 'download_count' => 0, 'view_count' => 0, + 'virtual_sales' => 0, + 'sales_count' => 0, + 'show_sales' => true, 'require_login' => !empty($meta['require_login']), 'require_captcha' => !empty($meta['require_captcha']), ]; @@ -171,9 +190,6 @@ if (!function_exists('aksrt_resource_card_badge_text')) { $pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; $pay_limit = (int) ($meta['pay_limit'] ?? 0); - $price = $pay_modo === 'points' - ? (float) ($meta['points_price'] ?? 0) - : (float) ($meta['price'] ?? 0); $type_labels = function_exists('aksrt_get_pay_types') ? aksrt_get_pay_types() : []; $type_name = $type_labels[$pay_type] ?? __('付费内容', 'aksrt'); @@ -182,6 +198,15 @@ if (!function_exists('aksrt_resource_card_badge_text')) { return __('会员专属', 'aksrt'); } + $priced = function_exists('aksrt_get_user_pay_price') + ? aksrt_get_user_pay_price($p) + : [ + 'amount' => $pay_modo === 'points' ? (float) ($meta['points_price'] ?? 0) : (float) ($meta['price'] ?? 0), + 'modo' => $pay_modo, + ]; + $price = (float) ($priced['amount'] ?? 0); + $pay_modo = (($priced['modo'] ?? $pay_modo) === 'points') ? 'points' : '0'; + // Free paid-type (price 0). if ($price <= 0) { if ($pay_type === 'download') { @@ -430,14 +455,20 @@ if (!function_exists('aksrt_resource_download_cost')) { * 优先级:单条 _aksrt_res_price > 全局 download_default_cost。 */ function aksrt_resource_download_cost($post = null): int { - $meta = aksrt_get_resource_meta($post); - // Prefer unified pay meta when available (pay_modo / points_price). - if (function_exists('aksrt_get_pay_meta')) { - $pay = aksrt_get_pay_meta($post); - if (is_array($pay) && (($pay['pay_type'] ?? '') === 'download' || !empty($pay['downloads']))) { - $meta = array_merge($meta, $pay); + $p = get_post($post); + $uid = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0; + + // Paid order → no further credit deduct. Avoid has_paid_access() here (recursion via access state). + if ($p instanceof WP_Post && $uid > 0 && class_exists('AKSRT_Order')) { + if (AKSRT_Order::get_instance()->has_paid_access( + $uid, + AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE, + (int) $p->ID + )) { + return 0; } } + $paid_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_paid_content_settings() : [ @@ -445,25 +476,38 @@ if (!function_exists('aksrt_resource_download_cost')) { 'default_cost' => 0, ]; - $pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; - // Cash mode: do not deduct credits for download unlock (order/cash checkout path). - // Points mode or legacy credit gate: use points_price / price. + $priced = function_exists('aksrt_get_user_pay_price') + ? aksrt_get_user_pay_price($post, $uid) + : null; + + $pay_modo = is_array($priced) && (($priced['modo'] ?? '0') === 'points') ? 'points' : '0'; + if (!is_array($priced) && function_exists('aksrt_get_pay_meta')) { + $pay = aksrt_get_pay_meta($post); + $pay_modo = (($pay['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; + } + + // Cash mode: order/checkout path unless legacy download_credit is on. if ($pay_modo !== 'points' && empty($paid_settings['download_credit'])) { return 0; } - if ($pay_modo === '0' && empty($paid_settings['download_credit'])) { - // Cash purchase path — cost handled by orders, not credit deduct. + + if ($uid > 0 && function_exists('aksrt_is_download_credit_exempt_user') && aksrt_is_download_credit_exempt_user($uid)) { return 0; } - if (function_exists('get_current_user_id') && aksrt_is_download_credit_exempt_user((int) get_current_user_id())) { + if (is_array($priced)) { + $price = (float) ($priced['amount'] ?? 0); + if ($price > 0) { + return (int) ceil($price); + } return 0; } + // Fallback without helper. + $meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : aksrt_get_resource_meta($post); if ($pay_modo === 'points') { $price = (float) ($meta['points_price'] ?? $meta['price'] ?? 0); } else { - // When download_credit is on, treat price as credit cost (legacy AKSRT). $price = (float) ($meta['price'] ?? 0); } if ($price > 0) { diff --git a/templates/parts/resource-download-box.php b/templates/parts/resource-download-box.php index 7b04838b..153c9eac 100644 --- a/templates/parts/resource-download-box.php +++ b/templates/parts/resource-download-box.php @@ -47,6 +47,15 @@ $paid_settings = class_exists('AKSRT_Settings') $cost = function_exists('aksrt_get_download_cost') ? aksrt_get_download_cost($post_id) : (int) ceil((float) ($meta['price'] ?? 0)); $credit_active = !empty($paid_settings['download_credit']) && $cost > 0; +$pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; +if (function_exists('aksrt_get_user_pay_price')) { + $priced = aksrt_get_user_pay_price($post_id); + $pay_modo = (($priced['modo'] ?? $pay_modo) === 'points') ? 'points' : '0'; +} +// Points mode always uses credit deduct path when cost > 0. +if ($pay_modo === 'points' && $cost > 0) { + $credit_active = true; +} $captcha_active = !empty($paid_settings['download_captcha']) || !empty($meta['require_captcha']); if ($captcha_active && function_exists('aksrt_is_captcha_enabled_for') && !aksrt_is_captcha_enabled_for('download')) { $captcha_active = false; @@ -55,58 +64,83 @@ $balance = (int) ($gate_data['balance'] ?? 0); $login_url = (string) ($gate_data['login_url'] ?? wp_login_url(get_permalink($post_id))); $is_need_login = $gate_blocked && $gate_code === 'aksrt_need_login'; - $is_low_credit = $gate_blocked && $gate_code === 'aksrt_credits_insufficient'; +$is_low_credit = $gate_blocked && $gate_code === 'aksrt_credits_insufficient'; +$is_need_vip = $gate_blocked && $gate_code === 'aksrt_need_vip'; +$is_need_purchase = $gate_blocked && $gate_code === 'aksrt_need_purchase'; - // Zibll pay_free_logged_show: free downloads still require login. - $free_logged_show = !empty($paid_settings['free_logged_show']); - $price_amount = (float) ($meta['price'] ?? 0); - if ($price_amount <= 0 && $free_logged_show && !is_user_logged_in()) { - $is_need_login = true; - $gate_blocked = true; - } +// Zibll pay_free_logged_show: free downloads still require login. +$free_logged_show = !empty($paid_settings['free_logged_show']); +$price_amount = (float) ($price_label['amount'] ?? $meta['price'] ?? 0); +if ($price_amount <= 0 && $free_logged_show && !is_user_logged_in()) { + $is_need_login = true; + $gate_blocked = true; +} - // Zibll down_alone_page: single entry to independent download page. - $alone_page = !empty($paid_settings['down_alone_page']) || !empty($paid_settings['download']['alone_page']); - $alone_url = ''; - if ($alone_page && function_exists('aksrt_get_single_download_page_url')) { - $alone_url = aksrt_get_single_download_page_url($post_id); - } - $use_alone_entry = $alone_page && $alone_url !== '' && !$is_need_login && !$is_low_credit; - if (!empty($GLOBALS['aksrt_on_download_file_page'])) { - $use_alone_entry = false; - } +// Zibll down_alone_page: single entry to independent download page. +$alone_page = !empty($paid_settings['down_alone_page']) || !empty($paid_settings['download']['alone_page']); +$alone_url = ''; +if ($alone_page && function_exists('aksrt_get_single_download_page_url')) { + $alone_url = aksrt_get_single_download_page_url($post_id); +} +$use_alone_entry = $alone_page && $alone_url !== '' && !$is_need_login && !$is_low_credit && !$is_need_vip && !$is_need_purchase; +if (!empty($GLOBALS['aksrt_on_download_file_page'])) { + $use_alone_entry = false; +} // 缩略图 $thumbnail = get_the_post_thumbnail_url($post_id, 'medium') ?: ''; $has_thumbnail = $thumbnail !== ''; -// 原价 & VIP 价 +// 原价 & VIP 价(按 pay_modo 切换现金/积分档) $original_price = (float) ($meta['original_price'] ?? 0); -$vip1_price = (float) ($meta['vip1_price'] ?? 0); -$vip2_price = (float) ($meta['vip2_price'] ?? 0); -$has_original = $original_price > 0 && $original_price > (float) ($meta['price'] ?? 0); -$has_vip = $vip1_price > 0 || $vip2_price > 0; +if ($pay_modo === 'points') { + $vip1_price = (float) ($meta['vip1_points'] ?? 0); + $vip2_price = (float) ($meta['vip2_points'] ?? 0); + $base_price = (float) ($meta['points_price'] ?? $meta['price'] ?? 0); +} else { + $vip1_price = (float) ($meta['vip1_price'] ?? 0); + $vip2_price = (float) ($meta['vip2_price'] ?? 0); + $base_price = (float) ($meta['price'] ?? 0); +} +$has_original = $original_price > 0 && $original_price > $base_price; +$has_vip = ($vip1_price > 0 && $vip1_price < $base_price) || ($vip2_price > 0 && $vip2_price < $base_price); +$pay_limit = (int) ($meta['pay_limit'] ?? 0); // 销量 $download_count = (int) ($meta['download_count'] ?? 0); $sales_count = (int) ($meta['sales_count'] ?? 0); $display_count = max($download_count, $sales_count); +$show_sales = !isset($meta['show_sales']) || !empty($meta['show_sales']); // 付费类型徽章 $pay_type_badge = function_exists('aksrt_get_pay_type_badge') ? aksrt_get_pay_type_badge($pay_type) : ''; // 货币名称 $currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt'); +$cash_symbol = '¥'; +if (class_exists('AKSRT_Settings')) { + $paid_display = AKSRT_Settings::get_paid_content_settings(); + $cash_symbol = (string) ($paid_display['display']['currency_symbol'] ?? '¥'); +} +$format_vip = static function (float $amount) use ($pay_modo, $currency, $cash_symbol): string { + if ($amount <= 0) { + return (string) __('免费', 'aksrt'); + } + $n = function_exists('aksrt_format_price') ? aksrt_format_price($amount) : (string) $amount; + return $pay_modo === 'points' ? ($n . ' ' . $currency) : ($cash_symbol . $n); +}; ?>

- 0) : ?> + 0) : ?> "> - + - +
- +
- 0) : ?> + 0 && $vip1_price < $base_price) : ?> - 0) : ?> + 0 && $vip2_price < $base_price) : ?> @@ -179,7 +219,19 @@ $currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name()

- + +
+ + + get_error_message() : __('会员专属资源', 'aksrt')); ?> + + +
+
@@ -220,6 +272,11 @@ $currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() $btn_text = __('登录后下载', 'aksrt'); $btn_icon = 'fa-sign-in-alt'; $btn_modifier = ' resource-download-box__btn--login'; + } elseif ($is_need_purchase) { + $btn_tag = 'button'; + $btn_text = __('购买后下载', 'aksrt'); + $btn_icon = 'fa-shopping-cart'; + $btn_modifier = ' resource-download-box__btn--purchase'; } elseif ($is_low_credit) { $btn_tag = 'button'; $btn_text = sprintf(__('解锁下载(%d 积分)', 'aksrt'), $cost); @@ -256,7 +313,7 @@ $currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name()
+
+ +

+ + get_error_message() : __('该资源仅限会员购买/下载。', 'aksrt')); ?> +

+ +

+ +