feat(ban): align ban prompts with the reference theme modals and block page
对照参考主题(Zibll)修正四项偏差,申诉链路从页面整体迁移至弹窗:
- feat(ban): 新增三个 1:1 复刻弹窗(REST /user-ban/{info,appeal,log}-modal):
查看禁封状态(多彩头 c-gray + 信息行 + 申诉按钮)、禁封申诉(edit-header
gavel/c-blue + 信息框 + 资料项 data[key] 输入 + 审核中态 + 禁止申诉态)、
禁封记录(多彩头 c-yellow + 计数 + 逐条卡片);user-ban.js 增加
data-aksrt-ban-info/appeal/log 委托与 admin-ajax 申诉提交
- feat(ban): 禁封/解封弹窗的「当前用户已有N次禁封记录」静态文案改为可点击
黄链(N>1),打开禁封记录弹窗;解封视图信息行改复用统一数据源
- refactor(ban): 治理展示态收敛至 Restriction::state_badges()(封号=c-red
违规已封号 / 小黑屋=c-yellow 小黑屋禁封中 / 禁言=c-gray 禁言中,颜色与
字段统一管理)+ get_ban_info()(状态/起止时间/原因/说明/申诉权限,
三弹窗与禁封页共用)
- feat(ban): 登录行为对齐参考主题——移除登录期拦截(enforce_login_ban)
与 complete_login 禁封闸门,封号用户可正常登录;新增 template_redirect
前台禁封页(1:1 zib_user_ban_restrict_login/zib_die_page:红盒提示 +
信息行 + 申诉权限 + 退出登录/申诉按钮,模板 templates/parts/user/ban-block.php)
- feat(profile): 公开主页禁封状态从资料卡下方提示框改为占据身份行元信息位
的 badg 徽章(参考主题 author_header_identity 语义);用户中心元信息改为
but 按钮(徽章+箭头)打开查看禁封状态弹窗;评论拒绝态链接同源
- refactor: 移除申诉页面模板(page-ban-appeal.php)及其资产/注册/入队、
get_profile_notice/profile_notice 链、state_labels/state_notices/
login_ban_message、UserRuntime 的 public_notice/ban_notice_text 死配置、
report_settings 死键、无消费方的 aksrt_ban_appeal_status 端点
- feat(ban): BanAppeal::ajax_submit 兼容弹窗 data[key] 表单结构
(逐项清洗入库,申诉说明取 data['申诉说明'],旧平铺字段保持兼容)
- chore: 重新构建 public.css/public.min.css(三个新弹窗几何与头部样式)
- test: 契约测试重写(徽章元信息位、三弹窗与记录链接、禁封页、允许登录 +
前台拦截、死代码移除断言),全量 362 项通过
This commit is contained in:
+53
-11
@@ -7235,17 +7235,35 @@ body.aksrt-modal-runtime-open {
|
|||||||
background: var(--main-bg-color, #fff);
|
background: var(--main-bg-color, #fff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- 查看禁封状态 / 禁封申诉 / 禁封记录弹窗(参考主题 modal-mini 几何 1:1)----
|
||||||
|
参考主题三个链接均传 data_class='modal-mini'(宽 400px)、data-height=240。 */
|
||||||
|
.aksrt-user-ban-info-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||||
|
.aksrt-user-ban-appeal-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||||
|
.aksrt-user-ban-log-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||||
|
width: min(400px, calc(100vw - 32px));
|
||||||
|
border-radius: var(--main-radius, 8px);
|
||||||
|
background: var(--main-bg-color, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-user-ban-info-modal .aksrt-modal-runtime__skeleton,
|
||||||
|
.aksrt-user-ban-appeal-modal .aksrt-modal-runtime__skeleton,
|
||||||
|
.aksrt-user-ban-log-modal .aksrt-modal-runtime__skeleton {
|
||||||
|
padding: 24px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .aksrt-modal-runtime__skeleton {
|
.aksrt-user-ban-modal .aksrt-modal-runtime__skeleton {
|
||||||
padding: 24px 18px;
|
padding: 24px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* edit-header:与禁封表单头部同构(关闭钮靠右,红色警示徽标 + 标题居左)。 */
|
/* edit-header:与禁封表单头部同构(关闭钮靠右,红色警示徽标 + 标题居左)。 */
|
||||||
.aksrt-user-ban-modal .edit-header {
|
.aksrt-user-ban-modal .edit-header,
|
||||||
|
.aksrt-user-ban-appeal-modal .edit-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-right: 32px;
|
padding-right: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .edit-header .close {
|
.aksrt-user-ban-modal .edit-header .close,
|
||||||
|
.aksrt-user-ban-appeal-modal .edit-header .close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -7262,7 +7280,11 @@ body.aksrt-modal-runtime-open {
|
|||||||
/* 解封视图多彩横幅(参考主题 zib_get_modal_colorful_header('c-yellow', ...)):
|
/* 解封视图多彩横幅(参考主题 zib_get_modal_colorful_header('c-yellow', ...)):
|
||||||
120px 高绝对定位横幅 + 旋转彩斑,后续表单整体下移 120px。 */
|
120px 高绝对定位横幅 + 旋转彩斑,后续表单整体下移 120px。 */
|
||||||
.aksrt-user-ban-modal .modal-colorful-header,
|
.aksrt-user-ban-modal .modal-colorful-header,
|
||||||
.aksrt-user-ban-modal .colorful-bg {
|
.aksrt-user-ban-modal .colorful-bg,
|
||||||
|
.aksrt-user-ban-info-modal .modal-colorful-header,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg,
|
||||||
|
.aksrt-user-ban-log-modal .modal-colorful-header,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: var(--this-color);
|
color: var(--this-color);
|
||||||
@@ -7270,7 +7292,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
background-size: 120%;
|
background-size: 120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .modal-colorful-header {
|
.aksrt-user-ban-modal .modal-colorful-header,
|
||||||
|
.aksrt-user-ban-info-modal .modal-colorful-header,
|
||||||
|
.aksrt-user-ban-log-modal .modal-colorful-header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -7282,11 +7306,15 @@ body.aksrt-modal-runtime-open {
|
|||||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .modal-colorful-header + * {
|
.aksrt-user-ban-modal .modal-colorful-header + *,
|
||||||
|
.aksrt-user-ban-info-modal .modal-colorful-header + *,
|
||||||
|
.aksrt-user-ban-log-modal .modal-colorful-header + * {
|
||||||
margin-top: 120px;
|
margin-top: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .modal-colorful-header > .close {
|
.aksrt-user-ban-modal .modal-colorful-header > .close,
|
||||||
|
.aksrt-user-ban-info-modal .modal-colorful-header > .close,
|
||||||
|
.aksrt-user-ban-log-modal .modal-colorful-header > .close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
@@ -7298,7 +7326,13 @@ body.aksrt-modal-runtime-open {
|
|||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make,
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make,
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before,
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before,
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make::before,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make::after,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make::before,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: var(--this-color);
|
color: var(--this-color);
|
||||||
background: var(--this-bg);
|
background: var(--this-bg);
|
||||||
@@ -7306,7 +7340,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make {
|
||||||
top: 60%;
|
top: 60%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 323px;
|
width: 323px;
|
||||||
@@ -7314,7 +7350,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
animation: aksrt-ban-colorful 120s linear infinite;
|
animation: aksrt-ban-colorful 120s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make::before,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make::before {
|
||||||
top: -325px;
|
top: -325px;
|
||||||
right: -558px;
|
right: -558px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -7324,7 +7362,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
transform: rotate(341deg);
|
transform: rotate(341deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make::after,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make::after {
|
||||||
top: -170px;
|
top: -170px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: -262px;
|
left: -262px;
|
||||||
@@ -7334,7 +7374,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
transform: rotate(326deg);
|
transform: rotate(326deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make + * {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make + *,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make + *,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make + * {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -318,17 +318,35 @@ body.aksrt-modal-runtime-open {
|
|||||||
background: var(--main-bg-color, #fff);
|
background: var(--main-bg-color, #fff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- 查看禁封状态 / 禁封申诉 / 禁封记录弹窗(参考主题 modal-mini 几何 1:1)----
|
||||||
|
参考主题三个链接均传 data_class='modal-mini'(宽 400px)、data-height=240。 */
|
||||||
|
.aksrt-user-ban-info-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||||
|
.aksrt-user-ban-appeal-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||||
|
.aksrt-user-ban-log-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||||
|
width: min(400px, calc(100vw - 32px));
|
||||||
|
border-radius: var(--main-radius, 8px);
|
||||||
|
background: var(--main-bg-color, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-user-ban-info-modal .aksrt-modal-runtime__skeleton,
|
||||||
|
.aksrt-user-ban-appeal-modal .aksrt-modal-runtime__skeleton,
|
||||||
|
.aksrt-user-ban-log-modal .aksrt-modal-runtime__skeleton {
|
||||||
|
padding: 24px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .aksrt-modal-runtime__skeleton {
|
.aksrt-user-ban-modal .aksrt-modal-runtime__skeleton {
|
||||||
padding: 24px 18px;
|
padding: 24px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* edit-header:与禁封表单头部同构(关闭钮靠右,红色警示徽标 + 标题居左)。 */
|
/* edit-header:与禁封表单头部同构(关闭钮靠右,红色警示徽标 + 标题居左)。 */
|
||||||
.aksrt-user-ban-modal .edit-header {
|
.aksrt-user-ban-modal .edit-header,
|
||||||
|
.aksrt-user-ban-appeal-modal .edit-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-right: 32px;
|
padding-right: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .edit-header .close {
|
.aksrt-user-ban-modal .edit-header .close,
|
||||||
|
.aksrt-user-ban-appeal-modal .edit-header .close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -345,7 +363,11 @@ body.aksrt-modal-runtime-open {
|
|||||||
/* 解封视图多彩横幅(参考主题 zib_get_modal_colorful_header('c-yellow', ...)):
|
/* 解封视图多彩横幅(参考主题 zib_get_modal_colorful_header('c-yellow', ...)):
|
||||||
120px 高绝对定位横幅 + 旋转彩斑,后续表单整体下移 120px。 */
|
120px 高绝对定位横幅 + 旋转彩斑,后续表单整体下移 120px。 */
|
||||||
.aksrt-user-ban-modal .modal-colorful-header,
|
.aksrt-user-ban-modal .modal-colorful-header,
|
||||||
.aksrt-user-ban-modal .colorful-bg {
|
.aksrt-user-ban-modal .colorful-bg,
|
||||||
|
.aksrt-user-ban-info-modal .modal-colorful-header,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg,
|
||||||
|
.aksrt-user-ban-log-modal .modal-colorful-header,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: var(--this-color);
|
color: var(--this-color);
|
||||||
@@ -353,7 +375,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
background-size: 120%;
|
background-size: 120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .modal-colorful-header {
|
.aksrt-user-ban-modal .modal-colorful-header,
|
||||||
|
.aksrt-user-ban-info-modal .modal-colorful-header,
|
||||||
|
.aksrt-user-ban-log-modal .modal-colorful-header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -365,11 +389,15 @@ body.aksrt-modal-runtime-open {
|
|||||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .modal-colorful-header + * {
|
.aksrt-user-ban-modal .modal-colorful-header + *,
|
||||||
|
.aksrt-user-ban-info-modal .modal-colorful-header + *,
|
||||||
|
.aksrt-user-ban-log-modal .modal-colorful-header + * {
|
||||||
margin-top: 120px;
|
margin-top: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .modal-colorful-header > .close {
|
.aksrt-user-ban-modal .modal-colorful-header > .close,
|
||||||
|
.aksrt-user-ban-info-modal .modal-colorful-header > .close,
|
||||||
|
.aksrt-user-ban-log-modal .modal-colorful-header > .close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
@@ -381,7 +409,13 @@ body.aksrt-modal-runtime-open {
|
|||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make,
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make,
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before,
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before,
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make::before,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make::after,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make::before,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: var(--this-color);
|
color: var(--this-color);
|
||||||
background: var(--this-bg);
|
background: var(--this-bg);
|
||||||
@@ -389,7 +423,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make {
|
||||||
top: 60%;
|
top: 60%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 323px;
|
width: 323px;
|
||||||
@@ -397,7 +433,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
animation: aksrt-ban-colorful 120s linear infinite;
|
animation: aksrt-ban-colorful 120s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make::before,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make::before {
|
||||||
top: -325px;
|
top: -325px;
|
||||||
right: -558px;
|
right: -558px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -407,7 +445,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
transform: rotate(341deg);
|
transform: rotate(341deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make::after,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make::after {
|
||||||
top: -170px;
|
top: -170px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: -262px;
|
left: -262px;
|
||||||
@@ -417,7 +457,9 @@ body.aksrt-modal-runtime-open {
|
|||||||
transform: rotate(326deg);
|
transform: rotate(326deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make + * {
|
.aksrt-user-ban-modal .colorful-bg > .colorful-make + *,
|
||||||
|
.aksrt-user-ban-info-modal .colorful-bg > .colorful-make + *,
|
||||||
|
.aksrt-user-ban-log-modal .colorful-bg > .colorful-make + * {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Ban appeal page styles.
|
|
||||||
* @package AKSRT
|
|
||||||
*/
|
|
||||||
.aksrt-ban-appeal .form-control {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 8px 10px;
|
|
||||||
border: 1px solid var(--main-border-color, #ddd);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.aksrt-ban-appeal .mb15 { margin-bottom: 15px; }
|
|
||||||
.aksrt-ban-appeal .but.c-blue {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
background: var(--focus-color, #2f6df6);
|
|
||||||
color: #fff;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.aksrt-ban-appeal .c-red { color: #e74c3c; }
|
|
||||||
.aksrt-ban-appeal .muted-box {
|
|
||||||
background: var(--surface-2, #f5f6f7);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
@@ -200,28 +200,6 @@
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aksrt-author-hero__notice {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin: 0 0 18px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-author-hero__notice--muted,
|
|
||||||
.aksrt-author-hero__notice--restricted {
|
|
||||||
color: #92400e;
|
|
||||||
background: rgba(245, 158, 11, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-author-hero__notice--banned {
|
|
||||||
color: #b91c1c;
|
|
||||||
background: rgba(239, 68, 68, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* No-cover variant */
|
/* No-cover variant */
|
||||||
.aksrt-author-hero--no-cover {
|
.aksrt-author-hero--no-cover {
|
||||||
border: 1px solid var(--color-line-soft);
|
border: 1px solid var(--color-line-soft);
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
binding_conflict: '该第三方账号已绑定其他账号',
|
binding_conflict: '该第三方账号已绑定其他账号',
|
||||||
account_conflict: '该第三方账号无法绑定到当前账号',
|
account_conflict: '该第三方账号无法绑定到当前账号',
|
||||||
already_bound: '该第三方账号已绑定其他账号',
|
already_bound: '该第三方账号已绑定其他账号',
|
||||||
account_banned: '您的账号已被封禁,禁封期间无法登录',
|
|
||||||
timeout: '第三方授权已超时,请重试',
|
timeout: '第三方授权已超时,请重试',
|
||||||
};
|
};
|
||||||
var oauthProviderLabels = {
|
var oauthProviderLabels = {
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Ban appeal form submit.
|
|
||||||
* @package AKSRT
|
|
||||||
*/
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
function ajaxUrl() {
|
|
||||||
if (window.aksrtData && window.aksrtData.ajaxUrl) return window.aksrtData.ajaxUrl;
|
|
||||||
if (window._win && window._win.ajax_url) return window._win.ajax_url;
|
|
||||||
return '/wp-admin/admin-ajax.php';
|
|
||||||
}
|
|
||||||
function init() {
|
|
||||||
var form = document.querySelector('.aksrt-ban-appeal-form');
|
|
||||||
if (!form) return;
|
|
||||||
form.addEventListener('submit', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var btn = form.querySelector('button[type="submit"]');
|
|
||||||
if (btn) btn.disabled = true;
|
|
||||||
var fd = new FormData(form);
|
|
||||||
if (!fd.get('action')) fd.set('action', 'aksrt_ban_appeal_submit');
|
|
||||||
fetch(ajaxUrl(), { method: 'POST', body: fd, credentials: 'same-origin' })
|
|
||||||
.then(function (r) { return r.json(); })
|
|
||||||
.then(function (data) {
|
|
||||||
if (!data || data.error) {
|
|
||||||
notyf((data && data.msg) || '提交失败', 'danger');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
notyf(data.msg || '提交成功');
|
|
||||||
if (data.reload) window.setTimeout(function () { location.reload(); }, 400);
|
|
||||||
})
|
|
||||||
.catch(function () { notyf('网络错误', 'danger'); })
|
|
||||||
.finally(function () { if (btn) btn.disabled = false; });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
jQuery(document).off('auto_fun.aksrt-ban-appeal-page').on('auto_fun.aksrt-ban-appeal-page', init);
|
|
||||||
})();
|
|
||||||
@@ -1044,7 +1044,6 @@
|
|||||||
bind_verification_required: '请先完成人机验证后再绑定',
|
bind_verification_required: '请先完成人机验证后再绑定',
|
||||||
account_conflict: '该第三方账号无法绑定到当前账号',
|
account_conflict: '该第三方账号无法绑定到当前账号',
|
||||||
already_bound: '该第三方账号已绑定其他账号',
|
already_bound: '该第三方账号已绑定其他账号',
|
||||||
account_banned: '您的账号已被封禁,禁封期间无法登录',
|
|
||||||
timeout: '第三方授权已超时,请重试'
|
timeout: '第三方授权已超时,请重试'
|
||||||
};
|
};
|
||||||
var providerLabel = providerLabels[provider] || '';
|
var providerLabel = providerLabels[provider] || '';
|
||||||
|
|||||||
+133
-2
@@ -5,6 +5,10 @@
|
|||||||
* - zib_get_edit_user_ban_link → [data-aksrt-ban] 委托入口(三点菜单内)
|
* - zib_get_edit_user_ban_link → [data-aksrt-ban] 委托入口(三点菜单内)
|
||||||
* - zib_get_set_user_ban_modal → REST /user-ban/modal 拉取弹窗内容
|
* - zib_get_set_user_ban_modal → REST /user-ban/modal 拉取弹窗内容
|
||||||
* - zib_ajax_set_user_ban → REST /user-ban/set 提交
|
* - zib_ajax_set_user_ban → REST /user-ban/set 提交
|
||||||
|
* - zib_get_ban_info_modal → [data-aksrt-ban-info](查看禁封状态)
|
||||||
|
* - zib_get_user_ban_appeal_modal → [data-aksrt-ban-appeal](禁封申诉)
|
||||||
|
* - zib_get_ban_log_modal → [data-aksrt-ban-log](禁封记录)
|
||||||
|
* - zib_user_ban_appeal(wp-ajax) → admin-ajax aksrt_ban_appeal_submit 提交
|
||||||
*
|
*
|
||||||
* 弹窗经 AksrtModalRuntime.openRemote 打开(与参考主题 refresh_modal 每次刷新
|
* 弹窗经 AksrtModalRuntime.openRemote 打开(与参考主题 refresh_modal 每次刷新
|
||||||
* 同语义):桌面 600px 宽(参考主题未传 data-class → Bootstrap .modal-dialog
|
* 同语义):桌面 600px 宽(参考主题未传 data-class → Bootstrap .modal-dialog
|
||||||
@@ -24,15 +28,28 @@
|
|||||||
return String(raw).replace(/\/$/, '');
|
return String(raw).replace(/\/$/, '');
|
||||||
})();
|
})();
|
||||||
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
|
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
|
||||||
|
var AJAX_URL = (window.aksrtData && window.aksrtData.ajaxUrl)
|
||||||
|
|| (window._win && window._win.ajax_url)
|
||||||
|
|| '/wp-admin/admin-ajax.php';
|
||||||
|
|
||||||
var MODAL_HEIGHT = 360;
|
var MODAL_HEIGHT = 360;
|
||||||
|
// 参考主题 data_class='modal-mini' + data-height=240:400px 宽、初始高 240。
|
||||||
|
var MINI_MODAL_HEIGHT = 240;
|
||||||
|
|
||||||
function openBanModal(userId) {
|
function ajaxUrl() {
|
||||||
|
return AJAX_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRemoteModal(options) {
|
||||||
if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.openRemote !== 'function') {
|
if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.openRemote !== 'function') {
|
||||||
notyf('禁封组件加载失败,请刷新页面后重试', 'danger');
|
notyf('禁封组件加载失败,请刷新页面后重试', 'danger');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.AksrtModalRuntime.openRemote({
|
window.AksrtModalRuntime.openRemote(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openBanModal(userId) {
|
||||||
|
openRemoteModal({
|
||||||
url: API_ROOT + '/user-ban/modal',
|
url: API_ROOT + '/user-ban/modal',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
query: { user_id: userId },
|
query: { user_id: userId },
|
||||||
@@ -47,6 +64,93 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查看禁封状态弹窗(参考主题 zib_get_ban_info_modal / user_ban_info_modal)。
|
||||||
|
function openInfoModal(userId) {
|
||||||
|
openRemoteModal({
|
||||||
|
url: API_ROOT + '/user-ban/info-modal',
|
||||||
|
method: 'GET',
|
||||||
|
query: { user_id: userId },
|
||||||
|
cache: false,
|
||||||
|
height: MINI_MODAL_HEIGHT,
|
||||||
|
mobileBottom: true,
|
||||||
|
kind: 'user-ban-info',
|
||||||
|
className: 'aksrt-user-ban-info-modal',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁封申诉弹窗(参考主题 zib_get_user_ban_appeal_modal;表单走 admin-ajax)。
|
||||||
|
function openAppealModal(userId) {
|
||||||
|
openRemoteModal({
|
||||||
|
url: API_ROOT + '/user-ban/appeal-modal',
|
||||||
|
method: 'GET',
|
||||||
|
query: { user_id: userId },
|
||||||
|
cache: false,
|
||||||
|
height: MINI_MODAL_HEIGHT,
|
||||||
|
mobileBottom: true,
|
||||||
|
kind: 'user-ban-appeal',
|
||||||
|
className: 'aksrt-user-ban-appeal-modal',
|
||||||
|
onContent: function (html, session) {
|
||||||
|
if (session && session.shell) bindAppealForm(session.shell);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁封记录弹窗(参考主题 zib_get_ban_log_modal)。
|
||||||
|
function openLogModal(userId) {
|
||||||
|
openRemoteModal({
|
||||||
|
url: API_ROOT + '/user-ban/log-modal',
|
||||||
|
method: 'GET',
|
||||||
|
query: { user_id: userId },
|
||||||
|
cache: false,
|
||||||
|
height: MINI_MODAL_HEIGHT,
|
||||||
|
mobileBottom: true,
|
||||||
|
kind: 'user-ban-log',
|
||||||
|
className: 'aksrt-user-ban-log-modal',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindAppealForm(shell) {
|
||||||
|
var form = shell.querySelector('[data-aksrt-appeal-form]');
|
||||||
|
if (!form || form.dataset.aksrtAppealBound === '1') return;
|
||||||
|
form.dataset.aksrtAppealBound = '1';
|
||||||
|
Array.prototype.forEach.call(form.querySelectorAll('textarea.form-control'), bindAutoHeight);
|
||||||
|
form.addEventListener('submit', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
submitAppeal(form);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitAppeal(form) {
|
||||||
|
var btn = form.querySelector('[data-aksrt-appeal-submit]');
|
||||||
|
if (btn) btn.disabled = true;
|
||||||
|
notyf('正在提交申诉…', 'load', 3000, 'aksrt-appeal-submit');
|
||||||
|
var fd = new FormData(form);
|
||||||
|
if (!fd.get('action')) fd.set('action', 'aksrt_ban_appeal_submit');
|
||||||
|
fetch(ajaxUrl(), { method: 'POST', body: fd, credentials: 'same-origin' })
|
||||||
|
.then(function (res) { return res.json(); })
|
||||||
|
.then(function (data) {
|
||||||
|
if (data && !data.error) {
|
||||||
|
notyf(data.msg || '申诉已提交', 'success');
|
||||||
|
if (data.reload) {
|
||||||
|
window.setTimeout(function () { window.location.reload(); }, 400);
|
||||||
|
} else if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.close === 'function') {
|
||||||
|
window.AksrtModalRuntime.close('submit');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (btn) btn.disabled = false;
|
||||||
|
notyf((data && data.msg) || '提交失败', 'danger');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
if (btn) btn.disabled = false;
|
||||||
|
notyf('网络错误,请重试', 'danger');
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
var toast = document.getElementById('aksrt-appeal-submit');
|
||||||
|
if (toast) toast.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function collectPayload(shell, userId, unban) {
|
function collectPayload(shell, userId, unban) {
|
||||||
var payload = { user_id: userId, ban: unban ? 0 : 1 };
|
var payload = { user_id: userId, ban: unban ? 0 : 1 };
|
||||||
if (unban) {
|
if (unban) {
|
||||||
@@ -212,6 +316,33 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查看禁封状态入口(用户中心元信息 / 评论拒绝态 / 禁封信息弹窗内申诉按钮)。
|
||||||
|
var infoBtn = event.target.closest('[data-aksrt-ban-info]');
|
||||||
|
if (infoBtn) {
|
||||||
|
event.preventDefault();
|
||||||
|
var infoUser = parseInt(infoBtn.getAttribute('data-aksrt-ban-info'), 10);
|
||||||
|
if (infoUser > 0) openInfoModal(infoUser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 申诉入口(禁封信息弹窗 / 登录后禁封页)。
|
||||||
|
var appealBtn = event.target.closest('[data-aksrt-ban-appeal]');
|
||||||
|
if (appealBtn) {
|
||||||
|
event.preventDefault();
|
||||||
|
var appealUser = parseInt(appealBtn.getAttribute('data-aksrt-ban-appeal'), 10);
|
||||||
|
if (appealUser > 0) openAppealModal(appealUser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁封记录入口(禁封/解封弹窗内的记录链接,仅管理员)。
|
||||||
|
var logBtn = event.target.closest('[data-aksrt-ban-log]');
|
||||||
|
if (logBtn) {
|
||||||
|
event.preventDefault();
|
||||||
|
var logUser = parseInt(logBtn.getAttribute('data-aksrt-ban-log'), 10);
|
||||||
|
if (logUser > 0) openLogModal(logUser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 弹窗内交互(事件委托到 document,弹窗壳由 runtime portal 到 body)。
|
// 弹窗内交互(事件委托到 document,弹窗壳由 runtime portal 到 body)。
|
||||||
var active = window.AksrtModalRuntime && window.AksrtModalRuntime.getActive
|
var active = window.AksrtModalRuntime && window.AksrtModalRuntime.getActive
|
||||||
? window.AksrtModalRuntime.getActive()
|
? window.AksrtModalRuntime.getActive()
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ defined('ABSPATH') || exit;
|
|||||||
* @type array $profile_details Server-filtered fixed public profile fields.
|
* @type array $profile_details Server-filtered fixed public profile fields.
|
||||||
* @type array $profile_signals Identity chips (verified, level, badges).
|
* @type array $profile_signals Identity chips (verified, level, badges).
|
||||||
* @type array $verify_state Verification tri-state {state,label} for the details dialog.
|
* @type array $verify_state Verification tri-state {state,label} for the details dialog.
|
||||||
* @type array $profile_notice Ban/restriction notice {state,label,text}, if any.
|
|
||||||
* @type string $background_url Cover image URL.
|
* @type string $background_url Cover image URL.
|
||||||
* @type float $credit_balance Credit balance (own profile only).
|
* @type float $credit_balance Credit balance (own profile only).
|
||||||
* @type string $currency_name Currency/credits display name.
|
* @type string $currency_name Currency/credits display name.
|
||||||
@@ -85,7 +84,6 @@ function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array {
|
|||||||
? \AKSRT\User\Home::get_instance()
|
? \AKSRT\User\Home::get_instance()
|
||||||
: null;
|
: null;
|
||||||
$profile_signals = $user_home_manager ? $user_home_manager->get_profile_signals($user_id) : [];
|
$profile_signals = $user_home_manager ? $user_home_manager->get_profile_signals($user_id) : [];
|
||||||
$profile_notice = $user_home_manager ? $user_home_manager->get_profile_notice($user_id) : [];
|
|
||||||
$show_report = $user_home_manager ? $user_home_manager->show_report_entry() : false;
|
$show_report = $user_home_manager ? $user_home_manager->show_report_entry() : false;
|
||||||
$verify_state = $user_home_manager ? $user_home_manager->get_verify_state($user_id) : ['state' => '', 'label' => ''];
|
$verify_state = $user_home_manager ? $user_home_manager->get_verify_state($user_id) : ['state' => '', 'label' => ''];
|
||||||
|
|
||||||
@@ -97,7 +95,6 @@ function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array {
|
|||||||
'recent_posts' => $recent_posts,
|
'recent_posts' => $recent_posts,
|
||||||
'profile_details' => $profile_details,
|
'profile_details' => $profile_details,
|
||||||
'profile_signals' => $profile_signals,
|
'profile_signals' => $profile_signals,
|
||||||
'profile_notice' => $profile_notice,
|
|
||||||
'verify_state' => $verify_state,
|
'verify_state' => $verify_state,
|
||||||
'show_report' => $show_report,
|
'show_report' => $show_report,
|
||||||
'background_url' => $background_url,
|
'background_url' => $background_url,
|
||||||
|
|||||||
+427
-102
@@ -1,12 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* 前台用户禁封(小黑屋 / 封号)— 1:1 复刻参考主题 Zibll 前台禁封能力:
|
* 前台用户禁封(小黑屋 / 封号)— 1:1 复刻参考主题 Zibll 前台禁封能力:
|
||||||
* zib_get_edit_user_ban_link → aksrt_get_edit_user_ban_link(三点菜单入口)
|
* zib_get_edit_user_ban_link → aksrt_get_edit_user_ban_link(三点菜单入口)
|
||||||
* zib_get_set_user_ban_modal → aksrt_get_user_ban_modal(禁封 / 解封弹窗)
|
* zib_get_set_user_ban_modal → aksrt_get_user_ban_modal(禁封 / 解封弹窗)
|
||||||
|
* zib_get_ban_info_modal → aksrt_get_user_ban_info_modal(查看禁封状态)
|
||||||
|
* zib_get_user_ban_appeal_modal → aksrt_get_user_ban_appeal_modal(禁封申诉)
|
||||||
|
* zib_get_ban_log_modal → aksrt_get_user_ban_log_modal(禁封记录)
|
||||||
|
* zib_user_ban_restrict_login → aksrt_render_user_ban_block_page(登录后禁封页)
|
||||||
*
|
*
|
||||||
* 数据源唯一:\AKSRT\User\Restriction(wp_aksrt_user_restriction 表)。
|
* 数据源唯一:\AKSRT\User\Restriction(wp_aksrt_user_restriction 表)。
|
||||||
* 弹窗内容经 REST /user-ban/modal 输出(AksrtModalRuntime.openRemote 拉取,
|
* 弹窗内容经 REST /user-ban/*-modal 输出(AksrtModalRuntime.openRemote 拉取,
|
||||||
* 与参考主题 refresh_modal 每次刷新同语义),提交走 REST /user-ban/set。
|
* 与参考主题 refresh_modal 每次刷新同语义),提交走 REST /user-ban/set 与
|
||||||
|
* admin-ajax aksrt_ban_appeal_submit。
|
||||||
*
|
*
|
||||||
* @package AKSRT
|
* @package AKSRT
|
||||||
*/
|
*/
|
||||||
@@ -16,14 +21,15 @@ defined('ABSPATH') || exit;
|
|||||||
/**
|
/**
|
||||||
* 用户治理展示态(禁封 / 小黑屋 / 禁言),含请求级缓存。
|
* 用户治理展示态(禁封 / 小黑屋 / 禁言),含请求级缓存。
|
||||||
*
|
*
|
||||||
* 单一数据源为 \AKSRT\User\Restriction::get_display_state() / state_labels();
|
* 单一数据源为 \AKSRT\User\Restriction::get_display_state() / state_badges()
|
||||||
* 本 helper 供用户中心页头、评论拒绝态、昵称徽章等位置复用,避免重复查库。
|
* (颜色 / 文案 / 图标统一管理);本 helper 供身份行徽章、用户中心元信息、
|
||||||
|
* 评论拒绝态等位置复用,避免重复查库。
|
||||||
*
|
*
|
||||||
* 对齐参考主题:
|
* 对齐参考主题:
|
||||||
* zib_user_is_ban → aksrt_get_user_ban_state(状态判定)
|
* zib_user_is_ban → aksrt_get_user_ban_state(状态判定)
|
||||||
* zib_ban_author_header_identity_filter → 资料卡/用户中心身份行徽章
|
* zib_ban_author_header_identity_filter → 资料卡/用户中心身份行徽章
|
||||||
*
|
*
|
||||||
* @return array{state:string,label:string,icon:string}|array{} state 为空串表示无治理状态。
|
* @return array{state:string,label:string,tone:string,icon:string}|array{} state 为空串表示无治理状态。
|
||||||
*/
|
*/
|
||||||
function aksrt_get_user_ban_state(int $user_id): array
|
function aksrt_get_user_ban_state(int $user_id): array
|
||||||
{
|
{
|
||||||
@@ -43,16 +49,13 @@ function aksrt_get_user_ban_state(int $user_id): array
|
|||||||
return $cache[$user_id] = [];
|
return $cache[$user_id] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$labels = class_exists('\AKSRT\User\Restriction') ? \AKSRT\User\Restriction::state_labels() : [];
|
$badges = class_exists('\AKSRT\User\Restriction') ? \AKSRT\User\Restriction::state_badges() : [];
|
||||||
$icons = [
|
$badge = is_array($badges[$state] ?? null) ? $badges[$state] : [];
|
||||||
'banned' => 'ban',
|
|
||||||
'restricted' => 'ban',
|
|
||||||
'muted' => 'comment-slash',
|
|
||||||
];
|
|
||||||
return $cache[$user_id] = [
|
return $cache[$user_id] = [
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
'label' => (string) ($labels[$state] ?? ''),
|
'label' => (string) ($badge['label'] ?? ''),
|
||||||
'icon' => (string) ($icons[$state] ?? 'ban'),
|
'tone' => (string) ($badge['tone'] ?? 'c-gray'),
|
||||||
|
'icon' => (string) ($badge['icon'] ?? 'ban'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,56 +71,346 @@ function aksrt_is_user_ban_restricted(int $user_id): bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 禁封申诉页 URL(自定义页面模板 templates/pages/page-ban-appeal.php,
|
* 治理状态徽章(占据身份行元信息位,参考主题 author_header_identity 钩子 /
|
||||||
* 即参考主题 zib_get_ban_info_modal / zib_get_user_ban_info_link 的落地视图)。
|
* zib_ban_author_header_identity_filter:badg + 图标 + 文案,红=封号、
|
||||||
* 站点未创建该页面时返回空串,调用方应降级为纯文本徽章。
|
* 黄=小黑屋、灰=禁言,字段与颜色由 Restriction::state_badges() 统一管理)。
|
||||||
*/
|
*/
|
||||||
function aksrt_get_ban_appeal_page_url(): string
|
function aksrt_get_user_ban_identity_badge(int $user_id, string $class = ''): 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 语义:状态 + 箭头 + 查看入口)。
|
|
||||||
*
|
|
||||||
* 排版复用 uc-header__meta-item(inline-flex 居中、12px 图标、13px 字号),
|
|
||||||
* 与相邻元信息(用户名 / 邮箱 / 城市)高度与字号完全一致;箭头置于提示内部,
|
|
||||||
* 不使用 badg-sm(0.8em 会破坏与相邻项的字号一致性)。
|
|
||||||
*/
|
|
||||||
function aksrt_get_user_ban_notice_html(int $user_id): string
|
|
||||||
{
|
{
|
||||||
$state = aksrt_get_user_ban_state($user_id);
|
$state = aksrt_get_user_ban_state($user_id);
|
||||||
if (empty($state['state'])) {
|
if (empty($state['state'])) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
$tone = 'banned' === $state['state'] ? 'c-red' : 'c-yellow';
|
$classes = trim('badg ' . (string) $state['tone'] . ($class !== '' ? ' ' . $class : ''));
|
||||||
$inner = '<i class="fa fas fa-' . esc_attr((string) $state['icon']) . '" aria-hidden="true"></i>'
|
return '<span class="' . esc_attr($classes) . '" title="' . esc_attr((string) $state['label']) . '">'
|
||||||
. '<span class="' . $tone . '">' . esc_html((string) $state['label']) . '</span>'
|
. '<i class="fa fas fa-' . esc_attr((string) $state['icon']) . ' mr6" aria-hidden="true"></i>'
|
||||||
. '<i class="fa fas fa-angle-right" aria-hidden="true"></i>';
|
. esc_html((string) $state['label']) . '</span>';
|
||||||
$class = 'uc-header__meta-item aksrt-user-ban-notice';
|
}
|
||||||
$link = aksrt_get_ban_appeal_page_url();
|
|
||||||
if ($link === '') {
|
/**
|
||||||
return '<span class="' . $class . '">' . $inner . '</span>';
|
* 「查看禁封状态」入口(用户中心元信息位,参考主题 user_page_header_desc
|
||||||
|
* 钩子 / zib_get_user_ban_info_link:but 按钮 = 徽章 + 文案 + 右箭头,
|
||||||
|
* 点击经 data-aksrt-ban-info 委托打开禁封信息弹窗)。
|
||||||
|
*/
|
||||||
|
function aksrt_get_user_ban_info_link(int $user_id, string $class = ''): string
|
||||||
|
{
|
||||||
|
if (!aksrt_user_ban_enabled()) {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
return '<a class="' . $class . '" href="' . esc_url($link) . '" title="' . esc_attr__('查看禁封状态', 'aksrt') . '">' . $inner . '</a>';
|
$state = aksrt_get_user_ban_state($user_id);
|
||||||
|
if (empty($state['state'])) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$badge = aksrt_get_user_ban_identity_badge($user_id);
|
||||||
|
if ($badge === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$classes = trim('but p2-10 ' . (string) $state['tone'] . ($class !== '' ? ' ' . $class : ''));
|
||||||
|
return '<a class="' . esc_attr($classes) . '" href="javascript:;" role="button"'
|
||||||
|
. ' data-aksrt-ban-info="' . esc_attr((string) $user_id) . '"'
|
||||||
|
. ' title="' . esc_attr__('查看禁封状态', 'aksrt') . '">' . $badge
|
||||||
|
. '<i class="fa fas fa-angle-right em12 ml6" aria-hidden="true"></i></a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁封信息弹窗(1:1 复刻参考主题 zib_get_ban_info_modal:多彩头 c-gray +
|
||||||
|
* 禁封信息行 + 申诉入口;本人或管理员可见)。
|
||||||
|
*
|
||||||
|
* @return string 弹窗 HTML(空串 = 无权查看或用户不存在)
|
||||||
|
*/
|
||||||
|
function aksrt_get_user_ban_info_modal(int $user_id): string
|
||||||
|
{
|
||||||
|
$user = $user_id > 0 ? get_userdata($user_id) : null;
|
||||||
|
if (!$user) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$viewer_id = get_current_user_id();
|
||||||
|
$can_view = $viewer_id > 0 && ($viewer_id === $user_id || aksrt_current_user_can_ban($user_id));
|
||||||
|
if (!$can_view) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = class_exists('\AKSRT\User\Restriction')
|
||||||
|
? \AKSRT\User\Restriction::get_instance()->get_ban_info($user_id)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
$html = '<div class="aksrt-user-ban-info-modal">';
|
||||||
|
// 弹窗头(参考主题 zib_get_modal_colorful_header('c-gray', ban 图标, 标题))。
|
||||||
|
$html .= '<div class="modal-colorful-header colorful-bg c-gray"><button type="button" class="close" data-aksrt-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">' . aksrt_user_ban_close_svg() . '</button><div class="colorful-make"></div><div class="text-center"><div class="em2x"><i class="fa fas fa-ban" aria-hidden="true"></i></div><div class="mt10 em12 padding-w10">' . esc_html__('小黑屋禁封中', 'aksrt') . '</div></div></div>';
|
||||||
|
|
||||||
|
if (empty($info)) {
|
||||||
|
$html .= '<div class="text-center c-yellow" style="padding: 50px 0;">' . esc_html__('您已解封,请刷新页面', 'aksrt') . '</div></div>';
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
$set_row = static function (string $label, string $value): string {
|
||||||
|
if ($value === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return '<div class="mb10 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>';
|
||||||
|
};
|
||||||
|
|
||||||
|
$html .= '<div class="mb20">';
|
||||||
|
$html .= $set_row(__('用户名', 'aksrt'), $user->display_name);
|
||||||
|
$html .= $set_row(__('状态', 'aksrt'), (string) $info['status']);
|
||||||
|
$html .= $set_row(__('开始时间', 'aksrt'), (string) $info['start']);
|
||||||
|
$html .= $set_row(__('结束时间', 'aksrt'), (string) $info['end']);
|
||||||
|
$html .= $set_row(__('原因', 'aksrt'), (string) $info['reason']);
|
||||||
|
$html .= $set_row(__('说明', 'aksrt'), (string) $info['desc']);
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
if (!empty($info['no_appeal'])) {
|
||||||
|
$html .= '<div class="muted-box em09"><div class="c-yellow">' . esc_html__('您当前已无法进行申诉', 'aksrt') . '</div>';
|
||||||
|
if ((string) $info['no_appeal_desc'] !== '') {
|
||||||
|
$html .= '<div class="mt6">' . esc_html((string) $info['no_appeal_desc']) . '</div>';
|
||||||
|
}
|
||||||
|
$html .= '</div>';
|
||||||
|
$html .= '<div class="mt20 but-average modal-buts"><button type="button" class="but" data-aksrt-modal-close>' . esc_html__('取消', 'aksrt') . '</button></div>';
|
||||||
|
} else {
|
||||||
|
$appeal_link = $viewer_id === $user_id ? aksrt_get_user_ban_appeal_link($user_id, 'but c-blue') : '';
|
||||||
|
$html .= '<div class="mt20 but-average modal-buts">';
|
||||||
|
$html .= '<button type="button" class="but" data-aksrt-modal-close>' . esc_html__('取消', 'aksrt') . '</button>';
|
||||||
|
$html .= $appeal_link !== '' ? $appeal_link : '';
|
||||||
|
$html .= '</div>';
|
||||||
|
}
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 「申诉」入口(参考主题 zib_get_user_ban_appeal_link:禁封申诉 modal-mini,
|
||||||
|
* 仅本人可发起;点击经 data-aksrt-ban-appeal 委托打开申诉弹窗)。
|
||||||
|
*/
|
||||||
|
function aksrt_get_user_ban_appeal_link(int $user_id, string $class = ''): string
|
||||||
|
{
|
||||||
|
if ($user_id <= 0 || get_current_user_id() !== $user_id) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$classes = trim((string) ($class !== '' ? $class : 'but c-blue'));
|
||||||
|
return '<a class="' . esc_attr($classes) . '" href="javascript:;" role="button"'
|
||||||
|
. ' data-aksrt-ban-appeal="' . esc_attr((string) $user_id) . '">'
|
||||||
|
. '<i class="fa fas fa-gavel mr6" aria-hidden="true"></i>'
|
||||||
|
. esc_html__('申诉', 'aksrt') . '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁封申诉弹窗(1:1 复刻参考主题 zib_get_user_ban_appeal_modal:
|
||||||
|
* edit-header(gavel / c-blue / 禁封申诉)+ 禁封信息行 + 申诉说明 + 资料项
|
||||||
|
* 输入 + 审核中态 + 禁止申诉态;仅本人可见可提交)。
|
||||||
|
*
|
||||||
|
* @return string 弹窗 HTML(空串 = 非本人或用户不存在)
|
||||||
|
*/
|
||||||
|
function aksrt_get_user_ban_appeal_modal(int $user_id): string
|
||||||
|
{
|
||||||
|
$user = $user_id > 0 ? get_userdata($user_id) : null;
|
||||||
|
if (!$user || get_current_user_id() !== $user_id) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = class_exists('\AKSRT\User\Restriction')
|
||||||
|
? \AKSRT\User\Restriction::get_instance()->get_ban_info($user_id)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
$settings = class_exists('\AKSRT\User\BanAppeal') ? \AKSRT\User\BanAppeal::settings() : ['enabled' => true, 'desc' => '', 'keys' => []];
|
||||||
|
$status = class_exists('\AKSRT\User\BanAppeal') ? \AKSRT\User\BanAppeal::get_status($user_id) : '';
|
||||||
|
$payload = class_exists('\AKSRT\User\BanAppeal') ? \AKSRT\User\BanAppeal::get_payload($user_id) : [];
|
||||||
|
|
||||||
|
// 弹窗头(参考主题 edit-header:关闭钮 + gavel 徽标 + 标题居左)。
|
||||||
|
$html = '<div class="aksrt-user-ban-appeal-modal">';
|
||||||
|
$html .= '<div class="mb10 edit-header touch"><button type="button" class="close" data-aksrt-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">' . aksrt_user_ban_close_svg() . '</button><b class="flex ac c-blue"><span class="toggle-radius mr10 c-blue"><i class="fa fas fa-gavel" aria-hidden="true"></i></span>' . esc_html__('禁封申诉', 'aksrt') . '</b></div>';
|
||||||
|
|
||||||
|
if (empty($info)) {
|
||||||
|
$html .= '<div class="text-center c-yellow" style="padding: 50px 0;">' . esc_html__('您已解封,请刷新页面', 'aksrt') . '</div></div>';
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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>';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 禁封信息行(参考主题申诉弹窗内嵌信息框:用户名/状态/起止时间/原因/说明)。
|
||||||
|
$info_box = '<div class="mb10 muted-box padding-10 em09">'
|
||||||
|
. $set_row(__('用户名', 'aksrt'), $user->display_name)
|
||||||
|
. $set_row(__('状态', 'aksrt'), (string) $info['status'])
|
||||||
|
. $set_row(__('开始时间', 'aksrt'), (string) $info['start'])
|
||||||
|
. $set_row(__('结束时间', 'aksrt'), (string) $info['end'])
|
||||||
|
. $set_row(__('原因', 'aksrt'), (string) $info['reason'])
|
||||||
|
. $set_row(__('说明', 'aksrt'), (string) $info['desc'])
|
||||||
|
. '</div>';
|
||||||
|
|
||||||
|
if (!empty($info['no_appeal'])) {
|
||||||
|
// 禁止申诉态(参考主题:c-yellow box-body + 禁止申诉说明)。
|
||||||
|
$html .= '<div><div class="c-yellow box-body">' . esc_html__('抱歉!您当前已无法进行申诉', 'aksrt') . '</div>';
|
||||||
|
if ((string) $info['no_appeal_desc'] !== '') {
|
||||||
|
$html .= '<div class="muted-box mt10">' . esc_html((string) $info['no_appeal_desc']) . '</div>';
|
||||||
|
}
|
||||||
|
$html .= '</div></div>';
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($settings['enabled'])) {
|
||||||
|
$html .= '<div class="c-yellow box-body">' . esc_html__('申诉功能暂未开启', 'aksrt') . '</div></div>';
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = '';
|
||||||
|
$footer = '';
|
||||||
|
if ($status === 'pending') {
|
||||||
|
// 审核中态(参考主题:黄色 badg + 已提交资料行 + 提交时间)。
|
||||||
|
$input = '<div class="c-yellow badg block mb6">' . esc_html__('您的申诉正在审核中,请耐心等待', 'aksrt') . '</div>';
|
||||||
|
$data = is_array($payload['data'] ?? null) ? $payload['data'] : [];
|
||||||
|
if (!empty($data)) {
|
||||||
|
foreach ($data as $k => $v) {
|
||||||
|
$input .= $set_row((string) $k, (string) $v);
|
||||||
|
}
|
||||||
|
} elseif (!empty($payload['reason'])) {
|
||||||
|
$input .= $set_row(__('申诉说明', 'aksrt'), (string) $payload['reason']);
|
||||||
|
}
|
||||||
|
$input .= $set_row(__('提交时间', 'aksrt'), (string) ($payload['time'] ?? ''));
|
||||||
|
} else {
|
||||||
|
// 申诉表单(参考主题:资料项逐项输入 + 申诉说明 textarea + 双按钮)。
|
||||||
|
foreach ((array) $settings['keys'] as $key) {
|
||||||
|
$key = (string) $key;
|
||||||
|
if ($key === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$input .= '<div class="mb10"><div class="muted-2-color mb6">' . esc_html($key) . '</div>'
|
||||||
|
. '<input class="form-control" type="input" name="data[' . esc_attr($key) . ']" value="' . esc_attr((string) ($payload['data'][$key] ?? '')) . '"></div>';
|
||||||
|
}
|
||||||
|
$input .= '<div class="mb10"><div class="muted-2-color mb6">' . esc_html__('请填写申诉说明', 'aksrt') . '</div>'
|
||||||
|
. '<textarea class="form-control mb20" name="data[' . esc_attr__('申诉说明', 'aksrt') . ']" tabindex="2" placeholder="' . esc_attr__('详细的说明更容易通过审核喔', 'aksrt') . '" rows="3">' . esc_textarea((string) ($payload['data'][__('申诉说明', 'aksrt')] ?? ($payload['reason'] ?? ''))) . '</textarea></div>';
|
||||||
|
$footer = '<div class="but-average modal-buts">'
|
||||||
|
. '<input type="hidden" name="action" value="aksrt_ban_appeal_submit">'
|
||||||
|
. wp_nonce_field('aksrt_ban_appeal', '_wpnonce', false, false)
|
||||||
|
. '<button type="button" class="but" data-aksrt-modal-close>' . esc_html__('取消', 'aksrt') . '</button>'
|
||||||
|
. '<button type="submit" class="but c-blue" data-aksrt-appeal-submit>' . esc_html__('提交申诉', 'aksrt') . '</button>'
|
||||||
|
. '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$desc_html = (string) $settings['desc'] !== '' ? '<div class="mb10 em09">' . wp_kses_post(nl2br(esc_html((string) $settings['desc']))) . '</div>' : '';
|
||||||
|
$html .= '<form class="aksrt-user-ban-appeal-form" data-aksrt-appeal-form>'
|
||||||
|
. '<div class="scroll-y mini-scrollbar max-vh7">' . $info_box . $input . $desc_html . '</div>'
|
||||||
|
. $footer . '</form>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 「查看禁封记录」链接(参考主题 zib_get_user_ban_log_link:em09 c-yellow
|
||||||
|
* 链接触发禁封记录弹窗;仅具备禁封权限的管理员可见)。
|
||||||
|
*/
|
||||||
|
function aksrt_get_user_ban_log_link(int $user_id, string $class = '', string $con = ''): string
|
||||||
|
{
|
||||||
|
if ($user_id <= 0 || !aksrt_current_user_can_ban($user_id)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if ($con === '') {
|
||||||
|
$con = '<i class="fa fas fa-ban mr6" aria-hidden="true"></i>' . esc_html__('查看禁封记录', 'aksrt');
|
||||||
|
}
|
||||||
|
$classes = trim('em09 c-yellow pointer ' . $class);
|
||||||
|
return '<a class="' . esc_attr($classes) . '" href="javascript:;" role="button"'
|
||||||
|
. ' data-aksrt-ban-log="' . esc_attr((string) $user_id) . '">' . $con . '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁封记录弹窗(1:1 复刻参考主题 zib_get_ban_log_modal:多彩头 c-yellow +
|
||||||
|
* 用户行 + 计数 + 逐条记录卡(类型徽章 / 结束时间 / 原因 / 说明 / 禁止申诉);
|
||||||
|
* 仅具备禁封权限的管理员可见)。
|
||||||
|
*
|
||||||
|
* @return string 弹窗 HTML(空串 = 无权查看)
|
||||||
|
*/
|
||||||
|
function aksrt_get_user_ban_log_modal(int $user_id): string
|
||||||
|
{
|
||||||
|
$user = $user_id > 0 ? get_userdata($user_id) : null;
|
||||||
|
if (!$user || !aksrt_current_user_can_ban($user_id)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$logs = class_exists('\AKSRT\User\Restriction')
|
||||||
|
? \AKSRT\User\Restriction::get_instance()->get_user_restrictions($user_id, false)
|
||||||
|
: [];
|
||||||
|
$logs = array_values(array_filter((array) $logs, static function ($r): bool {
|
||||||
|
return in_array((string) ($r['type'] ?? ''), ['ban', 'suspend'], true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 弹窗头(参考主题 zib_get_modal_colorful_header('c-yellow', ban 图标, 标题))。
|
||||||
|
$html = '<div class="aksrt-user-ban-log-modal">';
|
||||||
|
$html .= '<div class="modal-colorful-header colorful-bg c-yellow"><button type="button" class="close" data-aksrt-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">' . aksrt_user_ban_close_svg() . '</button><div class="colorful-make"></div><div class="text-center"><div class="em2x"><i class="fa fas fa-ban" aria-hidden="true"></i></div><div class="mt10 em12 padding-w10">' . esc_html__('禁封记录', 'aksrt') . '</div></div></div>';
|
||||||
|
|
||||||
|
$user_name = function_exists('aksrt_get_user_name')
|
||||||
|
? aksrt_get_user_name(['id' => $user_id, 'vip' => false, 'medal' => false, 'auth' => false, 'level' => false])
|
||||||
|
: '<b>' . esc_html($user->display_name) . '</b>';
|
||||||
|
$avatar_url = get_avatar_url($user_id, ['size' => 40]);
|
||||||
|
$html .= '<div class="flex ac mb6"><img class="avatar-mini mr6" src="' . esc_url((string) $avatar_url) . '" alt="">' . $user_name . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- aksrt_get_user_name escapes internally.
|
||||||
|
|
||||||
|
if (empty($logs)) {
|
||||||
|
$html .= '<div class="text-center c-yellow" style="padding: 50px 0;">' . esc_html__('该用户暂无禁封记录', 'aksrt') . '</div></div>';
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '<div class="c-yellow mb10">' . esc_html(sprintf(__('该用户共有%d次禁封记录', 'aksrt'), count($logs))) . '</div>';
|
||||||
|
$html .= '<div class="scroll-y mini-scrollbar max-vh7">';
|
||||||
|
$i = 1;
|
||||||
|
foreach ($logs as $log) {
|
||||||
|
$type = (string) ($log['type'] ?? '');
|
||||||
|
$is_ban = $type === 'ban';
|
||||||
|
$type_badge = $is_ban ? __('禁封', 'aksrt') : __('小黑屋', 'aksrt');
|
||||||
|
$end_time = !empty($log['expires_at']) ? (string) $log['expires_at'] : __('永久', 'aksrt');
|
||||||
|
$no_appeal = get_user_meta($user_id, '_aksrt_no_appeal', true);
|
||||||
|
$raw = (string) ($log['reason'] ?? '');
|
||||||
|
$reason = $raw;
|
||||||
|
$desc = '';
|
||||||
|
$marker = "\n" . __('禁封说明:', 'aksrt');
|
||||||
|
$pos = strpos($raw, $marker);
|
||||||
|
if (false !== $pos) {
|
||||||
|
$reason = substr($raw, 0, $pos);
|
||||||
|
$desc = trim(substr($raw, $pos + strlen($marker)));
|
||||||
|
}
|
||||||
|
$no_appeal_html = '';
|
||||||
|
if ($i === 1 && is_array($no_appeal) && trim((string) ($no_appeal['desc'] ?? '')) !== '') {
|
||||||
|
$no_appeal_html = '<div class="flex"><div class="author-set-left" style="min-width: 80px;"><span class="c-red">' . esc_html__('禁止申诉', 'aksrt') . '</span></div><div class="author-set-right mt6">' . esc_html((string) $no_appeal['desc']) . '</div></div>';
|
||||||
|
}
|
||||||
|
$html .= '<div class="em09 muted-box muted-2-color mb6 padding-10">'
|
||||||
|
. '<div class="border-bottom flex jsb p-b6"><div class="muted-color"><badge class="mr6 c-blue">' . $i . '</badge>' . esc_html((string) ($log['created_at'] ?? '')) . '</div>'
|
||||||
|
. '<span class="badg badg-sm ' . ($is_ban ? 'c-red' : 'c-yellow') . '">' . esc_html($type_badge) . '</span></div>'
|
||||||
|
. '<div class="flex"><div class="author-set-left" style="min-width: 80px;">' . esc_html__('结束时间', 'aksrt') . '</div><div class="author-set-right mt6">' . esc_html($end_time) . '</div></div>'
|
||||||
|
. '<div class="flex"><div class="author-set-left" style="min-width: 80px;">' . esc_html__('原因', 'aksrt') . '</div><div class="author-set-right mt6">' . esc_html(trim($reason)) . '</div></div>'
|
||||||
|
. ($desc !== '' ? '<div class="flex"><div class="author-set-left" style="min-width: 80px;">' . esc_html__('说明', 'aksrt') . '</div><div class="author-set-right mt6">' . esc_html($desc) . '</div></div>' : '')
|
||||||
|
. $no_appeal_html
|
||||||
|
. '</div>';
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
$html .= '</div></div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 受限位置治理提示(评论拒绝态,参考主题 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 : '');
|
||||||
|
$inner = '<i class="fa fas fa-' . esc_attr((string) $state['icon']) . ' mr6" aria-hidden="true"></i>'
|
||||||
|
. '<span class="' . esc_attr((string) $state['tone']) . '">' . esc_html($text) . '</span>';
|
||||||
|
if (aksrt_current_user_can_ban($user_id) || get_current_user_id() === $user_id) {
|
||||||
|
$inner .= ' <a class="c-blue" href="javascript:;" data-aksrt-ban-info="' . esc_attr((string) $user_id) . '">'
|
||||||
|
. 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>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,8 +423,7 @@ function aksrt_get_user_ban_name_badge(int $user_id): string
|
|||||||
if (empty($state['state'])) {
|
if (empty($state['state'])) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
$tone = 'banned' === $state['state'] ? 'c-red' : 'c-yellow';
|
return '<i class="fa fas fa-' . esc_attr((string) $state['icon']) . ' ' . esc_attr((string) $state['tone']) . ' ml3 aksrt-user-ban-badge"'
|
||||||
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>';
|
. ' title="' . esc_attr((string) $state['label']) . '" aria-hidden="true"></i>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,27 +442,46 @@ function aksrt_get_user_ban_avatar_badge(int $user_id): string
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 受限位置治理提示(评论拒绝态,参考主题 zib_get_user_ban_nocan_info 语义:
|
* 登录后禁封状态页(1:1 复刻参考主题 zib_user_ban_restrict_login /
|
||||||
* 状态徽章 +「暂时无法操作」+ 查看禁封状态入口)。
|
* zib_die_page:红盒提示 + 禁封信息行 + 申诉权限 + 退出登录/申诉按钮)。
|
||||||
*
|
* 由 Restriction::restrict_frontend_access() 在 template_redirect 调用。
|
||||||
* @param string $msg 具体受阻的操作描述,如「发表评论」。
|
|
||||||
*/
|
*/
|
||||||
function aksrt_get_user_ban_nocan_html(int $user_id, string $msg = ''): string
|
function aksrt_render_user_ban_block_page(int $user_id): void
|
||||||
{
|
{
|
||||||
$state = aksrt_get_user_ban_state($user_id);
|
$user = $user_id > 0 ? get_userdata($user_id) : null;
|
||||||
if (empty($state['state'])) {
|
if (!$user) {
|
||||||
return '';
|
return;
|
||||||
}
|
}
|
||||||
$text = (string) $state['label'] . ($msg !== '' ? ',' . $msg : '');
|
|
||||||
$tone = 'banned' === $state['state'] ? 'c-red' : 'c-yellow';
|
$info = class_exists('\AKSRT\User\Restriction')
|
||||||
$link = aksrt_get_ban_appeal_page_url();
|
? \AKSRT\User\Restriction::get_instance()->get_ban_info($user_id)
|
||||||
$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')
|
if (function_exists('wp_style_is') && !wp_script_is('aksrt-user-ban', 'enqueued')) {
|
||||||
. '<i class="fa fas fa-angle-right em09 ml3" aria-hidden="true"></i></a>';
|
$ban_js = get_template_directory() . '/assets/js/user-ban.js';
|
||||||
|
if (file_exists($ban_js)) {
|
||||||
|
wp_enqueue_script(
|
||||||
|
'aksrt-user-ban',
|
||||||
|
get_template_directory_uri() . '/assets/js/user-ban.js',
|
||||||
|
['loader_js'],
|
||||||
|
filemtime($ban_js) ?: '1.0.0',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return '<p class="comments-permission-denied aksrt-user-ban-nocan">' . $inner . '</p>';
|
|
||||||
|
if (!headers_sent()) {
|
||||||
|
header('Content-Type: text/html; charset=' . get_bloginfo('charset'));
|
||||||
|
}
|
||||||
|
|
||||||
|
get_header();
|
||||||
|
get_template_part('templates/parts/user/ban-block', null, [
|
||||||
|
'user' => $user,
|
||||||
|
'info' => $info,
|
||||||
|
]);
|
||||||
|
get_footer();
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -323,40 +634,52 @@ function aksrt_get_user_ban_modal(int $user_id): string
|
|||||||
// ── 已禁封视图:解封(参考主题解封弹窗同构) ──
|
// ── 已禁封视图:解封(参考主题解封弹窗同构) ──
|
||||||
$is_ban = (string) ($ban['type'] ?? '') === 'ban';
|
$is_ban = (string) ($ban['type'] ?? '') === 'ban';
|
||||||
$unban_title = $is_ban ? __('解封用户', 'aksrt') : __('移出小黑屋', 'aksrt');
|
$unban_title = $is_ban ? __('解封用户', 'aksrt') : __('移出小黑屋', 'aksrt');
|
||||||
$status_text = $is_ban ? __('禁封中(禁止登录)', 'aksrt') : __('小黑屋', 'aksrt');
|
|
||||||
$end_time = !empty($ban['expires_at']) ? (string) $ban['expires_at'] : __('永久', 'aksrt');
|
|
||||||
// AKSRT 单一 reason 列:禁封说明以“禁封说明:”后缀并入(set_user_ban 同语义),
|
|
||||||
// 解封视图拆回参考主题的「原因 / 说明」两行。
|
|
||||||
$raw_reason = (string) ($ban['reason'] ?? '');
|
|
||||||
$reason = $raw_reason;
|
|
||||||
$ban_desc = '';
|
|
||||||
$desc_marker = __('禁封说明:', 'aksrt');
|
|
||||||
$marker_pos = strpos($raw_reason, "\n" . $desc_marker);
|
|
||||||
if (false !== $marker_pos) {
|
|
||||||
$reason = substr($raw_reason, 0, $marker_pos);
|
|
||||||
$ban_desc = substr($raw_reason, $marker_pos + strlen("\n" . $desc_marker));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用户名徽章(参考主题 zib_get_user_name(...&class=inflex ac badg p2-10 em09))。
|
// 用户名徽章(参考主题 zib_get_user_name(...&class=inflex ac badg p2-10 em09))。
|
||||||
$user_badge = function_exists('aksrt_get_user_name')
|
$user_badge = function_exists('aksrt_get_user_name')
|
||||||
? aksrt_get_user_name(['id' => $user_id, 'class' => 'inflex ac badg p2-10 em09', 'vip' => false, 'medal' => false, 'auth' => false, 'level' => false])
|
? aksrt_get_user_name(['id' => $user_id, 'class' => 'inflex ac badg p2-10 em09', 'vip' => false, 'medal' => false, 'auth' => false, 'level' => false])
|
||||||
: '<b>' . esc_html($user->display_name) . '</b>';
|
: '<b>' . esc_html($user->display_name) . '</b>';
|
||||||
|
|
||||||
$set_row = static function (string $label, string $value): string {
|
// 禁封信息行(与查看禁封状态弹窗 / 申诉弹窗 / 禁封页共用的单一数据源)。
|
||||||
return '<div class="mb10 flex"><div class="author-set-left" style="min-width: 80px;">' . esc_html($label) . '</div><div class="author-set-right mt6">' . $value . '</div></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- callers pass escaped HTML.
|
$info = class_exists('\AKSRT\User\Restriction')
|
||||||
|
? \AKSRT\User\Restriction::get_instance()->get_ban_info($user_id)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
$set_row = static function (string $label, string $value, bool $raw = false): string {
|
||||||
|
if ($value === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$html_value = $raw ? $value : nl2br(esc_html($value)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw callers pass internally-escaped HTML.
|
||||||
|
return '<div class="mb10 flex"><div class="author-set-left" style="min-width: 80px;">' . esc_html($label) . '</div><div class="author-set-right mt6">' . $html_value . '</div></div>';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 禁封记录入口(参考主题:>1 次时展示可点击记录链接,打开禁封记录弹窗)。
|
||||||
|
$log_count = 0;
|
||||||
|
if (class_exists('\AKSRT\User\Restriction')) {
|
||||||
|
$all = \AKSRT\User\Restriction::get_instance()->get_user_restrictions($user_id, false);
|
||||||
|
foreach ((array) $all as $r) {
|
||||||
|
if (in_array((string) ($r['type'] ?? ''), ['ban', 'suspend'], true)) {
|
||||||
|
$log_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$log_link = aksrt_get_user_ban_log_link($user_id, 'em09 c-yellow', sprintf(__('当前用户已有%d次禁封记录,请谨慎操作', 'aksrt'), $log_count));
|
||||||
|
$log_view = ($log_count > 1 && $log_link !== '')
|
||||||
|
? '<div class="mb10 flex"><div class="author-set-left" style="min-width: 80px;">' . esc_html__('禁封记录', 'aksrt') . '</div><div class="author-set-right mt6">' . $log_link . '</div></div>'
|
||||||
|
: '';
|
||||||
|
|
||||||
// 弹窗头(参考主题 zib_get_modal_colorful_header('c-yellow', ban 图标, 标题):
|
// 弹窗头(参考主题 zib_get_modal_colorful_header('c-yellow', ban 图标, 标题):
|
||||||
// 120px 多彩横幅 + 大图标 + 居中标题,与禁封视图的 edit-header 头对应)。
|
// 120px 多彩横幅 + 大图标 + 居中标题,与禁封视图的 edit-header 头对应)。
|
||||||
$html .= '<div class="modal-colorful-header colorful-bg c-yellow"><button type="button" class="close" data-aksrt-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">' . aksrt_user_ban_close_svg() . '</button><div class="colorful-make"></div><div class="text-center"><div class="em2x"><i class="fa fas fa-ban" aria-hidden="true"></i></div><div class="mt10 em12 padding-w10">' . esc_html($unban_title) . '</div></div></div>';
|
$html .= '<div class="modal-colorful-header colorful-bg c-yellow"><button type="button" class="close" data-aksrt-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">' . aksrt_user_ban_close_svg() . '</button><div class="colorful-make"></div><div class="text-center"><div class="em2x"><i class="fa fas fa-ban" aria-hidden="true"></i></div><div class="mt10 em12 padding-w10">' . esc_html($unban_title) . '</div></div></div>';
|
||||||
$html .= '<form class="aksrt-user-ban-form" data-aksrt-ban-form data-aksrt-ban-mode="unban" data-aksrt-ban-user="' . esc_attr((string) $user_id) . '">';
|
$html .= '<form class="aksrt-user-ban-form" data-aksrt-ban-form data-aksrt-ban-mode="unban" data-aksrt-ban-user="' . esc_attr((string) $user_id) . '">';
|
||||||
$html .= '<div class="mb20">';
|
$html .= '<div class="mb20">';
|
||||||
$html .= $set_row(__('用户', 'aksrt'), $user_badge); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
$html .= $set_row(__('用户', 'aksrt'), $user_badge, true); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- aksrt_get_user_name escapes internally.
|
||||||
$html .= $set_row(__('状态', 'aksrt'), esc_html($status_text));
|
$html .= $set_row(__('状态', 'aksrt'), (string) ($info['status'] ?? ''));
|
||||||
$html .= $set_row(__('开始时间', 'aksrt'), esc_html((string) ($ban['created_at'] ?? '')));
|
$html .= $set_row(__('开始时间', 'aksrt'), (string) ($info['start'] ?? ''));
|
||||||
$html .= $set_row(__('结束时间', 'aksrt'), esc_html($end_time));
|
$html .= $set_row(__('结束时间', 'aksrt'), (string) ($info['end'] ?? ''));
|
||||||
$html .= $set_row(__('原因', 'aksrt'), nl2br(esc_html($reason)));
|
$html .= $set_row(__('原因', 'aksrt'), (string) ($info['reason'] ?? ''));
|
||||||
$html .= $set_row(__('说明', 'aksrt'), nl2br(esc_html($ban_desc)));
|
$html .= $set_row(__('说明', 'aksrt'), (string) ($info['desc'] ?? ''));
|
||||||
|
$html .= $log_view;
|
||||||
$html .= '</div>';
|
$html .= '</div>';
|
||||||
$html .= '<div class="mb20"><textarea class="form-control" name="desc" tabindex="2" placeholder="' . esc_attr__('给用户的留言', 'aksrt') . '" rows="2" autoHeight="true" maxHeight="110"></textarea></div>';
|
$html .= '<div class="mb20"><textarea class="form-control" name="desc" tabindex="2" placeholder="' . esc_attr__('给用户的留言', 'aksrt') . '" rows="2" autoHeight="true" maxHeight="110"></textarea></div>';
|
||||||
$html .= '<div class="mt20 but-average modal-buts">';
|
$html .= '<div class="mt20 but-average modal-buts">';
|
||||||
@@ -371,7 +694,8 @@ function aksrt_get_user_ban_modal(int $user_id): string
|
|||||||
$close_svg = aksrt_user_ban_close_svg();
|
$close_svg = aksrt_user_ban_close_svg();
|
||||||
$html .= '<div class="mb10 edit-header touch"><button type="button" class="close" data-aksrt-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">' . $close_svg . '</button><b class="flex ac c-red"><span class="toggle-radius mr10 c-red"><i class="fa fas fa-ban" aria-hidden="true"></i></span>' . esc_html__('禁封用户', 'aksrt') . '</b></div>';
|
$html .= '<div class="mb10 edit-header touch"><button type="button" class="close" data-aksrt-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">' . $close_svg . '</button><b class="flex ac c-red"><span class="toggle-radius mr10 c-red"><i class="fa fas fa-ban" aria-hidden="true"></i></span>' . esc_html__('禁封用户', 'aksrt') . '</b></div>';
|
||||||
|
|
||||||
// 历史禁封次数提示(参考主题:>1 次时展示禁封记录链接,此处展示计数提示)。
|
// 历史禁封次数提示(1:1 参考主题 zib_get_set_user_ban_modal:>1 次时
|
||||||
|
// 展示「禁封记录」行 + 可点击的黄链(打开禁封记录弹窗))。
|
||||||
$log_count = 0;
|
$log_count = 0;
|
||||||
if (class_exists('\AKSRT\User\Restriction')) {
|
if (class_exists('\AKSRT\User\Restriction')) {
|
||||||
$all = \AKSRT\User\Restriction::get_instance()->get_user_restrictions($user_id, false);
|
$all = \AKSRT\User\Restriction::get_instance()->get_user_restrictions($user_id, false);
|
||||||
@@ -381,8 +705,9 @@ function aksrt_get_user_ban_modal(int $user_id): string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$log_view = $log_count > 1
|
$log_link = aksrt_get_user_ban_log_link($user_id, '', sprintf(__('当前用户已有%d次禁封记录,请谨慎操作', 'aksrt'), $log_count));
|
||||||
? '<div class="mb10 flex"><div class="author-set-left" style="min-width: 80px;">' . esc_html__('禁封记录', 'aksrt') . '</div><div class="author-set-right mt6"><span class="em09 c-yellow">' . esc_html(sprintf(__('当前用户已有%d次禁封记录,请谨慎操作', 'aksrt'), $log_count)) . '</span></div></div>'
|
$log_view = ($log_count > 1 && $log_link !== '')
|
||||||
|
? '<div class="mb10 flex"><div class="author-set-left" style="min-width: 80px;">' . esc_html__('禁封记录', 'aksrt') . '</div><div class="author-set-right mt6">' . $log_link . '</div></div>'
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
$html .= '<form class="aksrt-user-ban-form" data-aksrt-ban-form data-aksrt-ban-mode="ban" data-aksrt-ban-user="' . esc_attr((string) $user_id) . '">';
|
$html .= '<form class="aksrt-user-ban-form" data-aksrt-ban-form data-aksrt-ban-mode="ban" data-aksrt-ban-user="' . esc_attr((string) $user_id) . '">';
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="uc-header__meta desc user-identity flex ac hh">
|
<div class="uc-header__meta desc user-identity flex ac hh">
|
||||||
<?php
|
<?php
|
||||||
// 治理状态提示(参考主题 user_page_header_desc 钩子 / zib_get_user_ban_info_link:
|
// 治理状态入口(参考主题 user_page_header_desc 钩子 / zib_get_user_ban_info_link:
|
||||||
// 徽章 +「查看禁封状态」入口,直达禁封申诉页)。
|
// 用户中心「封禁」元信息,点击打开查看禁封状态弹窗 → 申诉弹窗)。
|
||||||
if (function_exists('aksrt_get_user_ban_notice_html')) {
|
if (function_exists('aksrt_get_user_ban_info_link')) {
|
||||||
echo aksrt_get_user_ban_notice_html((int) $user_id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper escapes internally.
|
echo aksrt_get_user_ban_info_link((int) $user_id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper escapes internally.
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<?php if (function_exists('aksrt_render_user_medal_entry')) : ?>
|
<?php if (function_exists('aksrt_render_user_medal_entry')) : ?>
|
||||||
|
|||||||
@@ -325,6 +325,28 @@ class UserController extends ControllerBase {
|
|||||||
'callback' => [$this, 'set_user_ban'],
|
'callback' => [$this, 'set_user_ban'],
|
||||||
'permission_callback' => [$this, 'check_permission'],
|
'permission_callback' => [$this, 'check_permission'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 前台查看禁封状态弹窗(1:1 复刻参考主题 zib_get_ban_info_modal;
|
||||||
|
// 本人或管理员可见,权限在 helper 内判定)。
|
||||||
|
register_rest_route(self::API_NAMESPACE, '/user-ban/info-modal', [
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => [$this, 'get_user_ban_info_modal'],
|
||||||
|
'permission_callback' => static function () { return is_user_logged_in(); },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 前台禁封申诉弹窗(1:1 复刻参考主题 zib_get_user_ban_appeal_modal;仅本人)。
|
||||||
|
register_rest_route(self::API_NAMESPACE, '/user-ban/appeal-modal', [
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => [$this, 'get_user_ban_appeal_modal'],
|
||||||
|
'permission_callback' => static function () { return is_user_logged_in(); },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 前台禁封记录弹窗(1:1 复刻参考主题 zib_get_ban_log_modal;仅管理员)。
|
||||||
|
register_rest_route(self::API_NAMESPACE, '/user-ban/log-modal', [
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => [$this, 'get_user_ban_log_modal'],
|
||||||
|
'permission_callback' => static function () { return is_user_logged_in(); },
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -263,10 +263,6 @@ trait UserControllerAuthPhoneOperations {
|
|||||||
$user = $this->auth_find_user($target, $channel);
|
$user = $this->auth_find_user($target, $channel);
|
||||||
if (!$user || !$this->auth_consume_code('login', $target, sanitize_text_field((string) $payload['code']), $channel)) {
|
if (!$user || !$this->auth_consume_code('login', $target, sanitize_text_field((string) $payload['code']), $channel)) {
|
||||||
return $this->error(__('账号或验证码错误', 'aksrt'), 401);
|
return $this->error(__('账号或验证码错误', 'aksrt'), 401);
|
||||||
}
|
|
||||||
// 禁封用户禁止登录(与密码登录 enforce_login_ban 同语义:封号=无法登录)。
|
|
||||||
if (class_exists('\AKSRT\User\Restriction') && \AKSRT\User\Restriction::get_instance()->is_banned((int) $user->ID)) {
|
|
||||||
return $this->error(\AKSRT\User\Restriction::get_instance()->login_ban_message((int) $user->ID), 403);
|
|
||||||
}
|
}
|
||||||
if ($oauth_data) {
|
if ($oauth_data) {
|
||||||
$oauth_data = \AKSRT\Auth\Service::claim_oauth_bind_context($obk);
|
$oauth_data = \AKSRT\Auth\Service::claim_oauth_bind_context($obk);
|
||||||
@@ -297,9 +293,9 @@ trait UserControllerAuthPhoneOperations {
|
|||||||
$resolved_login = \AKSRT\Auth\Service::resolve_email_to_username($target);
|
$resolved_login = \AKSRT\Auth\Service::resolve_email_to_username($target);
|
||||||
$user = wp_authenticate($resolved_login, (string) ($payload['password'] ?? $payload['pwd'] ?? ''));
|
$user = wp_authenticate($resolved_login, (string) ($payload['password'] ?? $payload['pwd'] ?? ''));
|
||||||
if (is_wp_error($user) || !$user instanceof \WP_User) {
|
if (is_wp_error($user) || !$user instanceof \WP_User) {
|
||||||
// 透传具体失败原因(禁封等),不得泛化为“账号或密码错误”。
|
// 透传具体失败原因,不得泛化为“账号或密码错误”。
|
||||||
$bind_error = $user instanceof \WP_Error
|
$bind_error = $user instanceof \WP_Error
|
||||||
? \AKSRT\Auth\Service::friendly_login_error($user->get_error_message(), (string) $user->get_error_code())
|
? \AKSRT\Auth\Service::friendly_login_error($user->get_error_message())
|
||||||
: '';
|
: '';
|
||||||
return $this->error($bind_error !== '' ? $bind_error : __('账号或密码错误', 'aksrt'), 401);
|
return $this->error($bind_error !== '' ? $bind_error : __('账号或密码错误', 'aksrt'), 401);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,63 @@ trait UserControllerRestrictionOperations {
|
|||||||
return $this->success(['data' => ['html' => $html]]);
|
return $this->success(['data' => ['html' => $html]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前台查看禁封状态弹窗内容(1:1 复刻参考主题 zib_get_ban_info_modal,
|
||||||
|
* 经 aksrt_get_user_ban_info_modal 渲染;本人或管理员可见)。
|
||||||
|
*/
|
||||||
|
public function get_user_ban_info_modal(\WP_REST_Request $request): \WP_REST_Response {
|
||||||
|
$user_id = (int) $request->get_param('user_id');
|
||||||
|
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||||
|
return $this->error('用户不存在', 404);
|
||||||
|
}
|
||||||
|
if (!function_exists('aksrt_get_user_ban_info_modal')) {
|
||||||
|
return $this->error('禁封组件未加载', 501);
|
||||||
|
}
|
||||||
|
$html = aksrt_get_user_ban_info_modal($user_id);
|
||||||
|
if ($html === '') {
|
||||||
|
return $this->error('抱歉,您暂时没有此权限', 403);
|
||||||
|
}
|
||||||
|
return $this->success(['data' => ['html' => $html]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前台禁封申诉弹窗内容(1:1 复刻参考主题 zib_get_user_ban_appeal_modal,
|
||||||
|
* 经 aksrt_get_user_ban_appeal_modal 渲染;仅本人可见)。
|
||||||
|
*/
|
||||||
|
public function get_user_ban_appeal_modal(\WP_REST_Request $request): \WP_REST_Response {
|
||||||
|
$user_id = (int) $request->get_param('user_id');
|
||||||
|
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||||
|
return $this->error('用户不存在', 404);
|
||||||
|
}
|
||||||
|
if (!function_exists('aksrt_get_user_ban_appeal_modal')) {
|
||||||
|
return $this->error('申诉组件未加载', 501);
|
||||||
|
}
|
||||||
|
$html = aksrt_get_user_ban_appeal_modal($user_id);
|
||||||
|
if ($html === '') {
|
||||||
|
return $this->error('抱歉,您暂时没有此权限', 403);
|
||||||
|
}
|
||||||
|
return $this->success(['data' => ['html' => $html]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前台禁封记录弹窗内容(1:1 复刻参考主题 zib_get_ban_log_modal,
|
||||||
|
* 经 aksrt_get_user_ban_log_modal 渲染;仅具备禁封权限的管理员可见)。
|
||||||
|
*/
|
||||||
|
public function get_user_ban_log_modal(\WP_REST_Request $request): \WP_REST_Response {
|
||||||
|
$user_id = (int) $request->get_param('user_id');
|
||||||
|
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||||
|
return $this->error('用户不存在', 404);
|
||||||
|
}
|
||||||
|
if (!function_exists('aksrt_get_user_ban_log_modal')) {
|
||||||
|
return $this->error('禁封组件未加载', 501);
|
||||||
|
}
|
||||||
|
$html = aksrt_get_user_ban_log_modal($user_id);
|
||||||
|
if ($html === '') {
|
||||||
|
return $this->error('抱歉,您暂时没有此权限', 403);
|
||||||
|
}
|
||||||
|
return $this->success(['data' => ['html' => $html]]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前台禁封 / 解封提交(1:1 复刻参考主题 zib_ajax_set_user_ban 语义):
|
* 前台禁封 / 解封提交(1:1 复刻参考主题 zib_ajax_set_user_ban 语义):
|
||||||
* ban=0 解封(留言站内信);ban=1 拉入小黑屋 / ban=2 禁封用户,
|
* ban=0 解封(留言站内信);ban=1 拉入小黑屋 / ban=2 禁封用户,
|
||||||
@@ -173,13 +230,6 @@ trait UserControllerRestrictionOperations {
|
|||||||
if ($time_day > 0) {
|
if ($time_day > 0) {
|
||||||
$notice .= ' ' . sprintf(__('期限:%1$d 天,%2$s 起自动解封。', 'aksrt'), $time_day, $expires);
|
$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);
|
$this->send_ban_notice($user_id, $notice, __('账号限制', 'aksrt'), $desc);
|
||||||
|
|
||||||
// 删除全部内容:全部公开内容与已审核评论移至回收站(参考主题同行为)。
|
// 删除全部内容:全部公开内容与已审核评论移至回收站(参考主题同行为)。
|
||||||
|
|||||||
+1
-4
@@ -467,10 +467,7 @@ final class OAuth {
|
|||||||
if (!$user instanceof \WP_User) {
|
if (!$user instanceof \WP_User) {
|
||||||
$this->redirect_error('account_match_failed', $provider, 'login');
|
$this->redirect_error('account_match_failed', $provider, 'login');
|
||||||
}
|
}
|
||||||
$login_error = OAuthAccountService::complete_login($user, true);
|
OAuthAccountService::complete_login($user, true);
|
||||||
if (is_wp_error($login_error)) {
|
|
||||||
$this->redirect_error('account_banned', $provider, 'login');
|
|
||||||
}
|
|
||||||
$this->redirect_success('login', $provider, OAuthUrlPolicy::validated_redirect($redirect, home_url('/')));
|
$this->redirect_success('login', $provider, OAuthUrlPolicy::validated_redirect($redirect, home_url('/')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,27 +97,10 @@ final class OAuthAccountService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function complete_login(\WP_User $user, bool $remember = true): void {
|
||||||
* 完成登录(设置会话与 Cookie)。
|
|
||||||
*
|
|
||||||
* 禁封用户禁止登录:与密码登录的 Restriction::enforce_login_ban 同语义
|
|
||||||
* (封号=无法登录),覆盖短信验证码、公众号扫码、第三方 OAuth 等
|
|
||||||
* 非密码登录路径。返回 WP_Error 时调用方须中止登录并透出原因。
|
|
||||||
*/
|
|
||||||
public static function complete_login(\WP_User $user, bool $remember = true): ?\WP_Error {
|
|
||||||
if (
|
|
||||||
class_exists('\AKSRT\User\Restriction')
|
|
||||||
&& \AKSRT\User\Restriction::get_instance()->is_banned((int) $user->ID)
|
|
||||||
) {
|
|
||||||
return new \WP_Error(
|
|
||||||
'user_banned',
|
|
||||||
\AKSRT\User\Restriction::get_instance()->login_ban_message((int) $user->ID)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
wp_set_current_user($user->ID);
|
wp_set_current_user($user->ID);
|
||||||
wp_set_auth_cookie($user->ID, $remember);
|
wp_set_auth_cookie($user->ID, $remember);
|
||||||
do_action('wp_login', $user->user_login, $user);
|
do_action('wp_login', $user->user_login, $user);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function bindings(int $user_id): array {
|
public static function bindings(int $user_id): array {
|
||||||
|
|||||||
@@ -209,10 +209,7 @@ final class OAuthProxyClient {
|
|||||||
$this->redirect_error('account_match_failed', $provider, $action);
|
$this->redirect_error('account_match_failed', $provider, $action);
|
||||||
}
|
}
|
||||||
|
|
||||||
$login_error = OAuthAccountService::complete_login($user, true);
|
OAuthAccountService::complete_login($user, true);
|
||||||
if (is_wp_error($login_error)) {
|
|
||||||
$this->redirect_error('account_banned', $provider, $action);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->redirect_success(
|
$this->redirect_success(
|
||||||
'login',
|
'login',
|
||||||
|
|||||||
+2
-11
@@ -67,13 +67,7 @@ final class Service {
|
|||||||
return sanitize_user($login, true);
|
return sanitize_user($login, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function friendly_login_error(string $raw_message, string $error_code = ''): string {
|
public static function friendly_login_error(string $raw_message): string {
|
||||||
// 禁封提示已是完整语义文案(无法登录 + 期限 + 原因),直接透出,
|
|
||||||
// 不得泛化为“用户名或密码错误”。
|
|
||||||
if ('user_banned' === $error_code) {
|
|
||||||
return wp_strip_all_tags($raw_message);
|
|
||||||
}
|
|
||||||
|
|
||||||
$msg_lower = wp_strip_all_tags(strtolower($raw_message));
|
$msg_lower = wp_strip_all_tags(strtolower($raw_message));
|
||||||
|
|
||||||
if (false !== strpos($msg_lower, 'incorrect password') || false !== strpos($msg_lower, '密码不正确')) {
|
if (false !== strpos($msg_lower, 'incorrect password') || false !== strpos($msg_lower, '密码不正确')) {
|
||||||
@@ -141,10 +135,7 @@ final class Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_wp_error($user)) {
|
if (is_wp_error($user)) {
|
||||||
return [
|
return ['success' => false, 'error' => self::friendly_login_error($user->get_error_message())];
|
||||||
'success' => false,
|
|
||||||
'error' => self::friendly_login_error($user->get_error_message(), (string) $user->get_error_code()),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// REST API 上下文中 wp_signon 可能无法正确设置 Cookie
|
// REST API 上下文中 wp_signon 可能无法正确设置 Cookie
|
||||||
|
|||||||
@@ -442,10 +442,7 @@ final class WeixinGzh {
|
|||||||
if (!$user instanceof \WP_User) {
|
if (!$user instanceof \WP_User) {
|
||||||
return $this->error_response(is_wp_error($user) ? $user->get_error_message() : __('微信账号匹配失败', 'aksrt'), 400);
|
return $this->error_response(is_wp_error($user) ? $user->get_error_message() : __('微信账号匹配失败', 'aksrt'), 400);
|
||||||
}
|
}
|
||||||
$login_error = OAuthAccountService::complete_login($user, true);
|
OAuthAccountService::complete_login($user, true);
|
||||||
if (is_wp_error($login_error)) {
|
|
||||||
return $this->error_response($login_error->get_error_message(), 403);
|
|
||||||
}
|
|
||||||
$redirect = OAuthUrlPolicy::validated_redirect((string) ($payload['redirect_to'] ?? ''), home_url('/'));
|
$redirect = OAuthUrlPolicy::validated_redirect((string) ($payload['redirect_to'] ?? ''), home_url('/'));
|
||||||
return new \WP_REST_Response([
|
return new \WP_REST_Response([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
|||||||
@@ -527,29 +527,6 @@ trait ScriptsPart1b {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 禁封申诉页
|
|
||||||
if (is_page_template('templates/pages/page-ban-appeal.php')) {
|
|
||||||
$ba_css = get_template_directory() . '/assets/css/routes/ban-appeal.css';
|
|
||||||
if (file_exists($ba_css)) {
|
|
||||||
wp_enqueue_style(
|
|
||||||
'aksrt-route-ban-appeal',
|
|
||||||
$base_uri . '/assets/css/routes/ban-appeal.css',
|
|
||||||
['aksrt-public'],
|
|
||||||
(string) filemtime($ba_css)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$ba_js = get_template_directory() . '/assets/js/ban-appeal-page.js';
|
|
||||||
if (file_exists($ba_js)) {
|
|
||||||
wp_enqueue_script(
|
|
||||||
'aksrt-ban-appeal-page',
|
|
||||||
$base_uri . '/assets/js/ban-appeal-page.js',
|
|
||||||
['loader_js'],
|
|
||||||
(string) filemtime($ba_js),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消息中心页
|
// 消息中心页
|
||||||
if (get_query_var('aksrt_message_view')) {
|
if (get_query_var('aksrt_message_view')) {
|
||||||
$messages_css = get_template_directory() . '/assets/css/frontend/messages.css';
|
$messages_css = get_template_directory() . '/assets/css/frontend/messages.css';
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ class Templates {
|
|||||||
'templates/pages/page-submit.php' => __('AKSRT - 写文章/投稿', 'aksrt'),
|
'templates/pages/page-submit.php' => __('AKSRT - 写文章/投稿', 'aksrt'),
|
||||||
'templates/pages/page-posts-navs.php' => __('AKSRT - 文章导航', 'aksrt'),
|
'templates/pages/page-posts-navs.php' => __('AKSRT - 文章导航', 'aksrt'),
|
||||||
'templates/pages/page-friends.php' => __('AKSRT - 友情链接', 'aksrt'),
|
'templates/pages/page-friends.php' => __('AKSRT - 友情链接', 'aksrt'),
|
||||||
'templates/pages/page-ban-appeal.php' => __('AKSRT - 禁封申诉', 'aksrt'),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -577,7 +577,7 @@ class Settings {
|
|||||||
* verify:array{enabled:bool,public_display:bool,badge_text:string,accept_email_verified:bool,require_manual_review:bool},
|
* verify:array{enabled:bool,public_display:bool,badge_text:string,accept_email_verified:bool,require_manual_review:bool},
|
||||||
* level:array{enabled:bool,public_display:bool,source:string,label_prefix:string,thresholds:array<int,int>},
|
* level:array{enabled:bool,public_display:bool,source:string,label_prefix:string,thresholds:array<int,int>},
|
||||||
* badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array<int,array{slug:string,label:string,tone:string}>},
|
* badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array<int,array{slug:string,label:string,tone:string}>},
|
||||||
* report:array{enabled:bool,public_entry:bool,public_notice:bool,ban_notice_text:string,categories:array<int,array{slug:string,label:string}>}
|
* report:array{enabled:bool,public_entry:bool,categories:array<int,array{slug:string,label:string}>}
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
public static function get_user_identity_settings(): array {
|
public static function get_user_identity_settings(): array {
|
||||||
|
|||||||
@@ -109,12 +109,11 @@ class UserRuntime {
|
|||||||
* level:array{enabled:bool,public_display:bool,source:string,label_prefix:string,thresholds:array<int,int>},
|
* level:array{enabled:bool,public_display:bool,source:string,label_prefix:string,thresholds:array<int,int>},
|
||||||
* medal:array{enabled:bool,public_display:bool,limit:int,categories:array<int,array{cat_name:string,items:array<int,array{slug:string,name:string,desc:string,icon:string,get_type:string,get_val:int}>}>,items_by_slug:array<string,array{slug:string,name:string,desc:string,icon:string,get_type:string,get_val:int,cat_name:string}>},
|
* medal:array{enabled:bool,public_display:bool,limit:int,categories:array<int,array{cat_name:string,items:array<int,array{slug:string,name:string,desc:string,icon:string,get_type:string,get_val:int}>}>,items_by_slug:array<string,array{slug:string,name:string,desc:string,icon:string,get_type:string,get_val:int,cat_name:string}>},
|
||||||
* badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array<int,array{slug:string,label:string,tone:string,desc:string,icon_url?:string}>},
|
* badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array<int,array{slug:string,label:string,tone:string,desc:string,icon_url?:string}>},
|
||||||
* report:array{enabled:bool,public_entry:bool,public_notice:bool,ban_notice_text:string,categories:array<int,array{slug:string,label:string}>}
|
* report:array{enabled:bool,public_entry:bool,categories:array<int,array{slug:string,label:string}>}
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
public static function get_user_identity_settings(): array {
|
public static function get_user_identity_settings(): array {
|
||||||
$settings = Settings::get_all();
|
$settings = Settings::get_all();
|
||||||
$ban_notice_text = trim((string) ($settings['user_report_ban_notice_text'] ?? ''));
|
|
||||||
$verify_types = [
|
$verify_types = [
|
||||||
'personal' => [
|
'personal' => [
|
||||||
'slug' => 'personal',
|
'slug' => 'personal',
|
||||||
@@ -174,13 +173,8 @@ class UserRuntime {
|
|||||||
],
|
],
|
||||||
'report' => [
|
'report' => [
|
||||||
// 默认开启:对齐后台 schema default true 与参考主题 _pz('user_report_s', true)。
|
// 默认开启:对齐后台 schema default true 与参考主题 _pz('user_report_s', true)。
|
||||||
'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? true),
|
'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? true),
|
||||||
'public_entry' => !isset($settings['user_report_public_entry']) || !empty($settings['user_report_public_entry']),
|
'public_entry' => !isset($settings['user_report_public_entry']) || !empty($settings['user_report_public_entry']),
|
||||||
'public_notice' => !isset($settings['user_report_public_notice']) || !empty($settings['user_report_public_notice']),
|
|
||||||
// 原样返回配置值:缺省时由 Home::get_profile_notice() 按治理展示态
|
|
||||||
// 取分级默认文案(封号=全面限制/小黑屋=部分限制/禁言=评论域),
|
|
||||||
// 不再用「部分互动能力已关闭」一刀切描述封号账号。
|
|
||||||
'ban_notice_text' => $ban_notice_text,
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -278,12 +272,10 @@ class UserRuntime {
|
|||||||
$settings = self::read_report_section();
|
$settings = self::read_report_section();
|
||||||
return [
|
return [
|
||||||
// 默认开启:对齐后台 schema default true 与参考主题 _pz('user_report_s', true)。
|
// 默认开启:对齐后台 schema default true 与参考主题 _pz('user_report_s', true)。
|
||||||
'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? true),
|
'enabled' => (bool) ($settings['user_report_s'] ?? $settings['user_report_enabled'] ?? true),
|
||||||
'public_entry' => !isset($settings['user_report_public_entry']) || !empty($settings['user_report_public_entry']),
|
'public_entry' => !isset($settings['user_report_public_entry']) || !empty($settings['user_report_public_entry']),
|
||||||
'public_notice' => !isset($settings['user_report_public_notice']) || !empty($settings['user_report_public_notice']),
|
'img_enabled' => !empty($settings['user_report_img_s']),
|
||||||
'ban_notice_text' => sanitize_text_field((string) ($settings['user_report_ban_notice_text'] ?? '')),
|
'desc' => wp_kses_post((string) ($settings['user_report_desc'] ?? '')),
|
||||||
'img_enabled' => !empty($settings['user_report_img_s']),
|
|
||||||
'desc' => wp_kses_post((string) ($settings['user_report_desc'] ?? '')),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-18
@@ -26,7 +26,6 @@ class BanAppeal {
|
|||||||
|
|
||||||
public function init(): void {
|
public function init(): void {
|
||||||
add_action('wp_ajax_aksrt_ban_appeal_submit', [$this, 'ajax_submit']);
|
add_action('wp_ajax_aksrt_ban_appeal_submit', [$this, 'ajax_submit']);
|
||||||
add_action('wp_ajax_aksrt_ban_appeal_status', [$this, 'ajax_status']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,23 +61,6 @@ class BanAppeal {
|
|||||||
return is_array($p) ? $p : [];
|
return is_array($p) ? $p : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ajax_status(): void {
|
|
||||||
if (!is_user_logged_in()) {
|
|
||||||
wp_send_json(['error' => 1, 'msg' => __('请先登录', 'aksrt')]);
|
|
||||||
}
|
|
||||||
$user_id = get_current_user_id();
|
|
||||||
$ban = class_exists('\AKSRT\User\Restriction')
|
|
||||||
? \AKSRT\User\Restriction::get_instance()->get_active_ban($user_id)
|
|
||||||
: null;
|
|
||||||
wp_send_json([
|
|
||||||
'error' => 0,
|
|
||||||
'banned' => (bool) $ban,
|
|
||||||
'ban' => $ban,
|
|
||||||
'status' => self::get_status($user_id),
|
|
||||||
'payload'=> self::get_payload($user_id),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ajax_submit(): void {
|
public function ajax_submit(): void {
|
||||||
if (!is_user_logged_in()) {
|
if (!is_user_logged_in()) {
|
||||||
wp_send_json(['error' => 1, 'msg' => __('请先登录', 'aksrt')]);
|
wp_send_json(['error' => 1, 'msg' => __('请先登录', 'aksrt')]);
|
||||||
@@ -114,6 +96,28 @@ class BanAppeal {
|
|||||||
|
|
||||||
$reason_key = isset($_POST['reason_key']) ? sanitize_text_field(wp_unslash((string) $_POST['reason_key'])) : '';
|
$reason_key = isset($_POST['reason_key']) ? sanitize_text_field(wp_unslash((string) $_POST['reason_key'])) : '';
|
||||||
$reason = isset($_POST['reason']) ? sanitize_textarea_field(wp_unslash((string) $_POST['reason'])) : '';
|
$reason = isset($_POST['reason']) ? sanitize_textarea_field(wp_unslash((string) $_POST['reason'])) : '';
|
||||||
|
$data_rows = null;
|
||||||
|
|
||||||
|
// 申诉弹窗表单(1:1 参考主题 zib_get_user_ban_appeal_modal):资料项以
|
||||||
|
// data[<key>] 逐项提交,申诉说明取 data['申诉说明'];旧版平铺字段保持兼容。
|
||||||
|
if (isset($_POST['data']) && is_array($_POST['data'])) {
|
||||||
|
$clean = [];
|
||||||
|
foreach ($_POST['data'] as $k => $v) {
|
||||||
|
$clean[sanitize_text_field(wp_unslash((string) $k))] = sanitize_textarea_field(wp_unslash((string) $v));
|
||||||
|
}
|
||||||
|
$data_rows = $clean;
|
||||||
|
$reason_key = '';
|
||||||
|
$reason = (string) ($clean[__('申诉说明', 'aksrt')] ?? '');
|
||||||
|
if ($reason === '') {
|
||||||
|
foreach ($clean as $v) {
|
||||||
|
if (trim((string) $v) !== '') {
|
||||||
|
$reason = (string) $v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($reason === '') {
|
if ($reason === '') {
|
||||||
wp_send_json(['error' => 1, 'msg' => __('请填写申诉说明', 'aksrt')]);
|
wp_send_json(['error' => 1, 'msg' => __('请填写申诉说明', 'aksrt')]);
|
||||||
}
|
}
|
||||||
@@ -123,6 +127,9 @@ class BanAppeal {
|
|||||||
'reason' => $reason,
|
'reason' => $reason,
|
||||||
'time' => current_time('Y-m-d H:i'),
|
'time' => current_time('Y-m-d H:i'),
|
||||||
];
|
];
|
||||||
|
if ($data_rows !== null) {
|
||||||
|
$payload['data'] = $data_rows;
|
||||||
|
}
|
||||||
update_user_meta($user_id, self::META_PAYLOAD, $payload);
|
update_user_meta($user_id, self::META_PAYLOAD, $payload);
|
||||||
update_user_meta($user_id, self::META_STATUS, 'pending');
|
update_user_meta($user_id, self::META_STATUS, 'pending');
|
||||||
|
|
||||||
|
|||||||
@@ -340,49 +340,6 @@ class Home {
|
|||||||
return ['state' => $type, 'label' => $label];
|
return ['state' => $type, 'label' => $label];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取公开治理提示。
|
|
||||||
*
|
|
||||||
* 状态标识对齐参考主题公开主页禁封徽章(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')) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
|
|
||||||
if (empty($settings['report']['enabled']) || empty($settings['report']['public_notice'])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$state = '';
|
|
||||||
if (class_exists('\AKSRT\User\Restriction')) {
|
|
||||||
$state = \AKSRT\User\Restriction::get_instance()->get_display_state($user_id);
|
|
||||||
}
|
|
||||||
if ($state === '') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提示尾句:后台配置优先;缺省按治理展示态取分级文案
|
|
||||||
// (封号=全面限制,小黑屋=部分限制,禁言=评论域限制)。
|
|
||||||
$text = trim((string) ($settings['report']['ban_notice_text'] ?? ''));
|
|
||||||
if ($text === '') {
|
|
||||||
$state_notices = \AKSRT\User\Restriction::state_notices();
|
|
||||||
$text = (string) ($state_notices[$state] ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'state' => $state,
|
|
||||||
'label' => \AKSRT\User\Restriction::state_labels()[$state] ?? '',
|
|
||||||
'text' => $text,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公开主页是否显示举报入口。
|
* 公开主页是否显示举报入口。
|
||||||
*/
|
*/
|
||||||
|
|||||||
+66
-55
@@ -38,8 +38,10 @@ class Restriction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function init(): void {
|
public function init(): void {
|
||||||
// Enforce bans on login
|
// 禁封用户前台拦截(对齐参考主题 zib_user_ban_restrict_login:
|
||||||
add_filter('authenticate', [$this, 'enforce_login_ban'], 999, 3);
|
// 封号用户可正常登录,但前台任何页面均展示禁封状态页;禁封中
|
||||||
|
// 不得拦截登录本身——登录后的落地页即禁封页)。
|
||||||
|
add_action('template_redirect', [$this, 'restrict_frontend_access'], 1);
|
||||||
// Enforce comment mute
|
// Enforce comment mute
|
||||||
add_filter('pre_comment_on_post', [$this, 'enforce_comment_mute']);
|
add_filter('pre_comment_on_post', [$this, 'enforce_comment_mute']);
|
||||||
}
|
}
|
||||||
@@ -193,8 +195,8 @@ class Restriction {
|
|||||||
/**
|
/**
|
||||||
* 治理展示态(空串 = 无治理状态;ban 优先于 suspend,对齐后台单一状态语义)。
|
* 治理展示态(空串 = 无治理状态;ban 优先于 suspend,对齐后台单一状态语义)。
|
||||||
*
|
*
|
||||||
* 供资料卡提示框(Home::get_profile_notice)、用户中心页头、评论拒绝态、
|
* 供公开主页身份行徽章、用户中心元信息、评论拒绝态、昵称徽章共用,
|
||||||
* 昵称徽章共用,文案见 state_labels()。
|
* 徽章字段(文案/颜色/图标)见 state_badges()。
|
||||||
*/
|
*/
|
||||||
public function get_display_state(int $user_id): string {
|
public function get_display_state(int $user_id): string {
|
||||||
if ($user_id <= 0) {
|
if ($user_id <= 0) {
|
||||||
@@ -213,60 +215,62 @@ class Restriction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 治理展示态徽章文案 — 对齐参考主题公开主页禁封标识
|
* 治理展示态徽章字段(颜色 / 文案 / 图标统一管理,全前端提示面共用):
|
||||||
* (zib_ban_author_header_identity_filter:封号 → 违规已封号,
|
* 封号(ban) → c-red 违规已封号(最严重:无法登录);
|
||||||
* 小黑屋 → 小黑屋禁封中);禁言为 AKSRT 扩展状态。
|
* 小黑屋(suspend) → c-yellow 小黑屋禁封中(部分限制);
|
||||||
|
* 禁言(mute) → c-gray 禁言中(评论域限制)。
|
||||||
|
* 对齐参考主题 zib_ban_author_header_identity_filter 的红/默认双色语义,
|
||||||
|
* 禁言为 AKSRT 扩展状态。
|
||||||
*
|
*
|
||||||
* @return array<string,string> 展示态 => 文案
|
* @return array<string,array{label:string,tone:string,icon:string}> 展示态 => 徽章字段
|
||||||
*/
|
*/
|
||||||
public static function state_labels(): array {
|
public static function state_badges(): array {
|
||||||
return [
|
return [
|
||||||
self::STATE_BANNED => __('违规已封号', 'aksrt'),
|
self::STATE_BANNED => ['label' => __('违规已封号', 'aksrt'), 'tone' => 'c-red', 'icon' => 'ban'],
|
||||||
self::STATE_RESTRICTED => __('小黑屋禁封中', 'aksrt'),
|
self::STATE_RESTRICTED => ['label' => __('小黑屋禁封中', 'aksrt'), 'tone' => 'c-yellow', 'icon' => 'ban'],
|
||||||
self::STATE_MUTED => __('禁言中', 'aksrt'),
|
self::STATE_MUTED => ['label' => __('禁言中', 'aksrt'), 'tone' => 'c-gray', 'icon' => 'comment-slash'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 治理展示态公开提示尾句(资料卡提示框,接在徽章文案之后):
|
* 生效禁封信息行(查看禁封状态弹窗 / 申诉弹窗 / 登录后禁封页 / 管理端
|
||||||
* 封号 = 全面限制(无法登录、功能停用);
|
* 禁封解封弹窗共用;对齐参考主题 zib_get_ban_info_modal 的字段集)。
|
||||||
* 小黑屋 = 部分限制(发布、评论等操作受限);
|
|
||||||
* 禁言 = 仅评论域受限。
|
|
||||||
* 后台 user_report_ban_notice_text 配置后覆盖全部状态。
|
|
||||||
*
|
*
|
||||||
* @return array<string,string> 展示态 => 提示文案
|
* @return array<string,mixed>|array{} 空数组表示无生效禁封/小黑屋。
|
||||||
*/
|
*/
|
||||||
public static function state_notices(): array {
|
public function get_ban_info(int $user_id): array {
|
||||||
return [
|
if ($user_id <= 0) {
|
||||||
self::STATE_BANNED => __('禁封期间无法登录,账号功能全面停用。', 'aksrt'),
|
return [];
|
||||||
self::STATE_RESTRICTED => __('发布、评论等操作已受限制。', 'aksrt'),
|
|
||||||
self::STATE_MUTED => __('暂时无法发表评论。', 'aksrt'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录禁封提示文案(登录拦截 / 免密登录 / Toast 共用)。
|
|
||||||
* 对齐参考主题登录拦截页语义:封号 = 全面限制(无法登录),附期限与原因。
|
|
||||||
*/
|
|
||||||
public function login_ban_message(int $user_id): string {
|
|
||||||
$msg = __('您的账号已被封禁,禁封期间无法登录。', 'aksrt');
|
|
||||||
$ban = $this->get_active_ban($user_id);
|
|
||||||
if (is_array($ban)) {
|
|
||||||
$msg .= !empty($ban['expires_at'])
|
|
||||||
? ' ' . sprintf(__('解封时间:%s。', 'aksrt'), (string) $ban['expires_at'])
|
|
||||||
: ' ' . __('本次封禁为永久封禁。', 'aksrt');
|
|
||||||
$reason = (string) ($ban['reason'] ?? '');
|
|
||||||
$marker = "\n" . __('禁封说明:', 'aksrt');
|
|
||||||
$pos = strpos($reason, $marker);
|
|
||||||
if (false !== $pos) {
|
|
||||||
$reason = substr($reason, 0, $pos);
|
|
||||||
}
|
|
||||||
$reason = trim($reason);
|
|
||||||
if ($reason !== '') {
|
|
||||||
$msg .= ' ' . sprintf(__('原因:%s', 'aksrt'), $reason);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return $msg;
|
$ban = $this->get_active_ban($user_id) ?: $this->get_active_suspend($user_id);
|
||||||
|
if (!is_array($ban)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = (string) ($ban['type'] ?? '');
|
||||||
|
$raw = (string) ($ban['reason'] ?? '');
|
||||||
|
$reason = $raw;
|
||||||
|
$desc = '';
|
||||||
|
$marker = "\n" . __('禁封说明:', 'aksrt');
|
||||||
|
$pos = strpos($raw, $marker);
|
||||||
|
if (false !== $pos) {
|
||||||
|
$reason = substr($raw, 0, $pos);
|
||||||
|
$desc = trim(substr($raw, $pos + strlen($marker)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$no_appeal = get_user_meta($user_id, '_aksrt_no_appeal', true);
|
||||||
|
$no_appeal_desc = is_array($no_appeal) ? trim((string) ($no_appeal['desc'] ?? '')) : '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => $type,
|
||||||
|
'status' => $type === self::TYPE_BAN ? __('禁封中(禁止登录)', 'aksrt') : __('小黑屋禁封中', 'aksrt'),
|
||||||
|
'start' => (string) ($ban['created_at'] ?? ''),
|
||||||
|
'end' => !empty($ban['expires_at']) ? (string) $ban['expires_at'] : __('永久', 'aksrt'),
|
||||||
|
'reason' => trim($reason),
|
||||||
|
'desc' => $desc,
|
||||||
|
'no_appeal' => $no_appeal_desc !== '',
|
||||||
|
'no_appeal_desc' => $no_appeal_desc,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -305,16 +309,23 @@ class Restriction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enforce ban on login.
|
* 前台禁封拦截页(1:1 复刻参考主题 zib_user_ban_restrict_login /
|
||||||
|
* zib_die_page):封号(ban)用户登录后访问任何前台页面,均展示禁封状态页
|
||||||
|
* (红盒提示 + 禁封信息行 + 申诉权限 + 退出登录/申诉按钮)。
|
||||||
|
* 小黑屋/禁言为部分限制,可正常浏览站点,不走此拦截。
|
||||||
*/
|
*/
|
||||||
public function enforce_login_ban($user, string $username, string $password) {
|
public function restrict_frontend_access(): void {
|
||||||
if (is_wp_error($user) || !$user instanceof \WP_User) {
|
if (is_admin() || wp_doing_cron() || wp_doing_ajax()) {
|
||||||
return $user;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->is_banned($user->ID)) {
|
$user_id = get_current_user_id();
|
||||||
return new \WP_Error('user_banned', $this->login_ban_message($user->ID));
|
if ($user_id <= 0 || !$this->is_banned($user_id)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return $user;
|
if (!function_exists('aksrt_render_user_ban_block_page')) {
|
||||||
|
require_once AKSRT_DIR . '/inc/helpers/user-ban.php';
|
||||||
|
}
|
||||||
|
aksrt_render_user_ban_block_page($user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Template Name: AKSRT - 禁封申诉
|
|
||||||
* Template Post Type: page
|
|
||||||
*
|
|
||||||
* @package AKSRT
|
|
||||||
*/
|
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
|
||||||
|
|
||||||
use AKSRT\User\BanAppeal;
|
|
||||||
use AKSRT\User\Restriction;
|
|
||||||
|
|
||||||
get_header();
|
|
||||||
|
|
||||||
if (!is_user_logged_in()) {
|
|
||||||
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url(get_permalink()) : wp_login_url(get_permalink());
|
|
||||||
wp_safe_redirect($login);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$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">
|
|
||||||
<div class="content-layout content-layout--no-sidebar">
|
|
||||||
<div class="theme-box radius8 main-bg main-shadow box-body mb20">
|
|
||||||
<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;">
|
|
||||||
<i class="fa fas fa-check-circle em2x" aria-hidden="true"></i>
|
|
||||||
<p class="mt10"><?php esc_html_e('当前账号未处于禁封状态。', 'aksrt'); ?></p>
|
|
||||||
</div>
|
|
||||||
<?php elseif (empty($settings['enabled'])) : ?>
|
|
||||||
<div class="c-red"><i class="fa fas fa-info-circle mr6" aria-hidden="true"></i><?php esc_html_e('申诉功能暂未开启', 'aksrt'); ?></div>
|
|
||||||
<?php elseif ($status === 'pending') : ?>
|
|
||||||
<div class="c-red mb10"><?php esc_html_e('您的申诉正在审核中,请耐心等待。', 'aksrt'); ?></div>
|
|
||||||
<?php if (!empty($payload['reason'])) : ?>
|
|
||||||
<div class="muted-2-color"><?php echo esc_html(sprintf(__('申诉内容:%s', 'aksrt'), (string) $payload['reason'])); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (!empty($payload['time'])) : ?>
|
|
||||||
<div class="muted-2-color mt6"><?php echo esc_html(sprintf(__('提交时间:%s', 'aksrt'), (string) $payload['time'])); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php else : ?>
|
|
||||||
<?php if (!empty($settings['desc'])) : ?>
|
|
||||||
<div class="muted-color mb20"><?php echo wp_kses_post(nl2br(esc_html((string) $settings['desc']))); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($status === 'rejected') : ?>
|
|
||||||
<div class="c-red mb15"><?php esc_html_e('上次申诉未通过,可修改后重新提交。', 'aksrt'); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<form class="aksrt-ban-appeal-form">
|
|
||||||
<?php if (!empty($settings['keys'])) : ?>
|
|
||||||
<div class="mb15">
|
|
||||||
<label class="em09 muted-2-color"><?php esc_html_e('申诉类型', 'aksrt'); ?></label>
|
|
||||||
<select name="reason_key" class="form-control">
|
|
||||||
<?php foreach ($settings['keys'] as $key) : ?>
|
|
||||||
<option value="<?php echo esc_attr($key); ?>"><?php echo esc_html($key); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="mb15">
|
|
||||||
<label class="em09 muted-2-color"><?php esc_html_e('申诉说明', 'aksrt'); ?> *</label>
|
|
||||||
<textarea name="reason" class="form-control" rows="5" required placeholder="<?php esc_attr_e('请说明误封原因或补充材料说明', 'aksrt'); ?>"><?php echo esc_textarea((string) ($payload['reason'] ?? '')); ?></textarea>
|
|
||||||
</div>
|
|
||||||
<?php wp_nonce_field('aksrt_ban_appeal', '_wpnonce', false); ?>
|
|
||||||
<input type="hidden" name="action" value="aksrt_ban_appeal_submit">
|
|
||||||
<button type="submit" class="but c-blue padding-lg">
|
|
||||||
<i class="fa fas fa-paper-plane" aria-hidden="true"></i> <?php esc_html_e('提交申诉', 'aksrt'); ?>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<?php
|
|
||||||
get_footer();
|
|
||||||
@@ -44,7 +44,6 @@ $last_login_ts = (int) get_user_meta($user_id, 'aksrt_last_login', true);
|
|||||||
$last_login_time = $last_login_ts > 0 ? wp_date('Y-m-d H:i:s', $last_login_ts) : '';
|
$last_login_time = $last_login_ts > 0 ? wp_date('Y-m-d H:i:s', $last_login_ts) : '';
|
||||||
$has_more_details = $registered_time !== '' || $last_login_time !== '' || !empty($profile_details['website']) || !empty($profile_details['qq']) || !empty($profile_details['wechat']) || !empty($profile_details['github']);
|
$has_more_details = $registered_time !== '' || $last_login_time !== '' || !empty($profile_details['website']) || !empty($profile_details['qq']) || !empty($profile_details['wechat']) || !empty($profile_details['github']);
|
||||||
$signals = $dashboard['profile_signals'] ?? [];
|
$signals = $dashboard['profile_signals'] ?? [];
|
||||||
$notice = $dashboard['profile_notice'] ?? null;
|
|
||||||
$show_report = !empty($dashboard['show_report']);
|
$show_report = !empty($dashboard['show_report']);
|
||||||
$bg_url = function_exists('aksrt_get_user_cover_url') ? aksrt_get_user_cover_url($user_id) : '';
|
$bg_url = function_exists('aksrt_get_user_cover_url') ? aksrt_get_user_cover_url($user_id) : '';
|
||||||
$is_own = !empty($dashboard['is_own_profile']);
|
$is_own = !empty($dashboard['is_own_profile']);
|
||||||
@@ -136,6 +135,11 @@ $show_cover_menu = $has_more_details || $show_cover_report || $cover_ban_html !=
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
// 治理状态徽章占据身份行元信息位(参考主题 author_header_identity 钩子:
|
||||||
|
// 红=违规已封号 / 黄=小黑屋禁封中 / 灰=禁言中,字段与颜色统一管理)。
|
||||||
|
$ban_badge = function_exists('aksrt_get_user_ban_identity_badge')
|
||||||
|
? aksrt_get_user_ban_identity_badge($user_id)
|
||||||
|
: '';
|
||||||
// legacy parity: the badge overview leads the identity row.
|
// legacy parity: the badge overview leads the identity row.
|
||||||
$city_badge = function_exists('aksrt_get_user_city_badge_html')
|
$city_badge = function_exists('aksrt_get_user_city_badge_html')
|
||||||
? aksrt_get_user_city_badge_html($user_id, 'user_home', 'badg')
|
? aksrt_get_user_city_badge_html($user_id, 'user_home', 'badg')
|
||||||
@@ -144,8 +148,9 @@ $show_cover_menu = $has_more_details || $show_cover_report || $cover_ban_html !=
|
|||||||
? aksrt_render_user_medal_entry($user_id, 'user-home', $is_own)
|
? aksrt_render_user_medal_entry($user_id, 'user-home', $is_own)
|
||||||
: '';
|
: '';
|
||||||
?>
|
?>
|
||||||
<?php if ($medal_entry !== '' || $city_badge !== '') : ?>
|
<?php if ($ban_badge !== '' || $medal_entry !== '' || $city_badge !== '') : ?>
|
||||||
<div class="aksrt-author-hero__identity user-identity flex ac hh">
|
<div class="aksrt-author-hero__identity user-identity flex ac hh">
|
||||||
|
<?php echo $ban_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper escapes internally. ?>
|
||||||
<?php echo $medal_entry; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
<?php echo $medal_entry; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
<?php echo $city_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
<?php echo $city_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
</div>
|
</div>
|
||||||
@@ -155,27 +160,6 @@ $show_cover_menu = $has_more_details || $show_cover_report || $cover_ban_html !=
|
|||||||
<p class="aksrt-author-hero__desc"><?php echo esc_html($user_description); ?></p>
|
<p class="aksrt-author-hero__desc"><?php echo esc_html($user_description); ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!empty($notice) && is_array($notice)) : ?>
|
|
||||||
<?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>
|
</div>
|
||||||
|
|
||||||
<div class="aksrt-author-hero__actions">
|
<div class="aksrt-author-hero__actions">
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 登录后禁封状态页 — 1:1 复刻参考主题 zib_user_ban_restrict_login(zib_die_page):
|
||||||
|
* 红盒提示 + 禁封信息行 + 申诉权限 + 退出登录/申诉按钮。
|
||||||
|
* 由 Restriction::restrict_frontend_access()(template_redirect)渲染。
|
||||||
|
*
|
||||||
|
* Contract: get_template_part(..., null, $args)
|
||||||
|
* user WP_User 被封禁用户
|
||||||
|
* info array Restriction::get_ban_info() 的禁封信息行
|
||||||
|
*
|
||||||
|
* @package AKSRT
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
$args = is_array($args ?? null) ? $args : [];
|
||||||
|
$user = $args['user'] ?? null;
|
||||||
|
$info = is_array($args['info'] ?? null) ? $args['info'] : [];
|
||||||
|
|
||||||
|
if (!($user instanceof WP_User)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = (int) $user->ID;
|
||||||
|
$null_img = function_exists('aksrt_null_image_url') ? aksrt_null_image_url('null.svg') : '';
|
||||||
|
$logout_url = wp_logout_url(home_url());
|
||||||
|
$appeal_link = function_exists('aksrt_get_user_ban_appeal_link')
|
||||||
|
? aksrt_get_user_ban_appeal_link($user_id, 'but c-blue padding-lg hollow')
|
||||||
|
: '';
|
||||||
|
|
||||||
|
$ban_set_row = static function (string $label, string $value): string {
|
||||||
|
if ($value === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return '<div class="mb10 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 flex ac">
|
||||||
|
<div class="f404 flex1">
|
||||||
|
<?php if ($null_img !== '') : ?>
|
||||||
|
<img src="<?php echo esc_url($null_img); ?>" alt="">
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="f404-msg mt20">
|
||||||
|
<div class="c-red box-body separator mb30"><?php esc_html_e('抱歉,您的账号当前处于禁封状态,暂时无法登录', 'aksrt'); ?></div>
|
||||||
|
<div class="mb20 muted-box text-left" style="max-width: 600px; margin: auto;">
|
||||||
|
<?php
|
||||||
|
echo $ban_set_row(__('用户名', 'aksrt'), $user->display_name); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- row builder escapes.
|
||||||
|
echo $ban_set_row(__('状态', 'aksrt'), (string) ($info['status'] ?? ''));
|
||||||
|
echo $ban_set_row(__('开始时间', 'aksrt'), (string) ($info['start'] ?? ''));
|
||||||
|
echo $ban_set_row(__('结束时间', 'aksrt'), (string) ($info['end'] ?? ''));
|
||||||
|
echo $ban_set_row(__('原因', 'aksrt'), (string) ($info['reason'] ?? ''));
|
||||||
|
echo $ban_set_row(__('说明', 'aksrt'), (string) ($info['desc'] ?? ''));
|
||||||
|
if (!empty($info['no_appeal'])) {
|
||||||
|
echo $ban_set_row(__('申诉权限', 'aksrt'), __('本次禁封您将不能发起申诉', 'aksrt'));
|
||||||
|
echo $ban_set_row(__('禁止申诉说明', 'aksrt'), (string) ($info['no_appeal_desc'] ?? ''));
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<div class="mt20 text-center">
|
||||||
|
<a href="<?php echo esc_url($logout_url); ?>" class="but c-yellow padding-lg hollow mr20"><?php esc_html_e('退出登录', 'aksrt'); ?></a>
|
||||||
|
<?php echo $appeal_link; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper escapes internally. ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -20,9 +20,9 @@ const uninstall = read('uninstall.php')
|
|||||||
const userBanHelper = read('inc/helpers/user-ban.php')
|
const userBanHelper = read('inc/helpers/user-ban.php')
|
||||||
const userBanJs = read('assets/js/user-ban.js')
|
const userBanJs = read('assets/js/user-ban.js')
|
||||||
const authorHero = read('templates/parts/user/author-hero.php')
|
const authorHero = read('templates/parts/user/author-hero.php')
|
||||||
|
const banBlock = read('templates/parts/user/ban-block.php')
|
||||||
const ucHeader = read('inc/helpers/user-center-renderers/profile-header.php')
|
const ucHeader = read('inc/helpers/user-center-renderers/profile-header.php')
|
||||||
const commentsHelper = read('inc/helpers/aksrt-comments.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 identityHelper = read('inc/helpers/aksrt-identity.php')
|
||||||
const avatarHelper = read('inc/helpers/aksrt-avatar.php')
|
const avatarHelper = read('inc/helpers/aksrt-avatar.php')
|
||||||
const singleComponents = read('inc/helpers/aksrt-single-components.php')
|
const singleComponents = read('inc/helpers/aksrt-single-components.php')
|
||||||
@@ -38,6 +38,9 @@ const oauthFlow = read('src/Auth/OAuth.php')
|
|||||||
const oauthProxy = read('src/Auth/OAuthProxyClient.php')
|
const oauthProxy = read('src/Auth/OAuthProxyClient.php')
|
||||||
const authJs = read('assets/js/auth.js')
|
const authJs = read('assets/js/auth.js')
|
||||||
const userCenterJs = read('assets/js/routes/user-center.js')
|
const userCenterJs = read('assets/js/routes/user-center.js')
|
||||||
|
const modalRuntimeCss = read('assets/css/frontend/modal-runtime.css')
|
||||||
|
const templatesMap = read('src/Core/Templates.php')
|
||||||
|
const scriptsPart1b = read('src/Core/ScriptsPart1b.php')
|
||||||
|
|
||||||
test('report REST surface is canonical: submit + users/reports only', () => {
|
test('report REST surface is canonical: submit + users/reports only', () => {
|
||||||
assert.match(userController, /'\/report\/submit'/)
|
assert.match(userController, /'\/report\/submit'/)
|
||||||
@@ -148,62 +151,84 @@ test('ban appeal approval actually lifts restrictions', () => {
|
|||||||
assert.ok(!usersMenus.includes('lift_ban'))
|
assert.ok(!usersMenus.includes('lift_ban'))
|
||||||
})
|
})
|
||||||
|
|
||||||
test('profile governance notice reads from the restriction table', () => {
|
test('ban identity badge occupies the public profile meta row', () => {
|
||||||
assert.match(home, /->get_display_state\(\$user_id\)/)
|
// 公开主页:禁封状态占据身份行元信息位(参考主题 author_header_identity),
|
||||||
assert.match(home, /Restriction::state_labels\(\)\[\$state\]/)
|
// 不再是资料卡下方提示框(notice 链已移除)。
|
||||||
assert.match(home, /\\AKSRT\\User\\Restriction::get_instance\(\)/)
|
assert.match(userBanHelper, /function aksrt_get_user_ban_identity_badge/)
|
||||||
assert.ok(!home.includes('aksrt_user_ban_state'))
|
assert.match(authorHero, /aksrt_get_user_ban_identity_badge\(\$user_id\)/)
|
||||||
// 提示框内容契约:state|label|text,label 对齐参考主题公开主页禁封徽章文案。
|
assert.match(authorHero, /aksrt-author-hero__identity/)
|
||||||
assert.match(home, /'state'\s*=> \$state/)
|
assert.ok(!authorHero.includes('aksrt-author-hero__notice'), 'profile notice box must stay removed')
|
||||||
assert.match(home, /'label'\s*=>/)
|
assert.ok(!authorHero.includes('profile_notice'), 'dashboard must not carry profile_notice anymore')
|
||||||
// 提示尾句分级:封号=全面限制(无法登录),不得沿用“部分互动能力已关闭”。
|
assert.ok(!home.includes('get_profile_notice'), 'Home::get_profile_notice must stay removed')
|
||||||
assert.match(home, /\$text = trim\(\(string\) \(\$settings\['report'\]\['ban_notice_text'\] \?\? ''\)\)/)
|
assert.ok(!mainCss.includes('aksrt-author-hero__notice'), 'notice box CSS must stay removed')
|
||||||
assert.match(home, /state_notices\(\)/)
|
// 徽章字段(文案/颜色/图标)由 Restriction::state_badges() 统一管理。
|
||||||
assert.ok(!userRuntime.includes('当前账号处于限制状态,部分互动能力已关闭'), 'generic partial-restriction copy must not be the banned-state default')
|
assert.match(userBanHelper, /state_badges\(\)/)
|
||||||
// 模板必须消费真实契约键:tone/message 双键曾导致资料卡下方提示框渲染为空。
|
// 死配置清理:公开提示开关与提示文案键不再进入运行时。
|
||||||
assert.match(authorHero, /\$notice\['state'\]/)
|
assert.ok(!userRuntime.includes('public_notice'), 'dead public_notice setting must stay removed')
|
||||||
assert.match(authorHero, /\$notice\['text'\]/)
|
assert.ok(!userRuntime.includes('ban_notice_text'), 'dead ban_notice_text setting must stay removed')
|
||||||
assert.match(authorHero, /\$notice\['label'\]/)
|
// 用户中心元信息入口:but 按钮(徽章 + 箭头)→ 查看禁封状态弹窗。
|
||||||
assert.ok(!authorHero.includes("$notice['message']"), 'template must not read the removed message key')
|
assert.match(userBanHelper, /function aksrt_get_user_ban_info_link/)
|
||||||
assert.ok(!authorHero.includes("$notice['tone']"), 'template must not read the removed tone key')
|
assert.match(userBanHelper, /data-aksrt-ban-info=/)
|
||||||
|
assert.match(ucHeader, /aksrt_get_user_ban_info_link/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('governance display state is a single source shared by every prompt surface', () => {
|
test('governance display state is a single source shared by every prompt surface', () => {
|
||||||
// 展示态与文案集中在 Restriction(资料卡/用户中心/评论拒绝态/昵称徽章共用)。
|
// 展示态与徽章字段集中在 Restriction(身份行/用户中心/评论拒绝态/昵称徽章共用)。
|
||||||
for (const state of ['STATE_BANNED', 'STATE_RESTRICTED', 'STATE_MUTED']) {
|
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, 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 function get_display_state\(int \$user_id\): string/)
|
||||||
assert.match(restriction, /public static function state_labels\(\): array/)
|
assert.match(restriction, /public static function state_badges\(\): array/)
|
||||||
assert.match(restriction, /public static function state_notices\(\): array/)
|
// 颜色统一管理:封号=红、小黑屋=黄、禁言=灰。
|
||||||
for (const label of ["__('违规已封号', 'aksrt')", "__('小黑屋禁封中', 'aksrt')", "__('禁言中', 'aksrt')"]) {
|
assert.match(restriction, /'label' => __\('违规已封号', 'aksrt'\),\s*'tone' => 'c-red'/)
|
||||||
assert.ok(restriction.includes(label), `missing state label: ${label}`)
|
assert.match(restriction, /'label' => __\('小黑屋禁封中', 'aksrt'\),\s*'tone' => 'c-yellow'/)
|
||||||
}
|
assert.match(restriction, /'label' => __\('禁言中', 'aksrt'\),\s*'tone' => 'c-gray'/)
|
||||||
// 提示尾句分级:封号=全面限制,小黑屋=部分限制,禁言=评论域限制。
|
// 禁封信息行单一数据源(查看禁封状态弹窗/申诉弹窗/禁封页/管理端弹窗共用)。
|
||||||
for (const notice of [
|
assert.match(restriction, /public function get_ban_info\(int \$user_id\): array/)
|
||||||
"__('禁封期间无法登录,账号功能全面停用。', 'aksrt')",
|
assert.match(restriction, /'status'\s*=>/)
|
||||||
"__('发布、评论等操作已受限制。', 'aksrt')",
|
assert.match(restriction, /'no_appeal_desc'/)
|
||||||
"__('暂时无法发表评论。', 'aksrt')",
|
|
||||||
]) {
|
|
||||||
assert.ok(restriction.includes(notice), `missing state notice: ${notice}`)
|
|
||||||
}
|
|
||||||
assert.match(restriction, /is_banned\(\$user_id\)[\s\S]*?is_suspended\(\$user_id\)[\s\S]*?is_restricted\(\$user_id, self::TYPE_MUTE\)/)
|
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 = \[\]/)
|
assert.match(userBanHelper, /static \$cache = \[\]/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('ban prompt surface mirrors the reference theme entry points', () => {
|
test('ban prompt surface mirrors the reference theme modals and block page', () => {
|
||||||
// 用户中心页头(参考主题 user_page_header_desc / zib_get_user_ban_info_link)。
|
// 查看禁封状态弹窗(1:1 zib_get_ban_info_modal:多彩头 + 信息行 + 申诉按钮)。
|
||||||
assert.match(userBanHelper, /function aksrt_get_user_ban_notice_html/)
|
assert.match(userBanHelper, /function aksrt_get_user_ban_info_modal/)
|
||||||
assert.match(userBanHelper, /function aksrt_get_ban_appeal_page_url/)
|
assert.match(userBanHelper, /aksrt-user-ban-info-modal/)
|
||||||
assert.match(ucHeader, /aksrt_get_user_ban_notice_html/)
|
assert.match(userBanHelper, /您已解封,请刷新页面/)
|
||||||
// 用户中心提示排版与相邻元信息一致:复用 uc-header__meta-item 结构,
|
assert.match(userBanHelper, /您当前已无法进行申诉/)
|
||||||
// 不用 badg-sm(0.8em 破坏字号一致性),箭头置于提示内部。
|
// 禁封申诉弹窗(1:1 zib_get_user_ban_appeal_modal:edit-header + 信息框 +
|
||||||
const noticeFn = userBanHelper.slice(userBanHelper.indexOf('function aksrt_get_user_ban_notice_html'))
|
// 资料项 inputs + 审核中态 + 禁止申诉态;仅本人)。
|
||||||
const noticeBody = noticeFn.slice(0, noticeFn.indexOf('\n}'))
|
assert.match(userBanHelper, /function aksrt_get_user_ban_appeal_modal/)
|
||||||
assert.match(noticeBody, /\$class = 'uc-header__meta-item aksrt-user-ban-notice';/)
|
assert.match(userBanHelper, /aksrt-user-ban-appeal-modal/)
|
||||||
assert.ok(!noticeBody.includes('badg-sm'), 'user center notice must not use badg-sm (shrinks font vs meta row)')
|
assert.match(userBanHelper, /function aksrt_get_user_ban_appeal_link/)
|
||||||
assert.match(noticeBody, /fa-angle-right/)
|
assert.match(userBanHelper, /data-aksrt-ban-appeal=/)
|
||||||
|
assert.match(userBanHelper, /抱歉!您当前已无法进行申诉/)
|
||||||
|
assert.match(userBanHelper, /您的申诉正在审核中,请耐心等待/)
|
||||||
|
assert.match(userBanHelper, /name="data\[/)
|
||||||
|
assert.match(userBanHelper, /详细的说明更容易通过审核喔/)
|
||||||
|
// 申诉表单 data[key] 结构与后端兼容。
|
||||||
|
assert.match(banAppeal, /is_array\(\$_POST\['data'\]\)/)
|
||||||
|
assert.match(banAppeal, /申诉说明/)
|
||||||
|
// 禁封记录弹窗 + 记录链接(1:1 zib_get_ban_log_modal / zib_get_user_ban_log_link)。
|
||||||
|
assert.match(userBanHelper, /function aksrt_get_user_ban_log_modal/)
|
||||||
|
assert.match(userBanHelper, /function aksrt_get_user_ban_log_link/)
|
||||||
|
assert.match(userBanHelper, /data-aksrt-ban-log=/)
|
||||||
|
assert.match(userBanHelper, /当前用户已有%d次禁封记录,请谨慎操作/)
|
||||||
|
assert.match(userBanHelper, /该用户共有%d次禁封记录/)
|
||||||
|
// 登录后禁封状态页(1:1 zib_user_ban_restrict_login / zib_die_page)。
|
||||||
|
assert.match(userBanHelper, /function aksrt_render_user_ban_block_page/)
|
||||||
|
assert.match(banBlock, /抱歉,您的账号当前处于禁封状态,暂时无法登录/)
|
||||||
|
assert.match(banBlock, /退出登录/)
|
||||||
|
assert.match(banBlock, /aksrt_get_user_ban_appeal_link/)
|
||||||
|
for (const row of ['用户名', '状态', '开始时间', '结束时间', '原因', '说明', '申诉权限']) {
|
||||||
|
assert.ok(banBlock.includes(`__('${row}', 'aksrt')`), `ban block page missing row: ${row}`)
|
||||||
|
}
|
||||||
|
// Restriction 以 template_redirect 收口(不禁登录)。
|
||||||
|
assert.match(restriction, /add_action\('template_redirect', \[\$this, 'restrict_frontend_access'\], 1\)/)
|
||||||
|
assert.match(restriction, /public function restrict_frontend_access\(\): void/)
|
||||||
|
assert.ok(!restriction.includes('enforce_login_ban'), 'login-time ban block must stay removed')
|
||||||
// 评论拒绝态(参考主题 zib_get_user_ban_nocan_info)。
|
// 评论拒绝态(参考主题 zib_get_user_ban_nocan_info)。
|
||||||
assert.match(userBanHelper, /function aksrt_get_user_ban_nocan_html/)
|
assert.match(userBanHelper, /function aksrt_get_user_ban_nocan_html/)
|
||||||
assert.match(commentsHelper, /aksrt_get_user_ban_nocan_html/)
|
assert.match(commentsHelper, /aksrt_get_user_ban_nocan_html/)
|
||||||
@@ -213,22 +238,27 @@ test('ban prompt surface mirrors the reference theme entry points', () => {
|
|||||||
assert.match(userBanHelper, /function aksrt_get_user_ban_avatar_badge/)
|
assert.match(userBanHelper, /function aksrt_get_user_ban_avatar_badge/)
|
||||||
assert.match(identityHelper, /aksrt_get_user_ban_name_badge\(\$user_id\)/)
|
assert.match(identityHelper, /aksrt_get_user_ban_name_badge\(\$user_id\)/)
|
||||||
assert.match(avatarHelper, /aksrt_get_user_ban_avatar_badge\(\$user_id\)/)
|
assert.match(avatarHelper, /aksrt_get_user_ban_avatar_badge\(\$user_id\)/)
|
||||||
// 禁封申诉页完整状态信息(参考主题 zib_get_ban_info_modal)。
|
// 三个弹窗 REST 路由(modal-mini 几何 / 每次刷新)。
|
||||||
assert.match(appealPage, /get_active_suspend\(\$user_id\)/)
|
for (const route of ['/user-ban/info-modal', '/user-ban/appeal-modal', '/user-ban/log-modal']) {
|
||||||
for (const row of ['状态', '开始时间', '结束时间', '原因', '说明']) {
|
assert.ok(userController.includes(`'${route}'`), `missing REST route: ${route}`)
|
||||||
assert.match(appealPage, new RegExp(`__\\('${row}', 'aksrt'\\)`), `appeal page missing status row: ${row}`)
|
}
|
||||||
|
// 前端委托与 admin-ajax 提交。
|
||||||
|
for (const attr of ['data-aksrt-ban-info', 'data-aksrt-ban-appeal', 'data-aksrt-ban-log']) {
|
||||||
|
assert.ok(userBanJs.includes(attr), `missing JS delegation: ${attr}`)
|
||||||
|
}
|
||||||
|
assert.match(userBanJs, /aksrt_ban_appeal_submit/)
|
||||||
|
assert.match(userBanJs, /new FormData\(form\)/)
|
||||||
|
// modal-mini 几何(400px)与头部样式覆盖三个新弹窗类。
|
||||||
|
for (const cls of ['aksrt-user-ban-info-modal', 'aksrt-user-ban-appeal-modal', 'aksrt-user-ban-log-modal']) {
|
||||||
|
assert.ok(modalRuntimeCss.includes(cls), `missing modal geometry for ${cls}`)
|
||||||
}
|
}
|
||||||
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 产物同步。
|
// 头像角标样式:源文件与入队的 min 产物同步。
|
||||||
for (const css of [mainCss, mainMinCss]) {
|
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')
|
assert.ok(css.includes('aksrt-avatar-ban-badge'), 'ban avatar badge CSS must exist in both main.css and main.min.css')
|
||||||
}
|
}
|
||||||
|
// 申诉页面模板与专属资产已移除(死代码清理)。
|
||||||
|
assert.ok(!templatesMap.includes('page-ban-appeal'), 'ban appeal page template registration must stay removed')
|
||||||
|
assert.ok(!scriptsPart1b.includes('page-ban-appeal'), 'ban appeal page assets must stay removed')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('report entry hides targets already banned or suspended', () => {
|
test('report entry hides targets already banned or suspended', () => {
|
||||||
@@ -239,35 +269,28 @@ test('report entry hides targets already banned or suspended', () => {
|
|||||||
assert.match(authorHero, /aksrt_is_user_ban_restricted\(\$user_id\)/)
|
assert.match(authorHero, /aksrt_is_user_ban_restricted\(\$user_id\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('banned users get the ban reason on every login path, not a credentials error', () => {
|
test('banned users may log in and meet the frontend block page', () => {
|
||||||
// 登录禁封文案单一来源(全面限制语义 + 期限 + 原因)。
|
// 对齐参考主题:封号用户可正常登录(不禁登录),登录后前台任何页面展示禁封页。
|
||||||
assert.match(restriction, /public function login_ban_message\(int \$user_id\): string/)
|
assert.ok(!restriction.includes("add_filter('authenticate'"), 'login-time ban block must stay removed')
|
||||||
assert.match(restriction, /__\('您的账号已被封禁,禁封期间无法登录。', 'aksrt'\)/)
|
assert.ok(!authService.includes('user_banned'), 'dead user_banned error branch must stay removed')
|
||||||
assert.match(restriction, /new \\WP_Error\('user_banned', \$this->login_ban_message/)
|
assert.ok(!authService.includes('get_error_code'), 'friendly_login_error must not carry the dead code param')
|
||||||
// Toast 透传:REST 层不得把失败原因硬编码为“账号或密码错误”。
|
assert.ok(!oauthAccountService.includes('is_banned'), 'complete_login must not gate login')
|
||||||
|
const completeLoginFn = oauthAccountService.slice(oauthAccountService.indexOf('function complete_login'))
|
||||||
|
const completeLoginBody = completeLoginFn.slice(0, completeLoginFn.indexOf('\n}'))
|
||||||
|
assert.ok(!completeLoginBody.includes('WP_Error'), 'complete_login must stay void')
|
||||||
|
assert.ok(!phoneOps.includes('login_ban_message'), 'SMS path must not carry the removed ban gate')
|
||||||
|
assert.ok(!weixinGzh.includes('account_banned'), 'weixin gzh must not carry the removed ban error code')
|
||||||
|
assert.ok(!oauthFlow.includes('account_banned'), 'oauth flow must not carry the removed ban error code')
|
||||||
|
assert.ok(!oauthProxy.includes('account_banned'), 'oauth proxy must not carry the removed ban error code')
|
||||||
|
for (const js of [authJs, userCenterJs]) {
|
||||||
|
assert.ok(!js.includes('account_banned'), 'removed account_banned JS message must stay gone')
|
||||||
|
}
|
||||||
|
// Toast 透传(一般性修复,保留):REST 层不得把失败原因硬编码为凭证错误。
|
||||||
assert.match(phoneOps, /\$result\['error'\]/)
|
assert.match(phoneOps, /\$result\['error'\]/)
|
||||||
assert.ok(
|
assert.ok(
|
||||||
phoneOps.includes("$login_error !== '' ? $login_error : __('账号或密码错误', 'aksrt')"),
|
phoneOps.includes("$login_error !== '' ? $login_error : __('账号或密码错误', 'aksrt')"),
|
||||||
'auth_login must pass through the real failure reason'
|
'auth_login must pass through the real failure reason'
|
||||||
)
|
)
|
||||||
assert.match(authService, /friendly_login_error\(\$user->get_error_message\(\), \(string\) \$user->get_error_code\(\)\)/)
|
|
||||||
assert.match(authService, /'user_banned' === \$error_code/)
|
|
||||||
// 短信验证码(免密)登录禁封闸口:封号用户不得经此路径登录。
|
|
||||||
assert.match(phoneOps, /is_banned\(\(int\) \$user->ID\)/)
|
|
||||||
assert.match(phoneOps, /login_ban_message\(\(int\) \$user->ID\)/)
|
|
||||||
// 非密码登录统一收口:complete_login 返回 WP_Error,调用方必须中止。
|
|
||||||
assert.match(oauthAccountService, /public static function complete_login\(\\WP_User \$user, bool \$remember = true\): \?\\WP_Error/)
|
|
||||||
assert.match(oauthAccountService, /is_banned\(\(int\) \$user->ID\)/)
|
|
||||||
assert.match(weixinGzh, /\$login_error = OAuthAccountService::complete_login/)
|
|
||||||
assert.match(weixinGzh, /error_response\(\$login_error->get_error_message\(\), 403\)/)
|
|
||||||
assert.match(oauthFlow, /\$login_error = OAuthAccountService::complete_login/)
|
|
||||||
assert.match(oauthFlow, /redirect_error\('account_banned'/)
|
|
||||||
assert.match(oauthProxy, /\$login_error = OAuthAccountService::complete_login/)
|
|
||||||
assert.match(oauthProxy, /redirect_error\('account_banned'/)
|
|
||||||
// 前端 OAuth 错误映射同步补充 account_banned。
|
|
||||||
for (const js of [authJs, userCenterJs]) {
|
|
||||||
assert.match(js, /account_banned: '您的账号已被封禁,禁封期间无法登录'/)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('admin report notification links to the real page', () => {
|
test('admin report notification links to the real page', () => {
|
||||||
@@ -402,9 +425,13 @@ test('comments expose a report entry for other users when reporting is enabled',
|
|||||||
test('frontend ban modal mirrors the reference theme zib_get_set_user_ban_modal 1:1', () => {
|
test('frontend ban modal mirrors the reference theme zib_get_set_user_ban_modal 1:1', () => {
|
||||||
// 禁封方式/禁封时间/预置原因徽章/其它原因/禁封说明/删除全部内容/禁止用户申诉,
|
// 禁封方式/禁封时间/预置原因徽章/其它原因/禁封说明/删除全部内容/禁止用户申诉,
|
||||||
// 以及已禁封用户的状态信息与解封留言表单(弹窗经 REST /user-ban/modal 输出)。
|
// 以及已禁封用户的状态信息与解封留言表单(弹窗经 REST /user-ban/modal 输出)。
|
||||||
for (const label of ['拉入小黑屋', '禁封用户', '禁封方式', '禁封时间', '选择禁封原因', '其它原因', '禁封说明', '删除全部内容', '禁止用户申诉', '请填写禁止申诉说明', '解封用户', '移出小黑屋', '禁封中(禁止登录)', '小黑屋', '给用户的留言', '开始时间', '结束时间', '原因', '说明', '禁封记录', '确认禁封']) {
|
for (const label of ['拉入小黑屋', '禁封用户', '禁封方式', '禁封时间', '选择禁封原因', '其它原因', '禁封说明', '删除全部内容', '禁止用户申诉', '请填写禁止申诉说明', '解封用户', '移出小黑屋', '给用户的留言', '开始时间', '结束时间', '原因', '说明', '禁封记录', '确认禁封']) {
|
||||||
assert.ok(userBanHelper.includes(label), `ban modal missing reference label: ${label}`)
|
assert.ok(userBanHelper.includes(label), `ban modal missing reference label: ${label}`)
|
||||||
}
|
}
|
||||||
|
// 状态文案由 Restriction::get_ban_info() 单一数据源产出(各提示面共用)。
|
||||||
|
for (const label of ['禁封中(禁止登录)', '小黑屋禁封中']) {
|
||||||
|
assert.ok(restriction.includes(label), `ban status label missing from Restriction: ${label}`)
|
||||||
|
}
|
||||||
// 时长选项 1/7/14/30/100/永久(默认 7 天);类型映射:小黑屋→suspend,禁封→ban。
|
// 时长选项 1/7/14/30/100/永久(默认 7 天);类型映射:小黑屋→suspend,禁封→ban。
|
||||||
assert.match(userBanHelper, /1 => __\('1天', 'aksrt'\)/)
|
assert.match(userBanHelper, /1 => __\('1天', 'aksrt'\)/)
|
||||||
assert.match(userBanHelper, /7 => __\('7天', 'aksrt'\)/)
|
assert.match(userBanHelper, /7 => __\('7天', 'aksrt'\)/)
|
||||||
|
|||||||
Reference in New Issue
Block a user