chore(tools): drop one-shot PSR-4 migrators after apply

Remove psr4-migrate.php / psr4-fix-multi.php (mass file rewriters that
triggered AV false positives on merge). Migration is complete; keep
build-psr4-map inventory only and note this in the ADR.
This commit is contained in:
2026-07-17 13:40:04 +08:00
parent aaf458af1c
commit 70c345bb37
3 changed files with 2 additions and 888 deletions
+2 -2
View File
@@ -53,8 +53,8 @@ Domain folders were frozen after the root→domain move. Theme types then migrat
### Map tooling ### Map tooling
- `php tools/build-psr4-map.php --write` — inventory / map under `tools/psr4-map/` - `php tools/build-psr4-map.php --write` — read-only inventory / map under `tools/psr4-map/`
- Migration helpers under `tools/psr4-*.php` (historical; re-run only with care) - One-shot mass-rewrite migrators are **not** kept in-tree (completed migration; AV false positives on file-tree rewriters)
## Out of scope ## Out of scope
-215
View File
@@ -1,215 +0,0 @@
<?php
/**
* Robust multi-file splitter for PSR-4.
*
* @package AKSRT
*/
declare(strict_types=1);
$root = dirname(__DIR__);
$map = json_decode((string) file_get_contents($root . '/tools/psr4-map/map.json'), true, 512, JSON_THROW_ON_ERROR);
$by_old = [];
foreach ($map['entries'] as $e) {
$by_old[$e['old']] = $e;
}
/**
* Find all top-level type declarations with start offsets.
*
* @return list<array{name:string,kind:string,start:int,decl:int}>
*/
function list_types(string $text): array {
$out = [];
if (!preg_match_all(
'/^(?:abstract\s+|final\s+)?(class|trait|interface)\s+(\w+)/m',
$text,
$m,
PREG_SET_ORDER | PREG_OFFSET_CAPTURE
)) {
return [];
}
foreach ($m as $match) {
$decl = (int) $match[0][1];
$kind = $match[1][0];
$name = $match[2][0];
// Include preceding docblock.
$before = substr($text, 0, $decl);
$start = $decl;
if (preg_match('/(?:\n|^)(\s*\/\*\*[\s\S]*?\*\/\s*)\z/', $before, $dm, PREG_OFFSET_CAPTURE)) {
$start = (int) $dm[1][1];
}
$out[] = ['name' => $name, 'kind' => $kind, 'start' => $start, 'decl' => $decl];
}
return $out;
}
function extract_by_index(string $text, array $types, int $idx): string {
$start = $types[$idx]['start'];
// End is start of next type or EOF (but need end of braces of this type).
$brace = strpos($text, '{', $types[$idx]['decl']);
$len = strlen($text);
$depth = 0;
$in_str = false;
$str_q = '';
$in_line = false;
$in_block = false;
$escape = false;
for ($i = $brace; $i < $len; $i++) {
$ch = $text[$i];
$next = $i + 1 < $len ? $text[$i + 1] : '';
if ($in_line) {
if ($ch === "\n") {
$in_line = false;
}
continue;
}
if ($in_block) {
if ($ch === '*' && $next === '/') {
$in_block = false;
$i++;
}
continue;
}
if ($in_str) {
if ($escape) {
$escape = false;
continue;
}
if ($ch === '\\') {
$escape = true;
continue;
}
if ($ch === $str_q) {
$in_str = false;
}
continue;
}
if ($ch === '/' && $next === '/') {
$in_line = true;
$i++;
continue;
}
if ($ch === '/' && $next === '*') {
$in_block = true;
$i++;
continue;
}
if ($ch === '"' || $ch === "'") {
$in_str = true;
$str_q = $ch;
continue;
}
if ($ch === '{') {
$depth++;
} elseif ($ch === '}') {
$depth--;
if ($depth === 0) {
return substr($text, $start, $i - $start + 1);
}
}
}
throw new RuntimeException('brace match failed for ' . $types[$idx]['name']);
}
$rep = [];
foreach ($map['entries'] as $e) {
$rep[$e['old']] = '\\' . $e['new_fqcn'];
}
$rep['AKSRT_Creator_Revenue'] = '\\AKSRT\\Payment\\CreatorRevenue';
uksort($rep, static fn($a, $b) => strlen($b) <=> strlen($a));
function rewrite_refs(string $code, array $rep): string {
$preserve = [
'AKSRT_DIR', 'AKSRT_URI', 'AKSRT_VERSION', 'AKSRT_NO_EMAIL_GATE',
'AKSRT_PAY_TYPE_NO', 'AKSRT_PAY_TYPE_READING', 'AKSRT_PAY_TYPE_DOWNLOAD',
'AKSRT_PAY_TYPE_GALLERY', 'AKSRT_PAY_TYPE_VIDEO', 'AKSRT_PAY_TYPE_VIP',
];
foreach ($preserve as $i => $tok) {
$code = str_replace($tok, "\0P{$i}\0", $code);
}
$pattern = '/\b(' . implode('|', array_map(static fn($k) => preg_quote($k, '/'), array_keys($rep))) . ')\b/';
$code = preg_replace_callback($pattern, static fn($m) => $rep[$m[1]] ?? $m[1], $code) ?? $code;
foreach ($preserve as $i => $tok) {
$code = str_replace("\0P{$i}\0", $tok, $code);
}
return $code;
}
$files = [
'inc/classes/payment/class-aksrt-payment-gateway.php',
'inc/classes/widgets/class-aksrt-forum-widgets.php',
'inc/classes/widgets/class-aksrt-home-widgets.php',
'inc/classes/widgets/class-aksrt-shop-widgets.php',
'inc/classes/widgets/class-aksrt-widgets-batch1.php',
'inc/classes/widgets/class-aksrt-widgets-new.php',
'inc/classes/widgets/class-aksrt-widgets-original.php',
];
foreach ($files as $git_path) {
$text = shell_exec('git -C ' . escapeshellarg($root) . ' show HEAD:' . escapeshellarg($git_path));
if (!$text) {
fwrite(STDERR, "git show fail $git_path\n");
continue;
}
$types = list_types($text);
echo $git_path, ' has ', count($types), " types: ", implode(', ', array_column($types, 'name')), "\n";
foreach ($types as $idx => $info) {
$old = $info['name'];
if (!isset($by_old[$old])) {
fwrite(STDERR, " unmapped $old\n");
continue;
}
$entry = $by_old[$old];
$chunk = extract_by_index($text, $types, $idx);
// Rename: only the declaration line's name.
$chunk = preg_replace(
'/^((?:abstract\s+|final\s+)?' . preg_quote($info['kind'], '/') . '\s+)' . preg_quote($old, '/') . '\b/m',
'$1' . $entry['short'],
$chunk,
1
);
$chunk = rewrite_refs($chunk, $rep);
$ns = $entry['namespace'];
// Shorten same-namespace refs BUT not if it would re-break declaration - declaration already short.
$chunk = preg_replace('/\\\\' . preg_quote($ns, '/') . '\\\\(\w+)/', '$1', $chunk) ?? $chunk;
// Global WP types.
foreach (['WP_Widget', 'WP_REST_Request', 'WP_REST_Response', 'WP_Error', 'WP_Post', 'WP_User', 'WP_Query'] as $g) {
$chunk = preg_replace('/(?<!\\\\)\b' . $g . '\b/', '\\' . $g, $chunk) ?? $chunk;
}
$chunk = str_replace('\\\\', '\\', $chunk);
$out = "<?php\n/**\n * @package AKSRT\n */\n\nnamespace {$ns};\n\ndefined('ABSPATH') || exit;\n\n" . rtrim($chunk) . "\n";
// Verify
if (!preg_match('/\b' . preg_quote($info['kind'], '/') . '\s+' . preg_quote($entry['short'], '/') . '\b/', $out)) {
fwrite(STDERR, " VERIFY FAIL $old -> {$entry['short']}\n");
fwrite(STDERR, " head: " . substr($out, 0, 250) . "\n");
continue;
}
$path = $root . '/' . $entry['new_path'];
if (!is_dir(dirname($path))) {
mkdir(dirname($path), 0777, true);
}
file_put_contents($path, $out);
echo " OK {$old} => {$entry['new_path']} (", strlen($out), " bytes)\n";
}
}
// Globalize WP_* in all src files once more carefully
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root . '/src/AKSRT'));
foreach ($rii as $f) {
if (!$f->isFile() || !str_ends_with($f->getFilename(), '.php')) {
continue;
}
$p = $f->getPathname();
$t = (string) file_get_contents($p);
$orig = $t;
foreach (['WP_REST_Request', 'WP_REST_Response', 'WP_Error', 'WP_Post', 'WP_User', 'WP_Query', 'WP_Widget', 'WP_Admin_Bar', 'WP_HTTP_Response', 'WP_Term'] as $g) {
// only when used as type/extends/new/catch - simple: bare WP_ not already escaped
$t = preg_replace('/(?<!\\\\|[\w\\\\])' . preg_quote($g, '/') . '\b/', '\\' . $g, $t) ?? $t;
}
if ($t !== $orig) {
file_put_contents($p, $t);
}
}
echo "done\n";
-671
View File
@@ -1,671 +0,0 @@
#!/usr/bin/env php
<?php
/**
* One-shot PSR-4 migrator for AKSRT theme.
*
* Prerequisites: php tools/build-psr4-map.php --write
*
* Usage:
* php tools/psr4-migrate.php --dry-run
* php tools/psr4-migrate.php --apply
*
* @package AKSRT
*/
declare(strict_types=1);
$root = dirname(__DIR__);
$apply = in_array('--apply', $argv, true);
$dry = !$apply;
$map_file = $root . '/tools/psr4-map/map.json';
if (!is_file($map_file)) {
fwrite(STDERR, "Missing map. Run: php tools/build-psr4-map.php --write\n");
exit(1);
}
/** @var array{entries: list<array<string,mixed>>, procedural: list<string>} $map */
$map = json_decode((string) file_get_contents($map_file), true, 512, JSON_THROW_ON_ERROR);
const PRESERVE_TOKENS = [
'AKSRT_DIR',
'AKSRT_URI',
'AKSRT_VERSION',
'AKSRT_NO_EMAIL_GATE',
];
/**
* Find declaration start offset for a type name.
*/
function aksrt_find_decl_start(string $text, string $old_name): ?int {
if (!preg_match(
'/(?:abstract\s+|final\s+)?(?:class|trait|interface)\s+' . preg_quote($old_name, '/') . '\b/',
$text,
$m,
PREG_OFFSET_CAPTURE
)) {
return null;
}
$decl = (int) $m[0][1];
// Include immediately preceding docblock if present.
$before = substr($text, 0, $decl);
if (preg_match('/(?:\n|^)\s*\/\*\*[\s\S]*?\*\/\s*\z/', $before, $dm, PREG_OFFSET_CAPTURE)) {
return (int) $dm[0][1];
}
return $decl;
}
/**
* Extract one class/trait/interface by brace matching from its declaration.
*/
function aksrt_extract_type(string $text, string $old_name): ?string {
$start = aksrt_find_decl_start($text, $old_name);
if ($start === null) {
return null;
}
$brace = strpos($text, '{', $start);
if ($brace === false) {
// Possible empty interface? require body.
return null;
}
$len = strlen($text);
$depth = 0;
$in_str = false;
$str_q = '';
$in_line_comment = false;
$in_block_comment = false;
$escape = false;
for ($i = $brace; $i < $len; $i++) {
$ch = $text[$i];
$next = $i + 1 < $len ? $text[$i + 1] : '';
if ($in_line_comment) {
if ($ch === "\n") {
$in_line_comment = false;
}
continue;
}
if ($in_block_comment) {
if ($ch === '*' && $next === '/') {
$in_block_comment = false;
$i++;
}
continue;
}
if ($in_str) {
if ($escape) {
$escape = false;
continue;
}
if ($ch === '\\') {
$escape = true;
continue;
}
if ($ch === $str_q) {
$in_str = false;
}
continue;
}
if ($ch === '/' && $next === '/') {
$in_line_comment = true;
$i++;
continue;
}
if ($ch === '/' && $next === '*') {
$in_block_comment = true;
$i++;
continue;
}
if ($ch === '"' || $ch === "'") {
$in_str = true;
$str_q = $ch;
continue;
}
// Heredoc/nowdoc not handled fully — rare in this codebase.
if ($ch === '{') {
$depth++;
} elseif ($ch === '}') {
$depth--;
if ($depth === 0) {
return substr($text, $start, $i - $start + 1);
}
}
}
return null;
}
/**
* @param list<array<string,mixed>> $entries
* @return array<string, string>
*/
function aksrt_replacements(array $entries): array {
$rep = [];
foreach ($entries as $e) {
$rep[$e['old']] = '\\' . $e['new_fqcn'];
}
uksort($rep, static fn($a, $b) => strlen($b) <=> strlen($a));
return $rep;
}
/**
* @param array<string, string> $rep
*/
function aksrt_rewrite_refs(string $code, array $rep): string {
$protected = [];
$i = 0;
foreach (PRESERVE_TOKENS as $tok) {
$ph = "\0PRESERVE{$i}\0";
$protected[$ph] = $tok;
$code = str_replace($tok, $ph, $code);
$i++;
}
if ($rep) {
$pattern = '/\b(' . implode('|', array_map(static fn($k) => preg_quote($k, '/'), array_keys($rep))) . ')\b/';
$code = preg_replace_callback($pattern, static function ($m) use ($rep) {
return $rep[$m[1]] ?? $m[1];
}, $code) ?? $code;
}
foreach ($protected as $ph => $tok) {
$code = str_replace($ph, $tok, $code);
}
return $code;
}
/**
* @param array<string,mixed> $entry
* @param array<string,string> $rep
*/
function aksrt_build_namespaced_file(array $entry, string $body, array $rep): string {
$kind = $entry['kind'];
$old = $entry['old'];
$short = $entry['short'];
$ns = $entry['namespace'];
$body = preg_replace(
'/(abstract\s+|final\s+)?(' . preg_quote($kind, '/') . ')\s+' . preg_quote($old, '/') . '\b/',
'$1$2 ' . $short,
$body,
1
) ?? $body;
$body = aksrt_rewrite_refs($body, $rep);
// Same-namespace FQCN → short name.
$body = preg_replace(
'/\\\\' . preg_quote($ns, '/') . '\\\\(\w+)/',
'$1',
$body
) ?? $body;
$out = "<?php\n/**\n * @package AKSRT\n */\n\nnamespace {$ns};\n\ndefined('ABSPATH') || exit;\n\n";
$out .= rtrim($body) . "\n";
return $out;
}
function aksrt_ensure_dir(string $file): void {
$dir = dirname($file);
if (!is_dir($dir) && !mkdir($dir, 0777, true) && !is_dir($dir)) {
throw new RuntimeException("mkdir failed: {$dir}");
}
}
function aksrt_write(string $abs, string $contents, bool $dry): void {
if ($dry) {
echo '[dry] write ' . $abs . ' (' . strlen($contents) . " bytes)\n";
return;
}
aksrt_ensure_dir($abs);
if (file_put_contents($abs, $contents) === false) {
throw new RuntimeException("write failed: {$abs}");
}
}
$rep = aksrt_replacements($map['entries']);
$entries = $map['entries'];
echo ($dry ? 'DRY-RUN' : 'APPLY') . ' — ' . count($entries) . " types\n";
$old_type_paths = [];
$failed = [];
$written = 0;
foreach ($entries as $entry) {
$old_abs = $root . '/' . $entry['old_path'];
$new_abs = $root . '/' . $entry['new_path'];
$old_type_paths[$entry['old_path']] = true;
if (!is_file($old_abs)) {
if (is_file($new_abs)) {
continue;
}
$failed[] = "missing source {$entry['old_path']} for {$entry['old']}";
continue;
}
$text = (string) file_get_contents($old_abs);
$chunk = aksrt_extract_type($text, $entry['old']);
if ($chunk === null) {
$failed[] = "cannot extract {$entry['old']} from {$entry['old_path']}";
continue;
}
$contents = aksrt_build_namespaced_file($entry, $chunk, $rep);
aksrt_write($new_abs, $contents, $dry);
$written++;
}
echo "Type files: {$written}\n";
if ($failed) {
fwrite(STDERR, "Extraction failures (" . count($failed) . "):\n");
foreach (array_slice($failed, 0, 30) as $f) {
fwrite(STDERR, " {$f}\n");
}
if (!$dry) {
exit(1);
}
}
$proc_moves = [
'inc/classes/auth/class-aksrt-captcha.php' => 'inc/modules/captcha.php',
'inc/classes/payment/class-aksrt-creator-revenue.php' => 'inc/modules/creator-revenue.php',
'inc/classes/user/class-aksrt-user-notifications.php' => 'inc/modules/user-notifications.php',
'inc/classes/user/class-aksrt-user.php' => 'inc/modules/user-hub.php',
'inc/classes/user/user-parts/aksrt-user-account-ajax.php' => 'inc/modules/user-parts/account-ajax.php',
'inc/classes/user/user-parts/aksrt-user-routing.php' => 'inc/modules/user-parts/routing.php',
'inc/classes/user/user-parts/aksrt-user-template-registry.php' => 'inc/modules/user-parts/template-registry.php',
'inc/classes/user/user-parts/aksrt-user-urls.php' => 'inc/modules/user-parts/urls.php',
'inc/classes/widgets/class-aksrt-widgets.php' => 'inc/modules/widgets-register.php',
];
foreach ($proc_moves as $from => $to) {
$from_abs = $root . '/' . $from;
$to_abs = $root . '/' . $to;
if (!is_file($from_abs) && is_file($to_abs)) {
continue;
}
if (!is_file($from_abs)) {
echo "warn: procedural missing {$from}\n";
continue;
}
$text = (string) file_get_contents($from_abs);
if ($from === 'inc/classes/user/class-aksrt-user.php') {
$text = str_replace(
[
"__DIR__ . '/user-parts/aksrt-user-template-registry.php'",
"__DIR__ . '/user-parts/aksrt-user-urls.php'",
"__DIR__ . '/user-parts/aksrt-user-routing.php'",
"__DIR__ . '/user-parts/aksrt-user-account-ajax.php'",
],
[
"__DIR__ . '/user-parts/template-registry.php'",
"__DIR__ . '/user-parts/urls.php'",
"__DIR__ . '/user-parts/routing.php'",
"__DIR__ . '/user-parts/account-ajax.php'",
],
$text
);
}
$text = aksrt_rewrite_refs($text, $rep);
aksrt_write($to_abs, $text, $dry);
}
$scan_roots = [
$root . '/inc/helpers',
$root . '/inc/core-hooks',
$root . '/inc/modules',
$root . '/templates',
$root . '/tools',
];
$scan_files = [
$root . '/functions.php',
$root . '/inc/bootstrap.php',
$root . '/inc/core-hooks.php',
$root . '/uninstall.php',
$root . '/header.php',
$root . '/footer.php',
$root . '/index.php',
$root . '/single.php',
$root . '/page.php',
$root . '/archive.php',
$root . '/search.php',
$root . '/category.php',
$root . '/tag.php',
$root . '/author.php',
$root . '/404.php',
$root . '/home.php',
$root . '/front-page.php',
$root . '/comments.php',
$root . '/sidebar.php',
$root . '/image.php',
$root . '/go.php',
$root . '/slim-forum.php',
$root . '/archive-resources.php',
];
$php_files = $scan_files;
foreach ($scan_roots as $dir) {
if (!is_dir($dir)) {
continue;
}
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach ($rii as $file) {
if ($file->isFile() && str_ends_with($file->getFilename(), '.php')) {
$php_files[] = $file->getPathname();
}
}
}
$php_files = array_values(array_unique($php_files));
$rewritten = 0;
foreach ($php_files as $abs) {
if (!is_file($abs)) {
continue;
}
$rel = str_replace('\\', '/', substr($abs, strlen($root) + 1));
if (str_starts_with($rel, 'tools/build-psr4-map.php')
|| str_starts_with($rel, 'tools/psr4-migrate.php')
|| str_starts_with($rel, 'tools/psr4-map/')) {
continue;
}
if (str_starts_with($rel, 'src/')) {
continue;
}
$orig = (string) file_get_contents($abs);
$next = aksrt_rewrite_refs($orig, $rep);
if ($next !== $orig) {
aksrt_write($abs, $next, $dry);
$rewritten++;
}
}
echo "Reference rewrites in non-src PHP: {$rewritten} files\n";
// Helper pure-class files become stubs after class move — rewrite domain-model/shortcodes if they still have class bodies (migrator already wrote src copies).
// After apply, delete old type sources and helper class-only file.
$helper_pure = [
'inc/helpers/class-aksrt-nav-walker.php',
];
if (!$dry) {
foreach (array_keys($old_type_paths) as $rel) {
$abs = $root . '/' . $rel;
if (is_file($abs)) {
unlink($abs);
}
}
foreach (array_keys($proc_moves) as $rel) {
$abs = $root . '/' . $rel;
if (is_file($abs)) {
unlink($abs);
}
}
foreach ($helper_pure as $rel) {
$abs = $root . '/' . $rel;
if (is_file($abs)) {
unlink($abs);
}
}
// helpers that still contain only moved classes: strip by rewriting shortcodes/domain-model to empty require-safe stubs if class was extracted.
foreach (['inc/helpers/aksrt-shortcodes.php', 'inc/helpers/aksrt-domain-model.php'] as $rel) {
$abs = $root . '/' . $rel;
if (!is_file($abs)) {
continue;
}
$t = (string) file_get_contents($abs);
// If file still has class keyword for types we moved, replace with comment stub.
if (preg_match('/\b(class|trait|interface)\s+/', $t)) {
file_put_contents(
$abs,
"<?php\n/**\n * Procedural helpers previously co-located with classes (now under src/AKSRT).\n *\n * @package AKSRT\n */\n\ndefined('ABSPATH') || exit;\n"
);
}
}
// Prune empty dirs under inc/classes.
$classes = $root . '/inc/classes';
if (is_dir($classes)) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($classes, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $file) {
if ($file->isDir()) {
@rmdir($file->getPathname());
} else {
echo 'leftover file: ' . $file->getPathname() . "\n";
}
}
@rmdir($classes);
}
} else {
echo '[dry] would delete ' . count($old_type_paths) . " old type paths + procedural + prune inc/classes\n";
}
$lookup = [];
foreach ($entries as $e) {
$lookup[$e['old']] = $e['new_fqcn'];
}
$auth_fq = $lookup['AKSRT_Auth_Service'] ?? 'AKSRT\\Auth\\Service';
$settings_fq = $lookup['AKSRT_Settings'] ?? 'AKSRT\\Settings\\Settings';
$bootstrap = <<<PHP
<?php
/**
* AKSRT Theme Bootstrap Loader
*
* Helpers + procedural modules + core hooks.
* Domain classes load via Composer PSR-4 (src/AKSRT → AKSRT\\).
*
* @package AKSRT
* @version 3.4.0
*/
defined('ABSPATH') || exit;
// ============================================================
// Stage 1: helpers (must load before runtime that calls them)
// ============================================================
require_once AKSRT_DIR . '/inc/helpers/aksrt-icons.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-domain-model.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-template-tags.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-frontend-tags.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-flow.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-homepage.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-single-components.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-single.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-yiyan.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-single-render.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-archive.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-archive-filters.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-comments.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-forum.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-slider.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-vip.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-auth-ui.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-i18n-switch.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-posts-renderer.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-resource.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-pay.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-coupon-order.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-credits.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-null.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php';
require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-transactions.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-commerce.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-user-dashboard.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-user-center.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-user-home.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-shortcodes.php';
// ============================================================
// Stage 2: procedural modules (not PSR-4 types)
// ============================================================
require_once AKSRT_DIR . '/inc/modules/captcha.php';
require_once AKSRT_DIR . '/inc/modules/user-hub.php';
require_once AKSRT_DIR . '/inc/modules/user-notifications.php';
require_once AKSRT_DIR . '/inc/modules/creator-revenue.php';
require_once AKSRT_DIR . '/inc/modules/widgets-register.php';
// ============================================================
// Stage 3: core hooks
// ============================================================
require_once AKSRT_DIR . '/inc/core-hooks.php';
// ============================================================
// Stage 4: auth service global hooks
// ============================================================
if (class_exists(\\{$auth_fq}::class)) {
\\\\{$auth_fq}::init();
}
// ============================================================
// Stage 5: email verification backfill
// ============================================================
add_action('init', function () {
if (defined('AKSRT_NO_EMAIL_GATE') && AKSRT_NO_EMAIL_GATE) {
return;
}
if (!class_exists(\\{$auth_fq}::class) || !class_exists(\\{$settings_fq}::class)) {
return;
}
if (!\\\\{$settings_fq}::registration_requires_email_verification()) {
return;
}
if (get_option('aksrt_email_verification_backfill_done')) {
return;
}
if (!is_admin() && !(defined('DOING_CRON') && DOING_CRON)) {
return;
}
\\\\{$auth_fq}::backfill_legacy_users_as_verified();
}, 5);
add_action('aksrt_email_verification_backfill_batch', function (\$offset = 0) {
if (class_exists(\\{$auth_fq}::class)) {
\\\\{$auth_fq}::run_async_backfill_batch((int) \$offset);
}
}, 10, 1);
// ============================================================
// Stage 6: settings migration stub (admin only)
// ============================================================
add_action('admin_init', function () {
}, 5);
PHP;
// Fix accidental double backslashes from heredoc interpolation mess.
$bootstrap = str_replace(['\\\\' . $auth_fq, '\\\\' . $settings_fq, '\\{' ], ['\\' . $auth_fq, '\\' . $settings_fq, '{'], $bootstrap);
// Cleaner: rebuild bootstrap without broken escapes.
$bootstrap = "<?php\n"
. "/**\n * AKSRT Theme Bootstrap Loader\n *\n"
. " * Helpers + procedural modules + core hooks.\n"
. " * Domain classes load via Composer PSR-4 (src/AKSRT → AKSRT\\).\n"
. " *\n * @package AKSRT\n * @version 3.4.0\n */\n\n"
. "defined('ABSPATH') || exit;\n\n"
. "// Stage 1: helpers\n";
$helpers = [
'aksrt-icons.php',
'aksrt-domain-model.php',
'aksrt-template-tags.php',
'aksrt-frontend-tags.php',
'aksrt-flow.php',
'aksrt-homepage.php',
'aksrt-single-components.php',
'aksrt-single.php',
'aksrt-yiyan.php',
'aksrt-single-render.php',
'aksrt-archive.php',
'aksrt-archive-filters.php',
'aksrt-comments.php',
'aksrt-forum.php',
'aksrt-slider.php',
'aksrt-vip.php',
'aksrt-auth-ui.php',
'aksrt-i18n-switch.php',
'aksrt-posts-renderer.php',
'aksrt-resource.php',
'aksrt-pay.php',
'aksrt-coupon-order.php',
'aksrt-credits.php',
'aksrt-null.php',
'aksrt-identity.php',
'aksrt-rewards.php',
'menu-decoration.php',
'aksrt-transactions.php',
'aksrt-commerce.php',
'aksrt-user-dashboard.php',
'aksrt-user-center.php',
'aksrt-user-home.php',
'aksrt-shortcodes.php',
];
foreach ($helpers as $h) {
$bootstrap .= "require_once AKSRT_DIR . '/inc/helpers/{$h}';\n";
}
$bootstrap .= "\n// Stage 2: procedural modules\n";
foreach (['captcha.php', 'user-hub.php', 'user-notifications.php', 'creator-revenue.php', 'widgets-register.php'] as $m) {
$bootstrap .= "require_once AKSRT_DIR . '/inc/modules/{$m}';\n";
}
$bootstrap .= "\n// Stage 3: core hooks\n";
$bootstrap .= "require_once AKSRT_DIR . '/inc/core-hooks.php';\n\n";
$bootstrap .= "// Stage 4: auth service hooks\n";
$bootstrap .= "if (class_exists(\\{$auth_fq}::class)) {\n\t\\{$auth_fq}::init();\n}\n\n";
// The above still has escape issues - build with concatenation only:
$bootstrap = "<?php\n/**\n * AKSRT Theme Bootstrap Loader\n *\n * Helpers + procedural modules + core hooks.\n * Domain classes load via Composer PSR-4 (src/AKSRT).\n *\n * @package AKSRT\n * @version 3.4.0\n */\n\ndefined('ABSPATH') || exit;\n\n";
$bootstrap .= "// Stage 1: helpers\n";
foreach ($helpers as $h) {
$bootstrap .= "require_once AKSRT_DIR . '/inc/helpers/{$h}';\n";
}
$bootstrap .= "\n// Stage 2: procedural modules\n";
foreach (['captcha.php', 'user-hub.php', 'user-notifications.php', 'creator-revenue.php', 'widgets-register.php'] as $mod) {
$bootstrap .= "require_once AKSRT_DIR . '/inc/modules/{$mod}';\n";
}
$bootstrap .= "\n// Stage 3: core hooks\nrequire_once AKSRT_DIR . '/inc/core-hooks.php';\n\n";
$bootstrap .= "// Stage 4: auth service hooks\n";
$bootstrap .= 'if (class_exists(\\' . $auth_fq . '::class)) {' . "\n";
$bootstrap .= "\t\\" . $auth_fq . "::init();\n}\n\n";
$bootstrap .= "// Stage 5: email verification backfill\n";
$bootstrap .= "add_action('init', function () {\n";
$bootstrap .= "\tif (defined('AKSRT_NO_EMAIL_GATE') && AKSRT_NO_EMAIL_GATE) {\n\t\treturn;\n\t}\n";
$bootstrap .= "\tif (!class_exists(\\" . $auth_fq . '::class) || !class_exists(\\' . $settings_fq . "::class)) {\n\t\treturn;\n\t}\n";
$bootstrap .= "\tif (!\\" . $settings_fq . "::registration_requires_email_verification()) {\n\t\treturn;\n\t}\n";
$bootstrap .= "\tif (get_option('aksrt_email_verification_backfill_done')) {\n\t\treturn;\n\t}\n";
$bootstrap .= "\tif (!is_admin() && !(defined('DOING_CRON') && DOING_CRON)) {\n\t\treturn;\n\t}\n";
$bootstrap .= "\t\\" . $auth_fq . "::backfill_legacy_users_as_verified();\n}, 5);\n\n";
$bootstrap .= "add_action('aksrt_email_verification_backfill_batch', function (\$offset = 0) {\n";
$bootstrap .= "\tif (class_exists(\\" . $auth_fq . "::class)) {\n";
$bootstrap .= "\t\t\\" . $auth_fq . "::run_async_backfill_batch((int) \$offset);\n\t}\n}, 10, 1);\n\n";
$bootstrap .= "add_action('admin_init', function () {\n}, 5);\n";
aksrt_write($root . '/inc/bootstrap.php', $bootstrap, $dry);
$composer_path = $root . '/composer.json';
$composer = json_decode((string) file_get_contents($composer_path), true, 512, JSON_THROW_ON_ERROR);
$composer['autoload'] = [
'psr-4' => [
'AKSRT\\' => 'src/AKSRT/',
],
];
$composer['description'] = 'AKSRT WordPress Theme';
$json = json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
aksrt_write($composer_path, $json, $dry);
// functions.php must require composer autoload (already does) — ensure fatal if missing for theme classes.
if (!$dry) {
$fn = (string) file_get_contents($root . '/functions.php');
// Already rewritten class names. Optionally strengthen vendor require message.
if (!str_contains($fn, 'vendor/autoload.php')) {
fwrite(STDERR, "functions.php missing vendor/autoload.php require\n");
}
}
echo "Done.\n";
exit(($failed && !$dry) ? 1 : 0);