Files
wordpress-theme-aksrt/inc/classes/forum/class-aksrt-forum-draft-service.php
T
kate522 780c8cb4f7 feat(forum): frontend 1:1 list/tabs/urls/enqueue
Wire Zibll-style board sort tabs, shared topic rows, page-template
URLs, forum asset enqueue on home/plate, home synthesis load-more,
and topic last_reply meta for last-reply ordering. No dual-read.
2026-07-12 08:38:31 +08:00

411 lines
17 KiB
PHP

<?php
/**
* Forum draft management service.
*
* Extracted from AKSRT_Forum to isolate draft CRUD, loading,
* and AJAX handlers into a cohesive unit.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Forum_Draft_Service {
/** @var AKSRT_Forum */
private $forum;
public function __construct(AKSRT_Forum $forum) {
$this->forum = $forum;
}
public function save_draft(int $user_id, array $data) {
if ($user_id <= 0) {
return new WP_Error('not_logged_in', __('请先登录', 'aksrt'));
}
$draft_id = (int) ($data['draft_id'] ?? 0);
$post_arr = [
'post_type' => 'aksrt_forum_topic',
'post_status' => 'draft',
'post_author' => $user_id,
'post_title' => sanitize_text_field((string) ($data['title'] ?? '')),
'post_content' => wp_kses_post((string) ($data['content'] ?? '')),
];
if ($draft_id > 0) {
$existing = get_post($draft_id);
if (!$existing || $existing->post_type !== 'aksrt_forum_topic'
|| (int) $existing->post_author !== $user_id
|| $existing->post_status !== 'draft') {
return new WP_Error('invalid_draft', __('草稿不存在或无权编辑', 'aksrt'));
}
$post_arr['ID'] = $draft_id;
$result = wp_update_post($post_arr, true);
} else {
$result = wp_insert_post($post_arr, true);
}
if (is_wp_error($result)) {
return $result;
}
$post_id = (int) $result;
$board_id = (int) ($data['board_id'] ?? 0);
if ($board_id > 0) {
if ($this->forum->is_plate_cpt_active()) {
update_post_meta($post_id, '_aksrt_plate_id', $board_id);
} else {
wp_set_object_terms($post_id, [$board_id], 'aksrt_forum_board');
}
}
$tags = is_array($data['tags'] ?? null) ? $data['tags'] : [];
if (!empty($tags)) {
$clean_tags = array_slice(array_values(array_filter(array_map('sanitize_text_field', $tags))), 0, 5);
wp_set_object_terms($post_id, $clean_tags, 'aksrt_forum_tag');
}
$meta_fields = [
'visibility' => '_aksrt_visibility',
'bbs_type' => '_aksrt_bbs_type',
'special_topic' => '_aksrt_special_topic',
'cover_type' => '_aksrt_cover_type',
];
foreach ($meta_fields as $key => $meta_key) {
if (isset($data[$key])) {
update_post_meta($post_id, $meta_key, sanitize_key((string) $data[$key]));
}
}
if (isset($data['credits_cost'])) {
update_post_meta($post_id, '_aksrt_credits_cost', max(0, (int) $data['credits_cost']));
}
if (isset($data['pay_price'])) {
update_post_meta($post_id, '_aksrt_pay_price', sanitize_text_field((string) $data['pay_price']));
}
if (isset($data['visibility_roles']) && is_array($data['visibility_roles'])) {
update_post_meta($post_id, '_aksrt_visibility_roles', array_map('sanitize_key', $data['visibility_roles']));
}
$this->forum->save_cover_meta($post_id, $data);
if (!empty($data['vote_enabled'])) {
$vote_meta = $this->forum->prepare_vote_meta($data);
$this->forum->save_vote_meta($post_id, $vote_meta);
}
if (isset($data['topic_cat'])) {
$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');
}
}
return [
'post_id' => $post_id,
'message' => __('草稿已保存', 'aksrt'),
];
}
public function get_user_draft_count(int $user_id): int {
if ($user_id <= 0) {
return 0;
}
global $wpdb;
return (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'aksrt_forum_topic' AND post_status = 'draft' AND post_author = %d",
$user_id
));
}
/**
* @return array<int,array{id:int,title:string,date:string,board_name:string}>
*/
public function get_user_drafts(int $user_id, int $limit = 20): array {
if ($user_id <= 0) {
return [];
}
$query = new WP_Query([
'post_type' => 'aksrt_forum_topic',
'post_status' => 'draft',
'author' => $user_id,
'posts_per_page' => $limit,
'orderby' => 'modified',
'order' => 'DESC',
'no_found_rows' => true,
]);
$drafts = [];
while ($query->have_posts()) {
$query->the_post();
$post = get_post();
if (!$post) {
continue;
}
$board_name = '';
if ($this->forum->is_plate_cpt_active()) {
$plate_id = (int) get_post_meta($post->ID, '_aksrt_plate_id', true);
if ($plate_id > 0) {
$board_name = get_the_title($plate_id);
}
} else {
$terms = wp_get_post_terms($post->ID, 'aksrt_forum_board', ['fields' => 'names']);
if (!is_wp_error($terms) && !empty($terms)) {
$board_name = $terms[0];
}
}
$drafts[] = [
'id' => $post->ID,
'title' => $post->post_title ?: __('(无标题)', 'aksrt'),
'date' => $post->post_modified,
'board_name' => $board_name,
];
}
wp_reset_postdata();
return $drafts;
}
public function delete_draft(int $user_id, int $draft_id): bool {
if ($user_id <= 0 || $draft_id <= 0) {
return false;
}
$post = get_post($draft_id);
if (!$post || $post->post_type !== 'aksrt_forum_topic'
|| (int) $post->post_author !== $user_id
|| $post->post_status !== 'draft') {
return false;
}
return (bool) wp_delete_post($draft_id, true);
}
/**
* Load a draft's full data for the compose form.
*/
public function load_draft_data(int $user_id, int $draft_id): ?array {
if ($user_id <= 0 || $draft_id <= 0) {
return null;
}
$post = get_post($draft_id);
if (!$post || $post->post_type !== 'aksrt_forum_topic'
|| (int) $post->post_author !== $user_id
|| $post->post_status !== 'draft') {
return null;
}
$board_id = 0;
if ($this->forum->is_plate_cpt_active()) {
$board_id = (int) get_post_meta($draft_id, '_aksrt_plate_id', true);
} else {
$terms = wp_get_post_terms($draft_id, 'aksrt_forum_board', ['fields' => 'ids']);
if (!is_wp_error($terms) && !empty($terms)) {
$board_id = (int) $terms[0];
}
}
$tags = wp_get_post_terms($draft_id, 'aksrt_forum_tag', ['fields' => 'names']);
if (is_wp_error($tags)) {
$tags = [];
}
$topic_cat = '';
if (taxonomy_exists('aksrt_forum_topic_cat')) {
$cats = wp_get_post_terms($draft_id, 'aksrt_forum_topic_cat', ['fields' => 'names']);
if (!is_wp_error($cats) && !empty($cats)) {
$topic_cat = $cats[0];
}
}
$slide_raw = get_post_meta($draft_id, '_aksrt_featured_slide', true);
return [
'draft_id' => $draft_id,
'title' => $post->post_title,
'content' => $post->post_content,
'board_id' => $board_id,
'tags' => implode(', ', $tags),
'visibility' => get_post_meta($draft_id, '_aksrt_visibility', true) ?: AKSRT_Forum::VISIBILITY_PUBLIC,
'bbs_type' => get_post_meta($draft_id, '_aksrt_bbs_type', true) ?: AKSRT_Forum::BBS_TYPE_STANDARD,
'special_topic' => get_post_meta($draft_id, '_aksrt_special_topic', true) ?: '',
'cover_type' => get_post_meta($draft_id, '_aksrt_cover_type', true) ?: '',
'cover_image' => get_post_meta($draft_id, '_aksrt_cover_image', true) ?: '',
'featured_slide' => is_array($slide_raw) ? $slide_raw : [],
'featured_video' => get_post_meta($draft_id, '_aksrt_featured_video', true) ?: '',
'credits_cost' => (int) get_post_meta($draft_id, '_aksrt_credits_cost', true),
'pay_price' => get_post_meta($draft_id, '_aksrt_pay_price', true) ?: '',
'visibility_roles' => get_post_meta($draft_id, '_aksrt_visibility_roles', true) ?: [],
'topic_cat' => $topic_cat,
'vote_enabled' => get_post_meta($draft_id, '_aksrt_vote_enabled', true) === '1',
'vote_title' => get_post_meta($draft_id, '_aksrt_vote_title', true) ?: '',
'vote_multiple' => get_post_meta($draft_id, '_aksrt_vote_multiple', true) === '1',
'vote_type' => get_post_meta($draft_id, '_aksrt_vote_type', true) ?: 'standard',
'vote_expiry_days' => (int) get_post_meta($draft_id, '_aksrt_vote_expiry_days', true),
'vote_options' => $this->get_vote_options_text($draft_id),
];
}
private function get_vote_options_text(int $post_id): string {
$options = get_post_meta($post_id, '_aksrt_vote_options', true);
if (!is_array($options)) {
return '';
}
return implode("\n", array_column($options, 'label'));
}
// ──────────────────────────────────────────────────────────
// AJAX handlers
// ──────────────────────────────────────────────────────────
public function ajax_save_draft(): 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 = [
'draft_id' => (int) ($_POST['draft_id'] ?? 0),
'board_id' => (int) ($_POST['board_id'] ?? 0),
'title' => sanitize_text_field((string) ($_POST['title'] ?? '')),
'content' => wp_kses_post((string) ($_POST['content'] ?? '')),
'tags' => $this->forum->split_tags((string) ($_POST['tags'] ?? '')),
'special_topic' => sanitize_text_field((string) ($_POST['special_topic'] ?? '')),
'visibility' => sanitize_key((string) ($_POST['visibility'] ?? AKSRT_Forum::VISIBILITY_PUBLIC)),
'bbs_type' => sanitize_key((string) ($_POST['bbs_type'] ?? AKSRT_Forum::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']),
'vote_type' => sanitize_key((string) ($_POST['vote_type'] ?? 'standard')),
'vote_expiry_days' => (int) ($_POST['vote_expiry_days'] ?? 0),
'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']) : [],
];
$result = $this->save_draft($user_id, $data);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message()]);
}
wp_send_json_success($result);
}
public function ajax_load_draft(): void {
check_ajax_referer('aksrt_forum_compose', '_nonce');
$user_id = get_current_user_id();
$draft_id = (int) ($_GET['draft_id'] ?? $_POST['draft_id'] ?? 0);
if ($user_id <= 0) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
}
$data = $this->load_draft_data($user_id, $draft_id);
if (!$data) {
wp_send_json_error(['message' => __('草稿不存在', 'aksrt')]);
}
wp_send_json_success($data);
}
public function ajax_delete_draft(): void {
check_ajax_referer('aksrt_forum_compose', '_nonce');
$user_id = get_current_user_id();
$draft_id = (int) ($_POST['draft_id'] ?? 0);
if ($user_id <= 0) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
}
if (!$this->delete_draft($user_id, $draft_id)) {
wp_send_json_error(['message' => __('删除失败', 'aksrt')]);
}
wp_send_json_success(['message' => __('草稿已删除', 'aksrt')]);
}
// ──────────────────────────────────────────────────────────
// Publish draft (transition from draft → publish/pending)
// ──────────────────────────────────────────────────────────
public function publish_draft(int $user_id, int $draft_id, array $data) {
if ($user_id <= 0) {
return new WP_Error('not_logged_in', __('请先登录', 'aksrt'));
}
$post = get_post($draft_id);
if (!$post || $post->post_type !== 'aksrt_forum_topic'
|| (int) $post->post_author !== $user_id
|| $post->post_status !== 'draft') {
return new WP_Error('invalid_draft', __('草稿不存在', 'aksrt'));
}
$data['draft_id'] = $draft_id;
$save_result = $this->save_draft($user_id, $data);
if (is_wp_error($save_result)) {
return $save_result;
}
$board_id = (int) ($data['board_id'] ?? 0);
$title = sanitize_text_field((string) ($data['title'] ?? ''));
$content = wp_kses_post((string) ($data['content'] ?? ''));
if ($board_id <= 0 || $title === '' || $content === '') {
return new WP_Error('invalid_payload', __('请填写版块、标题和内容', 'aksrt'));
}
$can_publish = $this->forum->can_user_publish_directly($user_id, $board_id);
$post_status = $can_publish ? 'publish' : 'pending';
$review_status = $can_publish ? AKSRT_Forum::REVIEW_APPROVED : AKSRT_Forum::REVIEW_PENDING;
wp_update_post([
'ID' => $draft_id,
'post_status' => $post_status,
]);
update_post_meta($draft_id, '_aksrt_review_status', $review_status);
update_post_meta($draft_id, '_aksrt_sticky', AKSRT_Forum::STICKY_NONE);
update_post_meta($draft_id, '_aksrt_featured', '0');
update_post_meta($draft_id, '_aksrt_locked', '0');
update_post_meta($draft_id, '_aksrt_views', 0);
update_post_meta($draft_id, '_aksrt_last_reply', (string) get_post_field('post_date', $draft_id));
$bbs_type = get_post_meta($draft_id, '_aksrt_bbs_type', true) ?: AKSRT_Forum::BBS_TYPE_STANDARD;
if ($bbs_type === AKSRT_Forum::BBS_TYPE_QUESTION) {
update_post_meta($draft_id, '_aksrt_question_status', AKSRT_Forum::QUESTION_UNSOLVED);
}
if ($post_status === 'publish') {
$this->forum->sync_board_count($board_id);
if (class_exists('AKSRT_Credits_Earn')) {
do_action('aksrt_forum_topic_created', $user_id, $draft_id);
}
}
if ($post_status === 'pending') {
$this->forum->notify_topic_pending($draft_id, $user_id);
}
return [
'post_id' => $draft_id,
'message' => $post_status === 'publish'
? __('主题已发布', 'aksrt')
: __('主题已提交审核,审核通过后对外展示', 'aksrt'),
'redirect' => $post_status === 'publish'
? get_permalink($draft_id)
: add_query_arg('compose_status', 'pending', aksrt_get_forum_home_url()),
];
}
}