Files
wordpress-theme-aksrt/inc/classes/class-aksrt-comment-actions.php
T

349 lines
12 KiB
PHP

<?php
/**
* AKSRT Comment Actions
*
* REST endpoints for comment edit / delete / moderate.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Comment_Actions {
private static $instance = null;
private function __construct() {}
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
add_action('rest_api_init', [$this, 'register_routes']);
}
public function register_routes(): void {
register_rest_route('aksrt/v1', '/comment/edit', [
'methods' => 'POST',
'callback' => [$this, 'edit_comment'],
'permission_callback' => [$this, 'check_edit_permission'],
'args' => [
'comment_id' => ['required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint'],
'content' => ['required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_textarea_field'],
],
]);
register_rest_route('aksrt/v1', '/comment/delete', [
'methods' => 'POST',
'callback' => [$this, 'delete_comment'],
'permission_callback' => [$this, 'check_delete_permission'],
'args' => [
'comment_id' => ['required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint'],
],
]);
register_rest_route('aksrt/v1', '/comment/moderate', [
'methods' => 'POST',
'callback' => [$this, 'moderate_comment'],
'permission_callback' => function () {
return current_user_can('moderate_comments');
},
'args' => [
'comment_id' => ['required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint'],
'status' => ['required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field'],
],
]);
register_rest_route('aksrt/v1', '/comment/pin', [
'methods' => 'POST',
'callback' => [$this, 'pin_comment'],
'permission_callback' => [$this, 'check_pin_permission'],
'args' => [
'comment_id' => ['required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint'],
'pinned' => ['required' => false, 'type' => 'boolean'],
],
]);
register_rest_route('aksrt/v1', '/comment/like', [
'methods' => 'POST',
'callback' => [$this, 'like_comment'],
// Guests may like; abuse is bounded by per-visitor toggle state.
'permission_callback' => '__return_true',
'args' => [
'comment_id' => ['required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint'],
'active' => ['required' => false, 'type' => 'boolean'],
],
]);
}
/**
* Resolve the per-comment action contract (single source of truth).
*
* @param int $comment_id Comment ID.
* @return array{can_edit:bool,can_delete:bool,can_moderate:bool,can_pin:bool}
*/
private function permissions(int $comment_id): array {
if (function_exists('aksrt_comment_action_permissions')) {
return aksrt_comment_action_permissions($comment_id);
}
// Defensive fallback if the helper is unavailable.
$comment = get_comment($comment_id);
$is_mod = current_user_can('moderate_comments');
$is_owner = $comment && is_user_logged_in() && (int) $comment->user_id === get_current_user_id();
return [
'can_edit' => $is_mod || $is_owner,
'can_delete' => $is_mod || $is_owner,
'can_moderate' => $is_mod,
'can_pin' => $is_mod,
];
}
/**
* Permission: can edit? (author within configured window, or moderator)
*/
public function check_edit_permission(\WP_REST_Request $request): bool {
if (!is_user_logged_in()) return false;
return $this->permissions((int) $request->get_param('comment_id'))['can_edit'];
}
/**
* Permission: can delete? (author, or moderator)
*/
public function check_delete_permission(\WP_REST_Request $request): bool {
if (!is_user_logged_in()) return false;
return $this->permissions((int) $request->get_param('comment_id'))['can_delete'];
}
/**
* Permission: can pin? (moderator, top-level only)
*/
public function check_pin_permission(\WP_REST_Request $request): bool {
if (!is_user_logged_in()) return false;
return $this->permissions((int) $request->get_param('comment_id'))['can_pin'];
}
/**
* Edit comment content
*/
public function edit_comment(\WP_REST_Request $request): \WP_REST_Response {
$comment_id = $request->get_param('comment_id');
$content = $request->get_param('content');
$comment = get_comment($comment_id);
if (!$comment) {
return new \WP_REST_Response([
'success' => false,
'code' => 'comment_not_found',
'message' => __('评论不存在。', 'aksrt'),
], 404);
}
// Sanitize with wp_kses (allow basic HTML)
$allowed_html = [
'a' => ['href' => [], 'title' => [], 'target' => []],
'code' => [],
'pre' => [],
'strong' => [],
'em' => [],
'br' => [],
'p' => [],
];
$clean_content = wp_kses($content, $allowed_html);
$updated = wp_update_comment([
'comment_ID' => $comment_id,
'comment_content' => $clean_content,
]);
if (is_wp_error($updated)) {
return new \WP_REST_Response([
'success' => false,
'code' => 'update_failed',
'message' => __('更新失败。', 'aksrt'),
], 500);
}
return new \WP_REST_Response([
'success' => true,
'code' => 'updated',
'message' => __('评论已更新。', 'aksrt'),
'data' => [
'content' => $clean_content,
],
]);
}
/**
* Delete comment
*/
public function delete_comment(\WP_REST_Request $request): \WP_REST_Response {
$comment_id = $request->get_param('comment_id');
$comment = get_comment($comment_id);
if (!$comment) {
return new \WP_REST_Response([
'success' => false,
'code' => 'comment_not_found',
'message' => __('评论不存在。', 'aksrt'),
], 404);
}
$deleted = wp_delete_comment($comment_id, false); // soft delete
if (!$deleted) {
return new \WP_REST_Response([
'success' => false,
'code' => 'delete_failed',
'message' => __('删除失败。', 'aksrt'),
], 500);
}
return new \WP_REST_Response([
'success' => true,
'code' => 'deleted',
'message' => __('评论已删除。', 'aksrt'),
]);
}
/**
* Moderate comment (approve / hold / spam)
*/
public function moderate_comment(\WP_REST_Request $request): \WP_REST_Response {
$comment_id = $request->get_param('comment_id');
$status = $request->get_param('status');
$allowed_statuses = ['approve', 'hold', 'spam'];
if (!in_array($status, $allowed_statuses, true)) {
return new \WP_REST_Response([
'success' => false,
'code' => 'invalid_status',
'message' => __('无效状态。', 'aksrt'),
], 400);
}
$comment = get_comment($comment_id);
if (!$comment) {
return new \WP_REST_Response([
'success' => false,
'code' => 'comment_not_found',
'message' => __('评论不存在。', 'aksrt'),
], 404);
}
$status_map = [
'approve' => 'approved',
'hold' => 'hold',
'spam' => 'spam',
];
$result = wp_set_comment_status($comment_id, $status_map[$status]);
if (is_wp_error($result)) {
return new \WP_REST_Response([
'success' => false,
'code' => 'moderate_failed',
'message' => __('操作失败。', 'aksrt'),
], 500);
}
return new \WP_REST_Response([
'success' => true,
'code' => 'moderated',
'message' => __('操作成功。', 'aksrt'),
'data' => [
'status' => $status_map[$status],
],
]);
}
/**
* Toggle a comment's pinned ("置顶") state. Top-level comments only.
*/
public function pin_comment(\WP_REST_Request $request): \WP_REST_Response {
$comment_id = (int) $request->get_param('comment_id');
$comment = get_comment($comment_id);
if (!$comment) {
return new \WP_REST_Response([
'success' => false,
'code' => 'comment_not_found',
'message' => __('评论不存在。', 'aksrt'),
], 404);
}
if ((int) $comment->comment_parent !== 0) {
return new \WP_REST_Response([
'success' => false,
'code' => 'cannot_pin_reply',
'message' => __('仅顶层评论可置顶。', 'aksrt'),
], 400);
}
$current = '1' === (string) get_comment_meta($comment_id, '_aksrt_comment_pinned', true);
$target = null !== $request->get_param('pinned')
? (bool) $request->get_param('pinned')
: !$current;
if ($target) {
update_comment_meta($comment_id, '_aksrt_comment_pinned', '1');
} else {
delete_comment_meta($comment_id, '_aksrt_comment_pinned');
}
return new \WP_REST_Response([
'success' => true,
'code' => 'pinned',
'message' => $target ? __('评论已置顶。', 'aksrt') : __('已取消置顶。', 'aksrt'),
'data' => [
'pinned' => $target,
],
]);
}
/**
* Toggle the current visitor's like on a comment.
*/
public function like_comment(\WP_REST_Request $request): \WP_REST_Response {
$comment_id = (int) $request->get_param('comment_id');
$comment = get_comment($comment_id);
if (!$comment || '1' !== (string) $comment->comment_approved) {
return new \WP_REST_Response([
'success' => false,
'code' => 'comment_unavailable',
'message' => __('评论不存在或未通过审核。', 'aksrt'),
], 404);
}
if (!function_exists('aksrt_is_comment_liked') || !function_exists('aksrt_set_comment_like_state') || !function_exists('aksrt_update_comment_counter')) {
return new \WP_REST_Response([
'success' => false,
'code' => 'like_unavailable',
'message' => __('点赞功能不可用。', 'aksrt'),
], 500);
}
$current = aksrt_is_comment_liked($comment_id);
$target = null !== $request->get_param('active')
? (bool) $request->get_param('active')
: !$current;
if ($target !== $current) {
aksrt_set_comment_like_state($comment_id, $target);
$count = aksrt_update_comment_counter($comment_id, 'aksrt_comment_likes', $target ? 1 : -1);
} else {
$count = aksrt_get_comment_likes($comment_id);
}
return new \WP_REST_Response([
'success' => true,
'code' => 'liked',
'data' => [
'active' => $target,
'count' => $count,
],
]);
}
}