#!/usr/bin/env php >, procedural: list} $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> $entries * @return array */ 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 $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 $entry * @param array $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 = " '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, "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 = << [ '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);