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:
@@ -183,8 +183,8 @@ class ExtensionController extends ControllerBase {
|
||||
'provider' => (string) ($captcha['provider'] ?? ''),
|
||||
'configured' => !empty($captcha['configured']),
|
||||
'scenes' => $scenes,
|
||||
'download_required' => !empty($features['download_captcha']),
|
||||
'download_active' => !empty($features['download_captcha']) && !empty($scenes['download']),
|
||||
'download_required' => !empty($scenes['download']),
|
||||
'download_active' => !empty($scenes['download']),
|
||||
],
|
||||
'content_audit' => [
|
||||
'enabled' => !empty($services['audit']['enabled']),
|
||||
|
||||
@@ -182,9 +182,7 @@ class GlobalController extends ControllerBase {
|
||||
$params = $this->get_json_params($request);
|
||||
$allowed = [
|
||||
'resource_cpt_enabled' => 'bool',
|
||||
'download_login' => 'bool',
|
||||
'download_credit' => 'bool',
|
||||
'download_captcha' => 'bool',
|
||||
'search_include_resource' => 'bool',
|
||||
];
|
||||
$sanitized = $this->sanitize_fields($params, $allowed);
|
||||
|
||||
@@ -46,7 +46,7 @@ class PermissionsController extends ControllerBase {
|
||||
'groups' => $this->build_capability_groups($defs, $caps, $columns),
|
||||
'summary' => [
|
||||
'cap_count' => count($defs),
|
||||
'group_count' => 3,
|
||||
'group_count' => 5,
|
||||
'column_count' => count($columns),
|
||||
],
|
||||
],
|
||||
@@ -114,6 +114,16 @@ class PermissionsController extends ControllerBase {
|
||||
'title' => __('评论权限', 'aksrt'),
|
||||
'items' => [],
|
||||
],
|
||||
'upload' => [
|
||||
'key' => 'upload',
|
||||
'title' => __('上传权限', 'aksrt'),
|
||||
'items' => [],
|
||||
],
|
||||
'download' => [
|
||||
'key' => 'download',
|
||||
'title' => __('下载权限', 'aksrt'),
|
||||
'items' => [],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($defs as $cap_id => $def) {
|
||||
@@ -122,10 +132,13 @@ class PermissionsController extends ControllerBase {
|
||||
$bucket_key = 'comment';
|
||||
} elseif (str_starts_with($cap_id, 'new_post_')) {
|
||||
$bucket_key = 'post';
|
||||
} elseif (str_starts_with($cap_id, 'upload_')) {
|
||||
$bucket_key = 'upload';
|
||||
} elseif (str_starts_with($cap_id, 'resource_')) {
|
||||
$bucket_key = 'download';
|
||||
}
|
||||
|
||||
$excludes = $def['exclude_roles'] ?? [];
|
||||
$only_roles = $def['roles'] ?? null;
|
||||
$values = $caps[$cap_id] ?? [];
|
||||
|
||||
$visible_columns = [];
|
||||
@@ -133,9 +146,6 @@ class PermissionsController extends ControllerBase {
|
||||
if (in_array($column, $excludes, true)) {
|
||||
continue;
|
||||
}
|
||||
if ($only_roles !== null && !in_array($column, $only_roles, true) && !in_array($column, ['all', 'logged', 'level', 'vip', 'auth'], true)) {
|
||||
continue;
|
||||
}
|
||||
$visible_columns[] = $column;
|
||||
}
|
||||
|
||||
@@ -157,9 +167,6 @@ class PermissionsController extends ControllerBase {
|
||||
'level' => __('等级≥', 'aksrt'),
|
||||
'vip' => __('VIP≥', 'aksrt'),
|
||||
'auth' => __('已认证', 'aksrt'),
|
||||
'moderator' => __('版主', 'aksrt'),
|
||||
'plate_author' => __('超级版主', 'aksrt'),
|
||||
'cat_moderator' => __('分区版主', 'aksrt'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ class SchemaPayment {
|
||||
['label' => '正文底部', 'value' => 'bottom'],
|
||||
['label' => '文章框底部', 'value' => 'box_bottom'],
|
||||
]],
|
||||
['key' => 'pay_free_logged_show', 'label' => '免费资源需登录后下载', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'download', 'title' => '下载能力', 'fields' => [
|
||||
['key' => 'payment_download_enable', 'label' => '启用下载能力', 'type' => 'switch', 'default' => false],
|
||||
@@ -308,7 +307,6 @@ class SchemaPayment {
|
||||
['label' => '正文底部', 'value' => 'bottom'],
|
||||
['label' => '文章框底部', 'value' => 'box_bottom'],
|
||||
]],
|
||||
['key' => 'pay_free_logged_show', 'label' => '登录用户显示免费', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_show_ip', 'label' => '显示用户IP', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_show_device', 'label' => '显示设备信息', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_show_order_no', 'label' => '显示订单号', 'type' => 'switch', 'default' => true],
|
||||
|
||||
@@ -11,9 +11,9 @@ class SchemaPermissions {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'permissions',
|
||||
'title' => '功能&权限',
|
||||
'sections' => [
|
||||
'domain' => 'permissions',
|
||||
'title' => '功能与权限',
|
||||
'sections' => [
|
||||
'login' => [
|
||||
'key' => 'login',
|
||||
'title' => '人机验证',
|
||||
@@ -54,94 +54,24 @@ class SchemaPermissions {
|
||||
]],
|
||||
],
|
||||
],
|
||||
'comment' => [
|
||||
'key' => 'comment',
|
||||
'title' => '评论权限',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础权限', 'fields' => [
|
||||
['key' => 'comment_enabled', 'label' => '全局评论开关', 'type' => 'switch', 'default' => true, 'help' => '关闭后全站禁用评论功能'],
|
||||
['key' => 'comment_guest_allowed', 'label' => '允许游客评论', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_min_length', 'label' => '评论最少字数', 'type' => 'number', 'default' => 2, 'min' => 0, 'max' => 100],
|
||||
['key' => 'comment_max_length', 'label' => '评论最多字数', 'type' => 'number', 'default' => 1000, 'min' => 50, 'max' => 10000],
|
||||
['key' => 'comment_cool_down_seconds', 'label' => '评论冷却时间(秒)', 'type' => 'number', 'default' => 15, 'min' => 0, 'max' => 300],
|
||||
['key' => 'comment_max_links', 'label' => '最大链接数', 'type' => 'number', 'default' => 2, 'min' => 0, 'max' => 10],
|
||||
['key' => 'comment_max_images', 'label' => '最大图片数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 20],
|
||||
]],
|
||||
['key' => 'sensitive', 'title' => '敏感词', 'fields' => [
|
||||
['key' => 'comment_sensitive_words', 'label' => '敏感词列表', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'comment_sensitive_action', 'label' => '敏感词处理', 'type' => 'radio', 'default' => 'reject', 'options' => [
|
||||
['label' => '拒绝评论', 'value' => 'reject'],
|
||||
['label' => '替换为***', 'value' => 'replace'],
|
||||
['label' => '转入审核', 'value' => 'pending'],
|
||||
]],
|
||||
['key' => 'comment_sensitive_log', 'label' => '记录敏感词日志', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'anti-spam', 'title' => '反垃圾', 'fields' => [
|
||||
['key' => 'comment_link_filter', 'label' => '链接过滤', 'type' => 'switch', 'default' => true, 'help' => '评论包含链接自动进入审核'],
|
||||
['key' => 'comment_flood_protection', 'label' => '防刷评论', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_flood_interval', 'label' => '防刷间隔(秒)', 'type' => 'number', 'default' => 10, 'min' => 3, 'max' => 60],
|
||||
['key' => 'comment_max_per_hour', 'label' => '每小时最大评论数', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 200],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'comment_avatar_required', 'label' => '要求设置头像', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'comment_nickname_required', 'label' => '要求填写昵称', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_email_required', 'label' => '要求填写邮箱', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'attachment' => [
|
||||
'key' => 'attachment',
|
||||
'title' => '附件权限',
|
||||
'version' => '1.0.0',
|
||||
'title' => '附件配置',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'profile', 'title' => '头像与封面', 'fields' => [
|
||||
['key' => 'allow_avatar_upload', 'label' => '允许用户上传头像', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'allow_cover_upload', 'label' => '允许用户上传封面图', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'allow_background_upload', 'label' => '允许用户上传背景图', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'avatar_max_size', 'label' => '头像/封面大小上限 (MB)', 'type' => 'number', 'default' => 2, 'min' => 1, 'max' => 10],
|
||||
['key' => 'profile', 'title' => '用户资料卡背景图', 'fields' => [
|
||||
['key' => 'avatar_max_size', 'label' => '头像/用户资料卡背景图大小上限 (MB)', 'type' => 'number', 'default' => 2, 'min' => 1, 'max' => 10],
|
||||
]],
|
||||
['key' => 'content', 'title' => '内容上传', 'fields' => [
|
||||
['key' => 'allow_submit_images', 'label' => '允许投稿插入图片', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'content', 'title' => '内容上传限制', 'fields' => [
|
||||
['key' => 'submit_image_max_size', 'label' => '投稿图片大小上限 (MB)', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 20],
|
||||
['key' => 'allow_comment_images', 'label' => '允许评论插入图片', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'comment_image_max_size', 'label' => '评论图片大小上限 (MB)', 'type' => 'number', 'default' => 2, 'min' => 1, 'max' => 10],
|
||||
]],
|
||||
['key' => 'resource', 'title' => '资源附件', 'fields' => [
|
||||
['key' => 'allow_resource_upload', 'label' => '允许资源附件上传', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'resource', 'title' => '资源附件限制', 'fields' => [
|
||||
['key' => 'resource_max_size', 'label' => '资源附件大小上限 (MB)', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 200],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'download' => [
|
||||
'key' => 'download',
|
||||
'title' => '下载权限',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '下载设置', 'fields' => [
|
||||
['key' => 'download_require_login', 'label' => '下载需要登录', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'download_require_credits', 'label' => '允许积分购买模式', 'type' => 'switch', 'default' => false, 'help' => '兼容字段;运行时以 cms_features.download_credit 为准。开启表示 pay_modo=积分 可收银台一次购买,不是按次扣费。'],
|
||||
['key' => 'download_credits_cost', 'label' => '默认积分价参考', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 100, 'help' => '仅运营参考/兼容;实际价格以文章付费设置为准。'],
|
||||
['key' => 'download_daily_limit', 'label' => '每日下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '0为不限制'],
|
||||
['key' => 'download_link_expire', 'label' => '下载链接有效期(分钟)', 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 1440],
|
||||
]],
|
||||
['key' => 'vip', 'title' => 'VIP 特权', 'fields' => [
|
||||
['key' => 'download_vip_free', 'label' => 'VIP 免费下载', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'download_vip_daily_extra', 'label' => 'VIP每日额外次数', 'type' => 'number', 'default' => 10, 'min' => 0, 'max' => 999],
|
||||
]],
|
||||
['key' => 'role', 'title' => '角色权限', 'fields' => [
|
||||
['key' => 'download_role_limits', 'label' => '角色下载限制', 'type' => 'textarea', 'default' => '', 'help' => '格式: role|daily_limit,每行一条'],
|
||||
['key' => 'download_role_size', 'label' => '角色文件大小限制', 'type' => 'textarea', 'default' => '', 'help' => '格式: role|max_size_mb,每行一条'],
|
||||
]],
|
||||
['key' => 'captcha', 'title' => '安全设置', 'fields' => [
|
||||
['key' => 'download_captcha_hint', 'label' => '下载验证码', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '下载验证码门槛在「功能&权限 → 下载权限」页(cms_features.download_captcha)维护;服务商与场景在「人机验证」。'],
|
||||
['key' => 'download_ip_limit', 'label' => 'IP下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '同一IP每小时最大下载次数,0为不限制'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'moderation' => [
|
||||
'key' => 'moderation',
|
||||
'title' => '内容审核',
|
||||
@@ -159,9 +89,9 @@ class SchemaPermissions {
|
||||
]],
|
||||
['key' => 'ai', 'title' => 'AI 审核', 'fields' => [
|
||||
['key' => 'ai_enabled', 'label' => '启用 AI 审核', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'enabled', 'value' => true]],
|
||||
['key' => 'ai_api_hint', 'label' => '审核 API 配置', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => 'API 接入(百度/阿里云等)在「扩展 → API内容审核」中统一配置,此处仅控制审核范围和策略。', 'dependsOn' => ['key' => 'ai_enabled', 'value' => true]],
|
||||
['key' => 'ai_api_hint', 'label' => '审核 API 配置', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => 'API 接入在「扩展 → API 内容审核」中统一配置;此处仅控制审核范围和策略。', 'dependsOn' => ['key' => 'ai_enabled', 'value' => true]],
|
||||
['key' => 'ai_moderate_comments', 'label' => '用户评论', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'ai_enabled', 'value' => true]],
|
||||
['key' => 'ai_moderate_background', 'label' => '用户资料背景', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'ai_enabled', 'value' => true]],
|
||||
['key' => 'ai_moderate_background', 'label' => '用户资料卡背景图', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'ai_enabled', 'value' => true]],
|
||||
['key' => 'auto_approve_low_risk', 'label' => '低风险自动通过', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'ai_enabled', 'value' => true]],
|
||||
['key' => 'auto_reject_high_risk', 'label' => '高风险自动拒绝', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'ai_enabled', 'value' => true]],
|
||||
['key' => 'low_risk_max_score', 'label' => '低风险上限', 'type' => 'number', 'default' => 35, 'min' => 0, 'max' => 100, 'dependsOn' => ['key' => 'ai_enabled', 'value' => true]],
|
||||
@@ -175,21 +105,13 @@ class SchemaPermissions {
|
||||
['key' => 'rate_limit_min_interval', 'label' => '最小间隔(秒)', 'type' => 'number', 'default' => 8, 'min' => 1, 'max' => 300, 'dependsOn' => ['key' => 'rate_limit_enabled', 'value' => true]],
|
||||
['key' => 'rate_limit_max_per_hour', 'label' => '每小时上限', 'type' => 'number', 'default' => 15, 'min' => 1, 'max' => 200, 'dependsOn' => ['key' => 'rate_limit_enabled', 'value' => true]],
|
||||
]],
|
||||
['key' => 'geoip', 'title' => 'GeoIP 拦截', 'fields' => [
|
||||
['key' => 'geoip_enabled', 'label' => '启用 GeoIP 拦截', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'enabled', 'value' => true]],
|
||||
['key' => 'geoip_hint', 'label' => 'IP 数据源', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '复用「扩展 → IP归属地」中配置的数据源和 API Key。', 'dependsOn' => ['key' => 'geoip_enabled', 'value' => true]],
|
||||
['key' => 'geoip_block_countries', 'label' => '屏蔽国家/地区', 'type' => 'custom', 'default' => [], 'sanitize_type' => 'array', 'dependsOn' => ['key' => 'geoip_enabled', 'value' => true]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'capabilities' => [
|
||||
'key' => 'capabilities',
|
||||
'title' => '基本权限',
|
||||
'version' => '1.0.0',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
// Live editing uses PermissionsCapabilities.vue + /permissions/capabilities.
|
||||
// Keep an empty schema contract here so domain build() does not fatally call
|
||||
// a missing build_cap_matrix_groups() helper when loading login/moderation.
|
||||
'groups' => [],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -253,8 +253,6 @@ class SchemaPostList {
|
||||
['key' => 'comment_avatar_size', 'label' => '头像大小', 'type' => 'number', 'default' => 48, 'min' => 24, 'max' => 96],
|
||||
]],
|
||||
['key' => 'permissions', 'title' => '评论权限', 'fields' => [
|
||||
['key' => 'comment_require_login', 'label' => '评论需要登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'comment_allow_reply', 'label' => '允许回复', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_close_comments', 'label' => '关闭评论', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'comment_edit_window', 'label' => '作者自助编辑时限(分钟)', 'type' => 'number', 'default' => 10, 'min' => 0, 'max' => 1440],
|
||||
]],
|
||||
@@ -262,7 +260,6 @@ class SchemaPostList {
|
||||
['key' => 'comment_smilie', 'label' => 'Emoji表情', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_code', 'label' => '代码插入', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_img', 'label' => '图片上传', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_upload_img', 'label' => '评论图片上传', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'interaction', 'title' => '互动功能', 'fields' => [
|
||||
['key' => 'comment_hot_threshold', 'label' => '神评点赞阈值', 'type' => 'number', 'default' => 10, 'min' => 1, 'max' => 999],
|
||||
|
||||
@@ -602,8 +602,6 @@ class SchemaUsers {
|
||||
'groups' => [
|
||||
['key' => 'center', 'title' => '用户中心', 'fields' => [
|
||||
['key' => 'user_center_tabs', 'label' => '用户中心 Tab', 'type' => 'input', 'default' => 'profile,level,comments,favorites,vip,credits,revenue,auth', 'help' => '逗号分隔。level=我的等级;creator=创作中心;revenue=推广收益中心;auth=官方认证;等级、VIP、订单、创作中心、收益中心和认证会按功能开关自动注入'],
|
||||
['key' => 'user_cover_upload', 'label' => '封面上传', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_social_links', 'label' => '社交链接', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_desc_std', 'label' => '默认签名', 'type' => 'input', 'default' => '这家伙很懒…', 'desc' => '用户未设置个人简介时显示的默认文案'],
|
||||
['key' => 'user_city_s', 'label' => '显示城市(IP 归属地)', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_city_type', 'label' => '位置精度', 'type' => 'radio', 'default' => 'province', 'options' => [
|
||||
|
||||
@@ -659,6 +659,9 @@ class UserController extends ControllerBase {
|
||||
if ($user_id <= 0) {
|
||||
return $this->error(__('请先登录', 'aksrt'), 401);
|
||||
}
|
||||
if (!function_exists('aksrt_user_can') || !aksrt_user_can('upload_comment_image', $user_id)) {
|
||||
return $this->error(__('当前账号没有上传评论图片的权限', 'aksrt'), 403);
|
||||
}
|
||||
if (empty($_FILES['image']) && empty($_FILES['file'])) {
|
||||
return $this->error(__('请选择要上传的图片', 'aksrt'), 400);
|
||||
}
|
||||
@@ -667,10 +670,49 @@ class UserController extends ControllerBase {
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
|
||||
$file_key = !empty($_FILES['image']) ? 'image' : 'file';
|
||||
$attachment_id = media_handle_upload($file_key, 0);
|
||||
$file = $_FILES[$file_key];
|
||||
$upload_error = isset($file['error']) ? (int) $file['error'] : UPLOAD_ERR_NO_FILE;
|
||||
if ($upload_error !== UPLOAD_ERR_OK) {
|
||||
return $this->error(__('图片文件上传失败,请重新选择', 'aksrt'), 400);
|
||||
}
|
||||
|
||||
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
||||
$max_size_mb = max(1, min(10, (int) ($settings['comment_image_max_size'] ?? 2)));
|
||||
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
|
||||
if ($actual_size <= 0 || $actual_size > $max_size_mb * MB_IN_BYTES) {
|
||||
return $this->error(sprintf(__('图片大小必须在 1 字节至 %dMB 之间', 'aksrt'), $max_size_mb), 413);
|
||||
}
|
||||
|
||||
$mimes = [
|
||||
'jpg|jpeg|jpe' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
];
|
||||
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], $mimes);
|
||||
$image_size = wp_getimagesize($file['tmp_name']);
|
||||
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
|
||||
return $this->error(__('仅支持有效的 JPG、PNG、GIF 或 WebP 图片', 'aksrt'), 415);
|
||||
}
|
||||
if (class_exists('\AKSRT\Extensions\ContentAudit')) {
|
||||
$audit = \AKSRT\Extensions\ContentAudit::get_instance()->audit_image((string) $file['tmp_name']);
|
||||
if (empty($audit['passed'])) {
|
||||
return $this->error(__('图片未通过内容审核', 'aksrt'), 422);
|
||||
}
|
||||
}
|
||||
|
||||
$attachment_id = media_handle_upload($file_key, 0, [], [
|
||||
'test_form' => false,
|
||||
'mimes' => $mimes,
|
||||
]);
|
||||
if (is_wp_error($attachment_id)) {
|
||||
return $this->error($attachment_id->get_error_message(), 400);
|
||||
}
|
||||
wp_update_post(['ID' => (int) $attachment_id, 'post_author' => $user_id]);
|
||||
if (!wp_attachment_is_image((int) $attachment_id)) {
|
||||
wp_delete_attachment((int) $attachment_id, true);
|
||||
return $this->error(__('上传文件不是有效图片', 'aksrt'), 415);
|
||||
}
|
||||
$url = wp_get_attachment_url((int) $attachment_id);
|
||||
if (!$url) {
|
||||
return $this->error(__('上传失败', 'aksrt'), 500);
|
||||
|
||||
@@ -54,14 +54,11 @@ trait UserControllerProfileMediaOperations {
|
||||
return $this->error(__('请先登录', 'aksrt'), 401);
|
||||
}
|
||||
|
||||
$center_settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_user_center_settings()
|
||||
: ['avatar_upload' => true, 'cover_upload' => true];
|
||||
if ($is_avatar && empty($center_settings['avatar_upload'])) {
|
||||
return $this->error(__('头像上传功能未开启', 'aksrt'), 403);
|
||||
}
|
||||
if (!$is_avatar && (empty($center_settings['cover_upload']) || !aksrt_get_setting('allow_background_upload', false))) {
|
||||
return $this->error(__('背景图上传功能未开启', 'aksrt'), 403);
|
||||
$capability = $is_avatar ? 'upload_avatar' : 'upload_profile_background';
|
||||
if (!function_exists('aksrt_user_can') || !aksrt_user_can($capability, $user_id)) {
|
||||
return $this->error($is_avatar
|
||||
? __('当前账号没有上传头像的权限', 'aksrt')
|
||||
: __('当前账号没有上传用户资料卡背景图的权限', 'aksrt'), 403);
|
||||
}
|
||||
|
||||
$file_key = '';
|
||||
@@ -107,6 +104,12 @@ trait UserControllerProfileMediaOperations {
|
||||
if (!$image_size || empty($image_size[0]) || empty($image_size[1]) || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
|
||||
return $this->error(__('图片无效或尺寸过大', 'aksrt'), 415);
|
||||
}
|
||||
if (!$is_avatar && class_exists('\AKSRT\Extensions\ContentAudit')) {
|
||||
$audit = \AKSRT\Extensions\ContentAudit::get_instance()->audit_image((string) $file['tmp_name']);
|
||||
if (empty($audit['passed'])) {
|
||||
return $this->error(__('图片未通过内容审核', 'aksrt'), 422);
|
||||
}
|
||||
}
|
||||
|
||||
$attachment_id = media_handle_upload($file_key, 0, [], [
|
||||
'test_form' => false,
|
||||
|
||||
@@ -451,8 +451,21 @@ trait UserControllerReportOperations {
|
||||
return $this->error('无效的举报类型');
|
||||
}
|
||||
|
||||
$reporter_id = get_current_user_id();
|
||||
if ($reporter_id <= 0) {
|
||||
return $this->error(__('请先登录', 'aksrt'), 401);
|
||||
}
|
||||
if ($target_type === 'user') {
|
||||
if ($target_id === $reporter_id) {
|
||||
return $this->error(__('不能举报自己', 'aksrt'), 400);
|
||||
}
|
||||
if (!function_exists('aksrt_user_can') || !aksrt_user_can('user_report', $reporter_id, $target_id)) {
|
||||
return $this->error(__('当前账号没有举报用户的权限', 'aksrt'), 403);
|
||||
}
|
||||
}
|
||||
|
||||
$report_id = \AKSRT\Messages\ReportCenter::get_instance()->submit(
|
||||
get_current_user_id(),
|
||||
$reporter_id,
|
||||
$target_type,
|
||||
$target_id,
|
||||
$reason,
|
||||
|
||||
@@ -81,6 +81,15 @@ class Moderation {
|
||||
*/
|
||||
public function process(array $commentdata): array {
|
||||
|
||||
$actor = get_current_user_id();
|
||||
if (!function_exists('aksrt_user_can') || !aksrt_user_can('comment_add', $actor, (int) ($commentdata['comment_post_ID'] ?? 0))) {
|
||||
wp_die(
|
||||
esc_html__('当前账号没有发表评论的权限。', 'aksrt'),
|
||||
esc_html__('评论提交失败', 'aksrt'),
|
||||
['response' => $actor > 0 ? 403 : 401, 'back_link' => true]
|
||||
);
|
||||
}
|
||||
|
||||
// 人机验证:覆盖 REST 与原生 wp-comments-post 两条提交合同(与审核开关无关)
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$payload = is_array($_POST ?? null) ? wp_unslash($_POST) : [];
|
||||
|
||||
@@ -91,6 +91,15 @@ class Download {
|
||||
}
|
||||
|
||||
public function check_rest_nonce(\WP_REST_Request $request) {
|
||||
// The capability matrix can explicitly allow resource downloads for
|
||||
// everyone. A WordPress REST nonce only exists for authenticated
|
||||
// sessions, so requiring one from visitors would make the "all"
|
||||
// column ineffective. Anonymous requests still pass the unified
|
||||
// download gate (capability, payment and captcha) in rest_request().
|
||||
if (get_current_user_id() <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$nonce = $request->get_header('x-wp-nonce');
|
||||
if (!$nonce) {
|
||||
return new \WP_Error('aksrt_rest_no_nonce', __('缺少安全令牌', 'aksrt'), ['status' => 403]);
|
||||
@@ -109,7 +118,6 @@ class Download {
|
||||
}
|
||||
|
||||
$meta = function_exists('aksrt_get_download_box_meta') ? aksrt_get_download_box_meta($post) : [];
|
||||
$paid_settings = \AKSRT\Settings\Settings::get_paid_content_settings();
|
||||
$cost = aksrt_resource_download_cost($post);
|
||||
$uid = get_current_user_id();
|
||||
$priced = function_exists('aksrt_get_user_pay_price') ? aksrt_get_user_pay_price($post, $uid) : null;
|
||||
@@ -122,10 +130,7 @@ class Download {
|
||||
? Visibility::get_post_access_state($post, $uid)
|
||||
: [];
|
||||
|
||||
$need_captcha = !empty($paid_settings['download_captcha']) || !empty($meta['require_captcha']);
|
||||
if ($need_captcha && function_exists('aksrt_is_captcha_enabled_for') && !aksrt_is_captcha_enabled_for('download')) {
|
||||
$need_captcha = false;
|
||||
}
|
||||
$need_captcha = function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('download');
|
||||
|
||||
return new \WP_REST_Response([
|
||||
'post_id' => $post_id,
|
||||
@@ -134,7 +139,7 @@ class Download {
|
||||
'pay_modo' => $pay_modo,
|
||||
'pay_limit' => (int) ($meta['pay_limit'] ?? 0),
|
||||
'cost' => $cost,
|
||||
'require_login' => !empty($paid_settings['download_login']) || !empty($meta['require_login']) || (!empty($paid_settings['free_logged_show']) && $cost <= 0 && $display_price <= 0),
|
||||
'require_login' => !empty($access['need_login']),
|
||||
'require_captcha' => $need_captcha,
|
||||
'logged_in' => $uid > 0,
|
||||
'balance' => $uid > 0 && class_exists('\AKSRT\Credits\Credits') ? \AKSRT\Credits\Credits::balance($uid) : 0,
|
||||
@@ -181,7 +186,6 @@ class Download {
|
||||
$gate = aksrt_can_download($post);
|
||||
if (is_wp_error($gate)) return $gate;
|
||||
|
||||
$paid_settings = \AKSRT\Settings\Settings::get_paid_content_settings();
|
||||
$uid = get_current_user_id();
|
||||
$cost = aksrt_resource_download_cost($post);
|
||||
|
||||
@@ -193,8 +197,7 @@ class Download {
|
||||
}
|
||||
}
|
||||
|
||||
$need_captcha = !empty($paid_settings['download_captcha']) || !empty($meta['require_captcha']);
|
||||
// 下载双开关:cms_features.download_captcha(或单资源强制)且场景未停用。
|
||||
$need_captcha = function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('download');
|
||||
if ($need_captcha && function_exists('aksrt_require_captcha')) {
|
||||
$cap_payload = isset($params['captcha']) && is_array($params['captcha']) ? $params['captcha'] : [];
|
||||
// 场景未启用时 require 直接放行;启用时强制校验 token。
|
||||
|
||||
+14
-28
@@ -47,14 +47,6 @@ class Visibility {
|
||||
: (($pay_modo === 'points')
|
||||
? (float) ($pay_meta['points_price'] ?? 0)
|
||||
: (float) ($pay_meta['price'] ?? 0));
|
||||
$paid_settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_paid_content_settings()
|
||||
: [
|
||||
'download_login' => true,
|
||||
'download_credit' => true,
|
||||
'download_captcha' => false,
|
||||
];
|
||||
|
||||
$state = [
|
||||
'post_id' => $resource_post instanceof \WP_Post ? (int) $resource_post->ID : 0,
|
||||
'pay_type' => $pay_type,
|
||||
@@ -134,12 +126,13 @@ class Visibility {
|
||||
if ($allowed_member && $price <= 0) {
|
||||
$download_cost = 0;
|
||||
}
|
||||
$can_download = function_exists('aksrt_user_can') && aksrt_user_can('resource_download', $uid, (int) $resource_post->ID);
|
||||
$state['download_cost'] = max(0, $download_cost);
|
||||
$state['need_login'] = $uid <= 0;
|
||||
$state['need_login'] = !$can_download && $uid <= 0;
|
||||
$state['need_purchase'] = $allowed_member && $price > 0 && !$has_paid_order;
|
||||
$state['has_paid_access'] = $allowed_member && ($has_paid_order || $price <= 0);
|
||||
$state['can_view'] = true;
|
||||
$state['can_download'] = $allowed_member && ($uid > 0) && ($has_paid_order || $price <= 0);
|
||||
$state['can_download'] = $can_download && $allowed_member && ($has_paid_order || $price <= 0);
|
||||
if (!$allowed_member) {
|
||||
$state['can_download'] = false;
|
||||
$state['has_paid_access'] = false;
|
||||
@@ -172,16 +165,14 @@ class Visibility {
|
||||
}
|
||||
$state['download_cost'] = max(0, $download_cost);
|
||||
|
||||
$free_logged_show = !empty($paid_settings['free_logged_show']);
|
||||
$state['need_login'] = !empty($paid_settings['download_login'])
|
||||
|| !empty($pay_meta['require_login'])
|
||||
|| ($free_logged_show && $price <= 0);
|
||||
$state['require_captcha'] = !empty($paid_settings['download_captcha']) || !empty($pay_meta['require_captcha']);
|
||||
$can_download = function_exists('aksrt_user_can') && aksrt_user_can('resource_download', $uid, (int) $resource_post->ID);
|
||||
$state['need_login'] = !$can_download && $uid <= 0;
|
||||
$state['require_captcha'] = function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('download');
|
||||
|
||||
$state['need_purchase'] = !$has_paid_order && $price > 0;
|
||||
$state['has_paid_access'] = $has_paid_order || $price <= 0 || $has_free_access;
|
||||
$state['can_view'] = true;
|
||||
$state['can_download'] = (!$state['need_login'] || $uid > 0) && ($has_paid_order || $price <= 0);
|
||||
$state['can_download'] = $can_download && ($has_paid_order || $price <= 0);
|
||||
|
||||
return $state;
|
||||
}
|
||||
@@ -227,6 +218,13 @@ class Visibility {
|
||||
$state = self::get_post_access_state($resource_post, $user_id);
|
||||
$uid = $user_id > 0 ? $user_id : get_current_user_id();
|
||||
|
||||
if (!function_exists('aksrt_user_can') || !aksrt_user_can('resource_download', $uid, (int) $resource_post->ID)) {
|
||||
return new \WP_Error('aksrt_download_forbidden', __('当前账号没有下载资源的权限。', 'aksrt'), [
|
||||
'status' => $uid > 0 ? 403 : 401,
|
||||
'login_url' => wp_login_url(get_permalink($resource_post)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (($state['pay_type'] ?? '') !== \AKSRT\Core\DomainModel::PAY_TYPE_DOWNLOAD) {
|
||||
return new \WP_Error('aksrt_not_downloadable', __('该内容未启用下载能力。', 'aksrt'));
|
||||
}
|
||||
@@ -238,19 +236,7 @@ class Visibility {
|
||||
]);
|
||||
}
|
||||
|
||||
// Free download + pay_free_logged_show (belt-and-suspenders with state.need_login).
|
||||
$cost = (int) ($state['download_cost'] ?? 0);
|
||||
$paid_settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_paid_content_settings()
|
||||
: ['download_credit' => true, 'free_logged_show' => false];
|
||||
|
||||
if ($cost <= 0 && !empty($paid_settings['free_logged_show']) && $uid <= 0) {
|
||||
return new \WP_Error('aksrt_need_login', __('免费资源,请登录后下载。', 'aksrt'), [
|
||||
'status' => 401,
|
||||
'login_url' => wp_login_url(get_permalink($resource_post)),
|
||||
]);
|
||||
}
|
||||
|
||||
// Member-only (pay_limit).
|
||||
if (!empty($state['need_vip']) && empty($state['has_paid_access']) && empty($state['can_download'])) {
|
||||
$need_level = (int) ($state['pay_limit'] ?? 1);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -17,7 +17,12 @@ trait AuthReader {
|
||||
|
||||
/** 是否要求注册用户才能评论 */
|
||||
public static function comment_requires_registration(): bool {
|
||||
return !empty(self::get_value('comment_require_login', false)) && self::comment_enabled();
|
||||
if (!self::comment_enabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
return !class_exists(Capabilities::class) || !Capabilities::user_can('comment_add', $user_id);
|
||||
}
|
||||
|
||||
/** 是否允许注册 */
|
||||
|
||||
@@ -12,7 +12,7 @@ class Capabilities {
|
||||
/**
|
||||
* Capability definitions registry.
|
||||
*
|
||||
* @return array<string, array{label:string, roles?:string[], exclude_roles?:string[], default?:array<string,mixed>}>
|
||||
* @return array<string, array{label:string, exclude_roles?:string[], default?:array<string,mixed>}>
|
||||
*/
|
||||
public static function cap_definitions(): array {
|
||||
return [
|
||||
@@ -20,16 +20,11 @@ class Capabilities {
|
||||
'balance_transfer' => ['label' => __('余额转账给其他用户', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'points_transfer' => ['label' => __('积分转账给其他用户', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'balance_withdraw' => ['label' => __('余额提现', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'set_user_ban' => ['label' => __('设置用户封禁状态', 'aksrt'), 'roles' => ['cat_moderator'], 'default' => ['cat_moderator' => true]],
|
||||
'medal_manually_set' => ['label' => __('为用户授予徽章', 'aksrt'), 'roles' => ['cat_moderator'], 'default' => ['cat_moderator' => true]],
|
||||
|
||||
'new_post_add' => ['label' => __('发布新文章', 'aksrt'), 'default' => ['logged' => true]],
|
||||
'new_post_audit_no' => ['label' => __('投稿无需审核直接发布', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'new_post_audit_no_manual' => ['label' => __('无需人工审核直接发布', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'new_post_upload_img' => ['label' => __('投稿允许上传图片', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'new_post_upload_video' => ['label' => __('投稿允许上传视频', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'new_post_iframe_video' => ['label' => __('投稿允许插入 iframe 视频', 'aksrt'), 'exclude_roles' => ['all'], 'default' => ['logged' => true]],
|
||||
'new_post_upload_file' => ['label' => __('投稿允许上传文件', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'new_post_hide' => ['label' => __('投稿允许发布隐藏内容', 'aksrt'), 'default' => ['logged' => true]],
|
||||
'new_post_image_cover' => ['label' => __('发帖允许设置图片封面', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'new_post_slide_cover' => ['label' => __('允许设置幻灯片封面', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
@@ -40,18 +35,26 @@ class Capabilities {
|
||||
'new_post_delete' => ['label' => __('删除自己发布的投稿', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
|
||||
'comment_view' => ['label' => __('查看评论', 'aksrt'), 'default' => ['all' => true]],
|
||||
'comment_add' => ['label' => __('发表评论', 'aksrt'), 'default' => ['logged' => true]],
|
||||
'comment_edit' => ['label' => __('修改自己发布的评论', 'aksrt'), 'exclude_roles' => ['all'], 'default' => ['logged' => true]],
|
||||
'comment_delete' => ['label' => __('删除自己发布的评论', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_audit_no' => ['label' => __('评论无需审核直接发布', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_audit_no_manual' => ['label' => __('评论无需人工审核', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_set_topping_my_post' => ['label' => __('置顶自己文章下的评论', 'aksrt'), 'exclude_roles' => ['all'], 'default' => ['moderator' => true, 'plate_author' => true, 'cat_moderator' => true]],
|
||||
'comment_set_topping_other' => ['label' => __('置顶管理帖子下的评论', 'aksrt'), 'roles' => ['moderator', 'plate_author', 'cat_moderator'], 'default' => ['cat_moderator' => true]],
|
||||
'comment_set_topping_my_post' => ['label' => __('置顶自己文章下的评论', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_set_topping_other' => ['label' => __('置顶其他文章下的评论', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_edit_my_post' => ['label' => __('修改自己文章下的评论', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_edit_other' => ['label' => __('修改管理帖子下的评论', 'aksrt'), 'roles' => ['moderator', 'plate_author', 'cat_moderator'], 'default' => ['cat_moderator' => true]],
|
||||
'comment_audit_my_post' => ['label' => __('审核自己文章下的评论', 'aksrt'), 'exclude_roles' => ['all'], 'default' => ['moderator' => true, 'plate_author' => true, 'cat_moderator' => true]],
|
||||
'comment_audit_other' => ['label' => __('审核管理帖子下的评论', 'aksrt'), 'roles' => ['moderator', 'plate_author', 'cat_moderator'], 'default' => ['moderator' => true, 'plate_author' => true, 'cat_moderator' => true]],
|
||||
'comment_edit_other' => ['label' => __('修改其他文章下的评论', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_audit_my_post' => ['label' => __('审核自己文章下的评论', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_audit_other' => ['label' => __('审核其他文章下的评论', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_delete_my_post' => ['label' => __('删除自己文章下的评论', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'comment_delete_other' => ['label' => __('删除管理帖子下的评论', 'aksrt'), 'roles' => ['moderator', 'plate_author', 'cat_moderator'], 'default' => ['cat_moderator' => true]],
|
||||
'comment_delete_other' => ['label' => __('删除其他文章下的评论', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
|
||||
'upload_avatar' => ['label' => __('上传头像', 'aksrt'), 'default' => ['logged' => true]],
|
||||
'upload_profile_background' => ['label' => __('上传用户资料卡背景图', 'aksrt'), 'default' => ['logged' => true]],
|
||||
'upload_submit_image' => ['label' => __('投稿图片上传', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'upload_comment_image' => ['label' => __('评论图片上传', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'upload_resource_attachment'=> ['label' => __('资源附件上传', 'aksrt'), 'exclude_roles' => ['all']],
|
||||
'resource_download' => ['label' => __('资源下载', 'aksrt'), 'default' => ['logged' => true]],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -59,7 +62,7 @@ class Capabilities {
|
||||
* All role columns in the capability matrix.
|
||||
*/
|
||||
public static function cap_role_columns(): array {
|
||||
return ['all', 'logged', 'level', 'vip', 'auth', 'moderator', 'plate_author', 'cat_moderator'];
|
||||
return ['all', 'logged', 'level', 'vip', 'auth'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,14 +80,9 @@ class Capabilities {
|
||||
foreach ($defs as $cap_id => $def) {
|
||||
$defaults = $def['default'] ?? [];
|
||||
$excludes = $def['exclude_roles'] ?? [];
|
||||
$only_roles = $def['roles'] ?? null;
|
||||
|
||||
$row = [];
|
||||
foreach ($cols as $col) {
|
||||
if ($only_roles !== null && !in_array($col, $only_roles, true) && !in_array($col, ['all', 'logged', 'level', 'vip', 'auth'], true)) {
|
||||
$row[$col] = false;
|
||||
continue;
|
||||
}
|
||||
if (in_array($col, $excludes, true)) {
|
||||
$row[$col] = false;
|
||||
continue;
|
||||
|
||||
@@ -121,9 +121,7 @@ trait CreditsPaymentGroups {
|
||||
* Unified paid-content and download policy settings.
|
||||
*
|
||||
* @return array{
|
||||
* download_login:bool,
|
||||
* download_credit:bool,
|
||||
* download_captcha:bool,
|
||||
* default_cost:int,
|
||||
* paid_post_types:string[],
|
||||
* vip_free_download:bool,
|
||||
@@ -194,7 +192,7 @@ trait CreditsPaymentGroups {
|
||||
/**
|
||||
* 获取订单设置
|
||||
*
|
||||
* @return array{prefix:string,expiry:int,show_user:bool,show_paycount:bool,no_logged_in:bool,cookie_day:int,no_logged_remind:bool,wait_float:bool,order_max_minutes:int,mark:string,box_position:string,free_logged_show:bool,show_ip:bool,show_device:bool,show_order_no:bool,show_create_time:bool,show_pay_time:bool,status_texts:array,service:array}
|
||||
* @return array{prefix:string,expiry:int,show_user:bool,show_paycount:bool,no_logged_in:bool,cookie_day:int,no_logged_remind:bool,wait_float:bool,order_max_minutes:int,mark:string,box_position:string,show_ip:bool,show_device:bool,show_order_no:bool,show_create_time:bool,show_pay_time:bool,status_texts:array,service:array}
|
||||
*/
|
||||
public static function get_order_settings(): array {
|
||||
return PaymentRuntime::get_order_settings();
|
||||
|
||||
@@ -137,9 +137,7 @@ class HomeCms {
|
||||
public static function get_cms_features(): array {
|
||||
$defaults = [
|
||||
'resource_cpt_enabled' => true,
|
||||
'download_login' => true,
|
||||
'download_credit' => true,
|
||||
'download_captcha' => false,
|
||||
'search_include_resource' => true,
|
||||
];
|
||||
$s = Settings::get_all();
|
||||
|
||||
@@ -27,9 +27,7 @@ class PaymentRuntime {
|
||||
}
|
||||
|
||||
return [
|
||||
'download_login' => !empty($features['download_login']),
|
||||
'download_credit' => !empty($features['download_credit']) && Settings::credits_enabled(),
|
||||
'download_captcha' => !empty($features['download_captcha']),
|
||||
'default_cost' => Settings::download_default_cost(),
|
||||
'paid_post_types' => self::parse_slug_list((string) ($settings['payment_paid_post_types'] ?? 'post'), ['post']),
|
||||
'vip_free_download' => !empty($settings['payment_vip_free_download']),
|
||||
@@ -37,7 +35,6 @@ class PaymentRuntime {
|
||||
'catalog_note' => $catalog_note,
|
||||
'manual_delivery_note' => $manual_note !== '' ? $manual_note : __('付费或人工确认内容请在订单备注中记录交付方式。', 'aksrt'),
|
||||
'no_logged_in' => !isset($settings['pay_no_logged_in']) || !empty($settings['pay_no_logged_in']),
|
||||
'free_logged_show' => !empty($settings['pay_free_logged_show']),
|
||||
'down_alone_page' => !empty($settings['payment_down_alone_page']),
|
||||
'display' => [
|
||||
'mark' => sanitize_text_field((string) ($settings['pay_mark'] ?? '付费')),
|
||||
@@ -375,7 +372,6 @@ class PaymentRuntime {
|
||||
'order_max_minutes' => $max_minutes,
|
||||
'mark' => sanitize_text_field((string) ($s['pay_mark'] ?? '付费')),
|
||||
'box_position' => $box_position,
|
||||
'free_logged_show' => !empty($s['pay_free_logged_show']),
|
||||
'show_ip' => !empty($s['payment_show_ip']),
|
||||
'show_device' => !empty($s['payment_show_device']),
|
||||
'show_order_no' => !isset($s['payment_show_order_no']) || !empty($s['payment_show_order_no']),
|
||||
|
||||
@@ -461,14 +461,13 @@ class PostDisplay {
|
||||
'per_page' => max(5, min(100, (int) ($settings['comment_per_page'] ?? 20))),
|
||||
'nested_depth' => max(1, min(10, (int) ($settings['comment_nested_depth'] ?? 3))),
|
||||
'require_login' => Settings::comment_requires_registration(),
|
||||
'allow_reply' => !isset($settings['comment_allow_reply']) || !empty($settings['comment_allow_reply']),
|
||||
'avatar_size' => max(24, min(96, (int) ($settings['comment_avatar_size'] ?? 48))),
|
||||
'empty_text' => $empty_text !== '' ? $empty_text : __('暂无评论,来做第一个评论的人吧~', 'aksrt'),
|
||||
'toolbar' => [
|
||||
'smilie' => !isset($settings['comment_smilie']) || !empty($settings['comment_smilie']),
|
||||
'code' => !isset($settings['comment_code']) || !empty($settings['comment_code']),
|
||||
'image' => !isset($settings['comment_img']) || !empty($settings['comment_img']),
|
||||
'upload_img' => !empty($settings['comment_upload_img']),
|
||||
'upload_img' => function_exists('aksrt_user_can') && aksrt_user_can('upload_comment_image', get_current_user_id()),
|
||||
],
|
||||
'like_enabled' => !isset($settings['comment_like_s']) || !empty($settings['comment_like_s']),
|
||||
'author_tag' => !isset($settings['comment_author_tag']) || !empty($settings['comment_author_tag']),
|
||||
|
||||
@@ -573,7 +573,7 @@ class Settings {
|
||||
/**
|
||||
* Private user-center display settings.
|
||||
*
|
||||
* @return array{tabs:string[],avatar_upload:bool,cover_upload:bool,social_links:bool}
|
||||
* @return array{tabs:string[]}
|
||||
*/
|
||||
public static function get_user_center_settings(): array {
|
||||
return UserRuntime::get_user_center_settings();
|
||||
@@ -705,7 +705,7 @@ class Settings {
|
||||
/**
|
||||
* Comment display and behavior settings.
|
||||
*
|
||||
* @return array{order:string,pagination_mode:string,nested_depth:int,require_login:bool,allow_reply:bool,avatar_size:int,empty_text:string}
|
||||
* @return array{order:string,pagination_mode:string,nested_depth:int,require_login:bool,avatar_size:int,empty_text:string}
|
||||
*/
|
||||
public static function get_comment_display_settings(): array {
|
||||
return PostDisplay::get_comment_display_settings();
|
||||
|
||||
@@ -80,7 +80,7 @@ class UserRuntime {
|
||||
/**
|
||||
* Private user-center display settings.
|
||||
*
|
||||
* @return array{tabs:string[],avatar_upload:bool,cover_upload:bool,social_links:bool}
|
||||
* @return array{tabs:string[]}
|
||||
*/
|
||||
public static function get_user_center_settings(): array {
|
||||
$settings = Settings::get_all();
|
||||
@@ -133,9 +133,6 @@ class UserRuntime {
|
||||
|
||||
return [
|
||||
'tabs' => $normalized_tabs,
|
||||
'avatar_upload' => !isset($settings['allow_avatar_upload']) || !empty($settings['allow_avatar_upload']),
|
||||
'cover_upload' => !isset($settings['user_cover_upload']) || !empty($settings['user_cover_upload']),
|
||||
'social_links' => !isset($settings['user_social_links']) || !empty($settings['user_social_links']),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -409,9 +409,6 @@ class Badges {
|
||||
if (user_can($actor, 'manage_options')) {
|
||||
return true;
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Capabilities')) {
|
||||
return \AKSRT\Settings\Capabilities::user_can('medal_manually_set', $actor);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+5
-8
@@ -113,14 +113,11 @@ class Center {
|
||||
* 注册默认面板
|
||||
*/
|
||||
private function register_default_panels(): void {
|
||||
$settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_user_center_settings()
|
||||
: [
|
||||
'tabs' => ['profile', 'comments', 'favorites'],
|
||||
'avatar_upload' => true,
|
||||
'cover_upload' => true,
|
||||
'social_links' => true,
|
||||
];
|
||||
$settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_user_center_settings()
|
||||
: [
|
||||
'tabs' => ['profile', 'comments', 'favorites'],
|
||||
];
|
||||
|
||||
$definitions = [
|
||||
'profile' => [
|
||||
|
||||
Reference in New Issue
Block a user