Files
wordpress-theme-aksrt/src/Admin/Native/UsersMenusInviteCode.php
T
kate522andClaude Fable 5 a6776ffe0a feat(admin): upgrade invite code management with dialogs
Replace standalone add and export views with accessible dialogs, refine list interactions, and remove obsolete paths and unused counts. Fix filtered totals and reject oversized imports without truncation, with regression coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-30 18:53:01 +08:00

179 lines
11 KiB
PHP

<?php
/**
* Native invite-code list and page assets.
*
* @package AKSRT
*/
namespace AKSRT\Admin\Native;
defined('ABSPATH') || exit;
trait UsersMenusInviteCode {
use UsersMenusInviteForms;
public function enqueue_invite_assets(string $hook): void {
if ($hook !== 'users_page_aksrt_invite_codes') {
return;
}
$css = '/assets/css/admin/invite-codes.css';
$js = '/assets/js/admin-invite-codes.js';
wp_enqueue_style('aksrt-admin-invite-codes', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
wp_enqueue_script('aksrt-admin-invite-codes', AKSRT_URI . $js, [], (string) filemtime(AKSRT_DIR . $js), true);
}
public function render_invite_codes_page(): void {
if (!current_user_can('edit_users')) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
}
$service = \AKSRT\Payment\InviteCode::get_instance();
$this_url = admin_url('users.php?page=aksrt_invite_codes');
$nonce = wp_create_nonce('aksrt_invite_admin');
NativeUi::page_open('aksrt-inv-page');
NativeUi::header(
__('邀请码管理', 'aksrt'),
__('管理注册邀请码与使用奖励,点击邀请码即可复制。', 'aksrt'),
'dashicons-tickets-alt',
[
['label' => __('添加邀请码', 'aksrt'), 'dialog' => 'aksrt-inv-add', 'primary' => true, 'icon' => 'dashicons-plus-alt2'],
['label' => __('导出邀请码', 'aksrt'), 'dialog' => 'aksrt-inv-export', 'icon' => 'dashicons-download'],
]
);
$settings = $service->registration_settings();
if (empty($settings['enabled'])) {
$settings_url = admin_url('admin.php?page=aksrt-settings#/users/auth');
echo '<div class="notice notice-warning"><p>' . esc_html__('邀请码注册尚未启用,已添加的邀请码暂不能用于注册。', 'aksrt')
. ' <a href="' . esc_url($settings_url) . '">' . esc_html__('前往登录注册设置', 'aksrt') . '</a></p></div>';
}
$title = isset($_GET['aksrt_inv_title']) ? sanitize_text_field(rawurldecode((string) wp_unslash($_GET['aksrt_inv_title']))) : '';
$message = isset($_GET['aksrt_inv_msg']) ? sanitize_text_field(rawurldecode((string) wp_unslash($_GET['aksrt_inv_msg']))) : '';
$type = ($_GET['aksrt_inv_type'] ?? '') === 'error' ? 'error' : 'success';
if ($title !== '') {
echo '<div class="notice notice-' . esc_attr($type) . ' is-dismissible"><p><strong>' . esc_html($title) . '</strong>'
. ($message !== '' ? ' ' . esc_html($message) : '') . '</p></div>';
}
$this->render_invite_codes_list($service, $this_url, $nonce);
$this->render_invite_codes_add($nonce);
$this->render_invite_codes_export($nonce);
echo '<div class="aksrt-inv-toast" role="status" aria-live="polite" data-aksrt-inv-toast'
. ' data-copied="' . esc_attr__('邀请码已复制', 'aksrt') . '"'
. ' data-copy-failed="' . esc_attr__('自动复制失败,请选中邀请码后手动复制。', 'aksrt') . '"'
. ' data-exported="' . esc_attr__('下载请求已提交,请查看浏览器下载列表。', 'aksrt') . '"></div>';
NativeUi::page_close();
}
private function render_invite_codes_list(\AKSRT\Payment\InviteCode $service, string $this_url, string $nonce): void {
$status = in_array(($_GET['status'] ?? 'all'), ['all', 'unused', 'used'], true) ? (string) $_GET['status'] : 'all';
$s = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$remark = isset($_GET['remark']) ? sanitize_text_field((string) wp_unslash($_GET['remark'])) : '';
$data = $service->list_codes([
'page' => isset($_GET['paged']) ? max(1, (int) $_GET['paged']) : 1,
'status' => $status,
's' => $s,
'remark' => $remark,
'orderby' => isset($_GET['orderby']) ? sanitize_key((string) wp_unslash($_GET['orderby'])) : '',
'desc' => isset($_GET['desc']) ? sanitize_key((string) wp_unslash($_GET['desc'])) : '',
]);
$filters = ['status' => $status, 's' => $s, 'remark' => $remark, 'orderby' => $data['orderby'], 'desc' => $data['desc']];
$filter_url = add_query_arg($filters, $this_url);
NativeUi::card_open('', '', false, 'aksrt-inv-toolbar');
echo '<div class="aksrt-native-toolbar"><ul class="subsubsub" aria-label="' . esc_attr__('使用状态筛选', 'aksrt') . '">';
foreach (['all' => __('全部', 'aksrt'), 'unused' => __('未使用', 'aksrt'), 'used' => __('已使用', 'aksrt')] as $key => $label) {
$url = add_query_arg('status', $key, $filter_url);
echo '<li><a href="' . esc_url($url) . '"' . ($key === $status ? ' class="current" aria-current="page"' : '') . '>' . esc_html($label) . '</a></li>';
}
echo '</ul><form method="get" action="' . esc_url(admin_url('users.php')) . '" class="aksrt-native-filters">';
echo '<input type="hidden" name="page" value="aksrt_invite_codes">';
foreach (array_diff_key($filters, ['s' => '']) as $key => $value) {
echo '<input type="hidden" name="' . esc_attr($key) . '" value="' . esc_attr($value) . '">';
}
echo '<label class="screen-reader-text" for="aksrt-inv-search">' . esc_html__('搜索邀请码或标识', 'aksrt') . '</label>';
echo '<input type="search" id="aksrt-inv-search" name="s" value="' . esc_attr($s) . '" placeholder="' . esc_attr__('搜索邀请码或标识', 'aksrt') . '">';
echo '<button type="submit" class="button">' . esc_html__('搜索', 'aksrt') . '</button></form></div>';
if ($s !== '' || $remark !== '' || $status !== 'all') {
echo '<div class="aksrt-inv-filter-summary">';
if ($s !== '') {
echo '<span>' . esc_html(sprintf(__('关键词:%s', 'aksrt'), $s)) . '</span>';
}
if ($remark !== '') {
echo '<span>' . esc_html(sprintf(__('标识:%s', 'aksrt'), $remark)) . '</span>';
}
echo '<a href="' . esc_url($this_url) . '">' . esc_html__('清除筛选', 'aksrt') . '</a></div>';
}
NativeUi::card_close();
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" data-aksrt-inv-list'
. ' data-confirm="' . esc_attr__('确定删除选中的 %d 个邀请码及其使用记录?此操作不可撤销。', 'aksrt') . '">';
echo '<input type="hidden" name="action" value="aksrt_invite_delete"><input type="hidden" name="aksrt_nonce" value="' . esc_attr($nonce) . '">';
$bulk = '<div class="aksrt-native-inline"><span class="aksrt-inv-selection" data-aksrt-inv-selection data-label="' . esc_attr__('已选 %d 项', 'aksrt') . '" aria-live="polite"></span>'
. '<label class="screen-reader-text" for="bulk-action-selector-top">' . esc_html__('批量操作', 'aksrt') . '</label>'
. '<select name="bulk_action" id="bulk-action-selector-top"><option value="-1">' . esc_html__('批量操作', 'aksrt') . '</option><option value="delete">' . esc_html__('删除', 'aksrt') . '</option></select>'
. '<button type="submit" class="button" data-aksrt-inv-bulk disabled>' . esc_html__('应用', 'aksrt') . '</button></div>';
NativeUi::table_card_open(__('邀请码列表', 'aksrt'), $bulk);
echo '<table class="widefat fixed striped aksrt-inv-table"><thead><tr><td class="manage-column column-cb check-column">';
echo '<input type="checkbox" data-aksrt-inv-check-all aria-label="' . esc_attr__('全选本页邀请码', 'aksrt') . '"></td>';
$columns = [
'code' => __('邀请码', 'aksrt'), 'rewards' => __('使用奖励', 'aksrt'),
'created_at' => __('创建时间', 'aksrt'), 'updated_at' => __('更新时间', 'aksrt'),
'used_count' => __('使用状态', 'aksrt'), 'remark' => __('标识', 'aksrt'),
];
foreach ($columns as $key => $label) {
$active = $key === $data['orderby'];
$sort = $data['desc'] === 'ASC' ? 'ascending' : 'descending';
echo '<th scope="col" class="column-' . esc_attr($key) . '"' . ($active ? ' aria-sort="' . esc_attr($sort) . '"' : '') . '>';
if ($key === 'rewards') {
echo esc_html($label);
} else {
$next = $active && $data['desc'] === 'ASC' ? 'DESC' : 'ASC';
$url = add_query_arg(['orderby' => $key, 'desc' => $next], $filter_url);
$icon = $active && $data['desc'] === 'ASC' ? 'dashicons-arrow-up-alt2' : 'dashicons-arrow-down-alt2';
echo '<a class="orderby-but' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '" title="' . esc_attr($next === 'ASC' ? __('按此列升序排列', 'aksrt') : __('按此列降序排列', 'aksrt')) . '">'
. esc_html($label) . '<span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span></a>';
}
echo '</th>';
}
echo '</tr></thead><tbody>';
if ($data['items'] === []) {
echo NativeUi::empty_row(7, __('暂无符合条件的邀请码', 'aksrt'), __('调整筛选条件,或点击“添加邀请码”生成、导入新的邀请码。', 'aksrt'));
}
foreach ($data['items'] as $item) {
$used = (int) $item['used_count'];
$max_uses = (int) $item['max_uses'];
echo '<tr><th scope="row" class="check-column"><input type="checkbox" name="action_id[]" value="' . esc_attr((string) $item['id']) . '" aria-label="' . esc_attr(sprintf(__('选择邀请码 %s', 'aksrt'), $item['code'])) . '"></th>';
echo '<td><button type="button" class="aksrt-inv-code aksrt-native-mono" data-clipboard-text="' . esc_attr($item['code']) . '" aria-label="' . esc_attr(sprintf(__('复制邀请码 %s', 'aksrt'), $item['code'])) . '">' . esc_html($item['code']) . '<span class="dashicons dashicons-admin-page" aria-hidden="true"></span></button></td>';
echo '<td>' . wp_kses((string) $item['reward_text'], ['br' => []]) . '</td>';
echo '<td class="aksrt-inv-date">' . esc_html($item['created_at']) . '</td><td class="aksrt-inv-date">' . esc_html($item['updated_at']) . '</td><td>';
$label = $used > 0 && $max_uses > 0 && $used < $max_uses ? sprintf(__('已使用 %1$d/%2$d', 'aksrt'), $used, $max_uses) : ($used > 0 ? __('已使用', 'aksrt') : __('未使用', 'aksrt'));
echo NativeUi::badge($label, $used > 0 ? 'neutral' : 'success');
foreach ((array) $item['recent_users'] as $user) {
echo '<div class="aksrt-inv-user"><a href="' . esc_url(get_edit_user_link((int) $user['user_id'])) . '">' . esc_html($user['name']) . '</a></div>';
}
echo '</td><td>';
if ($item['remark'] !== '') {
echo '<a href="' . esc_url(add_query_arg('remark', $item['remark'], $this_url)) . '">' . esc_html($item['remark']) . '</a>';
} else {
echo '<span class="aksrt-native-muted">—</span>';
}
echo '</td></tr>';
}
echo '</tbody></table>';
NativeUi::table_wrap_close();
echo '<div class="tablenav bottom"><div class="tablenav-pages"><span class="displaying-num">' . esc_html(sprintf(__('共 %s 项', 'aksrt'), number_format_i18n((int) $data['total']))) . '</span>';
if ((int) $data['max_pages'] > 1) {
echo paginate_links([
'base' => add_query_arg('paged', '%#%', $filter_url), 'format' => '',
'current' => (int) $data['page'], 'total' => (int) $data['max_pages'],
'type' => 'plain', 'prev_text' => '«', 'next_text' => '»',
]);
}
echo '</div></div>';
NativeUi::card_close();
echo '</form>';
}
}