usedUsernames = User::pluck('username', 'username')->filter()->all(); $this->usedEmails = User::pluck('email', 'email')->filter()->all(); $chunk = (int) $this->option('chunk'); $imported = 0; $skipped = 0; if (! DB::getPdo()) { $this->error('Legacy DB connection "legacy" is not configured or reachable.'); return self::FAILURE; } DB::table('users') ->chunkById($chunk, function ($rows) use (&$imported, &$skipped) { $ids = $rows->pluck('user_id')->all(); $stats = DB::table('users_statistics') ->whereIn('user_id', $ids) ->get() ->keyBy('user_id'); foreach ($rows as $row) { try { $this->importRow($row, $stats[$row->user_id] ?? null); $imported++; } catch (\Throwable $e) { $skipped++; $this->warn("Skip user_id {$row->user_id}: {$e->getMessage()}"); } } }, 'user_id'); $this->info("Imported: {$imported}, Skipped: {$skipped}"); return self::SUCCESS; } protected function importRow($row, $statRow = null): void { $legacyId = (int) $row->user_id; $baseUsername = $this->sanitizeUsername($row->uname ?: ('user'.$legacyId)); $username = $this->uniqueUsername($baseUsername); $email = $this->prepareEmail($row->email ?? null, $username); $legacyPassword = $row->password2 ?: $row->password ?: null; // Optionally force-reset every imported user's password to a secure random value. if ($this->option('force-reset-all')) { $this->warn("Force-reset-all enabled: generating secure password for user_id {$row->user_id}."); $passwordHash = Hash::make(Str::random(64)); } else { // Force-reset known weak default passwords (e.g. "abc123"). if ($legacyPassword !== null && trim($legacyPassword) === 'abc123') { $this->warn("Weak password 'abc123' detected for user_id {$row->user_id}; forcing reset."); $passwordHash = Hash::make(Str::random(64)); } else { $passwordHash = Hash::make($legacyPassword ?: Str::random(32)); } } $uploads = $this->sanitizeStatValue($statRow->uploads ?? 0); $downloads = $this->sanitizeStatValue($statRow->downloads ?? 0); $pageviews = $this->sanitizeStatValue($statRow->pageviews ?? 0); $awards = $this->sanitizeStatValue($statRow->awards ?? 0); DB::transaction(function () use ($legacyId, $username, $email, $passwordHash, $row, $uploads, $downloads, $pageviews, $awards) { $now = now(); DB::table('users')->insert([ 'id' => $legacyId, 'username' => $username, 'name' => $row->real_name ?: $username, 'email' => $email, 'password' => $passwordHash, 'is_active' => (int) ($row->active ?? 1) === 1, 'needs_password_reset' => true, 'role' => 'user', 'legacy_password_algo' => null, 'last_visit_at' => $row->LastVisit ?: null, 'created_at' => $row->joinDate ?: $now, 'updated_at' => $now, ]); DB::table('user_profiles')->insert([ 'user_id' => $legacyId, 'bio' => $row->about_me ?: $row->description ?: null, 'avatar' => $row->picture ?: null, 'cover_image' => $row->cover_art ?: null, 'country' => $row->country ?: null, 'country_code' => $row->country_code ? substr($row->country_code, 0, 2) : null, 'language' => $row->lang ?: null, 'birthdate' => $row->birth ?: null, 'gender' => $row->gender ?: 'X', 'website' => $row->web ?: null, 'created_at' => $now, 'updated_at' => $now, ]); if (!empty($row->web)) { DB::table('user_social_links')->insert([ 'user_id' => $legacyId, 'platform' => 'website', 'url' => $row->web, 'created_at' => $now, 'updated_at' => $now, ]); } DB::table('user_statistics')->insert([ 'user_id' => $legacyId, 'uploads' => $uploads, 'downloads' => $downloads, 'pageviews' => $pageviews, 'awards' => $awards, 'created_at' => $now, 'updated_at' => $now, ]); }); } /** * Ensure statistic values are safe for unsigned DB columns. */ protected function sanitizeStatValue($value): int { $n = is_numeric($value) ? (int) $value : 0; if ($n < 0) { return 0; } return $n; } protected function sanitizeUsername(string $username): string { $username = strtolower(trim($username)); $username = preg_replace('/[^a-z0-9._-]/', '-', $username) ?: 'user'; return trim($username, '.-') ?: 'user'; } protected function uniqueUsername(string $base): string { $name = $base; $i = 1; while (isset($this->usedUsernames[$name]) || DB::table('users')->where('username', $name)->exists()) { $name = $base . '-' . $i; $i++; } $this->usedUsernames[$name] = $name; return $name; } protected function prepareEmail(?string $legacyEmail, string $username): string { $legacyEmail = $legacyEmail ? strtolower(trim($legacyEmail)) : null; $baseLocal = $this->sanitizeEmailLocal($username); $domain = 'users.skinbase.org'; $email = $legacyEmail ?: ($baseLocal . '@' . $domain); $email = $this->uniqueEmail($email, $baseLocal, $domain); return $email; } protected function uniqueEmail(string $email, string $baseLocal, string $domain): string { $i = 1; $local = explode('@', $email)[0]; $current = $email; while (isset($this->usedEmails[$current]) || DB::table('users')->where('email', $current)->exists()) { $current = $local . $i . '@' . $domain; $i++; } $this->usedEmails[$current] = $current; return $current; } protected function sanitizeEmailLocal(string $value): string { $local = strtolower(trim($value)); $local = preg_replace('/[^a-z0-9._-]/', '-', $local) ?: 'user'; return trim($local, '.-') ?: 'user'; } }