feat: full parity pass - suspend enforcement, report modal polish, wechat notify
- 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)
This commit is contained in:
+18
-1
@@ -79,7 +79,7 @@
|
|||||||
|
|
||||||
// 弹窗头:参考主题 edit-header 同构(关闭钮 + 红色警示徽标 + 标题)。
|
// 弹窗头:参考主题 edit-header 同构(关闭钮 + 红色警示徽标 + 标题)。
|
||||||
html += '<div class="mb10 edit-header touch">';
|
html += '<div class="mb10 edit-header touch">';
|
||||||
html += '<button type="button" class="close" data-aksrt-modal-close aria-label="关闭"><i class="fa fa-times" aria-hidden="true"></i></button>';
|
html += '<button type="button" class="close" data-aksrt-modal-close aria-label="关闭"><svg class="ic-close" aria-hidden="true"><use xlink:href="#icon-close"></use></svg></button>';
|
||||||
html += '<b class="flex ac c-red"><span class="toggle-radius mr10 c-red"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i></span>' + escapeHtml(title) + '</b>';
|
html += '<b class="flex ac c-red"><span class="toggle-radius mr10 c-red"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i></span>' + escapeHtml(title) + '</b>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
@@ -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) {
|
function bindReportModal(shell) {
|
||||||
|
shell.querySelectorAll('textarea.form-control').forEach(bindAutoHeight);
|
||||||
|
|
||||||
shell.addEventListener('click', function (event) {
|
shell.addEventListener('click', function (event) {
|
||||||
// 原因徽章:单选 active(参考主题 .active.badg 焦点色),其它原因展开文本框。
|
// 原因徽章:单选 active(参考主题 .active.badg 焦点色),其它原因展开文本框。
|
||||||
// 参考主题行为:重复点击已选徽章保持选中(先全清再激活),不做反选。
|
// 参考主题行为:重复点击已选徽章保持选中(先全清再激活),不做反选。
|
||||||
|
|||||||
@@ -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 ($is_edit) {
|
||||||
if (!$this->can_edit_post($posts_id, $user_id)) {
|
if (!$this->can_edit_post($posts_id, $user_id)) {
|
||||||
return ['success' => false, 'message' => __('没有编辑权限', 'aksrt')];
|
return ['success' => false, 'message' => __('没有编辑权限', 'aksrt')];
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ class WeChatNotify {
|
|||||||
|
|
||||||
private static $instance = null;
|
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 {
|
public static function get_instance(): self {
|
||||||
if (null === self::$instance) self::$instance = new self();
|
if (null === self::$instance) self::$instance = new self();
|
||||||
return self::$instance;
|
return self::$instance;
|
||||||
@@ -134,6 +140,14 @@ class WeChatNotify {
|
|||||||
$url = $data['url'] ?? home_url('/');
|
$url = $data['url'] ?? home_url('/');
|
||||||
$values = $data['values'] ?? [];
|
$values = $data['values'] ?? [];
|
||||||
|
|
||||||
|
// 通用兜底:站内通知未带模板字段时,用标题/内容合成。
|
||||||
|
if (empty($values)) {
|
||||||
|
$values = array_filter([
|
||||||
|
'first' => (string) ($data['title'] ?? ''),
|
||||||
|
'keyword1' => (string) ($data['description'] ?? ''),
|
||||||
|
], static function ($v) { return $v !== ''; });
|
||||||
|
}
|
||||||
|
|
||||||
$template_data = [];
|
$template_data = [];
|
||||||
foreach ($values as $key => $value) {
|
foreach ($values as $key => $value) {
|
||||||
$template_data[$key] = [
|
$template_data[$key] = [
|
||||||
@@ -172,9 +186,8 @@ class WeChatNotify {
|
|||||||
$config = $this->get_config();
|
$config = $this->get_config();
|
||||||
if (empty($config['enabled'])) return;
|
if (empty($config['enabled'])) return;
|
||||||
|
|
||||||
// 仅特定类型通知发送微信
|
// 仅特定类型通知发送微信(含举报处理结果与账号限制通知)
|
||||||
$allowed_types = ['comment_reply', 'system_notice', 'order_paid', 'credits_change'];
|
if (!in_array($type, self::NOTIFY_TYPES, true)) return;
|
||||||
if (!in_array($type, $allowed_types, true)) return;
|
|
||||||
|
|
||||||
$this->send($user_id, $type, $data);
|
$this->send($user_id, $type, $data);
|
||||||
}
|
}
|
||||||
@@ -219,7 +232,7 @@ class WeChatNotify {
|
|||||||
'has_template' => !empty($config['template_id']),
|
'has_template' => !empty($config['template_id']),
|
||||||
'bound_users' => $bound_count,
|
'bound_users' => $bound_count,
|
||||||
'app_secret' => !empty($config['app_secret']) ? '***' : '',
|
'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,
|
'system_notice' => true,
|
||||||
'order_paid' => true,
|
'order_paid' => true,
|
||||||
'credits_change' => false,
|
'credits_change' => false,
|
||||||
|
'report' => true,
|
||||||
|
'system' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return $subs;
|
return $subs;
|
||||||
@@ -243,7 +258,7 @@ class WeChatNotify {
|
|||||||
* Update notification subscriptions for a user.
|
* Update notification subscriptions for a user.
|
||||||
*/
|
*/
|
||||||
public function update_user_subscriptions(int $user_id, array $subscriptions): bool {
|
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 = [];
|
$clean = [];
|
||||||
foreach ($allowed as $key) {
|
foreach ($allowed as $key) {
|
||||||
$clean[$key] = !empty($subscriptions[$key]);
|
$clean[$key] = !empty($subscriptions[$key]);
|
||||||
|
|||||||
@@ -182,6 +182,28 @@ class Restriction {
|
|||||||
return $this->is_restricted($user_id, self::TYPE_BAN, self::SCOPE_GLOBAL);
|
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).
|
* 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) {
|
public function enforce_comment_mute($post_id) {
|
||||||
$user_id = get_current_user_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]);
|
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;
|
return $post_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -284,3 +284,38 @@ test('restriction REST operations pass expiry through to the service', () => {
|
|||||||
assert.match(restrictionOps, /\$params\['expires_at'\] \?\? ''/)
|
assert.match(restrictionOps, /\$params\['expires_at'\] \?\? ''/)
|
||||||
assert.match(restrictionOps, /Restriction::get_instance\(\)->restrict\(/)
|
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, /<svg class="ic-close" aria-hidden="true"><use xlink:href="#icon-close"><\/use><\/svg>/)
|
||||||
|
assert.ok(!reportJs.includes('fa-times'), 'close icon must use the SVG sprite, not FontAwesome')
|
||||||
|
// textarea 自动增高(参考主题 autoHeight 行为),隐藏中的“其它原因”跳过首次计算。
|
||||||
|
assert.match(reportJs, /function bindAutoHeight\(/)
|
||||||
|
assert.match(reportJs, /querySelectorAll\('textarea\.form-control'\)\.forEach\(bindAutoHeight\)/)
|
||||||
|
assert.match(reportJs, /offsetHeight > 0\) grow\(\)/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('wechat template messages cover report and account restriction types', () => {
|
||||||
|
const wechat = read('src/Payment/WeChatNotify.php')
|
||||||
|
assert.match(wechat, /const NOTIFY_TYPES = \['comment_reply', 'system_notice', 'order_paid', 'credits_change', 'report', 'system'\]/)
|
||||||
|
assert.match(wechat, /in_array\(\$type, self::NOTIFY_TYPES, true\)/)
|
||||||
|
// 订阅白名单与状态输出复用同一常量,不允许再出现硬编码子集。
|
||||||
|
assert.ok(!wechat.includes("['comment_reply', 'system_notice', 'order_paid', 'credits_change']"), 'type list must come from NOTIFY_TYPES')
|
||||||
|
// 站内通知缺省模板字段时用 title/description 兜底合成。
|
||||||
|
assert.match(wechat, /\$data\['description'\] \?\? ''/)
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user