- 购物车/商品/论坛表面复用 #77 soft recipe(mix 边 + soft ring) - 论坛 visibility=pay:订单 resource + 余额扣费闭环 - VIP 价/免费解锁 + 购买 CTA/AJAX,复用现有 Order/Credits Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
636 lines
24 KiB
PHP
636 lines
24 KiB
PHP
<?php
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait AKSRT_Forum_Topic_Service_Part_3 {
|
|
|
|
// ================================================================
|
|
|
|
/**
|
|
* Build a topic payload.
|
|
*/
|
|
public function build_topic_payload(WP_Post $post, int $viewer_user_id = 0): array {
|
|
$topic_id = (int) $post->ID;
|
|
$author_id = (int) $post->post_author;
|
|
$board_terms = get_the_terms($topic_id, 'aksrt_forum_board');
|
|
$tag_terms = get_the_terms($topic_id, 'aksrt_forum_tag');
|
|
|
|
$boards = [];
|
|
if ($board_terms && !is_wp_error($board_terms)) {
|
|
foreach ($board_terms as $board_term) {
|
|
$board_link = get_term_link($board_term);
|
|
$boards[] = [
|
|
'id' => (int) $board_term->term_id,
|
|
'name' => (string) $board_term->name,
|
|
'url' => !is_wp_error($board_link) ? (string) $board_link : '',
|
|
];
|
|
}
|
|
}
|
|
|
|
$tags = [];
|
|
if ($tag_terms && !is_wp_error($tag_terms)) {
|
|
foreach ($tag_terms as $tag_term) {
|
|
$tag_link = get_term_link($tag_term);
|
|
$tags[] = [
|
|
'id' => (int) $tag_term->term_id,
|
|
'name' => (string) $tag_term->name,
|
|
'url' => !is_wp_error($tag_link) ? (string) $tag_link : '',
|
|
];
|
|
}
|
|
}
|
|
|
|
$topic_cat_terms = get_the_terms($topic_id, 'aksrt_forum_topic_cat');
|
|
$topic_cat = null;
|
|
if ($topic_cat_terms && !is_wp_error($topic_cat_terms)) {
|
|
$first = $topic_cat_terms[0];
|
|
$tc_link = get_term_link($first);
|
|
$topic_cat = [
|
|
'id' => (int) $first->term_id,
|
|
'name' => (string) $first->name,
|
|
'url' => !is_wp_error($tc_link) ? (string) $tc_link : '',
|
|
];
|
|
}
|
|
|
|
$visibility = sanitize_key((string) get_post_meta($topic_id, '_aksrt_visibility', true));
|
|
if (!isset($this->get_visibility_map()[$visibility])) {
|
|
$visibility = AKSRT_Forum::VISIBILITY_PUBLIC;
|
|
}
|
|
|
|
$review_status = sanitize_key((string) get_post_meta($topic_id, '_aksrt_review_status', true));
|
|
if (!isset($this->get_review_map()[$review_status])) {
|
|
$review_status = $post->post_status === 'publish' ? AKSRT_Forum::REVIEW_APPROVED : AKSRT_Forum::REVIEW_PENDING;
|
|
}
|
|
|
|
$can_view = $this->can_view_topic($post, $viewer_user_id, $visibility, $review_status);
|
|
$excerpt = $post->post_excerpt !== ''
|
|
? $post->post_excerpt
|
|
: wp_trim_words(wp_strip_all_tags((string) $post->post_content), 40);
|
|
|
|
$sticky_level = $this->resolve_sticky_level($topic_id);
|
|
$featured = get_post_meta($topic_id, '_aksrt_featured', true) === '1';
|
|
$locked = get_post_meta($topic_id, '_aksrt_locked', true) === '1';
|
|
$bbs_type = sanitize_key((string) get_post_meta($topic_id, '_aksrt_bbs_type', true));
|
|
if (!in_array($bbs_type, [AKSRT_Forum::BBS_TYPE_STANDARD, AKSRT_Forum::BBS_TYPE_QUESTION], true)) {
|
|
$bbs_type = AKSRT_Forum::BBS_TYPE_STANDARD;
|
|
}
|
|
$question_status = $bbs_type === AKSRT_Forum::BBS_TYPE_QUESTION
|
|
? (int) get_post_meta($topic_id, '_aksrt_question_status', true)
|
|
: null;
|
|
$favorite_count = (int) get_post_meta($topic_id, '_aksrt_favorite_count', true);
|
|
$is_favorited = $viewer_user_id > 0 && $this->forum->is_favorited($viewer_user_id, $topic_id);
|
|
$score = (int) get_post_meta($topic_id, '_aksrt_score', true);
|
|
|
|
$cover = $this->build_cover_payload($topic_id);
|
|
|
|
return [
|
|
'id' => $topic_id,
|
|
'title' => (string) get_the_title($topic_id),
|
|
'excerpt' => (string) $excerpt,
|
|
'content' => (string) $post->post_content,
|
|
'status' => (string) $post->post_status,
|
|
'author_id' => $author_id,
|
|
'author' => $this->build_user_summary($author_id),
|
|
'date' => get_the_date('Y-m-d H:i', $topic_id),
|
|
'modified' => get_the_modified_date('Y-m-d H:i', $topic_id),
|
|
'comment_count' => (int) get_comments([
|
|
'post_id' => $topic_id,
|
|
'count' => true,
|
|
'status' => 'approve',
|
|
]),
|
|
'views' => (int) get_post_meta($topic_id, '_aksrt_views', true),
|
|
'permalink' => (string) get_permalink($topic_id),
|
|
'sticky' => $sticky_level > AKSRT_Forum::STICKY_NONE,
|
|
'sticky_level' => $sticky_level,
|
|
'sticky_label' => $this->get_sticky_label($sticky_level),
|
|
'featured' => $featured,
|
|
'locked' => $locked,
|
|
'special_topic' => (string) get_post_meta($topic_id, '_aksrt_special_topic', true),
|
|
'bbs_type' => $bbs_type,
|
|
'question_status' => $question_status,
|
|
'favorite_count' => $favorite_count,
|
|
'is_favorited' => $is_favorited,
|
|
'score' => $score,
|
|
'cover' => $cover,
|
|
'visibility' => [
|
|
'key' => $visibility,
|
|
'label' => $this->get_visibility_label($visibility),
|
|
],
|
|
'review_status' => [
|
|
'key' => $review_status,
|
|
'label' => $this->get_review_label($review_status),
|
|
],
|
|
'moderation_note' => (string) get_post_meta($topic_id, '_aksrt_moderation_note', true),
|
|
'plate_id' => (int) get_post_meta($topic_id, '_aksrt_plate_id', true),
|
|
'boards' => $boards,
|
|
'tags' => $tags,
|
|
'topic_cat' => $topic_cat,
|
|
'vote' => $this->get_vote_payload($topic_id, $viewer_user_id),
|
|
'can_view' => $can_view,
|
|
'can_edit' => $this->forum->can_edit_topic($viewer_user_id, $topic_id),
|
|
'can_moderate' => $this->forum->can_moderate_topic($viewer_user_id, $topic_id),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Determine whether current viewer can read a topic.
|
|
*/
|
|
public function can_view_topic(WP_Post $post, int $viewer_user_id, string $visibility, string $review_status): bool {
|
|
if (user_can($viewer_user_id, 'manage_options')) {
|
|
return true;
|
|
}
|
|
|
|
if ($post->post_status !== 'publish' || $review_status !== AKSRT_Forum::REVIEW_APPROVED) {
|
|
return $viewer_user_id > 0
|
|
&& ((int) $post->post_author === $viewer_user_id || $this->forum->can_moderate_topic($viewer_user_id, (int) $post->ID));
|
|
}
|
|
|
|
if ((int) $post->post_author === $viewer_user_id) {
|
|
return true;
|
|
}
|
|
|
|
if ($visibility === AKSRT_Forum::VISIBILITY_PUBLIC) {
|
|
return true;
|
|
}
|
|
|
|
if ($visibility === AKSRT_Forum::VISIBILITY_LOGIN) {
|
|
return $viewer_user_id > 0;
|
|
}
|
|
|
|
if ($visibility === AKSRT_Forum::VISIBILITY_COMMENT) {
|
|
if ($viewer_user_id <= 0) {
|
|
return false;
|
|
}
|
|
return $this->has_user_commented_on($viewer_user_id, (int) $post->ID);
|
|
}
|
|
|
|
if ($visibility === AKSRT_Forum::VISIBILITY_ROLE) {
|
|
if ($viewer_user_id <= 0) {
|
|
return false;
|
|
}
|
|
$allowed_roles = (array) get_post_meta((int) $post->ID, '_aksrt_visibility_roles', true);
|
|
if (empty($allowed_roles)) {
|
|
return true;
|
|
}
|
|
$user = get_userdata($viewer_user_id);
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
return !empty(array_intersect($user->roles, $allowed_roles));
|
|
}
|
|
|
|
if ($visibility === AKSRT_Forum::VISIBILITY_CREDITS) {
|
|
if ($viewer_user_id <= 0) {
|
|
return false;
|
|
}
|
|
return $this->has_user_unlocked_topic($viewer_user_id, (int) $post->ID);
|
|
}
|
|
|
|
if ($visibility === AKSRT_Forum::VISIBILITY_PAY) {
|
|
if ($viewer_user_id <= 0) {
|
|
return false;
|
|
}
|
|
return $this->has_user_paid_topic($viewer_user_id, (int) $post->ID);
|
|
}
|
|
|
|
if ($visibility === AKSRT_Forum::VISIBILITY_MODERATOR) {
|
|
if ($viewer_user_id <= 0) {
|
|
return false;
|
|
}
|
|
|
|
return $this->forum->can_moderate_topic($viewer_user_id, (int) $post->ID);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Build cover payload for a topic.
|
|
*
|
|
* Supports three cover types: image (single), slide (multiple images), video.
|
|
* Falls back to WP featured image (thumbnail) when no explicit cover_type is set.
|
|
*/
|
|
public function build_cover_payload(int $topic_id): ?array {
|
|
$cover_type = (string) get_post_meta($topic_id, '_aksrt_cover_type', true);
|
|
|
|
if ($cover_type === 'image') {
|
|
$url = (string) get_post_meta($topic_id, '_aksrt_cover_image', true);
|
|
if ($url === '') {
|
|
$url = (string) get_the_post_thumbnail_url($topic_id, 'medium_large');
|
|
}
|
|
if ($url === '') return null;
|
|
|
|
return [
|
|
'type' => 'image',
|
|
'url' => $url,
|
|
'ratio' => (string) (get_post_meta($topic_id, '_aksrt_cover_ratio', true) ?: '16:9'),
|
|
];
|
|
}
|
|
|
|
if ($cover_type === 'slide') {
|
|
$slides = get_post_meta($topic_id, '_aksrt_featured_slide', true);
|
|
if (!is_array($slides) || empty($slides)) return null;
|
|
|
|
return [
|
|
'type' => 'slide',
|
|
'images' => array_map('esc_url', array_slice($slides, 0, 9)),
|
|
'ratio' => (string) (get_post_meta($topic_id, '_aksrt_cover_ratio', true) ?: '16:9'),
|
|
];
|
|
}
|
|
|
|
if ($cover_type === 'video') {
|
|
$video_url = (string) get_post_meta($topic_id, '_aksrt_featured_video', true);
|
|
if ($video_url === '') return null;
|
|
|
|
$poster = (string) get_the_post_thumbnail_url($topic_id, 'medium_large');
|
|
return [
|
|
'type' => 'video',
|
|
'url' => $video_url,
|
|
'poster' => $poster,
|
|
'ratio' => (string) (get_post_meta($topic_id, '_aksrt_cover_ratio', true) ?: '16:9'),
|
|
];
|
|
}
|
|
|
|
// Fallback: use WP featured image if set
|
|
$thumb_url = (string) get_the_post_thumbnail_url($topic_id, 'medium_large');
|
|
if ($thumb_url !== '') {
|
|
return [
|
|
'type' => 'image',
|
|
'url' => $thumb_url,
|
|
'ratio' => '16:9',
|
|
];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Build user summary payload.
|
|
*/
|
|
public function build_user_summary(int $user_id): array {
|
|
$user = $user_id > 0 ? get_userdata($user_id) : false;
|
|
|
|
return [
|
|
'id' => $user_id,
|
|
'display_name' => $user ? $user->display_name : '',
|
|
'user_email' => $user ? $user->user_email : '',
|
|
'avatar_url' => $user ? $this->get_user_avatar_url($user_id) : '',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get avatar url.
|
|
*/
|
|
public function get_user_avatar_url(int $user_id): string {
|
|
if (function_exists('aksrt_get_avatar_url')) {
|
|
return (string) aksrt_get_avatar_url($user_id);
|
|
}
|
|
|
|
return (string) get_avatar_url($user_id);
|
|
}
|
|
|
|
// ================================================================
|
|
// Topic unlock
|
|
// ================================================================
|
|
|
|
public function unlock_topic_with_credits(int $user_id, int $topic_id): array {
|
|
$post = get_post($topic_id);
|
|
if (!$post || $post->post_type !== 'aksrt_forum_topic') {
|
|
return $this->forum->error(__('主题不存在', 'aksrt'));
|
|
}
|
|
|
|
$visibility = (string) get_post_meta($topic_id, '_aksrt_visibility', true);
|
|
if ($visibility !== AKSRT_Forum::VISIBILITY_CREDITS) {
|
|
return $this->forum->error(__('该主题不需要积分解锁', 'aksrt'));
|
|
}
|
|
|
|
if ($this->has_user_unlocked_topic($user_id, $topic_id)) {
|
|
return $this->forum->success(__('已解锁', 'aksrt'));
|
|
}
|
|
|
|
$cost = max(0, (int) get_post_meta($topic_id, '_aksrt_credits_cost', true));
|
|
if ($cost <= 0) {
|
|
return $this->forum->error(__('积分价格未设置', 'aksrt'));
|
|
}
|
|
|
|
if (!class_exists('AKSRT_Credits')) {
|
|
return $this->forum->error(__('积分系统不可用', 'aksrt'));
|
|
}
|
|
|
|
$balance = AKSRT_Credits::get_balance($user_id);
|
|
if ($balance < $cost) {
|
|
return $this->forum->error(sprintf(__('积分不足,需要 %d 积分', 'aksrt'), $cost));
|
|
}
|
|
|
|
$deducted = AKSRT_Credits::deduct($user_id, $cost, 'forum_topic_unlock', [
|
|
'topic_id' => $topic_id,
|
|
], sprintf(__('解锁帖子「%s」', 'aksrt'), get_the_title($topic_id)));
|
|
|
|
if (is_wp_error($deducted) || $deducted === false) {
|
|
$msg = is_wp_error($deducted) ? $deducted->get_error_message() : __('积分扣除失败', 'aksrt');
|
|
return $this->forum->error($msg ?: __('积分扣除失败', 'aksrt'));
|
|
}
|
|
|
|
$this->mark_topic_unlocked($user_id, $topic_id);
|
|
|
|
$author_id = (int) $post->post_author;
|
|
if ($author_id > 0 && $author_id !== $user_id) {
|
|
$author_share = max(0, (int) ($this->forum->get_forum_config()['credits']['author_share_percent'] ?? 70));
|
|
$author_amount = (int) floor($cost * $author_share / 100);
|
|
if ($author_amount > 0) {
|
|
AKSRT_Credits::add($author_id, $author_amount, 'forum_content_income', [
|
|
'topic_id' => $topic_id,
|
|
'buyer_id' => $user_id,
|
|
], sprintf(__('帖子「%s」被付费查看', 'aksrt'), get_the_title($topic_id)));
|
|
}
|
|
}
|
|
|
|
return $this->forum->success(__('解锁成功', 'aksrt'));
|
|
}
|
|
|
|
/**
|
|
* Cash/balance purchase for VISIBILITY_PAY topics.
|
|
*
|
|
* Reuses AKSRT_Order product_type=resource + balance credits conversion
|
|
* (1 CNY = 100 credits, same as AKSRT_Balance_Gateway).
|
|
*
|
|
* @return array{success:bool,message:string}
|
|
*/
|
|
public function purchase_topic_with_balance(int $user_id, int $topic_id): array {
|
|
$post = get_post($topic_id);
|
|
if (!$post || $post->post_type !== 'aksrt_forum_topic') {
|
|
return $this->forum->error(__('主题不存在', 'aksrt'));
|
|
}
|
|
|
|
if ($user_id <= 0) {
|
|
return $this->forum->error(__('请先登录', 'aksrt'));
|
|
}
|
|
|
|
$visibility = (string) get_post_meta($topic_id, '_aksrt_visibility', true);
|
|
if ($visibility !== AKSRT_Forum::VISIBILITY_PAY) {
|
|
return $this->forum->error(__('该主题不是付费可见内容', 'aksrt'));
|
|
}
|
|
|
|
if ($this->has_user_paid_topic($user_id, $topic_id)) {
|
|
return $this->forum->success(__('已购买', 'aksrt'));
|
|
}
|
|
|
|
$price = $this->resolve_topic_pay_price($topic_id, $user_id);
|
|
if (!empty($price['is_free'])) {
|
|
$this->mark_topic_unlocked($user_id, $topic_id);
|
|
return $this->forum->success(__('VIP 免费解锁成功', 'aksrt'), [
|
|
'amount' => 0,
|
|
'free' => true,
|
|
]);
|
|
}
|
|
|
|
$amount = (float) ($price['amount'] ?? 0);
|
|
if ($amount <= 0) {
|
|
return $this->forum->error(__('价格未设置', 'aksrt'));
|
|
}
|
|
|
|
if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Credits')) {
|
|
return $this->forum->error(__('支付系统不可用', 'aksrt'));
|
|
}
|
|
|
|
$product_type = class_exists('AKSRT_Domain_Model')
|
|
? AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE
|
|
: 'resource';
|
|
|
|
$order_number = AKSRT_Order::get_instance()->create($user_id, $product_type, $topic_id, $amount, 'CNY');
|
|
if ($order_number === '') {
|
|
return $this->forum->error(__('创建订单失败', 'aksrt'));
|
|
}
|
|
|
|
// Align with AKSRT_Balance_Gateway: 1 CNY = 100 credits.
|
|
$credits_cost = (int) ceil($amount * 100);
|
|
$balance = AKSRT_Credits::get_balance($user_id);
|
|
if ($balance < $credits_cost) {
|
|
AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_CANCELLED, [
|
|
'note' => __('余额不足,自动取消', 'aksrt'),
|
|
]);
|
|
return $this->forum->error(sprintf(
|
|
__('余额不足,需要 %d 积分(约 ¥%s),当前余额 %d', 'aksrt'),
|
|
$credits_cost,
|
|
number_format($amount, 2, '.', ''),
|
|
$balance
|
|
));
|
|
}
|
|
|
|
$deducted = AKSRT_Credits::deduct(
|
|
$user_id,
|
|
$credits_cost,
|
|
'forum_topic_pay',
|
|
[
|
|
'topic_id' => $topic_id,
|
|
'order' => $order_number,
|
|
],
|
|
sprintf(__('购买帖子「%s」', 'aksrt'), get_the_title($topic_id))
|
|
);
|
|
|
|
if (is_wp_error($deducted) || $deducted === false) {
|
|
AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_CANCELLED, [
|
|
'note' => __('扣款失败,自动取消', 'aksrt'),
|
|
]);
|
|
$msg = is_wp_error($deducted) ? $deducted->get_error_message() : __('扣款失败', 'aksrt');
|
|
return $this->forum->error($msg ?: __('扣款失败', 'aksrt'));
|
|
}
|
|
|
|
$paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [
|
|
'payment_method' => 'balance',
|
|
'payment_transaction_id' => 'BAL_' . $order_number,
|
|
'note' => sprintf(__('论坛付费主题余额支付 %d 积分', 'aksrt'), $credits_cost),
|
|
]);
|
|
|
|
if (!$paid) {
|
|
// Best-effort refund so user is not charged without access.
|
|
AKSRT_Credits::add(
|
|
$user_id,
|
|
$credits_cost,
|
|
'forum_topic_pay_refund',
|
|
['topic_id' => $topic_id, 'order' => $order_number],
|
|
sprintf(__('购买失败退款:%s', 'aksrt'), get_the_title($topic_id))
|
|
);
|
|
return $this->forum->error(__('订单确认失败,已尝试退回积分', 'aksrt'));
|
|
}
|
|
|
|
$this->mark_topic_unlocked($user_id, $topic_id);
|
|
|
|
return $this->forum->success(__('购买成功', 'aksrt'), [
|
|
'order_number' => $order_number,
|
|
'amount' => $amount,
|
|
'credits_cost' => $credits_cost,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 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}
|
|
*/
|
|
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;
|
|
}
|
|
|
|
if ($is_vip && $vip_set) {
|
|
return [
|
|
'amount' => max(0.0, $vip_price),
|
|
'is_vip_price' => true,
|
|
'is_free' => $vip_price <= 0,
|
|
'label' => $vip_price <= 0
|
|
? __('VIP 免费', 'aksrt')
|
|
: sprintf(__('VIP ¥%s', 'aksrt'), number_format($vip_price, 2, '.', '')),
|
|
'base' => $base,
|
|
'vip_set' => true,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'amount' => max(0.0, $base),
|
|
'is_vip_price' => false,
|
|
'is_free' => false,
|
|
'label' => $base > 0
|
|
? sprintf(__('¥%s', 'aksrt'), number_format($base, 2, '.', ''))
|
|
: __('价格未设置', 'aksrt'),
|
|
'base' => $base,
|
|
'vip_set' => $vip_set,
|
|
];
|
|
}
|
|
|
|
public function mark_topic_unlocked(int $user_id, int $topic_id): void {
|
|
if ($user_id <= 0 || $topic_id <= 0) {
|
|
return;
|
|
}
|
|
$unlocked = get_user_meta($user_id, 'aksrt_unlocked_topics', true);
|
|
if (!is_array($unlocked)) {
|
|
$unlocked = [];
|
|
}
|
|
$unlocked[] = $topic_id;
|
|
update_user_meta($user_id, 'aksrt_unlocked_topics', array_values(array_unique(array_map('intval', $unlocked))));
|
|
}
|
|
|
|
public function has_user_unlocked_topic(int $user_id, int $post_id): bool {
|
|
$unlocked = get_user_meta($user_id, 'aksrt_unlocked_topics', true);
|
|
if (!is_array($unlocked)) {
|
|
return false;
|
|
}
|
|
return in_array($post_id, array_map('intval', $unlocked), true);
|
|
}
|
|
|
|
/**
|
|
* Paid access for VISIBILITY_PAY: unlock list OR paid resource order OR VIP free.
|
|
*/
|
|
public function has_user_paid_topic(int $user_id, int $topic_id): bool {
|
|
if ($user_id <= 0 || $topic_id <= 0) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->has_user_unlocked_topic($user_id, $topic_id)) {
|
|
return true;
|
|
}
|
|
|
|
if (class_exists('AKSRT_Order')) {
|
|
$product_type = class_exists('AKSRT_Domain_Model')
|
|
? AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE
|
|
: 'resource';
|
|
if (AKSRT_Order::get_instance()->has_paid_access($user_id, $product_type, $topic_id)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
$price = $this->resolve_topic_pay_price($topic_id, $user_id);
|
|
if (!empty($price['is_free'])) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function has_user_commented_on(int $user_id, int $post_id): bool {
|
|
global $wpdb;
|
|
$count = (int) $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_post_ID = %d AND user_id = %d AND comment_approved = '1' LIMIT 1",
|
|
$post_id,
|
|
$user_id
|
|
));
|
|
return $count > 0;
|
|
}
|
|
|
|
// ================================================================
|
|
// Status helpers
|
|
// ================================================================
|
|
|
|
/**
|
|
* Normalize post status filter.
|
|
*
|
|
* @return array<int,string>|string
|
|
*/
|
|
public function normalize_post_status_query(string $status) {
|
|
if ($status === 'all') {
|
|
return ['publish', 'pending', 'draft'];
|
|
}
|
|
|
|
return $status !== '' ? $status : 'publish';
|
|
}
|
|
|
|
/**
|
|
* Visibility labels.
|
|
*/
|
|
public function get_visibility_map(): array {
|
|
return [
|
|
AKSRT_Forum::VISIBILITY_PUBLIC => __('公开可见', 'aksrt'),
|
|
AKSRT_Forum::VISIBILITY_LOGIN => __('登录可见', 'aksrt'),
|
|
AKSRT_Forum::VISIBILITY_COMMENT => __('评论可见', 'aksrt'),
|
|
AKSRT_Forum::VISIBILITY_ROLE => __('角色可见', 'aksrt'),
|
|
AKSRT_Forum::VISIBILITY_CREDITS => __('积分查看', 'aksrt'),
|
|
AKSRT_Forum::VISIBILITY_PAY => __('付费查看', 'aksrt'),
|
|
AKSRT_Forum::VISIBILITY_MODERATOR => __('版主可见', 'aksrt'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Review labels.
|
|
*/
|
|
public function get_review_map(): array {
|
|
return [
|
|
AKSRT_Forum::REVIEW_APPROVED => __('已通过', 'aksrt'),
|
|
AKSRT_Forum::REVIEW_PENDING => __('待审核', 'aksrt'),
|
|
AKSRT_Forum::REVIEW_REJECTED => __('已拒绝', 'aksrt'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get visibility label.
|
|
*/
|
|
public function get_visibility_label(string $visibility): string {
|
|
$map = $this->get_visibility_map();
|
|
return $map[$visibility] ?? $map[AKSRT_Forum::VISIBILITY_PUBLIC];
|
|
}
|
|
|
|
/**
|
|
* Get review label.
|
|
*/
|
|
public function get_review_label(string $review_status): string {
|
|
$map = $this->get_review_map();
|
|
return $map[$review_status] ?? $map[AKSRT_Forum::REVIEW_PENDING];
|
|
}
|
|
|
|
/**
|
|
* Get visibility options.
|
|
*/
|
|
public function get_visibility_options(): array {
|
|
$options = [];
|
|
foreach ($this->get_visibility_map() as $value => $label) {
|
|
$options[] = ['label' => $label, 'value' => $value];
|
|
}
|
|
return $options;
|
|
}
|
|
}
|