feat(ban): mirror reference theme ban/restriction prompt surface
修复公开主页禁封提示框为空的问题,并对齐参考主题(Zibll)全部前台禁封提示入口: - fix(profile): 资料卡下方治理提示框渲染为空——模板读取的 tone/message 键与 数据层实际输出的 state/label/text 契约不匹配;现按禁封类型展示 「违规已封号/小黑屋禁封中/禁言中」+ 可配置说明文案 - feat(ban): 用户中心页头展示治理状态徽章 + 查看详情入口,直达禁封申诉页 (参考主题 user_page_header_desc / zib_get_user_ban_info_link) - feat(ban): 评论拒绝态区分禁封/小黑屋/禁言,附状态徽章与申诉链接 (参考主题 zib_get_user_ban_nocan_info,原为笼统的无权限文案) - feat(ban): 禁封申诉页补全状态信息卡(状态/开始时间/结束时间/原因/说明/ 申诉权限),对齐 zib_get_ban_info_modal;此前仅有「禁封原因」一行且无入口 - feat(ban): 昵称旁禁封徽章 + 头像禁封角标(覆盖 VIP 位,参考主题 user_name_badge / user_avatar_badge 钩子语义),main.css 与入队的 main.min.css 同步新增角标样式 - fix(ban): 举报入口对已封禁/小黑屋目标隐藏(评论、文章下拉、封面菜单三处, 对齐 zib_get_report_link 排除语义;禁言为轻量限制不隐藏) - refactor(ban): 治理展示态收敛为单一数据源 Restriction::get_display_state() + state_labels(),资料卡/用户中心/评论拒绝态/昵称徽章共用 - feat(ban): 小黑屋用户开放申诉(对齐参考主题 zib_user_is_ban 双类型语义), 禁封站内信附加申诉页链接 - test: report-ban 契约测试 +3(展示态单一数据源、提示面入口矩阵、举报排除), 45 个测试文件全部通过
This commit is contained in:
@@ -1536,6 +1536,25 @@ svg.loading-circle.zts circle,
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* 头像禁封角标(禁封红 / 小黑屋-禁言黄,覆盖 VIP 角标位) */
|
||||
.aksrt-avatar-ban-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.aksrt-avatar-ban-badge.is-banned {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.aksrt-avatar-ban-badge.is-restricted {
|
||||
background: #f59e0b;
|
||||
}
|
||||
|
||||
.blur * {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -237,7 +237,14 @@ if (!function_exists('aksrt_get_avatar_box')) {
|
||||
$img = aksrt_get_avatar_html($subject, 80, ['class' => 'avatar']);
|
||||
|
||||
$badge = '';
|
||||
if ($vip && $user_id > 0 && function_exists('aksrt_get_user_vip_level') && aksrt_get_user_vip_level($user_id) > 0) {
|
||||
if ($vip && $user_id > 0) {
|
||||
// 禁封角标覆盖 VIP 角标位(参考主题 user_avatar_badge 过滤器覆盖语义)。
|
||||
$ban_badge = function_exists('aksrt_get_user_ban_avatar_badge')
|
||||
? aksrt_get_user_ban_avatar_badge($user_id)
|
||||
: '';
|
||||
if ($ban_badge !== '') {
|
||||
$badge = $ban_badge;
|
||||
} elseif (function_exists('aksrt_get_user_vip_level') && aksrt_get_user_vip_level($user_id) > 0) {
|
||||
$icon_url = '';
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||||
@@ -249,6 +256,7 @@ if (!function_exists('aksrt_get_avatar_box')) {
|
||||
$badge = '<img class="avatar-badge" src="' . $icon_url . '" title="' . esc_attr($badge_text) . '" alt="' . esc_attr($badge_text) . '">';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$html = '<span class="' . esc_attr($class) . '">' . $img . $badge . '</span>';
|
||||
if ($link && $user_id > 0) {
|
||||
|
||||
@@ -458,9 +458,18 @@ if (!function_exists('aksrt_comments_render_form')) :
|
||||
if (!$can_comment) {
|
||||
if (!$is_logged_in) {
|
||||
aksrt_comments_render_signarea($ctx);
|
||||
} else {
|
||||
// 治理状态区分提示(参考主题 zib_get_user_ban_nocan_info 语义:
|
||||
// 禁封中/小黑屋/禁言显示状态徽章与查看入口,而非笼统的无权限文案)。
|
||||
$ban_nocan = function_exists('aksrt_get_user_ban_nocan_html')
|
||||
? aksrt_get_user_ban_nocan_html(get_current_user_id(), __('暂时无法发表评论', 'aksrt'))
|
||||
: '';
|
||||
if ($ban_nocan !== '') {
|
||||
echo $ban_nocan; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper escapes internally.
|
||||
} else {
|
||||
echo '<p class="comments-permission-denied muted-2-color">' . esc_html__('当前账号没有发表评论的权限。', 'aksrt') . '</p>';
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -191,9 +191,15 @@ if (!function_exists('aksrt_get_user_name')) {
|
||||
: get_author_posts_url($user_id);
|
||||
$display = $user->display_name;
|
||||
$vip_html = '';
|
||||
if (!empty($args['vip']) && function_exists('aksrt_render_vip_badge')) {
|
||||
if (!empty($args['vip'])) {
|
||||
// 禁封徽章占据 VIP 图标位(参考主题 user_name_badge 过滤器覆盖语义)。
|
||||
$vip_html = function_exists('aksrt_get_user_ban_name_badge')
|
||||
? aksrt_get_user_ban_name_badge($user_id)
|
||||
: '';
|
||||
if ($vip_html === '' && function_exists('aksrt_render_vip_badge')) {
|
||||
$vip_html = (string) aksrt_render_vip_badge($user_id, 'sm');
|
||||
}
|
||||
}
|
||||
$name_class = trim('display-name text-ellipsis ' . (string) $args['name_class']);
|
||||
$name_link = '<a class="' . esc_attr($name_class) . '" href="' . esc_url($home) . '">' . esc_html($display) . '</a>';
|
||||
|
||||
|
||||
@@ -142,7 +142,8 @@ if (!function_exists('aksrt_get_single_meta_box')) :
|
||||
$more .= '<a href="' . esc_url($edit_link) . '" class="post-drop-meta__item"><i class="fa fas fa-edit" aria-hidden="true"></i><span>' . esc_html__('编辑文章', 'aksrt') . '</span></a>';
|
||||
}
|
||||
$more .= '<button type="button" class="post-drop-meta__item" data-share-copy data-share-url="' . esc_url((string) $permalink) . '"><i class="fa fas fa-link" aria-hidden="true"></i><span>' . esc_html__('复制链接', 'aksrt') . '</span></button>';
|
||||
if ($ban_item === '' && is_user_logged_in()) {
|
||||
$post_author_id = (int) get_post_field('post_author', $post_id);
|
||||
if ($ban_item === '' && is_user_logged_in() && !(function_exists('aksrt_is_user_ban_restricted') && $post_author_id > 0 && aksrt_is_user_ban_restricted($post_author_id))) {
|
||||
$report_payload = wp_json_encode([
|
||||
'target_type' => 'post',
|
||||
'target_id' => (int) $post_id,
|
||||
|
||||
@@ -26,7 +26,7 @@ defined('ABSPATH') || exit;
|
||||
* @type array $profile_details Server-filtered fixed public profile fields.
|
||||
* @type array $profile_signals Identity chips (verified, level, badges).
|
||||
* @type array $verify_state Verification tri-state {state,label} for the details dialog.
|
||||
* @type array $profile_notice Ban/restriction notice, if any.
|
||||
* @type array $profile_notice Ban/restriction notice {state,label,text}, if any.
|
||||
* @type string $background_url Cover image URL.
|
||||
* @type float $credit_balance Credit balance (own profile only).
|
||||
* @type string $currency_name Currency/credits display name.
|
||||
|
||||
@@ -162,6 +162,10 @@ if (!function_exists('aksrt_get_comment_report_html')) :
|
||||
if ($author_id > 0 && $author_id === $reporter_id) {
|
||||
return '';
|
||||
}
|
||||
// 已封禁/小黑屋用户不再显示举报入口(参考主题 zib_get_report_link 排除语义)。
|
||||
if ($author_id > 0 && function_exists('aksrt_is_user_ban_restricted') && aksrt_is_user_ban_restricted($author_id)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
|
||||
if (empty($settings['report']['enabled'])) {
|
||||
|
||||
@@ -13,6 +13,162 @@
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* 用户治理展示态(禁封 / 小黑屋 / 禁言),含请求级缓存。
|
||||
*
|
||||
* 单一数据源为 \AKSRT\User\Restriction::get_display_state() / state_labels();
|
||||
* 本 helper 供用户中心页头、评论拒绝态、昵称徽章等位置复用,避免重复查库。
|
||||
*
|
||||
* 对齐参考主题:
|
||||
* zib_user_is_ban → aksrt_get_user_ban_state(状态判定)
|
||||
* zib_ban_author_header_identity_filter → 资料卡/用户中心身份行徽章
|
||||
*
|
||||
* @return array{state:string,label:string,icon:string}|array{} state 为空串表示无治理状态。
|
||||
*/
|
||||
function aksrt_get_user_ban_state(int $user_id): array
|
||||
{
|
||||
static $cache = [];
|
||||
if ($user_id <= 0) {
|
||||
return [];
|
||||
}
|
||||
if (isset($cache[$user_id])) {
|
||||
return $cache[$user_id];
|
||||
}
|
||||
|
||||
$state = '';
|
||||
if (class_exists('\AKSRT\User\Restriction')) {
|
||||
$state = (string) \AKSRT\User\Restriction::get_instance()->get_display_state($user_id);
|
||||
}
|
||||
if ($state === '') {
|
||||
return $cache[$user_id] = [];
|
||||
}
|
||||
|
||||
$labels = class_exists('\AKSRT\User\Restriction') ? \AKSRT\User\Restriction::state_labels() : [];
|
||||
$icons = [
|
||||
'banned' => 'ban',
|
||||
'restricted' => 'ban',
|
||||
'muted' => 'comment-slash',
|
||||
];
|
||||
return $cache[$user_id] = [
|
||||
'state' => $state,
|
||||
'label' => (string) ($labels[$state] ?? ''),
|
||||
'icon' => (string) ($icons[$state] ?? 'ban'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 目标是否处于封禁/小黑屋状态(举报入口隐藏判定,参考主题
|
||||
* zib_get_report_link 中 zib_user_is_ban($user_id) 的排除语义;
|
||||
* 禁言为轻量评论域限制,不隐藏举报入口)。
|
||||
*/
|
||||
function aksrt_is_user_ban_restricted(int $user_id): bool
|
||||
{
|
||||
$state = aksrt_get_user_ban_state($user_id);
|
||||
return in_array((string) ($state['state'] ?? ''), ['banned', 'restricted'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁封申诉页 URL(自定义页面模板 templates/pages/page-ban-appeal.php,
|
||||
* 即参考主题 zib_get_ban_info_modal / zib_get_user_ban_info_link 的落地视图)。
|
||||
* 站点未创建该页面时返回空串,调用方应降级为纯文本徽章。
|
||||
*/
|
||||
function aksrt_get_ban_appeal_page_url(): string
|
||||
{
|
||||
static $url = null;
|
||||
if (null !== $url) {
|
||||
return $url;
|
||||
}
|
||||
$pages = get_posts([
|
||||
'post_type' => 'page',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => '_wp_page_template',
|
||||
'value' => 'templates/pages/page-ban-appeal.php',
|
||||
],
|
||||
],
|
||||
]);
|
||||
return $url = !empty($pages) ? (string) get_permalink((int) $pages[0]) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 治理状态徽章 +「查看详情」入口(用户中心页头,参考主题
|
||||
* user_page_header_desc 钩子 / zib_get_user_ban_info_link 语义)。
|
||||
*/
|
||||
function aksrt_get_user_ban_notice_html(int $user_id): string
|
||||
{
|
||||
$state = aksrt_get_user_ban_state($user_id);
|
||||
if (empty($state['state'])) {
|
||||
return '';
|
||||
}
|
||||
$tone = 'banned' === $state['state'] ? 'c-red' : 'c-yellow';
|
||||
$chip = '<span class="badg badg-sm ' . $tone . '" title="' . esc_attr((string) $state['label']) . '">'
|
||||
. '<i class="fa fas fa-' . esc_attr((string) $state['icon']) . ' mr6" aria-hidden="true"></i>'
|
||||
. esc_html((string) $state['label']) . '</span>';
|
||||
$link = aksrt_get_ban_appeal_page_url();
|
||||
if ($link === '') {
|
||||
return $chip;
|
||||
}
|
||||
return '<a class="aksrt-user-ban-notice" href="' . esc_url($link) . '">' . $chip
|
||||
. '<i class="fa fas fa-angle-right em09 ml6" aria-hidden="true"></i></a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 昵称旁禁封徽章(参考主题 user_name_badge 钩子语义:占据 VIP 图标位,
|
||||
* 头像角标 user_avatar_badge 为可选项,见 aksrt_get_user_ban_avatar_badge)。
|
||||
*/
|
||||
function aksrt_get_user_ban_name_badge(int $user_id): string
|
||||
{
|
||||
$state = aksrt_get_user_ban_state($user_id);
|
||||
if (empty($state['state'])) {
|
||||
return '';
|
||||
}
|
||||
$tone = 'banned' === $state['state'] ? 'c-red' : 'c-yellow';
|
||||
return '<i class="fa fas fa-' . esc_attr((string) $state['icon']) . ' ' . $tone . ' ml3 aksrt-user-ban-badge"'
|
||||
. ' title="' . esc_attr((string) $state['label']) . '" aria-hidden="true"></i>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 头像禁封角标(参考主题 user_avatar_badge 钩子语义:覆盖 VIP 角标位)。
|
||||
*/
|
||||
function aksrt_get_user_ban_avatar_badge(int $user_id): string
|
||||
{
|
||||
$state = aksrt_get_user_ban_state($user_id);
|
||||
if (empty($state['state'])) {
|
||||
return '';
|
||||
}
|
||||
$tone = 'banned' === $state['state'] ? 'is-banned' : 'is-restricted';
|
||||
return '<span class="avatar-badge aksrt-avatar-ban-badge ' . $tone . '" title="' . esc_attr((string) $state['label']) . '">'
|
||||
. '<i class="fa fas fa-' . esc_attr((string) $state['icon']) . '" aria-hidden="true"></i></span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 受限位置治理提示(评论拒绝态,参考主题 zib_get_user_ban_nocan_info 语义:
|
||||
* 状态徽章 +「暂时无法操作」+ 查看禁封状态入口)。
|
||||
*
|
||||
* @param string $msg 具体受阻的操作描述,如「发表评论」。
|
||||
*/
|
||||
function aksrt_get_user_ban_nocan_html(int $user_id, string $msg = ''): string
|
||||
{
|
||||
$state = aksrt_get_user_ban_state($user_id);
|
||||
if (empty($state['state'])) {
|
||||
return '';
|
||||
}
|
||||
$text = (string) $state['label'] . ($msg !== '' ? ',' . $msg : '');
|
||||
$tone = 'banned' === $state['state'] ? 'c-red' : 'c-yellow';
|
||||
$link = aksrt_get_ban_appeal_page_url();
|
||||
$inner = '<i class="fa fas fa-' . esc_attr((string) $state['icon']) . ' mr6" aria-hidden="true"></i>'
|
||||
. '<span class="' . $tone . '">' . esc_html($text) . '</span>';
|
||||
if ($link !== '') {
|
||||
$inner .= ' <a class="c-blue" href="' . esc_url($link) . '">' . esc_html__('查看禁封状态', 'aksrt')
|
||||
. '<i class="fa fas fa-angle-right em09 ml3" aria-hidden="true"></i></a>';
|
||||
}
|
||||
return '<p class="comments-permission-denied aksrt-user-ban-nocan">' . $inner . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁封功能开关(后台“用户&互动 → 禁封/举报 → 禁封功能”,
|
||||
* 对齐参考主题 _pz('user_ban_s', true):缺键默认开启)。
|
||||
|
||||
@@ -46,6 +46,13 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_
|
||||
<?php endif; ?>
|
||||
</h1>
|
||||
<div class="uc-header__meta desc user-identity flex ac hh">
|
||||
<?php
|
||||
// 治理状态提示(参考主题 user_page_header_desc 钩子 / zib_get_user_ban_info_link:
|
||||
// 徽章 +「查看禁封状态」入口,直达禁封申诉页)。
|
||||
if (function_exists('aksrt_get_user_ban_notice_html')) {
|
||||
echo aksrt_get_user_ban_notice_html((int) $user_id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper escapes internally.
|
||||
}
|
||||
?>
|
||||
<?php if (function_exists('aksrt_render_user_medal_entry')) : ?>
|
||||
<?php echo aksrt_render_user_medal_entry((int) $user_id, 'user-center', true); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -173,6 +173,13 @@ trait UserControllerRestrictionOperations {
|
||||
if ($time_day > 0) {
|
||||
$notice .= ' ' . sprintf(__('期限:%1$d 天,%2$s 起自动解封。', 'aksrt'), $time_day, $expires);
|
||||
}
|
||||
// 申诉指引(参考主题「您可以登录后在用户中心发起申诉」同语义)。
|
||||
if (!$no_appeal) {
|
||||
$appeal_url = function_exists('aksrt_get_ban_appeal_page_url') ? aksrt_get_ban_appeal_page_url() : '';
|
||||
if ($appeal_url !== '') {
|
||||
$notice .= ' ' . sprintf(__('如有异议,可在禁封申诉页提交申诉:%s', 'aksrt'), $appeal_url);
|
||||
}
|
||||
}
|
||||
$this->send_ban_notice($user_id, $notice, __('账号限制', 'aksrt'), $desc);
|
||||
|
||||
// 删除全部内容:全部公开内容与已审核评论移至回收站(参考主题同行为)。
|
||||
|
||||
@@ -92,9 +92,15 @@ class BanAppeal {
|
||||
wp_send_json(['error' => 1, 'msg' => __('申诉功能未开启', 'aksrt')]);
|
||||
}
|
||||
|
||||
$banned = class_exists('\AKSRT\User\Restriction')
|
||||
&& \AKSRT\User\Restriction::get_instance()->is_banned($user_id);
|
||||
if (!$banned) {
|
||||
// 禁封状态门禁:封号(ban)与小黑屋(suspend)均可申诉
|
||||
// (对齐参考主题 zib_user_is_ban 的 1/2 双类型语义)。
|
||||
$restricted = class_exists('\AKSRT\User\Restriction')
|
||||
&& in_array(
|
||||
\AKSRT\User\Restriction::get_instance()->get_display_state($user_id),
|
||||
[\AKSRT\User\Restriction::STATE_BANNED, \AKSRT\User\Restriction::STATE_RESTRICTED],
|
||||
true
|
||||
);
|
||||
if (!$restricted) {
|
||||
wp_send_json(['error' => 1, 'msg' => __('当前账号未处于禁封状态', 'aksrt')]);
|
||||
}
|
||||
// 禁封时勾选“禁止用户申诉”的门禁(参考主题 no_appeal 同语义)。
|
||||
|
||||
+8
-9
@@ -343,7 +343,12 @@ class Home {
|
||||
/**
|
||||
* 获取公开治理提示。
|
||||
*
|
||||
* @return array{state:string,text:string}|array{}
|
||||
* 状态标识对齐参考主题公开主页禁封徽章(zib_ban_author_header_identity_filter):
|
||||
* 封号(ban) → "违规已封号",小黑屋(suspend) → "小黑屋禁封中";
|
||||
* 禁言(mute) 为 AKSRT 扩展状态 → "禁言中"。text 为后台可配置说明文案
|
||||
* (未配置时回退默认文案,见 UserRuntime::get_user_identity_settings)。
|
||||
*
|
||||
* @return array{state:string,label:string,text:string}|array{}
|
||||
*/
|
||||
public function get_profile_notice(int $user_id): array {
|
||||
if ($user_id <= 0 || !class_exists('\AKSRT\Settings\Settings')) {
|
||||
@@ -357,14 +362,7 @@ class Home {
|
||||
|
||||
$state = '';
|
||||
if (class_exists('\AKSRT\User\Restriction')) {
|
||||
$svc = \AKSRT\User\Restriction::get_instance();
|
||||
if ($svc->is_banned($user_id)) {
|
||||
$state = 'banned';
|
||||
} elseif ($svc->is_restricted($user_id, \AKSRT\User\Restriction::TYPE_SUSPEND)) {
|
||||
$state = 'restricted';
|
||||
} elseif ($svc->is_restricted($user_id, \AKSRT\User\Restriction::TYPE_MUTE)) {
|
||||
$state = 'muted';
|
||||
}
|
||||
$state = \AKSRT\User\Restriction::get_instance()->get_display_state($user_id);
|
||||
}
|
||||
if ($state === '') {
|
||||
return [];
|
||||
@@ -372,6 +370,7 @@ class Home {
|
||||
|
||||
return [
|
||||
'state' => $state,
|
||||
'label' => \AKSRT\User\Restriction::state_labels()[$state] ?? '',
|
||||
'text' => (string) $settings['report']['ban_notice_text'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -22,6 +22,14 @@ class Restriction {
|
||||
const TYPES = [self::TYPE_WARN, self::TYPE_MUTE, self::TYPE_SUSPEND, self::TYPE_BAN];
|
||||
const SCOPES = [self::SCOPE_GLOBAL, self::SCOPE_COMMENT];
|
||||
|
||||
/**
|
||||
* 治理展示态(与 user-home.css 的 --banned/--restricted/--muted 色调修饰符同名):
|
||||
* 资料卡提示框、用户中心页头、评论拒绝态、昵称徽章共用同一组键。
|
||||
*/
|
||||
const STATE_BANNED = 'banned';
|
||||
const STATE_RESTRICTED = 'restricted';
|
||||
const STATE_MUTED = 'muted';
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
@@ -182,6 +190,43 @@ class Restriction {
|
||||
return $this->is_restricted($user_id, self::TYPE_BAN, self::SCOPE_GLOBAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 治理展示态(空串 = 无治理状态;ban 优先于 suspend,对齐后台单一状态语义)。
|
||||
*
|
||||
* 供资料卡提示框(Home::get_profile_notice)、用户中心页头、评论拒绝态、
|
||||
* 昵称徽章共用,文案见 state_labels()。
|
||||
*/
|
||||
public function get_display_state(int $user_id): string {
|
||||
if ($user_id <= 0) {
|
||||
return '';
|
||||
}
|
||||
if ($this->is_banned($user_id)) {
|
||||
return self::STATE_BANNED;
|
||||
}
|
||||
if ($this->is_suspended($user_id)) {
|
||||
return self::STATE_RESTRICTED;
|
||||
}
|
||||
if ($this->is_restricted($user_id, self::TYPE_MUTE)) {
|
||||
return self::STATE_MUTED;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 治理展示态徽章文案 — 对齐参考主题公开主页禁封标识
|
||||
* (zib_ban_author_header_identity_filter:封号 → 违规已封号,
|
||||
* 小黑屋 → 小黑屋禁封中);禁言为 AKSRT 扩展状态。
|
||||
*
|
||||
* @return array<string,string> 展示态 => 文案
|
||||
*/
|
||||
public static function state_labels(): array {
|
||||
return [
|
||||
self::STATE_BANNED => __('违规已封号', 'aksrt'),
|
||||
self::STATE_RESTRICTED => __('小黑屋禁封中', 'aksrt'),
|
||||
self::STATE_MUTED => __('禁言中', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is in 小黑屋 (active global suspend).
|
||||
*
|
||||
|
||||
@@ -22,8 +22,39 @@ if (!is_user_logged_in()) {
|
||||
$user_id = get_current_user_id();
|
||||
$settings = class_exists(BanAppeal::class) ? BanAppeal::settings() : ['enabled' => true, 'desc' => '', 'keys' => []];
|
||||
$ban = class_exists(Restriction::class) ? Restriction::get_instance()->get_active_ban($user_id) : null;
|
||||
if (!$ban && class_exists(Restriction::class)) {
|
||||
$ban = Restriction::get_instance()->get_active_suspend($user_id);
|
||||
}
|
||||
$status = class_exists(BanAppeal::class) ? BanAppeal::get_status($user_id) : '';
|
||||
$payload = class_exists(BanAppeal::class) ? BanAppeal::get_payload($user_id) : [];
|
||||
|
||||
// 禁封状态信息(对齐参考主题 zib_get_ban_info_modal:状态/起止时间/原因/说明/申诉权限)。
|
||||
$ban_status_text = '';
|
||||
$ban_reason = '';
|
||||
$ban_desc = '';
|
||||
$ban_end_text = '';
|
||||
$no_appeal = get_user_meta($user_id, '_aksrt_no_appeal', true);
|
||||
$no_appeal_desc = is_array($no_appeal) ? (string) ($no_appeal['desc'] ?? '') : '';
|
||||
if (is_array($ban)) {
|
||||
$ban_status_text = (string) ($ban['type'] ?? '') === Restriction::TYPE_BAN
|
||||
? __('禁封中(禁止登录)', 'aksrt')
|
||||
: __('小黑屋禁封中', 'aksrt');
|
||||
$ban_end_text = !empty($ban['expires_at']) ? (string) $ban['expires_at'] : __('永久', 'aksrt');
|
||||
$raw_reason = (string) ($ban['reason'] ?? '');
|
||||
$ban_reason = $raw_reason;
|
||||
$desc_marker = "\n" . __('禁封说明:', 'aksrt');
|
||||
$marker_pos = strpos($raw_reason, $desc_marker);
|
||||
if (false !== $marker_pos) {
|
||||
$ban_reason = substr($raw_reason, 0, $marker_pos);
|
||||
$ban_desc = substr($raw_reason, $marker_pos + strlen($desc_marker));
|
||||
}
|
||||
}
|
||||
$ban_set_row = static function (string $label, string $value): string {
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
return '<div class="flex"><div class="author-set-left" style="min-width: 80px;">' . esc_html($label) . '</div><div class="author-set-right mt6">' . esc_html($value) . '</div></div>';
|
||||
};
|
||||
?>
|
||||
<main class="container page-template-ban-appeal">
|
||||
<div class="content-wrap">
|
||||
@@ -32,6 +63,32 @@ $payload = class_exists(BanAppeal::class) ? BanAppeal::get_payload($user_id) : [
|
||||
<h1 class="article-title"><?php echo esc_html(get_the_title() ?: __('禁封申诉', 'aksrt')); ?></h1>
|
||||
</div>
|
||||
|
||||
<?php if (is_array($ban)) : ?>
|
||||
<div class="theme-box radius8 main-bg main-shadow box-body aksrt-ban-status mb20">
|
||||
<h2 class="mb15"><?php esc_html_e('禁封状态', 'aksrt'); ?></h2>
|
||||
<div class="muted-box padding-10 em09">
|
||||
<?php
|
||||
echo $ban_set_row(__('状态', 'aksrt'), $ban_status_text); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- row builder escapes.
|
||||
echo $ban_set_row(__('开始时间', 'aksrt'), (string) ($ban['created_at'] ?? ''));
|
||||
echo $ban_set_row(__('结束时间', 'aksrt'), $ban_end_text);
|
||||
echo $ban_set_row(__('原因', 'aksrt'), $ban_reason);
|
||||
echo $ban_set_row(__('说明', 'aksrt'), $ban_desc);
|
||||
?>
|
||||
<div class="flex">
|
||||
<div class="author-set-left" style="min-width: 80px;"><?php esc_html_e('申诉权限', 'aksrt'); ?></div>
|
||||
<div class="author-set-right mt6">
|
||||
<?php if ($no_appeal_desc !== '') : ?>
|
||||
<span class="c-red"><?php esc_html_e('本次禁封您将不能发起申诉', 'aksrt'); ?></span>
|
||||
<div class="mt6"><?php echo esc_html($no_appeal_desc); ?></div>
|
||||
<?php else : ?>
|
||||
<?php esc_html_e('可提交申诉', 'aksrt'); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="theme-box radius8 main-bg main-shadow box-body aksrt-ban-appeal">
|
||||
<?php if (!$ban) : ?>
|
||||
<div class="muted-2-color text-center" style="padding:40px 0;">
|
||||
@@ -49,12 +106,6 @@ $payload = class_exists(BanAppeal::class) ? BanAppeal::get_payload($user_id) : [
|
||||
<div class="muted-2-color mt6"><?php echo esc_html(sprintf(__('提交时间:%s', 'aksrt'), (string) $payload['time'])); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<?php if (!empty($ban['reason'])) : ?>
|
||||
<div class="muted-box mb20">
|
||||
<strong><?php esc_html_e('禁封原因', 'aksrt'); ?>:</strong>
|
||||
<?php echo esc_html((string) $ban['reason']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($settings['desc'])) : ?>
|
||||
<div class="muted-color mb20"><?php echo wp_kses_post(nl2br(esc_html((string) $settings['desc']))); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -66,7 +66,9 @@ $unread_count = $is_own ? aksrt_get_user_unread_notification_count($user_id) : 0
|
||||
$cover_ban_html = function_exists('aksrt_get_user_cover_menu_action_html')
|
||||
? aksrt_get_user_cover_menu_action_html($user_id)
|
||||
: '';
|
||||
$show_cover_report = $cover_ban_html === '' && $show_report && !$is_own;
|
||||
// 已封禁/小黑屋用户不展示举报入口(参考主题 zib_get_report_link 排除语义)。
|
||||
$target_ban_restricted = function_exists('aksrt_is_user_ban_restricted') && aksrt_is_user_ban_restricted($user_id);
|
||||
$show_cover_report = $cover_ban_html === '' && $show_report && !$is_own && !$target_ban_restricted;
|
||||
$show_cover_menu = $has_more_details || $show_cover_report || $cover_ban_html !== '';
|
||||
?><div class="aksrt-author-hero<?php echo $show_cover ? '' : ' aksrt-author-hero--no-cover'; ?>">
|
||||
<?php if ($show_cover) : ?>
|
||||
@@ -154,10 +156,25 @@ $show_cover_menu = $has_more_details || $show_cover_report || $cover_ban_html !=
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($notice) && is_array($notice)) : ?>
|
||||
<div class="aksrt-author-hero__notice aksrt-author-hero__notice--<?php echo esc_attr((string) ($notice['tone'] ?? 'muted')); ?>">
|
||||
<?php echo esc_html((string) ($notice['message'] ?? '')); ?>
|
||||
<?php
|
||||
// 数据契约:\AKSRT\User\Home::get_profile_notice() 输出 state|label|text;
|
||||
// 提示框色调修饰符与 state 同名(user-home.css:--banned/--restricted/--muted)。
|
||||
$notice_state = (string) ($notice['state'] ?? '');
|
||||
$notice_tone = in_array($notice_state, ['banned', 'restricted', 'muted'], true) ? $notice_state : 'muted';
|
||||
$notice_label = trim((string) ($notice['label'] ?? ''));
|
||||
$notice_text = trim((string) ($notice['text'] ?? ''));
|
||||
$notice_message = $notice_label !== '' && $notice_text !== ''
|
||||
? $notice_label . ',' . $notice_text
|
||||
: ($notice_label !== '' ? $notice_label : $notice_text);
|
||||
$notice_icon = 'muted' === $notice_state ? 'comment-slash' : 'ban';
|
||||
?>
|
||||
<?php if ($notice_message !== '') : ?>
|
||||
<div class="aksrt-author-hero__notice aksrt-author-hero__notice--<?php echo esc_attr($notice_tone); ?>">
|
||||
<i class="fa fas fa-<?php echo esc_attr($notice_icon); ?>" aria-hidden="true"></i>
|
||||
<span><?php echo esc_html($notice_message); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,6 +19,16 @@ const functions = read('functions.php')
|
||||
const uninstall = read('uninstall.php')
|
||||
const userBanHelper = read('inc/helpers/user-ban.php')
|
||||
const userBanJs = read('assets/js/user-ban.js')
|
||||
const authorHero = read('templates/parts/user/author-hero.php')
|
||||
const ucHeader = read('inc/helpers/user-center-renderers/profile-header.php')
|
||||
const commentsHelper = read('inc/helpers/aksrt-comments.php')
|
||||
const appealPage = read('templates/pages/page-ban-appeal.php')
|
||||
const identityHelper = read('inc/helpers/aksrt-identity.php')
|
||||
const avatarHelper = read('inc/helpers/aksrt-avatar.php')
|
||||
const singleComponents = read('inc/helpers/aksrt-single-components.php')
|
||||
const banAppeal = read('src/User/BanAppeal.php')
|
||||
const mainCss = read('assets/css/main.css')
|
||||
const mainMinCss = read('assets/css/main.min.css')
|
||||
|
||||
test('report REST surface is canonical: submit + users/reports only', () => {
|
||||
assert.match(userController, /'\/report\/submit'/)
|
||||
@@ -130,9 +140,75 @@ test('ban appeal approval actually lifts restrictions', () => {
|
||||
})
|
||||
|
||||
test('profile governance notice reads from the restriction table', () => {
|
||||
assert.match(home, /->is_banned\(\$user_id\)/)
|
||||
assert.match(home, /->get_display_state\(\$user_id\)/)
|
||||
assert.match(home, /Restriction::state_labels\(\)\[\$state\]/)
|
||||
assert.match(home, /\\AKSRT\\User\\Restriction::get_instance\(\)/)
|
||||
assert.ok(!home.includes('aksrt_user_ban_state'))
|
||||
// 提示框内容契约:state|label|text,label 对齐参考主题公开主页禁封徽章文案。
|
||||
assert.match(home, /'state'\s*=> \$state/)
|
||||
assert.match(home, /'label'\s*=>/)
|
||||
assert.match(home, /'text'\s*=> \(string\) \$settings\['report'\]\['ban_notice_text'\]/)
|
||||
// 模板必须消费真实契约键:tone/message 双键曾导致资料卡下方提示框渲染为空。
|
||||
assert.match(authorHero, /\$notice\['state'\]/)
|
||||
assert.match(authorHero, /\$notice\['text'\]/)
|
||||
assert.match(authorHero, /\$notice\['label'\]/)
|
||||
assert.ok(!authorHero.includes("$notice['message']"), 'template must not read the removed message key')
|
||||
assert.ok(!authorHero.includes("$notice['tone']"), 'template must not read the removed tone key')
|
||||
})
|
||||
|
||||
test('governance display state is a single source shared by every prompt surface', () => {
|
||||
// 展示态与文案集中在 Restriction(资料卡/用户中心/评论拒绝态/昵称徽章共用)。
|
||||
for (const state of ['STATE_BANNED', 'STATE_RESTRICTED', 'STATE_MUTED']) {
|
||||
assert.match(restriction, new RegExp(`const ${state}\\s+=`), `missing display state: ${state}`)
|
||||
}
|
||||
assert.match(restriction, /public function get_display_state\(int \$user_id\): string/)
|
||||
assert.match(restriction, /public static function state_labels\(\): array/)
|
||||
for (const label of ["__('违规已封号', 'aksrt')", "__('小黑屋禁封中', 'aksrt')", "__('禁言中', 'aksrt')"]) {
|
||||
assert.ok(restriction.includes(label), `missing state label: ${label}`)
|
||||
}
|
||||
assert.match(restriction, /is_banned\(\$user_id\)[\s\S]*?is_suspended\(\$user_id\)[\s\S]*?is_restricted\(\$user_id, self::TYPE_MUTE\)/)
|
||||
// 请求级缓存:评论列表逐条渲染昵称徽章不得反复查库。
|
||||
assert.match(userBanHelper, /static \$cache = \[\]/)
|
||||
})
|
||||
|
||||
test('ban prompt surface mirrors the reference theme entry points', () => {
|
||||
// 用户中心页头(参考主题 user_page_header_desc / zib_get_user_ban_info_link)。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_notice_html/)
|
||||
assert.match(userBanHelper, /function aksrt_get_ban_appeal_page_url/)
|
||||
assert.match(ucHeader, /aksrt_get_user_ban_notice_html/)
|
||||
// 评论拒绝态(参考主题 zib_get_user_ban_nocan_info)。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_nocan_html/)
|
||||
assert.match(commentsHelper, /aksrt_get_user_ban_nocan_html/)
|
||||
assert.match(commentsHelper, /暂时无法发表评论/)
|
||||
// 昵称徽章 + 头像角标(参考主题 user_name_badge / user_avatar_badge)。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_name_badge/)
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_avatar_badge/)
|
||||
assert.match(identityHelper, /aksrt_get_user_ban_name_badge\(\$user_id\)/)
|
||||
assert.match(avatarHelper, /aksrt_get_user_ban_avatar_badge\(\$user_id\)/)
|
||||
// 禁封申诉页完整状态信息(参考主题 zib_get_ban_info_modal)。
|
||||
assert.match(appealPage, /get_active_suspend\(\$user_id\)/)
|
||||
for (const row of ['状态', '开始时间', '结束时间', '原因', '说明']) {
|
||||
assert.match(appealPage, new RegExp(`__\\('${row}', 'aksrt'\\)`), `appeal page missing status row: ${row}`)
|
||||
}
|
||||
assert.match(appealPage, /esc_html_e\('申诉权限', 'aksrt'\)/)
|
||||
assert.match(appealPage, /禁封中\(禁止登录\)/)
|
||||
assert.match(appealPage, /本次禁封您将不能发起申诉/)
|
||||
// 禁封/小黑屋均可申诉(参考主题 zib_user_is_ban 双类型语义)。
|
||||
assert.match(banAppeal, /STATE_BANNED, \\AKSRT\\User\\Restriction::STATE_RESTRICTED/)
|
||||
// 禁封站内信附申诉入口(参考主题 zib_updata_user_ban_msg 申诉指引)。
|
||||
assert.match(restrictionOps, /aksrt_get_ban_appeal_page_url/)
|
||||
// 头像角标样式:源文件与入队的 min 产物同步。
|
||||
for (const css of [mainCss, mainMinCss]) {
|
||||
assert.ok(css.includes('aksrt-avatar-ban-badge'), 'ban avatar badge CSS must exist in both main.css and main.min.css')
|
||||
}
|
||||
})
|
||||
|
||||
test('report entry hides targets already banned or suspended', () => {
|
||||
// 参考主题 zib_get_report_link:已封禁/小黑屋用户不展示举报入口(禁言不隐藏)。
|
||||
assert.match(userBanHelper, /function aksrt_is_user_ban_restricted/)
|
||||
assert.match(commentItem, /aksrt_is_user_ban_restricted\(\$author_id\)/)
|
||||
assert.match(singleComponents, /aksrt_is_user_ban_restricted\(\$post_author_id\)/)
|
||||
assert.match(authorHero, /aksrt_is_user_ban_restricted\(\$user_id\)/)
|
||||
})
|
||||
|
||||
test('admin report notification links to the real page', () => {
|
||||
|
||||
Reference in New Issue
Block a user