feat(comments): AJAX fragment list/submit + load-more parity

Add REST comment/list + comment/submit HTML fragments, shared query
renderer for order/author/hottest, fix load-more policy and DOM
targets, and stop full-page scrapes for order/author/reload.
This commit is contained in:
kate522 committed 2026-07-12 08:59:50 +08:00
1 parent 0342aad9fb
commit 00d335ba16
8 files changed
+648 -222

No files matched your search

+189 -1
View File
@@ -27,6 +27,24 @@ class AKSRT_Comment_Actions {
}
public function register_routes(): void {
register_rest_route('aksrt/v1', '/comment/list', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'list_comments'],
'permission_callback' => '__return_true',
'args' => [
'post_id' => ['required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint'],
'page' => ['required' => false, 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
'order' => ['required' => false, 'type' => 'string', 'default' => 'newest', 'sanitize_callback' => 'sanitize_key'],
'author_only' => ['required' => false, 'type' => 'boolean', 'default' => false],
],
]);
register_rest_route('aksrt/v1', '/comment/submit', [
'methods' => 'POST',
'callback' => [$this, 'submit_comment'],
'permission_callback' => '__return_true',
]);
register_rest_route('aksrt/v1', '/comment/edit', [
'methods' => 'POST',
'callback' => [$this, 'edit_comment'],
@@ -127,6 +145,172 @@ class AKSRT_Comment_Actions {
return $this->permissions((int) $request->get_param('comment_id'))['can_pin'];
}
/**
* Fragment list for order / author / page without full HTML scrape.
*/
public function list_comments(\WP_REST_Request $request): \WP_REST_Response {
$post_id = (int) $request->get_param('post_id');
$post = get_post($post_id);
if (!$post) {
return new \WP_REST_Response([
'success' => false,
'code' => 'post_not_found',
'message' => __('文章不存在。', 'aksrt'),
], 404);
}
if (!function_exists('aksrt_comments_render_list_fragment')) {
return new \WP_REST_Response([
'success' => false,
'code' => 'fragment_unavailable',
'message' => __('评论片段渲染不可用。', 'aksrt'),
], 500);
}
$order = sanitize_key((string) ($request->get_param('order') ?: 'newest'));
$author_only = (bool) $request->get_param('author_only');
$page = max(1, (int) ($request->get_param('page') ?: 1));
$fragment = aksrt_comments_render_list_fragment($post_id, [
'order' => $order,
'author_only' => $author_only,
'page' => $page,
]);
return new \WP_REST_Response([
'success' => true,
'data' => $fragment,
]);
}
/**
* AJAX submit with HTML item for local insert.
* Runs through native WP comment pipeline (preprocess_comment / moderation).
*/
public function submit_comment(\WP_REST_Request $request): \WP_REST_Response {
$params = $request->get_json_params();
if (!is_array($params) || empty($params)) {
$params = $request->get_params();
}
if (!is_array($params)) {
$params = [];
}
$post_id = absint($params['comment_post_ID'] ?? $params['post_id'] ?? 0);
$content = isset($params['comment']) ? (string) $params['comment'] : '';
$parent = absint($params['comment_parent'] ?? 0);
if ($post_id <= 0 || trim(wp_strip_all_tags($content)) === '') {
return new \WP_REST_Response([
'success' => false,
'code' => 'invalid_payload',
'message' => __('请输入评论内容。', 'aksrt'),
], 400);
}
$post = get_post($post_id);
if (!$post || !comments_open($post_id)) {
return new \WP_REST_Response([
'success' => false,
'code' => 'comments_closed',
'message' => __('评论已关闭。', 'aksrt'),
], 403);
}
$nonce = (string) ($params['_aksrt_comment_nonce'] ?? $params['_wpnonce'] ?? '');
if ($nonce !== '' && !wp_verify_nonce($nonce, 'aksrt_comment_nonce') && !wp_verify_nonce($nonce, 'wp_rest')) {
return new \WP_REST_Response([
'success' => false,
'code' => 'invalid_nonce',
'message' => __('安全校验失败,请刷新后重试。', 'aksrt'),
], 403);
}
$user = wp_get_current_user();
$commentdata = [
'comment_post_ID' => $post_id,
'comment_content' => $content,
'comment_parent' => $parent,
'comment_type' => '',
'comment_author_IP' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash((string) $_SERVER['REMOTE_ADDR'])) : '',
'comment_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash((string) $_SERVER['HTTP_USER_AGENT'])) : '',
'comment_date' => current_time('mysql'),
'comment_date_gmt' => current_time('mysql', true),
];
if ($user && $user->ID > 0) {
$commentdata['user_id'] = (int) $user->ID;
$commentdata['comment_author'] = $user->display_name ?: $user->user_login;
$commentdata['comment_author_email'] = $user->user_email;
$commentdata['comment_author_url'] = $user->user_url;
} else {
$commentdata['user_id'] = 0;
$commentdata['comment_author'] = sanitize_text_field((string) ($params['author'] ?? ''));
$commentdata['comment_author_email'] = sanitize_email((string) ($params['email'] ?? ''));
$commentdata['comment_author_url'] = esc_url_raw((string) ($params['url'] ?? ''));
if ($commentdata['comment_author'] === '' || $commentdata['comment_author_email'] === '') {
return new \WP_REST_Response([
'success' => false,
'code' => 'guest_fields_required',
'message' => __('请填写昵称和邮箱。', 'aksrt'),
], 400);
}
}
foreach (['author', 'email', 'url', 'comment', 'comment_post_ID', 'comment_parent', '_aksrt_comment_nonce'] as $k) {
if (isset($params[$k])) {
$_POST[$k] = $params[$k];
}
}
foreach ($params as $k => $v) {
if (is_string($k) && (str_contains($k, 'captcha') || str_starts_with($k, 'g-recaptcha') || str_starts_with($k, 'cf-turnstile') || str_starts_with($k, 'h-captcha'))) {
$_POST[$k] = $v;
}
}
$comment_id = wp_new_comment(wp_slash($commentdata), true);
if (is_wp_error($comment_id)) {
return new \WP_REST_Response([
'success' => false,
'code' => $comment_id->get_error_code() ?: 'submit_failed',
'message' => $comment_id->get_error_message() ?: __('评论提交失败。', 'aksrt'),
], 400);
}
$comment = get_comment((int) $comment_id);
if (!$comment) {
return new \WP_REST_Response([
'success' => false,
'code' => 'submit_failed',
'message' => __('评论提交失败。', 'aksrt'),
], 500);
}
$settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_comment_display_settings() : [];
$html = function_exists('aksrt_comments_render_single_html')
? aksrt_comments_render_single_html($comment, $settings)
: '';
$needs_moderation = '0' === (string) $comment->comment_approved;
$wait_time = 0;
return new \WP_REST_Response([
'success' => true,
'code' => 'submitted',
'message' => $needs_moderation ? __('评论已提交,等待管理员审核', 'aksrt') : __('评论发布成功', 'aksrt'),
'html' => $html,
'is_reply'=> $parent > 0,
'parent_id' => $parent,
'needs_moderation' => $needs_moderation,
'wait_time' => $wait_time,
'data' => [
'comment_id' => (int) $comment_id,
'approved' => (string) $comment->comment_approved,
],
]);
}
/**
* Edit comment content
*/
@@ -168,12 +352,16 @@ class AKSRT_Comment_Actions {
], 500);
}
$rendered = function_exists('aksrt_render_comment_content')
? aksrt_render_comment_content($clean_content)
: wpautop($clean_content);
return new \WP_REST_Response([
'success' => true,
'code' => 'updated',
'message' => __('评论已更新。', 'aksrt'),
'data' => [
'content' => $clean_content,
'content' => $rendered,
],
]);
}