option('sql') ?? base_path('scripts/legacy-passwords-export.sql'); $chunk = (int) $this->option('chunk'); try { DB::connection('legacy')->getPdo(); } catch (\Throwable $e) { $this->error('Legacy DB connection is not available: ' . $e->getMessage()); return self::FAILURE; } $now = now()->format('Y-m-d H:i:s'); $lines = []; $lines[] = '-- Legacy password export'; $lines[] = '-- Generated: ' . $now; $lines[] = '-- Source: legacy DB (read-only)'; $lines[] = ''; $lines[] = 'SET NAMES utf8mb4;'; $lines[] = 'USE `' . DB::getDatabaseName() . '`;'; $lines[] = 'START TRANSACTION;'; $lines[] = ''; $exported = 0; DB::connection('legacy') ->table('users') ->select(['user_id', 'password2', 'password']) ->orderBy('user_id') ->chunk($chunk, function ($rows) use (&$lines, &$exported, $now) { foreach ($rows as $r) { $id = (int) ($r->user_id ?? 0); $hash = trim((string) ($r->password2 ?: $r->password ?: '')); if ($id === 0 || $hash === '') { continue; } $algo = 'unknown'; if (preg_match('/^\$2[aby]\$/', $hash)) { $algo = 'bcrypt'; } elseif (preg_match('/^\$argon2/', $hash)) { $algo = 'argon2'; } $escaped = str_replace(['\\', "'"], ['\\\\', "\\'"], $hash); $lines[] = "-- user_id={$id} legacy_algo={$algo}"; $lines[] = "SAVEPOINT sp_{$id};"; $lines[] = "UPDATE `users` SET `password` = '{$escaped}', `legacy_password_algo` = '{$algo}', `needs_password_reset` = 1, `updated_at` = '{$now}' WHERE `id` = {$id};"; $lines[] = ''; $exported++; } }); $lines[] = 'COMMIT;'; $lines[] = ''; $lines[] = "-- Exported: {$exported} user(s)"; $sql = implode("\n", $lines) . "\n"; if (file_put_contents($sqlPath, $sql) === false) { $this->error('Could not write SQL file: ' . $sqlPath); return self::FAILURE; } $this->info('Wrote ' . $exported . ' rows to: ' . $sqlPath); return self::SUCCESS; } }