feat(users): govern profiles and access
Normalize fixed profile fields, profile actions, and the five-column capability matrix for comments, uploads, and downloads.
This commit is contained in:
@@ -266,6 +266,15 @@ class CommentActions {
|
||||
], 403);
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
if (!function_exists('aksrt_user_can') || !aksrt_user_can('comment_add', $user_id, $post_id)) {
|
||||
return new \WP_REST_Response([
|
||||
'success' => false,
|
||||
'code' => 'comment_forbidden',
|
||||
'message' => __('当前账号没有发表评论的权限。', 'aksrt'),
|
||||
], $user_id > 0 ? 403 : 401);
|
||||
}
|
||||
|
||||
$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([
|
||||
|
||||
@@ -36,12 +36,124 @@ class FrontSubmit {
|
||||
// Guest path (when enabled).
|
||||
add_action('wp_ajax_nopriv_aksrt_submit_post', [$this, 'ajax_submit_post']);
|
||||
|
||||
add_action('rest_api_init', [$this, 'register_rest_routes']);
|
||||
|
||||
add_filter('body_class', [$this, 'body_class']);
|
||||
add_filter('wp_robots', [$this, 'robots_noindex']);
|
||||
add_filter('aksrt_floating_buttons_enabled', [$this, 'disable_chrome_on_submit']);
|
||||
add_filter('aksrt_footer_tabbar_enabled', [$this, 'disable_chrome_on_submit']);
|
||||
}
|
||||
|
||||
public function register_rest_routes(): void {
|
||||
register_rest_route('aksrt/v1', '/front-submit/upload/(?P<kind>image|resource)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_upload_attachment'],
|
||||
'permission_callback' => [$this, 'can_upload_attachment'],
|
||||
'args' => [
|
||||
'kind' => [
|
||||
'validate_callback' => static function ($value): bool {
|
||||
return in_array((string) $value, ['image', 'resource'], true);
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function can_upload_attachment(\WP_REST_Request $request): bool {
|
||||
$user_id = get_current_user_id();
|
||||
if ($user_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (!$this->can_submit($user_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$kind = (string) $request->get_param('kind');
|
||||
$cap = $kind === 'resource' ? 'upload_resource_attachment' : 'upload_submit_image';
|
||||
return $this->user_can_cap($cap, $user_id);
|
||||
}
|
||||
|
||||
public function rest_upload_attachment(\WP_REST_Request $request) {
|
||||
$kind = (string) $request->get_param('kind');
|
||||
$user_id = get_current_user_id();
|
||||
$files = $request->get_file_params();
|
||||
$file = $files['file'] ?? null;
|
||||
|
||||
if (!is_array($file) || empty($file['tmp_name']) || !empty($file['error'])) {
|
||||
return new \WP_Error('aksrt_upload_missing_file', __('请选择要上传的文件。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
||||
$max_mb = $kind === 'resource'
|
||||
? max(1, min(200, (int) ($settings['resource_max_size'] ?? 50)))
|
||||
: max(1, min(20, (int) ($settings['submit_image_max_size'] ?? 5)));
|
||||
$max_size = $max_mb * MB_IN_BYTES;
|
||||
$size = (int) ($file['size'] ?? 0);
|
||||
|
||||
if ($size <= 0 || $size > $max_size) {
|
||||
return new \WP_Error(
|
||||
'aksrt_upload_too_large',
|
||||
sprintf(__('文件大小不能超过 %dMB。', 'aksrt'), $max_mb),
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
$filename = sanitize_file_name((string) ($file['name'] ?? ''));
|
||||
$checked = wp_check_filetype_and_ext((string) $file['tmp_name'], $filename, get_allowed_mime_types());
|
||||
$mime = (string) ($checked['type'] ?? '');
|
||||
if ($filename === '' || $mime === '') {
|
||||
return new \WP_Error('aksrt_upload_invalid_type', __('不支持的文件类型。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
if ($kind === 'image') {
|
||||
$image_info = @getimagesize((string) $file['tmp_name']);
|
||||
if ($image_info === false || strpos($mime, 'image/') !== 0) {
|
||||
return new \WP_Error('aksrt_upload_invalid_image', __('请上传有效的图片文件。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if (((int) $image_info[0] * (int) $image_info[1]) > 40000000) {
|
||||
return new \WP_Error('aksrt_upload_image_too_large', __('图片尺寸过大。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if (class_exists('\AKSRT\Extensions\ContentAudit')) {
|
||||
$audit = \AKSRT\Extensions\ContentAudit::get_instance()->audit_image((string) $file['tmp_name']);
|
||||
if (empty($audit['passed'])) {
|
||||
return new \WP_Error('aksrt_upload_image_rejected', __('图片未通过内容审核。', 'aksrt'), ['status' => 422]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('media_handle_upload')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
}
|
||||
|
||||
$_FILES['aksrt_front_submit_file'] = $file;
|
||||
$attachment_id = media_handle_upload('aksrt_front_submit_file', 0, [], ['test_form' => false]);
|
||||
unset($_FILES['aksrt_front_submit_file']);
|
||||
if (is_wp_error($attachment_id)) {
|
||||
return new \WP_Error('aksrt_upload_failed', $attachment_id->get_error_message(), ['status' => 400]);
|
||||
}
|
||||
|
||||
$attachment_id = (int) $attachment_id;
|
||||
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
|
||||
update_post_meta($attachment_id, '_aksrt_front_submit_upload_type', $kind);
|
||||
update_post_meta($attachment_id, '_aksrt_front_submit_upload_user_id', $user_id);
|
||||
|
||||
$url = wp_get_attachment_url($attachment_id);
|
||||
if (!$url) {
|
||||
wp_delete_attachment($attachment_id, true);
|
||||
return new \WP_Error('aksrt_upload_failed', __('上传后无法读取文件。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new \WP_REST_Response([
|
||||
'success' => true,
|
||||
'attachment_id' => $attachment_id,
|
||||
'url' => $url,
|
||||
'name' => get_the_title($attachment_id),
|
||||
'mime' => $mime,
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
@@ -48,9 +48,12 @@ trait FrontSubmitFeatured {
|
||||
}
|
||||
$video = esc_url_raw((string) ($payload['url'] ?? $payload['video'] ?? ''));
|
||||
$pic = esc_url_raw((string) ($payload['pic'] ?? $payload['poster'] ?? $payload['cover'] ?? ''));
|
||||
if ($pic !== '' && !$this->user_can_cap('upload_submit_image', $user_id)) {
|
||||
$pic = '';
|
||||
}
|
||||
$post_meta['featured_video'] = $video;
|
||||
$post_meta['featured_slide'] = '';
|
||||
if ($pic !== '') {
|
||||
if ($pic !== '' && $this->can_use_front_submit_image_url($pic, $user_id)) {
|
||||
$post_meta['thumbnail_url'] = $pic;
|
||||
$post_meta['cover_images'] = $pic;
|
||||
$this->maybe_set_thumbnail_url($post_id, $pic);
|
||||
@@ -70,22 +73,26 @@ trait FrontSubmitFeatured {
|
||||
foreach ($ids as $item) {
|
||||
if (is_array($item)) {
|
||||
$u = esc_url_raw((string) ($item['url'] ?? ''));
|
||||
if ($u !== '') {
|
||||
$urls[] = $u;
|
||||
} elseif (!empty($item['id'])) {
|
||||
$src = wp_get_attachment_image_url((int) $item['id'], 'full');
|
||||
$attachment_id = absint($item['id'] ?? 0);
|
||||
if ($attachment_id > 0 && $this->can_use_front_submit_image($attachment_id, $user_id)) {
|
||||
$src = wp_get_attachment_image_url($attachment_id, 'full');
|
||||
if ($src) {
|
||||
$urls[] = $src;
|
||||
}
|
||||
} elseif ($u !== '' && $this->user_can_cap('upload_submit_image', $user_id)) {
|
||||
$urls[] = $u;
|
||||
}
|
||||
} elseif (is_numeric($item)) {
|
||||
$src = wp_get_attachment_image_url((int) $item, 'full');
|
||||
$attachment_id = (int) $item;
|
||||
$src = $this->can_use_front_submit_image($attachment_id, $user_id)
|
||||
? wp_get_attachment_image_url($attachment_id, 'full')
|
||||
: false;
|
||||
if ($src) {
|
||||
$urls[] = $src;
|
||||
}
|
||||
} else {
|
||||
$u = esc_url_raw((string) $item);
|
||||
if ($u !== '') {
|
||||
if ($u !== '' && $this->user_can_cap('upload_submit_image', $user_id)) {
|
||||
$urls[] = $u;
|
||||
}
|
||||
}
|
||||
@@ -93,7 +100,7 @@ trait FrontSubmitFeatured {
|
||||
}
|
||||
$post_meta['featured_slide'] = implode(',', $urls);
|
||||
$post_meta['featured_video'] = '';
|
||||
if (!empty($urls[0])) {
|
||||
if (!empty($urls[0]) && $this->can_use_front_submit_image_url((string) $urls[0], $user_id)) {
|
||||
$post_meta['thumbnail_url'] = $urls[0];
|
||||
$post_meta['cover_images'] = $urls[0];
|
||||
$this->maybe_set_thumbnail_url($post_id, $urls[0]);
|
||||
@@ -106,6 +113,9 @@ trait FrontSubmitFeatured {
|
||||
}
|
||||
$url = esc_url_raw((string) ($payload['url'] ?? $payload['image'] ?? ''));
|
||||
$id = absint($payload['id'] ?? 0);
|
||||
if ($id > 0 && !$this->can_use_front_submit_image($id, $user_id)) {
|
||||
return;
|
||||
}
|
||||
if ($url === '' && $id > 0) {
|
||||
$src = wp_get_attachment_image_url($id, 'full');
|
||||
if ($src) {
|
||||
@@ -114,12 +124,12 @@ trait FrontSubmitFeatured {
|
||||
}
|
||||
$post_meta['featured_video'] = '';
|
||||
$post_meta['featured_slide'] = '';
|
||||
if ($url !== '') {
|
||||
if ($url !== '' && $this->can_use_front_submit_image_url($url, $user_id)) {
|
||||
$post_meta['thumbnail_url'] = $url;
|
||||
$post_meta['cover_images'] = $url;
|
||||
if ($id > 0) {
|
||||
set_post_thumbnail($post_id, $id);
|
||||
} else {
|
||||
} elseif ($this->user_can_cap('upload_submit_image', $user_id)) {
|
||||
$this->maybe_set_thumbnail_url($post_id, $url);
|
||||
}
|
||||
}
|
||||
@@ -154,4 +164,20 @@ trait FrontSubmitFeatured {
|
||||
}
|
||||
$this->set_thumbnail_from_url($post_id, $url);
|
||||
}
|
||||
|
||||
private function can_use_front_submit_image(int $attachment_id, int $user_id): bool {
|
||||
if ($attachment_id <= 0 || !$this->user_can_cap('upload_submit_image', $user_id)) {
|
||||
return false;
|
||||
}
|
||||
$attachment = get_post($attachment_id);
|
||||
return $attachment instanceof \WP_Post
|
||||
&& $attachment->post_type === 'attachment'
|
||||
&& (int) $attachment->post_author === $user_id
|
||||
&& wp_attachment_is_image($attachment_id);
|
||||
}
|
||||
|
||||
private function can_use_front_submit_image_url(string $url, int $user_id): bool {
|
||||
$attachment_id = attachment_url_to_postid($url);
|
||||
return $attachment_id > 0 && $this->can_use_front_submit_image($attachment_id, $user_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ trait FrontSubmitPay {
|
||||
}
|
||||
}
|
||||
|
||||
$downloads = $this->normalize_front_downloads($raw['downloads'] ?? $data['pay_download'] ?? []);
|
||||
$downloads = $this->normalize_front_downloads($raw['downloads'] ?? $data['pay_download'] ?? [], $user_id);
|
||||
if (!empty($downloads) && $this->user_can_cap('new_post_pay_download', $user_id)) {
|
||||
$pay_type = DomainModel::PAY_TYPE_DOWNLOAD;
|
||||
$meta['downloads'] = $downloads;
|
||||
@@ -163,7 +163,7 @@ trait FrontSubmitPay {
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
private function normalize_front_downloads($raw): array {
|
||||
private function normalize_front_downloads($raw, int $user_id): array {
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
@@ -178,16 +178,31 @@ trait FrontSubmitPay {
|
||||
if ($url === '') {
|
||||
continue;
|
||||
}
|
||||
$attachment_id = attachment_url_to_postid($url);
|
||||
if ($attachment_id > 0 && !$this->can_use_front_submit_resource($attachment_id, $user_id)) {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'label' => sanitize_text_field((string) ($row['label'] ?? $row['name'] ?? '')),
|
||||
'url' => $url,
|
||||
'type' => 'external',
|
||||
'type' => $attachment_id > 0 ? 'direct' : 'external',
|
||||
'extract_code' => sanitize_text_field((string) ($row['extract_code'] ?? $row['copy_val'] ?? $row['more'] ?? '')),
|
||||
];
|
||||
}
|
||||
return DomainModel::sanitize_downloads($out);
|
||||
}
|
||||
|
||||
private function can_use_front_submit_resource(int $attachment_id, int $user_id): bool {
|
||||
if ($attachment_id <= 0 || !$this->user_can_cap('upload_resource_attachment', $user_id)) {
|
||||
return false;
|
||||
}
|
||||
$attachment = get_post($attachment_id);
|
||||
return $attachment instanceof \WP_Post
|
||||
&& $attachment->post_type === 'attachment'
|
||||
&& (int) $attachment->post_author === $user_id
|
||||
&& get_post_meta($attachment_id, '_aksrt_front_submit_upload_type', true) === 'resource';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
|
||||
@@ -183,8 +183,12 @@ trait FrontSubmitSave {
|
||||
|
||||
// Legacy thumbnail URL fallback.
|
||||
$thumbnail_url = esc_url_raw((string) ($data['thumbnail'] ?? ''));
|
||||
if ($thumbnail_url !== '' && empty($data['featured_data'])) {
|
||||
$this->set_thumbnail_from_url((int) $in_id, $thumbnail_url);
|
||||
if ($thumbnail_url !== '' && empty($data['featured_data']) && $this->user_can_cap('upload_submit_image', $user_id)) {
|
||||
$attachment_id = attachment_url_to_postid($thumbnail_url);
|
||||
$attachment = $attachment_id > 0 ? get_post($attachment_id) : null;
|
||||
if ($attachment instanceof \WP_Post && (int) $attachment->post_author === $user_id && wp_attachment_is_image($attachment_id)) {
|
||||
set_post_thumbnail((int) $in_id, $attachment_id);
|
||||
}
|
||||
}
|
||||
|
||||
$post = get_post((int) $in_id);
|
||||
|
||||
Reference in New Issue
Block a user