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:
2026-08-02 22:06:06 +08:00
parent 56f1a8175f
commit 8c5b1555c9
285 changed files with 26 additions and 3600 deletions
@@ -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;
}
}