Files
wordpress-theme-aksrt/src/AKSRT/Forum/TopicService.php
T
kate522 24330653f0 feat(ux): forum pay-hide depth, clogin OAuth, ban appeals admin, posts-navs widget
- Forum compose/save/draft: VIP prices, partial hide, coupons + shortcode tools
- Rainbow clogin aggregate login (rewrite + schema + CLoginClient)
- Native ban appeals tab: approve/reject + lift ban + notify
- WidgetPostsNavs + register missing forum widgets
- Submit editor shortcodes: login/reply/vip/payshow
2026-07-18 23:31:57 +08:00

276 lines
10 KiB
PHP

<?php
/**
* @package AKSRT
*/
namespace AKSRT\Forum;
defined('ABSPATH') || exit;
class TopicService {
use TopicServicePart1;
use TopicServicePart2;
use TopicServicePart3;
use TopicServicePart4;
private Forum $forum;
public function __construct(Forum $forum) {
$this->forum = $forum;
}
// ================================================================
// Topic CRUD
// ================================================================
/**
* Update topic content and editable governance metadata.
*
* @return bool|\WP_Error
*/
public function update_topic(int $topic_id, array $data) {
$post = get_post($topic_id);
if (!$post || $post->post_type !== 'aksrt_forum_topic') {
return new \WP_Error('invalid_topic', __('帖子不存在', 'aksrt'));
}
$update = ['ID' => $topic_id];
if (isset($data['title'])) {
$title = sanitize_text_field((string) $data['title']);
if ($title === '') {
return new \WP_Error('empty_title', __('标题不能为空', 'aksrt'));
}
$update['post_title'] = $title;
}
if (isset($data['content'])) {
$content = wp_kses_post((string) $data['content']);
if ($content === '') {
return new \WP_Error('empty_content', __('内容不能为空', 'aksrt'));
}
$update['post_content'] = $content;
}
if (isset($data['excerpt'])) {
$update['post_excerpt'] = sanitize_textarea_field((string) $data['excerpt']);
}
$result = wp_update_post($update, true);
if (is_wp_error($result)) {
return $result;
}
if (isset($data['tags']) && is_array($data['tags'])) {
$tags = array_values(array_filter(array_map('sanitize_text_field', $data['tags'])));
wp_set_object_terms($topic_id, $tags, 'aksrt_forum_tag');
}
$this->save_topic_governance_meta($topic_id, $data);
return true;
}
/**
* Create topic.
*
* @return array<string,mixed>|\WP_Error
*/
public function create_topic(int $user_id, array $data) {
if ($user_id <= 0) {
return new \WP_Error('not_logged_in', __('请先登录', 'aksrt'));
}
$board_id = (int) ($data['board_id'] ?? 0);
$title = sanitize_text_field((string) ($data['title'] ?? ''));
$content = wp_kses_post((string) ($data['content'] ?? ''));
$visibility = sanitize_key((string) ($data['visibility'] ?? Forum::VISIBILITY_PUBLIC));
$special_topic = sanitize_text_field((string) ($data['special_topic'] ?? ''));
$tags = is_array($data['tags'] ?? null) ? $data['tags'] : [];
$bbs_type = sanitize_key((string) ($data['bbs_type'] ?? Forum::BBS_TYPE_STANDARD));
if (!in_array($bbs_type, [Forum::BBS_TYPE_STANDARD, Forum::BBS_TYPE_QUESTION], true)) {
$bbs_type = Forum::BBS_TYPE_STANDARD;
}
if ($board_id <= 0 || $title === '' || $content === '') {
return new \WP_Error('invalid_payload', __('请填写所有必填项', 'aksrt'));
}
// §7.3: Strip unauthorized media from content based on compose capabilities
$content = $this->sanitize_topic_content($user_id, $content);
$board_ok = $this->assert_board($board_id);
if (is_wp_error($board_ok)) {
return $board_ok;
}
if (!isset($this->get_visibility_map()[$visibility])) {
$visibility = Forum::VISIBILITY_PUBLIC;
}
$can_publish = $this->can_user_publish_directly($user_id, $board_id);
$post_status = $can_publish ? 'publish' : 'pending';
$review_status = $can_publish ? Forum::REVIEW_APPROVED : Forum::REVIEW_PENDING;
$post_id = wp_insert_post([
'post_title' => $title,
'post_content' => $content,
'post_status' => $post_status,
'post_author' => $user_id,
'post_type' => 'aksrt_forum_topic',
], true);
if (is_wp_error($post_id)) {
return $post_id;
}
$this->attach_topic_to_board((int) $post_id, $board_id);
$clean_tags = array_values(array_filter(array_map('sanitize_text_field', $tags)));
if (count($clean_tags) > 5) {
$clean_tags = array_slice($clean_tags, 0, 5);
}
if (!empty($clean_tags)) {
wp_set_object_terms($post_id, $clean_tags, 'aksrt_forum_tag');
}
$topic_cat = sanitize_text_field((string) ($data['topic_cat'] ?? ''));
if ($topic_cat !== '' && taxonomy_exists('aksrt_forum_topic_cat')) {
wp_set_object_terms($post_id, [$topic_cat], 'aksrt_forum_topic_cat');
}
update_post_meta($post_id, '_aksrt_visibility', $visibility);
update_post_meta($post_id, '_aksrt_review_status', $review_status);
if ($visibility === Forum::VISIBILITY_CREDITS) {
$credits_cost = max(0, (int) ($data['credits_cost'] ?? 0));
update_post_meta($post_id, '_aksrt_credits_cost', $credits_cost);
update_post_meta($post_id, '_aksrt_pay_credits', $credits_cost);
if (isset($data['credits_vip1']) && (string) $data['credits_vip1'] !== '') {
update_post_meta($post_id, '_aksrt_credits_vip1', max(0, (int) $data['credits_vip1']));
}
if (isset($data['credits_vip2']) && (string) $data['credits_vip2'] !== '') {
update_post_meta($post_id, '_aksrt_credits_vip2', max(0, (int) $data['credits_vip2']));
}
update_post_meta($post_id, '_aksrt_pay_hide_part', !empty($data['pay_hide_part']) ? 1 : 0);
}
if ($visibility === Forum::VISIBILITY_PAY) {
$pay_price = sanitize_text_field((string) ($data['pay_price'] ?? ''));
update_post_meta($post_id, '_aksrt_pay_price', $pay_price);
if (isset($data['pay_vip1_price']) && (string) $data['pay_vip1_price'] !== '') {
$v1 = max(0, (float) $data['pay_vip1_price']);
update_post_meta($post_id, '_aksrt_pay_vip1_price', $v1);
update_post_meta($post_id, '_aksrt_pay_vip_price', $v1);
}
if (isset($data['pay_vip2_price']) && (string) $data['pay_vip2_price'] !== '') {
update_post_meta($post_id, '_aksrt_pay_vip2_price', max(0, (float) $data['pay_vip2_price']));
}
update_post_meta($post_id, '_aksrt_pay_hide_part', !empty($data['pay_hide_part']) ? 1 : 0);
update_post_meta($post_id, '_aksrt_topic_coupon_s', !empty($data['topic_coupon_s']) ? 1 : 0);
}
if ($visibility === Forum::VISIBILITY_ROLE) {
$roles = isset($data['visibility_roles']) && is_array($data['visibility_roles'])
? array_map('sanitize_key', $data['visibility_roles']) : [];
update_post_meta($post_id, '_aksrt_visibility_roles', $roles);
}
update_post_meta($post_id, '_aksrt_special_topic', $special_topic);
// 置顶分级 0~3(§7.3):新建主题默认不置顶。
update_post_meta($post_id, '_aksrt_sticky', Forum::STICKY_NONE);
update_post_meta($post_id, '_aksrt_featured', '0');
update_post_meta($post_id, '_aksrt_locked', '0');
update_post_meta($post_id, '_aksrt_views', 0);
update_post_meta($post_id, '_aksrt_last_reply', (string) get_post_field('post_date', $post_id));
update_post_meta($post_id, '_aksrt_bbs_type', $bbs_type);
if ($bbs_type === Forum::BBS_TYPE_QUESTION) {
update_post_meta($post_id, '_aksrt_question_status', Forum::QUESTION_UNSOLVED);
}
$this->save_cover_meta($post_id, $data);
$vote_meta = $this->prepare_vote_meta($data);
$this->save_vote_meta($post_id, $vote_meta);
if ($post_status === 'publish') {
$this->forum->sync_board_count($board_id);
}
if (class_exists('\AKSRT\Credits\Earn') && $post_status === 'publish') {
do_action('aksrt_forum_topic_created', $user_id, $post_id);
}
if ($post_status === 'pending') {
$this->forum->notify_topic_pending($post_id, $user_id);
}
return [
'post_id' => $post_id,
'message' => $post_status === 'publish'
? __('主题已发布', 'aksrt')
: __('主题已提交审核,审核通过后对外展示', 'aksrt'),
'redirect' => $post_status === 'publish'
? get_permalink($post_id)
: add_query_arg('compose_status', 'pending', aksrt_get_forum_home_url()),
];
}
// ================================================================
// Board / plate attachment helpers
// ================================================================
/**
* Validate board_id as plate CPT post ID when plate is active, else taxonomy term.
*
* @return true|\WP_Error
*/
private function assert_board(int $board_id, string $error_message = '') {
if ($error_message === '') {
$error_message = __('版块不存在', 'aksrt');
}
if ($board_id <= 0) {
return new \WP_Error('invalid_board', $error_message);
}
if ($this->forum->is_plate_cpt_active()) {
$post = get_post($board_id);
if (!$post || $post->post_type !== 'aksrt_forum_plate' || $post->post_status !== 'publish') {
return new \WP_Error('invalid_board', $error_message);
}
return true;
}
$board = get_term($board_id, 'aksrt_forum_board');
if (!$board || is_wp_error($board)) {
return new \WP_Error('invalid_board', $error_message);
}
return true;
}
/**
* Attach a topic to a board.
* Plate CPT active: store `_aksrt_plate_id` only (native association).
* Otherwise: taxonomy term on aksrt_forum_board.
*/
private function attach_topic_to_board(int $topic_id, int $board_id): void {
if ($topic_id <= 0 || $board_id <= 0) {
return;
}
if ($this->forum->is_plate_cpt_active()) {
update_post_meta($topic_id, '_aksrt_plate_id', $board_id);
return;
}
if (taxonomy_exists('aksrt_forum_board')) {
wp_set_object_terms($topic_id, [$board_id], 'aksrt_forum_board');
}
}
// ================================================================
// Topic moderation
// ================================================================
}