Files
wordpress-theme-aksrt/tests/widget-system/report-submit-harness.php
T
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

472 lines
20 KiB
PHP

<?php
/**
* Exercise report submission, storage, notifications, and admin filtering without WordPress.
*
* @package Jio
*/
namespace Jio\Core {
final class DBMigrator {
public static function table(string $name): string { return 'wp_jio_' . $name; }
}
}
namespace Jio\Settings {
final class Settings {
public static array $values = [];
public static function get_all(): array { return self::$values; }
public static function read_domain(string $domain, string $section): array {
unset($domain, $section);
return self::$values;
}
}
}
namespace Jio\Messages {
final class Message {
public static function get_instance(): self { return new self(); }
public function send(int $user_id, string $type, string $content, array $args = []): void {
$GLOBALS['jio_test_messages'][] = compact('user_id', 'type', 'content', 'args');
}
}
}
namespace Jio\Attachment {
final class PendingUploads {
public static function claim_text(string $text, int $user_id): void {
$GLOBALS['jio_test_claims'][] = [$text, $user_id];
}
}
}
namespace Jio\User {
final class Restriction {
public const TYPE_WARN = 'warn';
public const TYPE_MUTE = 'mute';
public const TYPE_SUSPEND = 'suspend';
public const TYPE_BAN = 'ban';
public const TYPES = [self::TYPE_WARN, self::TYPE_MUTE, self::TYPE_SUSPEND, self::TYPE_BAN];
public const SCOPE_GLOBAL = 'global';
public static array $calls = [];
public static function get_instance(): self { return new self(); }
public function restrict(int $target_id, string $type, string $reason, string $scope, string $expires_at, int $actor_id): array {
self::$calls[] = compact('target_id', 'type', 'reason', 'scope', 'expires_at', 'actor_id');
return ['success' => true];
}
}
}
namespace {
define('ABSPATH', dirname(__DIR__, 2) . DIRECTORY_SEPARATOR);
define('ARRAY_A', 'ARRAY_A');
class WP_User {
public function __construct(public int $ID, public string $display_name) {}
}
class WP_User_Query {}
class WP_Post {
public string $post_status = 'publish';
public function __construct(public int $ID) {}
}
class WP_REST_Request {
public function __construct(private array $params = []) {}
public function get_json_params(): array { return $this->params; }
public function get_param(string $key) { return $this->params[$key] ?? null; }
}
class WP_REST_Response {
public function __construct(private array $data = [], private int $status = 200) {}
public function get_data(): array { return $this->data; }
public function get_status(): int { return $this->status; }
}
require_once ABSPATH . 'src/Messages/ReportCenter.php';
require_once ABSPATH . 'src/Settings/UserRuntime.php';
require_once ABSPATH . 'src/Admin/UserControllerReportOperations.php';
final class TestWpdb {
public string $prefix = 'wp_';
public int $insert_id = 0;
public string $last_error = '';
public bool $fail_insert = false;
public array $rows = [];
public array $insert_calls = [];
private int $next_id = 1;
private array $last_args = [];
public function prepare(string $query, ...$args): string {
if (count($args) === 1 && is_array($args[0])) {
$args = $args[0];
}
$this->last_args = $args;
return $query;
}
public function insert(string $table, array $data, array $format = []) {
$this->insert_calls[] = [$table, $data, $format];
if ($this->fail_insert) {
return false;
}
$id = $this->next_id++;
$this->insert_id = $id;
$this->rows[] = array_merge(['id' => $id], $data, ['notes_json' => '[]']);
return 1;
}
public function get_var(string $query) {
if (str_contains($query, 'reporter_id = %d')) {
[$reporter_id, $target_type, $target_id] = array_pad($this->last_args, 3, null);
foreach ($this->rows as $row) {
if ((int) $row['reporter_id'] === (int) $reporter_id
&& (string) $row['target_type'] === (string) $target_type
&& (int) $row['target_id'] === (int) $target_id
&& (string) $row['status'] === 'pending'
) {
return (int) $row['id'];
}
}
return null;
}
if (str_contains($query, 'COUNT(*)') && str_contains($query, 'target_type = %s')) {
[$target_type, $target_id] = array_pad($this->last_args, 2, null);
return count(array_filter($this->rows, static fn (array $row): bool =>
(string) $row['target_type'] === (string) $target_type
&& (int) $row['target_id'] === (int) $target_id
));
}
return 0;
}
public function get_results(string $query, $output = null): array {
if (str_contains($query, 'GROUP BY status')) {
$counts = [];
foreach ($this->rows as $row) {
$status = (string) ($row['status'] ?? '');
$counts[$status] = ($counts[$status] ?? 0) + 1;
}
$results = [];
foreach ($counts as $status => $count) {
$results[] = ['status' => $status, 'cnt' => $count];
}
return $results;
}
$rows = $this->rows;
$index = 0;
foreach (['status', 'category', 'target_type'] as $field) {
if (str_contains($query, $field . ' = %s')) {
$value = (string) ($this->last_args[$index++] ?? '');
$rows = array_values(array_filter($rows, static fn (array $row): bool =>
(string) ($row[$field] ?? '') === $value
));
}
}
return $rows;
}
public function get_row(string $query, $output = null) {
$id = (int) ($this->last_args[0] ?? 0);
foreach ($this->rows as $row) {
if ((int) $row['id'] === $id) {
return $output === ARRAY_A ? $row : (object) $row;
}
}
return null;
}
public function update(string $table, array $data, array $where, array $format = [], array $where_format = []) {
unset($table, $format, $where_format);
foreach ($this->rows as &$row) {
if ((int) ($row['id'] ?? 0) === (int) ($where['id'] ?? 0)) {
$row = array_merge($row, $data);
unset($row);
return 1;
}
}
unset($row);
return false;
}
}
final class ReportFixtureController {
use \Jio\Admin\UserControllerReportOperations;
protected function success($data = [], int $status = 200): WP_REST_Response {
return new WP_REST_Response(array_merge(['success' => true], $data), $status);
}
protected function error(string $message, int $status = 400): WP_REST_Response {
return new WP_REST_Response(['success' => false, 'message' => $message], $status);
}
protected function get_json_params(WP_REST_Request $request): array {
return $request->get_json_params();
}
private function build_admin_user_summary(int $user_id, $user): array {
return [
'id' => $user_id,
'display_name' => $user ? $user->display_name : '',
'email' => '',
];
}
}
function __(string $text, string $domain = ''): string { return $text; }
function sanitize_key($value): string { return preg_replace('/[^a-z0-9_\-]/', '', strtolower((string) $value)) ?? ''; }
function sanitize_text_field($value): string {
return trim(preg_replace('/[\r\n\t]+/', ' ', strip_tags((string) $value)) ?? '');
}
function sanitize_textarea_field($value): string { return trim(strip_tags((string) $value)); }
function esc_url_raw($url): string { return (string) $url; }
function wp_kses_post($value): string { return strip_tags((string) $value); }
function current_time(string $type): string { unset($type); return '2026-10-30 12:00:00'; }
function wp_json_encode($value): string { return json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?: '[]'; }
function get_current_user_id(): int { return (int) ($GLOBALS['jio_test_user_id'] ?? 0); }
function get_userdata(int $user_id) { return $user_id > 0 ? new WP_User($user_id, 'Reporter ' . $user_id) : false; }
function get_users(array $args = []): array { unset($args); return [901]; }
function admin_url(string $path = ''): string { return 'https://example.test/wp-admin/' . ltrim($path, '/'); }
function get_post(int $id) { return $id > 0 ? new WP_Post($id) : false; }
function get_the_title($post): string { return 'Article #' . (is_object($post) ? $post->ID : (int) $post); }
function get_permalink($post): string { return 'https://example.test/post/' . (is_object($post) ? $post->ID : (int) $post); }
function do_action(string $hook, ...$args): void {
if ($hook === 'jio_report_submitted') {
$GLOBALS['jio_test_actions'][] = $args;
}
}
if (!function_exists('mb_substr')) {
function mb_substr(string $text, int $start, ?int $length = null): string {
return $length === null ? substr($text, $start) : substr($text, $start, $length);
}
}
function reset_report_fixture(array $preset_names = ['垃圾信息', '人身攻击']): void {
$GLOBALS['wpdb'] = new TestWpdb();
$GLOBALS['jio_test_user_id'] = 42;
$GLOBALS['jio_test_actions'] = [];
$GLOBALS['jio_test_messages'] = [];
$GLOBALS['jio_test_claims'] = [];
\Jio\User\Restriction::$calls = [];
\Jio\Settings\Settings::$values = [
'ban_preset_reason' => array_map(static fn (string $name): array => ['t' => $name], $preset_names),
'user_report_img_s' => true,
];
}
function submit_report_case(array $payload): WP_REST_Response {
return (new ReportFixtureController())->submit_report(new WP_REST_Request($payload));
}
function snapshot_response(WP_REST_Response $response): array {
$wpdb = $GLOBALS['wpdb'];
return [
'response' => $response->get_data(),
'status' => $response->get_status(),
'rows' => $wpdb->rows,
'insert_calls' => $wpdb->insert_calls,
'actions' => $GLOBALS['jio_test_actions'],
'messages' => $GLOBALS['jio_test_messages'],
'claims' => $GLOBALS['jio_test_claims'],
];
}
$results = [];
reset_report_fixture(['垃圾信息', '人身攻击']);
$results['runtime_reasons'] = \Jio\Settings\UserRuntime::ban_settings()['reasons'];
reset_report_fixture();
$results['preset_with_evidence'] = snapshot_response(submit_report_case([
'target_type' => 'post',
'target_id' => 73,
'category' => '垃圾信息',
'reason' => '补充详情 https://example.test/uploads/proof.png',
]));
reset_report_fixture();
$results['preset_label_only'] = snapshot_response(submit_report_case([
'target_type' => 'comment',
'target_id' => 88,
'category' => '人身攻击',
'reason' => '',
]));
reset_report_fixture(['0']);
$results['falsey_text'] = snapshot_response(submit_report_case([
'target_type' => 'post',
'target_id' => 90,
'category' => '0',
'reason' => '0',
]));
$long_label = '非常长的预置举报分类名称超过数据库原先四十字符限制并且应完整保留在工单及后台列表当中';
reset_report_fixture([$long_label]);
$results['long_preset_label'] = snapshot_response(submit_report_case([
'target_type' => 'post',
'target_id' => 19,
'category' => $long_label,
'reason' => '',
]));
reset_report_fixture();
$results['other_with_details'] = snapshot_response(submit_report_case([
'target_type' => 'comment',
'target_id' => 99,
'category' => '其他原因',
'reason' => '请核查相关资料',
]));
reset_report_fixture();
$results['sanitized_snapshot'] = snapshot_response(submit_report_case([
'target_type' => 'post',
'target_id' => 12,
'category' => ' <b>垃圾信息</b> ',
'reason' => '<strong>补充详情</strong>',
]));
reset_report_fixture();
$results['blank_category'] = snapshot_response(submit_report_case([
'target_type' => 'post', 'target_id' => 5, 'category' => '', 'reason' => '详情',
]));
reset_report_fixture();
$results['missing_other_details'] = snapshot_response(submit_report_case([
'target_type' => 'post', 'target_id' => 5, 'category' => '其他原因', 'reason' => ' ',
]));
reset_report_fixture();
$results['unknown_category'] = snapshot_response(submit_report_case([
'target_type' => 'post', 'target_id' => 5, 'category' => '未配置的分类', 'reason' => '详情',
]));
reset_report_fixture();
$results['hash_category'] = snapshot_response(submit_report_case([
'target_type' => 'post', 'target_id' => 5, 'category' => 'ab73c91f054e', 'reason' => '详情',
]));
reset_report_fixture();
$GLOBALS['jio_test_user_id'] = 0;
$results['unauthenticated'] = snapshot_response(submit_report_case([
'target_type' => 'post', 'target_id' => 5, 'category' => '垃圾信息', 'reason' => '',
]));
reset_report_fixture();
$results['invalid_target'] = snapshot_response(submit_report_case([
'target_type' => 'post', 'target_id' => 0, 'category' => '垃圾信息', 'reason' => '',
]));
reset_report_fixture();
$GLOBALS['wpdb']->fail_insert = true;
$GLOBALS['wpdb']->insert_id = 777;
$results['insert_failure'] = snapshot_response(submit_report_case([
'target_type' => 'post', 'target_id' => 5, 'category' => '垃圾信息', 'reason' => '含有举证',
]));
reset_report_fixture();
$GLOBALS['wpdb']->rows[] = [
'id' => 51,
'reporter_id' => 42,
'target_type' => 'post',
'target_id' => 73,
'reason' => '既有详情',
'category' => '垃圾信息',
'status' => 'pending',
'created_at' => '2026-10-30 11:00:00',
'updated_at' => '',
'notes_json' => '[]',
];
$results['duplicate'] = snapshot_response(submit_report_case([
'target_type' => 'post', 'target_id' => 73, 'category' => '垃圾信息', 'reason' => '重复提交',
]));
reset_report_fixture(['涉嫌侵权']);
$admin_controller = new ReportFixtureController();
$submit_response = submit_report_case([
'target_type' => 'post', 'target_id' => 31, 'category' => '涉嫌侵权', 'reason' => '',
]);
$list_response = $admin_controller->get_report_list(new WP_REST_Request(['category' => '涉嫌侵权']));
$list_data = $list_response->get_data()['data'] ?? [];
$detail_response = $admin_controller->get_report_detail(new WP_REST_Request(['id' => 1]));
$detail_data = $detail_response->get_data()['data'] ?? [];
$results['admin_category'] = [
'submit_success' => !empty($submit_response->get_data()['success']),
'list_items' => $list_data['items'] ?? [],
'list_options' => $list_data['options']['categories'] ?? [],
'detail_report' => $detail_data['report'] ?? [],
];
reset_report_fixture();
$service_id = \Jio\Messages\ReportCenter::get_instance()->submit(42, 'post', 6, '详情', '');
$results['service_blank_category'] = [
'id' => $service_id,
'rows' => $GLOBALS['wpdb']->rows,
'insert_calls' => $GLOBALS['wpdb']->insert_calls,
];
reset_report_fixture(['提交时的分类', '待删除的分类']);
$first_snapshot_submit = submit_report_case([
'target_type' => 'post', 'target_id' => 61, 'category' => '提交时的分类', 'reason' => '',
]);
$second_snapshot_submit = submit_report_case([
'target_type' => 'post', 'target_id' => 62, 'category' => '待删除的分类', 'reason' => '',
]);
$admin_controller = new ReportFixtureController();
\Jio\Settings\Settings::$values['ban_preset_reason'] = [['t' => '当前新分类']];
$changed_list = $admin_controller->get_report_list(new WP_REST_Request())->get_data()['data'] ?? [];
\Jio\Settings\Settings::$values['ban_preset_reason'] = [];
$removed_list = $admin_controller->get_report_list(new WP_REST_Request())->get_data()['data'] ?? [];
$first_detail = $admin_controller->get_report_detail(new WP_REST_Request(['id' => 1]))->get_data()['data']['report'] ?? [];
$second_detail = $admin_controller->get_report_detail(new WP_REST_Request(['id' => 2]))->get_data()['data']['report'] ?? [];
$results['category_snapshot'] = [
'submit_success' => !empty($first_snapshot_submit->get_data()['success']) && !empty($second_snapshot_submit->get_data()['success']),
'changed_list_categories' => array_column($changed_list['items'] ?? [], 'category'),
'changed_list_options' => array_column($changed_list['options']['categories'] ?? [], 'value'),
'removed_list_categories' => array_column($removed_list['items'] ?? [], 'category'),
'details' => [
(string) ($first_detail['category'] ?? ''),
(string) ($second_detail['category'] ?? ''),
],
];
reset_report_fixture(['人身攻击']);
$GLOBALS['wpdb']->rows[] = [
'id' => 101, 'reporter_id' => 42, 'target_type' => 'user', 'target_id' => 701,
'reason' => '', 'category' => '人身攻击', 'status' => 'pending',
'created_at' => '2026-10-30 11:00:00', 'updated_at' => null, 'notes_json' => '[]',
];
$preset_process = (new ReportFixtureController())->process_report(new WP_REST_Request([
'id' => 101, 'status' => 'resolved', 'action_taken' => 'warn', 'note' => '',
]));
$preset_row = $GLOBALS['wpdb']->rows[0];
$results['process_preset_only'] = [
'success' => !empty($preset_process->get_data()['success']),
'restriction' => \Jio\User\Restriction::$calls[0] ?? [],
'report_reason' => $preset_row['reason'],
'report_category' => $preset_row['category'],
];
reset_report_fixture();
$custom_reason = '自定义说明不能丢';
$GLOBALS['wpdb']->rows[] = [
'id' => 102, 'reporter_id' => 42, 'target_type' => 'user', 'target_id' => 702,
'reason' => $custom_reason, 'category' => '其他原因', 'status' => 'pending',
'created_at' => '2026-10-30 11:00:00', 'updated_at' => null, 'notes_json' => '[]',
];
$other_process = (new ReportFixtureController())->process_report(new WP_REST_Request([
'id' => 102, 'status' => 'resolved', 'action_taken' => 'warn', 'note' => '',
]));
$other_row = $GLOBALS['wpdb']->rows[0];
$results['process_other_with_details'] = [
'success' => !empty($other_process->get_data()['success']),
'restriction' => \Jio\User\Restriction::$calls[0] ?? [],
'report_reason' => $other_row['reason'],
'report_category' => $other_row['category'],
];
echo json_encode($results, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
}