refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
This commit is contained in:
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Admin;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait UserControllerInviteOperations {
|
||||
|
||||
/**
|
||||
* GET /invite/codes — 邀请码列表(管理后台)
|
||||
*/
|
||||
public function get_invite_codes(\WP_REST_Request $request): \WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
||||
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
|
||||
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$source = sanitize_key((string) $request->get_param('source'));
|
||||
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
||||
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
||||
|
||||
$usermeta_table = $wpdb->usermeta;
|
||||
$invite_table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('invite') : '';
|
||||
|
||||
$invite_rows = [];
|
||||
if ($invite_table) {
|
||||
$records = $wpdb->get_results("SELECT * FROM {$invite_table}", ARRAY_A);
|
||||
foreach ((array) $records as $record) {
|
||||
$invite_rows[(string) $record['invite_code']] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$user_rows = $wpdb->get_results(
|
||||
"SELECT user_id, meta_value AS invite_code FROM {$usermeta_table} WHERE meta_key = 'aksrt_invite_code' ORDER BY umeta_id DESC",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
foreach ((array) $user_rows as $row) {
|
||||
$user_id = (int) $row['user_id'];
|
||||
$user = get_userdata($user_id);
|
||||
$code = sanitize_key((string) $row['invite_code']);
|
||||
$record = $invite_rows[$code] ?? null;
|
||||
|
||||
$item = [
|
||||
'row_key' => 'user:' . $user_id,
|
||||
'source_type' => 'user',
|
||||
'source_label' => __('用户邀请码', 'aksrt'),
|
||||
'user' => $this->build_admin_user_summary($user_id, $user),
|
||||
'invite_code' => $code,
|
||||
'status' => $record ? 'used' : 'active',
|
||||
'status_label' => $record ? __('已使用', 'aksrt') : __('可用', 'aksrt'),
|
||||
'created_at' => $user ? (string) $user->user_registered : '',
|
||||
'used_at' => $record['created_at'] ?? '',
|
||||
'note' => '',
|
||||
'batch_id' => '',
|
||||
'created_by' => null,
|
||||
'registration_status' => $record['status'] ?? '',
|
||||
'invitee' => $this->build_admin_user_summary((int) ($record['invitee_id'] ?? 0)),
|
||||
'can_bulk' => false,
|
||||
'actions' => ['disable'],
|
||||
];
|
||||
|
||||
if ($this->invite_item_matches_filters($item, $keyword, $status, $source, $date_from, $date_to)) {
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->load_standalone_invite_codes() as $entry) {
|
||||
$code = sanitize_key((string) ($entry['code'] ?? ''));
|
||||
$record = $invite_rows[$code] ?? null;
|
||||
$used_by = (int) ($entry['used_by'] ?? 0);
|
||||
$disabled = !empty($entry['disabled']);
|
||||
$used = !empty($entry['used']) || $used_by > 0 || !empty($record['invitee_id']);
|
||||
$status_key = $disabled ? 'disabled' : ($used ? 'used' : 'active');
|
||||
$status_map = [
|
||||
'active' => __('可用', 'aksrt'),
|
||||
'used' => __('已使用', 'aksrt'),
|
||||
'disabled' => __('已禁用', 'aksrt'),
|
||||
];
|
||||
$invitee_id = $used_by > 0 ? $used_by : (int) ($record['invitee_id'] ?? 0);
|
||||
|
||||
$item = [
|
||||
'row_key' => 'standalone:' . $code,
|
||||
'source_type' => 'standalone',
|
||||
'source_label' => __('独立邀请码', 'aksrt'),
|
||||
'user' => null,
|
||||
'invite_code' => $code,
|
||||
'status' => $status_key,
|
||||
'status_label' => $status_map[$status_key],
|
||||
'created_at' => (string) ($entry['created_at'] ?? ''),
|
||||
'used_at' => (string) ($entry['used_at'] ?? ($record['created_at'] ?? '')),
|
||||
'note' => (string) ($entry['note'] ?? ''),
|
||||
'batch_id' => (string) ($entry['batch_id'] ?? ''),
|
||||
'created_by' => $this->build_admin_user_summary((int) ($entry['created_by'] ?? 0)),
|
||||
'registration_status' => $record['status'] ?? '',
|
||||
'invitee' => $this->build_admin_user_summary($invitee_id),
|
||||
'can_bulk' => true,
|
||||
'actions' => ['disable', 'delete'],
|
||||
];
|
||||
|
||||
if ($this->invite_item_matches_filters($item, $keyword, $status, $source, $date_from, $date_to)) {
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
usort($items, static function ($left, $right) {
|
||||
return strcmp((string) ($right['created_at'] ?? ''), (string) ($left['created_at'] ?? ''));
|
||||
});
|
||||
|
||||
$total = count($items);
|
||||
$offset = ($page - 1) * $per_page;
|
||||
$paged = array_slice($items, $offset, $per_page);
|
||||
$stats = $this->build_invite_operation_stats($items, $invite_rows);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => array_values($paged),
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => max(1, (int) ceil(max(1, $total) / $per_page)),
|
||||
'stats' => $stats,
|
||||
'options' => [
|
||||
'statuses' => [
|
||||
['label' => __('全部状态', 'aksrt'), 'value' => ''],
|
||||
['label' => __('可用', 'aksrt'), 'value' => 'active'],
|
||||
['label' => __('已使用', 'aksrt'), 'value' => 'used'],
|
||||
['label' => __('已禁用', 'aksrt'), 'value' => 'disabled'],
|
||||
],
|
||||
'sources' => [
|
||||
['label' => __('全部来源', 'aksrt'), 'value' => ''],
|
||||
['label' => __('用户邀请码', 'aksrt'), 'value' => 'user'],
|
||||
['label' => __('独立邀请码', 'aksrt'), 'value' => 'standalone'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /invite/codes — 批量生成邀请码(管理后台)
|
||||
* Body: { count: int, length: int, prefix: string }
|
||||
* 为指定用户或批量创建邀请码。
|
||||
*/
|
||||
public function generate_invite_codes(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Credits\Invite')) {
|
||||
return $this->error(__('邀请系统未启用', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$count = max(1, min(100, (int) ($params['count'] ?? 1)));
|
||||
$length = max(4, min(12, (int) ($params['length'] ?? 6)));
|
||||
$prefix = sanitize_key((string) ($params['prefix'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
$batch_id = sanitize_text_field((string) ($params['batch_id'] ?? ''));
|
||||
|
||||
if ($batch_id === '') {
|
||||
$batch_id = 'INV-' . gmdate('YmdHis') . '-' . wp_generate_password(4, false, false);
|
||||
}
|
||||
|
||||
$generated = [];
|
||||
$codes = $this->load_standalone_invite_codes();
|
||||
$alphabet = 'abcdefghjkmnpqrstuvwxyz23456789';
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$code = '';
|
||||
for ($attempt = 0; $attempt < 12; $attempt++) {
|
||||
$candidate = $prefix;
|
||||
for ($j = 0; $j < $length; $j++) {
|
||||
$candidate .= $alphabet[random_int(0, strlen($alphabet) - 1)];
|
||||
}
|
||||
$candidate = sanitize_key($candidate);
|
||||
if (!$this->invite_code_exists_globally($candidate, $codes)) {
|
||||
$code = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($code === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$codes[] = [
|
||||
'code' => $code,
|
||||
'created_at' => current_time('mysql'),
|
||||
'used' => false,
|
||||
'used_by' => 0,
|
||||
'used_at' => '',
|
||||
'disabled' => false,
|
||||
'note' => $note,
|
||||
'batch_id' => $batch_id,
|
||||
'created_by' => get_current_user_id(),
|
||||
];
|
||||
$generated[] = $code;
|
||||
}
|
||||
|
||||
$this->save_standalone_invite_codes($codes);
|
||||
|
||||
return $this->success([
|
||||
'message' => sprintf(__('已生成 %d 个邀请码', 'aksrt'), count($generated)),
|
||||
'data' => [
|
||||
'codes' => $generated,
|
||||
'batch_id' => $batch_id,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /invite/codes/{id}/disable — 禁用邀请码
|
||||
*/
|
||||
public function disable_invite_code(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$user_id = (int) $request->get_param('id');
|
||||
|
||||
if ($user_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$removed = $this->remove_user_invite_code($user_id);
|
||||
if (!$removed) {
|
||||
return $this->error(__('邀请码不存在或已禁用', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('邀请码已禁用', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function bulk_update_invite_codes(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$action = sanitize_key((string) ($params['action'] ?? ''));
|
||||
$targets = isset($params['targets']) && is_array($params['targets']) ? $params['targets'] : [];
|
||||
|
||||
if (!in_array($action, ['disable', 'delete'], true) || empty($targets)) {
|
||||
return $this->error(__('批量操作参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$codes = $this->load_standalone_invite_codes();
|
||||
$processed = 0;
|
||||
|
||||
foreach ($targets as $target) {
|
||||
$source_type = sanitize_key((string) ($target['source_type'] ?? ''));
|
||||
if ($source_type === 'user') {
|
||||
$user_id = (int) ($target['user_id'] ?? 0);
|
||||
if ($action === 'disable' && $this->remove_user_invite_code($user_id)) {
|
||||
$processed++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($source_type !== 'standalone') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$code = sanitize_key((string) ($target['code'] ?? $target['invite_code'] ?? ''));
|
||||
foreach ($codes as $index => $entry) {
|
||||
$entry_code = sanitize_key((string) ($entry['code'] ?? ''));
|
||||
if ($entry_code !== $code) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($action === 'delete' && empty($entry['used']) && empty($entry['used_by'])) {
|
||||
unset($codes[$index]);
|
||||
} else {
|
||||
$entry['disabled'] = true;
|
||||
$codes[$index] = $entry;
|
||||
}
|
||||
$processed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->save_standalone_invite_codes($codes);
|
||||
|
||||
return $this->success([
|
||||
'message' => sprintf(__('已处理 %d 条邀请码记录', 'aksrt'), $processed),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /invite/stats — 邀请系统统计
|
||||
*/
|
||||
public function get_invite_stats(): \WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$items = [];
|
||||
$user_records = $wpdb->get_results(
|
||||
"SELECT user_id, meta_value AS invite_code FROM {$wpdb->usermeta} WHERE meta_key = 'aksrt_invite_code'",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
foreach ((array) $user_records as $row) {
|
||||
$items[] = [
|
||||
'source_type' => 'user',
|
||||
'status' => 'active',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($this->load_standalone_invite_codes() as $entry) {
|
||||
$used = !empty($entry['used']) || !empty($entry['used_by']);
|
||||
$disabled = !empty($entry['disabled']);
|
||||
$items[] = [
|
||||
'source_type' => 'standalone',
|
||||
'status' => $disabled ? 'disabled' : ($used ? 'used' : 'active'),
|
||||
];
|
||||
}
|
||||
|
||||
$stats = $this->build_invite_operation_stats($items);
|
||||
return $this->success(['data' => $stats]);
|
||||
}
|
||||
|
||||
private function load_standalone_invite_codes(): array {
|
||||
$codes = get_option('aksrt_standalone_invite_codes', []);
|
||||
return is_array($codes) ? array_values($codes) : [];
|
||||
}
|
||||
|
||||
private function save_standalone_invite_codes(array $codes): void {
|
||||
update_option('aksrt_standalone_invite_codes', array_values($codes), false);
|
||||
}
|
||||
|
||||
private function invite_code_exists_globally(string $candidate, array $standalone_codes = []): bool {
|
||||
if ($candidate === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$existing_user_code = $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 ($existing_user_code) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($standalone_codes as $entry) {
|
||||
if (sanitize_key((string) ($entry['code'] ?? '')) === $candidate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$invite_table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('invite') : '';
|
||||
if ($invite_table) {
|
||||
$existing_invite = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT id FROM {$invite_table} WHERE invite_code = %s LIMIT 1",
|
||||
$candidate
|
||||
));
|
||||
if ($existing_invite) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function invite_item_matches_filters(array $item, string $keyword, string $status, string $source, string $date_from, string $date_to): bool {
|
||||
if ($status !== '' && ($item['status'] ?? '') !== $status) {
|
||||
return false;
|
||||
}
|
||||
if ($source !== '' && ($item['source_type'] ?? '') !== $source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$created_at = (string) ($item['created_at'] ?? '');
|
||||
if ($date_from !== '' && $created_at !== '' && strcmp($created_at, $date_from . ' 00:00:00') < 0) {
|
||||
return false;
|
||||
}
|
||||
if ($date_to !== '' && $created_at !== '' && strcmp($created_at, $date_to . ' 23:59:59') > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($keyword === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$haystack = implode(' ', array_filter([
|
||||
(string) ($item['invite_code'] ?? ''),
|
||||
(string) ($item['status_label'] ?? ''),
|
||||
(string) ($item['note'] ?? ''),
|
||||
(string) ($item['batch_id'] ?? ''),
|
||||
(string) ($item['user']['display_name'] ?? ''),
|
||||
(string) ($item['invitee']['display_name'] ?? ''),
|
||||
(string) ($item['invitee']['email'] ?? ''),
|
||||
]));
|
||||
|
||||
return mb_stripos($haystack, $keyword) !== false;
|
||||
}
|
||||
|
||||
private function build_invite_operation_stats(array $items, array $invite_rows = []): array {
|
||||
$stats = [
|
||||
'total_codes' => count($items),
|
||||
'active_codes' => 0,
|
||||
'used_codes' => 0,
|
||||
'disabled_codes' => 0,
|
||||
'user_codes' => 0,
|
||||
'standalone_codes' => 0,
|
||||
'total_invites' => count($invite_rows),
|
||||
'total_rewarded' => 0,
|
||||
];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$status = $item['status'] ?? '';
|
||||
if ($status === 'active') {
|
||||
$stats['active_codes']++;
|
||||
} elseif ($status === 'used') {
|
||||
$stats['used_codes']++;
|
||||
} elseif ($status === 'disabled') {
|
||||
$stats['disabled_codes']++;
|
||||
}
|
||||
|
||||
if (($item['source_type'] ?? '') === 'user') {
|
||||
$stats['user_codes']++;
|
||||
} elseif (($item['source_type'] ?? '') === 'standalone') {
|
||||
$stats['standalone_codes']++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($invite_rows as $row) {
|
||||
if (($row['status'] ?? '') === 'rewarded') {
|
||||
$stats['total_rewarded']++;
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function remove_user_invite_code(int $user_id): bool {
|
||||
$user_id = (int) $user_id;
|
||||
if ($user_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = get_user_meta($user_id, 'aksrt_invite_code', true);
|
||||
if (!$code) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delete_user_meta($user_id, 'aksrt_invite_code');
|
||||
|
||||
$map = get_option('aksrt_invite_code_map', []);
|
||||
if (is_array($map) && isset($map[$code])) {
|
||||
unset($map[$code]);
|
||||
update_option('aksrt_invite_code_map', $map, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user