导入 AKSRT 主题开发源码

This commit is contained in:
Claude Code committed 2026-07-03 22:50:34 +08:00
1 parent 9681dd3d48
commit 1268a55dd0
595 files changed
+127705

No files matched your search

@@ -0,0 +1,413 @@
<?php
defined('ABSPATH') || exit;
trait AKSRT_Forum_Topic_Service_Part_1 {
public function moderate_topic(int $topic_id, string $action, array $payload, int $operator_id = 0) {
$post = get_post($topic_id);
if (!$post || $post->post_type !== 'aksrt_forum_topic') {
return new WP_Error('invalid_topic', __('帖子不存在', 'aksrt'));
}
if (!$this->forum->can_moderate_topic($operator_id, $topic_id)) {
return new WP_Error('forbidden', __('无权执行该治理动作', 'aksrt'));
}
$action = sanitize_key($action);
$note = sanitize_textarea_field((string) ($payload['note'] ?? ''));
switch ($action) {
case 'move_board':
$target_board_id = (int) ($payload['target_board_id'] ?? 0);
$target_board = get_term($target_board_id, 'aksrt_forum_board');
if (!$target_board || is_wp_error($target_board)) {
return new WP_Error('invalid_board', __('目标版块不存在', 'aksrt'));
}
$current_board_ids = wp_get_post_terms($topic_id, 'aksrt_forum_board', ['fields' => 'ids']);
wp_set_object_terms($topic_id, [$target_board_id], 'aksrt_forum_board');
foreach ($current_board_ids as $current_board_id) {
$this->forum->sync_board_count((int) $current_board_id);
}
$this->forum->sync_board_count($target_board_id);
break;
case 'visibility':
$visibility = sanitize_key((string) ($payload['visibility'] ?? ''));
if (!isset($this->get_visibility_map()[$visibility])) {
return new WP_Error('invalid_visibility', __('可见性参数无效', 'aksrt'));
}
update_post_meta($topic_id, '_aksrt_visibility', $visibility);
if ($visibility === AKSRT_Forum::VISIBILITY_CREDITS) {
update_post_meta($topic_id, '_aksrt_credits_cost', max(0, (int) ($payload['credits_cost'] ?? 0)));
delete_post_meta($topic_id, '_aksrt_pay_price');
delete_post_meta($topic_id, '_aksrt_visibility_roles');
} elseif ($visibility === AKSRT_Forum::VISIBILITY_PAY) {
update_post_meta($topic_id, '_aksrt_pay_price', sanitize_text_field((string) ($payload['pay_price'] ?? '')));
delete_post_meta($topic_id, '_aksrt_credits_cost');
delete_post_meta($topic_id, '_aksrt_visibility_roles');
} elseif ($visibility === AKSRT_Forum::VISIBILITY_ROLE) {
$roles = isset($payload['visibility_roles']) && is_array($payload['visibility_roles'])
? array_values(array_filter(array_map('sanitize_key', $payload['visibility_roles']))) : [];
update_post_meta($topic_id, '_aksrt_visibility_roles', $roles);
delete_post_meta($topic_id, '_aksrt_credits_cost');
delete_post_meta($topic_id, '_aksrt_pay_price');
} else {
delete_post_meta($topic_id, '_aksrt_credits_cost');
delete_post_meta($topic_id, '_aksrt_pay_price');
delete_post_meta($topic_id, '_aksrt_visibility_roles');
}
break;
case 'review':
$review_status = sanitize_key((string) ($payload['review_status'] ?? ''));
if (!isset($this->get_review_map()[$review_status])) {
return new WP_Error('invalid_review_status', __('审核状态参数无效', 'aksrt'));
}
$board_ids = wp_get_post_terms($topic_id, 'aksrt_forum_board', ['fields' => 'ids']);
update_post_meta($topic_id, '_aksrt_review_status', $review_status);
if ($review_status === AKSRT_Forum::REVIEW_APPROVED) {
wp_update_post(['ID' => $topic_id, 'post_status' => 'publish']);
} elseif ($review_status === AKSRT_Forum::REVIEW_PENDING) {
wp_update_post(['ID' => $topic_id, 'post_status' => 'pending']);
} else {
wp_update_post(['ID' => $topic_id, 'post_status' => 'draft']);
}
foreach ($board_ids as $board_id) {
$this->forum->sync_board_count((int) $board_id);
}
break;
case 'sticky': {
$raw = isset($payload['value']) ? $payload['value'] : null;
if ($raw === false || $raw === null) {
$level = AKSRT_Forum::STICKY_NONE;
} elseif (is_numeric($raw)) {
$level = (int) $raw;
} else {
$level = !empty($raw) ? AKSRT_Forum::STICKY_PLATE : AKSRT_Forum::STICKY_NONE;
}
$level = max(AKSRT_Forum::STICKY_NONE, min(AKSRT_Forum::STICKY_GLOBAL, $level));
update_post_meta($topic_id, '_aksrt_sticky', $level);
break;
}
case 'featured': {
$prev = get_post_meta($topic_id, '_aksrt_featured', true) === '1';
$next = !empty($payload['value']);
update_post_meta($topic_id, '_aksrt_featured', $next ? '1' : '0');
// 精华联动:从「未设精华」→「设为精华」时触发积分奖励 + 站内信通知(§7.3/§7.7/§7.8)。
// 从「精华」→「取消」时不回收积分,仅不重复发放。
if ($next && !$prev) {
$this->reward_essence($topic_id, $operator_id);
}
break;
}
case 'locked': {
$locked = !empty($payload['value']);
update_post_meta($topic_id, '_aksrt_locked', $locked ? '1' : '0');
wp_update_post([
'ID' => $topic_id,
'comment_status' => $locked ? 'closed' : 'open',
]);
break;
}
case 'question_status': {
$bbs_type = sanitize_key((string) get_post_meta($topic_id, '_aksrt_bbs_type', true));
if ($bbs_type !== AKSRT_Forum::BBS_TYPE_QUESTION) {
return new WP_Error('not_question', __('该帖不是提问类型', 'aksrt'));
}
$status = (int) ($payload['value'] ?? AKSRT_Forum::QUESTION_UNSOLVED);
$status = in_array($status, [AKSRT_Forum::QUESTION_UNSOLVED, AKSRT_Forum::QUESTION_SOLVED], true)
? $status : AKSRT_Forum::QUESTION_UNSOLVED;
update_post_meta($topic_id, '_aksrt_question_status', $status);
break;
}
case 'close': {
$closed = !empty($payload['value']);
if ($closed) {
update_post_meta($topic_id, '_aksrt_closed', '1');
wp_update_post(['ID' => $topic_id, 'comment_status' => 'closed']);
} else {
delete_post_meta($topic_id, '_aksrt_closed');
$locked = get_post_meta($topic_id, '_aksrt_locked', true) === '1';
if (!$locked) {
wp_update_post(['ID' => $topic_id, 'comment_status' => 'open']);
}
}
break;
}
case 'hide': {
$hidden = !empty($payload['value']);
if ($hidden) {
update_post_meta($topic_id, '_aksrt_hidden', '1');
$board_ids = wp_get_post_terms($topic_id, 'aksrt_forum_board', ['fields' => 'ids']);
wp_update_post(['ID' => $topic_id, 'post_status' => 'private']);
foreach ($board_ids as $bid) {
$this->forum->sync_board_count((int) $bid);
}
} else {
delete_post_meta($topic_id, '_aksrt_hidden');
$board_ids = wp_get_post_terms($topic_id, 'aksrt_forum_board', ['fields' => 'ids']);
wp_update_post(['ID' => $topic_id, 'post_status' => 'publish']);
foreach ($board_ids as $bid) {
$this->forum->sync_board_count((int) $bid);
}
}
break;
}
case 'delete': {
$board_ids = wp_get_post_terms($topic_id, 'aksrt_forum_board', ['fields' => 'ids']);
wp_trash_post($topic_id);
foreach ($board_ids as $bid) {
$this->forum->sync_board_count((int) $bid);
}
$author_id = (int) $post->post_author;
if ($author_id > 0 && $author_id !== $operator_id) {
$this->forum->notify($author_id, 'system', sprintf(
__('你的帖子「%s」已被删除', 'aksrt'),
$post->post_title
), [
'title' => __('帖子已删除', 'aksrt'),
'target_url' => home_url('/forums/'),
'reason' => $note,
]);
}
break;
}
case 'undelete': {
if ($post->post_status !== 'trash') {
return new WP_Error('not_trashed', __('该帖不在回收站中', 'aksrt'));
}
wp_untrash_post($topic_id);
$review_status = (string) get_post_meta($topic_id, '_aksrt_review_status', true);
if ($review_status === AKSRT_Forum::REVIEW_APPROVED) {
wp_update_post(['ID' => $topic_id, 'post_status' => 'publish']);
} else {
wp_update_post(['ID' => $topic_id, 'post_status' => 'pending']);
}
$board_ids = wp_get_post_terms($topic_id, 'aksrt_forum_board', ['fields' => 'ids']);
foreach ($board_ids as $bid) {
$this->forum->sync_board_count((int) $bid);
}
break;
}
default:
return new WP_Error('invalid_action', __('不支持的治理动作', 'aksrt'));
}
if ($note !== '') {
update_post_meta($topic_id, '_aksrt_moderation_note', $note);
}
update_post_meta($topic_id, '_aksrt_moderated_by', $operator_id);
update_post_meta($topic_id, '_aksrt_moderated_at', current_time('mysql'));
$this->forum->notify_moderation_result($topic_id, $action, $operator_id);
return [
'message' => __('治理动作已执行', 'aksrt'),
'topic' => $this->build_topic_payload(get_post($topic_id), $operator_id),
];
}
/**
* 精华联动奖励(§7.3 联动 + §7.7 被设精华奖励 + §7.8 精华通知)。
*
* - 触发积分奖励:读 `credits.essence` 配置,有每日上限与去重 meta(防止重复发放)。
* - 触发站内信通知作者(依赖 AKSRT_Message inbox 子系统)。
* - 缺失依赖(积分模块未启用/未配置)时静默跳过,不阻塞治理动作。
*/
public function reward_essence(int $topic_id, int $operator_id): void {
$post = get_post($topic_id);
if (!$post || $post->post_type !== 'aksrt_forum_topic') {
return;
}
$author_id = (int) $post->post_author;
if ($author_id <= 0) {
return;
}
// 去重:同一帖已发过精华奖励则跳过(取消再加不重复发)。
if (get_post_meta($topic_id, '_aksrt_essence_rewarded', true) === '1') {
return;
}
// 积分奖励(§7.7):读 `credits.essence`,带每日上限。
if (class_exists('AKSRT_Credits') && class_exists('AKSRT_Settings')) {
$cfg = AKSRT_Settings::get_forum_settings();
$amount = (int) ($cfg['credits']['essence'] ?? 0);
if ($amount > 0) {
$daily_max = (int) ($cfg['credits']['daily_max'] ?? 50);
if ($daily_max > 0 && $this->today_credits_count($author_id, 'forum_essence') >= $daily_max) {
$amount = 0;
}
if ($amount > 0) {
AKSRT_Credits::add($author_id, $amount, 'forum_essence', [
'type' => 'post',
'id' => $topic_id,
], __('主题被设为精华', 'aksrt'));
update_post_meta($topic_id, '_aksrt_essence_rewarded', '1');
update_post_meta($topic_id, '_aksrt_essence_rewarded_at', current_time('mysql'));
}
}
}
// 站内信通知作者(§7.8)。
if (class_exists('AKSRT_Message')) {
$title = get_the_title($topic_id);
$permalink = (string) get_permalink($topic_id);
AKSRT_Message::get_instance()->send(
$author_id,
'forum_essence',
sprintf(__('你的主题「%s」被设为精华', 'aksrt'), $title),
[
'title' => __('主题被设为精华', 'aksrt'),
'target_url' => $permalink,
]
);
}
do_action('aksrt_forum_topic_featured', $topic_id, $author_id, $operator_id);
}
/**
* 今日某 reason 的积分发放次数(用于精华/被加分等每日上限判断)。
*/
public function today_credits_count(int $user_id, string $reason): int {
if (!class_exists('AKSRT_DB_Migrator') || $user_id <= 0) {
return 0;
}
$table = AKSRT_DB_Migrator::table('credit_log');
if (!$table) {
return 0;
}
global $wpdb;
$today = current_time('Y-m-d');
return (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table} WHERE user_id = %d AND reason = %s AND DATE(created_at) = %s",
$user_id, $reason, $today
));
}
/**
* 置顶分级 label(对标 zibll zib_bbs_get_topping_badge)。
*/
public function get_sticky_label(int $level): string {
switch ($level) {
case AKSRT_Forum::STICKY_GLOBAL:
return __('全局置顶', 'aksrt');
case AKSRT_Forum::STICKY_SUPER:
return __('超级置顶', 'aksrt');
case AKSRT_Forum::STICKY_PLATE:
return __('置顶', 'aksrt');
default:
return '';
}
}
/**
* 读取并规范化置顶分级 meta。
*
* 兼容历史 0/1 二态 meta("1" → STICKY_PLATE),新数据存 0~3 整数。
*/
public function resolve_sticky_level(int $topic_id): int {
$raw = get_post_meta($topic_id, '_aksrt_sticky', true);
if ($raw === '' || $raw === null) {
return AKSRT_Forum::STICKY_NONE;
}
if (is_numeric($raw)) {
return max(AKSRT_Forum::STICKY_NONE, min(AKSRT_Forum::STICKY_GLOBAL, (int) $raw));
}
return $raw === '1' ? AKSRT_Forum::STICKY_PLATE : AKSRT_Forum::STICKY_NONE;
}
// ================================================================
// Voting system
// ================================================================
/**
* Submit vote.
*
* @return array<string,mixed>|WP_Error
*/
public function submit_vote(int $topic_id, int $user_id, array $option_ids) {
if ($user_id <= 0) {
return new WP_Error('not_logged_in', __('请先登录后投票', 'aksrt'));
}
$post = get_post($topic_id);
if (!$post || $post->post_type !== 'aksrt_forum_topic') {
return new WP_Error('invalid_topic', __('帖子不存在', 'aksrt'));
}
$vote = $this->get_vote_payload($topic_id, $user_id);
if (empty($vote['enabled'])) {
return new WP_Error('vote_disabled', __('当前主题未开启投票', 'aksrt'));
}
if (!empty($vote['expired'])) {
return new WP_Error('vote_expired', __('投票已结束', 'aksrt'));
}
if (!empty($vote['has_voted'])) {
return new WP_Error('already_voted', __('你已经参与过本次投票', 'aksrt'));
}
$selected = array_values(array_unique(array_filter(array_map('sanitize_text_field', $option_ids))));
if (empty($selected)) {
return new WP_Error('empty_selection', __('请选择至少一个投票选项', 'aksrt'));
}
if (empty($vote['multiple']) && count($selected) > 1) {
return new WP_Error('too_many_selection', __('当前投票仅支持单选', 'aksrt'));
}
$options = get_post_meta($topic_id, '_aksrt_vote_options', true);
$options = is_array($options) ? $options : [];
$valid_option_ids = wp_list_pluck($options, 'id');
foreach ($selected as $selected_id) {
if (!in_array($selected_id, $valid_option_ids, true)) {
return new WP_Error('invalid_option', __('投票选项不存在', 'aksrt'));
}
}
$records = get_post_meta($topic_id, '_aksrt_vote_records', true);
$records = is_array($records) ? $records : [];
$records[(string) $user_id] = $selected;
update_post_meta($topic_id, '_aksrt_vote_records', $records);
foreach ($options as &$option) {
if (in_array((string) ($option['id'] ?? ''), $selected, true)) {
$option['count'] = max(0, (int) ($option['count'] ?? 0)) + 1;
}
}
unset($option);
update_post_meta($topic_id, '_aksrt_vote_options', $options);
return [
'message' => __('投票成功', 'aksrt'),
'vote' => $this->get_vote_payload($topic_id, $user_id),
];
}
}
@@ -0,0 +1,376 @@
<?php
defined('ABSPATH') || exit;
trait AKSRT_Forum_Topic_Service_Part_2 {
/**
* Prepare vote meta from request data.
*/
public function prepare_vote_meta(array $data): array {
$enabled = !empty($data['vote_enabled']);
$title = sanitize_text_field((string) ($data['vote_title'] ?? ''));
$multiple = !empty($data['vote_multiple']);
$raw_options = $data['vote_options'] ?? $data['vote_options_text'] ?? [];
$labels = [];
if (is_array($raw_options)) {
foreach ($raw_options as $option) {
$option = sanitize_text_field((string) $option);
if ($option !== '') {
$labels[] = $option;
}
}
} else {
$lines = preg_split('/\r\n|\r|\n/', (string) $raw_options) ?: [];
foreach ($lines as $line) {
$line = sanitize_text_field((string) $line);
if ($line !== '') {
$labels[] = $line;
}
}
}
$labels = array_values(array_unique($labels));
$config = $this->forum->get_config();
$vote_max = (int) ($config['other']['vote_max'] ?? 8);
if ($vote_max > 0 && count($labels) > $vote_max) {
$labels = array_slice($labels, 0, $vote_max);
}
$vote_type = sanitize_key((string) ($data['vote_type'] ?? 'standard'));
if ($vote_type === 'pk' && count($labels) !== 2) {
$vote_type = 'standard';
}
if ($vote_type === 'pk') {
$multiple = false;
}
if (count($labels) < 2) {
$enabled = false;
}
$expiry_days = (int) ($data['vote_expiry_days'] ?? 0);
$options = [];
foreach ($labels as $index => $label) {
$options[] = [
'id' => 'option_' . ($index + 1),
'label' => $label,
'count' => 0,
];
}
return [
'enabled' => $enabled,
'title' => $title,
'multiple' => $multiple,
'vote_type' => $vote_type,
'expiry_days' => $expiry_days,
'options' => $options,
];
}
/**
* Save vote meta while preserving counts when possible.
*/
public function save_vote_meta(int $topic_id, array $vote_meta): void {
$existing_options = get_post_meta($topic_id, '_aksrt_vote_options', true);
$existing_options = is_array($existing_options) ? $existing_options : [];
$existing_by_label = [];
foreach ($existing_options as $existing_option) {
if (!empty($existing_option['label'])) {
$existing_by_label[(string) $existing_option['label']] = $existing_option;
}
}
$options = [];
foreach ($vote_meta['options'] as $index => $option) {
$label = (string) ($option['label'] ?? '');
if ($label === '') {
continue;
}
$existing = $existing_by_label[$label] ?? null;
$options[] = [
'id' => $existing['id'] ?? ('option_' . ($index + 1)),
'label' => $label,
'count' => (int) ($existing['count'] ?? 0),
];
}
update_post_meta($topic_id, '_aksrt_vote_enabled', !empty($vote_meta['enabled']) ? '1' : '0');
update_post_meta($topic_id, '_aksrt_vote_title', sanitize_text_field((string) ($vote_meta['title'] ?? '')));
update_post_meta($topic_id, '_aksrt_vote_multiple', !empty($vote_meta['multiple']) ? '1' : '0');
update_post_meta($topic_id, '_aksrt_vote_options', $options);
$vote_type = in_array($vote_meta['vote_type'] ?? '', ['standard', 'pk'], true) ? $vote_meta['vote_type'] : 'standard';
update_post_meta($topic_id, '_aksrt_vote_type', $vote_type);
$expiry_days = (int) ($vote_meta['expiry_days'] ?? 0);
if (!in_array($expiry_days, [0, 1, 7, 30], true)) {
$expiry_days = 0;
}
update_post_meta($topic_id, '_aksrt_vote_expiry_days', $expiry_days);
if ($expiry_days > 0 && !get_post_meta($topic_id, '_aksrt_vote_started_at', true)) {
update_post_meta($topic_id, '_aksrt_vote_started_at', current_time('mysql'));
}
if (empty($vote_meta['enabled'])) {
delete_post_meta($topic_id, '_aksrt_vote_records');
}
}
/**
* Get vote payload for a topic.
*/
public function get_vote_payload(int $topic_id, int $user_id = 0): array {
$enabled = get_post_meta($topic_id, '_aksrt_vote_enabled', true) === '1';
$options = get_post_meta($topic_id, '_aksrt_vote_options', true);
$options = is_array($options) ? $options : [];
$records = get_post_meta($topic_id, '_aksrt_vote_records', true);
$records = is_array($records) ? $records : [];
$vote_type = sanitize_key((string) get_post_meta($topic_id, '_aksrt_vote_type', true));
if (!in_array($vote_type, ['standard', 'pk'], true)) {
$vote_type = 'standard';
}
$expiry_days = (int) get_post_meta($topic_id, '_aksrt_vote_expiry_days', true);
$started_at = (string) get_post_meta($topic_id, '_aksrt_vote_started_at', true);
$expired = false;
if ($expiry_days > 0 && $started_at !== '') {
$expire_time = strtotime($started_at) + ($expiry_days * DAY_IN_SECONDS);
$expired = time() >= $expire_time;
}
$config = $this->forum->get_config();
$display_mode = $config['other']['vote_number_type'] ?? 'percent';
$selected = $user_id > 0 && isset($records[(string) $user_id]) && is_array($records[(string) $user_id])
? array_values(array_map('strval', $records[(string) $user_id]))
: [];
$total_votes = 0;
foreach ($options as &$option) {
$count = max(0, (int) ($option['count'] ?? 0));
$option['count'] = $count;
$option['selected'] = in_array((string) ($option['id'] ?? ''), $selected, true);
$total_votes += $count;
}
unset($option);
return [
'enabled' => $enabled && !empty($options),
'title' => (string) get_post_meta($topic_id, '_aksrt_vote_title', true),
'multiple' => get_post_meta($topic_id, '_aksrt_vote_multiple', true) === '1',
'vote_type' => $vote_type,
'expiry_days' => $expiry_days,
'expired' => $expired,
'display_mode' => $display_mode,
'options' => $options,
'total_votes' => $total_votes,
'has_voted' => !empty($selected),
'selected' => $selected,
];
}
// ================================================================
// Content processing
// ================================================================
/**
* Save governance meta fields from an editable payload.
*/
public function save_topic_governance_meta(int $topic_id, array $data): void {
if (isset($data['visibility'])) {
$visibility = sanitize_key((string) $data['visibility']);
if (isset($this->get_visibility_map()[$visibility])) {
update_post_meta($topic_id, '_aksrt_visibility', $visibility);
}
}
if (array_key_exists('special_topic', $data)) {
update_post_meta($topic_id, '_aksrt_special_topic', sanitize_text_field((string) $data['special_topic']));
}
if (array_key_exists('vote_enabled', $data) || array_key_exists('vote_options', $data) || array_key_exists('vote_options_text', $data)) {
$this->save_vote_meta($topic_id, $this->prepare_vote_meta($data));
}
}
public function save_cover_meta(int $post_id, array $data): void {
$cover_type = sanitize_key((string) ($data['cover_type'] ?? ''));
if (!in_array($cover_type, ['image', 'slide', 'video'], true)) {
delete_post_meta($post_id, '_aksrt_cover_type');
return;
}
update_post_meta($post_id, '_aksrt_cover_type', $cover_type);
if ($cover_type === 'image') {
$url = esc_url_raw((string) ($data['cover_image'] ?? ''));
update_post_meta($post_id, '_aksrt_cover_image', $url);
} elseif ($cover_type === 'slide') {
$raw = $data['featured_slide'] ?? '';
$slides = is_array($raw) ? $raw : json_decode((string) $raw, true);
if (!is_array($slides)) {
$slides = [];
}
$slides = array_values(array_filter(array_map('esc_url_raw', array_slice($slides, 0, 9))));
update_post_meta($post_id, '_aksrt_featured_slide', $slides);
} elseif ($cover_type === 'video') {
$url = esc_url_raw((string) ($data['featured_video'] ?? ''));
update_post_meta($post_id, '_aksrt_featured_video', $url);
}
}
/**
* Sanitize topic content based on user's compose capabilities.
*
* Strips <img>, <video>, <audio>, <iframe>, [aksrt_hide] blocks
* when the author lacks the corresponding permission.
*/
public function sanitize_topic_content(int $user_id, string $content): string {
$caps = $this->forum->get_compose_capabilities($user_id);
if (empty($caps['can_upload_image'])) {
$content = preg_replace('/<img[^>]*>/i', '', $content);
}
if (empty($caps['can_upload_video'])) {
$content = preg_replace('/<video[^>]*>.*?<\/video>/is', '', $content);
$content = preg_replace('/<audio[^>]*>.*?<\/audio>/is', '', $content);
}
if (empty($caps['can_iframe_video'])) {
$content = preg_replace('/<iframe[^>]*>.*?<\/iframe>/is', '', $content);
}
if (empty($caps['can_set_hide'])) {
$content = preg_replace('/\[aksrt_hide[^\]]*\](.*?)\[\/aksrt_hide\]/s', '$1', $content);
}
return $content;
}
/**
* Register the [aksrt_hide] shortcode for hidden content blocks.
*/
public static function register_hide_shortcode(): void {
add_shortcode('aksrt_hide', [self::class, 'render_hide_shortcode']);
}
/**
* Render [aksrt_hide] shortcode.
*
* Attributes:
* type — 'login' (default) | 'comment' | 'role' | 'credits'
* roles — comma-separated role slugs (for type=role)
* cost — int credits cost (for type=credits)
*/
public static function render_hide_shortcode($atts, $content = ''): string {
$atts = shortcode_atts([
'type' => 'login',
'roles' => '',
'cost' => 0,
], $atts, 'aksrt_hide');
$type = sanitize_key($atts['type']);
$user_id = get_current_user_id();
$visible = false;
$message = '';
switch ($type) {
case 'login':
$visible = $user_id > 0;
if (!$visible) {
$message = __('此内容需要登录后才能查看。', 'aksrt');
}
break;
case 'comment':
if ($user_id <= 0) {
$message = __('登录并评论后可查看隐藏内容。', 'aksrt');
} else {
$post_id = get_the_ID();
if ($post_id && function_exists('get_comments')) {
$has_commented = (int) get_comments([
'post_id' => $post_id,
'user_id' => $user_id,
'count' => true,
'status' => 'approve',
]) > 0;
$visible = $has_commented;
}
if (!$visible) {
$message = __('评论后可查看隐藏内容。', 'aksrt');
}
}
break;
case 'role':
if ($user_id <= 0) {
$message = __('此内容仅特定角色可见。', 'aksrt');
} else {
$allowed = array_filter(array_map('trim', explode(',', (string) $atts['roles'])));
if (empty($allowed)) {
$visible = true;
} else {
$user = get_userdata($user_id);
$visible = $user && !empty(array_intersect($user->roles, $allowed));
}
if (!$visible) {
$message = __('此内容仅限特定用户角色查看。', 'aksrt');
}
}
break;
case 'credits':
$cost = max(0, (int) $atts['cost']);
if ($user_id <= 0) {
$message = sprintf(__('此内容需要 %d 积分解锁。请先登录。', 'aksrt'), $cost);
} elseif ($cost <= 0) {
$visible = true;
} else {
$balance = class_exists('AKSRT_Credits') ? AKSRT_Credits::get_balance($user_id) : 0;
$visible = $balance >= $cost;
if (!$visible) {
$message = sprintf(__('此内容需要 %d 积分解锁(当前余额:%d)。', 'aksrt'), $cost, (int) $balance);
}
}
break;
default:
$visible = $user_id > 0;
if (!$visible) {
$message = __('此内容需要登录后才能查看。', 'aksrt');
}
}
if ($visible) {
return '<div class="aksrt-forum-hide aksrt-forum-hide--revealed">' . do_shortcode($content) . '</div>';
}
return '<div class="aksrt-forum-hide aksrt-forum-hide--locked">'
. '<div class="aksrt-forum-hide__message">'
. '<i class="fas fa-lock"></i> '
. esc_html($message)
. '</div></div>';
}
/**
* Split a tag string.
*/
public function split_tags(string $tags): array {
$parts = preg_split('/[,,]/', $tags) ?: [];
$clean = [];
foreach ($parts as $part) {
$part = sanitize_text_field(trim($part));
if ($part !== '') {
$clean[] = $part;
}
}
return array_values(array_unique($clean));
}
}
@@ -0,0 +1,440 @@
<?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_unlocked_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 (!$deducted) {
return $this->forum->error(__('积分扣除失败', 'aksrt'));
}
$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_unique($unlocked));
$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'));
}
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, $unlocked, true);
}
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;
}
}
@@ -0,0 +1,266 @@
<?php
defined('ABSPATH') || exit;
trait AKSRT_Forum_Topic_Service_Part_4 {
/**
* Get review options.
*/
public function get_review_options(): array {
$options = [];
foreach ($this->get_review_map() as $value => $label) {
$options[] = ['label' => $label, 'value' => $value];
}
return $options;
}
/**
* Check whether a user can publish directly.
*/
public function can_user_publish_directly(int $user_id, int $board_id = 0): bool {
if ($user_id <= 0) {
return false;
}
if (user_can($user_id, 'manage_options')) {
return true;
}
if ($this->forum->is_forum_admin($user_id)) {
return true;
}
if ($board_id > 0 && $this->forum->is_board_moderator($user_id, $board_id)) {
return true;
}
return false;
}
// ── AJAX edit (extracted from facade) ──
public function ajax_edit_topic(): void {
check_ajax_referer('aksrt_forum_compose', '_nonce');
$user_id = get_current_user_id();
$topic_id = (int) ($_POST['topic_id'] ?? 0);
if ($user_id <= 0) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
}
if (!$this->forum->can_edit_topic($user_id, $topic_id)) {
wp_send_json_error(['message' => __('无编辑权限', 'aksrt')]);
}
$title = sanitize_text_field((string) ($_POST['title'] ?? ''));
$content = wp_kses_post((string) ($_POST['content'] ?? ''));
if ($title === '' || $content === '') {
wp_send_json_error(['message' => __('标题和内容不能为空', 'aksrt')]);
}
$result = wp_update_post([
'ID' => $topic_id,
'post_title' => $title,
'post_content' => $content,
], true);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message()]);
}
$tags = $this->split_tags((string) ($_POST['tags'] ?? ''));
$clean_tags = array_slice(array_values(array_filter(array_map('sanitize_text_field', $tags))), 0, 5);
wp_set_object_terms($topic_id, $clean_tags, 'aksrt_forum_tag');
if (isset($_POST['special_topic'])) {
update_post_meta($topic_id, '_aksrt_special_topic', sanitize_text_field((string) $_POST['special_topic']));
}
if (isset($_POST['topic_cat']) && taxonomy_exists('aksrt_forum_topic_cat')) {
$topic_cat = sanitize_text_field((string) $_POST['topic_cat']);
wp_set_object_terms($topic_id, $topic_cat !== '' ? [$topic_cat] : [], 'aksrt_forum_topic_cat');
}
$caps = $this->forum->get_compose_capabilities($user_id);
if ($caps['can_set_visibility'] && isset($_POST['visibility'])) {
$visibility = sanitize_key((string) $_POST['visibility']);
if (isset($this->get_visibility_map()[$visibility])) {
update_post_meta($topic_id, '_aksrt_visibility', $visibility);
if ($visibility === self::VISIBILITY_CREDITS) {
update_post_meta($topic_id, '_aksrt_credits_cost', max(0, (int) ($_POST['credits_cost'] ?? 0)));
}
if ($visibility === self::VISIBILITY_PAY) {
update_post_meta($topic_id, '_aksrt_pay_price', sanitize_text_field((string) ($_POST['pay_price'] ?? '')));
}
if ($visibility === self::VISIBILITY_ROLE) {
$roles = isset($_POST['visibility_roles']) && is_array($_POST['visibility_roles'])
? array_map('sanitize_key', $_POST['visibility_roles']) : [];
update_post_meta($topic_id, '_aksrt_visibility_roles', $roles);
}
}
}
if ($caps['can_set_cover']) {
$this->save_cover_meta($topic_id, [
'cover_type' => sanitize_key((string) ($_POST['cover_type'] ?? '')),
'cover_image' => esc_url_raw((string) ($_POST['cover_image'] ?? '')),
'featured_slide' => (string) ($_POST['featured_slide'] ?? ''),
'featured_video' => esc_url_raw((string) ($_POST['featured_video'] ?? '')),
]);
}
if ($caps['can_set_vote'] && isset($_POST['vote_enabled'])) {
$vote_data = [
'vote_enabled' => !empty($_POST['vote_enabled']),
'vote_title' => sanitize_text_field((string) ($_POST['vote_title'] ?? '')),
'vote_options_text' => sanitize_textarea_field((string) ($_POST['vote_options'] ?? '')),
'vote_multiple' => !empty($_POST['vote_multiple']),
'vote_type' => sanitize_key((string) ($_POST['vote_type'] ?? 'standard')),
'vote_expiry_days' => (int) ($_POST['vote_expiry_days'] ?? 0),
];
$vote_meta = $this->prepare_vote_meta($vote_data);
$this->save_vote_meta($topic_id, $vote_meta);
}
wp_send_json_success([
'message' => __('帖子已更新', 'aksrt'),
'redirect' => get_permalink($topic_id),
]);
}
public function can_edit_topic(int $user_id, int $topic_id): bool {
if ($user_id <= 0 || $topic_id <= 0) {
return false;
}
// Moderators and above can always edit
if ($this->forum->forum_user_can($user_id, 'topic_edit_other', $topic_id)) {
return true;
}
// Authors can edit their own unlocked topics
$post = get_post($topic_id);
if (!$post || $post->post_type !== 'aksrt_forum_topic') {
return false;
}
if ((int) $post->post_author !== $user_id) {
return false;
}
return get_post_meta($topic_id, '_aksrt_locked', true) !== '1';
}
public function update_topic_meta(int $topic_id, array $meta): bool {
if ($topic_id <= 0) {
return false;
}
if (!$this->forum->can_moderate_topic(get_current_user_id(), $topic_id) && !current_user_can('manage_options')) {
return false;
}
$allowed_keys = ['_aksrt_sticky', '_aksrt_featured', '_aksrt_locked', '_aksrt_highlight'];
foreach ($meta as $key => $value) {
if (!in_array($key, $allowed_keys, true)) {
continue;
}
update_post_meta($topic_id, $key, !empty($value) ? '1' : '0');
}
return true;
}
public function ajax_update_topic(): void {
$user_id = get_current_user_id();
$topic_id = (int) ($_POST['topic_id'] ?? 0);
if ($topic_id <= 0 || !$this->forum->forum_user_can($user_id, 'topic_moderate', $topic_id)) {
wp_send_json_error(['message' => __('权限不足', 'aksrt')]);
}
$meta = [];
if (isset($_POST['sticky'])) {
$meta['_aksrt_sticky'] = !empty($_POST['sticky']);
}
if (isset($_POST['featured'])) {
$meta['_aksrt_featured'] = !empty($_POST['featured']);
}
if (isset($_POST['locked'])) {
$meta['_aksrt_locked'] = !empty($_POST['locked']);
}
if ($this->update_topic_meta($topic_id, $meta)) {
wp_send_json_success(['message' => __('已更新', 'aksrt')]);
}
wp_send_json_error(['message' => __('更新失败', 'aksrt')]);
}
public function ajax_create_topic(): void {
check_ajax_referer('aksrt_forum_compose', '_nonce');
$user_id = get_current_user_id();
if ($user_id <= 0) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
}
$data = [
'board_id' => (int) ($_POST['board_id'] ?? 0),
'title' => sanitize_text_field((string) ($_POST['title'] ?? '')),
'content' => wp_kses_post((string) ($_POST['content'] ?? '')),
'tags' => $this->split_tags((string) ($_POST['tags'] ?? '')),
'special_topic' => sanitize_text_field((string) ($_POST['special_topic'] ?? '')),
'visibility' => sanitize_key((string) ($_POST['visibility'] ?? self::VISIBILITY_PUBLIC)),
'bbs_type' => sanitize_key((string) ($_POST['bbs_type'] ?? self::BBS_TYPE_STANDARD)),
'topic_cat' => sanitize_text_field((string) ($_POST['topic_cat'] ?? '')),
'vote_enabled' => !empty($_POST['vote_enabled']),
'vote_title' => sanitize_text_field((string) ($_POST['vote_title'] ?? '')),
'vote_options_text' => sanitize_textarea_field((string) ($_POST['vote_options'] ?? '')),
'vote_multiple' => !empty($_POST['vote_multiple']),
'cover_type' => sanitize_key((string) ($_POST['cover_type'] ?? '')),
'cover_image' => esc_url_raw((string) ($_POST['cover_image'] ?? '')),
'featured_slide' => (string) ($_POST['featured_slide'] ?? ''),
'featured_video' => esc_url_raw((string) ($_POST['featured_video'] ?? '')),
'credits_cost' => (int) ($_POST['credits_cost'] ?? 0),
'pay_price' => sanitize_text_field((string) ($_POST['pay_price'] ?? '')),
'visibility_roles' => isset($_POST['visibility_roles']) && is_array($_POST['visibility_roles'])
? array_map('sanitize_key', $_POST['visibility_roles']) : [],
];
$draft_id = (int) ($_POST['draft_id'] ?? 0);
if ($draft_id > 0) {
$result = $this->forum->publish_draft($user_id, $draft_id, $data);
} else {
$result = $this->create_topic($user_id, $data);
}
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message()]);
}
wp_send_json_success([
'message' => (string) ($result['message'] ?? __('主题已提交', 'aksrt')),
'post_id' => (int) ($result['post_id'] ?? 0),
'redirect' => (string) ($result['redirect'] ?? home_url('/forums/')),
]);
}
public function ajax_unlock_topic(): void {
check_ajax_referer('aksrt_forum_action', '_nonce');
$user_id = get_current_user_id();
if ($user_id <= 0) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
}
$topic_id = (int) ($_POST['topic_id'] ?? 0);
if ($topic_id <= 0) {
wp_send_json_error(['message' => __('主题不存在', 'aksrt')]);
}
$result = $this->unlock_topic_with_credits($user_id, $topic_id);
if (!empty($result['success'])) {
wp_send_json_success($result);
} else {
wp_send_json_error(['message' => $result['message'] ?? __('解锁失败', 'aksrt')]);
}
}
}