Add aksrt_download_logs workbench, zibll hidecontent alias + topic partial-hide path, VIP1/VIP2 price matrix for topics, and users list commerce snapshot column with deep links.
203 lines
8.4 KiB
PHP
203 lines
8.4 KiB
PHP
<?php
|
|
/**
|
|
* Native WP admin: resource download token/log browser (zibll paydown log parity, best-effort).
|
|
*
|
|
* Source: {prefix}aksrt_dl_token (issued tokens; used rows kept ~7 days by cleanup).
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class AKSRT_Admin_Pay_Download_Logs {
|
|
|
|
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_DB_Migrator')) {
|
|
echo '<div class="notice notice-error"><p>' . esc_html__('数据库服务不可用。', 'aksrt') . '</p></div></div>';
|
|
return;
|
|
}
|
|
|
|
$table = AKSRT_DB_Migrator::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' => '«',
|
|
'next_text' => '»',
|
|
'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'));
|
|
}
|
|
}
|
|
}
|