Files
wordpress-theme-aksrt/inc/helpers/aksrt-pay.php
T

1457 lines
61 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 Pay System — 统一付费内容系统(1:1 对齐 zibll)
*
* 核心机制:
* - 任何 post 通过 _aksrt_pay meta 成为付费文章
* - pay_type 驱动行为(no/reading/download/gallery/video/vip)
* - 统一的价格、VIP 价、下载源、属性、图库、视频管理
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
// ============================================================
// 付费类型常量
// ============================================================
if (!defined('AKSRT_PAY_TYPE_NO')) {
define('AKSRT_PAY_TYPE_NO', \AKSRT\Core\DomainModel::PAY_TYPE_NO);
}
if (!defined('AKSRT_PAY_TYPE_READING')) {
define('AKSRT_PAY_TYPE_READING', \AKSRT\Core\DomainModel::PAY_TYPE_READING);
}
if (!defined('AKSRT_PAY_TYPE_DOWNLOAD')) {
define('AKSRT_PAY_TYPE_DOWNLOAD', \AKSRT\Core\DomainModel::PAY_TYPE_DOWNLOAD);
}
if (!defined('AKSRT_PAY_TYPE_GALLERY')) {
define('AKSRT_PAY_TYPE_GALLERY', \AKSRT\Core\DomainModel::PAY_TYPE_GALLERY);
}
if (!defined('AKSRT_PAY_TYPE_VIDEO')) {
define('AKSRT_PAY_TYPE_VIDEO', \AKSRT\Core\DomainModel::PAY_TYPE_VIDEO);
}
if (!defined('AKSRT_PAY_TYPE_VIP')) {
define('AKSRT_PAY_TYPE_VIP', \AKSRT\Core\DomainModel::PAY_TYPE_VIP);
}
/**
* 检查用户是否为 VIP 豁免用户(免扣积分下载)。
*
* @param int $user_id
* @return bool
*/
if (!function_exists('aksrt_is_vip_exempt')) {
function aksrt_is_vip_exempt(int $user_id = 0): bool {
if ($user_id <= 0) {
$user_id = get_current_user_id();
}
if ($user_id <= 0) {
return false;
}
// 优先使用 aksrt_is_download_credit_exempt_user(如果已定义)
if (function_exists('aksrt_is_download_credit_exempt_user')) {
return aksrt_is_download_credit_exempt_user($user_id);
}
// 回退:直接检查 VIP 角色
if (function_exists('aksrt_is_vip') && aksrt_is_vip($user_id)) {
return true;
}
return false;
}
}
function aksrt_get_pay_types(): array {
return \AKSRT\Core\DomainModel::pay_type_labels();
return [
AKSRT_PAY_TYPE_NO => __('不付费', 'aksrt'),
AKSRT_PAY_TYPE_READING => __('付费阅读', 'aksrt'),
AKSRT_PAY_TYPE_DOWNLOAD => __('付费下载', 'aksrt'),
AKSRT_PAY_TYPE_GALLERY => __('付费图库', 'aksrt'),
AKSRT_PAY_TYPE_VIDEO => __('付费视频', 'aksrt'),
AKSRT_PAY_TYPE_VIP => __('VIP 专属', 'aksrt'),
];
}
/**
* Cash payment methods available for content cashiers.
*
* @return array<int, array{id:string,name:string}>
*/
if (!function_exists('aksrt_list_cash_payment_methods')) {
function aksrt_list_cash_payment_methods(bool $logged_in = true): array {
$ids = $logged_in
? ['balance', 'alipay', 'wechatpay', 'paypal', 'stripe', 'epay_alipay', 'epay_wxpay']
: ['alipay', 'wechatpay', 'paypal', 'stripe', 'epay_alipay', 'epay_wxpay'];
if (!class_exists('\AKSRT\Payment\Gateway')) {
// No gateway manager → no real methods (avoid dead balance/alipay stubs).
return [];
}
$methods = [];
$gw = \AKSRT\Payment\Gateway::get_instance();
foreach ($ids as $id) {
$g = $gw->get_gateway($id);
if (!$g) {
continue;
}
if (method_exists($g, 'is_configured') && !$g->is_configured()) {
continue;
}
$methods[] = [
'id' => $id,
'name' => method_exists($g, 'get_name') ? (string) $g->get_name() : $id,
];
}
// Do not inject a fake balance/alipay option when nothing is configured —
// callers should surface “暂无可用支付方式” instead of a dead method.
return $methods;
}
}
// ============================================================
// 元数据读取
// ============================================================
if (!function_exists('aksrt_get_pay_meta')) {
function aksrt_get_pay_meta($post = null): array {
$p = get_post($post);
if (!($p instanceof WP_Post)) return aksrt_default_pay_meta();
$meta = get_post_meta($p->ID, \AKSRT\Core\DomainModel::PAY_META_KEY, true);
if (is_array($meta) && isset($meta['pay_type'])) {
return wp_parse_args(\AKSRT\Core\DomainModel::sanitize_pay_meta($meta), aksrt_default_pay_meta());
}
return aksrt_default_pay_meta();
}
}
if (!function_exists('aksrt_default_pay_meta')) {
function aksrt_default_pay_meta(): array {
return \AKSRT\Core\DomainModel::default_pay_meta();
return [
'pay_type' => AKSRT_PAY_TYPE_NO,
'price' => 0.0,
'original_price' => 0.0,
'vip1_price' => 0.0,
'vip2_price' => 0.0,
'version' => '',
'size' => '',
'license' => '',
'compat' => '',
'demo_url' => '',
'downloads' => [],
'require_login' => false,
'require_captcha' => false,
'attributes' => [],
'gallery' => [],
'video_url' => '',
];
}
}
if (!function_exists('aksrt_is_downloadable_content')) {
function aksrt_is_downloadable_content($post = null): bool {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return false;
}
$meta = aksrt_get_pay_meta($p);
if (($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO) !== AKSRT_PAY_TYPE_DOWNLOAD) {
return false;
}
return !empty($meta['downloads']) && is_array($meta['downloads']);
}
}
if (!function_exists('aksrt_get_download_box_meta')) {
function aksrt_get_download_box_meta($post = null): array {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return [];
}
if (function_exists('aksrt_get_resource_meta') && function_exists('aksrt_is_resource_post') && aksrt_is_resource_post($p)) {
$meta = aksrt_get_resource_meta($p);
$meta['subject_label'] = __('资源', 'aksrt');
return $meta;
}
$pay_meta = aksrt_get_pay_meta($p);
$download_count = (int) get_post_meta($p->ID, \AKSRT\Core\DomainModel::RESOURCE_DOWNLOAD_COUNT_KEY, true);
$view_count = (int) get_post_meta($p->ID, \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY, true);
$virtual_sales = max(0, (int) ($pay_meta['virtual_sales'] ?? 0));
$sales_volume = (int) get_post_meta($p->ID, \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true);
$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;
}
$over_price = max(0.0, (float) ($pay_meta['download_limit_over_price'] ?? 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'] ?? ''),
'demo_url' => (string) ($pay_meta['demo_url'] ?? ''),
'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_volume' => max(0, $sales_volume),
// Zibll: virtual (pay_cuont) + real sales_volume; fall back to downloads when no sales yet.
'sales_count' => $show_sales ? ($virtual_sales + max(0, $sales_volume, $download_count)) : 0,
'show_sales' => $show_sales,
'download_limit_over_price' => $over_price,
'require_login' => !empty($pay_meta['require_login']),
'require_captcha' => !empty($pay_meta['require_captcha']),
'subject_label' => __('内容', 'aksrt'),
];
}
}
/**
* Latest paid content order for a user/post (resource product_type).
*
* @return array<string,mixed>|null
*/
if (!function_exists('aksrt_get_user_content_paid_order')) {
function aksrt_get_user_content_paid_order(int $post_id, int $user_id = 0): ?array {
$uid = $user_id > 0 ? $user_id : get_current_user_id();
if ($uid <= 0 || $post_id <= 0 || !class_exists('\AKSRT\Payment\OrderRepository')) {
return null;
}
$orders = \AKSRT\Payment\OrderRepository::get_instance()->get_user_orders($uid, [
'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE,
'status' => '',
'limit' => 20,
'offset' => 0,
]);
foreach ($orders as $order) {
if ((int) ($order['product_id'] ?? 0) !== $post_id) {
continue;
}
$status = (string) ($order['status'] ?? '');
if (\AKSRT\Core\DomainModel::is_paid_order_status($status)) {
return $order;
}
}
return null;
}
}
/**
* Increment real sales volume for a paid content post (idempotent per order via order meta note flag optional).
*/
if (!function_exists('aksrt_bump_content_sales_volume')) {
function aksrt_bump_content_sales_volume(int $post_id, int $delta = 1): int {
if ($post_id <= 0 || $delta === 0) {
return 0;
}
$key = \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY;
$cur = (int) get_post_meta($post_id, $key, true);
$next = max(0, $cur + $delta);
update_post_meta($post_id, $key, $next);
return $next;
}
}
/**
* Whether guest cash purchase is allowed (Zibll pay_no_logged_in + pay_limit=0).
*/
if (!function_exists('aksrt_allow_guest_content_pay')) {
function aksrt_allow_guest_content_pay($post = null): bool {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return false;
}
$meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($p) : [];
$pay_limit = (int) ($meta['pay_limit'] ?? 0);
if ($pay_limit > 0) {
return false;
}
$pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0';
if ($pay_modo === 'points') {
return false; // points require login
}
$order = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_order_settings() : [];
$paid = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_paid_content_settings() : [];
$allow = !empty($order['no_logged_in']) || !empty($paid['no_logged_in']);
/**
* Filter guest content pay allow.
*
* @param bool $allow
* @param WP_Post $p
*/
return (bool) apply_filters('aksrt_allow_guest_content_pay', $allow, $p);
}
}
/**
* Cookie lifetime days for guest paid content (Zibll pay_cookie_day).
*/
if (!function_exists('aksrt_guest_pay_cookie_days')) {
function aksrt_guest_pay_cookie_days(int $post_id = 0): int {
$days = 15;
if (class_exists('\AKSRT\Settings\Settings')) {
$order = \AKSRT\Settings\Settings::get_order_settings();
$days = max(1, min(31, (int) ($order['cookie_day'] ?? 15)));
}
/**
* @param int $days
* @param int $post_id
*/
return max(1, (int) apply_filters('aksrt_guest_pay_cookie_days', $days, $post_id));
}
}
/**
* Client IP helper for cookie token binding.
*/
if (!function_exists('aksrt_pay_client_ip')) {
function aksrt_pay_client_ip(): string {
if (function_exists('aksrt_get_client_ip')) {
return (string) aksrt_get_client_ip();
}
return (string) ($_SERVER['REMOTE_ADDR'] ?? '');
}
}
/**
* Build token material for guest order cookie (zibll-style).
*
* @param array<string,mixed> $order
*/
if (!function_exists('aksrt_content_order_cookie_token_key')) {
function aksrt_content_order_cookie_token_key(array $order): string {
$meta = is_array($order['meta_payload'] ?? null) ? $order['meta_payload'] : [];
$ip = (string) ($meta['ip_address'] ?? $order['ip_address'] ?? '');
$id = (string) ($order['id'] ?? $order['order_number'] ?? '');
$post_id = (int) ($order['product_id'] ?? $meta['post_id'] ?? 0);
$num = (string) ($order['order_number'] ?? '');
$amount = function_exists('aksrt_format_price')
? aksrt_format_price((float) ($order['amount'] ?? 0))
: (string) round((float) ($order['amount'] ?? 0), 2);
return $id . '_' . $post_id . '_' . trim($num) . '_' . $amount . '_' . trim($ip);
}
}
/**
* Set guest paid cookies after cash order paid (content).
*
* @param array<string,mixed> $order
*/
if (!function_exists('aksrt_set_guest_pay_cookies')) {
function aksrt_set_guest_pay_cookies(array $order): void {
$post_id = (int) ($order['product_id'] ?? 0);
$order_number = (string) ($order['order_number'] ?? '');
if ($post_id <= 0 || $order_number === '') {
return;
}
$days = aksrt_guest_pay_cookie_days($post_id);
$expire = time() + (DAY_IN_SECONDS * $days);
$path = defined('COOKIEPATH') && COOKIEPATH ? COOKIEPATH : '/';
$domain = defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : '';
$secure = is_ssl();
$httponly = true;
setcookie('aksrt_pay_' . $post_id, $order_number, $expire, $path, $domain, $secure, $httponly);
$_COOKIE['aksrt_pay_' . $post_id] = $order_number;
$token = md5(aksrt_content_order_cookie_token_key($order));
$token_name = 'aksrt_pay_token_' . md5($order_number);
setcookie($token_name, $token, $expire, $path, $domain, $secure, $httponly);
$_COOKIE[$token_name] = $token;
}
}
/**
* Set guest paid cookies after content order paid (Zibll zibpay_posts_order_created_set_cookie).
*
* @param array<string,mixed> $order
*/
if (!function_exists('aksrt_set_guest_content_pay_cookies')) {
function aksrt_set_guest_content_pay_cookies(array $order): void {
aksrt_set_guest_pay_cookies($order);
}
}
/**
* Resolve guest paid order from browser cookie for a product.
*
* @return array<string,mixed>|null
*/
if (!function_exists('aksrt_get_guest_paid_order')) {
function aksrt_get_guest_paid_order(int $product_id, string $product_type = ''): ?array {
if ($product_id <= 0 || empty($_COOKIE['aksrt_pay_' . $product_id])) {
return null;
}
if (!class_exists('\AKSRT\Payment\OrderRepository') || !class_exists('\AKSRT\Core\DomainModel')) {
return null;
}
$order_number = sanitize_text_field((string) $_COOKIE['aksrt_pay_' . $product_id]);
if ($order_number === '') {
return null;
}
$order = \AKSRT\Payment\OrderRepository::get_instance()->get($order_number);
if (!$order) {
return null;
}
$ptype = sanitize_key((string) ($order['product_type'] ?? ''));
$want = sanitize_key($product_type);
if ($want !== '' && $ptype !== $want) {
return null;
}
if ((int) ($order['product_id'] ?? 0) !== $product_id) {
return null;
}
// Guest orders only (logged-in users use user_id access).
if ((int) ($order['user_id'] ?? -1) !== 0) {
return null;
}
$status = (string) ($order['status'] ?? '');
if (!\AKSRT\Core\DomainModel::is_paid_order_status($status)) {
return null;
}
$paid_at = (string) ($order['paid_at'] ?? $order['created_at'] ?? '');
if ($paid_at !== '') {
$ts = strtotime($paid_at);
$days = aksrt_guest_pay_cookie_days($product_id);
if ($ts !== false && ($ts + DAY_IN_SECONDS * $days) < time()) {
return null;
}
}
$token_name = 'aksrt_pay_token_' . md5($order_number);
$cookie_token = isset($_COOKIE[$token_name]) ? (string) $_COOKIE[$token_name] : '';
$expect = md5(aksrt_content_order_cookie_token_key($order));
if ($cookie_token === '' || !hash_equals($expect, $cookie_token)) {
return null;
}
return $order;
}
}
/**
* Resolve guest paid order from browser cookie for a post (content/resource).
*
* @return array<string,mixed>|null
*/
if (!function_exists('aksrt_get_guest_content_paid_order')) {
function aksrt_get_guest_content_paid_order(int $post_id): ?array {
$type = class_exists('\AKSRT\Core\DomainModel')
? \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE
: 'resource';
return aksrt_get_guest_paid_order($post_id, $type);
}
}
/**
* Whether current request has paid access via guest cookie (content/resource product).
*/
if (!function_exists('aksrt_guest_has_paid_access')) {
function aksrt_guest_has_paid_access(int $post_id): bool {
return null !== aksrt_get_guest_content_paid_order($post_id);
}
}
/**
* 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\Core\DomainModel::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\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt');
$cash_symbol = '¥';
if (class_exists('\AKSRT\Settings\Settings')) {
$paid = \AKSRT\Settings\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),
];
}
}
/**
* Whether content cashier may accept coupons for this post (zibll coupon_post_s || coupon_s).
*/
if (!function_exists('aksrt_is_content_coupon_allowed')) {
function aksrt_is_content_coupon_allowed($post = null): bool {
$meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : [];
if (!empty($meta['coupon_s'])) {
return true;
}
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_content_coupon_settings')) {
$cfg = \AKSRT\Settings\Settings::get_content_coupon_settings();
return !empty($cfg['enabled']);
}
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_paid_content_settings')) {
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
return !empty($paid['coupon_post_s']);
}
return false;
}
}
/**
* Buyer-facing 推广折扣 amount for a post (0 if no eligible referrer).
* Cash only; mirrors zibpay_get_post_rebate_discount.
*/
if (!function_exists('aksrt_get_post_rebate_discount')) {
function aksrt_get_post_rebate_discount($post = null, int $user_id = 0): float {
$meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : [];
$fixed = max(0.0, (float) ($meta['pay_rebate_discount'] ?? 0));
if ($fixed <= 0) {
return 0.0;
}
if (!class_exists('\AKSRT\Settings\Settings') || !method_exists('\AKSRT\Settings\Settings', 'get_payment_affiliate_settings')) {
return 0.0;
}
$aff = \AKSRT\Settings\Settings::get_payment_affiliate_settings();
if (empty($aff['enabled'])) {
return 0.0;
}
$uid = $user_id > 0 ? $user_id : (function_exists('get_current_user_id') ? (int) get_current_user_id() : 0);
$referrer_id = class_exists('\AKSRT\Payment\ReferralAttribution')
? \AKSRT\Payment\ReferralAttribution::get_instance()->resolve($uid)
: 0;
if ($referrer_id <= 0) {
return 0.0;
}
// Need a positive ratio for referrer (VIP tier of referrer preferred).
$ratio = (int) round(\AKSRT\Settings\Settings::affiliate_ratio_for_user($referrer_id));
if ($ratio <= 0) {
return 0.0;
}
return round($fixed, 2);
}
}
/**
* Whether cash checkout may use points deduction (zibll points_deduction).
*
* zibll: zibpay_is_allow_points_deduction() always returns false ("暂未启用").
* Keep the same contract: schema/UI may exist, runtime is hard-off unless filter overrides.
*/
if (!function_exists('aksrt_is_allow_points_deduction')) {
function aksrt_is_allow_points_deduction(string $context = 'content'): bool {
// Mirror zibll hard-off first.
$allowed = false;
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_all')) {
$all = \AKSRT\Settings\Settings::get_all();
// Even if switch on, default remains false like zibll until product enables it.
if (!empty($all['points_deduction_s']) && class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'credits_enabled') && \AKSRT\Settings\Settings::credits_enabled()) {
$allowed = false; // still disabled at runtime
}
}
return (bool) apply_filters('aksrt_is_allow_points_deduction', $allowed, $context);
}
}
/**
* Content cashier does not accept card_pass payment_method.
* Card-pass for balance/VIP/points is user-center redeem (zibll order types 4/8/9),
* not content pay types (reading/download/gallery/video).
*/
if (!function_exists('aksrt_is_content_card_pass_pay_allowed')) {
function aksrt_is_content_card_pass_pay_allowed($post = null): bool {
return (bool) apply_filters('aksrt_is_content_card_pass_pay_allowed', false, $post);
}
}
// ============================================================
// 状态判断
// ============================================================
if (!function_exists('aksrt_is_paid_post')) {
function aksrt_is_paid_post($post = null): bool {
return aksrt_get_pay_meta($post)['pay_type'] !== AKSRT_PAY_TYPE_NO;
}
}
if (!function_exists('aksrt_is_paid_reading')) {
function aksrt_is_paid_reading($post = null): bool {
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_READING;
}
}
/**
* Detect the canonical paid-fragment shortcode in a post's raw content.
*
* The editor writes this exact shortcode format, so this is the single
* source of truth for whether a reading price has any protected fragment to
* apply to. It deliberately does not inspect rendered content.
*/
if (!function_exists('aksrt_post_has_payshow_shortcode')) {
function aksrt_post_has_payshow_shortcode($post = null): bool {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return false;
}
return preg_match(
'/\[hidecontent\b[^\]]*\btype\s*=\s*(?:"payshow"|\'payshow\'|payshow)[^\]]*\]/i',
(string) $p->post_content
) === 1;
}
}
/**
* A paid fragment is active only for an explicitly priced paid-reading post.
*
* A zero-price reading configuration is intentionally public: authors can
* save content before setting a price without accidentally hiding it.
*/
if (!function_exists('aksrt_is_payshow_purchase_enabled')) {
function aksrt_is_payshow_purchase_enabled($post = null): bool {
if (!aksrt_post_has_payshow_shortcode($post)) {
return false;
}
$meta = aksrt_get_pay_meta($post);
if (($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO) !== AKSRT_PAY_TYPE_READING) {
return false;
}
$pay_modo = ($meta['pay_modo'] ?? '0') === 'points' ? 'points' : '0';
$price = $pay_modo === 'points'
? (float) ($meta['points_price'] ?? 0)
: (float) ($meta['price'] ?? 0);
return $price > 0;
}
}
if (!function_exists('aksrt_is_paid_download')) {
function aksrt_is_paid_download($post = null): bool {
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_DOWNLOAD;
}
}
if (!function_exists('aksrt_is_paid_gallery')) {
function aksrt_is_paid_gallery($post = null): bool {
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_GALLERY;
}
}
if (!function_exists('aksrt_is_paid_video')) {
function aksrt_is_paid_video($post = null): bool {
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_VIDEO;
}
}
if (!function_exists('aksrt_is_vip_only')) {
function aksrt_is_vip_only($post = null): bool {
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_VIP;
}
}
// ============================================================
// 付费状态检查
// ============================================================
if (!function_exists('aksrt_has_paid_access')) {
function aksrt_has_paid_access($post = null, int $user_id = 0): bool {
$p = get_post($post);
if (!($p instanceof WP_Post)) return true;
// 单一真相:付费访问由 \AKSRT\Content\Visibility(基于订单 + VIP)裁决。
if (class_exists('\AKSRT\Content\Visibility')) {
$state = \AKSRT\Content\Visibility::get_post_access_state($p, $user_id);
return !empty($state['has_paid_access']);
}
// 兜底:仅在 service 不可用时使用,避免离线环境直接拒绝。
$meta = aksrt_get_pay_meta($p);
if ($meta['pay_type'] === AKSRT_PAY_TYPE_NO) return true;
if ((float) $meta['price'] <= 0 && $meta['pay_type'] !== AKSRT_PAY_TYPE_VIP) return true;
$uid = $user_id > 0 ? $user_id : get_current_user_id();
if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) return true;
if ($uid > 0 && get_user_meta($uid, '_aksrt_paid_' . $p->ID, true)) return true;
return false;
}
}
if (!function_exists('aksrt_can_access_content')) {
function aksrt_can_access_content($post = null): bool {
$p = get_post($post);
if (!($p instanceof WP_Post)) return true;
if (class_exists('\AKSRT\Content\Visibility')) {
return \AKSRT\Content\Visibility::can_view_paid_content($p);
}
$pay_type = aksrt_get_pay_meta($p)['pay_type'];
if ($pay_type === AKSRT_PAY_TYPE_NO) return true;
// 只有 reading/gallery/video 需要隐藏内容
if (!in_array($pay_type, [AKSRT_PAY_TYPE_READING, AKSRT_PAY_TYPE_GALLERY, AKSRT_PAY_TYPE_VIDEO], true)) {
return true;
}
return aksrt_has_paid_access($p);
}
}
if (!function_exists('aksrt_can_download')) {
/**
* 下载授权门禁 —— 单一合同。
*
* 实际判定交给 \AKSRT\Content\Visibility::can_download_resource()。
* 不再使用 download_credit 做按次扣积分门禁。
*
* @param int|WP_Post|null $post
* @param int $user_id 0 表示当前用户
* @return true|WP_Error
*/
function aksrt_can_download($post = null, int $user_id = 0) {
if (class_exists('\AKSRT\Content\Visibility')) {
return \AKSRT\Content\Visibility::can_download_resource($post, $user_id);
}
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return new WP_Error('aksrt_invalid', __('无效内容。', 'aksrt'));
}
$meta = aksrt_get_pay_meta($p);
if (($meta['pay_type'] ?? '') !== AKSRT_PAY_TYPE_DOWNLOAD) {
return new WP_Error('aksrt_not_downloadable', __('该内容不是可下载资源。', 'aksrt'));
}
$uid = $user_id > 0 ? $user_id : get_current_user_id();
if (!function_exists('aksrt_user_can') || !aksrt_user_can('resource_download', $uid, (int) $p->ID)) {
$login_url = wp_login_url(get_permalink($p));
return new WP_Error('aksrt_need_login', __('请先登录。', 'aksrt'), ['login_url' => $login_url]);
}
if ($uid > 0 && function_exists('aksrt_has_paid_access') && aksrt_has_paid_access($p, $uid)) {
return true;
}
if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) {
return true;
}
// Fallback without Content_Visibility: require purchase when priced.
$cost = function_exists('aksrt_get_download_cost') ? aksrt_get_download_cost($p) : 0;
if ($cost > 0) {
return new WP_Error('aksrt_need_purchase', __('请先购买后再下载。', 'aksrt'), [
'status' => 402,
'cost' => $cost,
'post_id'=> (int) $p->ID,
]);
}
return true;
}
}
if (!function_exists('aksrt_get_download_cost')) {
function aksrt_get_download_cost($post = null): int {
if (function_exists('aksrt_resource_download_cost')) {
return (int) aksrt_resource_download_cost($post);
}
$meta = aksrt_get_pay_meta($post);
return (int) ceil((float) $meta['price']);
}
}
/**
* URL of the Zibll-style single-post download page (page template + ?post=ID).
*/
if (!function_exists('aksrt_get_single_download_page_url')) {
function aksrt_get_single_download_page_url($post = null): string {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return '';
}
$page_url = '';
if (function_exists('aksrt_find_template_page')) {
$page = aksrt_find_template_page('templates/pages/page-download-file.php');
if ($page instanceof WP_Post) {
$page_url = get_permalink($page);
}
}
if ($page_url === '') {
// Fallback: look for any page using the template via slug convention.
$page_url = home_url('/download-file/');
}
return add_query_arg('post', (int) $p->ID, $page_url);
}
}
/**
* Whether independent download page mode is enabled (Zibll down_alone_page).
*/
if (!function_exists('aksrt_is_download_alone_page_enabled')) {
function aksrt_is_download_alone_page_enabled(): bool {
if (!class_exists('\AKSRT\Settings\Settings')) {
return false;
}
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
return !empty($paid['down_alone_page']) || !empty($paid['download']['alone_page']);
}
}
// ============================================================
// 价格显示
// ============================================================
if (!function_exists('aksrt_get_pay_price_label')) {
function aksrt_get_pay_price_label($post = null): array {
$meta = aksrt_get_pay_meta($post);
$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);
$vip_level = is_array($priced) ? (int) ($priced['vip_level'] ?? 0) : 0;
$currency = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt');
$cash_symbol = '¥';
if (class_exists('\AKSRT\Settings\Settings')) {
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
$cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥');
}
// pay_type VIP-only or pay_limit not met → member-exclusive badge.
$member_blocked = ($meta['pay_type'] === AKSRT_PAY_TYPE_VIP)
|| ($pay_limit > 0 && $vip_level < $pay_limit);
if ($member_blocked) {
return [
'text' => __('会员专属', 'aksrt'),
'free' => false,
'amount' => 0,
'base' => is_array($priced) ? (float) ($priced['base'] ?? 0) : 0,
'modo' => $pay_modo,
'pay_limit' => $pay_limit,
];
}
$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' => __('免费', 'aksrt'),
'free' => true,
'amount' => 0,
'base' => $base,
'modo' => $pay_modo,
'pay_limit' => $pay_limit,
];
}
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' => $pay_limit,
];
}
return [
'text' => $cash_symbol . aksrt_format_price($price),
'free' => false,
'amount' => $price,
'base' => $base,
'modo' => '0',
'pay_limit' => $pay_limit,
];
}
}
if (!function_exists('aksrt_format_price')) {
function aksrt_format_price(float $price): string {
if (fmod($price, 1.0) === 0.0) return (string) (int) $price;
return rtrim(rtrim(number_format($price, 2, '.', ''), '0'), '.');
}
}
// ============================================================
// 付费类型徽章
// ============================================================
if (!function_exists('aksrt_get_pay_type_badge')) {
function aksrt_get_pay_type_badge(string $pay_type): string {
$badges = [
AKSRT_PAY_TYPE_READING => ['label' => __('付费阅读', 'aksrt'), 'color' => '#EF4444', 'icon' => 'fa-lock'],
AKSRT_PAY_TYPE_DOWNLOAD => ['label' => __('付费下载', 'aksrt'), 'color' => '#F97316', 'icon' => 'fa-download'],
AKSRT_PAY_TYPE_GALLERY => ['label' => __('付费图库', 'aksrt'), 'color' => '#10B981', 'icon' => 'fa-image'],
AKSRT_PAY_TYPE_VIDEO => ['label' => __('付费视频', 'aksrt'), 'color' => '#8B5CF6', 'icon' => 'fa-play-circle'],
AKSRT_PAY_TYPE_VIP => ['label' => __('VIP 专属', 'aksrt'), 'color' => '#3B82F6', 'icon' => 'fa-certificate'],
];
if (!isset($badges[$pay_type])) return '';
$b = $badges[$pay_type];
return sprintf(
'<span class="aksrt-pay-badge" style="background:%s"><i class="fa %s"></i> %s</span>',
esc_attr($b['color']), esc_attr($b['icon']), esc_html($b['label'])
);
}
}
/**
* Zibll zibpay_get_service() — service bullets under pay/download box.
*
* @return string HTML list of service items.
*/
if (!function_exists('aksrt_get_pay_service_html')) {
function aksrt_get_pay_service_html(string $class = 'resource-download-box__guarantee'): string {
$service = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$order = \AKSRT\Settings\Settings::get_order_settings();
$service = is_array($order['service'] ?? null) ? $order['service'] : [];
}
$items = [];
$text = trim((string) ($service['text'] ?? ''));
$value = trim((string) ($service['value'] ?? ''));
$icon = trim((string) ($service['icon'] ?? 'fa fas fa-check-circle'));
if ($icon === '') {
$icon = 'fa fas fa-check-circle';
}
// Support multi-line pay_service textarea (one bullet per line).
$lines = [];
if ($text !== '') {
$lines = preg_split('/\r\n|\r|\n/', $text) ?: [];
}
if ($value !== '') {
$lines[] = $value;
}
$lines = array_values(array_filter(array_map('trim', $lines)));
if ($lines === []) {
// Defaults aligned with previous hardcoded guarantees.
$lines = [
__('安全检测', 'aksrt'),
__('支持 VIP 权益', 'aksrt'),
__('支持积分结算', 'aksrt'),
];
}
$html = '';
foreach ($lines as $line) {
$html .= '<span class="' . esc_attr($class) . '">';
$html .= '<i class="' . esc_attr($icon) . '" aria-hidden="true"></i> ';
$html .= esc_html($line);
$html .= '</span>';
}
return $html;
}
}
/**
* Demo link button (Zibll zibpay_get_demo_link).
*/
if (!function_exists('aksrt_get_demo_link_html')) {
function aksrt_get_demo_link_html($post = null, string $class = 'resource-download-box__btn btn-outline resource-download-box__btn--demo'): string {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return '';
}
$meta = aksrt_get_pay_meta($p);
if (($meta['pay_type'] ?? '') !== AKSRT_PAY_TYPE_DOWNLOAD) {
return '';
}
$url = trim((string) ($meta['demo_url'] ?? ''));
if ($url === '') {
return '';
}
$text = __('查看演示', 'aksrt');
return sprintf(
'<a target="_blank" rel="nofollow noopener" href="%s" class="%s"><i class="fa fas fa-external-link-alt" aria-hidden="true"></i><span>%s</span></a>',
esc_url($url),
esc_attr($class),
esc_html($text)
);
}
}
/**
* Product attributes row (Zibll zibpay_get_product_attributes).
*/
if (!function_exists('aksrt_get_pay_attributes_html')) {
function aksrt_get_pay_attributes_html($post = null, string $class = 'resource-download-box__attrs'): string {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return '';
}
$meta = aksrt_get_pay_meta($p);
$attrs = is_array($meta['attributes'] ?? null) ? $meta['attributes'] : [];
// Built-in version/size/license/compat as attributes when set.
$builtin = [
'version' => __('版本', 'aksrt'),
'size' => __('大小', 'aksrt'),
'license' => __('授权', 'aksrt'),
'compat' => __('兼容', 'aksrt'),
];
$rows = [];
foreach ($builtin as $key => $label) {
$val = trim((string) ($meta[$key] ?? ''));
if ($val !== '') {
$rows[] = ['name' => $label, 'value' => $val];
}
}
foreach ($attrs as $row) {
if (!is_array($row)) {
continue;
}
$name = trim((string) ($row['name'] ?? $row['key'] ?? $row['label'] ?? ''));
$value = trim((string) ($row['value'] ?? $row['val'] ?? ''));
if ($name === '' || $value === '') {
continue;
}
$rows[] = ['name' => $name, 'value' => $value];
}
if ($rows === []) {
return '';
}
$html = '<ul class="' . esc_attr($class) . '">';
foreach ($rows as $row) {
$html .= '<li class="' . esc_attr($class) . '-item">';
$html .= '<span class="' . esc_attr($class) . '-name">' . esc_html($row['name']) . '</span>';
$html .= '<span class="' . esc_attr($class) . '-value">' . esc_html($row['value']) . '</span>';
$html .= '</li>';
}
$html .= '</ul>';
return $html;
}
}
// ============================================================
// 付费墙 HTML
// ============================================================
if (!function_exists('aksrt_get_paywall_html')) {
function aksrt_get_paywall_html(WP_Post $post, bool $is_fragment = false): string {
$meta = aksrt_get_pay_meta($post);
$pay_type = $meta['pay_type'];
$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);
$vip_level = is_array($priced) ? (int) ($priced['vip_level'] ?? 0) : 0;
$currency = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt');
$cash_symbol = '¥';
if (class_exists('\AKSRT\Settings\Settings')) {
$paid = \AKSRT\Settings\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')],
AKSRT_PAY_TYPE_GALLERY => ['icon' => 'fa-image', 'label' => __('付费图库', 'aksrt'), 'desc' => __('付费后即可查看全部图片', 'aksrt')],
AKSRT_PAY_TYPE_VIDEO => ['icon' => 'fa-play-circle', 'label' => __('付费视频', 'aksrt'), 'desc' => __('付费后即可观看完整视频', 'aksrt')],
];
$type_info = $type_labels[$pay_type] ?? $type_labels[AKSRT_PAY_TYPE_READING];
if ($is_fragment && $pay_type === AKSRT_PAY_TYPE_READING) {
$type_info['desc'] = __('付费后即可查看此片段内容', 'aksrt');
}
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink($post));
$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);
$member_blocked = $pay_limit > 0 && $vip_level < $pay_limit;
$vip_url = function_exists('aksrt_get_user_center_tab_url')
? aksrt_get_user_center_tab_url('credits')
: (function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/'));
ob_start();
?>
<div class="aksrt-paywall<?php echo $is_fragment ? ' aksrt-paywall--fragment' : ''; ?>" data-post-id="<?php echo esc_attr((string) $post->ID); ?>" data-pay-type="<?php echo esc_attr($pay_type); ?>" data-pay-modo="<?php echo esc_attr($pay_modo); ?>">
<div class="aksrt-paywall__card" role="group" aria-label="<?php esc_attr_e('付费内容购买', 'aksrt'); ?>">
<div class="aksrt-paywall__icon">
<i class="fa <?php echo esc_attr($type_info['icon']); ?>" aria-hidden="true"></i>
</div>
<div class="aksrt-paywall__body">
<h3 class="aksrt-paywall__title"><?php echo esc_html($type_info['label']); ?></h3>
<p class="aksrt-paywall__desc"><?php echo esc_html($type_info['desc']); ?></p>
<div class="aksrt-paywall__price">
<?php if ($member_blocked) : ?>
<span class="aksrt-paywall__price-member"><?php esc_html_e('会员专属', 'aksrt'); ?></span>
<?php elseif ($price > 0) : ?>
<span class="aksrt-paywall__price-amount"><?php echo esc_html($price_text); ?></span>
<?php if ($has_original) : ?>
<span class="aksrt-paywall__price-original"><?php echo esc_html(($pay_modo === 'points' ? aksrt_format_price($original_price) . ' ' . $currency : $cash_symbol . aksrt_format_price($original_price))); ?></span>
<?php endif; ?>
<?php else : ?>
<span class="aksrt-paywall__price-free"><?php esc_html_e('免费', 'aksrt'); ?></span>
<?php endif; ?>
</div>
</div>
<div class="aksrt-paywall__actions">
<?php if (!$is_logged_in) : ?>
<?php if ($pay_limit <= 0 && $price > 0 && $pay_modo !== 'points' && function_exists('aksrt_allow_guest_content_pay') && aksrt_allow_guest_content_pay($post)) : ?>
<button type="button" class="but <?php echo $pay_modo === 'points' ? 'jb-yellow' : 'jb-red'; ?> padding-lg cashier-link aksrt-paywall__btn" data-aksrt-pay-content="<?php echo esc_attr((string) $post->ID); ?>">
<i class="fa fas fa-shopping-cart" aria-hidden="true"></i> <?php printf(esc_html__('游客购买(%s)', 'aksrt'), $price_text); ?>
</button>
<a href="<?php echo esc_url($login_url); ?>" class="aksrt-paywall__login-link"><?php esc_html_e('已有账号?登录后购买', 'aksrt'); ?></a>
<?php else : ?>
<a href="<?php echo esc_url($login_url); ?>" class="but jb-red padding-lg aksrt-paywall__btn">
<i class="fa fas fa-sign-in-alt" aria-hidden="true"></i> <?php esc_html_e('登录后购买', 'aksrt'); ?>
</a>
<?php endif; ?>
<?php elseif ($member_blocked) : ?>
<a href="<?php echo esc_url($vip_url); ?>" class="but jb-vip padding-lg aksrt-paywall__btn">
<i class="fa fas fa-certificate" aria-hidden="true"></i> <?php esc_html_e('开通会员后可查看', 'aksrt'); ?>
</a>
<?php elseif ($price > 0) : ?>
<button type="button" class="but <?php echo $pay_modo === 'points' ? 'jb-yellow' : 'jb-red'; ?> padding-lg cashier-link aksrt-paywall__btn" data-aksrt-pay-content="<?php echo esc_attr((string) $post->ID); ?>">
<i class="fa fas fa-shopping-cart" aria-hidden="true"></i> <?php printf(esc_html__('立即购买(%s)', 'aksrt'), $price_text); ?>
</button>
<?php endif; ?>
<p class="aksrt-paywall__hint">
<i class="fa fas fa-shield-alt" aria-hidden="true"></i> <?php esc_html_e('购买后永久有效,支持重复查看', 'aksrt'); ?>
</p>
</div>
</div>
</div>
<?php
return ob_get_clean();
}
}
// ============================================================
// 付费图库轮播(Swiper)
// ============================================================
if (!function_exists('aksrt_render_paid_gallery')) {
function aksrt_render_paid_gallery(WP_Post $post): string {
$meta = aksrt_get_pay_meta($post);
if ($meta['pay_type'] !== AKSRT_PAY_TYPE_GALLERY) return '';
$images = $meta['gallery'] ?? [];
if (empty($images)) return '';
$has_access = aksrt_has_paid_access($post);
$base_uri = get_template_directory_uri();
ob_start();
?>
<div class="aksrt-paid-gallery" data-post-id="<?php echo esc_attr((string) $post->ID); ?>">
<?php if ($has_access) : ?>
<!-- 已付费:Swiper 轮播 -->
<div class="new-swiper aksrt-paid-gallery__swiper" data-loop="true">
<div class="swiper-wrapper">
<?php foreach ($images as $img_url) : ?>
<div class="swiper-slide">
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $img_url, '', aksrt_get_lazy_thumb('lg')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="">
</div>
<?php endforeach; ?>
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<?php else : ?>
<!-- 未付费:仅首图模糊预览,不输出完整图库 URL 列表 -->
<div class="aksrt-paid-gallery__locked">
<div class="aksrt-paid-gallery__preview">
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $images[0], 'aksrt-gallery-locked', aksrt_get_lazy_thumb('lg')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="">
</div>
<div class="aksrt-paid-gallery__overlay">
<i class="fa fas fa-image" aria-hidden="true"></i>
<p><?php
printf(
/* translators: %d: image count */
esc_html__('共 %d 张图片,付费后可查看', 'aksrt'),
count($images)
);
?></p>
<?php $price_label = function_exists('aksrt_get_pay_price_label') ? aksrt_get_pay_price_label($post) : ['text' => '']; ?>
<?php if (!empty($price_label['text'])) : ?>
<span class="aksrt-paid-gallery__price"><?php echo esc_html((string) $price_label['text']); ?></span>
<?php endif; ?>
<button type="button" class="but jb-red padding-lg cashier-link" data-aksrt-pay-content="<?php echo esc_attr((string) $post->ID); ?>">
<i class="fa fas fa-shopping-cart" aria-hidden="true"></i> <?php esc_html_e('立即购买', 'aksrt'); ?>
</button>
</div>
</div>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}
}
// ============================================================
// 付费视频播放器
// ============================================================
if (!function_exists('aksrt_render_paid_video')) {
function aksrt_render_paid_video(WP_Post $post): string {
$meta = aksrt_get_pay_meta($post);
if ($meta['pay_type'] !== AKSRT_PAY_TYPE_VIDEO) return '';
$video_url = $meta['video_url'] ?? '';
if (empty($video_url)) return '';
$has_access = aksrt_has_paid_access($post);
ob_start();
?>
<div class="aksrt-paid-video" data-post-id="<?php echo esc_attr((string) $post->ID); ?>">
<?php if ($has_access) : ?>
<!-- 已付费:视频播放器 -->
<div class="aksrt-paid-video__player">
<?php if (preg_match('/\.(mp4|webm|ogg)$/i', $video_url)) : ?>
<video controls preload="metadata"<?php echo aksrt_is_lazy('lazy_posts_content') ? ' class="lazyload" src="" data-src="' . esc_url($video_url) . '"' : ' src="' . esc_url($video_url) . '"'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> style="width:100%;border-radius:var(--radius-lg);">
<?php esc_html_e('您的浏览器不支持视频播放。', 'aksrt'); ?>
</video>
<?php else : ?>
<div class="aksrt-paid-video__embed">
<iframe<?php echo aksrt_is_lazy('lazy_posts_content') ? ' class="lazyload" src="" data-src="' . esc_url($video_url) . '"' : ' src="' . esc_url($video_url) . '"'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> frameborder="0" allowfullscreen></iframe>
</div>
<?php endif; ?>
</div>
<?php else : ?>
<!-- 未付费:锁定预览(不输出真实视频地址) -->
<div class="aksrt-paid-video__locked">
<div class="aksrt-paid-video__placeholder">
<i class="fa fas fa-play-circle" aria-hidden="true"></i>
<p><?php esc_html_e('付费后即可观看完整视频', 'aksrt'); ?></p>
<?php $price_label = function_exists('aksrt_get_pay_price_label') ? aksrt_get_pay_price_label($post) : ['text' => '']; ?>
<?php if (!empty($price_label['text'])) : ?>
<span class="aksrt-paid-video__price"><?php echo esc_html((string) $price_label['text']); ?></span>
<?php endif; ?>
<p style="margin-top:14px;">
<button type="button" class="but jb-red padding-lg cashier-link" data-aksrt-pay-content="<?php echo esc_attr((string) $post->ID); ?>">
<i class="fa fas fa-shopping-cart" aria-hidden="true"></i> <?php esc_html_e('立即购买', 'aksrt'); ?>
</button>
</p>
</div>
</div>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}
}
// ============================================================
// 内容过滤钩子(注册在 core-hooks.php)
// ============================================================
// 这些函数供 core-hooks.php 中的 the_content 过滤器调用
if (!function_exists('aksrt_paid_content_filter')) {
function aksrt_paid_content_filter(string $content): string {
if (is_admin() || !is_singular() || !in_the_loop() || !is_main_query()) {
return $content;
}
global $post;
if (!($post instanceof WP_Post)) {
return $content;
}
$meta = aksrt_get_pay_meta($post);
$pay_type = (string) ($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO);
// Reading is protected only by the payshow shortcode. Never replace
// the entire article body with a post-level purchase wall.
if ($pay_type === AKSRT_PAY_TYPE_READING) {
return $content;
}
if (aksrt_can_access_content($post)) {
return $content;
}
if (!in_array($pay_type, [AKSRT_PAY_TYPE_GALLERY, AKSRT_PAY_TYPE_VIDEO], true)) {
return $content;
}
// free_preview teaser + paywall. Never render full gallery/video media when unpaid.
$preview = trim((string) ($meta['free_preview'] ?? ''));
$out = '';
if ($preview !== '') {
$out .= '<div class="aksrt-paywall-preview">' . wp_kses_post($preview) . '</div>';
}
$out .= aksrt_get_paywall_html($post);
if ($pay_type === AKSRT_PAY_TYPE_GALLERY) {
$out .= aksrt_render_paid_gallery($post);
} elseif ($pay_type === AKSRT_PAY_TYPE_VIDEO) {
$out .= aksrt_render_paid_video($post);
}
return $out;
}
}
// 已付费的内容增强
if (!function_exists('aksrt_paid_content_enhance')) {
function aksrt_paid_content_enhance(string $content): string {
if (is_admin() || !is_singular() || !in_the_loop() || !is_main_query()) {
return $content;
}
global $post;
if (!($post instanceof WP_Post)) {
return $content;
}
$meta = aksrt_get_pay_meta($post);
$pay_type = (string) ($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO);
if ($pay_type === AKSRT_PAY_TYPE_NO) {
return $content;
}
if (!aksrt_has_paid_access($post)) {
return $content;
}
if ($pay_type === AKSRT_PAY_TYPE_GALLERY && strpos($content, 'aksrt-paid-gallery') === false) {
$gallery_html = aksrt_render_paid_gallery($post);
if ($gallery_html) {
$content .= $gallery_html;
}
}
if ($pay_type === AKSRT_PAY_TYPE_VIDEO && strpos($content, 'aksrt-paid-video') === false) {
$video_html = aksrt_render_paid_video($post);
if ($video_html) {
$content .= $video_html;
}
}
return $content;
}
}
// ============================================================
// 付费图库/视频样式
// ============================================================
add_action('wp_head', function (): void {
if (is_admin() || !is_singular()) return;
global $post;
if (!($post instanceof WP_Post)) return;
$meta = aksrt_get_pay_meta($post);
if (!in_array($meta['pay_type'], [AKSRT_PAY_TYPE_GALLERY, AKSRT_PAY_TYPE_VIDEO], true)) return;
?>
<style>
.aksrt-paid-gallery{margin:24px 0}
.aksrt-paid-gallery__swiper{border-radius:var(--radius-lg);overflow:hidden}
.aksrt-paid-gallery__swiper img{width:100%;height:auto;display:block}
.aksrt-paid-gallery__locked{position:relative;border-radius:var(--radius-lg);overflow:hidden}
.aksrt-paid-gallery__preview img{width:100%;display:block}
.aksrt-paid-gallery__overlay{position:absolute;inset:0;background:rgba(0,0,0,.5);display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;gap:12px}
.aksrt-paid-gallery__overlay i{font-size:48px;opacity:.8}
.aksrt-paid-gallery__overlay p{font-size:16px;margin:0}
.aksrt-paid-gallery__price{font-size:20px;font-weight:700;background:rgba(255,255,255,.2);padding:6px 16px;border-radius:20px}
.aksrt-paid-video{margin:24px 0}
.aksrt-paid-video__embed{position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:var(--radius-lg)}
.aksrt-paid-video__embed iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:none}
.aksrt-paid-video__locked{background:var(--surface-2);border-radius:var(--radius-lg);padding:60px 20px;text-align:center}
.aksrt-paid-video__placeholder i{font-size:64px;color:var(--color-ink-4);margin-bottom:16px}
.aksrt-paid-video__placeholder p{font-size:16px;color:var(--color-ink-3);margin:0 0 12px}
.aksrt-paid-video__price{font-size:20px;font-weight:700;color:var(--color-error)}
</style>
<?php
});