Merge pull request 'Worktree zibll native admin menus' (#147) from worktree-zibll-native-admin-menus into main

Reviewed-on: CYL/wordpress-theme-aksrt#147
This commit is contained in:
2026-07-15 02:37:16 +08:00
6 changed files with 565 additions and 14 deletions
+5
View File
@@ -315,6 +315,11 @@ if (is_admin() && class_exists('AKSRT_Topic_Admin_List')) {
if (is_admin() && class_exists('AKSRT_Topic_Meta_Box')) {
AKSRT_Topic_Meta_Box::get_instance()->init();
}
// 论坛版块列表列
if (is_admin() && class_exists('AKSRT_Plate_Admin_List')) {
AKSRT_Plate_Admin_List::get_instance()->init();
}
+1
View File
@@ -325,6 +325,7 @@ require_once AKSRT_DIR . '/inc/classes/shop/class-aksrt-product-variants-meta-bo
require_once AKSRT_DIR . '/inc/classes/forum/class-aksrt-plate-meta-box.php';
require_once AKSRT_DIR . '/inc/classes/forum/class-aksrt-topic-admin-list.php';
require_once AKSRT_DIR . '/inc/classes/forum/class-aksrt-topic-meta-box.php';
require_once AKSRT_DIR . '/inc/classes/forum/class-aksrt-plate-admin-list.php';
require_once AKSRT_DIR . '/inc/classes/shop/shop-traits/trait-aksrt-shop-part-1.php';
require_once AKSRT_DIR . '/inc/classes/shop/shop-traits/trait-aksrt-shop-part-2.php';
require_once AKSRT_DIR . '/inc/classes/shop/shop-traits/trait-aksrt-shop-part-3.php';
@@ -35,7 +35,9 @@ class AKSRT_Admin_Pay_Menus {
add_action('admin_post_aksrt_pay_disable_charge_card', [$this, 'handle_disable_charge_card']);
add_action('admin_post_aksrt_pay_batch_charge_cards', [$this, 'handle_batch_charge_cards']);
add_action('admin_post_aksrt_pay_update_shipping', [$this, 'handle_update_shipping']);
add_action('admin_post_aksrt_pay_create_shipping', [$this, 'handle_create_shipping']);
add_action('admin_post_aksrt_pay_update_after_sale', [$this, 'handle_update_after_sale']);
add_action('admin_post_aksrt_pay_create_after_sale', [$this, 'handle_create_after_sale']);
add_action('admin_post_aksrt_pay_create_coupon', [$this, 'handle_create_coupon']);
add_action('admin_post_aksrt_pay_disable_coupon', [$this, 'handle_disable_coupon']);
add_action('admin_post_aksrt_pay_settle_income', [$this, 'handle_settle_income']);
@@ -202,8 +204,12 @@ class AKSRT_Admin_Pay_Menus {
'card_batch_failed' => __('卡密批量生成失败。', 'aksrt'),
'shipping_updated' => __('物流状态已更新。', 'aksrt'),
'shipping_failed' => __('物流状态更新失败。', 'aksrt'),
'shipping_created' => __('发货记录已创建。', 'aksrt'),
'shipping_create_failed' => __('发货记录创建失败。', 'aksrt'),
'after_sale_updated' => __('售后状态已更新。', 'aksrt'),
'after_sale_failed' => __('售后状态更新失败。', 'aksrt'),
'after_sale_created' => __('售后工单已创建。', 'aksrt'),
'after_sale_create_failed' => __('售后工单创建失败。', 'aksrt'),
'coupon_created' => __('优惠码已创建。', 'aksrt'),
'coupon_create_failed'=> __('优惠码创建失败(可能重复)。', 'aksrt'),
'coupon_disabled' => __('优惠码已禁用。', 'aksrt'),
@@ -286,6 +292,13 @@ class AKSRT_Admin_Pay_Menus {
return;
}
$detail_order = isset($_GET['order']) ? sanitize_text_field((string) wp_unslash($_GET['order'])) : '';
if ($detail_order !== '') {
$this->render_order_detail_page($detail_order);
$this->render_footer();
return;
}
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
$search = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
@@ -353,7 +366,8 @@ class AKSRT_Admin_Pay_Menus {
echo '<td>' . esc_html(number_format((float) ($row['amount'] ?? 0), 2)) . ' ' . esc_html((string) ($row['currency'] ?? '')) . '</td>';
echo '<td>' . esc_html($status_label) . '</td>';
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
echo '<td>';
echo '<td style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;">';
echo '<a class="button button-small" href="' . esc_url($this->page_url('aksrt_orders', ['order' => $order_number])) . '">' . esc_html__('详情', 'aksrt') . '</a>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:flex;gap:4px;align-items:center;">';
echo '<input type="hidden" name="action" value="aksrt_pay_update_order_status" />';
echo '<input type="hidden" name="order_number" value="' . esc_attr($order_number) . '" />';
@@ -392,6 +406,133 @@ class AKSRT_Admin_Pay_Menus {
$this->render_footer();
}
private function render_order_detail_page(string $order_number): void {
$repo = AKSRT_Order_Repository::get_instance();
$order = $repo->get($order_number);
if (!$order) {
echo '<div class="notice notice-error"><p>' . esc_html__('订单不存在。', 'aksrt') . '</p></div>';
echo '<p><a class="button" href="' . esc_url($this->page_url('aksrt_orders')) . '">' . esc_html__('返回订单列表', 'aksrt') . '</a></p>';
return;
}
$labels = class_exists('AKSRT_Domain_Model')
? AKSRT_Domain_Model::order_status_labels()
: [];
$context = $repo->get_admin_order_context($order_number);
$user_id = (int) ($order['user_id'] ?? 0);
$user = $user_id > 0 ? get_userdata($user_id) : null;
$status = (string) ($order['status'] ?? '');
$shipping_items = class_exists('AKSRT_Shipping')
? AKSRT_Shipping::get_instance()->get_by_order($order_number)
: [];
$after_sale = class_exists('AKSRT_After_Sale')
? (AKSRT_After_Sale::get_instance()->get_list(['order_number' => $order_number, 'limit' => 20])['items'] ?? [])
: [];
echo '<p><a class="button" href="' . esc_url($this->page_url('aksrt_orders')) . '">&larr; ' . esc_html__('返回订单列表', 'aksrt') . '</a></p>';
echo '<table class="widefat striped" style="max-width:960px;"><tbody>';
$rows = [
__('订单号', 'aksrt') => $order_number,
__('用户', 'aksrt') => $user ? $user->display_name . ' (#' . $user_id . ')' : (string) $user_id,
__('商品类型', 'aksrt') => (string) ($order['product_type'] ?? ''),
__('商品 ID', 'aksrt') => (string) ($order['product_id'] ?? ''),
__('金额', 'aksrt') => number_format((float) ($order['amount'] ?? 0), 2) . ' ' . (string) ($order['currency'] ?? ''),
__('状态', 'aksrt') => $labels[$status] ?? $status,
__('支付方式', 'aksrt') => (string) ($order['payment_method'] ?? ''),
__('交易号', 'aksrt') => (string) ($order['payment_transaction_id'] ?? ''),
__('创建时间', 'aksrt') => (string) ($order['created_at'] ?? ''),
__('支付时间', 'aksrt') => (string) ($order['paid_at'] ?? ''),
__('备注', 'aksrt') => (string) ($order['note'] ?? ''),
];
foreach ($rows as $label => $value) {
echo '<tr><th style="width:160px;">' . esc_html($label) . '</th><td>' . esc_html((string) $value) . '</td></tr>';
}
if (!empty($context)) {
echo '<tr><th>' . esc_html__('附加上下文', 'aksrt') . '</th><td><pre style="white-space:pre-wrap;margin:0;">' . esc_html(wp_json_encode($context, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) . '</pre></td></tr>';
}
echo '</tbody></table>';
echo '<h2 style="margin-top:20px;">' . esc_html__('更新状态', 'aksrt') . '</h2>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:flex;gap:8px;align-items:center;">';
echo '<input type="hidden" name="action" value="aksrt_pay_update_order_status" />';
echo '<input type="hidden" name="order_number" value="' . esc_attr($order_number) . '" />';
echo '<input type="hidden" name="redirect_order" value="' . esc_attr($order_number) . '" />';
wp_nonce_field('aksrt_pay_update_order_status_' . $order_number);
echo '<select name="new_status">';
foreach ($labels as $key => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr($key), selected($status, $key, false), esc_html($label));
}
echo '</select> ';
submit_button(__('保存状态', 'aksrt'), 'primary', '', false);
echo '</form>';
echo '<h2 style="margin-top:24px;">' . esc_html__('物流记录', 'aksrt') . '</h2>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin:8px 0 16px;display:flex;flex-wrap:wrap;gap:8px;align-items:end;">';
echo '<input type="hidden" name="action" value="aksrt_pay_create_shipping" />';
echo '<input type="hidden" name="order_number" value="' . esc_attr($order_number) . '" />';
wp_nonce_field('aksrt_pay_create_shipping_' . $order_number);
echo '<p><label>' . esc_html__('承运商', 'aksrt') . '<br><input type="text" name="carrier" required placeholder="SF/YTO/..." /></label></p>';
echo '<p><label>' . esc_html__('运单号', 'aksrt') . '<br><input type="text" name="tracking_no" required /></label></p>';
echo '<p><label>' . esc_html__('备注', 'aksrt') . '<br><input type="text" name="note" /></label></p>';
submit_button(__('创建发货', 'aksrt'), 'secondary', '', false);
echo ' <a class="button" href="' . esc_url($this->page_url('aksrt_shipping')) . '">' . esc_html__('物流管理', 'aksrt') . '</a>';
echo '</form>';
if (!$shipping_items) {
echo '<p>' . esc_html__('暂无物流记录。', 'aksrt') . '</p>';
} else {
echo '<table class="widefat striped"><thead><tr><th>ID</th><th>' . esc_html__('承运商', 'aksrt') . '</th><th>' . esc_html__('运单号', 'aksrt') . '</th><th>' . esc_html__('状态', 'aksrt') . '</th><th>' . esc_html__('时间', 'aksrt') . '</th></tr></thead><tbody>';
foreach ($shipping_items as $item) {
echo '<tr>';
echo '<td>' . esc_html((string) ($item['id'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($item['carrier'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($item['tracking_no'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($item['status'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($item['created_at'] ?? '')) . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
}
echo '<h2 style="margin-top:24px;">' . esc_html__('售后工单', 'aksrt') . '</h2>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin:8px 0 16px;display:flex;flex-wrap:wrap;gap:8px;align-items:end;">';
echo '<input type="hidden" name="action" value="aksrt_pay_create_after_sale" />';
echo '<input type="hidden" name="order_number" value="' . esc_attr($order_number) . '" />';
echo '<input type="hidden" name="user_id" value="' . esc_attr((string) $user_id) . '" />';
wp_nonce_field('aksrt_pay_create_after_sale_' . $order_number);
echo '<p><label>' . esc_html__('类型', 'aksrt') . '<br><select name="type">';
foreach ([
'refund' => __('仅退款', 'aksrt'),
'refund_return' => __('退货退款', 'aksrt'),
'exchange' => __('换货', 'aksrt'),
'repair' => __('维修', 'aksrt'),
] as $k => $label) {
printf('<option value="%s">%s</option>', esc_attr($k), esc_html($label));
}
echo '</select></label></p>';
echo '<p><label>' . esc_html__('金额', 'aksrt') . '<br><input type="number" step="0.01" min="0" name="amount" value="' . esc_attr((string) ($order['amount'] ?? 0)) . '" /></label></p>';
echo '<p><label>' . esc_html__('原因', 'aksrt') . '<br><input type="text" name="reason" required class="regular-text" /></label></p>';
echo '<p><label>' . esc_html__('备注', 'aksrt') . '<br><input type="text" name="note" /></label></p>';
submit_button(__('创建售后', 'aksrt'), 'secondary', '', false);
echo ' <a class="button" href="' . esc_url($this->page_url('aksrt_after_sale', ['s' => $order_number])) . '">' . esc_html__('售后管理', 'aksrt') . '</a>';
echo '</form>';
if (!$after_sale) {
echo '<p>' . esc_html__('暂无售后工单。', 'aksrt') . '</p>';
} else {
echo '<table class="widefat striped"><thead><tr><th>ID</th><th>' . esc_html__('类型', 'aksrt') . '</th><th>' . esc_html__('状态', 'aksrt') . '</th><th>' . esc_html__('时间', 'aksrt') . '</th></tr></thead><tbody>';
foreach ($after_sale as $item) {
echo '<tr>';
echo '<td>' . esc_html((string) ($item['id'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($item['type'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($item['status'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($item['created_at'] ?? '')) . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
}
}
public function handle_update_order_status(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
@@ -406,9 +547,13 @@ class AKSRT_Admin_Pay_Menus {
$ok = AKSRT_Order_Repository::get_instance()->update_status($order_number, $new_status, ['force' => true]);
}
wp_safe_redirect($this->page_url('aksrt_orders', [
'aksrt_pay_msg' => $ok ? 'order_updated' : 'order_failed',
]));
$args = ['aksrt_pay_msg' => $ok ? 'order_updated' : 'order_failed'];
$redirect_order = isset($_POST['redirect_order']) ? sanitize_text_field((string) wp_unslash($_POST['redirect_order'])) : '';
if ($redirect_order !== '') {
$args['order'] = $redirect_order;
}
wp_safe_redirect($this->page_url('aksrt_orders', $args));
exit;
}
@@ -911,6 +1056,64 @@ class AKSRT_Admin_Pay_Menus {
exit;
}
public function handle_create_shipping(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
}
$order_number = isset($_POST['order_number']) ? sanitize_text_field((string) wp_unslash($_POST['order_number'])) : '';
check_admin_referer('aksrt_pay_create_shipping_' . $order_number);
$ok = false;
if ($order_number !== '' && class_exists('AKSRT_Shipping')) {
$id = AKSRT_Shipping::get_instance()->create([
'order_number' => $order_number,
'carrier' => sanitize_text_field((string) wp_unslash($_POST['carrier'] ?? '')),
'tracking_no' => sanitize_text_field((string) wp_unslash($_POST['tracking_no'] ?? '')),
'note' => sanitize_textarea_field((string) wp_unslash($_POST['note'] ?? '')),
'operator_id' => get_current_user_id(),
]);
$ok = $id > 0;
}
wp_safe_redirect($this->page_url('aksrt_orders', [
'order' => $order_number,
'aksrt_pay_msg' => $ok ? 'shipping_created' : 'shipping_create_failed',
]));
exit;
}
public function handle_create_after_sale(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
}
$order_number = isset($_POST['order_number']) ? sanitize_text_field((string) wp_unslash($_POST['order_number'])) : '';
check_admin_referer('aksrt_pay_create_after_sale_' . $order_number);
$ok = false;
if ($order_number !== '' && class_exists('AKSRT_After_Sale')) {
$user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0;
if ($user_id <= 0 && class_exists('AKSRT_Order_Repository')) {
$order = AKSRT_Order_Repository::get_instance()->get($order_number);
$user_id = (int) ($order['user_id'] ?? 0);
}
$id = AKSRT_After_Sale::get_instance()->create([
'order_number' => $order_number,
'user_id' => $user_id,
'type' => sanitize_key((string) wp_unslash($_POST['type'] ?? 'refund')),
'reason' => sanitize_textarea_field((string) wp_unslash($_POST['reason'] ?? '')),
'amount' => (float) ($_POST['amount'] ?? 0),
'note' => sanitize_textarea_field((string) wp_unslash($_POST['note'] ?? '')),
]);
$ok = $id > 0;
}
wp_safe_redirect($this->page_url('aksrt_orders', [
'order' => $order_number,
'aksrt_pay_msg' => $ok ? 'after_sale_created' : 'after_sale_create_failed',
]));
exit;
}
/* ------------------------------------------------------------------
* After-sale
* ----------------------------------------------------------------*/
@@ -32,6 +32,8 @@ class AKSRT_Admin_Users_Menus {
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_batch_invite_codes', [$this, 'handle_batch_invite_codes']);
add_action('admin_post_aksrt_users_disable_invite_code', [$this, 'handle_disable_invite_code']);
}
public function register_menus(): void {
@@ -120,15 +122,19 @@ class AKSRT_Admin_Users_Menus {
}
$code = sanitize_key((string) wp_unslash($_GET['aksrt_users_msg']));
$map = [
'message_sent' => __('消息已发送。', 'aksrt'),
'message_failed' => __('消息发送失败。', 'aksrt'),
'verify_updated' => __('认证状态已更新。', 'aksrt'),
'verify_failed' => __('认证审核失败。', 'aksrt'),
'report_updated' => __('举报状态已更新。', 'aksrt'),
'report_failed' => __('举报更新失败。', 'aksrt'),
'restriction_added' => __('限制已添加。', 'aksrt'),
'message_sent' => __('消息已发送。', 'aksrt'),
'message_failed' => __('消息发送失败。', 'aksrt'),
'verify_updated' => __('认证状态已更新。', 'aksrt'),
'verify_failed' => __('认证审核失败。', 'aksrt'),
'report_updated' => __('举报状态已更新。', 'aksrt'),
'report_failed' => __('举报更新失败。', 'aksrt'),
'restriction_added' => __('限制已添加。', 'aksrt'),
'restriction_removed' => __('限制已移除。', 'aksrt'),
'restriction_failed'=> __('限制操作失败。', 'aksrt'),
'restriction_failed' => __('限制操作失败。', 'aksrt'),
'invite_batch_ok' => __('邀请码批量生成完成。', 'aksrt'),
'invite_batch_failed' => __('邀请码批量生成失败。', 'aksrt'),
'invite_disabled' => __('邀请码已禁用。', 'aksrt'),
'invite_failed' => __('邀请码操作失败。', 'aksrt'),
];
if (!isset($map[$code])) {
return;
@@ -233,8 +239,68 @@ class AKSRT_Admin_Users_Menus {
echo '<div class="wrap"><h1>' . esc_html__('邀请码管理', 'aksrt') . '</h1>';
$this->flash();
$spa = admin_url('admin.php?page=aksrt-settings#/users/invite-codes');
echo '<p><a class="button" href="' . esc_url($spa) . '">' . esc_html__('SPA 批量生成(过渡)', 'aksrt') . '</a></p>';
echo '<h2>' . esc_html__('批量生成独立邀请码', 'aksrt') . '</h2>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin:12px 0 20px;display:flex;flex-wrap:wrap;gap:8px;align-items:end;">';
echo '<input type="hidden" name="action" value="aksrt_users_batch_invite_codes" />';
wp_nonce_field('aksrt_users_batch_invite_codes');
echo '<p><label>' . esc_html__('数量', 'aksrt') . '<br><input type="number" name="count" min="1" max="100" value="10" /></label></p>';
echo '<p><label>' . esc_html__('长度', 'aksrt') . '<br><input type="number" name="length" min="4" max="12" value="6" /></label></p>';
echo '<p><label>' . esc_html__('前缀', 'aksrt') . '<br><input type="text" name="prefix" value="" /></label></p>';
echo '<p><label>' . esc_html__('备注', 'aksrt') . '<br><input type="text" name="note" value="" class="regular-text" /></label></p>';
submit_button(__('生成', 'aksrt'), 'primary', '', false);
echo '</form>';
if (!empty($_GET['aksrt_generated'])) {
echo '<div class="notice notice-success"><p>' . esc_html(sprintf(
/* translators: %d count */
__('成功生成 %d 个邀请码。', 'aksrt'),
(int) $_GET['aksrt_generated']
)) . '</p></div>';
}
$standalone = get_option('aksrt_standalone_invite_codes', []);
$standalone = is_array($standalone) ? array_reverse(array_values($standalone)) : [];
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 (!$standalone) {
echo '<tr><td colspan="6">' . esc_html__('暂无独立邀请码', 'aksrt') . '</td></tr>';
}
$shown = 0;
foreach ($standalone as $entry) {
if ($shown >= 200) {
break;
}
$code = sanitize_key((string) ($entry['code'] ?? ''));
if ($code === '') {
continue;
}
$disabled = !empty($entry['disabled']);
$used = !empty($entry['used']) || !empty($entry['used_by']);
$status = $disabled ? __('已禁用', 'aksrt') : ($used ? __('已使用', 'aksrt') : __('可用', 'aksrt'));
echo '<tr>';
echo '<td><code>' . esc_html($code) . '</code></td>';
echo '<td>' . esc_html($status) . '</td>';
echo '<td>' . esc_html((string) ($entry['batch_id'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($entry['note'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($entry['created_at'] ?? '')) . '</td>';
echo '<td>';
if (!$disabled && !$used) {
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
echo '<input type="hidden" name="action" value="aksrt_users_disable_invite_code" />';
echo '<input type="hidden" name="code" value="' . esc_attr($code) . '" />';
wp_nonce_field('aksrt_users_disable_invite_code_' . $code);
submit_button(__('禁用', 'aksrt'), 'small', '', false);
echo '</form>';
} else {
echo '—';
}
echo '</td></tr>';
$shown++;
}
echo '</tbody></table>';
global $wpdb;
$rows = $wpdb->get_results(
@@ -269,6 +335,104 @@ class AKSRT_Admin_Users_Menus {
echo '</tbody></table></div>';
}
public function handle_batch_invite_codes(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
}
check_admin_referer('aksrt_users_batch_invite_codes');
$count = isset($_POST['count']) ? max(1, min(100, (int) $_POST['count'])) : 10;
$length = isset($_POST['length']) ? max(4, min(12, (int) $_POST['length'])) : 6;
$prefix = isset($_POST['prefix']) ? sanitize_key((string) wp_unslash($_POST['prefix'])) : '';
$note = isset($_POST['note']) ? sanitize_textarea_field((string) wp_unslash($_POST['note'])) : '';
$batch = 'INV-' . gmdate('YmdHis') . '-' . wp_generate_password(4, false, false);
$alphabet = 'abcdefghjkmnpqrstuvwxyz23456789';
$codes = get_option('aksrt_standalone_invite_codes', []);
$codes = is_array($codes) ? array_values($codes) : [];
$existing = [];
foreach ($codes as $entry) {
$c = sanitize_key((string) ($entry['code'] ?? ''));
if ($c !== '') {
$existing[$c] = true;
}
}
global $wpdb;
$generated = [];
for ($i = 0; $i < $count; $i++) {
$code = '';
for ($attempt = 0; $attempt < 20; $attempt++) {
$candidate = $prefix;
for ($j = 0; $j < $length; $j++) {
$candidate .= $alphabet[random_int(0, strlen($alphabet) - 1)];
}
$candidate = sanitize_key($candidate);
if ($candidate === '' || isset($existing[$candidate])) {
continue;
}
$user_hit = $wpdb->get_var($wpdb->prepare(
"SELECT umeta_id FROM {$wpdb->usermeta} WHERE meta_key = 'aksrt_invite_code' AND meta_value = %s LIMIT 1",
$candidate
));
if ($user_hit) {
continue;
}
$code = $candidate;
break;
}
if ($code === '') {
continue;
}
$existing[$code] = true;
$codes[] = [
'code' => $code,
'created_at' => current_time('mysql'),
'used' => false,
'used_by' => 0,
'used_at' => '',
'disabled' => false,
'note' => $note,
'batch_id' => $batch,
'created_by' => get_current_user_id(),
];
$generated[] = $code;
}
update_option('aksrt_standalone_invite_codes', array_values($codes), false);
$ok = !empty($generated);
$this->redirect('aksrt_invite_codes', $ok ? 'invite_batch_ok' : 'invite_batch_failed', [
'aksrt_generated' => count($generated),
]);
}
public function handle_disable_invite_code(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
}
$code = isset($_POST['code']) ? sanitize_key((string) wp_unslash($_POST['code'])) : '';
check_admin_referer('aksrt_users_disable_invite_code_' . $code);
$ok = false;
if ($code !== '') {
$codes = get_option('aksrt_standalone_invite_codes', []);
$codes = is_array($codes) ? $codes : [];
foreach ($codes as $i => $entry) {
if (sanitize_key((string) ($entry['code'] ?? '')) !== $code) {
continue;
}
$entry['disabled'] = true;
$codes[$i] = $entry;
$ok = true;
break;
}
if ($ok) {
update_option('aksrt_standalone_invite_codes', array_values($codes), false);
}
}
$this->redirect('aksrt_invite_codes', $ok ? 'invite_disabled' : 'invite_failed');
}
/* ------------------------------------------------------------------
* Verify
* ----------------------------------------------------------------*/
@@ -45,6 +45,12 @@ class AKSRT_Shop_Term_Meta {
add_action('aksrt_product_cat_edit_form_fields', [$this, 'render_cat_edit_fields'], 10, 1);
add_action('created_aksrt_product_cat', [$this, 'save_cat_term_fields']);
add_action('edited_aksrt_product_cat', [$this, 'save_cat_term_fields']);
// Product tag display fields.
add_action('aksrt_product_tag_add_form_fields', [$this, 'render_tag_add_fields']);
add_action('aksrt_product_tag_edit_form_fields', [$this, 'render_tag_edit_fields'], 10, 1);
add_action('created_aksrt_product_tag', [$this, 'save_tag_term_fields']);
add_action('edited_aksrt_product_tag', [$this, 'save_tag_term_fields']);
}
// ─── Taxonomy Registration ───────────────────────────────────
@@ -677,4 +683,69 @@ class AKSRT_Shop_Term_Meta {
update_term_meta($term_id, $key, $value);
}
}
public function render_tag_add_fields(): void {
foreach ($this->get_tag_field_definitions() as $key => $def) {
echo '<div class="form-field">';
echo '<label for="' . esc_attr($key) . '">' . esc_html((string) ($def['description'] ?? $key)) . '</label>';
$this->render_tag_field_control($key, $def['default'] ?? '');
echo '</div>';
}
}
public function render_tag_edit_fields($term): void {
if (!($term instanceof WP_Term)) {
return;
}
foreach ($this->get_tag_field_definitions() as $key => $def) {
$value = get_term_meta($term->term_id, $key, true);
if ($value === '' || $value === false) {
$value = $def['default'] ?? '';
}
echo '<tr class="form-field"><th scope="row"><label for="' . esc_attr($key) . '">' . esc_html((string) ($def['description'] ?? $key)) . '</label></th><td>';
$this->render_tag_field_control($key, $value);
echo '</td></tr>';
}
}
/**
* @param mixed $value
*/
private function render_tag_field_control(string $key, $value): void {
$val = (string) $value;
if ($key === 'shop_tag_is_important') {
echo '<label><input type="checkbox" name="' . esc_attr($key) . '" value="1" ' . checked($val, '1', false) . '> ' . esc_html__('启用', 'aksrt') . '</label>';
return;
}
$type = $key === 'shop_tag_priority' ? 'number' : 'text';
printf(
'<input type="%s" class="regular-text" name="%s" id="%s" value="%s" />',
esc_attr($type),
esc_attr($key),
esc_attr($key),
esc_attr($val)
);
}
public function save_tag_term_fields(int $term_id): void {
if (!current_user_can('manage_categories')) {
return;
}
foreach ($this->get_tag_field_definitions() as $key => $def) {
if ($key === 'shop_tag_is_important') {
$raw = isset($_POST[$key]) ? '1' : '';
} else {
if (!isset($_POST[$key])) {
continue;
}
$raw = wp_unslash($_POST[$key]);
}
if (isset($def['sanitize']) && is_callable($def['sanitize'])) {
$value = call_user_func($def['sanitize'], $raw);
} else {
$value = sanitize_text_field((string) $raw);
}
update_term_meta($term_id, $key, $value);
}
}
}
@@ -0,0 +1,107 @@
<?php
/**
* Native list-table columns for aksrt_forum_plate.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Plate_Admin_List {
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
if (!is_admin()) {
return;
}
add_filter('manage_edit-aksrt_forum_plate_columns', [$this, 'columns']);
add_action('manage_aksrt_forum_plate_posts_custom_column', [$this, 'render_column'], 10, 2);
add_filter('manage_edit-aksrt_forum_plate_sortable_columns', [$this, 'sortable_columns']);
add_action('pre_get_posts', [$this, 'apply_orderby']);
}
public function columns(array $columns): array {
$new = [];
foreach ($columns as $key => $label) {
$new[$key] = $label;
if ($key === 'title') {
$new['aksrt_plate_mods'] = __('版主数', 'aksrt');
$new['aksrt_plate_posts'] = __('帖子数', 'aksrt');
$new['aksrt_plate_reply'] = __('回复数', 'aksrt');
$new['aksrt_plate_views'] = __('浏览', 'aksrt');
$new['aksrt_plate_follow'] = __('关注', 'aksrt');
$new['aksrt_plate_pending_apps'] = __('待审申请', 'aksrt');
}
}
return $new;
}
public function sortable_columns(array $columns): array {
$columns['aksrt_plate_posts'] = 'aksrt_posts_count';
$columns['aksrt_plate_views'] = 'aksrt_plate_views';
return $columns;
}
public function render_column(string $column, int $post_id): void {
switch ($column) {
case 'aksrt_plate_mods':
$mods = get_post_meta($post_id, '_aksrt_moderators', true);
$count = is_array($mods) ? count(array_filter(array_map('intval', $mods))) : 0;
echo esc_html((string) $count);
break;
case 'aksrt_plate_posts':
echo esc_html((string) (int) get_post_meta($post_id, '_aksrt_posts_count', true));
break;
case 'aksrt_plate_reply':
echo esc_html((string) (int) get_post_meta($post_id, '_aksrt_reply_count', true));
break;
case 'aksrt_plate_views':
echo esc_html((string) (int) get_post_meta($post_id, '_aksrt_plate_views', true));
break;
case 'aksrt_plate_follow':
echo esc_html((string) (int) get_post_meta($post_id, '_aksrt_follow_count', true));
break;
case 'aksrt_plate_pending_apps':
$apps = get_post_meta($post_id, '_aksrt_moderator_applications', true);
$pending = 0;
if (is_array($apps)) {
foreach ($apps as $app) {
if (is_array($app) && (($app['status'] ?? '') === 'pending')) {
$pending++;
}
}
}
if ($pending > 0) {
echo '<strong style="color:#b32d2e;">' . esc_html((string) $pending) . '</strong>';
} else {
echo '0';
}
break;
}
}
public function apply_orderby(WP_Query $query): void {
if (!is_admin() || !$query->is_main_query()) {
return;
}
if ($query->get('post_type') !== 'aksrt_forum_plate') {
return;
}
$orderby = $query->get('orderby');
if ($orderby === 'aksrt_posts_count') {
$query->set('meta_key', '_aksrt_posts_count');
$query->set('orderby', 'meta_value_num');
} elseif ($orderby === 'aksrt_plate_views') {
$query->set('meta_key', '_aksrt_plate_views');
$query->set('orderby', 'meta_value_num');
}
}
}