feat(forum): add complete forum workflows
This commit is contained in:
1 parent
a254cbd3e9
commit
ee65fc7fd2
39 files changed
+3425
-4
No files matched your search
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
namespace Jio\Forum;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait RestPosts {
|
||||
public static function create_post(\WP_REST_Request $request) {
|
||||
return self::save_post($request, 0);
|
||||
}
|
||||
|
||||
public static function update_post(\WP_REST_Request $request) {
|
||||
return self::save_post($request, absint($request['id']));
|
||||
}
|
||||
|
||||
private static function save_post(\WP_REST_Request $request, int $id) {
|
||||
$user_id = self::actor();
|
||||
$current = $id > 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('付费价格超出范围');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user