0 ? self::post($id) : null; if ($id > 0 && (!$current || !Access::can_manage_post($user_id, $id))) { return self::fail('无权编辑此帖子', 403); } $input = self::input($request); $plate_id = absint($input['plate_id'] ?? ($current ? get_post_meta($id, '_jio_forum_plate_id', true) : 0)); if (!self::plate($plate_id) || !Access::can_publish($user_id, $plate_id)) { return self::fail('没有在此版块发布的权限', 403); } if ($id === 0 && !Access::is_moderator($user_id, $plate_id)) { $limit = max(0, (int) jio_get_setting('jio_forum_daily_post_limit', 20)); if ($limit > 0) { $recent = get_posts(['post_type' => Core::POST, 'post_status' => ['publish', 'pending'], 'author' => $user_id, 'date_query' => [['after' => 'today']], 'posts_per_page' => $limit, 'fields' => 'ids']); if (count($recent) >= $limit) { return self::fail('已达到今日发帖上限', 429); } } } $title = sanitize_text_field((string) ($input['title'] ?? ($current->post_title ?? ''))); $content = wp_kses_post((string) ($input['content'] ?? ($current->post_content ?? ''))); if (mb_strlen($title) < 2 || mb_strlen($title) > 120 || trim(wp_strip_all_tags($content)) === '') { return self::fail('请填写标题和正文'); } $type = sanitize_key((string) ($input['type'] ?? ($current ? get_post_meta($id, '_jio_forum_type', true) : 'normal'))); if (!in_array($type, ['normal', 'question', 'vote'], true)) { return self::fail('帖子类型无效'); } if (($type === 'vote' && !jio_get_setting('jio_forum_vote_enabled', true)) || ($type === 'question' && !jio_get_setting('jio_forum_question_enabled', true))) { return self::fail('当前未启用此帖子类型'); } $votes = []; $vote_type = 'single'; $vote_title = ''; $vote_days = 0; if ($type === 'vote') { $vote_type = sanitize_key((string) ($input['vote_type'] ?? ($current ? get_post_meta($id, '_jio_forum_vote_type', true) : 'single'))); if ($vote_type === '') $vote_type = 'single'; if (!in_array($vote_type, ['single', 'multiple', 'pk'], true)) return self::fail('投票方式无效'); $vote_title = sanitize_text_field((string) ($input['vote_title'] ?? ($current ? get_post_meta($id, '_jio_forum_vote_title', true) : ''))); if (mb_strlen($vote_title) > 60) return self::fail('投票标题过长'); $vote_days = absint($input['vote_days'] ?? 0); if (!in_array($vote_days, [0, 1, 7, 30], true)) return self::fail('投票有效期无效'); $raw = (string) ($input['vote_options'] ?? ''); if ($raw === '' && $current) { $votes = (array) get_post_meta($id, '_jio_forum_vote_options', true); } else { $votes = array_values(array_unique(array_filter(array_map('sanitize_text_field', preg_split('/\R/', $raw))))); } $max_votes = max(2, min(20, (int) jio_get_setting('jio_forum_vote_max', 10))); if (count($votes) < 2 || count($votes) > $max_votes) { return self::fail('投票选项数量无效'); } if ($vote_type === 'pk' && count($votes) !== 2) return self::fail('双选 PK 只能有两个选项'); if ($current && get_post_meta($id, '_jio_forum_vote_total', true) && $votes !== (array) get_post_meta($id, '_jio_forum_vote_options', true)) { return self::fail('已有投票后不能更改选项'); } if ($current && get_post_meta($id, '_jio_forum_vote_total', true) && $vote_type !== (string) get_post_meta($id, '_jio_forum_vote_type', true)) { return self::fail('已有投票后不能更改投票方式'); } } $view = sanitize_key((string) ($input['view_limit'] ?? ($current ? get_post_meta($id, '_jio_forum_view_limit', true) : 'all'))); if (!in_array($view, ['all', 'login', 'follow', 'points', 'pay'], true)) { return self::fail('访问权限无效'); } if (($view === 'points' && !jio_get_setting('jio_forum_points_enabled', true)) || ($view === 'pay' && !jio_get_setting('jio_forum_paid_content', true))) { return self::fail('当前未启用此解锁方式'); } $price = (string) ($input['price'] ?? ($current ? get_post_meta($id, '_jio_forum_price', true) : '0')); if (($view === 'points' && (!ctype_digit($price) || (int) $price <= 0)) || ($view === 'pay' && !preg_match('/^(0|[1-9]\d*)(?:\.\d{1,2})?$/', $price))) { return self::fail('解锁价格无效'); } if (in_array($view, ['points', 'pay'], true)) { $valid_price = self::validate_price($price, $view); if (is_wp_error($valid_price)) return $valid_price; foreach (['vip1', 'vip2'] as $vip) { if (!isset($input['price_' . $vip]) || $input['price_' . $vip] === '') continue; $valid_price = self::validate_price((string) $input['price_' . $vip], $view); if (is_wp_error($valid_price)) return $valid_price; } } if (isset($input['topics']) && !jio_get_setting('jio_forum_user_topics', true) && !Access::is_admin($user_id)) { foreach (array_filter(array_map('sanitize_text_field', explode(',', (string) $input['topics']))) as $name) { if (!term_exists($name, Core::TOPIC)) return self::fail('无权创建新话题', 403); } } if ($current && (int) get_post_meta($id, '_jio_forum_vote_total', true) > 0 && $type !== 'vote') { return self::fail('已有投票后不能更改帖子类型'); } if ($current && get_post_meta($id, '_jio_forum_adopted_comment', true) && $type !== 'question') { return self::fail('已有采纳回复后不能更改帖子类型'); } $status = Access::is_moderator($user_id, $plate_id) || !jio_get_setting('jio_forum_post_review', true) ? 'publish' : 'pending'; $post_data = [ 'ID' => $id, 'post_type' => Core::POST, 'post_title' => $title, 'post_content' => $content, 'post_status' => $status, 'post_author' => $current ? (int) $current->post_author : $user_id, 'comment_status' => $current ? $current->comment_status : 'open', 'ping_status' => 'closed', ]; $result = $id ? wp_update_post($post_data, true) : wp_insert_post($post_data, true); if (is_wp_error($result) || !$result) { return self::fail('保存失败', 500); } $id = (int) $result; if (class_exists('\\Jio\\Attachment\\PendingUploads')) { \Jio\Attachment\PendingUploads::claim_text($content, $user_id); } update_post_meta($id, '_jio_forum_plate_id', $plate_id); update_post_meta($id, '_jio_forum_type', $type); update_post_meta($id, '_jio_forum_view_limit', $view); update_post_meta($id, '_jio_forum_price', $price); foreach (['vip1', 'vip2'] as $vip) { if (!isset($input['price_' . $vip])) continue; $vip_price = (string) $input['price_' . $vip]; if ($vip_price === '' || preg_match($view === 'points' ? '/^\d+$/' : '/^(0|[1-9]\d*)(?:\.\d{1,2})?$/', $vip_price)) { update_post_meta($id, '_jio_forum_price_' . $vip, $vip_price); } } if ($type === 'vote') { update_post_meta($id, '_jio_forum_vote_options', $votes); update_post_meta($id, '_jio_forum_vote_type', $vote_type); update_post_meta($id, '_jio_forum_vote_title', $vote_title); if (!$current || !get_post_meta($id, '_jio_forum_vote_started', true)) { $started = current_time('timestamp'); update_post_meta($id, '_jio_forum_vote_started', $started); update_post_meta($id, '_jio_forum_vote_deadline', $vote_days ? $started + $vote_days * DAY_IN_SECONDS : 0); } } foreach ([Core::TOPIC => 'topics', Core::TAG => 'tags'] as $taxonomy => $key) { if (isset($input[$key])) { $names = array_values(array_filter(array_map('sanitize_text_field', explode(',', (string) $input[$key])))); wp_set_object_terms($id, $names, $taxonomy); } } do_action($current ? 'jio_forum_post_updated' : 'jio_forum_post_created', $id, $user_id, $status); return self::success(['id' => $id, 'url' => $status === 'pending' ? Core::edit_url($id) : get_permalink($id), 'status' => $status], $status === 'pending' ? '已提交审核' : '帖子已发布'); } public static function create_plate(\WP_REST_Request $request) { return self::save_plate($request, 0); } public static function update_plate(\WP_REST_Request $request) { return self::save_plate($request, absint($request['id'])); } private static function save_plate(\WP_REST_Request $request, int $id) { $user_id = self::actor(); $current = $id > 0 ? self::plate($id) : null; if ($id > 0 && (!$current || !Access::is_moderator($user_id, $id))) { return self::fail('无权编辑版块', 403); } if ($id === 0 && !Access::is_admin($user_id) && !jio_get_setting('jio_forum_user_plates', true)) { return self::fail('当前不允许创建版块', 403); } $input = self::input($request); $title = sanitize_text_field((string) ($input['title'] ?? ($current->post_title ?? ''))); if (mb_strlen($title) < 2 || mb_strlen($title) > 80) { return self::fail('版块名称需为 2 至 80 个字符'); } $follow_mode = sanitize_key((string) ($input['follow_mode'] ?? ($current ? get_post_meta($id, '_jio_forum_follow_mode', true) : 'all'))); $view_limit = sanitize_key((string) ($input['view_limit'] ?? ($current ? get_post_meta($id, '_jio_forum_view_limit', true) : 'all'))); if (!in_array($follow_mode, ['all', 'points', 'pay'], true) || !in_array($view_limit, ['all', 'login', 'follow', 'points', 'pay'], true)) { return self::fail('版块权限无效'); } if (in_array($follow_mode, ['points', 'pay'], true) && !jio_get_setting('jio_forum_paid_follow', true)) { return self::fail('付费关注未启用'); } if (($follow_mode === 'points' || $view_limit === 'points') && !jio_get_setting('jio_forum_points_enabled', true)) { return self::fail('积分解锁未启用'); } $follow_price = (string) ($input['follow_price'] ?? ($current ? get_post_meta($id, '_jio_forum_follow_price', true) : '0')); if ($follow_price === '') $follow_price = '0'; if (!preg_match($follow_mode === 'points' ? '/^\d+$/' : '/^(0|[1-9]\d*)(?:\.\d{1,2})?$/', $follow_price)) { return self::fail('关注价格无效'); } if (in_array($follow_mode, ['points', 'pay'], true)) { $valid_price = self::validate_price($follow_price, $follow_mode); if (is_wp_error($valid_price)) return $valid_price; foreach (['vip1', 'vip2'] as $vip) { if (!isset($input['follow_price_' . $vip]) || $input['follow_price_' . $vip] === '') continue; $valid_price = self::validate_price((string) $input['follow_price_' . $vip], $follow_mode); if (is_wp_error($valid_price)) return $valid_price; } } $status = Access::is_admin($user_id) || !jio_get_setting('jio_forum_plate_review', true) ? 'publish' : 'pending'; $result = $id ? wp_update_post([ 'ID' => $id, 'post_title' => $title, 'post_content' => wp_kses_post((string) ($input['content'] ?? $current->post_content)), ], true) : wp_insert_post([ 'post_type' => Core::PLATE, 'post_title' => $title, 'post_status' => $status, 'post_author' => $user_id, 'post_content' => wp_kses_post((string) ($input['content'] ?? '')), ], true); if (is_wp_error($result) || !$result) { return self::fail('版块保存失败', 500); } $id = (int) $result; if (isset($input['category_id'])) { $term_id = absint($input['category_id']); if ($term_id && term_exists($term_id, Core::CATEGORY)) { wp_set_object_terms($id, [$term_id], Core::CATEGORY); } } if (Access::is_moderator($user_id, $id) && isset($input['publish_limit'])) { $limit = sanitize_key((string) $input['publish_limit']); if (in_array($limit, ['all', 'follow'], true)) { update_post_meta($id, '_jio_forum_publish_limit', $limit); } } update_post_meta($id, '_jio_forum_follow_mode', $follow_mode); update_post_meta($id, '_jio_forum_follow_price', $follow_price); update_post_meta($id, '_jio_forum_view_limit', $view_limit); foreach (['vip1', 'vip2'] as $vip) { if (!isset($input['follow_price_' . $vip])) continue; $vip_price = (string) $input['follow_price_' . $vip]; if ($vip_price === '' || preg_match($follow_mode === 'points' ? '/^\d+$/' : '/^(0|[1-9]\d*)(?:\.\d{1,2})?$/', $vip_price)) { update_post_meta($id, '_jio_forum_follow_price_' . $vip, $vip_price); } } return self::success(['id' => $id, 'url' => get_post_status($id) === 'publish' ? get_permalink($id) : Core::home_url(), 'status' => get_post_status($id)], get_post_status($id) === 'pending' ? '版块已提交审核' : '版块已保存'); } private static function validate_price(string $value, string $mode) { if ($mode === 'points') { $max = (string) PHP_INT_MAX; return preg_match('/^(0|[1-9]\d*)$/', $value) && (strlen($value) < strlen($max) || strlen($value) === strlen($max) && strcmp($value, $max) <= 0) ? true : self::fail('积分价格超出范围'); } try { \Jio\Ledger\AssetAmount::balance($value); return true; } catch (\InvalidArgumentException $exception) { return self::fail('付费价格超出范围'); } } }