Files
wordpress-theme-aksrt/tests/widget-system/pending-upload-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

212 lines
8.6 KiB
PHP

<?php
/** WordPress-free pending upload lifecycle and cleanup harness. */
declare(strict_types=1);
define('ABSPATH', __DIR__ . '/');
define('DAY_IN_SECONDS', 86400);
define('HOUR_IN_SECONDS', 3600);
final class WP_REST_Request {
public function __construct(private array $params) {}
public function get_param(string $key) { return $this->params[$key] ?? null; }
}
final class WP_REST_Response {
public function __construct(public array $data, public int $status = 200) {}
}
function absint($value): int { return abs((int) $value); }
function __($text, $domain = '') { return $text; }
function get_current_user_id(): int { return $GLOBALS['current_user']; }
function is_user_logged_in(): bool { return get_current_user_id() > 0; }
function get_post(int $id) { return $GLOBALS['posts'][$id] ?? null; }
function get_post_meta(int $id, string $key, bool $single = false) { return $GLOBALS['meta'][$id][$key] ?? ''; }
function update_post_meta(int $id, string $key, $value): void { $GLOBALS['meta'][$id][$key] = $value; }
function delete_post_meta(int $id, string $key): void { unset($GLOBALS['meta'][$id][$key]); }
function wp_delete_attachment(int $id, bool $force) {
$GLOBALS['deletions'][] = [$id, $force];
if (!empty($GLOBALS['delete_failures'][$id])) return false;
$post = get_post($id);
unset($GLOBALS['posts'][$id], $GLOBALS['meta'][$id]);
return $post;
}
function wp_get_upload_dir(): array { return ['baseurl' => 'https://example.test/wp-content/uploads']; }
function wp_parse_url(string $url, int $component = -1) { return parse_url($url, $component); }
function set_url_scheme(string $url): string { return preg_replace('#^https?:#', 'https:', $url); }
function attachment_url_to_postid(string $url): int { return $GLOBALS['attachment_urls'][$url] ?? 0; }
function add_action(string $hook, callable $callback, int $priority = 10, int $accepted = 1): void {
$GLOBALS['hooks'][$hook] = [$callback, $accepted];
}
function register_rest_route(string $namespace, string $route, array $args): void {
$GLOBALS['routes'][] = [$namespace, $route, $args];
}
function wp_next_scheduled(string $hook) { return $GLOBALS['scheduled'][$hook] ?? false; }
function wp_schedule_event(int $at, string $recurrence, string $hook): void {
$GLOBALS['schedule_calls'][] = [$at, $recurrence, $hook];
$GLOBALS['scheduled'][$hook] = $at;
}
final class PendingWpdb {
public string $posts = 'wp_posts';
public string $postmeta = 'wp_postmeta';
public array $queries = [];
public ?array $forced_ids = null;
public $after_select = null;
private array $params = [];
public function prepare(string $sql, ...$params): string {
$this->params = $params;
$this->queries[] = ['sql' => $sql, 'params' => $params];
return $sql;
}
public function get_col(string $sql): array {
if ($this->forced_ids !== null) {
$ids = $this->forced_ids;
} else {
[$key, $cutoff, $limit] = $this->params;
$posts = $GLOBALS['posts'];
ksort($posts);
$ids = [];
foreach ($posts as $id => $post) {
$pending = get_post_meta($id, $key, true);
if ($post->post_type !== 'attachment' || $pending === '' || (int) $pending >= $cutoff) continue;
if (str_contains($sql, 'p.post_parent = 0') && $post->post_parent !== 0) continue;
$ids[] = $id;
if (count($ids) >= $limit) break;
}
}
if (is_callable($this->after_select)) ($this->after_select)();
return $ids;
}
}
require dirname(__DIR__, 2) . '/src/Attachment/PendingUploads.php';
use Jio\Attachment\PendingUploads;
function reset_fixture(): void {
$GLOBALS['posts'] = [];
$GLOBALS['meta'] = [];
$GLOBALS['attachment_urls'] = [];
$GLOBALS['deletions'] = [];
$GLOBALS['delete_failures'] = [];
$GLOBALS['current_user'] = 7;
$GLOBALS['wpdb'] = new PendingWpdb();
}
function attachment(int $id, int $owner = 7, int $parent = 0, ?int $pending = null): string {
$GLOBALS['posts'][$id] = (object) ['ID' => $id, 'post_type' => 'attachment', 'post_author' => $owner, 'post_parent' => $parent];
$url = 'https://example.test/wp-content/uploads/file-' . $id . '.jpg';
$GLOBALS['attachment_urls'][$url] = $id;
if ($pending !== null) update_post_meta($id, PendingUploads::META, $pending);
return $url;
}
function pending_ids(): array {
return array_values(array_filter(array_keys($GLOBALS['posts']), static fn(int $id): bool => get_post_meta($id, PendingUploads::META, true) !== ''));
}
$service = PendingUploads::get_instance();
$results = [];
reset_fixture();
$GLOBALS['hooks'] = [];
$GLOBALS['routes'] = [];
$GLOBALS['scheduled'] = [];
$GLOBALS['schedule_calls'] = [];
$service->init();
$service->maybe_schedule();
$service->maybe_schedule();
$service->register_routes();
$route = $GLOBALS['routes'][0];
$permission = $route[2]['permission_callback'];
$member_allowed = $permission();
$GLOBALS['current_user'] = 0;
$results['registration'] = [
'hooks' => array_keys($GLOBALS['hooks']),
'schedule_count' => count($GLOBALS['schedule_calls']),
'recurrence' => $GLOBALS['schedule_calls'][0][1],
'namespace' => $route[0], 'route' => $route[1], 'method' => $route[2]['methods'],
'member_allowed' => $member_allowed, 'guest_allowed' => $permission(),
];
reset_fixture();
for ($id = 1; $id <= 25; $id++) attachment($id, 7, 0, time() - 2 * DAY_IN_SECONDS);
attachment(26, 8, 0, time() - 2 * DAY_IN_SECONDS);
attachment(27);
PendingUploads::mark(27);
$marked_at = get_post_meta(27, PendingUploads::META, true);
PendingUploads::mark(0);
PendingUploads::claim_ids(array_merge(range(1, 27), [1, 0]), 7);
$results['claim_ids'] = ['remaining' => pending_ids(), 'marked_recently' => abs(time() - $marked_at) <= 2, 'invalid_marked' => isset($GLOBALS['meta'][0])];
reset_fixture();
$urls = [];
for ($id = 1; $id <= 25; $id++) $urls[] = attachment($id, 7, 0, time() - 2 * DAY_IN_SECONDS);
PendingUploads::claim_urls($urls, 7);
$default_remaining = pending_ids();
PendingUploads::claim_urls($urls, 7, null);
$results['claim_urls'] = ['default_remaining' => $default_remaining, 'unlimited_remaining' => pending_ids()];
reset_fixture();
$urls = [];
for ($id = 1; $id <= 25; $id++) $urls[] = attachment($id, 7, 0, time() - 2 * DAY_IN_SECONDS);
$other = attachment(26, 8, 0, time() - 2 * DAY_IN_SECONDS);
$outside = 'https://cdn.other.test/file.jpg';
attachment(27, 7, 0, time() - 2 * DAY_IN_SECONDS);
$GLOBALS['attachment_urls'][$outside] = 27;
$text = implode(' ', array_map(static fn(string $url): string => '<img src="' . $url . '">', array_merge($urls, [$other, $outside])));
PendingUploads::claim_text($text, 7);
$text_default = pending_ids();
PendingUploads::claim_text($text, 7, null);
$results['claim_text'] = ['default_remaining' => $text_default, 'unlimited_remaining' => pending_ids()];
reset_fixture();
attachment(1, 7, 0, time());
attachment(2, 8, 0, time());
attachment(3);
attachment(4, 7, 99, time());
$denied = [PendingUploads::discard(2, 7), PendingUploads::discard(3, 7), PendingUploads::discard(4, 7), PendingUploads::discard(1, 0)];
$allowed = $service->rest_discard(new WP_REST_Request(['id' => 1]));
$repeat = $service->rest_discard(new WP_REST_Request(['id' => 1]));
$results['discard'] = ['denied' => $denied, 'success' => $allowed->data['success'], 'repeat_status' => $repeat->status, 'deleted' => $GLOBALS['deletions']];
reset_fixture();
$expired = time() - 2 * DAY_IN_SECONDS;
attachment(1, 7, 0, $expired);
attachment(2, 7, 0, time() - HOUR_IN_SECONDS);
attachment(3);
attachment(4, 7, 99, $expired);
$service->cleanup();
$results['cleanup'] = [
'deleted' => $GLOBALS['deletions'], 'remaining' => array_keys($GLOBALS['posts']),
'query' => $GLOBALS['wpdb']->queries[0],
];
reset_fixture();
for ($id = 1; $id <= 6; $id++) attachment($id, 7, 0, $expired);
$GLOBALS['posts'][6]->post_type = 'post';
$GLOBALS['wpdb']->forced_ids = range(1, 6);
$GLOBALS['wpdb']->after_select = static function (): void {
PendingUploads::claim_ids([1], 7);
$GLOBALS['posts'][2]->post_parent = 99;
PendingUploads::mark(3);
unset($GLOBALS['posts'][4]);
};
$service->cleanup();
$results['stale_selection'] = ['deleted' => $GLOBALS['deletions']];
reset_fixture();
for ($id = 1; $id <= 105; $id++) attachment($id, 7, 0, $expired);
$service->cleanup();
$first_batch = count($GLOBALS['deletions']);
$service->cleanup();
$results['batch'] = ['first' => $first_batch, 'total' => count($GLOBALS['deletions']), 'remaining' => count($GLOBALS['posts'])];
reset_fixture();
attachment(1, 7, 0, $expired);
$GLOBALS['delete_failures'][1] = true;
$service->cleanup();
$retained_after_failure = isset($GLOBALS['posts'][1]) && get_post_meta(1, PendingUploads::META, true) !== '';
$GLOBALS['delete_failures'][1] = false;
$service->cleanup();
$results['retry'] = ['retained' => $retained_after_failure, 'deleted_after_retry' => !isset($GLOBALS['posts'][1])];
echo json_encode($results, JSON_THROW_ON_ERROR);