feat(user-ban): frontend ban modal replaces the backend restrictions tab
1:1 replicate the reference theme user-ban flow on the frontend: - Ban/unban modal rendered by inc/helpers/user-ban.php, opened via AksrtModalRuntime.openRemote (600px desktop dialog, 360px initial height, mobile bottom drawer, c-yellow colorful unban header). - Three entry points wired: user homepage cover three-dot menu, comment section menu, article author card menu (ban item takes priority over report, matching the reference hook order). - REST endpoints /user-ban/modal and /user-ban/set under aksrt/v1, guarded by check_permission, persisted through User\Restriction. - Submit semantics match zib_ajax_set_user_ban: ban reloads the page, unban only closes the modal. Cleanup (no dual-track): the backend ban-restrictions tab, its form handlers and flash keys are removed; ticket processing links to the frontend user homepage as the reference theme does; admin/report and appeal notification URLs move to type/status query args. Verification: 337/337 widget-system contract tests, 41/41 admin and front-submit tests, php -l clean on all changed PHP files.
This commit is contained in:
@@ -7224,6 +7224,141 @@ body.aksrt-modal-runtime-open {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ---- 禁封用户弹窗(参考主题 set_user_ban 模态框几何 1:1)----
|
||||
参考主题 zib_get_edit_user_ban_link 未传 data-class → 走 Bootstrap 默认
|
||||
.modal-dialog(>=768px 宽 600px);data-height=360 → .modal-content 初始
|
||||
高度 360 后动画至内容高度;mobile-bottom → <769px 底部抽屉。
|
||||
此处以 min-height:360px 复刻初始高度契约,桌面宽取 600px。 */
|
||||
.aksrt-user-ban-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
width: min(600px, calc(100vw - 32px));
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .aksrt-modal-runtime__skeleton {
|
||||
padding: 24px 18px;
|
||||
}
|
||||
|
||||
/* edit-header:与禁封表单头部同构(关闭钮靠右,红色警示徽标 + 标题居左)。 */
|
||||
.aksrt-user-ban-modal .edit-header {
|
||||
position: relative;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .edit-header .close {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
float: none;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
/* 解封视图多彩横幅(参考主题 zib_get_modal_colorful_header('c-yellow', ...)):
|
||||
120px 高绝对定位横幅 + 旋转彩斑,后续表单整体下移 120px。 */
|
||||
.aksrt-user-ban-modal .modal-colorful-header,
|
||||
.aksrt-user-ban-modal .colorful-bg {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: var(--this-color);
|
||||
background: var(--this-bg);
|
||||
background-size: 120%;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .modal-colorful-header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .modal-colorful-header + * {
|
||||
margin-top: 120px;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .modal-colorful-header > .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 9;
|
||||
color: var(--this-color);
|
||||
font-size: 18px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.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::after {
|
||||
position: absolute;
|
||||
color: var(--this-color);
|
||||
background: var(--this-bg);
|
||||
border-radius: 100%;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make {
|
||||
top: 60%;
|
||||
bottom: 0;
|
||||
width: 323px;
|
||||
height: 290px;
|
||||
animation: aksrt-ban-colorful 120s linear infinite;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before {
|
||||
top: -325px;
|
||||
right: -558px;
|
||||
bottom: 0;
|
||||
width: 677px;
|
||||
height: 300px;
|
||||
opacity: 0.6;
|
||||
transform: rotate(341deg);
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after {
|
||||
top: -170px;
|
||||
bottom: 0;
|
||||
left: -262px;
|
||||
width: 402px;
|
||||
height: 214px;
|
||||
opacity: 0.4;
|
||||
transform: rotate(326deg);
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make + * {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 单选徽章行(参考主题 .form-but-radio 契约):input 由 main.css 隐藏,
|
||||
label 可点;选中态由 main.css 的 input:checked + .but-radio 驱动。 */
|
||||
.aksrt-user-ban-modal .form-but-radio label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .form-but-radio .but-radio {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 开关行(删除全部内容 / 禁止用户申诉):原生 checkbox 隐藏,
|
||||
视觉态由 main.css 的 input:checked ~ .form-switch 驱动。 */
|
||||
.aksrt-user-ban-modal .aksrt-user-ban-form label {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@keyframes aksrt-ban-colorful {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ---- order review modal content ---- */
|
||||
.order-comment-from .shop-score-box {
|
||||
display: flex;
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -307,6 +307,141 @@ body.aksrt-modal-runtime-open {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ---- 禁封用户弹窗(参考主题 set_user_ban 模态框几何 1:1)----
|
||||
参考主题 zib_get_edit_user_ban_link 未传 data-class → 走 Bootstrap 默认
|
||||
.modal-dialog(>=768px 宽 600px);data-height=360 → .modal-content 初始
|
||||
高度 360 后动画至内容高度;mobile-bottom → <769px 底部抽屉。
|
||||
此处以 min-height:360px 复刻初始高度契约,桌面宽取 600px。 */
|
||||
.aksrt-user-ban-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
width: min(600px, calc(100vw - 32px));
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .aksrt-modal-runtime__skeleton {
|
||||
padding: 24px 18px;
|
||||
}
|
||||
|
||||
/* edit-header:与禁封表单头部同构(关闭钮靠右,红色警示徽标 + 标题居左)。 */
|
||||
.aksrt-user-ban-modal .edit-header {
|
||||
position: relative;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .edit-header .close {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
float: none;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 0;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
/* 解封视图多彩横幅(参考主题 zib_get_modal_colorful_header('c-yellow', ...)):
|
||||
120px 高绝对定位横幅 + 旋转彩斑,后续表单整体下移 120px。 */
|
||||
.aksrt-user-ban-modal .modal-colorful-header,
|
||||
.aksrt-user-ban-modal .colorful-bg {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: var(--this-color);
|
||||
background: var(--this-bg);
|
||||
background-size: 120%;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .modal-colorful-header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .modal-colorful-header + * {
|
||||
margin-top: 120px;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .modal-colorful-header > .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 9;
|
||||
color: var(--this-color);
|
||||
font-size: 18px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.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::after {
|
||||
position: absolute;
|
||||
color: var(--this-color);
|
||||
background: var(--this-bg);
|
||||
border-radius: 100%;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make {
|
||||
top: 60%;
|
||||
bottom: 0;
|
||||
width: 323px;
|
||||
height: 290px;
|
||||
animation: aksrt-ban-colorful 120s linear infinite;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::before {
|
||||
top: -325px;
|
||||
right: -558px;
|
||||
bottom: 0;
|
||||
width: 677px;
|
||||
height: 300px;
|
||||
opacity: 0.6;
|
||||
transform: rotate(341deg);
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make::after {
|
||||
top: -170px;
|
||||
bottom: 0;
|
||||
left: -262px;
|
||||
width: 402px;
|
||||
height: 214px;
|
||||
opacity: 0.4;
|
||||
transform: rotate(326deg);
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .colorful-bg > .colorful-make + * {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 单选徽章行(参考主题 .form-but-radio 契约):input 由 main.css 隐藏,
|
||||
label 可点;选中态由 main.css 的 input:checked + .but-radio 驱动。 */
|
||||
.aksrt-user-ban-modal .form-but-radio label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-user-ban-modal .form-but-radio .but-radio {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 开关行(删除全部内容 / 禁止用户申诉):原生 checkbox 隐藏,
|
||||
视觉态由 main.css 的 input:checked ~ .form-switch 驱动。 */
|
||||
.aksrt-user-ban-modal .aksrt-user-ban-form label {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@keyframes aksrt-ban-colorful {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ---- order review modal content ---- */
|
||||
.order-comment-from .shop-score-box {
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* AKSRT User Ban — 前台“禁封/解封用户”弹窗
|
||||
*
|
||||
* 1:1 复刻参考主题 Zibll 前台禁封能力:
|
||||
* - zib_get_edit_user_ban_link → [data-aksrt-ban] 委托入口(三点菜单内)
|
||||
* - zib_get_set_user_ban_modal → REST /user-ban/modal 拉取弹窗内容
|
||||
* - zib_ajax_set_user_ban → REST /user-ban/set 提交
|
||||
*
|
||||
* 弹窗经 AksrtModalRuntime.openRemote 打开(与参考主题 refresh_modal 每次刷新
|
||||
* 同语义):桌面 600px 宽(参考主题未传 data-class → Bootstrap .modal-dialog
|
||||
* 默认宽 600px)/ min-height 360px(data-height=360 → .modal-content 初始高度),
|
||||
* 移动端底部抽屉(mobileBottom)。提交成功语义与参考主题 zib_ajax_set_user_ban
|
||||
* 返回值一致:禁封 → reload:true(整页刷新),解封 → hide_modal:true(仅关闭弹窗)。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var API_ROOT = (function () {
|
||||
var d = window.aksrtData || {};
|
||||
var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1';
|
||||
return String(raw).replace(/\/$/, '');
|
||||
})();
|
||||
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
|
||||
|
||||
var MODAL_HEIGHT = 360;
|
||||
|
||||
function openBanModal(userId) {
|
||||
if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.openRemote !== 'function') {
|
||||
notyf('禁封组件加载失败,请刷新页面后重试', 'danger');
|
||||
return;
|
||||
}
|
||||
window.AksrtModalRuntime.openRemote({
|
||||
url: API_ROOT + '/user-ban/modal',
|
||||
method: 'GET',
|
||||
query: { user_id: userId },
|
||||
cache: false,
|
||||
height: MODAL_HEIGHT,
|
||||
mobileBottom: true,
|
||||
kind: 'user-ban',
|
||||
className: 'aksrt-user-ban-modal',
|
||||
onContent: function (html, session) {
|
||||
if (session && session.shell) bindBanModal(session.shell);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function collectPayload(shell, userId, unban) {
|
||||
var payload = { user_id: userId, ban: unban ? 0 : 1 };
|
||||
if (unban) {
|
||||
var descInput = shell.querySelector('[data-aksrt-ban-form] [name="desc"]');
|
||||
payload.desc = (descInput && descInput.value || '').trim();
|
||||
return payload;
|
||||
}
|
||||
|
||||
var form = shell.querySelector('[data-aksrt-ban-form]');
|
||||
if (!form) return payload;
|
||||
|
||||
var banInput = form.querySelector('input[name="ban"]:checked');
|
||||
payload.ban = banInput ? parseInt(banInput.value, 10) : 1;
|
||||
|
||||
var timeInput = form.querySelector('input[name="time_day"]:checked');
|
||||
payload.time_day = timeInput ? parseInt(timeInput.value, 10) : 7;
|
||||
|
||||
var reasonInput = form.querySelector('input[name="reason"]:checked');
|
||||
payload.reason = reasonInput ? reasonInput.value : '';
|
||||
var otherInput = form.querySelector('[name="reason_other"]');
|
||||
payload.reason_other = (otherInput && otherInput.value || '').trim();
|
||||
|
||||
var desc = form.querySelector('[name="desc"]');
|
||||
payload.desc = (desc && desc.value || '').trim();
|
||||
|
||||
payload.delete_content = !!(form.querySelector('[name="delete_content"]') || {}).checked;
|
||||
payload.no_appeal = !!(form.querySelector('[name="no_appeal"]') || {}).checked;
|
||||
var noAppealDesc = form.querySelector('[name="no_appeal_desc"]');
|
||||
payload.no_appeal_desc = (noAppealDesc && noAppealDesc.value || '').trim();
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function submitBan(shell, userId, unban) {
|
||||
var payload = collectPayload(shell, userId, unban);
|
||||
|
||||
if (!unban) {
|
||||
var reason = payload.reason === 'other' || !payload.reason ? payload.reason_other : payload.reason;
|
||||
if (!reason) {
|
||||
notyf('请选择或输入禁封原因', 'danger');
|
||||
return;
|
||||
}
|
||||
if (payload.no_appeal && !payload.no_appeal_desc) {
|
||||
notyf('请输入禁止用户申诉的说明或原因', 'danger');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var submitBtn = shell.querySelector(unban ? '[data-aksrt-unban-submit]' : '[data-aksrt-ban-submit]');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
notyf(unban ? '正在解封…' : '正在提交禁封…', 'load', 3000, 'aksrt-ban-submit');
|
||||
|
||||
window
|
||||
.fetch(API_ROOT + '/user-ban/set', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (result) {
|
||||
if (result && result.success) {
|
||||
notyf(result.message || '操作成功', 'success');
|
||||
// 参考主题 zib_ajax_set_user_ban 返回值语义:
|
||||
// 禁封 → reload:true(整页刷新,入口按钮状态随页面重渲染);
|
||||
// 解封 → hide_modal:true(仅关闭弹窗)。
|
||||
if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.close === 'function') {
|
||||
window.AksrtModalRuntime.close('submit');
|
||||
}
|
||||
if (!unban) window.location.reload();
|
||||
} else {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
notyf((result && result.message) || '操作失败', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
notyf('网络错误,请重试', 'danger');
|
||||
})
|
||||
.then(function () {
|
||||
var toast = document.getElementById('aksrt-ban-submit');
|
||||
if (toast) toast.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function bindAutoHeight(textarea) {
|
||||
// 1:1 复刻参考主题 textarea[autoHeight] 行为(input/focus 触发):
|
||||
// 高度 = scrollHeight + 上下边框;超过 maxHeight 属性则锁定 maxHeight 并
|
||||
// 打开自身滚动条(参考主题 main.js autoHeight 同语义)。
|
||||
if (!textarea || textarea.dataset.aksrtAutoHeight === '1') return;
|
||||
textarea.dataset.aksrtAutoHeight = '1';
|
||||
var grow = function () {
|
||||
var style = getComputedStyle(textarea);
|
||||
var border = (parseInt(style.getPropertyValue('border-top-width'), 10) || 0)
|
||||
+ (parseInt(style.getPropertyValue('border-bottom-width'), 10) || 0);
|
||||
var max = parseInt(textarea.getAttribute('maxHeight'), 10) || 0;
|
||||
textarea.style.height = 'auto';
|
||||
if (max > textarea.scrollHeight) {
|
||||
textarea.style.height = textarea.scrollHeight + border + 'px';
|
||||
textarea.style.overflow = 'hidden';
|
||||
} else {
|
||||
textarea.style.height = max + 'px';
|
||||
textarea.style.overflow = 'auto';
|
||||
}
|
||||
};
|
||||
textarea.addEventListener('input', grow);
|
||||
textarea.addEventListener('focus', grow);
|
||||
if (textarea.offsetHeight > 0) grow();
|
||||
}
|
||||
|
||||
function syncBanForm(shell) {
|
||||
var form = shell.querySelector('[data-aksrt-ban-form]');
|
||||
if (!form || form.getAttribute('data-aksrt-ban-mode') !== 'ban') return;
|
||||
|
||||
var banInput = form.querySelector('input[name="ban"]:checked');
|
||||
var ban = banInput ? banInput.value : '1';
|
||||
Array.prototype.forEach.call(form.querySelectorAll('[data-aksrt-ban-desc]'), function (el) {
|
||||
el.style.display = el.getAttribute('data-aksrt-ban-desc') === ban ? '' : 'none';
|
||||
});
|
||||
|
||||
var reasonInput = form.querySelector('input[name="reason"]:checked');
|
||||
var otherBox = form.querySelector('[data-aksrt-ban-reason-other]');
|
||||
if (otherBox) otherBox.style.display = reasonInput && reasonInput.value === 'other' ? '' : 'none';
|
||||
|
||||
var noAppealBox = form.querySelector('[data-aksrt-ban-noappeal-desc]');
|
||||
var noAppealInput = form.querySelector('[name="no_appeal"]');
|
||||
if (noAppealBox) noAppealBox.style.display = noAppealInput && noAppealInput.checked ? '' : 'none';
|
||||
}
|
||||
|
||||
function bindBanModal(shell) {
|
||||
Array.prototype.forEach.call(shell.querySelectorAll('textarea.form-control'), bindAutoHeight);
|
||||
syncBanForm(shell);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收起封面三点菜单面板(参考主题 author_header_drop_lists 菜单项点击后
|
||||
* 不自动收起,需手动关闭;评论区与文章作者下拉由各自脚本点击项后自动收起)。
|
||||
*/
|
||||
function closeCoverMenu(trigger) {
|
||||
var menu = trigger && trigger.closest ? trigger.closest('[data-aksrt-menu]') : null;
|
||||
if (!menu) return;
|
||||
var panel = menu.querySelector('.aksrt-cover-menu__panel');
|
||||
if (panel) panel.hidden = true;
|
||||
var menuTrigger = menu.querySelector('.aksrt-cover-menu__trigger');
|
||||
if (menuTrigger) menuTrigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
function initBanBindings() {
|
||||
if (document.documentElement.dataset.aksrtBanInitialized === '1') return;
|
||||
document.documentElement.dataset.aksrtBanInitialized = '1';
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
if (!event.target || !event.target.closest) return;
|
||||
|
||||
// 禁封 / 解封入口(封面菜单按钮 / 评论区菜单项 / 文章作者菜单项)。
|
||||
var banBtn = event.target.closest('[data-aksrt-ban]');
|
||||
if (banBtn) {
|
||||
event.preventDefault();
|
||||
var userId = parseInt(banBtn.getAttribute('data-aksrt-ban'), 10);
|
||||
if (userId > 0) {
|
||||
closeCoverMenu(banBtn);
|
||||
openBanModal(userId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 弹窗内交互(事件委托到 document,弹窗壳由 runtime portal 到 body)。
|
||||
var active = window.AksrtModalRuntime && window.AksrtModalRuntime.getActive
|
||||
? window.AksrtModalRuntime.getActive()
|
||||
: null;
|
||||
if (!active || !active.contains(event.target)) return;
|
||||
|
||||
var shell = active;
|
||||
var form = shell.querySelector('[data-aksrt-ban-form]');
|
||||
var modalUser = form ? parseInt(form.getAttribute('data-aksrt-ban-user'), 10) : 0;
|
||||
|
||||
var submitBtn = event.target.closest('[data-aksrt-ban-submit]');
|
||||
if (submitBtn) {
|
||||
event.preventDefault();
|
||||
submitBtn.disabled = true;
|
||||
submitBan(shell, modalUser, false);
|
||||
return;
|
||||
}
|
||||
|
||||
var unbanBtn = event.target.closest('[data-aksrt-unban-submit]');
|
||||
if (unbanBtn) {
|
||||
event.preventDefault();
|
||||
unbanBtn.disabled = true;
|
||||
submitBan(shell, modalUser, true);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('change', function (event) {
|
||||
var active = window.AksrtModalRuntime && window.AksrtModalRuntime.getActive
|
||||
? window.AksrtModalRuntime.getActive()
|
||||
: null;
|
||||
if (!active || !event.target || !active.contains(event.target)) return;
|
||||
syncBanForm(active);
|
||||
});
|
||||
|
||||
// 弹窗内容注入后由 onContent 绑定;bs.modal 展示契约下对 user-ban 弹窗做同步。
|
||||
jQuery(document).on('loaded.bs.modal show.bs.modal', function (event) {
|
||||
var shell = event.target && event.target.classList && event.target.classList.contains('aksrt-user-ban-modal')
|
||||
? event.target
|
||||
: null;
|
||||
if (shell) syncBanForm(shell);
|
||||
});
|
||||
}
|
||||
|
||||
jQuery(document)
|
||||
.off('auto_fun.aksrt-ban')
|
||||
.on('auto_fun.aksrt-ban', initBanBindings);
|
||||
})();
|
||||
@@ -51,6 +51,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-pay.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-coupon-order.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-credits.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-null.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/user-ban.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-ajax-list.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-links.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/search/aksrt-search-posts.php';
|
||||
|
||||
@@ -132,18 +132,24 @@ if (!function_exists('aksrt_get_single_meta_box')) :
|
||||
$more .= '<button type="button" class="but cir post-drop-meta__toggle" aria-haspopup="true" aria-controls="' . esc_attr($menu_id) . '" aria-expanded="false" aria-label="' . esc_attr__('更多', 'aksrt') . '">';
|
||||
$more .= '<i class="fa fas fa-ellipsis-h" aria-hidden="true"></i></button>';
|
||||
$more .= '<div id="' . esc_attr($menu_id) . '" class="post-drop-meta__menu" aria-hidden="true">';
|
||||
// 首项(参考主题 zib_get_post_more_dropdown 顺序):管理员 → 禁封用户;
|
||||
// 无禁封权限时不占位,由下方举报项兜底(「禁封优先、否则举报」互斥语义)。
|
||||
$ban_item = function_exists('aksrt_get_post_author_ban_item')
|
||||
? aksrt_get_post_author_ban_item((int) get_post_field('post_author', $post_id), (string) $permalink)
|
||||
: '';
|
||||
$more .= $ban_item; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper escapes internally.
|
||||
if ($edit_link) {
|
||||
$more .= '<a href="' . esc_url($edit_link) . '" class="post-drop-meta__item"><i class="fa fas fa-edit" aria-hidden="true"></i><span>' . esc_html__('编辑文章', 'aksrt') . '</span></a>';
|
||||
}
|
||||
$more .= '<button type="button" class="post-drop-meta__item" data-share-copy data-share-url="' . esc_url((string) $permalink) . '"><i class="fa fas fa-link" aria-hidden="true"></i><span>' . esc_html__('复制链接', 'aksrt') . '</span></button>';
|
||||
if (is_user_logged_in()) {
|
||||
if ($ban_item === '' && is_user_logged_in()) {
|
||||
$report_payload = wp_json_encode([
|
||||
'target_type' => 'post',
|
||||
'target_id' => (int) $post_id,
|
||||
'target_name' => get_the_title($post_id),
|
||||
]);
|
||||
$more .= '<button type="button" class="post-drop-meta__item" data-aksrt-report="' . esc_attr((string) $report_payload) . '"><i class="fa fas fa-flag" aria-hidden="true"></i><span>' . esc_html__('举报', 'aksrt') . '</span></button>';
|
||||
} else {
|
||||
} elseif ($ban_item === '') {
|
||||
$more .= '<a href="' . esc_url($login_url) . '" class="post-drop-meta__item"><i class="fa fas fa-flag" aria-hidden="true"></i><span>' . esc_html__('登录后举报', 'aksrt') . '</span></a>';
|
||||
}
|
||||
$more .= '</div></div>';
|
||||
|
||||
@@ -217,7 +217,9 @@ if (!function_exists('aksrt_get_comments_action_dropdown_html')) :
|
||||
if (!empty($perms['can_delete'])) {
|
||||
$lists .= '<li><a class="comment-trash-link c-red" data-comment-action="delete" data-commentid="' . (int) $comment->comment_ID . '" data-postid="' . (int) $comment->comment_post_ID . '" href="javascript:;"><i class="fa far fa-trash-alt mr10 fa-fw" aria-hidden="true"></i>' . esc_html__('删除', 'aksrt') . '</a></li>';
|
||||
}
|
||||
$lists .= aksrt_get_comment_report_html($comment);
|
||||
// 末项(参考主题 comments_action_lists 钩子顺序):管理员 → 禁封用户;
|
||||
// 否则 → 举报(登录且非本人且举报开启时,既有评论举报契约)。
|
||||
$lists .= aksrt_get_comment_ban_or_report_item($comment);
|
||||
|
||||
if ($lists === '') {
|
||||
return '';
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
/**
|
||||
* 前台用户禁封(小黑屋 / 封号)— 1:1 复刻参考主题 Zibll 前台禁封能力:
|
||||
* zib_get_edit_user_ban_link → aksrt_get_edit_user_ban_link(三点菜单入口)
|
||||
* zib_get_set_user_ban_modal → aksrt_get_user_ban_modal(禁封 / 解封弹窗)
|
||||
*
|
||||
* 数据源唯一:\AKSRT\User\Restriction(wp_aksrt_user_restriction 表)。
|
||||
* 弹窗内容经 REST /user-ban/modal 输出(AksrtModalRuntime.openRemote 拉取,
|
||||
* 与参考主题 refresh_modal 每次刷新同语义),提交走 REST /user-ban/set。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* 禁封功能开关(后台“用户&互动 → 禁封/举报 → 禁封功能”,
|
||||
* 对齐参考主题 _pz('user_ban_s', true):缺键默认开启)。
|
||||
*/
|
||||
function aksrt_user_ban_enabled(): bool
|
||||
{
|
||||
$settings = class_exists('\AKSRT\Admin\SchemaProvider')
|
||||
? \AKSRT\Settings\Settings::read_domain('users', 'report')
|
||||
: (class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : []);
|
||||
$settings = is_array($settings) ? $settings : [];
|
||||
return !isset($settings['user_ban_s']) || !empty($settings['user_ban_s']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前用户能否禁封目标用户(对齐参考主题 zib_current_user_can('set_user_ban') 语义)。
|
||||
*/
|
||||
function aksrt_current_user_can_ban(int $user_id): bool
|
||||
{
|
||||
if ($user_id <= 0 || !aksrt_user_ban_enabled()) {
|
||||
return false;
|
||||
}
|
||||
$current_id = get_current_user_id();
|
||||
if ($current_id <= 0 || $current_id === $user_id) {
|
||||
return false;
|
||||
}
|
||||
if (!current_user_can('manage_options')) {
|
||||
return false;
|
||||
}
|
||||
if (function_exists('is_super_admin') && is_super_admin($user_id)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取用户当前生效中的禁封记录(ban 优先于 suspend,对齐后台单一状态语义)。
|
||||
*/
|
||||
function aksrt_get_user_current_ban(int $user_id): ?array
|
||||
{
|
||||
if ($user_id <= 0 || !class_exists('\AKSRT\User\Restriction')) {
|
||||
return null;
|
||||
}
|
||||
$svc = \AKSRT\User\Restriction::get_instance();
|
||||
return $svc->get_active_ban($user_id) ?: $svc->get_active_suspend($user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁封 / 解封触发器(参考主题 zib_get_edit_user_ban_link)。
|
||||
* 输出 data-aksrt-ban 委托入口,由 assets/js/user-ban.js 打开禁封弹窗。
|
||||
*/
|
||||
function aksrt_get_edit_user_ban_link(int $user_id = 0, string $class = '', string $con = ''): string
|
||||
{
|
||||
if (!aksrt_current_user_can_ban($user_id)) {
|
||||
return '';
|
||||
}
|
||||
if ($con === '') {
|
||||
$con = aksrt_get_user_current_ban($user_id)
|
||||
? '<i class="fa fas fa-ban mr6" aria-hidden="true"></i>' . __('解封此用户', 'aksrt')
|
||||
: '<i class="fa fas fa-ban mr6" aria-hidden="true"></i>' . __('禁封此用户', 'aksrt');
|
||||
}
|
||||
return '<a href="javascript:;" class="' . esc_attr($class) . '" data-aksrt-ban="' . esc_attr((string) $user_id) . '">' . $con . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户主页封面三点菜单项(参考主题 author_header_drop_lists 钩子语义):
|
||||
* 有禁封权限 → 禁封入口;否则(开启举报且非本人)→ 举报入口。
|
||||
*/
|
||||
function aksrt_get_user_cover_menu_action_html(int $user_id): string
|
||||
{
|
||||
if (aksrt_current_user_can_ban($user_id)) {
|
||||
$ban = aksrt_get_user_current_ban($user_id);
|
||||
$label = $ban ? __('解封此用户', 'aksrt') : __('禁封此用户', 'aksrt');
|
||||
return '<button type="button" role="menuitem" data-aksrt-ban="' . esc_attr((string) $user_id) . '"><i class="fa fas fa-ban" aria-hidden="true"></i>' . esc_html($label) . '</button>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 评论三点菜单项(参考主题 comments_action_lists 钩子语义):
|
||||
* 管理员 → 禁封用户(固定文案,与参考主题 zib_ban_comments_action_lists_filter
|
||||
* 一致——解封态由弹窗内容呈现);否则 → 举报( Aksrt 既有评论举报契约)。
|
||||
*/
|
||||
function aksrt_get_comment_ban_or_report_item(\WP_Comment $comment): string
|
||||
{
|
||||
$user_id = (int) $comment->user_id;
|
||||
if ($user_id > 0 && aksrt_current_user_can_ban($user_id)) {
|
||||
return '<li><a class="comment-ban-link" href="javascript:;" data-aksrt-ban="' . esc_attr((string) $user_id) . '"><i class="fa fas fa-ban mr6 fa-fw c-red" aria-hidden="true"></i>' . esc_html__('禁封用户', 'aksrt') . '</a></li>';
|
||||
}
|
||||
if (function_exists('aksrt_get_comment_report_html')) {
|
||||
return aksrt_get_comment_report_html($comment);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章作者资料卡三点菜单项(参考主题 zib_get_post_more_dropdown 语义,菜单首项):
|
||||
* 管理员 → 禁封用户(固定文案,解封态由弹窗内容呈现);否则 → 空(由既有
|
||||
* 文章举报项兜底,与参考主题「禁封优先、否则举报」互斥语义一致)。
|
||||
*/
|
||||
function aksrt_get_post_author_ban_item(int $author_id, string $post_url = ''): string
|
||||
{
|
||||
if ($author_id <= 0 || get_current_user_id() === $author_id) {
|
||||
return '';
|
||||
}
|
||||
if (aksrt_current_user_can_ban($author_id)) {
|
||||
return '<a href="javascript:;" class="post-drop-meta__item" data-aksrt-ban="' . esc_attr((string) $author_id) . '"><i class="fa fas fa-ban" aria-hidden="true"></i><span>' . esc_html__('禁封用户', 'aksrt') . '</span></a>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗关闭图标(与 report.js 同款 SVG 雪碧图契约)。
|
||||
*/
|
||||
function aksrt_user_ban_close_svg(): string
|
||||
{
|
||||
return '<svg class="ic-close" aria-hidden="true"><use xlink:href="#icon-close"></use></svg>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁封 / 解封弹窗内容 — 1:1 复刻参考主题 zib_get_set_user_ban_modal:
|
||||
* 未禁封 → 禁封表单(禁封方式 / 禁封时间 / 预置原因徽章 / 其它原因 /
|
||||
* 禁封说明 / 删除全部内容 / 禁止用户申诉(含说明));
|
||||
* 已禁封 → 解封视图(状态信息 + 解封留言表单)。
|
||||
*
|
||||
* @return string 弹窗 HTML(空串 = 无权操作或用户不存在)
|
||||
*/
|
||||
function aksrt_get_user_ban_modal(int $user_id): string
|
||||
{
|
||||
$user = $user_id > 0 ? get_userdata($user_id) : null;
|
||||
if (!$user || !aksrt_current_user_can_ban($user_id)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ban = aksrt_get_user_current_ban($user_id);
|
||||
|
||||
// 用户行(头像 + 昵称),与参考主题弹窗头部同构。
|
||||
$avatar_url = get_avatar_url($user_id, ['size' => 40]);
|
||||
$user_name = function_exists('aksrt_get_user_name')
|
||||
? aksrt_get_user_name(['id' => $user_id, 'vip' => false, 'medal' => false])
|
||||
: esc_html($user->display_name);
|
||||
$user_row = '<div class="flex ac mb6">'
|
||||
. '<img class="avatar-mini mr6" src="' . esc_url((string) $avatar_url) . '" alt="">'
|
||||
. '<b>' . $user_name . '</b></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- aksrt_get_user_name escapes internally.
|
||||
|
||||
$html = '<div class="aksrt-user-ban-modal">';
|
||||
|
||||
if ($ban) {
|
||||
// ── 已禁封视图:解封(参考主题解封弹窗同构) ──
|
||||
$is_ban = (string) ($ban['type'] ?? '') === 'ban';
|
||||
$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))。
|
||||
$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])
|
||||
: '<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.
|
||||
};
|
||||
|
||||
// 弹窗头(参考主题 zib_get_modal_colorful_header('c-yellow', ban 图标, 标题):
|
||||
// 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 .= '<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 .= $set_row(__('用户', 'aksrt'), $user_badge); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$html .= $set_row(__('状态', 'aksrt'), esc_html($status_text));
|
||||
$html .= $set_row(__('开始时间', 'aksrt'), esc_html((string) ($ban['created_at'] ?? '')));
|
||||
$html .= $set_row(__('结束时间', 'aksrt'), esc_html($end_time));
|
||||
$html .= $set_row(__('原因', 'aksrt'), nl2br(esc_html($reason)));
|
||||
$html .= $set_row(__('说明', 'aksrt'), nl2br(esc_html($ban_desc)));
|
||||
$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="mt20 but-average modal-buts">';
|
||||
$html .= '<button type="button" class="but" data-aksrt-modal-close>' . esc_html__('取消', 'aksrt') . '</button>';
|
||||
$html .= '<button type="button" class="but c-yellow" data-aksrt-unban-submit>' . esc_html(sprintf(__('确认%s', 'aksrt'), $unban_title)) . '</button>';
|
||||
$html .= '</div></form></div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
// ── 未禁封视图:禁封表单(参考主题禁封弹窗同构) ──
|
||||
$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>';
|
||||
|
||||
// 历史禁封次数提示(参考主题:>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_view = $log_count > 1
|
||||
? '<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>'
|
||||
: '';
|
||||
|
||||
$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 .= '<div class="scroll-y mini-scrollbar max-vh7">';
|
||||
$html .= '<div class="mt10 mb20">' . $user_row . $log_view . '</div>';
|
||||
|
||||
// 禁封方式(1=拉入小黑屋 suspend / 2=禁封用户 ban)。
|
||||
$html .= '<div class="mb6 muted-2-color">' . esc_html__('禁封方式', 'aksrt') . '</div>';
|
||||
$html .= '<div class="form-radio form-but-radio">';
|
||||
$html .= '<label><input type="radio" name="ban" value="1" checked="checked"><span class="p2-10 mr6 but but-radio">' . esc_html__('拉入小黑屋', 'aksrt') . '</span></label>';
|
||||
$html .= '<label><input type="radio" name="ban" value="2"><span class="p2-10 mr6 but but-radio">' . esc_html__('禁封用户', 'aksrt') . '</span></label>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="em09 c-red opacity8 mb10">';
|
||||
$html .= '<div data-aksrt-ban-desc="1">' . esc_html__('将用户拉入小黑屋后,用户将失去所有的发布权限及大部分的操作权限,例如发帖、评论等', 'aksrt') . '</div>';
|
||||
$html .= '<div data-aksrt-ban-desc="2" style="display:none;">' . esc_html__('禁封的用户将不能登录', 'aksrt') . '</div>';
|
||||
$html .= '</div>';
|
||||
|
||||
// 禁封时间(1/7/14/30/100/永久,默认 7 天,对齐参考主题 checked(7, ...))。
|
||||
$html .= '<div class="muted-2-color mb6">' . esc_html__('禁封时间', 'aksrt') . '</div>';
|
||||
$html .= '<div class="form-radio mb10 form-but-radio">';
|
||||
foreach ([1 => __('1天', 'aksrt'), 7 => __('7天', 'aksrt'), 14 => __('14天', 'aksrt'), 30 => __('30天', 'aksrt'), 100 => __('100天', 'aksrt'), 0 => __('永久', 'aksrt')] as $days => $label) {
|
||||
$checked = 7 === $days ? ' checked="checked"' : '';
|
||||
$html .= '<label><input type="radio" name="time_day" value="' . esc_attr((string) $days) . '"' . $checked . '><span class="p2-10 mr6 but but-radio">' . esc_html($label) . '</span></label>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
|
||||
// 选择禁封原因(预置徽章 + 其它原因)。
|
||||
$html .= '<div class="muted-2-color mb6">' . esc_html__('选择禁封原因', 'aksrt') . '</div>';
|
||||
$html .= '<div class="form-radio mb10 form-but-radio">';
|
||||
$reasons = class_exists('\AKSRT\Settings\UserRuntime') ? (\AKSRT\Settings\UserRuntime::ban_settings()['reasons'] ?? []) : [];
|
||||
foreach ($reasons as $reason) {
|
||||
$html .= '<label><input type="radio" name="reason" value="' . esc_attr((string) $reason['name']) . '"><span class="p2-10 mr6 but but-radio">' . esc_html((string) $reason['name']) . '</span></label>';
|
||||
}
|
||||
$html .= '<label><input type="radio" name="reason" value="other"><span class="p2-10 mr6 but but-radio">' . esc_html__('其它原因', 'aksrt') . '</span></label>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="mb20" data-aksrt-ban-reason-other="1" style="display:none;"><input type="text" class="form-control" name="reason_other" tabindex="1" placeholder="' . esc_attr__('请填写禁封原因', 'aksrt') . '"></div>';
|
||||
|
||||
// 禁封说明。
|
||||
$html .= '<div class="muted-2-color mb6">' . esc_html__('禁封说明', 'aksrt') . '</div>';
|
||||
$html .= '<textarea class="form-control mb20" name="desc" tabindex="2" placeholder="' . esc_attr__('请输入禁封说明或给用户的留言', 'aksrt') . '" rows="2" autoHeight="true" maxHeight="110"></textarea>';
|
||||
|
||||
// 删除全部内容。
|
||||
$html .= '<div class="mb20"><label class="flex jsb ac" style="font-weight:normal;"><input class="hide" name="delete_content" type="checkbox"><div class="flex1 mr20"><div class="muted-2-color mb3">' . esc_html__('删除全部内容', 'aksrt') . '</div><div class="muted-3-color px12">' . esc_html__('将该用户发布的全部文章、帖子、评论移至回收站', 'aksrt') . '</div></div><div class="form-switch flex0"></div></label></div>';
|
||||
|
||||
// 禁止用户申诉(含说明联动)。
|
||||
$html .= '<div class="mb20">';
|
||||
$html .= '<div class="mb6"><label class="flex jsb ac" style="font-weight:normal;"><input class="hide" name="no_appeal" type="checkbox"><div class="flex1 mr20"><div class="muted-2-color mb3">' . esc_html__('禁止用户申诉', 'aksrt') . '</div><div class="muted-3-color px12">' . esc_html__('开启后用户将无法进行申诉', 'aksrt') . '</div></div><div class="form-switch flex0"></div></label></div>';
|
||||
$html .= '<div data-aksrt-ban-noappeal-desc style="display:none;"><textarea class="form-control" name="no_appeal_desc" tabindex="1" placeholder="' . esc_attr__('请填写禁止申诉说明', 'aksrt') . '" rows="2"></textarea></div>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<div class="mt20 but-average modal-buts">';
|
||||
$html .= '<button type="button" class="but" data-aksrt-modal-close>' . esc_html__('取消', 'aksrt') . '</button>';
|
||||
$html .= '<button type="button" class="but c-red" data-aksrt-ban-submit><i class="fa fas fa-ban" aria-hidden="true"></i>' . esc_html__('确认禁封', 'aksrt') . '</button>';
|
||||
$html .= '</div>';
|
||||
$html .= '</form></div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
@@ -36,8 +36,6 @@ class UsersMenus {
|
||||
add_action('admin_post_aksrt_users_review_verify', [$this, 'handle_review_verify']);
|
||||
add_action('admin_post_aksrt_users_delete_verify', [$this, 'handle_delete_verify']);
|
||||
add_action('admin_post_aksrt_users_update_report', [$this, 'handle_update_report']);
|
||||
add_action('admin_post_aksrt_users_add_restriction', [$this, 'handle_add_restriction']);
|
||||
add_action('admin_post_aksrt_users_remove_restriction', [$this, 'handle_remove_restriction']);
|
||||
add_action('admin_post_aksrt_users_process_appeal', [$this, 'handle_process_appeal']);
|
||||
add_action('admin_post_aksrt_users_mutate_vip', [$this, 'handle_mutate_vip']);
|
||||
}
|
||||
@@ -159,9 +157,6 @@ class UsersMenus {
|
||||
'verify_failed' => __('认证审核失败。', 'aksrt'),
|
||||
'report_updated' => __('举报状态已更新。', 'aksrt'),
|
||||
'report_failed' => __('举报更新失败。', 'aksrt'),
|
||||
'restriction_added' => __('限制已添加。', 'aksrt'),
|
||||
'restriction_removed' => __('限制已移除。', 'aksrt'),
|
||||
'restriction_failed' => __('限制操作失败。', 'aksrt'),
|
||||
'appeal_approved' => __('申诉已通过,限制已解除。', 'aksrt'),
|
||||
'appeal_rejected' => __('申诉已驳回。', 'aksrt'),
|
||||
'appeal_failed' => __('申诉处理失败。', 'aksrt'),
|
||||
@@ -287,247 +282,13 @@ class UsersMenus {
|
||||
echo '<div class="wrap"><h2>' . esc_html__('用户禁封申诉及用户举报管理', 'aksrt') . '</h2>';
|
||||
$this->flash();
|
||||
|
||||
$tab = isset($_GET['tab']) ? sanitize_key((string) wp_unslash($_GET['tab'])) : 'tickets';
|
||||
if (!in_array($tab, ['tickets', 'restrictions'], true)) {
|
||||
$tab = 'tickets';
|
||||
}
|
||||
|
||||
echo '<h2 class="nav-tab-wrapper">';
|
||||
printf('<a class="nav-tab%s" href="%s">%s</a>', $tab === 'tickets' ? ' nav-tab-active' : '', esc_url(admin_url('users.php?page=aksrt_user_ban')), esc_html__('申诉&举报', 'aksrt'));
|
||||
printf('<a class="nav-tab%s" href="%s">%s</a>', $tab === 'restrictions' ? ' nav-tab-active' : '', esc_url(admin_url('users.php?page=aksrt_user_ban&tab=restrictions')), esc_html__('禁封限制', 'aksrt'));
|
||||
echo '</h2>';
|
||||
|
||||
if ($tab === 'restrictions') {
|
||||
$this->render_restrictions_tab();
|
||||
} else {
|
||||
$this->render_tickets_tab();
|
||||
}
|
||||
// 与参考主题一致:后台仅承载「申诉&举报」工单处理;禁封/解封操作
|
||||
// 全部在前台完成(用户主页封面 / 评论区 / 文章作者栏三点菜单 →
|
||||
// 封禁弹窗,REST /user-ban/modal + /user-ban/set)。
|
||||
$this->render_tickets_tab();
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁封限制页 — 1:1 对齐参考主题 zib_get_set_user_ban_modal:
|
||||
* 禁封方式(拉入小黑屋/禁封用户)+ 禁封时间(1/7/14/30/100/永久)+
|
||||
* 选择禁封原因(预置徽章 + 其它原因)+ 禁封说明 + 删除全部内容 +
|
||||
* 禁止用户申诉(含说明);已禁封用户展示状态信息与解封留言表单。
|
||||
* 参考主题无 scope 维度,禁封一律全局。
|
||||
*/
|
||||
private function render_restrictions_tab(): void {
|
||||
if (!class_exists('\AKSRT\User\Restriction')) {
|
||||
echo '<div class="notice notice-error"><p>' . esc_html__('限制模块未初始化。', 'aksrt') . '</p></div>';
|
||||
return;
|
||||
}
|
||||
$svc = \AKSRT\User\Restriction::get_instance();
|
||||
|
||||
$page_url = admin_url('users.php?page=aksrt_user_ban&tab=restrictions');
|
||||
$target_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0;
|
||||
$unban = isset($_GET['action']) && sanitize_key((string) wp_unslash($_GET['action'])) === 'unban';
|
||||
$target = $target_id > 0 ? get_userdata($target_id) : null;
|
||||
|
||||
// 页面级样式(wp-admin 不加载前端 kit,就地声明参考主题同款外观)
|
||||
echo '<style>
|
||||
.aksrt-ban-modal .muted-2-color{color:#777;}
|
||||
.aksrt-ban-modal .author-set-left{min-width:80px;flex:0 0 auto;color:#777;}
|
||||
.aksrt-ban-modal .author-set-right{margin-top:0;}
|
||||
.aksrt-ban-modal .form-radio label{display:inline-block;margin:0 6px 6px 0;}
|
||||
.aksrt-ban-modal .form-radio input[type=radio]{display:none;}
|
||||
.aksrt-ban-modal .form-radio input[type=radio]+span{display:inline-block;padding:4px 10px;border:1px solid #dcdcde;border-radius:4px;background:#f6f7f7;cursor:pointer;}
|
||||
.aksrt-ban-modal .form-radio input[type=radio]:checked+span{background:#2271b1;border-color:#2271b1;color:#fff;}
|
||||
.aksrt-ban-modal .but-average.modal-buts{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:16px;max-width:480px;}
|
||||
.aksrt-ban-modal .but-average.modal-buts .but{min-height:40px;text-align:center;border-radius:10px;border:1px solid #dcdcde;background:#f6f7f7;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;}
|
||||
.aksrt-ban-modal .but-average.modal-buts .but.c-blue{background:#2271b1;border-color:#2271b1;color:#fff;}
|
||||
.aksrt-ban-modal .but-average.modal-buts .but.c-yellow{background:#dba617;border-color:#dba617;color:#fff;}
|
||||
.aksrt-ban-modal .switch-row{display:flex;align-items:center;margin:0 0 14px;font-weight:400;}
|
||||
.aksrt-ban-modal .switch-row .switch-title{color:#777;margin-bottom:3px;}
|
||||
.aksrt-ban-modal .switch-row .switch-help{color:#a7aaad;font-size:12px;}
|
||||
.aksrt-ban-modal textarea,.aksrt-ban-modal input[type=text]{width:100%;max-width:560px;}
|
||||
</style>';
|
||||
|
||||
// 当前生效中的全局禁封(ban 优先于 suspend,对齐参考主题单一状态语义)
|
||||
$current = null;
|
||||
if ($target_id > 0) {
|
||||
$restrictions = $svc->get_user_restrictions($target_id, true);
|
||||
foreach ($restrictions as $r) {
|
||||
if (($r['scope'] ?? '') !== 'global' || !in_array($r['type'], ['ban', 'suspend'], true)) {
|
||||
continue;
|
||||
}
|
||||
if ($current === null || ($r['type'] === 'ban' && $current['type'] !== 'ban')) {
|
||||
$current = $r;
|
||||
}
|
||||
}
|
||||
// 禁言解除视图:记录列表“解除禁言”进入(无更重的 ban/suspend 时生效)。
|
||||
if ($unban && $current === null) {
|
||||
foreach ($restrictions as $r) {
|
||||
if (($r['type'] ?? '') === 'mute') {
|
||||
$current = $r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo '<div class="table-box aksrt-ban-modal" style="max-width:760px;padding:12px 16px;">';
|
||||
|
||||
if (!$target) {
|
||||
echo '<p>' . esc_html__('请先通过“用户 ID”定位要禁封或解封的用户(也可从工单列表的“点击此处”进入)。', 'aksrt') . '</p>';
|
||||
} elseif ($current !== null) {
|
||||
// —— 已禁封视图(参考主题解封弹窗同构):状态信息 + 解封留言表单 ——
|
||||
$unban_titles = [
|
||||
'ban' => __('解封用户', 'aksrt'),
|
||||
'suspend' => __('移出小黑屋', 'aksrt'),
|
||||
'mute' => __('解除禁言', 'aksrt'),
|
||||
];
|
||||
$status_texts = [
|
||||
'ban' => __('禁封中(禁止登录)', 'aksrt'),
|
||||
'suspend' => __('小黑屋禁封中', 'aksrt'),
|
||||
'mute' => __('禁言中', 'aksrt'),
|
||||
];
|
||||
$cur_type = (string) ($current['type'] ?? 'ban');
|
||||
$unban_title = $unban_titles[$cur_type] ?? $unban_titles['ban'];
|
||||
$status_text = $status_texts[$cur_type] ?? $status_texts['ban'];
|
||||
$end_time = !empty($current['expires_at']) ? (string) $current['expires_at'] : __('永久', 'aksrt');
|
||||
echo '<h2 style="margin-top:0;">' . esc_html($unban_title) . '</h2>';
|
||||
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('用户', 'aksrt') . '</div><div class="author-set-right">' . esc_html($target->display_name) . '</div></div>';
|
||||
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('状态', 'aksrt') . '</div><div class="author-set-right">' . esc_html($status_text) . '</div></div>';
|
||||
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('开始时间', 'aksrt') . '</div><div class="author-set-right">' . esc_html((string) ($current['created_at'] ?? '')) . '</div></div>';
|
||||
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('结束时间', 'aksrt') . '</div><div class="author-set-right">' . esc_html($end_time) . '</div></div>';
|
||||
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('原因', 'aksrt') . '</div><div class="author-set-right">' . nl2br(esc_html((string) ($current['reason'] ?? ''))) . '</div></div>';
|
||||
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
|
||||
echo '<input type="hidden" name="action" value="aksrt_users_remove_restriction" />';
|
||||
echo '<input type="hidden" name="user_id" value="' . esc_attr((string) $target_id) . '" />';
|
||||
wp_nonce_field('aksrt_users_unban_' . $target_id);
|
||||
echo '<p><textarea class="large-text" name="desc" rows="2" placeholder="' . esc_attr__('给用户的留言', 'aksrt') . '"></textarea></p>';
|
||||
echo '<div class="but-average modal-buts">';
|
||||
echo '<a class="but" href="' . esc_url($page_url) . '">' . esc_html__('取消', 'aksrt') . '</a>';
|
||||
echo '<button type="submit" class="but c-yellow">' . esc_html(sprintf(__('确认%s', 'aksrt'), $unban_title)) . '</button>';
|
||||
echo '</div>';
|
||||
echo '</form>';
|
||||
} else {
|
||||
// —— 禁封表单(参考主题禁封弹窗同构) ——
|
||||
echo '<h2 style="margin-top:0;">' . esc_html__('禁封用户', 'aksrt') . '</h2>';
|
||||
echo '<form class="aksrt-ban-form" method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
|
||||
echo '<input type="hidden" name="action" value="aksrt_users_add_restriction" />';
|
||||
echo '<input type="hidden" name="user_id" value="' . esc_attr((string) $target_id) . '" />';
|
||||
wp_nonce_field('aksrt_users_add_restriction');
|
||||
|
||||
echo '<div class="mt10 mb20 flex ac mb6">' . get_avatar($target_id, 40, '', '', ['class' => 'avatar-mini mr6']) . '<b>' . esc_html($target->display_name) . '</b></div>';
|
||||
|
||||
// 禁封方式
|
||||
echo '<div class="mb6 muted-2-color">' . esc_html__('禁封方式', 'aksrt') . '</div>';
|
||||
echo '<div class="form-radio form-but-radio">';
|
||||
echo '<label><input type="radio" name="ban" value="1" checked="checked"><span class="p2-10 mr6">' . esc_html__('拉入小黑屋', 'aksrt') . '</span></label>';
|
||||
echo '<label><input type="radio" name="ban" value="2"><span class="p2-10 mr6">' . esc_html__('禁封用户', 'aksrt') . '</span></label>';
|
||||
echo '</div>';
|
||||
echo '<div class="em09 c-red opacity8 mb10">';
|
||||
echo '<div data-aksrt-ban-desc="1">' . esc_html__('将用户拉入小黑屋后,用户将失去所有的发布权限及大部分的操作权限,例如发帖、评论等', 'aksrt') . '</div>';
|
||||
echo '<div data-aksrt-ban-desc="2" style="display:none;">' . esc_html__('禁封的用户将不能登录', 'aksrt') . '</div>';
|
||||
echo '</div>';
|
||||
|
||||
// 禁封时间
|
||||
echo '<div class="mb6 muted-2-color">' . esc_html__('禁封时间', 'aksrt') . '</div>';
|
||||
echo '<div class="form-radio mb10 form-but-radio">';
|
||||
foreach ([1 => __('1天', 'aksrt'), 7 => __('7天', 'aksrt'), 14 => __('14天', 'aksrt'), 30 => __('30天', 'aksrt'), 100 => __('100天', 'aksrt'), 0 => __('永久', 'aksrt')] as $days => $label) {
|
||||
echo '<label><input type="radio" name="time_day" value="' . esc_attr((string) $days) . '"' . checked(7, $days, false) . '><span class="p2-10 mr6">' . esc_html($label) . '</span></label>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
// 选择禁封原因(预置原因 + 其它原因)
|
||||
echo '<div class="mb6 muted-2-color">' . esc_html__('选择禁封原因', 'aksrt') . '</div>';
|
||||
echo '<div class="form-radio mb10 form-but-radio">';
|
||||
foreach (\AKSRT\Settings\UserRuntime::ban_settings()['reasons'] ?? [] as $reason) {
|
||||
echo '<label><input type="radio" name="reason" value="' . esc_attr($reason['name']) . '"><span class="p2-10 mr6">' . esc_html($reason['name']) . '</span></label>';
|
||||
}
|
||||
echo '<label><input type="radio" name="reason" value="other"><span class="p2-10 mr6">' . esc_html__('其它原因', 'aksrt') . '</span></label>';
|
||||
echo '</div>';
|
||||
echo '<div class="aksrt-reason-other mb20" style="display:none;"><input type="text" class="regular-text" name="reason_other" placeholder="' . esc_attr__('请填写禁封原因', 'aksrt') . '"></div>';
|
||||
|
||||
// 禁封说明
|
||||
echo '<div class="mb6 muted-2-color">' . esc_html__('禁封说明', 'aksrt') . '</div>';
|
||||
echo '<p><textarea class="large-text" name="desc" rows="2" placeholder="' . esc_attr__('请输入禁封说明或给用户的留言', 'aksrt') . '"></textarea></p>';
|
||||
|
||||
// 删除全部内容
|
||||
echo '<div class="switch-row"><label><input type="checkbox" name="delete_content" value="1"> <span class="flex1 mr20"><span class="switch-title">' . esc_html__('删除全部内容', 'aksrt') . '</span><br><span class="switch-help">' . esc_html__('将该用户发布的全部文章、帖子、评论移至回收站', 'aksrt') . '</span></span></label></div>';
|
||||
|
||||
// 禁止用户申诉
|
||||
echo '<div class="switch-row"><label><input type="checkbox" name="no_appeal" value="1"> <span class="flex1 mr20"><span class="switch-title">' . esc_html__('禁止用户申诉', 'aksrt') . '</span><br><span class="switch-help">' . esc_html__('开启后用户将无法进行申诉', 'aksrt') . '</span></span></label></div>';
|
||||
echo '<div class="aksrt-no-appeal-desc mb20" style="display:none;"><textarea class="large-text" name="no_appeal_desc" rows="2" placeholder="' . esc_attr__('请填写禁止申诉说明', 'aksrt') . '"></textarea></div>';
|
||||
|
||||
echo '<div class="but-average modal-buts">';
|
||||
echo '<a class="but" href="' . esc_url($page_url) . '">' . esc_html__('取消', 'aksrt') . '</a>';
|
||||
echo '<button type="submit" class="but c-blue">' . esc_html__('确认提交', 'aksrt') . '</button>';
|
||||
echo '</div>';
|
||||
echo '</form>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
// —— 限制记录列表(参考主题列语义:状态/开始/结束/原因) ——
|
||||
global $wpdb;
|
||||
$table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('user_restriction') : '';
|
||||
if ($table === '') {
|
||||
echo '<div class="notice notice-warning"><p>' . esc_html__('限制表不可用。', 'aksrt') . '</p></div>';
|
||||
return;
|
||||
}
|
||||
$items = $wpdb->get_results("SELECT * FROM {$table} ORDER BY id DESC LIMIT 100", ARRAY_A);
|
||||
$now = gmdate('Y-m-d H:i:s');
|
||||
echo '<h2>' . esc_html__('限制记录', 'aksrt') . '</h2>';
|
||||
echo '<table class="wp-list-table widefat fixed striped"><thead><tr>';
|
||||
echo '<th>' . esc_html__('用户', 'aksrt') . '</th><th>' . esc_html__('禁封状态', 'aksrt') . '</th><th>' . esc_html__('开始时间', 'aksrt') . '</th><th>' . esc_html__('结束时间', 'aksrt') . '</th><th>' . esc_html__('原因', 'aksrt') . '</th><th>' . esc_html__('操作', 'aksrt') . '</th>';
|
||||
echo '</tr></thead><tbody>';
|
||||
if (!$items) {
|
||||
echo '<tr><td colspan="6">' . esc_html__('暂无限制记录', 'aksrt') . '</td></tr>';
|
||||
}
|
||||
foreach ((array) $items as $r) {
|
||||
$type = (string) ($r['type'] ?? '');
|
||||
$expired = !empty($r['expires_at']) && (string) $r['expires_at'] <= $now;
|
||||
$is_global = ($r['scope'] ?? '') === 'global' && in_array($type, ['ban', 'suspend'], true);
|
||||
if ($expired) {
|
||||
$status_html = '<span style="color:#a7aaad;">' . esc_html__('已过期', 'aksrt') . '</span>';
|
||||
} elseif ($type === 'ban') {
|
||||
$status_html = '<span style="color:#fb4444;">' . esc_html__('禁封中(禁止登录)', 'aksrt') . '</span>';
|
||||
} elseif ($type === 'suspend') {
|
||||
$status_html = '<span style="color:#fd6a11;">' . esc_html__('小黑屋禁封中', 'aksrt') . '</span>';
|
||||
} elseif ($type === 'mute') {
|
||||
$status_html = '<span style="color:#fd6a11;">' . esc_html__('禁言中', 'aksrt') . '</span>';
|
||||
} else {
|
||||
$status_html = '<span>' . esc_html__('警告', 'aksrt') . '</span>';
|
||||
}
|
||||
$r_user_id = (int) ($r['user_id'] ?? 0);
|
||||
$r_user = $r_user_id > 0 ? get_userdata($r_user_id) : null;
|
||||
echo '<tr>';
|
||||
echo '<td>' . ($r_user ? esc_html($r_user->display_name) : esc_html((string) $r_user_id)) . '</td>';
|
||||
echo '<td>' . $status_html . '</td>';
|
||||
echo '<td>' . esc_html((string) ($r['created_at'] ?? '')) . '</td>';
|
||||
echo '<td>' . esc_html(!empty($r['expires_at']) ? (string) $r['expires_at'] : __('永久', 'aksrt')) . '</td>';
|
||||
echo '<td>' . nl2br(esc_html((string) ($r['reason'] ?? ''))) . '</td>';
|
||||
echo '<td>';
|
||||
if (!$expired && $is_global) {
|
||||
echo '<a class="button" href="' . esc_url(add_query_arg(['action' => 'unban', 'user_id' => $r_user_id], $page_url)) . '">' . esc_html(($type === 'ban' ? __('解封', 'aksrt') : __('移出小黑屋', 'aksrt'))) . '</a>';
|
||||
} elseif (!$expired && $type === 'mute') {
|
||||
echo '<a class="button" href="' . esc_url(add_query_arg(['action' => 'unban', 'user_id' => $r_user_id], $page_url)) . '">' . esc_html__('解除禁言', 'aksrt') . '</a>';
|
||||
}
|
||||
echo '</td></tr>';
|
||||
}
|
||||
echo '</tbody></table>';
|
||||
|
||||
// 依赖联动(参考主题 dependency-box 同语义):禁封方式说明/其它原因/禁止申诉说明
|
||||
echo '<script>
|
||||
(function ($) {
|
||||
function syncBanForm() {
|
||||
$(".aksrt-ban-modal").each(function () {
|
||||
var $box = $(this);
|
||||
var ban = $box.find("input[name=\\"ban\\"]:checked").val();
|
||||
$box.find("[data-aksrt-ban-desc]").each(function () {
|
||||
$(this).toggle($(this).attr("data-aksrt-ban-desc") === ban);
|
||||
});
|
||||
$box.find(".aksrt-reason-other").toggle($box.find("input[name=\\"reason\\"]:checked").val() === "other");
|
||||
$box.find(".aksrt-no-appeal-desc").toggle($box.find("input[name=\\"no_appeal\\"]").is(":checked"));
|
||||
});
|
||||
}
|
||||
$(document).on("change", ".aksrt-ban-modal input", syncBanForm);
|
||||
$(document).ready(syncBanForm);
|
||||
})(jQuery);
|
||||
</script>';
|
||||
}
|
||||
|
||||
public function handle_update_report(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
@@ -541,103 +302,6 @@ class UsersMenus {
|
||||
$this->redirect('aksrt_user_ban', $ok ? 'report_updated' : 'report_failed', ['tab' => 'tickets']);
|
||||
}
|
||||
|
||||
public function handle_add_restriction(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('权限不足', 'aksrt'));
|
||||
}
|
||||
check_admin_referer('aksrt_users_add_restriction');
|
||||
|
||||
$user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0;
|
||||
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||
$this->redirect('aksrt_user_ban', 'restriction_failed', ['tab' => 'restrictions']);
|
||||
}
|
||||
|
||||
// 禁封方式(参考主题语义):1=拉入小黑屋(suspend) / 2=禁封用户(ban,禁止登录)
|
||||
$ban = isset($_POST['ban']) ? (int) $_POST['ban'] : 1;
|
||||
$type = $ban === 2 ? 'ban' : 'suspend';
|
||||
$time_day = isset($_POST['time_day']) ? (int) $_POST['time_day'] : 0;
|
||||
$desc = isset($_POST['desc']) ? sanitize_textarea_field((string) wp_unslash($_POST['desc'])) : '';
|
||||
|
||||
// 原因:预置徽章值或“其它原因”自定义文本
|
||||
$reason = isset($_POST['reason']) ? sanitize_text_field((string) wp_unslash($_POST['reason'])) : '';
|
||||
if ($reason === '' || $reason === 'other') {
|
||||
$reason = isset($_POST['reason_other']) ? sanitize_text_field((string) wp_unslash($_POST['reason_other'])) : '';
|
||||
}
|
||||
if ($reason === '') {
|
||||
$this->redirect('aksrt_user_ban', 'restriction_failed', ['tab' => 'restrictions', 'user_id' => $user_id]);
|
||||
}
|
||||
if ($desc !== '') {
|
||||
$reason .= "\n" . sprintf(__('禁封说明:%s', 'aksrt'), $desc);
|
||||
}
|
||||
|
||||
$no_appeal = !empty($_POST['no_appeal']);
|
||||
$no_appeal_desc = isset($_POST['no_appeal_desc']) ? sanitize_textarea_field((string) wp_unslash($_POST['no_appeal_desc'])) : '';
|
||||
if ($no_appeal && $no_appeal_desc === '') {
|
||||
// 参考主题同校验:勾选禁止申诉必须填写说明
|
||||
$this->redirect('aksrt_user_ban', 'restriction_failed', ['tab' => 'restrictions', 'user_id' => $user_id]);
|
||||
}
|
||||
|
||||
$expires = $time_day > 0 ? date('Y-m-d H:i:s', strtotime('+' . $time_day . ' day', current_time('timestamp'))) : '';
|
||||
$result = \AKSRT\User\Restriction::get_instance()->restrict($user_id, $type, $reason, 'global', $expires, get_current_user_id());
|
||||
$ok = !empty($result['success']);
|
||||
if ($ok) {
|
||||
// 禁封状态变更站内信(参考主题 zib_updata_user_ban_msg 同语义)
|
||||
$msg = $type === 'ban' ? __('您的账号已被禁封,将无法登录。', 'aksrt') : __('您已被拉入小黑屋,发布等相关权限已被限制。', 'aksrt');
|
||||
if ($time_day > 0) {
|
||||
$msg .= ' ' . sprintf(__('期限:%1$d 天,%2$s 起自动解封。', 'aksrt'), $time_day, $expires);
|
||||
}
|
||||
\AKSRT\Messages\Message::get_instance()->send($user_id, 'system', $msg, ['title' => __('账号限制', 'aksrt')]);
|
||||
|
||||
if ($no_appeal) {
|
||||
update_user_meta($user_id, '_aksrt_no_appeal', [
|
||||
'desc' => $no_appeal_desc,
|
||||
'at' => current_time('mysql'),
|
||||
]);
|
||||
}
|
||||
|
||||
// 删除全部内容:文章/帖子/评论移至回收站(参考主题同行为)
|
||||
if (!empty($_POST['delete_content'])) {
|
||||
$post_ids = get_posts([
|
||||
'author' => $user_id,
|
||||
'post_type' => get_post_types(['public' => true]),
|
||||
'post_status' => 'publish',
|
||||
'numberposts' => -1,
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
foreach ($post_ids as $pid) {
|
||||
wp_trash_post((int) $pid);
|
||||
}
|
||||
foreach (get_comments(['user_id' => $user_id, 'status' => 'approve', 'number' => 500]) as $comment) {
|
||||
wp_trash_comment((int) $comment->comment_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->redirect('aksrt_user_ban', $ok ? 'restriction_added' : 'restriction_failed', ['tab' => 'restrictions', 'user_id' => $user_id]);
|
||||
}
|
||||
|
||||
public function handle_remove_restriction(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('权限不足', 'aksrt'));
|
||||
}
|
||||
$user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0;
|
||||
check_admin_referer('aksrt_users_unban_' . $user_id);
|
||||
$desc = isset($_POST['desc']) ? sanitize_textarea_field((string) wp_unslash($_POST['desc'])) : '';
|
||||
|
||||
$removed = $user_id > 0 && class_exists('\AKSRT\User\Restriction')
|
||||
? \AKSRT\User\Restriction::get_instance()->lift_user_restriction($user_id)
|
||||
: 0;
|
||||
delete_user_meta($user_id, '_aksrt_no_appeal');
|
||||
|
||||
// 解封站内信(含给用户的留言)
|
||||
$msg = __('您的账号限制已解除。', 'aksrt');
|
||||
if ($desc !== '') {
|
||||
$msg .= ' ' . sprintf(__('留言:%s', 'aksrt'), $desc);
|
||||
}
|
||||
\AKSRT\Messages\Message::get_instance()->send($user_id, 'system', $msg, ['title' => __('限制解除', 'aksrt')]);
|
||||
|
||||
$this->redirect('aksrt_user_ban', $removed > 0 ? 'restriction_removed' : 'restriction_failed', ['tab' => 'restrictions', 'user_id' => $user_id]);
|
||||
}
|
||||
|
||||
public function handle_process_appeal(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('权限不足', 'aksrt'));
|
||||
|
||||
@@ -477,7 +477,9 @@ trait UsersMenusTickets {
|
||||
if (class_exists('\AKSRT\User\Restriction') && \AKSRT\User\Restriction::get_instance()->is_restricted($target_id)) {
|
||||
echo $this->ticket_ban_info_html($target_id);
|
||||
} else {
|
||||
$ban_url = admin_url('users.php?page=aksrt_user_ban&tab=restrictions&user_id=' . $target_id);
|
||||
// 与参考主题一致:跳转到前台该用户公开主页,经封面右下角三点
|
||||
// 菜单的「禁封此用户」打开封禁弹窗完成处理(后台不提供禁封表单)。
|
||||
$ban_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($target_id) : get_author_posts_url($target_id);
|
||||
echo '<div style="color:#16a60a;">' . esc_html__('用户状态正常', 'aksrt') . '</div>';
|
||||
echo '<p class="description">' . esc_html__('请查阅相关资料以及举证,如举报事实清楚,可以', 'aksrt');
|
||||
echo ' <a target="_blank" href="' . esc_url($ban_url) . '">' . esc_html__('点击此处', 'aksrt') . '</a> ';
|
||||
|
||||
@@ -310,6 +310,21 @@ class UserController extends ControllerBase {
|
||||
'callback' => [$this, 'get_restriction_stats'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// 前台禁封弹窗(1:1 复刻参考主题 set_user_ban_modal / zib_get_set_user_ban_modal)。
|
||||
// modal 内容每次拉取(restriction 状态可能已变),不缓存。
|
||||
register_rest_route(self::API_NAMESPACE, '/user-ban/modal', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_user_ban_modal'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// 前台禁封 / 解封提交(对齐参考主题 wp_ajax_set_user_ban)。
|
||||
register_rest_route(self::API_NAMESPACE, '/user-ban/set', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'set_user_ban'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -79,4 +79,135 @@ trait UserControllerRestrictionOperations {
|
||||
$stats = \AKSRT\User\Restriction::get_instance()->get_stats();
|
||||
return $this->success(['data' => $stats]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台禁封弹窗内容(1:1 复刻参考主题 zib_get_set_user_ban_modal,
|
||||
* 经 aksrt_get_user_ban_modal 渲染;参考主题 refresh_modal 每次刷新同语义)。
|
||||
*/
|
||||
public function get_user_ban_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_modal')) {
|
||||
return $this->error('禁封组件未加载', 501);
|
||||
}
|
||||
$html = aksrt_get_user_ban_modal($user_id);
|
||||
if ($html === '') {
|
||||
return $this->error('抱歉,您暂时没有此权限', 403);
|
||||
}
|
||||
return $this->success(['data' => ['html' => $html]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台禁封 / 解封提交(1:1 复刻参考主题 zib_ajax_set_user_ban 语义):
|
||||
* ban=0 解封(留言站内信);ban=1 拉入小黑屋 / ban=2 禁封用户,
|
||||
* 支持禁封时长、预置/其它原因、禁封说明、删除全部内容、禁止申诉(含说明)。
|
||||
*/
|
||||
public function set_user_ban(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $request->get_json_params() ?? [];
|
||||
$user_id = (int) ($params['user_id'] ?? 0);
|
||||
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||
return $this->error('用户不存在或参数传入错误', 404);
|
||||
}
|
||||
if (!function_exists('aksrt_current_user_can_ban') || !aksrt_current_user_can_ban($user_id)) {
|
||||
return $this->error('抱歉,您暂时没有此权限', 403);
|
||||
}
|
||||
if (!class_exists('\AKSRT\User\Restriction')) {
|
||||
return $this->error('限制系统未启用', 501);
|
||||
}
|
||||
|
||||
$svc = \AKSRT\User\Restriction::get_instance();
|
||||
$ban = (int) ($params['ban'] ?? 0);
|
||||
$desc = isset($params['desc']) ? sanitize_textarea_field((string) wp_unslash($params['desc'])) : '';
|
||||
|
||||
// ── 解封 ──
|
||||
if (0 === $ban) {
|
||||
$svc->lift_user_restriction($user_id);
|
||||
delete_user_meta($user_id, '_aksrt_no_appeal');
|
||||
$this->send_ban_notice($user_id, __('您的账号限制已解除。', 'aksrt'), __('限制解除', 'aksrt'), $desc);
|
||||
return $this->success(['message' => __('已解封此用户', 'aksrt'), 'data' => ['banned' => false]]);
|
||||
}
|
||||
|
||||
// ── 禁封 ──
|
||||
$type = 2 === $ban ? \AKSRT\User\Restriction::TYPE_BAN : \AKSRT\User\Restriction::TYPE_SUSPEND;
|
||||
|
||||
$reason = isset($params['reason']) ? sanitize_text_field((string) wp_unslash($params['reason'])) : '';
|
||||
if ($reason === '' || $reason === 'other') {
|
||||
$reason = isset($params['reason_other']) ? sanitize_text_field((string) wp_unslash($params['reason_other'])) : '';
|
||||
}
|
||||
if ($reason === '') {
|
||||
return $this->error(__('请输入禁封原因', 'aksrt'));
|
||||
}
|
||||
|
||||
$time_day = isset($params['time_day']) ? (int) $params['time_day'] : 0;
|
||||
$expires = $time_day > 0 ? date('Y-m-d H:i:s', strtotime('+' . $time_day . ' day', current_time('timestamp'))) : '';
|
||||
|
||||
$no_appeal = !empty($params['no_appeal']);
|
||||
$no_appeal_desc = isset($params['no_appeal_desc']) ? sanitize_textarea_field((string) wp_unslash($params['no_appeal_desc'])) : '';
|
||||
if ($no_appeal && $no_appeal_desc === '') {
|
||||
return $this->error(__('请输入禁止用户申诉的说明或原因', 'aksrt'));
|
||||
}
|
||||
|
||||
// 禁封说明并入原因列(与后台工单禁封表单同存储语义)。
|
||||
if ($desc !== '') {
|
||||
$reason .= "\n" . sprintf(__('禁封说明:%s', 'aksrt'), $desc);
|
||||
}
|
||||
|
||||
$result = $svc->restrict($user_id, $type, $reason, \AKSRT\User\Restriction::SCOPE_GLOBAL, $expires, get_current_user_id());
|
||||
if (empty($result['success'])) {
|
||||
return $this->error((string) ($result['message'] ?? __('操作失败', 'aksrt')));
|
||||
}
|
||||
|
||||
if ($no_appeal) {
|
||||
update_user_meta($user_id, '_aksrt_no_appeal', [
|
||||
'desc' => $no_appeal_desc,
|
||||
'at' => current_time('mysql'),
|
||||
]);
|
||||
}
|
||||
|
||||
// 站内信(后台禁封表单 / 参考主题 zib_updata_user_ban_msg 同语义)。
|
||||
$notice = $type === \AKSRT\User\Restriction::TYPE_BAN
|
||||
? __('您的账号已被禁封,将无法登录。', 'aksrt')
|
||||
: __('您已被拉入小黑屋,发布等相关权限已被限制。', 'aksrt');
|
||||
if ($time_day > 0) {
|
||||
$notice .= ' ' . sprintf(__('期限:%1$d 天,%2$s 起自动解封。', 'aksrt'), $time_day, $expires);
|
||||
}
|
||||
$this->send_ban_notice($user_id, $notice, __('账号限制', 'aksrt'), $desc);
|
||||
|
||||
// 删除全部内容:全部公开内容与已审核评论移至回收站(参考主题同行为)。
|
||||
if (!empty($params['delete_content'])) {
|
||||
$post_ids = get_posts([
|
||||
'author' => $user_id,
|
||||
'post_type' => get_post_types(['public' => true]),
|
||||
'post_status' => 'publish',
|
||||
'numberposts' => -1,
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
foreach ($post_ids as $pid) {
|
||||
wp_trash_post((int) $pid);
|
||||
}
|
||||
foreach (get_comments(['user_id' => $user_id, 'status' => 'approve', 'number' => 500]) as $comment) {
|
||||
wp_trash_comment((int) $comment->comment_ID);
|
||||
}
|
||||
}
|
||||
|
||||
$send_msg = 2 === $ban ? __('已禁封此用户', 'aksrt') : __('已将此用户拉入小黑屋', 'aksrt');
|
||||
return $this->success(['message' => $send_msg, 'data' => ['banned' => true]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁封 / 解封站内信(留言拼附在正文之后)。
|
||||
*/
|
||||
private function send_ban_notice(int $user_id, string $notice, string $title, string $desc = ''): void
|
||||
{
|
||||
if (!class_exists('\AKSRT\Messages\Message')) {
|
||||
return;
|
||||
}
|
||||
$msg = $notice;
|
||||
if ($desc !== '') {
|
||||
$msg .= ' ' . sprintf(__('留言:%s', 'aksrt'), $desc);
|
||||
}
|
||||
\AKSRT\Messages\Message::get_instance()->send($user_id, 'system', $msg, ['title' => $title]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +279,8 @@ trait ScriptsPart1b {
|
||||
|
||||
// 举报脚本:举报入口渲染于文章页 meta 菜单、评论区与用户主页/个人中心,
|
||||
// 因此 singular 页面与 profile 路由都需要加载(文章页举报按钮此前无脚本支撑)。
|
||||
// 禁封脚本:禁封入口渲染于用户主页封面三点菜单、评论区三点菜单与文章
|
||||
// 作者栏三点菜单,与举报脚本同一组页面,合并加载。
|
||||
if (is_singular() || $is_profile_page) {
|
||||
$report_js_path = get_template_directory() . '/assets/js/report.js';
|
||||
if (file_exists($report_js_path)) {
|
||||
@@ -290,6 +292,16 @@ trait ScriptsPart1b {
|
||||
true
|
||||
);
|
||||
}
|
||||
$user_ban_js_path = get_template_directory() . '/assets/js/user-ban.js';
|
||||
if (file_exists($user_ban_js_path)) {
|
||||
wp_enqueue_script(
|
||||
'aksrt-user-ban',
|
||||
$base_uri . '/assets/js/user-ban.js',
|
||||
['loader_js'],
|
||||
filemtime($user_ban_js_path) ?: $version,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 用户中心 / 公开主页:路由专属 CSS
|
||||
|
||||
@@ -87,7 +87,8 @@ class ReportCenter {
|
||||
$content,
|
||||
[
|
||||
'title' => __('用户举报', 'aksrt'),
|
||||
'target_url' => admin_url('users.php?page=aksrt_user_ban&tab=tickets'),
|
||||
// 直达待处理举报队列(参考主题 admin.php?page=user_ban&type=report&status=0 同语义)。
|
||||
'target_url' => admin_url('users.php?page=aksrt_user_ban&type=report&status=pending'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ final class MailNotifications {
|
||||
|
||||
public static function ban_appeal_submitted(int $user_id, array $payload): void {
|
||||
$user = get_userdata($user_id);
|
||||
if ($user instanceof \WP_User) self::send_admin('ban_appeal_admin', ['username' => $user->display_name, 'url' => admin_url('users.php?page=aksrt_user_ban&tab=appeals')]);
|
||||
if ($user instanceof \WP_User) self::send_admin('ban_appeal_admin', ['username' => $user->display_name, 'url' => admin_url('users.php?page=aksrt_user_ban&type=appeal&status=pending')]);
|
||||
}
|
||||
|
||||
public static function ban_appeal_processed(int $user_id, string $decision, string $note): void {
|
||||
|
||||
@@ -141,7 +141,8 @@ class BanAppeal {
|
||||
$summary,
|
||||
[
|
||||
'title' => __('禁封申诉', 'aksrt'),
|
||||
'target_url' => admin_url('users.php?page=aksrt_user_ban&tab=appeals'),
|
||||
// 直达待处理申诉队列(与工单页 type=appeal&status=pending 过滤同参)。
|
||||
'target_url' => admin_url('users.php?page=aksrt_user_ban&type=appeal&status=pending'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,13 @@ $show_header_notification = !empty($notification_settings['enabled'])
|
||||
&& in_array('user_home_header', (array) ($notification_settings['icon_show'] ?? []), true);
|
||||
$messages_url = $show_header_notification && function_exists('aksrt_get_messages_page_url') ? aksrt_get_messages_page_url() : '';
|
||||
$unread_count = $is_own ? aksrt_get_user_unread_notification_count($user_id) : 0;
|
||||
// 封面三点菜单末项:管理员 → 禁封/解封(参考主题 author_header_drop_lists 钩子语义,
|
||||
// 禁封权限优先于举报);否则 → 举报用户(保持既有契约)。
|
||||
$cover_ban_html = function_exists('aksrt_get_user_cover_menu_action_html')
|
||||
? aksrt_get_user_cover_menu_action_html($user_id)
|
||||
: '';
|
||||
$show_cover_report = $cover_ban_html === '' && $show_report && !$is_own;
|
||||
$show_cover_menu = $has_more_details || $show_cover_report || $cover_ban_html !== '';
|
||||
?><div class="aksrt-author-hero<?php echo $show_cover ? '' : ' aksrt-author-hero--no-cover'; ?>">
|
||||
<?php if ($show_cover) : ?>
|
||||
<div class="aksrt-author-hero__cover">
|
||||
@@ -74,12 +81,12 @@ $unread_count = $is_own ? aksrt_get_user_unread_notification_count($user_id) : 0
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($has_more_details || ($show_report && !$is_own)) : ?>
|
||||
<?php if ($show_cover_menu) : ?>
|
||||
<div class="aksrt-cover-menu aksrt-cover-menu--author" data-aksrt-menu>
|
||||
<button type="button" class="aksrt-cover-menu__trigger" aria-haspopup="true" aria-expanded="false" aria-label="<?php esc_attr_e('个人主页操作', 'aksrt'); ?>"><i class="fa fas fa-ellipsis-h" aria-hidden="true"></i></button>
|
||||
<div class="aksrt-cover-menu__panel" role="menu" hidden>
|
||||
<?php if ($has_more_details) : ?><button type="button" role="menuitem" data-aksrt-profile-details><i class="fa fas fa-address-card" aria-hidden="true"></i><?php esc_html_e('更多资料', 'aksrt'); ?></button><?php endif; ?>
|
||||
<?php if ($show_report && !$is_own) : ?>
|
||||
<?php if ($show_cover_report) : ?>
|
||||
<?php
|
||||
// 统一举报契约:report.js 弹窗展示被举报对象(头像 + 名称)。
|
||||
$report_payload = [
|
||||
@@ -92,6 +99,10 @@ $unread_count = $is_own ? aksrt_get_user_unread_notification_count($user_id) : 0
|
||||
<?php if (is_user_logged_in()) : ?><button type="button" role="menuitem" data-aksrt-report="<?php echo esc_attr(wp_json_encode($report_payload)); ?>"><i class="fa fas fa-flag" aria-hidden="true"></i><?php esc_html_e('举报用户', 'aksrt'); ?></button>
|
||||
<?php else : ?><a role="menuitem" href="<?php echo esc_url(aksrt_get_login_url($home_url)); ?>"><i class="fa fas fa-flag" aria-hidden="true"></i><?php esc_html_e('举报用户', 'aksrt'); ?></a><?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
// 禁封/解封入口(user-ban.js 的 [data-aksrt-ban] 委托)。
|
||||
echo $cover_ban_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper escapes internally.
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -17,6 +17,8 @@ const home = read('src/User/Home.php')
|
||||
const commentItem = read('inc/helpers/comments/item.php')
|
||||
const functions = read('functions.php')
|
||||
const uninstall = read('uninstall.php')
|
||||
const userBanHelper = read('inc/helpers/user-ban.php')
|
||||
const userBanJs = read('assets/js/user-ban.js')
|
||||
|
||||
test('report REST surface is canonical: submit + users/reports only', () => {
|
||||
assert.match(userController, /'\/report\/submit'/)
|
||||
@@ -147,7 +149,7 @@ test('admin report notification links to the real page', () => {
|
||||
assert.match(reportCenter, /notify_admins_new_report\(/)
|
||||
assert.match(reportCenter, /notify_reporter_status_changed\(/)
|
||||
assert.match(reportCenter, /Message::get_instance\(\)->send\(/)
|
||||
assert.match(reportCenter, /users\.php\?page=aksrt_user_ban&tab=tickets/)
|
||||
assert.match(reportCenter, /users\.php\?page=aksrt_user_ban&type=report&status=pending/)
|
||||
})
|
||||
|
||||
test('native ban page mirrors the reference theme layout', () => {
|
||||
@@ -201,10 +203,18 @@ test('native ban page mirrors the reference theme layout', () => {
|
||||
// 旧的三 tab 结构已收敛;举报处理表单不包含处罚下拉(对齐参考主题)
|
||||
assert.ok(!usersMenus.includes('tab=reports'), 'old reports tab is gone')
|
||||
assert.ok(!usersMenus.includes('tab=appeals'), 'old appeals tab is gone')
|
||||
// 禁封限制 Tab 已移除(参考主题无此后台 Tab):禁封改走前台封禁弹窗。
|
||||
assert.ok(!usersMenus.includes('tab=restrictions'), 'restrictions tab is gone')
|
||||
assert.ok(!usersMenus.includes('render_restrictions_tab'), 'restrictions tab renderer is gone')
|
||||
assert.ok(!usersMenus.includes('handle_add_restriction'), 'backend ban form handler is gone')
|
||||
assert.ok(!usersMenus.includes('handle_remove_restriction'), 'backend unban form handler is gone')
|
||||
assert.ok(!usersMenus.includes('render_reports_tab'))
|
||||
assert.ok(!usersMenus.includes('render_appeals_tab'))
|
||||
assert.match(usersMenus, /use UsersMenusTickets;/)
|
||||
assert.ok(!tickets.includes('name="action_taken"'), 'report process form should be status-only like the reference theme')
|
||||
// 工单处理页「点击此处」跳转前台用户公开主页(封面三点菜单 → 封禁弹窗)。
|
||||
assert.match(tickets, /aksrt_get_user_home_url\(\$target_id\)/)
|
||||
assert.ok(!tickets.includes('tab=restrictions'), 'ticket ban link must not point at the removed backend tab')
|
||||
})
|
||||
|
||||
test('report modal mirrors the reference theme: preset reasons, image evidence, notice, geometry', () => {
|
||||
@@ -245,38 +255,71 @@ test('comments expose a report entry for other users when reporting is enabled',
|
||||
assert.match(commentItem, /comment-report-link/)
|
||||
assert.match(commentItem, /report'\]\['enabled'\]/)
|
||||
assert.match(commentItem, /\$author_id === \$reporter_id/)
|
||||
// 下拉菜单(置顶/编辑/批准/删除 + 举报)包含举报项。
|
||||
assert.match(commentItem, /function aksrt_get_comments_action_dropdown_html[\s\S]*?\$lists \.= aksrt_get_comment_report_html/)
|
||||
// 下拉菜单(置顶/编辑/批准/删除 + 禁封或举报)末项走禁封优先、否则举报。
|
||||
assert.match(commentItem, /function aksrt_get_comments_action_dropdown_html[\s\S]*?\$lists \.= aksrt_get_comment_ban_or_report_item/)
|
||||
// 禁封项(管理员可见):参考主题 comments_action_lists 钩子语义,渲染于 user-ban.php。
|
||||
assert.match(userBanHelper, /comment-ban-link/)
|
||||
assert.match(userBanHelper, /data-aksrt-ban="/)
|
||||
// 举报不作为 comt-meta 独立按钮直接渲染。
|
||||
assert.doesNotMatch(commentItem, /\$report\s*=\s*aksrt_get_comment_report_html\(\$comment\)/)
|
||||
})
|
||||
|
||||
test('restrictions tab mirrors the reference ban modal 1:1', () => {
|
||||
// 禁封方式/禁封时间/预置原因徽章/禁封说明/删除全部内容/禁止用户申诉,
|
||||
// 以及已禁封用户的状态信息与解封留言表单(zib_get_set_user_ban_modal 同构)。
|
||||
for (const label of ['拉入小黑屋', '禁封用户', '禁封方式', '禁封时间', '选择禁封原因', '其它原因', '禁封说明', '删除全部内容', '禁止用户申诉', '请填写禁止申诉说明', '解封用户', '移出小黑屋', '解除禁言', '小黑屋禁封中', '禁封中(禁止登录)', '禁言中', '给用户的留言', '开始时间', '结束时间']) {
|
||||
assert.ok(usersMenus.includes(label), `restrictions tab missing reference label: ${label}`)
|
||||
test('frontend ban modal mirrors the reference theme zib_get_set_user_ban_modal 1:1', () => {
|
||||
// 禁封方式/禁封时间/预置原因徽章/其它原因/禁封说明/删除全部内容/禁止用户申诉,
|
||||
// 以及已禁封用户的状态信息与解封留言表单(弹窗经 REST /user-ban/modal 输出)。
|
||||
for (const label of ['拉入小黑屋', '禁封用户', '禁封方式', '禁封时间', '选择禁封原因', '其它原因', '禁封说明', '删除全部内容', '禁止用户申诉', '请填写禁止申诉说明', '解封用户', '移出小黑屋', '禁封中(禁止登录)', '小黑屋', '给用户的留言', '开始时间', '结束时间', '原因', '说明', '禁封记录', '确认禁封']) {
|
||||
assert.ok(userBanHelper.includes(label), `ban modal missing reference label: ${label}`)
|
||||
}
|
||||
// 禁言(mute)记录在操作列提供“解除禁言”入口,且解除视图能选中 mute 记录
|
||||
// (此前操作列对 mute 为空、顶部视图只认 ban/suspend,禁言无法从后台解除)。
|
||||
assert.match(usersMenus, /\$type === 'mute'/)
|
||||
assert.match(usersMenus, /'mute'\s*=>\s*__\('解除禁言', 'aksrt'\)/)
|
||||
assert.match(usersMenus, /\$unban && \$current === null/)
|
||||
// 时长选项 1/7/14/30/100/永久(默认 7 天);类型映射:小黑屋→suspend,禁封→ban。
|
||||
assert.match(usersMenus, /1 => __\('1天', 'aksrt'\)/)
|
||||
assert.match(usersMenus, /7 => __\('7天', 'aksrt'\)/)
|
||||
assert.match(usersMenus, /100 => __\('100天', 'aksrt'\)/)
|
||||
assert.match(usersMenus, /checked\(7, \$days, false\)/)
|
||||
assert.match(usersMenus, /\$type\s*=\s*\$ban === 2 \? 'ban' : 'suspend'/)
|
||||
assert.match(userBanHelper, /1 => __\('1天', 'aksrt'\)/)
|
||||
assert.match(userBanHelper, /7 => __\('7天', 'aksrt'\)/)
|
||||
assert.match(userBanHelper, /100 => __\('100天', 'aksrt'\)/)
|
||||
assert.match(userBanHelper, /7 === \$days \? ' checked="checked"' : ''/)
|
||||
// 预置原因来自 UserRuntime::ban_settings(与后台同一数据源)。
|
||||
assert.match(userBanHelper, /UserRuntime::ban_settings\(\)\['reasons'\]/)
|
||||
// 勾选禁止申诉必须填写说明(参考主题同校验),落用户 meta 供申诉入口拦截。
|
||||
assert.match(usersMenus, /_aksrt_no_appeal/)
|
||||
assert.match(usersMenus, /请填写禁止申诉说明/)
|
||||
assert.match(userBanHelper, /_aksrt_no_appeal|no_appeal/)
|
||||
const appeal = read('src/User/BanAppeal.php')
|
||||
assert.match(appeal, /_aksrt_no_appeal/)
|
||||
// 解封流程:按用户解封 + 留言站内信(旧 restriction_id 通道退役)。
|
||||
assert.match(usersMenus, /wp_nonce_field\('aksrt_users_unban_' \. \$target_id\)/)
|
||||
assert.match(usersMenus, /lift_user_restriction\(\$user_id\)/)
|
||||
assert.ok(!usersMenus.includes('name="restriction_id"'), 'per-record removal superseded by per-user unban')
|
||||
// 弹窗入口按钮契约:[data-aksrt-ban] 委托(封面按钮 / 评论菜单项 / 文章作者菜单项)。
|
||||
assert.match(userBanHelper, /data-aksrt-ban="/)
|
||||
assert.match(userBanHelper, /function aksrt_get_user_cover_menu_action_html/)
|
||||
assert.match(userBanHelper, /function aksrt_get_comment_ban_or_report_item/)
|
||||
assert.match(userBanHelper, /function aksrt_get_post_author_ban_item/)
|
||||
// 权限模型(参考主题 zib_current_user_can('set_user_ban') 语义):
|
||||
// manage_options + 非本人 + 非超级管理员 + 功能开关。
|
||||
assert.match(userBanHelper, /current_user_can\('manage_options'\)/)
|
||||
assert.match(userBanHelper, /\$current_id === \$user_id/)
|
||||
assert.match(userBanHelper, /is_super_admin\(\$user_id\)/)
|
||||
assert.match(userBanHelper, /user_ban_s/)
|
||||
// REST 路由:modal 拉取 + set 提交(permission_callback 校验)。
|
||||
assert.match(userController, /'\/user-ban\/modal'/)
|
||||
assert.match(userController, /'\/user-ban\/set'/)
|
||||
// 解封视图多彩横幅(参考主题 zib_get_modal_colorful_header('c-yellow', ...))。
|
||||
assert.match(userBanHelper, /modal-colorful-header colorful-bg c-yellow/)
|
||||
// 提交后整页刷新(禁封 reload:true)/ 仅关闭弹窗(解封 hide_modal:true)。
|
||||
assert.match(userBanJs, /window\.location\.reload\(\)/)
|
||||
assert.match(userBanJs, /AksrtModalRuntime\.close\('submit'\)/)
|
||||
assert.match(userBanJs, /data-aksrt-ban-submit/)
|
||||
assert.match(userBanJs, /data-aksrt-unban-submit/)
|
||||
// 桌面 600px(参考主题未传 data-class → Bootstrap .modal-dialog 默认宽)
|
||||
// + min-height 360px(data-height=360);移动端底部抽屉。
|
||||
assert.match(userBanJs, /MODAL_HEIGHT = 360/)
|
||||
assert.match(userBanJs, /mobileBottom: true/)
|
||||
const css = read('assets/css/frontend/modal-runtime.css')
|
||||
assert.match(css, /\.aksrt-user-ban-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /600px/)
|
||||
// textarea autoHeight 1:1(参考主题 maxHeight 锁高 + 边框补偿)。
|
||||
assert.match(userBanJs, /getAttribute\('maxHeight'\)/)
|
||||
// 三个入口模板接线:封面三点菜单 / 评论区三点菜单 / 文章作者 metabox 三点菜单。
|
||||
const authorHero = read('templates/parts/user/author-hero.php')
|
||||
assert.match(authorHero, /aksrt_get_user_cover_menu_action_html\(\$user_id\)/)
|
||||
assert.match(commentItem, /aksrt_get_comment_ban_or_report_item\(\$comment\)/)
|
||||
const singleComponents = read('inc/helpers/aksrt-single-components.php')
|
||||
assert.match(singleComponents, /aksrt_get_post_author_ban_item\(/)
|
||||
// 脚本入队(singular + profile,与 report.js 同组)。
|
||||
const scripts = read('src/Core/ScriptsPart1b.php')
|
||||
assert.match(scripts, /'aksrt-user-ban'/)
|
||||
})
|
||||
|
||||
test('uninstall cleans up every ban/report meta key written by the theme', () => {
|
||||
|
||||
Reference in New Issue
Block a user