Files
wordpress-theme-aksrt/inc/helpers/aksrt-pay.php
T
kate522 abe2f57417 feat(pay): wire VIP-tier pricing into cost, gates, and download box
Resolve user-facing pay amounts via aksrt_get_user_pay_price (cash/points +
VIP1/2 tiers), surface pay_modo/pay_limit through download meta and REST policy,
and tighten content-visibility / download-box UX for member-only and purchase
gates while improving local stream throttle math.
2026-07-14 11:13:17 +08:00

966 lines
41 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_Domain_Model::PAY_TYPE_NO);
}
if (!defined('AKSRT_PAY_TYPE_READING')) {
define('AKSRT_PAY_TYPE_READING', AKSRT_Domain_Model::PAY_TYPE_READING);
}
if (!defined('AKSRT_PAY_TYPE_DOWNLOAD')) {
define('AKSRT_PAY_TYPE_DOWNLOAD', AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD);
}
if (!defined('AKSRT_PAY_TYPE_GALLERY')) {
define('AKSRT_PAY_TYPE_GALLERY', AKSRT_Domain_Model::PAY_TYPE_GALLERY);
}
if (!defined('AKSRT_PAY_TYPE_VIDEO')) {
define('AKSRT_PAY_TYPE_VIDEO', AKSRT_Domain_Model::PAY_TYPE_VIDEO);
}
if (!defined('AKSRT_PAY_TYPE_VIP')) {
define('AKSRT_PAY_TYPE_VIP', AKSRT_Domain_Model::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_Domain_Model::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'),
];
}
// ============================================================
// 元数据读取
// ============================================================
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_Domain_Model::PAY_META_KEY, true);
if (is_array($meta) && isset($meta['pay_type'])) {
return wp_parse_args(AKSRT_Domain_Model::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_Domain_Model::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_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'] ?? ''),
'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_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'),
];
}
}
/**
* 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),
];
}
}
// ============================================================
// 状态判断
// ============================================================
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;
}
}
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()
* (由 inc/helpers/aksrt-resource.php 的同名 helper 在更早加载时定义)。
* 此处仅作为该 helper 不可用时的兜底,避免出现第二套下载真相。
*
* @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'));
}
$paid_settings = class_exists('AKSRT_Settings')
? AKSRT_Settings::get_paid_content_settings()
: ['download_login' => true, 'download_credit' => true];
$uid = $user_id > 0 ? $user_id : get_current_user_id();
if (!empty($paid_settings['download_login']) && $uid <= 0) {
$login_url = wp_login_url(get_permalink($p));
return new WP_Error('aksrt_need_login', __('请先登录。', 'aksrt'), ['login_url' => $login_url]);
}
if ($uid > 0 && aksrt_has_paid_access($p, $uid)) return true;
if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) return true;
$cost = aksrt_get_download_cost($p);
if ($cost > 0 && !empty($paid_settings['download_credit'])) {
$balance = function_exists('aksrt_user_credits') ? aksrt_user_credits($uid) : 0;
if ($balance < $cost) {
return new WP_Error('aksrt_credits_insufficient', __('积分不足。', 'aksrt'), [
'balance' => $balance,
'cost' => $cost,
]);
}
}
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')) {
return false;
}
$paid = AKSRT_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);
$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 ($meta['pay_type'] === AKSRT_PAY_TYPE_VIP || $pay_limit > 0) {
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' => 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,
];
}
}
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-images'],
AKSRT_PAY_TYPE_VIDEO => ['label' => __('付费视频', 'aksrt'), 'color' => '#8B5CF6', 'icon' => 'fa-play-circle'],
AKSRT_PAY_TYPE_VIP => ['label' => __('VIP 专属', 'aksrt'), 'color' => '#3B82F6', 'icon' => 'fa-crown'],
];
if (!isset($badges[$pay_type])) return '';
$b = $badges[$pay_type];
return sprintf(
'<span class="aksrt-pay-badge" style="background:%s"><i class="fas %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')) {
$order = AKSRT_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'] ?? 'fas fa-check-circle'));
if ($icon === '') {
$icon = '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="fas fa-external-link-alt" aria-hidden="true"></i><span>%s</span></a>',
esc_url($url),
esc_attr($class),
esc_html($text)
);
}
}
// ============================================================
// 付费墙 HTML
// ============================================================
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'];
$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')],
AKSRT_PAY_TYPE_GALLERY => ['icon' => 'fa-images', '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];
$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);
ob_start();
?>
<div class="aksrt-paywall" 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__overlay">
<div class="aksrt-paywall__blur"></div>
<div class="aksrt-paywall__card">
<div class="aksrt-paywall__icon">
<i class="fas <?php echo esc_attr($type_info['icon']); ?>" aria-hidden="true"></i>
</div>
<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 ($pay_limit > 0) : ?>
<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>
<?php if (!$is_logged_in) : ?>
<a href="<?php echo esc_url($login_url); ?>" class="but jb-red padding-lg cashier-link aksrt-paywall__btn">
<i class="fa fa-sign-in" aria-hidden="true"></i> <?php esc_html_e('登录后购买', 'aksrt'); ?>
</a>
<?php elseif ($pay_limit > 0) : ?>
<span class="but jb-vip padding-lg aksrt-paywall__btn is-disabled">
<i class="fas fa-crown" aria-hidden="true"></i> <?php esc_html_e('开通会员后可查看', 'aksrt'); ?>
</span>
<?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 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="fas fa-shield-alt"></i> <?php esc_html_e('购买后永久有效,支持重复查看', 'aksrt'); ?>
</p>
</div>
</div>
</div>
<?php
return ob_get_clean();
}
}
// ============================================================
// 付费墙样式注入
// ============================================================
add_action('wp_head', function (): void {
if (is_admin() || !is_singular()) return;
global $post;
if (!($post instanceof WP_Post)) return;
if (!function_exists('aksrt_can_access_content') || aksrt_can_access_content($post)) return;
?>
<style>
.aksrt-paywall{position:relative;margin:24px 0}
.aksrt-paywall__overlay{position:relative}
.aksrt-paywall__blur{filter:blur(12px);pointer-events:none;user-select:none;max-height:200px;overflow:hidden;opacity:.3}
.aksrt-paywall__card{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface-1,#fff);border:1px solid var(--color-line,#e5e7eb);border-radius:var(--radius-lg,14px);padding:40px;text-align:center;box-shadow:0 12px 40px rgba(0,0,0,.12);min-width:320px;max-width:420px}
.aksrt-paywall__icon{font-size:48px;color:var(--color-primary,#f04494);margin-bottom:16px}
.aksrt-paywall__title{font-size:20px;font-weight:700;margin:0 0 8px;color:var(--color-ink,#15171A)}
.aksrt-paywall__desc{font-size:14px;color:var(--color-ink-3,#525866);margin:0 0 20px}
.aksrt-paywall__price{margin-bottom:20px}
.aksrt-paywall__price-amount{font-size:28px;font-weight:700;color:var(--color-error,#EF4444)}
.aksrt-paywall__price-original{font-size:14px;color:var(--color-ink-4,#8A8F98);text-decoration:line-through;margin-left:8px}
.aksrt-paywall__price-free{font-size:20px;font-weight:600;color:var(--color-success,#10B981)}
.aksrt-paywall__btn{display:inline-flex;align-items:center;gap:8px}
.aksrt-paywall__hint{font-size:12px;color:var(--color-ink-4,#8A8F98);margin:16px 0 0}
.aksrt-gallery-locked{filter:blur(8px);pointer-events:none;user-select:none}
</style>
<?php
});
// ============================================================
// 付费图库轮播(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="swiper aksrt-paid-gallery__swiper">
<div class="swiper-wrapper">
<?php foreach ($images as $img_url) : ?>
<div class="swiper-slide">
<img src="<?php echo esc_url($img_url); ?>" alt="" loading="lazy">
</div>
<?php endforeach; ?>
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<?php else : ?>
<!-- 未付费:模糊预览 + 付费墙 -->
<div class="aksrt-paid-gallery__locked">
<div class="aksrt-paid-gallery__preview">
<img src="<?php echo esc_url($images[0]); ?>" alt="" class="aksrt-gallery-locked">
</div>
<div class="aksrt-paid-gallery__overlay">
<i class="fas fa-images" aria-hidden="true"></i>
<p><?php esc_html_e('付费后即可查看全部图片', 'aksrt'); ?></p>
<?php
$price_label = aksrt_get_pay_price_label($post);
$currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
?>
<span class="aksrt-paid-gallery__price">
<?php echo esc_html($price_label['text']); ?>
</span>
</div>
</div>
<?php endif; ?>
</div>
<?php if ($has_access) : ?>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
new Swiper('.aksrt-paid-gallery__swiper', {
loop: true,
pagination: { el: '.swiper-pagination', clickable: true },
navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' },
lazy: true,
});
});
</script>
<?php endif; ?>
<?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" style="width:100%;border-radius:var(--radius-lg);">
<source src="<?php echo esc_url($video_url); ?>">
<?php esc_html_e('您的浏览器不支持视频播放。', 'aksrt'); ?>
</video>
<?php else : ?>
<div class="aksrt-paid-video__embed">
<iframe src="<?php echo esc_url($video_url); ?>" frameborder="0" allowfullscreen></iframe>
</div>
<?php endif; ?>
</div>
<?php else : ?>
<!-- 未付费:锁定预览 -->
<div class="aksrt-paid-video__locked">
<div class="aksrt-paid-video__placeholder">
<i class="fas fa-play-circle" aria-hidden="true"></i>
<p><?php esc_html_e('付费后即可观看完整视频', 'aksrt'); ?></p>
<?php $price_label = aksrt_get_pay_price_label($post); ?>
<span class="aksrt-paid-video__price"><?php echo esc_html($price_label['text']); ?></span>
</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()) return $content;
global $post;
if (!($post instanceof WP_Post)) return $content;
if (aksrt_can_access_content($post)) return $content;
// 图库类型:在内容末尾追加图库轮播
$meta = aksrt_get_pay_meta($post);
if ($meta['pay_type'] === AKSRT_PAY_TYPE_GALLERY) {
return aksrt_get_paywall_html($post) . aksrt_render_paid_gallery($post);
}
// 视频类型:在内容末尾追加视频播放器
if ($meta['pay_type'] === AKSRT_PAY_TYPE_VIDEO) {
return aksrt_get_paywall_html($post) . aksrt_render_paid_video($post);
}
return aksrt_get_paywall_html($post);
}
}
// 已付费的内容增强
if (!function_exists('aksrt_paid_content_enhance')) {
function aksrt_paid_content_enhance(string $content): string {
if (is_admin() || !is_singular()) return $content;
global $post;
if (!($post instanceof WP_Post)) return $content;
$meta = aksrt_get_pay_meta($post);
if ($meta['pay_type'] === AKSRT_PAY_TYPE_NO) return $content;
if (!aksrt_has_paid_access($post)) return $content;
// 已付费的图库:追加 Swiper 轮播
if ($meta['pay_type'] === AKSRT_PAY_TYPE_GALLERY) {
$gallery_html = aksrt_render_paid_gallery($post);
if ($gallery_html) $content .= $gallery_html;
}
// 已付费的视频:追加播放器
if ($meta['pay_type'] === AKSRT_PAY_TYPE_VIDEO) {
$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
});