From 0df6e760e39095f4ae393ef2bc23903008555c75 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 20 Sep 2026 16:09:12 +0800 Subject: [PATCH] feat: full parity pass - suspend enforcement, report modal polish, wechat notify MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restriction: add is_suspended/get_active_suspend; comment block now covers suspend (小黑屋) in addition to mute, with reason in the deny message - FrontSubmitSave::persist: block post/draft/edit submission for suspended users (reference-theme semantics: 小黑屋 loses all publishing rights) - report.js: close icon switched from fa-times to the SVG sprite ic-close (#icon-close), same as reference theme; textareas gain autoHeight growth (hidden 'other reason' box skips the first measurement) - WeChatNotify: NOTIFY_TYPES now includes report/system so report results and account restriction notices fan out to WeChat template messages; generic title/description fallback for template values - contract tests: +3 tests (suspend enforcement, modal polish, wechat types) --- assets/js/report.js | 19 ++++++++- src/Core/FrontSubmitSave.php | 10 +++++ src/Payment/WeChatNotify.php | 25 +++++++++--- src/User/Restriction.php | 40 ++++++++++++++++++- .../report-ban-contract.test.cjs | 35 ++++++++++++++++ 5 files changed, 121 insertions(+), 8 deletions(-) diff --git a/assets/js/report.js b/assets/js/report.js index 7b7a047c..2a05bba4 100644 --- a/assets/js/report.js +++ b/assets/js/report.js @@ -79,7 +79,7 @@ // 弹窗头:参考主题 edit-header 同构(关闭钮 + 红色警示徽标 + 标题)。 html += '
'; - html += ''; + html += ''; html += '' + escapeHtml(title) + ''; html += '
'; @@ -284,7 +284,24 @@ }); } + function bindAutoHeight(textarea) { + // 对齐参考主题 autoHeight 行为:随内容自动增高,最小保持初始行数高度。 + // 隐藏中的 textarea(如“其它原因”)跳过首次计算,待可见后按实际高度增长。 + if (!textarea || textarea.dataset.aksrtAutoHeight === '1') return; + textarea.dataset.aksrtAutoHeight = '1'; + var minHeight = 0; + var grow = function () { + if (!minHeight) minHeight = textarea.offsetHeight || 0; + textarea.style.height = 'auto'; + textarea.style.height = Math.max(textarea.scrollHeight, minHeight) + 'px'; + }; + textarea.addEventListener('input', grow); + if (textarea.offsetHeight > 0) grow(); + } + function bindReportModal(shell) { + shell.querySelectorAll('textarea.form-control').forEach(bindAutoHeight); + shell.addEventListener('click', function (event) { // 原因徽章:单选 active(参考主题 .active.badg 焦点色),其它原因展开文本框。 // 参考主题行为:重复点击已选徽章保持选中(先全清再激活),不做反选。 diff --git a/src/Core/FrontSubmitSave.php b/src/Core/FrontSubmitSave.php index ebc806a8..2208fb7a 100644 --- a/src/Core/FrontSubmitSave.php +++ b/src/Core/FrontSubmitSave.php @@ -43,6 +43,16 @@ trait FrontSubmitSave { } } + // 小黑屋(suspend):剥夺发布权限,发帖/草稿/编辑均拦截(对齐参考主题)。 + if (!$is_guest && \AKSRT\User\Restriction::get_instance()->is_suspended($user_id)) { + $suspend = \AKSRT\User\Restriction::get_instance()->get_active_suspend($user_id); + $message = __('您已被关入小黑屋,暂时无法发布内容', 'aksrt'); + if (!empty($suspend['reason'])) { + $message .= ',原因:' . $suspend['reason']; + } + return ['success' => false, 'message' => $message]; + } + if ($is_edit) { if (!$this->can_edit_post($posts_id, $user_id)) { return ['success' => false, 'message' => __('没有编辑权限', 'aksrt')]; diff --git a/src/Payment/WeChatNotify.php b/src/Payment/WeChatNotify.php index 88019568..07936c6a 100644 --- a/src/Payment/WeChatNotify.php +++ b/src/Payment/WeChatNotify.php @@ -11,6 +11,12 @@ class WeChatNotify { private static $instance = null; + /** + * 可联动微信模板消息的站内通知类型。 + * report:举报处理结果;system:禁封/限制/解除等账号限制通知。 + */ + const NOTIFY_TYPES = ['comment_reply', 'system_notice', 'order_paid', 'credits_change', 'report', 'system']; + public static function get_instance(): self { if (null === self::$instance) self::$instance = new self(); return self::$instance; @@ -134,6 +140,14 @@ class WeChatNotify { $url = $data['url'] ?? home_url('/'); $values = $data['values'] ?? []; + // 通用兜底:站内通知未带模板字段时,用标题/内容合成。 + if (empty($values)) { + $values = array_filter([ + 'first' => (string) ($data['title'] ?? ''), + 'keyword1' => (string) ($data['description'] ?? ''), + ], static function ($v) { return $v !== ''; }); + } + $template_data = []; foreach ($values as $key => $value) { $template_data[$key] = [ @@ -172,9 +186,8 @@ class WeChatNotify { $config = $this->get_config(); if (empty($config['enabled'])) return; - // 仅特定类型通知发送微信 - $allowed_types = ['comment_reply', 'system_notice', 'order_paid', 'credits_change']; - if (!in_array($type, $allowed_types, true)) return; + // 仅特定类型通知发送微信(含举报处理结果与账号限制通知) + if (!in_array($type, self::NOTIFY_TYPES, true)) return; $this->send($user_id, $type, $data); } @@ -219,7 +232,7 @@ class WeChatNotify { 'has_template' => !empty($config['template_id']), 'bound_users' => $bound_count, 'app_secret' => !empty($config['app_secret']) ? '***' : '', - 'allowed_types' => ['comment_reply', 'system_notice', 'order_paid', 'credits_change'], + 'allowed_types' => self::NOTIFY_TYPES, ]; } @@ -234,6 +247,8 @@ class WeChatNotify { 'system_notice' => true, 'order_paid' => true, 'credits_change' => false, + 'report' => true, + 'system' => true, ]; } return $subs; @@ -243,7 +258,7 @@ class WeChatNotify { * Update notification subscriptions for a user. */ public function update_user_subscriptions(int $user_id, array $subscriptions): bool { - $allowed = ['comment_reply', 'system_notice', 'order_paid', 'credits_change']; + $allowed = self::NOTIFY_TYPES; $clean = []; foreach ($allowed as $key) { $clean[$key] = !empty($subscriptions[$key]); diff --git a/src/User/Restriction.php b/src/User/Restriction.php index cf797c00..a3c52da2 100644 --- a/src/User/Restriction.php +++ b/src/User/Restriction.php @@ -182,6 +182,28 @@ class Restriction { return $this->is_restricted($user_id, self::TYPE_BAN, self::SCOPE_GLOBAL); } + /** + * Check if a user is in 小黑屋 (active global suspend). + * + * 对齐参考主题语义:小黑屋用户失去所有发布权限(发帖、评论等)及大部分操作权限。 + */ + public function is_suspended(int $user_id): bool { + return $this->is_restricted($user_id, self::TYPE_SUSPEND, self::SCOPE_GLOBAL); + } + + /** + * Get the active suspend restriction for a user (if any). + */ + public function get_active_suspend(int $user_id): ?array { + $restrictions = $this->get_user_restrictions($user_id, true); + foreach ($restrictions as $r) { + if ($r['type'] === self::TYPE_SUSPEND && $r['scope'] === self::SCOPE_GLOBAL) { + return $r; + } + } + return null; + } + /** * Get the active ban restriction for a user (if any). */ @@ -214,13 +236,27 @@ class Restriction { } /** - * Enforce comment mute. + * Enforce comment restriction on submission (mute / 小黑屋). + * + * 禁言(mute,评论域)与小黑屋(suspend,全域)都会拦截评论—— + * 对齐参考主题:小黑屋用户失去评论等发布权限。 */ public function enforce_comment_mute($post_id) { $user_id = get_current_user_id(); - if ($user_id > 0 && $this->is_restricted($user_id, self::TYPE_MUTE, self::SCOPE_COMMENT)) { + if ($user_id <= 0) { + return $post_id; + } + if ($this->is_restricted($user_id, self::TYPE_MUTE, self::SCOPE_COMMENT)) { wp_die('您已被限制评论。', '评论限制', ['response' => 403]); } + if ($this->is_suspended($user_id)) { + $suspend = $this->get_active_suspend($user_id); + $message = '您已被关入小黑屋,暂时无法评论。'; + if (!empty($suspend['reason'])) { + $message .= '原因:' . $suspend['reason']; + } + wp_die($message, '发布限制', ['response' => 403]); + } return $post_id; } diff --git a/tests/widget-system/report-ban-contract.test.cjs b/tests/widget-system/report-ban-contract.test.cjs index d679718f..005856ad 100644 --- a/tests/widget-system/report-ban-contract.test.cjs +++ b/tests/widget-system/report-ban-contract.test.cjs @@ -284,3 +284,38 @@ test('restriction REST operations pass expiry through to the service', () => { assert.match(restrictionOps, /\$params\['expires_at'\] \?\? ''/) assert.match(restrictionOps, /Restriction::get_instance\(\)->restrict\(/) }) + +test('suspend (小黑屋) enforcement blocks publishing end to end', () => { + // 服务层提供 is_suspended / get_active_suspend(对齐参考主题:小黑屋 + // 失去所有发布权限——发帖、评论等;此前 suspend 只在公开主页显示提示)。 + assert.match(restriction, /public function is_suspended\(int \$user_id\): bool/) + assert.match(restriction, /public function get_active_suspend\(int \$user_id\): \?array/) + // 前台发帖入口(FrontSubmitSave::persist)拦截发帖/草稿/编辑。 + const frontSave = read('src/Core/FrontSubmitSave.php') + assert.match(frontSave, /Restriction::get_instance\(\)->is_suspended\(\$user_id\)/) + assert.match(frontSave, /您已被关入小黑屋/) + // 评论提交(pre_comment_on_post)同时覆盖禁言(mute)与小黑屋(suspend)。 + assert.match(restriction, /enforce_comment_mute[\s\S]*?is_suspended\(\$user_id\)/) + assert.match(restriction, /您已被关入小黑屋,暂时无法评论/) +}) + +test('report modal close icon and textarea autoHeight match the reference theme', () => { + const reportJs = read('assets/js/report.js') + // 关闭按钮走 SVG 雪碧图 icon-close(与参考主题 .ic-close 同构,fa-times 退役)。 + assert.match(reportJs, /