Files
wordpress-theme-aksrt/src/Admin/Native/PayDownloadLogs.php
T
kate522 8c5b1555c9 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).
2026-08-02 22:06:06 +08:00

201 lines
8.2 KiB
PHP

<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Native;
defined('ABSPATH') || exit;
class PayDownloadLogs {
public const CAPABILITY = 'manage_options';
public const PAGE_SLUG = 'aksrt_download_logs';
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 {
// Read-only.
}
public function render_page(): void {
$this->ensure_cap();
echo '<div class="wrap"><h1>' . esc_html__('下载记录', 'aksrt') . '</h1>';
if (!class_exists('\AKSRT\Core\DBMigrator')) {
echo '<div class="notice notice-error"><p>' . esc_html__('数据库服务不可用。', 'aksrt') . '</p></div></div>';
return;
}
$table = \AKSRT\Core\DBMigrator::table('dl_token');
if ($table === '') {
echo '<div class="notice notice-error"><p>' . esc_html__('下载令牌表未初始化。', 'aksrt') . '</p></div></div>';
return;
}
$post_id = isset($_GET['post_id']) ? max(0, (int) $_GET['post_id']) : 0;
$user_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0;
$only_used = !isset($_GET['used']) || (string) $_GET['used'] !== '0';
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$per_page = 30;
$result = $this->query_logs($table, [
'post_id' => $post_id,
'user_id' => $user_id,
'only_used' => $only_used,
'limit' => $per_page,
'offset' => ($page_num - 1) * $per_page,
]);
$items = $result['items'];
$total = $result['total'];
$pages = max(1, (int) ceil($total / $per_page));
echo '<p class="description">' . esc_html__('基于下载令牌表(aksrt_dl_token)。已使用记录会保留一段时间后清理;未使用且过期的令牌会被定期删除。', 'aksrt') . '</p>';
echo '<form method="get" style="margin:12px 0;">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '" />';
echo '<label>' . esc_html__('文章 ID', 'aksrt') . ' <input type="number" min="0" name="post_id" value="' . esc_attr($post_id > 0 ? (string) $post_id : '') . '" class="small-text" /></label> ';
echo '<label>' . esc_html__('用户 ID', 'aksrt') . ' <input type="number" min="0" name="user_id" value="' . esc_attr($user_id > 0 ? (string) $user_id : '') . '" class="small-text" /></label> ';
echo '<label><input type="checkbox" name="used" value="1" ' . checked($only_used, true, false) . ' /> ' . esc_html__('仅已使用', 'aksrt') . '</label> ';
// When unchecked, pass used=0 explicitly.
if (!$only_used) {
// no-op; checkbox not checked
}
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
echo ' <a class="button" href="' . esc_url(admin_url('admin.php?page=' . self::PAGE_SLUG)) . '">' . esc_html__('重置', 'aksrt') . '</a>';
echo '</form>';
// Fix: unchecked checkbox means used filter off — re-read without default if form submitted with no used.
// Already handled via isset($_GET['used']).
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr>';
echo '<th>' . esc_html__('时间', 'aksrt') . '</th>';
echo '<th>' . esc_html__('用户', 'aksrt') . '</th>';
echo '<th>' . esc_html__('文章', 'aksrt') . '</th>';
echo '<th>' . esc_html__('源序号', 'aksrt') . '</th>';
echo '<th>' . esc_html__('积分消耗', 'aksrt') . '</th>';
echo '<th>' . esc_html__('使用', 'aksrt') . '</th>';
echo '<th>' . esc_html__('过期', 'aksrt') . '</th>';
echo '<th>' . esc_html__('IP', 'aksrt') . '</th>';
echo '</tr></thead><tbody>';
if (!$items) {
echo '<tr><td colspan="8">' . esc_html__('暂无下载记录。', 'aksrt') . '</td></tr>';
}
foreach ($items as $row) {
$uid = (int) ($row['user_id'] ?? 0);
$pid = (int) ($row['post_id'] ?? 0);
$user = $uid > 0 ? get_userdata($uid) : null;
$title = $pid > 0 ? get_the_title($pid) : '';
if ($title === '') {
$title = $pid > 0 ? sprintf(__('(已删除 #%d)', 'aksrt'), $pid) : '—';
}
$edit = $pid > 0 ? get_edit_post_link($pid, 'raw') : '';
$used = (int) ($row['used_count'] ?? 0);
$max = (int) ($row['max_uses'] ?? 1);
echo '<tr>';
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
echo '<td>';
if ($user) {
echo esc_html($user->display_name . ' (#' . $uid . ')');
echo '<br><a href="' . esc_url(admin_url('admin.php?page=' . self::PAGE_SLUG . '&user_id=' . $uid)) . '">' . esc_html__('仅此用户', 'aksrt') . '</a>';
} else {
echo esc_html((string) $uid);
}
echo '</td>';
echo '<td>';
if ($edit) {
echo '<a href="' . esc_url($edit) . '">' . esc_html($title) . '</a>';
} else {
echo esc_html($title);
}
if ($pid > 0) {
echo '<br><a href="' . esc_url(admin_url('admin.php?page=' . self::PAGE_SLUG . '&post_id=' . $pid)) . '">' . esc_html__('仅此文章', 'aksrt') . '</a>';
}
echo '</td>';
echo '<td>' . esc_html((string) (int) ($row['source_index'] ?? 0)) . '</td>';
echo '<td>' . esc_html((string) (int) ($row['cost'] ?? 0)) . '</td>';
echo '<td>' . esc_html($used . ' / ' . $max) . ($used > 0 ? ' <span class="dashicons dashicons-yes" style="color:green;"></span>' : '') . '</td>';
echo '<td>' . esc_html((string) ($row['expires_at'] ?? '')) . '</td>';
echo '<td><code>' . esc_html((string) ($row['ip_bind'] ?? '')) . '</code></td>';
echo '</tr>';
}
echo '</tbody></table>';
if ($pages > 1) {
echo '<div class="tablenav"><div class="tablenav-pages">';
echo paginate_links([
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '&laquo;',
'next_text' => '&raquo;',
'total' => $pages,
'current' => $page_num,
]);
echo '</div></div>';
}
echo '</div>';
}
/**
* @param array{post_id?:int,user_id?:int,only_used?:bool,limit?:int,offset?:int} $args
* @return array{items: array<int,array<string,mixed>>, total:int}
*/
private function query_logs(string $table, array $args): array {
global $wpdb;
$where = ['1=1'];
$params = [];
$post_id = (int) ($args['post_id'] ?? 0);
if ($post_id > 0) {
$where[] = 'post_id = %d';
$params[] = $post_id;
}
$user_id = (int) ($args['user_id'] ?? 0);
if ($user_id > 0) {
$where[] = 'user_id = %d';
$params[] = $user_id;
}
if (!empty($args['only_used'])) {
$where[] = 'used_count > 0';
}
$where_sql = implode(' AND ', $where);
$limit = max(1, min(100, (int) ($args['limit'] ?? 30)));
$offset = max(0, (int) ($args['offset'] ?? 0));
$count_sql = "SELECT COUNT(*) FROM {$table} WHERE {$where_sql}";
$total = $params
? (int) $wpdb->get_var($wpdb->prepare($count_sql, $params))
: (int) $wpdb->get_var($count_sql);
$sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d";
$list_params = $params;
$list_params[] = $limit;
$list_params[] = $offset;
$rows = $wpdb->get_results($wpdb->prepare($sql, $list_params), ARRAY_A);
return [
'items' => is_array($rows) ? $rows : [],
'total' => $total,
];
}
private function ensure_cap(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
}
}
}