Files
kate522 b447c41f13 refactor(admin): 清理付费 meta 死字段并补齐商品介绍渲染
延续上一轮 pay_count「只写不读」的排查,把其余写而不读的字段一次
性处理完。按字段性质分两类处理,不是一律删。

【删除】纯数据垃圾,无界面入口、无消费方:
- card_keys / purchase_limit / stock:只有 DomainModel 的默认值与
  sanitize,全仓零引用。契约测试原本还断言它们不得出现在 metabox 上。
- 随 card_keys 一并删除 sanitle_card_keys() 助手。
- video_scale_height「固定长宽比例」:参考主题里这是轮播组件 widget
  的参数,不属于付费视频字段,当初误搬到付费 metabox 上且从未被消费。
  连默认值、sanitize、metabox 保存项一起移除,并同步修正契约测试。

【补齐】有界面入口、管理员填了但不显示的四个字段:
pay_title / pay_doc / pay_details / pay_extra_hide 此前只有 metabox
输入框和 sanitize,前端一个都不读。按参考主题 pages/download.php
的结构接到单篇资源下载页:
- pay_title 有值则替代文章标题作商品标题(回退 get_the_title);
- pay_doc 商品简介、pay_details 更多详情渲染在标题区下方;
- pay_extra_hide 额外隐藏内容只在有下载权限时渲染在下载盒之后。
三处 HTML 输出均走 wp_kses_post,与 sanitize 侧的安全 HTML 口径一致。

同时把 pay_extra_hide 接进文章页付费盒
(aksrt_get_paid_box_html):该函数本身只在有付费权限时执行,天然
就是「付费后才可见」的位置,复用已有的 .pay-extra-hide 样式。

验证:php -l 四个 PHP 文件无语法错误;
node --test tests/widget-system/*.test.cjs 449 项全部通过。
2026-10-06 20:17:58 +08:00

1774 lines
77 KiB
PHP
Raw Permalink 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 对齐 legacy)
*
* 核心机制:
* - 任何 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);
}
if (!function_exists('aksrt_asset_price_minor')) {
function aksrt_asset_price_minor($value, string $pay_modo): int {
if ($pay_modo === 'points') {
return is_int($value) ? max(0, $value) : 0;
}
if (!is_string($value)) {
return 0;
}
try {
return \AKSRT\Ledger\AssetAmount::balance_minor($value);
} catch (\InvalidArgumentException $e) {
return 0;
}
}
}
if (!function_exists('aksrt_asset_price_value')) {
function aksrt_asset_price_value(int $minor, string $pay_modo) {
return $pay_modo === 'points'
? max(0, $minor)
: \AKSRT\Ledger\AssetAmount::decimal_from_minor(max(0, $minor));
}
}
/**
* 检查用户是否为 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();
}
/**
* 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 [];
}
// 渠道展示名对齐参考主题 zibpay_get_payment_method_args:
// 微信 / 支付宝 / 余额 / 卡密 / PayPal / Stripe(网关 get_name 仅供
// 订单等内部场景,支付方式 UI 统一用本映射)。
$labels = [
'balance' => __('余额', 'aksrt'),
'alipay' => __('支付宝', 'aksrt'),
'epay_alipay' => __('支付宝', 'aksrt'),
'wechatpay' => __('微信', 'aksrt'),
'epay_wxpay' => __('微信', 'aksrt'),
'paypal' => 'PayPal',
'stripe' => 'Stripe',
];
$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' => $labels[$id] ?? (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 aksrt_sort_payment_methods($methods);
}
}
/**
* 「收款接口显示顺序」槽位 → 网关 id 映射。
*
* 后台 sortable 项的 key 与之一一对应;微信/支付宝槽位同时收纳易支付子通道。
*
* @return array<string, array<int, string>>
*/
if (!function_exists('aksrt_payment_order_slots')) {
function aksrt_payment_order_slots(): array {
return [
'wechat' => ['wechatpay', 'epay_wxpay'],
'alipay' => ['alipay', 'epay_alipay'],
'paypal' => ['paypal'],
'stripe' => ['stripe'],
'balance' => ['balance'],
'card_pass' => ['card_pass'],
];
}
}
/**
* 按后台「收款接口显示顺序」(pay_sdk_order)重排支付方式列表。
*
* 语义对齐参考主题 zibpay_get_payment_methods():按配置槽位顺序把命中的方式
* 依次提到前面,未纳入槽位的方式保持原有相对顺序并留在末尾;未配置顺序时
* 原样返回(即保持注册顺序)。这是排序的唯一实现,所有收银台构建支付方式
* 列表的位置都必须经由此函数。
*
* @param array<int, array{id:string,name:string}> $methods
* @return array<int, array{id:string,name:string}>
*/
if (!function_exists('aksrt_sort_payment_methods')) {
function aksrt_sort_payment_methods(array $methods): array {
if (!$methods) {
return $methods;
}
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
$configured = is_array($settings) ? ($settings['pay_sdk_order'] ?? []) : [];
if (!is_array($configured) || $configured === []) {
return $methods;
}
$slots = aksrt_payment_order_slots();
$remaining = $methods;
$sorted = [];
foreach (array_keys($configured) as $slot) {
$slot = sanitize_key((string) $slot);
if ($slot === '') {
continue;
}
foreach ($slots[$slot] ?? [] as $gateway_id) {
foreach ($remaining as $index => $method) {
if ((string) ($method['id'] ?? '') !== $gateway_id) {
continue;
}
$sorted[] = $method;
unset($remaining[$index]);
break;
}
}
}
foreach ($remaining as $method) {
$sorted[] = $method;
}
return $sorted;
}
}
// ============================================================
// 元数据读取
// ============================================================
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();
}
}
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 = $pay_meta['download_limit_over_price'] ?? ($pay_modo === 'points' ? 0 : '0.00');
return [
'version' => (string) ($pay_meta['version'] ?? ''),
'size' => (string) ($pay_meta['size'] ?? ''),
'price' => (string) ($pay_meta['price'] ?? '0.00'),
'points_price' => (int) ($pay_meta['points_price'] ?? 0),
'pay_modo' => $pay_modo,
'pay_limit' => $pay_limit,
'original_price' => (string) ($pay_meta['original_price'] ?? '0.00'),
'vip1_price' => (string) ($pay_meta['vip1_price'] ?? '0.00'),
'vip2_price' => (string) ($pay_meta['vip2_price'] ?? '0.00'),
'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),
// 展示销量 = 真实订单 + 后台「销量浮动」(pay_count)。
'sales_count' => $show_sales ? aksrt_get_content_sales_count($p, $pay_meta) : 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, [
'status' => '',
'limit' => 20,
'offset' => 0,
]);
foreach ($orders as $order) {
if ((int) ($order['product_id'] ?? 0) !== $post_id || !\AKSRT\Core\DomainModel::is_resource_product_type((string) ($order['product_type'] ?? ''))) {
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;
}
}
/**
* Total sales shown to visitors: real paid orders plus the configured 销量浮动.
*
* pay_count is the only field behind the 后台「销量浮动」输入框(资源 metabox 与
* 快捷编辑都写这个键),此前没有任何读取方用它,导致设了不生效。所有展示销量
* 的地方都必须走这个口径,不要再各自拼 virtual_sales / sales_volume。
*
* @param array<string,mixed> $pay_meta 已取得的付费 meta,传空则按 $post_id 现取。
*/
if (!function_exists('aksrt_get_content_sales_count')) {
function aksrt_get_content_sales_count($post = null, array $pay_meta = []): int {
$post_id = $post instanceof \WP_Post ? (int) $post->ID : (int) $post;
if ($post_id <= 0) {
return 0;
}
if ($pay_meta === []) {
$pay_meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : [];
}
$real = max(0, (int) get_post_meta($post_id, \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true));
$float = max(0, (int) ($pay_meta['pay_count'] ?? 0));
return max(0, $real + $float);
}
}
/**
* Whether guest cash purchase is allowed (legacy 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 (legacy 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 (legacy-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((string) ($order['amount'] ?? '0.00'))
: (string) ($order['amount'] ?? '0.00');
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 (legacy aksrtpay_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.
*
* @param int|WP_Post|null $post
* @param int $user_id
* @return array{amount:int|string,base:int|string,vip_amount:int|string|null,modo:string,currency:string,is_free:bool,pay_limit:int,vip_level:int,original:int|string,vip1:int|string,vip2:int|string}
*/
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 = '¥';
$vip_settings = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
$cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥');
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
}
$vip_levels = is_array($vip_settings['levels'] ?? null) ? $vip_settings['levels'] : [];
if ($pay_modo === 'points') {
$base_minor = max(0, (int) ($meta['points_price'] ?? 0));
$vip1_minor = max(0, (int) ($meta['vip1_points'] ?? 0));
$vip2_minor = max(0, (int) ($meta['vip2_points'] ?? 0));
$currency_label = $currency;
} else {
$parse_cash = static function ($value): int {
if (!is_string($value)) {
return 0;
}
try {
return \AKSRT\Ledger\AssetAmount::balance_minor($value);
} catch (\InvalidArgumentException $e) {
return 0;
}
};
$base_minor = $parse_cash($meta['price'] ?? '0');
$vip1_minor = $parse_cash($meta['vip1_price'] ?? '0');
$vip2_minor = $parse_cash($meta['vip2_price'] ?? '0');
$currency_label = $cash_symbol;
}
$vip_prices = [
1 => $vip1_minor,
2 => $vip2_minor,
];
$vip_minor = null;
for ($level = min(2, $vip_level); $level >= 1; $level--) {
if (empty($vip_levels[$level]['enabled'])) {
continue;
}
$level_price = $vip_prices[$level];
if ($level_price < $base_minor) {
$vip_minor = $level_price;
}
break;
}
$amount_minor = $vip_minor ?? $base_minor;
// Role/VIP free-download exemption (global setting).
if ($amount_minor > 0 && $uid > 0 && function_exists('aksrt_is_download_credit_exempt_user') && aksrt_is_download_credit_exempt_user($uid)) {
$amount_minor = 0;
}
if ($pay_modo === 'points') {
$original = max(0, (int) ($meta['original_price'] ?? 0));
$amount = $amount_minor;
$base = $base_minor;
$vip1 = $vip1_minor;
$vip2 = $vip2_minor;
$vip_amount = $vip_minor;
} else {
$original_minor = $parse_cash($meta['original_price'] ?? '0');
$amount = \AKSRT\Ledger\AssetAmount::decimal_from_minor($amount_minor);
$base = \AKSRT\Ledger\AssetAmount::decimal_from_minor($base_minor);
$vip1 = \AKSRT\Ledger\AssetAmount::decimal_from_minor($vip1_minor);
$vip2 = \AKSRT\Ledger\AssetAmount::decimal_from_minor($vip2_minor);
$vip_amount = $vip_minor === null ? null : \AKSRT\Ledger\AssetAmount::decimal_from_minor($vip_minor);
$original = \AKSRT\Ledger\AssetAmount::decimal_from_minor($original_minor);
}
return [
'amount' => $amount,
'base' => $base,
'vip_amount' => $vip_amount,
'modo' => $pay_modo,
'currency' => $currency_label,
'is_free' => $amount_minor <= 0,
'pay_limit' => $pay_limit,
'vip_level' => $vip_level,
'original' => $original,
'vip1' => $vip1,
'vip2' => $vip2,
];
}
}
/**
* Whether content cashier may accept coupons for this post (legacy 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 aksrtpay_get_post_rebate_discount.
*/
if (!function_exists('aksrt_get_post_rebate_discount')) {
function aksrt_get_post_rebate_discount($post = null, int $user_id = 0): string {
$meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : [];
$fixed = is_string($meta['pay_rebate_discount'] ?? null) ? (string) $meta['pay_rebate_discount'] : '0.00';
$fixed_minor = aksrt_asset_price_minor($fixed, '0');
if ($fixed_minor <= 0) {
return '0.00';
}
if (!class_exists('\AKSRT\Settings\Settings') || !method_exists('\AKSRT\Settings\Settings', 'get_payment_affiliate_settings')) {
return '0.00';
}
$aff = \AKSRT\Settings\Settings::get_payment_affiliate_settings();
if (empty($aff['enabled'])) {
return '0.00';
}
$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.00';
}
// Need a positive ratio for referrer (VIP tier of referrer preferred).
$ratio = (int) \AKSRT\Settings\Settings::affiliate_ratio_for_user($referrer_id);
if ($ratio <= 0) {
return '0.00';
}
return \AKSRT\Ledger\AssetAmount::decimal_from_minor($fixed_minor);
}
}
/**
* Whether cash checkout may use points deduction (legacy points_deduction).
*
* legacy: aksrtpay_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 legacy 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 legacy 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 (legacy 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'
? (int) ($meta['points_price'] ?? 0)
: (string) ($meta['price'] ?? '0.00');
return aksrt_asset_price_minor($price, $pay_modo) > 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;
$pay_modo = ($meta['pay_modo'] ?? '0') === 'points' ? 'points' : '0';
$price = $pay_modo === 'points' ? (int) ($meta['points_price'] ?? 0) : (string) ($meta['price'] ?? '0.00');
if (aksrt_asset_price_minor($price, $pay_modo) <= 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_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);
if (($meta['pay_modo'] ?? '0') !== 'points') {
return 0;
}
return max(0, (int) ($meta['points_price'] ?? 0));
}
}
/**
* URL of the legacy-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);
}
}
// ============================================================
// 价格显示
// ============================================================
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) ? ($priced['base'] ?? ($pay_modo === 'points' ? 0 : '0.00')) : ($pay_modo === 'points' ? 0 : '0.00'),
'modo' => $pay_modo,
'pay_limit' => $pay_limit,
];
}
$price = is_array($priced)
? ($priced['amount'] ?? ($pay_modo === 'points' ? 0 : '0.00'))
: ($pay_modo === 'points' ? (int) ($meta['points_price'] ?? 0) : (string) ($meta['price'] ?? '0.00'));
$base = is_array($priced) ? ($priced['base'] ?? $price) : $price;
$price_minor = aksrt_asset_price_minor($price, $pay_modo);
if ($price_minor <= 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($price): string {
if (is_int($price)) {
return (string) max(0, $price);
}
if (!is_string($price)) {
return '0';
}
try {
$decimal = \AKSRT\Ledger\AssetAmount::balance($price)->decimal();
} catch (\InvalidArgumentException $e) {
return '0';
}
return str_ends_with($decimal, '.00')
? substr($decimal, 0, -3)
: rtrim($decimal, '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'])
);
}
}
/**
* legacy aksrtpay_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'] : [];
}
$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';
}
$lines = [];
if ($text !== '') {
$lines = preg_split('/\r\n|\r|\n/', $text) ?: [];
}
if ($value !== '') {
$lines[] = $value;
}
$lines = array_values(array_filter(array_map('trim', $lines)));
$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 (legacy aksrtpay_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 (legacy aksrtpay_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;
}
}
/**
* Render the configured icon for a paid-content VIP tier.
*/
if (!function_exists('aksrt_get_pay_vip_icon_html')) {
function aksrt_get_pay_vip_icon_html(array $level_settings, string $class = 'mr3 em14'): string {
$icon_url = esc_url((string) ($level_settings['img_icon'] ?? ''));
if ($icon_url === '') {
return '<i class="fa fas fa-certificate ' . esc_attr($class) . '" aria-hidden="true"></i>';
}
return '<img class="pay-vip-level-icon ' . esc_attr($class) . '" src="' . $icon_url . '" alt="" decoding="async">';
}
}
/**
* Render the reference-theme base-price block for a paid article.
*/
if (!function_exists('aksrt_get_show_pay_price_html')) {
function aksrt_get_show_pay_price_html(array $meta, array $priced, string $class = 'c-red'): string {
$modo = (($priced['modo'] ?? '0') === 'points') ? 'points' : '0';
$base = $priced['base'] ?? ($modo === 'points' ? 0 : '0.00');
$pay_limit = (int) ($priced['pay_limit'] ?? ($meta['pay_limit'] ?? 0));
$cash_symbol = '¥';
$points_name = __('积分', 'aksrt');
$vip_settings = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$paid_settings = \AKSRT\Settings\Settings::get_paid_content_settings();
$cash_symbol = (string) ($paid_settings['display']['currency_symbol'] ?? '¥');
$points_name = \AKSRT\Settings\Settings::get_currency_name();
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
}
$vip_levels = is_array($vip_settings['levels'] ?? null) ? $vip_settings['levels'] : [];
if ($pay_limit > 0 && !empty($vip_settings['enabled'])) {
$level_settings = is_array($vip_levels[$pay_limit] ?? null) ? $vip_levels[$pay_limit] : [];
$level_name = (string) ($level_settings['name'] ?? sprintf(__('VIP%d', 'aksrt'), $pay_limit));
$title = $pay_limit === 2
? sprintf(__('仅%s可购买', 'aksrt'), $level_name)
: sprintf(__('%s及以上会员可购买', 'aksrt'), $level_name);
$icon = aksrt_get_pay_vip_icon_html($level_settings);
return '<div class="price-box"><div class="' . esc_attr($class) . ' padding-h10"><b data-toggle="tooltip" title="' . esc_attr($title) . '" class="badg radius jb-vip' . esc_attr((string) $pay_limit) . ' pay-vip-exclusive">' . $icon . esc_html__('会员专属资源', 'aksrt') . '</b></div></div>';
}
if ($modo === 'points') {
return '<div class="price-box"><div class="c-yellow ' . esc_attr($class) . '"><span class="em12 mr3"><i class="fa fas fa-coins" aria-hidden="true"></i></span><b class="em3x">' . esc_html(aksrt_format_price($base)) . '</b><span class="px12 ml3">' . esc_html($points_name) . '</span></div></div>';
}
$mark = '<span class="pay-mark">' . esc_html($cash_symbol) . '</span>';
$base_minor = aksrt_asset_price_minor($base, '0');
$original = $meta['original_price'] ?? '0.00';
$original_minor = aksrt_asset_price_minor($original, '0');
$original_html = '';
if ($original_minor > $base_minor) {
$original_html = '<span class="original-price" title="' . esc_attr(sprintf(__('原价 %s', 'aksrt'), aksrt_format_price($original))) . '">' . $mark . esc_html(aksrt_format_price($original)) . '</span>';
}
$promotion_tag = trim((string) ($meta['promotion_tag'] ?? ''));
if ($promotion_tag !== '' && $original_minor > 0) {
$original_html = '<div class="inline-block ml10 text-left"><badge>' . wp_kses_post($promotion_tag) . '</badge><br>' . $original_html . '</div>';
}
$price = '<div class="' . esc_attr($class) . '"><b class="em3x">' . $mark . esc_html(aksrt_format_price($base)) . '</b>' . $original_html . '</div>';
return '<div class="price-box">' . $price . '</div>';
}
}
/**
* Render all enabled VIP prices for a paid article.
*/
if (!function_exists('aksrt_get_posts_vip_price')) {
function aksrt_get_posts_vip_price(array $meta): string {
if (!class_exists('\AKSRT\Settings\Settings')) {
return '';
}
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
if (empty($vip_settings['enabled'])) {
return '';
}
$levels = is_array($vip_settings['levels'] ?? null) ? $vip_settings['levels'] : [];
$modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0';
$base = $modo === 'points' ? (int) ($meta['points_price'] ?? 0) : (string) ($meta['price'] ?? '0.00');
$base_minor = aksrt_asset_price_minor($base, $modo);
if ($base_minor <= 0) {
return '';
}
$mark = $modo === 'points'
? '<span class="px12"><i class="fa fas fa-coins" aria-hidden="true"></i></span>'
: '<span class="px12">' . esc_html((string) (\AKSRT\Settings\Settings::get_paid_content_settings()['display']['currency_symbol'] ?? '¥')) . '</span>';
$user_id = get_current_user_id();
$user_vip_level = $user_id > 0 && function_exists('aksrt_get_user_vip_level')
? (int) aksrt_get_user_vip_level($user_id)
: 0;
$items = [];
foreach ([1, 2] as $level) {
$level_settings = is_array($levels[$level] ?? null) ? $levels[$level] : [];
if (empty($level_settings['enabled'])) {
continue;
}
$key = $modo === 'points' ? 'vip' . $level . '_points' : 'vip' . $level . '_price';
$value = $meta[$key] ?? ($modo === 'points' ? 0 : '0.00');
$vip_minor = aksrt_asset_price_minor($value, $modo);
if ($vip_minor >= $base_minor) {
continue;
}
$level_name = (string) ($level_settings['name'] ?? sprintf(__('VIP%d', 'aksrt'), $level));
$price = $vip_minor === 0
? esc_html__('免费', 'aksrt')
: $mark . esc_html(aksrt_format_price($value));
$icon = aksrt_get_pay_vip_icon_html($level_settings);
$action_class = $user_id <= 0 ? ' signin-loader' : ($user_vip_level < $level ? ' pay-vip' : '');
$title = sprintf(__('开通%s', 'aksrt'), $level_name);
$title_attr = $action_class !== ''
? ' data-toggle="tooltip" title="' . esc_attr($title) . '"'
: '';
$items[] = '<span class="but vip-price' . $action_class . '" vip-level="' . esc_attr((string) $level) . '" data-vip-level="' . esc_attr((string) $level) . '"' . $title_attr . '>' . $icon . esc_html($level_name) . '<span class="em12 ml3 vip-price-text">' . $price . '</span></span>';
}
$html = implode('', $items);
if (count($items) > 1) {
$html = '<span class="vip-price-buts but-average inline">' . $html . '</span>';
}
return $html;
}
}
// ============================================================
// 付费墙 HTML
// ============================================================
if (!function_exists('aksrt_get_paywall_html')) {
function aksrt_get_paywall_html(WP_Post $post, array $args = []): string {
$is_widget = ($args['layout'] ?? 'article') === 'widget';
$theme = sanitize_html_class((string) ($args['theme'] ?? 'jb-red')) ?: 'jb-red';
$meta = aksrt_get_pay_meta($post);
$pay_type = (string) ($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO);
if ($pay_type === AKSRT_PAY_TYPE_NO) return '';
$priced = aksrt_get_user_pay_price($post);
$modo = (string) ($priced['modo'] ?? '0');
$amount = $priced['amount'] ?? 0;
$amount_minor = aksrt_asset_price_minor($amount, $modo);
$pay_limit = (int) ($priced['pay_limit'] ?? ($meta['pay_limit'] ?? 0));
$vip_level = (int) ($priced['vip_level'] ?? 0);
$pay_vip_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_vip_settings() : [];
$member_blocked = $pay_limit > 0 && !empty($pay_vip_settings['enabled']) && $vip_level < $pay_limit;
$type_labels = [
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'),
];
$type_label = $type_labels[$pay_type] ?? __('付费内容', 'aksrt');
$order_types = [
AKSRT_PAY_TYPE_READING => 1,
AKSRT_PAY_TYPE_DOWNLOAD => 2,
AKSRT_PAY_TYPE_GALLERY => 5,
AKSRT_PAY_TYPE_VIDEO => 6,
AKSRT_PAY_TYPE_VIP => 4,
];
$order_type = (int) ($order_types[$pay_type] ?? 1);
$title = get_the_title($post);
$doc = trim((string) ($args['message'] ?? ''));
if ($doc === '') {
$doc = sprintf(__('此内容为%s,请付费后查看', 'aksrt'), $type_label);
}
$sales = aksrt_get_content_sales_count($post, $meta);
$tag = $is_widget
? '<badge class="pay-tag abs-center">' . esc_html($type_label) . '</badge>'
: '<div class="pay-tag abs-center">' . esc_html($type_label) . '</div>';
$block_class = $is_widget ? ' btn-block' : '';
$button = '';
if ($member_blocked) {
$level_settings = is_array($pay_vip_settings['levels'][$pay_limit] ?? null) ? $pay_vip_settings['levels'][$pay_limit] : [];
$level_name = (string) ($level_settings['name'] ?? sprintf(__('VIP%d', 'aksrt'), $pay_limit));
$vip_action = $vip_level > 0
? sprintf(__('升级%s', 'aksrt'), $level_name)
: __('开通会员', 'aksrt');
$action_class = is_user_logged_in() ? 'pay-vip' : 'signin-loader';
$icon = aksrt_get_pay_vip_icon_html($level_settings);
$button = '<div class="badg c-yellow em09 mb6"><i class="fa fas fa-fw fa-info-circle mr6" aria-hidden="true"></i>' . esc_html(sprintf(__('您暂无购买权限,请先%s', 'aksrt'), $vip_action)) . '</div>';
$button .= '<a href="javascript:;" vip-level="' . esc_attr((string) $pay_limit) . '" data-vip-level="' . esc_attr((string) $pay_limit) . '" class="but btn-block jb-vip' . esc_attr((string) $pay_limit) . ' ' . esc_attr($action_class) . ' padding-lg">' . $icon . esc_html($vip_action) . '</a>';
} elseif (!is_user_logged_in()) {
if ($amount_minor > 0 && $modo === '0' && aksrt_allow_guest_content_pay($post)) {
$button = '<button type="button" class="but jb-red padding-lg cashier-link' . $block_class . '" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fas fa-shopping-cart" aria-hidden="true"></i> ' . esc_html__('立即购买', 'aksrt') . '</button>';
} else {
$button = '<a href="javascript:;" class="but jb-blue signin-loader padding-lg' . $block_class . '"><i class="fa fas fa-sign-in-alt mr10" aria-hidden="true"></i>' . esc_html__('登录购买', 'aksrt') . '</a>';
}
} elseif ($amount_minor > 0) {
$button = '<button type="button" class="but ' . ($modo === 'points' ? 'jb-yellow' : 'jb-red') . ' padding-lg cashier-link' . $block_class . '" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fas fa-shopping-cart" aria-hidden="true"></i> ' . esc_html__('立即购买', 'aksrt') . '</button>';
} else {
$button = '<button type="button" class="but jb-blue padding-lg cashier-link' . $block_class . '" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fas fa-eye" aria-hidden="true"></i> ' . esc_html__('查看内容', 'aksrt') . '</button>';
}
$price = aksrt_get_show_pay_price_html($meta, $priced);
$vip = aksrt_get_posts_vip_price($meta);
$attrs = aksrt_get_pay_attributes_html($post, 'pay-attr');
$sales_label = function_exists('aksrt_format_number') ? aksrt_format_number($sales) : number_format_i18n($sales);
$count = $sales > 0 ? '<badge class="img-badge hot jb-blue px12">' . esc_html(sprintf(__('已售 %s', 'aksrt'), $sales_label)) . '</badge>' : '';
$service = aksrt_get_pay_service_html('muted-2-color');
$widget_service = $service !== '' ? '<div class="px12 muted-2-color">' . $service . '</div>' : '';
$article_service = $service !== '' ? '<div class="px12 muted-2-color mt10 text-right">' . $service . '</div>' : '';
$demo = aksrt_get_demo_link_html($post, 'but jb-yellow padding-lg');
$demo_row = $demo !== '' ? '<div class="mt10">' . $demo . '</div>' : '';
$discount = $modo === '0' ? aksrt_get_post_rebate_discount($post) : '0.00';
$discount_text = aksrt_asset_price_minor($discount, '0') > 0
? sprintf(__('推广优惠 %s', 'aksrt'), (string) ($priced['currency'] ?? '¥') . aksrt_format_price($discount))
: '';
$discount_banner = $discount_text !== '' ? '<div class="text-center padding-6 jb-yellow px12">' . esc_html($discount_text) . '</div>' : '';
if ($is_widget && aksrt_has_paid_access($post)) {
$view_labels = [
AKSRT_PAY_TYPE_DOWNLOAD => '<i class="fa fas fa-download fa-fw" aria-hidden="true"></i>' . esc_html__('获取下载地址', 'aksrt'),
AKSRT_PAY_TYPE_GALLERY => '<i class="fa far fa-file-image fa-fw" aria-hidden="true"></i>' . esc_html__('查看图片', 'aksrt'),
AKSRT_PAY_TYPE_VIDEO => '<i class="fa fas fa-play-circle fa-fw" aria-hidden="true"></i>' . esc_html__('查看视频', 'aksrt'),
];
$view_label = $view_labels[$pay_type] ?? '<i class="fa far fa-dot-circle fa-fw" aria-hidden="true"></i>' . esc_html__('查看内容', 'aksrt');
return '<div class="zib-widget pay-box pay-widget paid-box order-type-' . $order_type . '" style="padding:0">' . $tag . '<div class="relative-h ' . esc_attr($theme) . '" style="background-size:120%"><div class="pay-widget__orb pay-widget__orb--right ' . esc_attr($theme) . '"></div><div class="pay-widget__orb pay-widget__orb--left ' . esc_attr($theme) . '"></div><div class="relative box-body"><div class="text-center mt10 box-body"><p><i class="fa fas fa-shopping-bag fa-3x" aria-hidden="true"></i></p><b class="em14">' . esc_html__('已购买', 'aksrt') . '</b></div></div></div><div class="box-body">' . $widget_service . '<div class="mt10"><a href="#posts-pay" class="but padding-lg btn-block ' . esc_attr($theme) . '">' . $view_label . '</a></div>' . $demo_row . $attrs . '</div>' . $count . '</div>';
}
if ($is_widget) {
return '<div class="zib-widget pay-box pay-widget order-type-' . $order_type . '" style="padding:0">' . $tag . '<div class="relative-h ' . esc_attr($theme) . '" style="background-size:120%"><div class="pay-widget__orb pay-widget__orb--right ' . esc_attr($theme) . '"></div><div class="pay-widget__orb pay-widget__orb--left ' . esc_attr($theme) . '"></div><div class="relative box-body"><div class="text-center mt10">' . $price . '</div></div><div class="relative">' . $vip . '</div></div>' . $discount_banner . '<div class="box-body">' . $widget_service . '<div class="mt10">' . $button . '</div>' . $demo_row . $attrs . '</div>' . $count . '</div>';
}
$thumb = get_the_post_thumbnail($post, 'medium', ['class' => 'fit-cover', 'alt' => '']);
if ($thumb === '') {
$thumb = '<div class="pay-thumb-placeholder"><i class="fa fas fa-lock" aria-hidden="true"></i></div>';
}
$graphic_actions = '';
if ($demo !== '' || $discount_text !== '') {
$graphic_actions = '<div class="abs-center text-center left-bottom">';
$graphic_actions .= $demo !== '' ? '<div>' . $demo . '</div>' : '';
$graphic_actions .= $discount_text !== '' ? '<div class="padding-6 jb-red px12 hidden-xs">' . esc_html($discount_text) . '</div>' : '';
$graphic_actions .= '</div>';
}
$discount_mobile = $discount_text !== '' ? '<div class="visible-xs-block badg c-red px12 mb6">' . esc_html($discount_text) . '</div>' : '';
if ($pay_type === AKSRT_PAY_TYPE_GALLERY) {
$gallery_count = count((array) ($meta['gallery'] ?? []));
$graphic = '<div class="flex0 relative mr20 hide-sm pay-thumb"><div class="graphic"><div class="blur-10 absolute">' . $thumb . '</div><div class="absolute graphic-mask" style="opacity:.3"></div><div class="abs-center text-center">' . esc_html(sprintf(__('共%s张图片', 'aksrt'), $gallery_count)) . '<br>' . esc_html__('请付费后查看', 'aksrt') . '</div>' . $graphic_actions . '</div></div>';
} elseif ($pay_type === AKSRT_PAY_TYPE_VIDEO) {
$graphic = '<div class="flex0 relative mr20 hide-sm pay-thumb"><div class="graphic">' . $thumb . '<div class="absolute graphic-mask" style="opacity:.2"></div><div class="abs-center text-center"><i class="fa far fa-play-circle fa-4x opacity8" aria-hidden="true"></i></div>' . $graphic_actions . '</div></div>';
} else {
$graphic = '<div class="flex0 relative mr20 hide-sm pay-thumb"><div class="graphic">' . $thumb . $graphic_actions . '</div></div>';
}
$demo_mobile = $demo !== '' ? '<div class="mt10 visible-xs-block">' . $demo . '</div>' : '';
return '<div class="zib-widget pay-box order-type-' . $order_type . '" id="posts-pay"><div class="flex pay-flexbox">' . $graphic . '<div class="flex-auto-h flex xx jsb"><dt class="text-ellipsis pay-title">' . esc_html($title) . '</dt><div class="mt6 em09 muted-2-color">' . wp_kses_post($doc) . '</div><div>' . $price . '</div>' . $discount_mobile . ($vip !== '' ? '<div>' . $vip . '</div>' : '') . '<div class="text-right mt10">' . $button . '</div></div></div>' . $article_service . $demo_mobile . $attrs . $tag . $count . '</div>';
}
}
/**
* Resolve the paid order that granted access to a post, when access came from
* an actual purchase rather than a free or VIP entitlement.
*
* @return array<string,mixed>|null
*/
if (!function_exists('aksrt_get_current_content_paid_order')) {
function aksrt_get_current_content_paid_order(WP_Post $post): ?array {
if (is_user_logged_in()) {
return aksrt_get_user_content_paid_order($post->ID);
}
return aksrt_get_guest_content_paid_order($post->ID);
}
}
/**
* Reference-theme download actions used inside the purchased article card.
*/
if (!function_exists('aksrt_get_paid_download_actions_html')) {
function aksrt_get_paid_download_actions_html(array $meta): string {
$downloads = is_array($meta['downloads'] ?? null) ? array_values($meta['downloads']) : [];
if ($downloads === []) {
return '<div class="muted-2-color text-center">' . esc_html__('暂无可下载资源', 'aksrt') . '</div>';
}
$buttons = '';
foreach ($downloads as $index => $download) {
if (!is_array($download)) {
continue;
}
$url = (string) ($download['url'] ?? '');
if ($url === '') {
// 直连模式没有可用的原始地址,空源不渲染。
continue;
}
$label = trim((string) ($download['label'] ?? ''));
$service = function_exists('aksrt_detect_cloud_service')
? aksrt_detect_cloud_service($url)
: ['key' => 'other', 'name' => __('本地下载', 'aksrt')];
if ($label === '') {
$label = (string) ($service['name'] ?? __('本地下载', 'aksrt'));
}
$extract_code = trim((string) ($download['extract_code'] ?? ''));
$service_key = sanitize_html_class((string) ($service['key'] ?? 'other')) ?: 'other';
$button_class = trim((string) ($download['class'] ?? ''));
$button_class = $button_class !== '' ? $button_class : 'b-theme ' . $service_key;
$icon = trim((string) ($download['icon'] ?? ''));
$icon_html = $icon !== '' && function_exists('aksrt_icon_markup') ? aksrt_icon_markup($icon) : '';
if ($icon_html === '') {
$icon_html = '<i class="fa fas fa-download" aria-hidden="true"></i>';
}
$copy_value = trim((string) ($download['copy_val'] ?? ''));
$copy_key = trim((string) ($download['copy_key'] ?? ''));
$copy_label = $extract_code !== '' ? $extract_code : ($copy_key !== '' ? $copy_key : __('更多内容', 'aksrt'));
$buttons .= '<div class="but-download flex ac">';
$buttons .= '<a class="mr10 but ' . esc_attr($button_class) . '" href="' . esc_url($url) . '" target="_blank" rel="nofollow noopener">';
$buttons .= $icon_html . esc_html($label) . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($copy_value !== '') {
$tooltip = $copy_key !== '' ? ' data-toggle="tooltip" title="' . esc_attr(sprintf(__('点击复制%s', 'aksrt'), $copy_key)) . '"' : '';
$buttons .= '<button type="button" class="but"' . $tooltip . ' data-clipboard-tag="' . esc_attr($copy_key !== '' ? $copy_key : __('内容', 'aksrt')) . '" data-clipboard-text="' . esc_attr($copy_value) . '">' . esc_html($copy_label) . '</button>';
} elseif ($extract_code !== '') {
$buttons .= '<span class="badg">' . esc_html($extract_code) . '</span>';
}
$buttons .= '</div>';
}
return $buttons !== ''
? '<div class="flex ac hh">' . $buttons . '</div>'
: '<div class="muted-2-color text-center">' . esc_html__('暂无可下载资源', 'aksrt') . '</div>';
}
}
/**
* Purchased article card matching Zibll's paid-box structure.
*/
if (!function_exists('aksrt_get_paid_box_html')) {
function aksrt_get_paid_box_html(WP_Post $post): string {
if (!aksrt_has_paid_access($post)) {
return '';
}
$meta = aksrt_get_pay_meta($post);
$pay_type = (string) ($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO);
if ($pay_type === AKSRT_PAY_TYPE_NO) {
return '';
}
$order_types = [
AKSRT_PAY_TYPE_READING => 1,
AKSRT_PAY_TYPE_DOWNLOAD => 2,
AKSRT_PAY_TYPE_GALLERY => 5,
AKSRT_PAY_TYPE_VIDEO => 6,
AKSRT_PAY_TYPE_VIP => 4,
];
$type_labels = [
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'),
];
$order_type = (int) ($order_types[$pay_type] ?? 1);
$type_label = (string) ($type_labels[$pay_type] ?? __('付费内容', 'aksrt'));
$title = get_the_title($post);
$sales = aksrt_get_content_sales_count($post, $meta);
$sales_label = function_exists('aksrt_format_number') ? aksrt_format_number($sales) : number_format_i18n($sales);
$count = $sales > 0 ? '<badge class="img-badge hot jb-blue px12">' . esc_html(sprintf(__('已售 %s', 'aksrt'), $sales_label)) . '</badge>' : '';
$order = aksrt_get_current_content_paid_order($post);
$priced = aksrt_get_user_pay_price($post);
$vip_level = max(0, min(2, (int) ($priced['vip_level'] ?? 0)));
$header = '';
$paid_box = '';
if (is_array($order)) {
$order_number = (string) ($order['order_number'] ?? '');
$paid_at = (string) ($order['paid_at'] ?? $order['created_at'] ?? '');
$order_currency = (string) ($order['currency'] ?? '');
$order_amount = $order['amount'] ?? '0.00';
if ($order_currency === 'points') {
$currency_name = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt');
$amount_text = aksrt_format_price($order_amount) . ' ' . $currency_name;
} else {
$cash_symbol = '¥';
if (class_exists('\AKSRT\Settings\Settings')) {
$settings = \AKSRT\Settings\Settings::get_paid_content_settings();
$cash_symbol = (string) ($settings['display']['currency_symbol'] ?? '¥');
}
$amount_text = $cash_symbol . aksrt_format_price($order_amount);
}
$paid_info = '<div class="flex jsb"><span>' . esc_html__('订单号', 'aksrt') . '</span><span>' . esc_html($order_number) . '</span></div>';
if ($paid_at !== '') {
$paid_info .= '<div class="flex jsb"><span>' . esc_html__('支付时间', 'aksrt') . '</span><span>' . esc_html($paid_at) . '</span></div>';
}
$paid_info .= '<div class="flex jsb"><span>' . esc_html__('支付金额', 'aksrt') . '</span><span>' . esc_html($amount_text) . '</span></div>';
$paid_box = '<div class="flex ac jb-green padding-10 em09"><div class="text-center flex-auto"><div class="mb6"><i class="fa fas fa-shopping-bag fa-2x" aria-hidden="true"></i></div><b class="em12">' . esc_html__('已购买', 'aksrt') . '</b></div><div class="em09 paid-info flex-auto">' . $paid_info . '</div></div>';
} elseif ($vip_level > 0 || $pay_type === AKSRT_PAY_TYPE_VIP) {
$level = max(1, $vip_level);
$price_label = aksrt_get_pay_price_label($post);
$price = '<div class="price-box"><div class="text-center"><b class="em3x">' . esc_html((string) ($price_label['text'] ?? '')) . '</b></div></div>';
$paid_box = '<div class="flex jsb ac payvip-icon box-body vipbg-v' . esc_attr((string) $level) . '">' . $price . '<div class="flex0"><b class="em12"><i class="fa fas fa-certificate mr10 em12" aria-hidden="true"></i>' . esc_html(sprintf(__('VIP%d 会员免费', 'aksrt'), $level)) . '</b></div></div>';
} else {
$type_label = str_replace(__('付费', 'aksrt'), __('免费', 'aksrt'), $type_label);
}
if ($pay_type === AKSRT_PAY_TYPE_GALLERY) {
$header = aksrt_render_paid_gallery($post);
} elseif ($pay_type === AKSRT_PAY_TYPE_VIDEO) {
$header = aksrt_render_paid_video($post);
}
$download_box = '';
$root_data = '';
if ($pay_type === AKSRT_PAY_TYPE_DOWNLOAD) {
$root_data = ' data-aksrt-download-box data-post-id="' . esc_attr((string) $post->ID) . '"';
$download_box = '<div class="hidden-box show"><div class="hidden-text"><i class="fa fas fa-download mr6" aria-hidden="true"></i>' . esc_html__('资源下载', 'aksrt') . '</div>' . aksrt_get_paid_download_actions_html($meta) . '</div>';
$demo = aksrt_get_demo_link_html($post, 'but jb-yellow padding-lg');
if ($demo !== '') {
$download_box .= '<div class="mt10">' . $demo . '</div>';
}
$download_box .= aksrt_get_pay_attributes_html($post, 'pay-attr');
}
$service = aksrt_get_pay_service_html('inline-block ml10');
$service = $service !== '' ? '<div class="px12 muted-2-color mt10 text-right">' . $service . '</div>' : '';
$title_style = $count !== '' ? ' style="padding-right:48px"' : '';
$body_class = 'box-body relative' . ($pay_type === AKSRT_PAY_TYPE_VIDEO ? ' dplayer-featured' : '');
// 付费后才展示的额外隐藏内容。整个函数只在有付费权限时才会走到这里,
// 因此无需再判断购买状态;HTML 由管理员在 metabox 里录入,已过 kses。
$pay_extra_hide = trim((string) ($meta['pay_extra_hide'] ?? ''));
$pay_extra_hide = $pay_extra_hide !== '' ? '<div class="pay-extra-hide">' . $pay_extra_hide . '</div>' : '';
return '<div class="pay-box zib-widget paid-box order-type-' . $order_type . '" id="posts-pay"' . $root_data . '>'
. $header
. $paid_box
. '<div class="' . esc_attr($body_class) . '">'
. $count
. '<div' . $title_style . '><span class="badg c-red hollow badg-sm mr6">' . esc_html($type_label) . '</span><b>' . esc_html($title) . '</b></div>'
. $download_box
. $pay_extra_hide
. $service
. '</div></div>';
}
}
// ============================================================
// 付费图库轮播(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 '';
if (!aksrt_has_paid_access($post)) return '';
$count = count($images);
$backdrop = (string) end($images);
ob_start();
?>
<div class="relative-h paid-gallery aksrt-paid-gallery" data-post-id="<?php echo esc_attr((string) $post->ID); ?>">
<div class="absolute blur-10 opacity3"><img<?php echo aksrt_get_lazy_attr('lazy_other', $backdrop, 'fit-cover', aksrt_get_lazy_thumb('lg')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt=""></div>
<div style="margin-top:-20px" class="relative mb6"><span class="badg b-theme badg-sm"><?php echo esc_html(sprintf(__('共%s张图片', 'aksrt'), $count)); ?></span></div>
<div class="new-swiper swiper-container swiper-scroll aksrt-paid-gallery__swiper" data-loop="true">
<div class="swiper-wrapper">
<?php foreach ($images as $index => $img_url) : ?>
<div class="swiper-slide mr10" style="width:150px">
<div class="graphic" style="padding-bottom:100%!important">
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $img_url, 'fit-cover', aksrt_get_lazy_thumb('lg')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="">
<div class="abs-center right-top"><badge class="b-black opacity8 mr6 mt6"><?php echo esc_html(($index + 1) . '/' . $count); ?></badge></div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</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 '';
if (!aksrt_has_paid_access($post)) return '';
ob_start();
?>
<div class="aksrt-paid-video" data-post-id="<?php echo esc_attr((string) $post->ID); ?>">
<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>
</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;
}
// Only the configured teaser remains in the article body. The single
// template owns the one canonical purchase card for gallery/video.
$preview = trim((string) ($meta['free_preview'] ?? ''));
return $preview !== '' ? '<div class="aksrt-paywall-preview">' . wp_kses_post($preview) . '</div>' : '';
}
}
// ============================================================
// 付费图库/视频样式
// ============================================================
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-video{margin: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}
</style>
<?php
});