refactor(autoload): migrate theme classes to Composer PSR-4

Move 288 types from global AKSRT_* + inc/classes manual requires into
namespace AKSRT\{Domain} under src/AKSRT/, wire composer.json psr-4,
and slim bootstrap to helpers/modules/hooks only. Split multi-class
widget/gateway files, update admin contract audit paths, and document
the layout in ADR/README.
This commit is contained in:
kate522 committed 2026-07-17 13:29:22 +08:00
1 parent 8f349b40fb
commit aaf458af1c
404 files changed
+13602 -7705

No files matched your search

+407
View File
@@ -0,0 +1,407 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Forum;
defined('ABSPATH') || exit;
class DraftService {
/** @var Forum */
private $forum;
public function __construct(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) ?: Forum::VISIBILITY_PUBLIC,
'bbs_type' => get_post_meta($draft_id, '_aksrt_bbs_type', true) ?: 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'] ?? Forum::VISIBILITY_PUBLIC)),
'bbs_type' => sanitize_key((string) ($_POST['bbs_type'] ?? 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 ? Forum::REVIEW_APPROVED : 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', 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) ?: Forum::BBS_TYPE_STANDARD;
if ($bbs_type === Forum::BBS_TYPE_QUESTION) {
update_post_meta($draft_id, '_aksrt_question_status', 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()),
];
}
}