资源 metabox 和快捷编辑都能设置「销量浮动」(pay_count),但设了完全 不起作用——所有读取方用的是另一个键 virtual_sales(§3.8 电商的虚拟 销量加成),pay_count 只被写、从未被读。 新增统一读取口径 aksrt_get_content_sales_count($post, $pay_meta): 真实订单销量 + pay_count,并把四处各自拼装销量的地方全部接到这个 口径上: - aksrt_get_download_box_meta() 的 sales_count(内容帖) - aksrt_get_resource_meta() 的 sales_count(资源帖) - 付费墙 aksrt_get_paywall_html() 的已售徽标 - 付费盒 aksrt_get_paid_box_html() 的已售徽标 - 后台「所有文章」销售数据列的销量 此前资源帖的 sales_count 走的是「虚拟销量 + 下载量」这条与内容帖 不一致的口径,现统一。 快捷编辑的 pay_count 运算本是整数加减 + max(0,...) 归零,与读取口径 一致,无需改动。 _aksrt_sales_volume 保持不变,它仍是订单流水累加的副本;列表列取 订单表统计与 meta 副本的较大值再加浮动量,避免两条链路数字打架。 新增 sales-volume-float-contract.test.cjs 4 项,锁住「pay_count 必须 被销量读取方消费」这一契约,防止以后又被改成摆设。 验证:node --test tests/widget-system/*.test.cjs 449 项全部通过 (原有 445 项 + 新增 4 项);php -l 三个 PHP 文件无语法错误。
1768 lines
76 KiB
PHP
1768 lines
76 KiB
PHP
<?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' : '');
|
||
|
||
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
|
||
. $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
|
||
});
|