feat(admin,forum): download logs, hidecontent, VIP tiers, users commerce
Add aksrt_download_logs workbench, zibll hidecontent alias + topic partial-hide path, VIP1/VIP2 price matrix for topics, and users list commerce snapshot column with deep links.
This commit is contained in:
@@ -28,6 +28,7 @@ Do **not** reintroduce operational workbenches into the SPA once native pages sh
|
||||
├─ 概览 aksrt_pay
|
||||
├─ 订单 aksrt_orders
|
||||
├─ 付费文章 aksrt_pay_products
|
||||
├─ 下载记录 aksrt_download_logs
|
||||
├─ 发货及物流 aksrt_shipping
|
||||
├─ 售后管理 aksrt_after_sale
|
||||
├─ 优惠码 aksrt_coupons
|
||||
|
||||
@@ -99,8 +99,8 @@ Note: zibll「商品管理」`zibpay_product_page` is **paid article sales**, no
|
||||
| 2b Shipping/after-sale/coupons/income/rebate | done |
|
||||
| 3 users.php submenus | done |
|
||||
| 4 SPA strip | done (ops routes redirect to native) |
|
||||
| 5+ behavior parity | done — native ops shell modularized (orders/shipping/after-sale/coupons/charge-cards/income/rebate/withdraw) + plate moderator roles/advanced limits/icon/sort + coupon product_scope create/edit/batch + paid-post sales report (`aksrt_pay_products`) + topic pay/points visibility admin/runtime alignment + plate follow-pay MVP + post list sales column + order type tabs + topic plate re-check |
|
||||
| 5 remaining | download-log admin page; topic VIP1/2 full matrix; hidecontent shortcode partial-hide; users commerce columns |
|
||||
| 5+ behavior parity | done — native ops shell modularized + plate follow-pay MVP + topic pay/points + sales report + post sales column + order type tabs + download logs page + hidecontent alias + topic VIP1/2 matrix + users commerce column + topic partial hide |
|
||||
| 5 remaining | optional: multi-gateway for forum/topics; full zibll download modal UX; topic virtual_sales/coupon fields |
|
||||
|
||||
## Dual-UI rule
|
||||
|
||||
|
||||
@@ -374,6 +374,9 @@ if (is_admin() && class_exists('AKSRT_Admin_Pay_Orders')) {
|
||||
if (is_admin() && class_exists('AKSRT_Admin_Pay_Products')) {
|
||||
AKSRT_Admin_Pay_Products::get_instance()->init();
|
||||
}
|
||||
if (is_admin() && class_exists('AKSRT_Admin_Pay_Download_Logs')) {
|
||||
AKSRT_Admin_Pay_Download_Logs::get_instance()->init();
|
||||
}
|
||||
if (is_admin() && class_exists('AKSRT_Admin_Pay_Withdrawals')) {
|
||||
AKSRT_Admin_Pay_Withdrawals::get_instance()->init();
|
||||
}
|
||||
|
||||
@@ -179,6 +179,7 @@ require_once AKSRT_DIR . '/inc/classes/admin/native/class-aksrt-admin-pay-shippi
|
||||
require_once AKSRT_DIR . '/inc/classes/admin/native/class-aksrt-admin-pay-after-sale.php';
|
||||
require_once AKSRT_DIR . '/inc/classes/admin/native/class-aksrt-admin-pay-orders.php';
|
||||
require_once AKSRT_DIR . '/inc/classes/admin/native/class-aksrt-admin-pay-products.php';
|
||||
require_once AKSRT_DIR . '/inc/classes/admin/native/class-aksrt-admin-pay-download-logs.php';
|
||||
require_once AKSRT_DIR . '/inc/classes/admin/native/class-aksrt-admin-pay-withdrawals.php';
|
||||
require_once AKSRT_DIR . '/inc/classes/admin/native/class-aksrt-admin-pay-income.php';
|
||||
require_once AKSRT_DIR . '/inc/classes/admin/native/class-aksrt-admin-pay-rebate.php';
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
/**
|
||||
* Native WP admin: resource download token/log browser (zibll paydown log parity, best-effort).
|
||||
*
|
||||
* Source: {prefix}aksrt_dl_token (issued tokens; used rows kept ~7 days by cleanup).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Pay_Download_Logs {
|
||||
|
||||
public const CAPABILITY = 'manage_options';
|
||||
public const PAGE_SLUG = 'aksrt_download_logs';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
// Read-only.
|
||||
}
|
||||
|
||||
public function render_page(): void {
|
||||
$this->ensure_cap();
|
||||
echo '<div class="wrap"><h1>' . esc_html__('下载记录', 'aksrt') . '</h1>';
|
||||
|
||||
if (!class_exists('AKSRT_DB_Migrator')) {
|
||||
echo '<div class="notice notice-error"><p>' . esc_html__('数据库服务不可用。', 'aksrt') . '</p></div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$table = AKSRT_DB_Migrator::table('dl_token');
|
||||
if ($table === '') {
|
||||
echo '<div class="notice notice-error"><p>' . esc_html__('下载令牌表未初始化。', 'aksrt') . '</p></div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = isset($_GET['post_id']) ? max(0, (int) $_GET['post_id']) : 0;
|
||||
$user_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0;
|
||||
$only_used = !isset($_GET['used']) || (string) $_GET['used'] !== '0';
|
||||
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
|
||||
$per_page = 30;
|
||||
|
||||
$result = $this->query_logs($table, [
|
||||
'post_id' => $post_id,
|
||||
'user_id' => $user_id,
|
||||
'only_used' => $only_used,
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page_num - 1) * $per_page,
|
||||
]);
|
||||
$items = $result['items'];
|
||||
$total = $result['total'];
|
||||
$pages = max(1, (int) ceil($total / $per_page));
|
||||
|
||||
echo '<p class="description">' . esc_html__('基于下载令牌表(aksrt_dl_token)。已使用记录会保留一段时间后清理;未使用且过期的令牌会被定期删除。', 'aksrt') . '</p>';
|
||||
|
||||
echo '<form method="get" style="margin:12px 0;">';
|
||||
echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '" />';
|
||||
echo '<label>' . esc_html__('文章 ID', 'aksrt') . ' <input type="number" min="0" name="post_id" value="' . esc_attr($post_id > 0 ? (string) $post_id : '') . '" class="small-text" /></label> ';
|
||||
echo '<label>' . esc_html__('用户 ID', 'aksrt') . ' <input type="number" min="0" name="user_id" value="' . esc_attr($user_id > 0 ? (string) $user_id : '') . '" class="small-text" /></label> ';
|
||||
echo '<label><input type="checkbox" name="used" value="1" ' . checked($only_used, true, false) . ' /> ' . esc_html__('仅已使用', 'aksrt') . '</label> ';
|
||||
// When unchecked, pass used=0 explicitly.
|
||||
if (!$only_used) {
|
||||
// no-op; checkbox not checked
|
||||
}
|
||||
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
|
||||
echo ' <a class="button" href="' . esc_url(admin_url('admin.php?page=' . self::PAGE_SLUG)) . '">' . esc_html__('重置', 'aksrt') . '</a>';
|
||||
echo '</form>';
|
||||
|
||||
// Fix: unchecked checkbox means used filter off — re-read without default if form submitted with no used.
|
||||
// Already handled via isset($_GET['used']).
|
||||
|
||||
echo '<table class="wp-list-table widefat fixed striped">';
|
||||
echo '<thead><tr>';
|
||||
echo '<th>' . esc_html__('时间', 'aksrt') . '</th>';
|
||||
echo '<th>' . esc_html__('用户', 'aksrt') . '</th>';
|
||||
echo '<th>' . esc_html__('文章', 'aksrt') . '</th>';
|
||||
echo '<th>' . esc_html__('源序号', 'aksrt') . '</th>';
|
||||
echo '<th>' . esc_html__('积分消耗', 'aksrt') . '</th>';
|
||||
echo '<th>' . esc_html__('使用', 'aksrt') . '</th>';
|
||||
echo '<th>' . esc_html__('过期', 'aksrt') . '</th>';
|
||||
echo '<th>' . esc_html__('IP', 'aksrt') . '</th>';
|
||||
echo '</tr></thead><tbody>';
|
||||
|
||||
if (!$items) {
|
||||
echo '<tr><td colspan="8">' . esc_html__('暂无下载记录。', 'aksrt') . '</td></tr>';
|
||||
}
|
||||
|
||||
foreach ($items as $row) {
|
||||
$uid = (int) ($row['user_id'] ?? 0);
|
||||
$pid = (int) ($row['post_id'] ?? 0);
|
||||
$user = $uid > 0 ? get_userdata($uid) : null;
|
||||
$title = $pid > 0 ? get_the_title($pid) : '';
|
||||
if ($title === '') {
|
||||
$title = $pid > 0 ? sprintf(__('(已删除 #%d)', 'aksrt'), $pid) : '—';
|
||||
}
|
||||
$edit = $pid > 0 ? get_edit_post_link($pid, 'raw') : '';
|
||||
$used = (int) ($row['used_count'] ?? 0);
|
||||
$max = (int) ($row['max_uses'] ?? 1);
|
||||
|
||||
echo '<tr>';
|
||||
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
|
||||
echo '<td>';
|
||||
if ($user) {
|
||||
echo esc_html($user->display_name . ' (#' . $uid . ')');
|
||||
echo '<br><a href="' . esc_url(admin_url('admin.php?page=' . self::PAGE_SLUG . '&user_id=' . $uid)) . '">' . esc_html__('仅此用户', 'aksrt') . '</a>';
|
||||
} else {
|
||||
echo esc_html((string) $uid);
|
||||
}
|
||||
echo '</td>';
|
||||
echo '<td>';
|
||||
if ($edit) {
|
||||
echo '<a href="' . esc_url($edit) . '">' . esc_html($title) . '</a>';
|
||||
} else {
|
||||
echo esc_html($title);
|
||||
}
|
||||
if ($pid > 0) {
|
||||
echo '<br><a href="' . esc_url(admin_url('admin.php?page=' . self::PAGE_SLUG . '&post_id=' . $pid)) . '">' . esc_html__('仅此文章', 'aksrt') . '</a>';
|
||||
}
|
||||
echo '</td>';
|
||||
echo '<td>' . esc_html((string) (int) ($row['source_index'] ?? 0)) . '</td>';
|
||||
echo '<td>' . esc_html((string) (int) ($row['cost'] ?? 0)) . '</td>';
|
||||
echo '<td>' . esc_html($used . ' / ' . $max) . ($used > 0 ? ' <span class="dashicons dashicons-yes" style="color:green;"></span>' : '') . '</td>';
|
||||
echo '<td>' . esc_html((string) ($row['expires_at'] ?? '')) . '</td>';
|
||||
echo '<td><code>' . esc_html((string) ($row['ip_bind'] ?? '')) . '</code></td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</tbody></table>';
|
||||
|
||||
if ($pages > 1) {
|
||||
echo '<div class="tablenav"><div class="tablenav-pages">';
|
||||
echo paginate_links([
|
||||
'base' => add_query_arg('paged', '%#%'),
|
||||
'format' => '',
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
'total' => $pages,
|
||||
'current' => $page_num,
|
||||
]);
|
||||
echo '</div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{post_id?:int,user_id?:int,only_used?:bool,limit?:int,offset?:int} $args
|
||||
* @return array{items: array<int,array<string,mixed>>, total:int}
|
||||
*/
|
||||
private function query_logs(string $table, array $args): array {
|
||||
global $wpdb;
|
||||
|
||||
$where = ['1=1'];
|
||||
$params = [];
|
||||
|
||||
$post_id = (int) ($args['post_id'] ?? 0);
|
||||
if ($post_id > 0) {
|
||||
$where[] = 'post_id = %d';
|
||||
$params[] = $post_id;
|
||||
}
|
||||
$user_id = (int) ($args['user_id'] ?? 0);
|
||||
if ($user_id > 0) {
|
||||
$where[] = 'user_id = %d';
|
||||
$params[] = $user_id;
|
||||
}
|
||||
if (!empty($args['only_used'])) {
|
||||
$where[] = 'used_count > 0';
|
||||
}
|
||||
|
||||
$where_sql = implode(' AND ', $where);
|
||||
$limit = max(1, min(100, (int) ($args['limit'] ?? 30)));
|
||||
$offset = max(0, (int) ($args['offset'] ?? 0));
|
||||
|
||||
$count_sql = "SELECT COUNT(*) FROM {$table} WHERE {$where_sql}";
|
||||
$total = $params
|
||||
? (int) $wpdb->get_var($wpdb->prepare($count_sql, $params))
|
||||
: (int) $wpdb->get_var($count_sql);
|
||||
|
||||
$sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d";
|
||||
$list_params = $params;
|
||||
$list_params[] = $limit;
|
||||
$list_params[] = $offset;
|
||||
$rows = $wpdb->get_results($wpdb->prepare($sql, $list_params), ARRAY_A);
|
||||
|
||||
return [
|
||||
'items' => is_array($rows) ? $rows : [],
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
private function ensure_cap(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,17 @@ class AKSRT_Admin_Pay_Menus {
|
||||
wp_die(esc_html__('付费文章模块未初始化。', 'aksrt'));
|
||||
},
|
||||
],
|
||||
'download_logs' => [
|
||||
'slug' => 'aksrt_download_logs',
|
||||
'title' => __('下载记录', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT_Admin_Pay_Download_Logs')) {
|
||||
AKSRT_Admin_Pay_Download_Logs::get_instance()->render_page();
|
||||
return;
|
||||
}
|
||||
wp_die(esc_html__('下载记录模块未初始化。', 'aksrt'));
|
||||
},
|
||||
],
|
||||
'shipping' => [
|
||||
'slug' => 'aksrt_shipping',
|
||||
'title' => __('发货及物流', 'aksrt'),
|
||||
|
||||
@@ -141,6 +141,10 @@ class AKSRT_Admin_Pay_Products {
|
||||
echo '<a class="button button-small" href="' . esc_url($edit_url) . '">' . esc_html__('编辑', 'aksrt') . '</a>';
|
||||
}
|
||||
echo '<a class="button button-small" href="' . esc_url($orders_url) . '">' . esc_html__('订单', 'aksrt') . '</a>';
|
||||
if ($pt === AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD) {
|
||||
$dl_url = $this->page_url('aksrt_download_logs', ['post_id' => $post_id]);
|
||||
echo '<a class="button button-small" href="' . esc_url($dl_url) . '">' . esc_html__('下载记录', 'aksrt') . '</a>';
|
||||
}
|
||||
$permalink = get_permalink($post_id);
|
||||
if ($permalink) {
|
||||
echo '<a class="button button-small" target="_blank" rel="noopener noreferrer" href="' . esc_url($permalink) . '">' . esc_html__('查看', 'aksrt') . '</a>';
|
||||
|
||||
@@ -532,6 +532,10 @@ class AKSRT_Post_Meta_Box {
|
||||
echo '<br><span class="description">' . esc_html__('暂无订单', 'aksrt') . '</span>';
|
||||
}
|
||||
echo '<br><a href="' . esc_url($products_url) . '">' . esc_html__('付费文章', 'aksrt') . '</a>';
|
||||
if ($pay_type === AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD) {
|
||||
$dl_url = admin_url('admin.php?page=aksrt_download_logs&post_id=' . $post_id);
|
||||
echo ' · <a href="' . esc_url($dl_url) . '">' . esc_html__('下载记录', 'aksrt') . '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
|
||||
@@ -44,10 +44,14 @@ class AKSRT_User_Credits_Admin {
|
||||
public function add_balance_column(array $columns): array {
|
||||
$currency_name = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
$columns['aksrt_credits_balance'] = sprintf(__('%s余额', 'aksrt'), $currency_name);
|
||||
$columns['aksrt_user_commerce'] = __('资产/分成', 'aksrt');
|
||||
return $columns;
|
||||
}
|
||||
|
||||
public function render_balance_column(string $output, string $column_name, int $user_id): string {
|
||||
if ($column_name === 'aksrt_user_commerce') {
|
||||
return $this->render_commerce_column($user_id);
|
||||
}
|
||||
if ($column_name !== 'aksrt_credits_balance') {
|
||||
return $output;
|
||||
}
|
||||
@@ -59,6 +63,55 @@ class AKSRT_User_Credits_Admin {
|
||||
return '<strong>' . esc_html((string) $balance) . '</strong>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact commerce snapshot: VIP, revenue, rebate, download logs link.
|
||||
*/
|
||||
private function render_commerce_column(int $user_id): string {
|
||||
$bits = [];
|
||||
if (function_exists('aksrt_get_user_vip_level')) {
|
||||
$lv = (int) aksrt_get_user_vip_level($user_id);
|
||||
if ($lv > 0) {
|
||||
$bits[] = '<span class="aksrt-vip-badge">VIP' . esc_html((string) $lv) . '</span>';
|
||||
}
|
||||
}
|
||||
if (class_exists('AKSRT_Revenue')) {
|
||||
$stats = AKSRT_Revenue::get_instance()->get_stats($user_id);
|
||||
$pending = (float) ($stats['pending'] ?? 0);
|
||||
$total = (float) ($stats['total'] ?? 0);
|
||||
if ($total > 0 || $pending > 0) {
|
||||
$bits[] = sprintf(
|
||||
/* translators: 1 total 2 pending */
|
||||
esc_html__('分成 ¥%1$s(待结 ¥%2$s)', 'aksrt'),
|
||||
number_format($total, 2),
|
||||
number_format($pending, 2)
|
||||
);
|
||||
$bits[] = '<a href="' . esc_url(admin_url('admin.php?page=aksrt_income')) . '">' . esc_html__('明细', 'aksrt') . '</a>';
|
||||
}
|
||||
}
|
||||
if (class_exists('AKSRT_Rebate')) {
|
||||
$list = AKSRT_Rebate::get_instance()->get_list(['user_id' => $user_id, 'limit' => 1]);
|
||||
$total_r = (int) ($list['total'] ?? 0);
|
||||
if ($total_r > 0) {
|
||||
$sum = 0.0;
|
||||
// Lightweight sum via stats if available; else show count.
|
||||
if (method_exists(AKSRT_Rebate::get_instance(), 'get_stats')) {
|
||||
// global stats only — show link with count
|
||||
$bits[] = sprintf(esc_html__('返佣 %d 笔', 'aksrt'), $total_r);
|
||||
} else {
|
||||
$bits[] = sprintf(esc_html__('返佣 %d 笔', 'aksrt'), $total_r);
|
||||
}
|
||||
$bits[] = '<a href="' . esc_url(admin_url('admin.php?page=aksrt_rebate')) . '">' . esc_html__('返佣', 'aksrt') . '</a>';
|
||||
}
|
||||
}
|
||||
$bits[] = '<a href="' . esc_url(admin_url('admin.php?page=aksrt_download_logs&user_id=' . $user_id)) . '">' . esc_html__('下载记录', 'aksrt') . '</a>';
|
||||
$bits[] = '<a href="' . esc_url(admin_url('admin.php?page=aksrt_orders&s=' . $user_id)) . '">' . esc_html__('订单', 'aksrt') . '</a>';
|
||||
|
||||
if (!$bits) {
|
||||
return '—';
|
||||
}
|
||||
return '<div style="font-size:12px;line-height:1.5;">' . implode('<br>', $bits) . '</div>';
|
||||
}
|
||||
|
||||
public function make_balance_sortable(array $columns): array {
|
||||
$columns['aksrt_credits_balance'] = 'aksrt_credits_balance';
|
||||
return $columns;
|
||||
@@ -91,6 +144,11 @@ class AKSRT_User_Credits_Admin {
|
||||
esc_html(sprintf(__('调整%s', 'aksrt'), $currency_name))
|
||||
);
|
||||
}
|
||||
$actions['aksrt_user_dl_logs'] = sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url(admin_url('admin.php?page=aksrt_download_logs&user_id=' . (int) $user->ID)),
|
||||
esc_html__('下载记录', 'aksrt')
|
||||
);
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
@@ -83,6 +83,19 @@ class AKSRT_Topic_Meta_Box {
|
||||
$pay_vip_price = get_post_meta($post->ID, '_aksrt_pay_vip_price', true);
|
||||
$vip_set = ($pay_vip_price !== '' && $pay_vip_price !== null && $pay_vip_price !== false);
|
||||
$vip_price_val = $vip_set ? (float) $pay_vip_price : '';
|
||||
$pay_vip1_price = get_post_meta($post->ID, '_aksrt_pay_vip1_price', true);
|
||||
$pay_vip2_price = get_post_meta($post->ID, '_aksrt_pay_vip2_price', true);
|
||||
$vip1_set = ($pay_vip1_price !== '' && $pay_vip1_price !== null && $pay_vip1_price !== false);
|
||||
$vip2_set = ($pay_vip2_price !== '' && $pay_vip2_price !== null && $pay_vip2_price !== false);
|
||||
// Migrate single VIP price into vip1 display if vip1 empty.
|
||||
if (!$vip1_set && $vip_set) {
|
||||
$vip1_set = true;
|
||||
$pay_vip1_price = $pay_vip_price;
|
||||
}
|
||||
$credits_vip1 = get_post_meta($post->ID, '_aksrt_credits_vip1', true);
|
||||
$credits_vip2 = get_post_meta($post->ID, '_aksrt_credits_vip2', true);
|
||||
$credits_vip1_set = ($credits_vip1 !== '' && $credits_vip1 !== null && $credits_vip1 !== false);
|
||||
$credits_vip2_set = ($credits_vip2 !== '' && $credits_vip2 !== null && $credits_vip2 !== false);
|
||||
$pay_hide_part = (int) get_post_meta($post->ID, '_aksrt_pay_hide_part', true) === 1;
|
||||
$view_roles = get_post_meta($post->ID, '_aksrt_visibility_roles', true);
|
||||
if (!is_array($view_roles)) {
|
||||
@@ -172,21 +185,33 @@ class AKSRT_Topic_Meta_Box {
|
||||
<input type="number" step="1" min="0" class="widefat" name="aksrt_credits_cost" id="aksrt_credits_cost" value="<?php echo esc_attr((string) $credits_cost); ?>">
|
||||
<span class="description"><?php esc_html_e('可见性为「支付积分后可见」时使用。', 'aksrt'); ?></span>
|
||||
</p>
|
||||
<p class="aksrt-topic-meta-credits-fields">
|
||||
<label for="aksrt_credits_vip1"><strong><?php esc_html_e('VIP1 积分价', 'aksrt'); ?></strong></label>
|
||||
<input type="number" step="1" min="0" class="widefat" name="aksrt_credits_vip1" id="aksrt_credits_vip1" value="<?php echo esc_attr($credits_vip1_set ? (string) (int) $credits_vip1 : ''); ?>" placeholder="<?php esc_attr_e('留空=不设', 'aksrt'); ?>">
|
||||
</p>
|
||||
<p class="aksrt-topic-meta-credits-fields">
|
||||
<label for="aksrt_credits_vip2"><strong><?php esc_html_e('VIP2 积分价', 'aksrt'); ?></strong></label>
|
||||
<input type="number" step="1" min="0" class="widefat" name="aksrt_credits_vip2" id="aksrt_credits_vip2" value="<?php echo esc_attr($credits_vip2_set ? (string) (int) $credits_vip2 : ''); ?>" placeholder="<?php esc_attr_e('留空=不设;0=免费', 'aksrt'); ?>">
|
||||
</p>
|
||||
<p class="aksrt-topic-meta-pay-fields">
|
||||
<label for="aksrt_pay_price"><strong><?php esc_html_e('付费价格(元)', 'aksrt'); ?></strong></label>
|
||||
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_pay_price" id="aksrt_pay_price" value="<?php echo esc_attr((string) $pay_price); ?>">
|
||||
</p>
|
||||
<p class="aksrt-topic-meta-pay-fields">
|
||||
<label for="aksrt_pay_vip_price"><strong><?php esc_html_e('VIP 价格(元)', 'aksrt'); ?></strong></label>
|
||||
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_pay_vip_price" id="aksrt_pay_vip_price" value="<?php echo esc_attr($vip_set ? (string) $vip_price_val : ''); ?>" placeholder="<?php esc_attr_e('留空=不设 VIP 价', 'aksrt'); ?>">
|
||||
<span class="description"><?php esc_html_e('填 0 表示 VIP 免费;留空则 VIP 与普通同价。', 'aksrt'); ?></span>
|
||||
<label for="aksrt_pay_vip1_price"><strong><?php esc_html_e('VIP1 价格(元)', 'aksrt'); ?></strong></label>
|
||||
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_pay_vip1_price" id="aksrt_pay_vip1_price" value="<?php echo esc_attr($vip1_set ? (string) (float) $pay_vip1_price : ''); ?>" placeholder="<?php esc_attr_e('留空=不设 VIP1 价', 'aksrt'); ?>">
|
||||
</p>
|
||||
<p class="aksrt-topic-meta-pay-fields">
|
||||
<label for="aksrt_pay_vip2_price"><strong><?php esc_html_e('VIP2 价格(元)', 'aksrt'); ?></strong></label>
|
||||
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_pay_vip2_price" id="aksrt_pay_vip2_price" value="<?php echo esc_attr($vip2_set ? (string) (float) $pay_vip2_price : ''); ?>" placeholder="<?php esc_attr_e('留空=不设;0=VIP2 免费', 'aksrt'); ?>">
|
||||
<span class="description"><?php esc_html_e('兼容旧「VIP 价格」字段:未填 VIP1 时仍可读 _aksrt_pay_vip_price。', 'aksrt'); ?></span>
|
||||
</p>
|
||||
<p class="aksrt-topic-meta-pay-hide-fields">
|
||||
<label>
|
||||
<input type="checkbox" name="aksrt_pay_hide_part" value="1" <?php checked($pay_hide_part); ?>>
|
||||
<?php esc_html_e('仅隐藏部分内容', 'aksrt'); ?>
|
||||
</label>
|
||||
<span class="description" style="display:block;"><?php esc_html_e('开启后仍以全文门控为默认;部分隐藏依赖正文隐藏块。未解锁用户正文不会下发。', 'aksrt'); ?></span>
|
||||
<span class="description" style="display:block;"><?php esc_html_e('开启后正文仍输出,付费块用 [payshow] 或 [hidecontent type="payshow"] 包裹。未开启则全文门控。', 'aksrt'); ?></span>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
@@ -281,9 +306,22 @@ class AKSRT_Topic_Meta_Box {
|
||||
}
|
||||
update_post_meta($post_id, '_aksrt_credits_cost', $cost);
|
||||
update_post_meta($post_id, '_aksrt_pay_credits', $cost);
|
||||
|
||||
if (isset($_POST['aksrt_credits_vip1']) && (string) wp_unslash($_POST['aksrt_credits_vip1']) !== '') {
|
||||
update_post_meta($post_id, '_aksrt_credits_vip1', max(0, (int) wp_unslash($_POST['aksrt_credits_vip1'])));
|
||||
} else {
|
||||
delete_post_meta($post_id, '_aksrt_credits_vip1');
|
||||
}
|
||||
if (isset($_POST['aksrt_credits_vip2']) && (string) wp_unslash($_POST['aksrt_credits_vip2']) !== '') {
|
||||
update_post_meta($post_id, '_aksrt_credits_vip2', max(0, (int) wp_unslash($_POST['aksrt_credits_vip2'])));
|
||||
} else {
|
||||
delete_post_meta($post_id, '_aksrt_credits_vip2');
|
||||
}
|
||||
} else {
|
||||
delete_post_meta($post_id, '_aksrt_credits_cost');
|
||||
delete_post_meta($post_id, '_aksrt_pay_credits');
|
||||
delete_post_meta($post_id, '_aksrt_credits_vip1');
|
||||
delete_post_meta($post_id, '_aksrt_credits_vip2');
|
||||
}
|
||||
|
||||
// Cash pay.
|
||||
@@ -291,15 +329,34 @@ class AKSRT_Topic_Meta_Box {
|
||||
$price = isset($_POST['aksrt_pay_price']) ? max(0, (float) wp_unslash($_POST['aksrt_pay_price'])) : 0;
|
||||
update_post_meta($post_id, '_aksrt_pay_price', $price);
|
||||
|
||||
if (isset($_POST['aksrt_pay_vip_price']) && (string) wp_unslash($_POST['aksrt_pay_vip_price']) !== '') {
|
||||
$vip1_written = false;
|
||||
if (isset($_POST['aksrt_pay_vip1_price']) && (string) wp_unslash($_POST['aksrt_pay_vip1_price']) !== '') {
|
||||
$vip1 = max(0, (float) wp_unslash($_POST['aksrt_pay_vip1_price']));
|
||||
update_post_meta($post_id, '_aksrt_pay_vip1_price', $vip1);
|
||||
// Keep legacy single VIP key in sync with VIP1 for older readers.
|
||||
update_post_meta($post_id, '_aksrt_pay_vip_price', $vip1);
|
||||
$vip1_written = true;
|
||||
} else {
|
||||
delete_post_meta($post_id, '_aksrt_pay_vip1_price');
|
||||
}
|
||||
if (isset($_POST['aksrt_pay_vip2_price']) && (string) wp_unslash($_POST['aksrt_pay_vip2_price']) !== '') {
|
||||
update_post_meta($post_id, '_aksrt_pay_vip2_price', max(0, (float) wp_unslash($_POST['aksrt_pay_vip2_price'])));
|
||||
} else {
|
||||
delete_post_meta($post_id, '_aksrt_pay_vip2_price');
|
||||
}
|
||||
// Legacy field if posted without vip1.
|
||||
if (!$vip1_written && isset($_POST['aksrt_pay_vip_price']) && (string) wp_unslash($_POST['aksrt_pay_vip_price']) !== '') {
|
||||
$vip = max(0, (float) wp_unslash($_POST['aksrt_pay_vip_price']));
|
||||
update_post_meta($post_id, '_aksrt_pay_vip_price', $vip);
|
||||
} else {
|
||||
update_post_meta($post_id, '_aksrt_pay_vip1_price', $vip);
|
||||
} elseif (!$vip1_written) {
|
||||
delete_post_meta($post_id, '_aksrt_pay_vip_price');
|
||||
}
|
||||
} else {
|
||||
delete_post_meta($post_id, '_aksrt_pay_price');
|
||||
delete_post_meta($post_id, '_aksrt_pay_vip_price');
|
||||
delete_post_meta($post_id, '_aksrt_pay_vip1_price');
|
||||
delete_post_meta($post_id, '_aksrt_pay_vip2_price');
|
||||
}
|
||||
|
||||
// Partial hide only meaningful for pay/credits; otherwise clear.
|
||||
|
||||
@@ -68,11 +68,18 @@ trait AKSRT_Forum_Topic_Service_Part_3 {
|
||||
$pay_hide_part = (int) get_post_meta($topic_id, '_aksrt_pay_hide_part', true) === 1;
|
||||
$content = (string) $post->post_content;
|
||||
if (!$can_view) {
|
||||
// Prevent content leak via payload/API when template forgets can_view check.
|
||||
// Partial-hide flag only applies after unlock; denied users get empty body.
|
||||
$content = '';
|
||||
if (in_array($visibility, [AKSRT_Forum::VISIBILITY_CREDITS, AKSRT_Forum::VISIBILITY_PAY, AKSRT_Forum::VISIBILITY_LOGIN, AKSRT_Forum::VISIBILITY_COMMENT, AKSRT_Forum::VISIBILITY_ROLE, AKSRT_Forum::VISIBILITY_MODERATOR], true)) {
|
||||
$excerpt = '';
|
||||
// Partial hide: keep body for shortcode processing; strip locked blocks at render.
|
||||
// Full hide (default): blank content to avoid API/template leaks.
|
||||
if ($pay_hide_part && in_array($visibility, [AKSRT_Forum::VISIBILITY_CREDITS, AKSRT_Forum::VISIBILITY_PAY], true)) {
|
||||
// leave content; payshow/hidecontent shortcodes gate paid parts
|
||||
$excerpt = wp_trim_words(wp_strip_all_tags(
|
||||
preg_replace('/\[(payshow|hidecontent)[^\]]*\].*?\[\/\1\]/is', '', $content) ?? $content
|
||||
), 40);
|
||||
} else {
|
||||
$content = '';
|
||||
if (in_array($visibility, [AKSRT_Forum::VISIBILITY_CREDITS, AKSRT_Forum::VISIBILITY_PAY, AKSRT_Forum::VISIBILITY_LOGIN, AKSRT_Forum::VISIBILITY_COMMENT, AKSRT_Forum::VISIBILITY_ROLE, AKSRT_Forum::VISIBILITY_MODERATOR], true)) {
|
||||
$excerpt = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +341,34 @@ trait AKSRT_Forum_Topic_Service_Part_3 {
|
||||
}
|
||||
|
||||
$cost = max(0, (int) get_post_meta($topic_id, '_aksrt_credits_cost', true));
|
||||
if ($cost <= 0) {
|
||||
$cost = max(0, (int) get_post_meta($topic_id, '_aksrt_pay_credits', true));
|
||||
}
|
||||
// VIP tier discount for credits.
|
||||
$vip_level = 0;
|
||||
if (function_exists('aksrt_get_user_vip_level')) {
|
||||
$vip_level = (int) aksrt_get_user_vip_level($user_id);
|
||||
} elseif (function_exists('aksrt_is_vip2') && aksrt_is_vip2($user_id)) {
|
||||
$vip_level = 2;
|
||||
} elseif (function_exists('aksrt_is_vip') && aksrt_is_vip($user_id)) {
|
||||
$vip_level = 1;
|
||||
}
|
||||
if ($vip_level >= 2) {
|
||||
$v2 = get_post_meta($topic_id, '_aksrt_credits_vip2', true);
|
||||
if ($v2 !== '' && $v2 !== null && $v2 !== false) {
|
||||
$cost = max(0, (int) $v2);
|
||||
}
|
||||
} elseif ($vip_level >= 1) {
|
||||
$v1 = get_post_meta($topic_id, '_aksrt_credits_vip1', true);
|
||||
if ($v1 !== '' && $v1 !== null && $v1 !== false) {
|
||||
$cost = max(0, (int) $v1);
|
||||
}
|
||||
}
|
||||
if ($cost <= 0 && $vip_level > 0) {
|
||||
// VIP free credits unlock.
|
||||
$this->mark_topic_unlocked($user_id, $topic_id);
|
||||
return $this->forum->success(__('VIP 免费解锁成功', 'aksrt'), ['free' => true]);
|
||||
}
|
||||
if ($cost <= 0) {
|
||||
return $this->forum->error(__('积分价格未设置', 'aksrt'));
|
||||
}
|
||||
@@ -491,31 +526,69 @@ trait AKSRT_Forum_Topic_Service_Part_3 {
|
||||
/**
|
||||
* Resolve cash price for a pay-visibility topic.
|
||||
*
|
||||
* @return array{amount:float,is_vip_price:bool,is_free:bool,label:string,base:float,vip_set:bool}
|
||||
* VIP resolution: VIP2 price → VIP1/legacy VIP price → base.
|
||||
*
|
||||
* @return array{amount:float,is_vip_price:bool,is_free:bool,label:string,base:float,vip_set:bool,vip_level:int}
|
||||
*/
|
||||
public function resolve_topic_pay_price(int $topic_id, int $user_id = 0): array {
|
||||
$base_raw = get_post_meta($topic_id, '_aksrt_pay_price', true);
|
||||
$base = is_numeric($base_raw) ? (float) $base_raw : 0.0;
|
||||
|
||||
$vip_raw = get_post_meta($topic_id, '_aksrt_pay_vip_price', true);
|
||||
$vip_set = ($vip_raw !== '' && $vip_raw !== null && $vip_raw !== false);
|
||||
$vip_price = $vip_set && is_numeric($vip_raw) ? (float) $vip_raw : 0.0;
|
||||
|
||||
$is_vip = $user_id > 0 && function_exists('aksrt_is_vip') && aksrt_is_vip($user_id);
|
||||
if (!$is_vip && $user_id > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($user_id)) {
|
||||
$is_vip = true;
|
||||
$vip_level = 0;
|
||||
if ($user_id > 0) {
|
||||
if (function_exists('aksrt_get_user_vip_level')) {
|
||||
$vip_level = (int) aksrt_get_user_vip_level($user_id);
|
||||
} elseif (function_exists('aksrt_is_vip2') && aksrt_is_vip2($user_id)) {
|
||||
$vip_level = 2;
|
||||
} elseif (function_exists('aksrt_is_vip') && aksrt_is_vip($user_id)) {
|
||||
$vip_level = 1;
|
||||
}
|
||||
if ($vip_level <= 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($user_id)) {
|
||||
$vip_level = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_vip && $vip_set) {
|
||||
$pick = static function ($raw) {
|
||||
if ($raw === '' || $raw === null || $raw === false) {
|
||||
return null;
|
||||
}
|
||||
return is_numeric($raw) ? (float) $raw : null;
|
||||
};
|
||||
|
||||
$vip2 = $pick(get_post_meta($topic_id, '_aksrt_pay_vip2_price', true));
|
||||
$vip1 = $pick(get_post_meta($topic_id, '_aksrt_pay_vip1_price', true));
|
||||
if ($vip1 === null) {
|
||||
// Legacy single VIP key.
|
||||
$vip1 = $pick(get_post_meta($topic_id, '_aksrt_pay_vip_price', true));
|
||||
}
|
||||
|
||||
$amount = $base;
|
||||
$is_vip_price = false;
|
||||
$vip_set = ($vip1 !== null || $vip2 !== null);
|
||||
|
||||
if ($vip_level >= 2 && $vip2 !== null) {
|
||||
$amount = max(0.0, $vip2);
|
||||
$is_vip_price = true;
|
||||
} elseif ($vip_level >= 1 && $vip1 !== null) {
|
||||
$amount = max(0.0, $vip1);
|
||||
$is_vip_price = true;
|
||||
} elseif ($vip_level >= 2 && $vip1 !== null) {
|
||||
// VIP2 user falls back to VIP1 price when VIP2 unset.
|
||||
$amount = max(0.0, $vip1);
|
||||
$is_vip_price = true;
|
||||
}
|
||||
|
||||
if ($is_vip_price) {
|
||||
return [
|
||||
'amount' => max(0.0, $vip_price),
|
||||
'amount' => $amount,
|
||||
'is_vip_price' => true,
|
||||
'is_free' => $vip_price <= 0,
|
||||
'label' => $vip_price <= 0
|
||||
'is_free' => $amount <= 0,
|
||||
'label' => $amount <= 0
|
||||
? __('VIP 免费', 'aksrt')
|
||||
: sprintf(__('VIP ¥%s', 'aksrt'), number_format($vip_price, 2, '.', '')),
|
||||
: sprintf(__('VIP ¥%s', 'aksrt'), number_format($amount, 2, '.', '')),
|
||||
'base' => $base,
|
||||
'vip_set' => true,
|
||||
'vip_level' => $vip_level,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -528,6 +601,7 @@ trait AKSRT_Forum_Topic_Service_Part_3 {
|
||||
: __('价格未设置', 'aksrt'),
|
||||
'base' => $base,
|
||||
'vip_set' => $vip_set,
|
||||
'vip_level' => $vip_level,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -326,9 +326,13 @@ function aksrt_register_forum_topic_meta() {
|
||||
// 付费内容扩展位(论坛帖子付费门槛;前台 forum-topic 渲染)
|
||||
'_aksrt_allow_view' => ['type' => 'string', 'default' => 'public'],
|
||||
'_aksrt_pay_price' => ['type' => 'number', 'default' => 0],
|
||||
'_aksrt_pay_vip_price' => ['type' => 'number', 'default' => 0],
|
||||
'_aksrt_pay_vip_price' => ['type' => 'number', 'default' => 0], // legacy single VIP
|
||||
'_aksrt_pay_vip1_price' => ['type' => 'number', 'default' => 0],
|
||||
'_aksrt_pay_vip2_price' => ['type' => 'number', 'default' => 0],
|
||||
'_aksrt_pay_credits' => ['type' => 'integer', 'default' => 0], // legacy alias of credits_cost
|
||||
'_aksrt_credits_cost' => ['type' => 'integer', 'default' => 0], // runtime unlock price
|
||||
'_aksrt_credits_vip1' => ['type' => 'integer', 'default' => 0],
|
||||
'_aksrt_credits_vip2' => ['type' => 'integer', 'default' => 0],
|
||||
'_aksrt_pay_hide_part' => ['type' => 'integer', 'default' => 0],
|
||||
'_aksrt_visibility_roles' => ['type' => 'array', 'default' => []],
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ class AKSRT_Shortcodes {
|
||||
add_shortcode('reply', [$this, 'render_reply']);
|
||||
add_shortcode('vip', [$this, 'render_vip']);
|
||||
add_shortcode('payshow', [$this, 'render_payshow']);
|
||||
// Zibll-compatible hidecontent alias (type=payshow|reply|login|vip|password|…)
|
||||
add_shortcode('hidecontent', [$this, 'render_hidecontent']);
|
||||
add_shortcode('content', [$this, 'render_content_gate']);
|
||||
// 文章归档 / 个人资料卡 → AKSRT_Templates 模板
|
||||
add_shortcode('aksrt_archive', [$this, 'render_archive']);
|
||||
@@ -101,16 +103,13 @@ class AKSRT_Shortcodes {
|
||||
}
|
||||
|
||||
/**
|
||||
* [payshow]...[/payshow] — 付费后可见
|
||||
* [payshow]...[/payshow] — 付费后可见(文章 content-pay 或论坛主题解锁)
|
||||
*/
|
||||
public function render_payshow(array $atts = [], string $content = ''): string {
|
||||
$atts = shortcode_atts(['message' => ''], $atts, 'payshow');
|
||||
$pid = get_the_ID();
|
||||
|
||||
$can_view = false;
|
||||
if ($pid && class_exists('AKSRT_Content_Visibility')) {
|
||||
$can_view = AKSRT_Content_Visibility::can_view_paid_content($pid);
|
||||
}
|
||||
$can_view = $this->viewer_can_see_paid_block($pid);
|
||||
|
||||
if ($can_view) {
|
||||
return '<div class="aksrt-hidden-content aksrt-hidden-content--unlocked aksrt-hidden-content--payshow">'
|
||||
@@ -132,6 +131,90 @@ class AKSRT_Shortcodes {
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zibll [hidecontent type="payshow|reply|login|…"]…[/hidecontent]
|
||||
*/
|
||||
public function render_hidecontent(array $atts = [], string $content = ''): string {
|
||||
$atts = shortcode_atts([
|
||||
'type' => 'payshow',
|
||||
'message' => '',
|
||||
'pass' => '',
|
||||
], $atts, 'hidecontent');
|
||||
$type = sanitize_key((string) $atts['type']);
|
||||
$msg = (string) $atts['message'];
|
||||
|
||||
switch ($type) {
|
||||
case 'reply':
|
||||
case 'comment':
|
||||
return $this->render_reply(['message' => $msg], $content);
|
||||
case 'login':
|
||||
case 'signin':
|
||||
return $this->render_hide([
|
||||
'mode' => 'login',
|
||||
'message' => $msg !== '' ? $msg : __('登录后可查看隐藏内容', 'aksrt'),
|
||||
], $content);
|
||||
case 'password':
|
||||
case 'pass':
|
||||
return $this->render_hide([
|
||||
'mode' => 'password',
|
||||
'message' => $msg,
|
||||
'pass' => (string) $atts['pass'],
|
||||
], $content);
|
||||
case 'vip':
|
||||
return $this->render_vip(['message' => $msg], $content);
|
||||
case 'payshow':
|
||||
case 'pay':
|
||||
case 'points':
|
||||
default:
|
||||
return $this->render_payshow(['message' => $msg], $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether current viewer may see a paid hide block on this post/topic.
|
||||
*/
|
||||
private function viewer_can_see_paid_block(int $pid): bool {
|
||||
if ($pid <= 0) {
|
||||
return false;
|
||||
}
|
||||
$post = get_post($pid);
|
||||
if (!$post instanceof WP_Post) {
|
||||
return false;
|
||||
}
|
||||
$uid = get_current_user_id();
|
||||
if ($uid > 0 && ((int) $post->post_author === $uid || user_can($uid, 'manage_options'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Forum topics: paid/credits unlock list or orders.
|
||||
if ($post->post_type === 'aksrt_forum_topic' && class_exists('AKSRT_Forum')) {
|
||||
$forum = AKSRT_Forum::get_instance();
|
||||
$visibility = sanitize_key((string) get_post_meta($pid, '_aksrt_visibility', true));
|
||||
if ($visibility === 'public' || $visibility === '') {
|
||||
return true;
|
||||
}
|
||||
if (method_exists($forum, 'has_user_unlocked_topic') && $forum->has_user_unlocked_topic($uid, $pid)) {
|
||||
return true;
|
||||
}
|
||||
if (method_exists($forum, 'has_user_paid_topic') && $forum->has_user_paid_topic($uid, $pid)) {
|
||||
return true;
|
||||
}
|
||||
// Free VIP price counts as paid for cash topics.
|
||||
if ($visibility === 'pay' && method_exists($forum, 'resolve_topic_pay_price')) {
|
||||
$price = $forum->resolve_topic_pay_price($pid, $uid);
|
||||
if (!empty($price['is_free'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (class_exists('AKSRT_Content_Visibility')) {
|
||||
return AKSRT_Content_Visibility::can_view_paid_content($pid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* [content type="admin|author|vip"]...[/content] — 按角色/身份控制可见性
|
||||
*/
|
||||
|
||||
@@ -59,6 +59,83 @@ if (empty($topic['can_view'])) :
|
||||
$deny_vis = $topic['visibility']['key'] ?? '';
|
||||
$credits_cost = (int) get_post_meta($topic_id, '_aksrt_credits_cost', true);
|
||||
$pay_price = get_post_meta($topic_id, '_aksrt_pay_price', true);
|
||||
$pay_hide_part = !empty($topic['pay_hide_part']) || ((int) get_post_meta($topic_id, '_aksrt_pay_hide_part', true) === 1);
|
||||
$partial_ok = $pay_hide_part && in_array($deny_vis, ['pay', 'credits'], true);
|
||||
|
||||
// Partial hide: show title + body with payshow/hidecontent locked blocks + pay CTA.
|
||||
if ($partial_ok) :
|
||||
?>
|
||||
<main id="main-content" class="site-main content-shell content-shell--stream content-shell--forum" tabindex="-1">
|
||||
<div class="content-wrap container">
|
||||
<div class="content-layout content-layout--no-sidebar">
|
||||
<div class="content-layout__primary">
|
||||
<div class="aksrt-forum-topic">
|
||||
<header class="aksrt-forum-topic__header">
|
||||
<h1 class="aksrt-forum-topic__title"><?php echo esc_html((string) ($topic['title'] ?? get_the_title($topic_id))); ?></h1>
|
||||
</header>
|
||||
<div class="aksrt-forum-topic__denied aksrt-forum-topic__denied--partial">
|
||||
<i class="fas fa-lock"></i>
|
||||
<p><?php esc_html_e('部分内容需解锁后查看。', 'aksrt'); ?></p>
|
||||
<?php if ($deny_vis === 'credits' && $credits_cost > 0 && is_user_logged_in()) : ?>
|
||||
<button type="button" class="aksrt-forum-topic__primary-btn js-aksrt-unlock-topic"
|
||||
data-topic-id="<?php echo esc_attr((string) $topic_id); ?>"
|
||||
data-cost="<?php echo esc_attr((string) $credits_cost); ?>">
|
||||
<i class="fas fa-unlock"></i> <?php printf(esc_html__('使用 %d 积分解锁', 'aksrt'), $credits_cost); ?>
|
||||
</button>
|
||||
<span class="aksrt-forum-topic__unlock-feedback js-aksrt-action-feedback" aria-live="polite"></span>
|
||||
<?php elseif ($deny_vis === 'pay' && is_user_logged_in()) : ?>
|
||||
<?php
|
||||
$charge_amount = (float) $pay_price;
|
||||
$vip_price_raw = get_post_meta($topic_id, '_aksrt_pay_vip_price', true);
|
||||
$vip_set = ($vip_price_raw !== '' && $vip_price_raw !== null && $vip_price_raw !== false);
|
||||
$viewer_id = get_current_user_id();
|
||||
$is_vip_user = $viewer_id > 0 && function_exists('aksrt_is_vip') && aksrt_is_vip($viewer_id);
|
||||
$is_free_vip = false;
|
||||
if ($is_vip_user && $vip_set) {
|
||||
$charge_amount = max(0.0, (float) $vip_price_raw);
|
||||
$is_free_vip = $charge_amount <= 0;
|
||||
}
|
||||
?>
|
||||
<button type="button" class="aksrt-forum-topic__primary-btn js-aksrt-purchase-topic"
|
||||
data-topic-id="<?php echo esc_attr((string) $topic_id); ?>"
|
||||
data-price="<?php echo esc_attr(number_format($charge_amount, 2, '.', '')); ?>"
|
||||
data-free="<?php echo $is_free_vip ? '1' : '0'; ?>">
|
||||
<i class="fas fa-shopping-cart"></i>
|
||||
<?php
|
||||
if ($is_free_vip) {
|
||||
esc_html_e('VIP 免费解锁', 'aksrt');
|
||||
} else {
|
||||
printf(esc_html__('余额支付 ¥%s', 'aksrt'), esc_html(number_format($charge_amount, 2, '.', '')));
|
||||
}
|
||||
?>
|
||||
</button>
|
||||
<span class="aksrt-forum-topic__unlock-feedback js-aksrt-action-feedback" aria-live="polite"></span>
|
||||
<?php elseif (!is_user_logged_in()) : ?>
|
||||
<a href="<?php echo esc_url(wp_login_url(get_permalink($topic_id))); ?>" class="aksrt-forum-topic__primary-btn"><?php esc_html_e('登录后解锁', 'aksrt'); ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<article class="aksrt-forum-topic__content aksrt-forum-topic__content--partial">
|
||||
<?php
|
||||
// Render WP content so payshow/hidecontent shortcodes run for locked parts.
|
||||
if (have_posts()) {
|
||||
while (have_posts()) {
|
||||
the_post();
|
||||
the_content();
|
||||
}
|
||||
} else {
|
||||
echo apply_filters('the_content', (string) ($topic['content'] ?? '')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
?>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<?php
|
||||
get_footer();
|
||||
return;
|
||||
endif;
|
||||
?>
|
||||
<main id="main-content" class="site-main content-shell content-shell--stream content-shell--forum" tabindex="-1">
|
||||
<div class="content-wrap container">
|
||||
|
||||
Reference in New Issue
Block a user