进行了一些小优化
This commit is contained in:
@@ -281,7 +281,7 @@ if (!function_exists('aksrt_render_admin_avatar_field')) {
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row"><?php esc_html_e('当前头像', 'aksrt'); ?></th>
|
<th scope="row"><?php esc_html_e('当前头像', 'aksrt'); ?></th>
|
||||||
<td>
|
<td>
|
||||||
<?php echo aksrt_get_avatar_html($user->ID, 96, ['loading' => 'eager']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
<?php echo get_avatar($user->ID, 96, '', $user->display_name, ['loading' => 'eager']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
<p><input type="file" name="aksrt_avatar_file" accept="image/jpeg,image/png,image/webp,image/gif"></p>
|
<p><input type="file" name="aksrt_avatar_file" accept="image/jpeg,image/png,image/webp,image/gif"></p>
|
||||||
<?php if (aksrt_get_custom_avatar_url((int) $user->ID, 96) !== '') : ?>
|
<?php if (aksrt_get_custom_avatar_url((int) $user->ID, 96) !== '') : ?>
|
||||||
<label><input type="checkbox" name="aksrt_avatar_reset" value="1"> <?php esc_html_e('恢复项目默认头像并删除当前头像文件', 'aksrt'); ?></label>
|
<label><input type="checkbox" name="aksrt_avatar_reset" value="1"> <?php esc_html_e('恢复项目默认头像并删除当前头像文件', 'aksrt'); ?></label>
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ trait UsersMenusVerify {
|
|||||||
$application = (new \AKSRT\Identity\ApplicationRepository())->find_by_uuid($uuid);
|
$application = (new \AKSRT\Identity\ApplicationRepository())->find_by_uuid($uuid);
|
||||||
$result = (!$application || (int) $application['user_id'] !== $user_id || (string) $application['status'] !== $expected)
|
$result = (!$application || (int) $application['user_id'] !== $user_id || (string) $application['status'] !== $expected)
|
||||||
? new \WP_Error('aksrt_verify_conflict', __('认证状态已变化,请刷新后重试', 'aksrt'))
|
? new \WP_Error('aksrt_verify_conflict', __('认证状态已变化,请刷新后重试', 'aksrt'))
|
||||||
: \AKSRT\User\AuthApply::review($user_id, $status, $note, get_current_user_id());
|
: \AKSRT\User\AuthApply::review($user_id, $uuid, $status, $note, get_current_user_id(), []);
|
||||||
$extra = ['status' => $status === 'cancelled' ? 'cancelled' : 'pending'];
|
$extra = ['status' => $status === 'cancelled' ? 'cancelled' : 'pending'];
|
||||||
if (is_wp_error($result)) $extra['aksrt_verify_error'] = $result->get_error_message();
|
if (is_wp_error($result)) $extra['aksrt_verify_error'] = $result->get_error_message();
|
||||||
$this->redirect('aksrt_user_auth', is_wp_error($result) ? 'verify_failed' : 'verify_updated', $extra);
|
$this->redirect('aksrt_user_auth', is_wp_error($result) ? 'verify_failed' : 'verify_updated', $extra);
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ final class TransitionService {
|
|||||||
return $updated;
|
return $updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return array<int,string> */
|
||||||
|
public static function allowed_targets(string $status): array {
|
||||||
|
return self::ALLOWED[sanitize_key($status)] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply an identity transition inside an already-owned database transaction.
|
* Apply an identity transition inside an already-owned database transaction.
|
||||||
* The caller must lock the application row before invoking this method.
|
* The caller must lock the application row before invoking this method.
|
||||||
|
|||||||
+181
-11
@@ -29,6 +29,9 @@ class AuthApply {
|
|||||||
add_filter('manage_users_custom_column', [$this, 'render_admin_user_column'], 10, 3);
|
add_filter('manage_users_custom_column', [$this, 'render_admin_user_column'], 10, 3);
|
||||||
add_action('show_user_profile', [$this, 'render_admin_profile_status']);
|
add_action('show_user_profile', [$this, 'render_admin_profile_status']);
|
||||||
add_action('edit_user_profile', [$this, 'render_admin_profile_status']);
|
add_action('edit_user_profile', [$this, 'render_admin_profile_status']);
|
||||||
|
add_action('personal_options_update', [$this, 'handle_admin_profile_update']);
|
||||||
|
add_action('edit_user_profile_update', [$this, 'handle_admin_profile_update']);
|
||||||
|
add_action('admin_notices', [$this, 'render_admin_profile_notice']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,10 +246,12 @@ class AuthApply {
|
|||||||
return $history;
|
return $history;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function review(int $user_id, string $status, string $note, int $reviewer_id) {
|
/** @param array<string,string> $profile_fields */
|
||||||
|
public static function review(int $user_id, string $application_uuid, string $status, string $note, int $reviewer_id, array $profile_fields) {
|
||||||
$repo = new ApplicationRepository();
|
$repo = new ApplicationRepository();
|
||||||
$application = $repo->latest_for_user($user_id);
|
$application_uuid = sanitize_text_field($application_uuid);
|
||||||
if (!$application) {
|
$application = wp_is_uuid($application_uuid) ? $repo->find_by_uuid($application_uuid) : null;
|
||||||
|
if (!$application || (int) $application['user_id'] !== $user_id) {
|
||||||
return new \WP_Error('aksrt_verify_missing', __('认证申请不存在', 'aksrt'));
|
return new \WP_Error('aksrt_verify_missing', __('认证申请不存在', 'aksrt'));
|
||||||
}
|
}
|
||||||
$status = sanitize_key($status);
|
$status = sanitize_key($status);
|
||||||
@@ -257,10 +262,19 @@ class AuthApply {
|
|||||||
if (in_array($status, ['rejected', 'cancelled'], true) && $note === '') {
|
if (in_array($status, ['rejected', 'cancelled'], true) && $note === '') {
|
||||||
return new \WP_Error('aksrt_verify_note', __('请填写处理原因', 'aksrt'));
|
return new \WP_Error('aksrt_verify_note', __('请填写处理原因', 'aksrt'));
|
||||||
}
|
}
|
||||||
$result = (new TransitionService($repo))->transition($application, $status, [
|
$fields = [];
|
||||||
'review_note' => $note,
|
foreach (['public_name', 'public_desc'] as $key) {
|
||||||
'reviewer_id' => $reviewer_id,
|
if (array_key_exists($key, $profile_fields)) {
|
||||||
], ['source' => 'admin', 'event_type' => 'manual_review', 'actor_user_id' => $reviewer_id]);
|
$fields[$key] = (string) $profile_fields[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fields['review_note'] = $note;
|
||||||
|
$fields['reviewer_id'] = $reviewer_id;
|
||||||
|
$result = (new TransitionService($repo))->transition($application, $status, $fields, [
|
||||||
|
'source' => 'admin',
|
||||||
|
'event_type' => 'manual_review',
|
||||||
|
'actor_user_id' => $reviewer_id,
|
||||||
|
]);
|
||||||
return is_wp_error($result) ? $result : ['status' => $status, 'reviewed_at' => (string) $result['completed_at']];
|
return is_wp_error($result) ? $result : ['status' => $status, 'reviewed_at' => (string) $result['completed_at']];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,14 +293,170 @@ class AuthApply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function render_admin_profile_status(\WP_User $user): void {
|
public function render_admin_profile_status(\WP_User $user): void {
|
||||||
$status = self::get_status((int) $user->ID);
|
$user_id = (int) $user->ID;
|
||||||
$info = self::get_auth_info((int) $user->ID);
|
$snapshot = self::snapshot($user_id);
|
||||||
|
$application = is_array($snapshot['verified'] ?? null)
|
||||||
|
? $snapshot['verified']
|
||||||
|
: (is_array($snapshot['latest'] ?? null) ? $snapshot['latest'] : null);
|
||||||
|
$raw_status = (string) ($application['status'] ?? '');
|
||||||
|
$can_manage = $application !== null
|
||||||
|
&& current_user_can('aksrt_review_identity')
|
||||||
|
&& current_user_can('edit_user', $user_id);
|
||||||
|
$allowed_actions = array_values(array_intersect(
|
||||||
|
TransitionService::allowed_targets($raw_status),
|
||||||
|
['verified', 'rejected', 'cancelled']
|
||||||
|
));
|
||||||
|
$public_name = (string) ($application['public_name'] ?? '');
|
||||||
|
$public_desc = (string) ($application['public_desc'] ?? '');
|
||||||
?>
|
?>
|
||||||
<h2><?php esc_html_e('身份认证(AKSRT)', 'aksrt'); ?></h2>
|
<h2><?php esc_html_e('身份认证(AKSRT)', 'aksrt'); ?></h2>
|
||||||
<table class="form-table" role="presentation">
|
<table class="form-table" role="presentation">
|
||||||
<tr><th><?php esc_html_e('认证状态', 'aksrt'); ?></th><td><?php echo esc_html($status ?: __('未认证', 'aksrt')); ?></td></tr>
|
<tr>
|
||||||
<?php if (!empty($info['name'])) : ?><tr><th><?php esc_html_e('认证身份', 'aksrt'); ?></th><td><?php echo esc_html((string) $info['name']); ?></td></tr><?php endif; ?>
|
<th><?php esc_html_e('认证状态', 'aksrt'); ?></th>
|
||||||
|
<td><strong><?php echo esc_html($raw_status === '' ? __('未认证', 'aksrt') : self::admin_status_label($raw_status)); ?></strong></td>
|
||||||
|
</tr>
|
||||||
|
<?php if ($application !== null && $can_manage) : ?>
|
||||||
|
<tr>
|
||||||
|
<th><label for="aksrt_identity_public_name"><?php esc_html_e('认证身份', 'aksrt'); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<input type="text" class="regular-text" id="aksrt_identity_public_name" name="aksrt_identity_public_name" maxlength="64" value="<?php echo esc_attr($public_name); ?>">
|
||||||
|
<input type="hidden" name="aksrt_identity_application_uuid" value="<?php echo esc_attr((string) $application['application_uuid']); ?>">
|
||||||
|
<?php wp_nonce_field('aksrt_identity_profile_' . $user_id, 'aksrt_identity_profile_nonce'); ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="aksrt_identity_public_desc"><?php esc_html_e('认证公开说明', 'aksrt'); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<textarea class="large-text" id="aksrt_identity_public_desc" name="aksrt_identity_public_desc" rows="3" maxlength="300"><?php echo esc_textarea($public_desc); ?></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php if ($allowed_actions !== []) : ?>
|
||||||
|
<tr>
|
||||||
|
<th><label for="aksrt_identity_action"><?php esc_html_e('更改认证状态', 'aksrt'); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<select id="aksrt_identity_action" name="aksrt_identity_action">
|
||||||
|
<option value=""><?php esc_html_e('保持当前状态', 'aksrt'); ?></option>
|
||||||
|
<?php foreach ($allowed_actions as $action) : ?>
|
||||||
|
<option value="<?php echo esc_attr($action); ?>"><?php echo esc_html(self::admin_action_label($action)); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<p><label for="aksrt_identity_review_note"><?php esc_html_e('处理说明', 'aksrt'); ?></label></p>
|
||||||
|
<textarea class="large-text" id="aksrt_identity_review_note" name="aksrt_identity_review_note" rows="2"></textarea>
|
||||||
|
<p class="description"><?php esc_html_e('驳回或取消认证时必须填写处理说明。', 'aksrt'); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php elseif ($application !== null) : ?>
|
||||||
|
<?php if ($public_name !== '') : ?><tr><th><?php esc_html_e('认证身份', 'aksrt'); ?></th><td><?php echo esc_html($public_name); ?></td></tr><?php endif; ?>
|
||||||
|
<?php if ($public_desc !== '') : ?><tr><th><?php esc_html_e('认证公开说明', 'aksrt'); ?></th><td><?php echo nl2br(esc_html($public_desc)); ?></td></tr><?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
</table>
|
</table>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function handle_admin_profile_update(int $user_id): void {
|
||||||
|
if (
|
||||||
|
!current_user_can('aksrt_review_identity')
|
||||||
|
|| !current_user_can('edit_user', $user_id)
|
||||||
|
|| !isset($_POST['aksrt_identity_profile_nonce'])
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$nonce = sanitize_text_field((string) wp_unslash($_POST['aksrt_identity_profile_nonce']));
|
||||||
|
if (!wp_verify_nonce($nonce, 'aksrt_identity_profile_' . $user_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$repo = new ApplicationRepository();
|
||||||
|
$snapshot = $repo->snapshot_for_user($user_id);
|
||||||
|
$application = is_array($snapshot['verified'] ?? null)
|
||||||
|
? $snapshot['verified']
|
||||||
|
: (is_array($snapshot['latest'] ?? null) ? $snapshot['latest'] : null);
|
||||||
|
if ($application === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$application_uuid = sanitize_text_field((string) wp_unslash($_POST['aksrt_identity_application_uuid'] ?? ''));
|
||||||
|
if ($application_uuid === '' || !hash_equals((string) $application['application_uuid'], $application_uuid)) {
|
||||||
|
$this->set_admin_profile_notice('error', __('身份认证记录已变化,请刷新后重试。', 'aksrt'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$public_name = wp_html_excerpt(sanitize_text_field((string) wp_unslash($_POST['aksrt_identity_public_name'] ?? '')), 64, '');
|
||||||
|
$public_desc = wp_html_excerpt(sanitize_textarea_field((string) wp_unslash($_POST['aksrt_identity_public_desc'] ?? '')), 300, '');
|
||||||
|
$action = sanitize_key((string) wp_unslash($_POST['aksrt_identity_action'] ?? ''));
|
||||||
|
$note = sanitize_textarea_field((string) wp_unslash($_POST['aksrt_identity_review_note'] ?? ''));
|
||||||
|
$fields = [
|
||||||
|
'public_name' => $public_name,
|
||||||
|
'public_desc' => $public_desc,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($action === '') {
|
||||||
|
$updated = $repo->update((int) $application['id'], $fields);
|
||||||
|
$this->set_admin_profile_notice($updated ? 'success' : 'error', $updated ? __('身份认证资料已更新。', 'aksrt') : __('身份认证资料更新失败。', 'aksrt'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowed = TransitionService::allowed_targets((string) $application['status']);
|
||||||
|
if (!in_array($action, $allowed, true) || !in_array($action, ['verified', 'rejected', 'cancelled'], true)) {
|
||||||
|
$this->set_admin_profile_notice('error', __('当前认证状态不能执行该操作。', 'aksrt'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($action === 'verified' && $public_name === '') {
|
||||||
|
$this->set_admin_profile_notice('error', __('通过认证时请填写认证身份。', 'aksrt'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (in_array($action, ['rejected', 'cancelled'], true) && $note === '') {
|
||||||
|
$this->set_admin_profile_notice('error', __('驳回或取消认证时请填写处理说明。', 'aksrt'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = self::review($user_id, $application_uuid, $action, $note, get_current_user_id(), $fields);
|
||||||
|
$this->set_admin_profile_notice(
|
||||||
|
is_wp_error($result) ? 'error' : 'success',
|
||||||
|
is_wp_error($result) ? $result->get_error_message() : __('身份认证已更新。', 'aksrt')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render_admin_profile_notice(): void {
|
||||||
|
$key = 'aksrt_identity_profile_notice_' . get_current_user_id();
|
||||||
|
$notice = get_transient($key);
|
||||||
|
if (!is_array($notice) || empty($notice['message'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete_transient($key);
|
||||||
|
$type = ($notice['type'] ?? '') === 'success' ? 'success' : 'error';
|
||||||
|
echo '<div class="notice notice-' . esc_attr($type) . ' is-dismissible"><p>' . esc_html((string) $notice['message']) . '</p></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function set_admin_profile_notice(string $type, string $message): void {
|
||||||
|
set_transient('aksrt_identity_profile_notice_' . get_current_user_id(), [
|
||||||
|
'type' => $type,
|
||||||
|
'message' => $message,
|
||||||
|
], 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function admin_status_label(string $status): string {
|
||||||
|
$labels = [
|
||||||
|
'created' => __('已创建', 'aksrt'),
|
||||||
|
'provider_pending' => __('等待核身', 'aksrt'),
|
||||||
|
'provider_processing' => __('云端处理中', 'aksrt'),
|
||||||
|
'provider_failed' => __('云认证失败', 'aksrt'),
|
||||||
|
'provider_unsupported' => __('云端不支持', 'aksrt'),
|
||||||
|
'provider_expired' => __('会话已过期', 'aksrt'),
|
||||||
|
'manual_pending' => __('人工待审', 'aksrt'),
|
||||||
|
'verified' => __('已认证', 'aksrt'),
|
||||||
|
'rejected' => __('已驳回', 'aksrt'),
|
||||||
|
'cancelled' => __('已取消', 'aksrt'),
|
||||||
|
];
|
||||||
|
return $labels[$status] ?? $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function admin_action_label(string $action): string {
|
||||||
|
$labels = [
|
||||||
|
'verified' => __('通过认证', 'aksrt'),
|
||||||
|
'rejected' => __('驳回申请', 'aksrt'),
|
||||||
|
'cancelled' => __('取消认证', 'aksrt'),
|
||||||
|
];
|
||||||
|
return $labels[$action] ?? $action;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ class BalanceAdmin {
|
|||||||
}
|
}
|
||||||
$balance_minor = \AKSRT\Ledger\LedgerService::get_instance()->balance((int) $user->ID, \AKSRT\Ledger\AssetType::BALANCE);
|
$balance_minor = \AKSRT\Ledger\LedgerService::get_instance()->balance((int) $user->ID, \AKSRT\Ledger\AssetType::BALANCE);
|
||||||
$can = $this->can_manage();
|
$can = $this->can_manage();
|
||||||
$log = \AKSRT\Ledger\LedgerQuery::get_instance()->history(['user_id' => (int) $user->ID, 'asset' => 'balance', 'page' => 1, 'per_page' => 8]);
|
|
||||||
?>
|
?>
|
||||||
<a id="aksrt-user-wallet"></a>
|
<a id="aksrt-user-wallet"></a>
|
||||||
<h2><?php esc_html_e('现金钱包(AKSRT)', 'aksrt'); ?></h2>
|
<h2><?php esc_html_e('现金钱包(AKSRT)', 'aksrt'); ?></h2>
|
||||||
@@ -105,28 +104,6 @@ class BalanceAdmin {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<tr>
|
|
||||||
<th><?php esc_html_e('近期流水', 'aksrt'); ?></th>
|
|
||||||
<td>
|
|
||||||
<?php if (!empty($log['items'])) : ?>
|
|
||||||
<table class="widefat striped" style="max-width:640px;">
|
|
||||||
<thead><tr><th><?php esc_html_e('时间', 'aksrt'); ?></th><th><?php esc_html_e('变动', 'aksrt'); ?></th><th><?php esc_html_e('余额', 'aksrt'); ?></th><th><?php esc_html_e('原因', 'aksrt'); ?></th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($log['items'] as $row) : ?>
|
|
||||||
<tr>
|
|
||||||
<td><?php echo esc_html(get_date_from_gmt((string) ($row['created_at'] ?? ''), 'Y-m-d H:i')); ?></td>
|
|
||||||
<td><?php echo esc_html(((int) $row['delta_minor'] > 0 ? '+' : '-') . \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, abs((int) $row['delta_minor']))->formatted()); ?></td>
|
|
||||||
<td><?php echo esc_html(\AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, (int) ($row['balance_after_minor'] ?? 0))->formatted()); ?></td>
|
|
||||||
<td><?php echo esc_html(\AKSRT\Ledger\LedgerPresentation::reason_label((string) ($row['reason'] ?? ''))); ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<?php else : ?>
|
|
||||||
<p><?php esc_html_e('暂无流水', 'aksrt'); ?></p>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,45 +200,7 @@ class CreditsAdmin {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// ========== 近期积分流水 ==========
|
|
||||||
$log = \AKSRT\Ledger\LedgerQuery::get_instance()->history(['user_id' => (int) $user->ID, 'asset' => 'points', 'page' => 1, 'per_page' => 20]);
|
|
||||||
$items = $log['items'] ?? [];
|
|
||||||
?>
|
|
||||||
<h3 style="margin-top:24px;"><?php echo esc_html(sprintf(__('近期%s流水', 'aksrt'), $currency_name)); ?></h3>
|
|
||||||
<?php if (!empty($items)) : ?>
|
|
||||||
<table class="widefat striped" style="max-width:800px;">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?php esc_html_e('时间', 'aksrt'); ?></th>
|
|
||||||
<th><?php esc_html_e('类型', 'aksrt'); ?></th>
|
|
||||||
<th><?php esc_html_e('备注', 'aksrt'); ?></th>
|
|
||||||
<th><?php esc_html_e('变动', 'aksrt'); ?></th>
|
|
||||||
<th><?php esc_html_e('余额', 'aksrt'); ?></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($items as $row) :
|
|
||||||
$delta = (int) $row['delta_minor'];
|
|
||||||
$reason_key = (string) ($row['reason'] ?? '');
|
|
||||||
$label = \AKSRT\Ledger\LedgerPresentation::reason_label($reason_key);
|
|
||||||
?>
|
|
||||||
<tr>
|
|
||||||
<td><?php echo esc_html(get_date_from_gmt((string) $row['created_at'], 'Y-m-d H:i')); ?></td>
|
|
||||||
<td><?php echo esc_html($label); ?></td>
|
|
||||||
<td><?php echo esc_html((string) ($row['remark'] ?? '')); ?></td>
|
|
||||||
<td style="color:<?php echo $delta >= 0 ? '#16a34a' : '#ef4444'; ?>; font-weight:600;">
|
|
||||||
<?php echo esc_html(($delta > 0 ? '+' : '') . $delta); ?>
|
|
||||||
</td>
|
|
||||||
<td><?php echo esc_html((string) (int) $row['balance_after_minor']); ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<?php else : ?>
|
|
||||||
<p style="color:#888;"><?php echo esc_html(sprintf(__('暂无%s流水记录。', 'aksrt'), $currency_name)); ?></p>
|
|
||||||
<?php endif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 处理积分调整 ==========
|
// ========== 处理积分调整 ==========
|
||||||
|
|||||||
+11
-2
@@ -70,7 +70,7 @@ class LevelAdmin {
|
|||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
$level = Level::get_level($user_id);
|
$level = Level::get_level($user_id);
|
||||||
$icon = Level::get_badge_html($user_id, 'aksrt-user-level-icon--admin');
|
$icon = $this->render_badge($level);
|
||||||
return '<span class="aksrt-user-level-admin-cell">'
|
return '<span class="aksrt-user-level-admin-cell">'
|
||||||
. '<span class="aksrt-user-level-admin-cell__level">' . $icon
|
. '<span class="aksrt-user-level-admin-cell__level">' . $icon
|
||||||
. '<strong>' . esc_html(Level::get_level_name($level)) . '</strong></span>'
|
. '<strong>' . esc_html(Level::get_level_name($level)) . '</strong></span>'
|
||||||
@@ -117,7 +117,7 @@ class LevelAdmin {
|
|||||||
<th><?php esc_html_e('当前等级', 'aksrt'); ?></th>
|
<th><?php esc_html_e('当前等级', 'aksrt'); ?></th>
|
||||||
<td>
|
<td>
|
||||||
<span style="display:inline-flex;align-items:center;gap:8px;font-size:16px;">
|
<span style="display:inline-flex;align-items:center;gap:8px;font-size:16px;">
|
||||||
<?php echo Level::get_badge_html((int) $user->ID, 'aksrt-user-level-icon--admin'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
<?php echo $this->render_badge((int) $progress['level']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
<strong><?php echo esc_html((string) $progress['name']); ?></strong>
|
<strong><?php echo esc_html((string) $progress['name']); ?></strong>
|
||||||
</span>
|
</span>
|
||||||
<p class="description"><?php printf(esc_html__('累计经验:%1$d;距离下一等级:%2$d', 'aksrt'), (int) $progress['integral'], (int) $progress['remaining']); ?></p>
|
<p class="description"><?php printf(esc_html__('累计经验:%1$d;距离下一等级:%2$d', 'aksrt'), (int) $progress['integral'], (int) $progress['remaining']); ?></p>
|
||||||
@@ -156,4 +156,13 @@ class LevelAdmin {
|
|||||||
update_user_meta($user_id, Level::META_INTEGRAL, $integral);
|
update_user_meta($user_id, Level::META_INTEGRAL, $integral);
|
||||||
update_user_meta($user_id, Level::META_LEVEL, $level);
|
update_user_meta($user_id, Level::META_LEVEL, $level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function render_badge(int $level): string {
|
||||||
|
$name = Level::get_level_name($level);
|
||||||
|
$icon = Level::get_level_icon_url($level);
|
||||||
|
if ($icon !== '') {
|
||||||
|
return '<img class="aksrt-user-level-icon aksrt-user-level-icon--admin" src="' . esc_url($icon) . '" alt="' . esc_attr($name) . '" title="' . esc_attr($name) . '" decoding="async">';
|
||||||
|
}
|
||||||
|
return '<span class="aksrt-user-level-badge aksrt-user-level-icon--admin" title="' . esc_attr($name) . '">LV' . esc_html((string) $level) . '</span>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,3 +167,36 @@ test('retired admin pages, schemas, APIs, and metadata chains are absent', () =>
|
|||||||
assert.match(read('assets/admin/src/api/modules/tools.js'), /\/settings\/health/);
|
assert.match(read('assets/admin/src/api/modules/tools.js'), /\/settings\/health/);
|
||||||
assert.match(read('assets/admin/src/constants/sections/tools.js'), /route: '\/tools\/health'/);
|
assert.match(read('assets/admin/src/constants/sections/tools.js'), /route: '\/tools\/health'/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('native user profile omits ledger tables and renders canonical images eagerly', () => {
|
||||||
|
const credits = read('src/User/CreditsAdmin.php');
|
||||||
|
const balance = read('src/User/BalanceAdmin.php');
|
||||||
|
const avatar = read('inc/helpers/aksrt-avatar.php');
|
||||||
|
const level = read('src/User/LevelAdmin.php');
|
||||||
|
|
||||||
|
assert.doesNotMatch(credits, /LedgerQuery::get_instance\(\)->history/);
|
||||||
|
assert.doesNotMatch(credits, /近期%s流水|近期积分流水/);
|
||||||
|
assert.doesNotMatch(balance, /LedgerQuery::get_instance\(\)->history/);
|
||||||
|
assert.doesNotMatch(balance, /近期流水/);
|
||||||
|
assert.match(avatar, /get_avatar\(\$user->ID, 96, '', \$user->display_name, \['loading' => 'eager'\]\)/);
|
||||||
|
assert.doesNotMatch(avatar, /aksrt_get_avatar_html\(\$user->ID, 96/);
|
||||||
|
assert.match(level, /Level::get_level_icon_url\(\$level\)/);
|
||||||
|
assert.match(level, /<img class="aksrt-user-level-icon aksrt-user-level-icon--admin" src="/);
|
||||||
|
assert.doesNotMatch(level, /Level::get_badge_html/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('identity details and governed status actions are editable on the native profile', () => {
|
||||||
|
const auth = read('src/User/AuthApply.php');
|
||||||
|
const transitions = read('src/Identity/TransitionService.php');
|
||||||
|
|
||||||
|
assert.match(auth, /add_action\('edit_user_profile_update', \[\$this, 'handle_admin_profile_update'\]\)/);
|
||||||
|
assert.match(auth, /current_user_can\('aksrt_review_identity'\)/);
|
||||||
|
assert.match(auth, /wp_nonce_field\('aksrt_identity_profile_' \.[\s\S]*?'aksrt_identity_profile_nonce'\)/);
|
||||||
|
assert.match(auth, /name="aksrt_identity_application_uuid"/);
|
||||||
|
assert.match(auth, /name="aksrt_identity_public_name"/);
|
||||||
|
assert.match(auth, /name="aksrt_identity_public_desc"/);
|
||||||
|
assert.match(auth, /name="aksrt_identity_action"/);
|
||||||
|
assert.match(auth, /TransitionService::allowed_targets/);
|
||||||
|
assert.match(auth, /self::review\(\$user_id, \$application_uuid, \$action, \$note, get_current_user_id\(\), \$fields\)/);
|
||||||
|
assert.match(transitions, /public static function allowed_targets/);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user