option('dry-run'); $chunk = max(1, (int) $this->option('chunk')); $skipEmpty = (bool) $this->option('skip-empty'); try { DB::connection('legacy')->getPdo(); } catch (\Throwable $exception) { $this->error('Cannot connect to legacy database: ' . $exception->getMessage()); return self::FAILURE; } $legacyTable = $this->resolveLegacyTable(); if ($legacyTable === null) { $this->error('Legacy table `news_comment` or `news_comments` was not found.'); return self::FAILURE; } if (! DB::getSchemaBuilder()->hasTable('news_article_comments')) { $this->error('Target table `news_article_comments` is missing. Run migrations first.'); return self::FAILURE; } if (! DB::getSchemaBuilder()->hasColumn('news_articles', 'legacy_news_id')) { $this->error('Column `news_articles.legacy_news_id` is missing. Run migrations first.'); return self::FAILURE; } if ($dryRun) { $this->warn('[DRY-RUN] No data will be written.'); } $articleMap = DB::table('news_articles') ->whereNotNull('legacy_news_id') ->pluck('id', 'legacy_news_id') ->mapWithKeys(fn ($articleId, $legacyId): array => [(int) $legacyId => (int) $articleId]) ->all(); $validUserIds = DB::table('users') ->whereNull('deleted_at') ->pluck('id') ->flip() ->all(); $alreadyImported = DB::table('news_article_comments') ->whereNotNull('legacy_id') ->pluck('legacy_id') ->flip() ->all(); $total = DB::connection('legacy')->table($legacyTable)->count(); if ($total === 0) { $this->warn('No legacy news comments found.'); return self::SUCCESS; } $stats = [ 'imported' => 0, 'skipped_duplicate' => 0, 'skipped_article' => 0, 'skipped_empty' => 0, 'users_unmapped' => 0, 'errors' => 0, ]; $touchedArticleIds = []; DB::connection('legacy') ->table($legacyTable) ->orderBy('comment_id') ->chunk($chunk, function ($rows) use (&$alreadyImported, $articleMap, $validUserIds, $dryRun, $skipEmpty, &$stats, &$touchedArticleIds): void { $inserts = []; foreach ($rows as $row) { $legacyId = (int) ($row->comment_id ?? 0); $legacyNewsId = (int) ($row->news_id ?? 0); $legacyUserId = (int) ($row->user_id ?? 0); $body = trim((string) ($row->message ?? '')); if ($legacyId < 1) { $stats['errors']++; continue; } if (isset($alreadyImported[$legacyId])) { $stats['skipped_duplicate']++; continue; } if ($body === '') { if ($skipEmpty) { $stats['skipped_empty']++; continue; } $body = '[no content]'; } $articleId = $articleMap[$legacyNewsId] ?? null; if (! $articleId) { $stats['skipped_article']++; continue; } $resolvedUserId = isset($validUserIds[$legacyUserId]) ? $legacyUserId : null; if ($resolvedUserId === null && $legacyUserId > 0) { $stats['users_unmapped']++; } $timestamp = $this->normalizeTimestamp($row->posted ?? null); $authorName = trim((string) ($row->author ?? '')); $record = [ 'legacy_id' => $legacyId, 'legacy_user_id' => $legacyUserId > 0 ? $legacyUserId : null, 'article_id' => $articleId, 'user_id' => $resolvedUserId, 'parent_id' => null, 'author_name' => $authorName !== '' ? $authorName : null, 'body' => $body, 'rendered_body' => nl2br(e($body)), 'status' => 'visible', 'legacy_posted_at' => $timestamp, 'created_at' => $timestamp, 'updated_at' => $timestamp, 'deleted_at' => null, ]; if (! $dryRun) { $inserts[] = $record; $alreadyImported[$legacyId] = true; $touchedArticleIds[$articleId] = $articleId; } $stats['imported']++; } if (! $dryRun && $inserts !== []) { try { DB::table('news_article_comments')->insert($inserts); } catch (\Throwable) { foreach ($inserts as $insert) { try { DB::table('news_article_comments')->insertOrIgnore([$insert]); } catch (\Throwable) { $stats['errors']++; } } } } }); if (! $dryRun && $touchedArticleIds !== []) { DB::table('news_articles') ->whereIn('id', array_values($touchedArticleIds)) ->update(['comments_enabled' => true]); } $this->table( ['Result', 'Count'], [ ['Imported', $stats['imported']], ['Skipped - already imported', $stats['skipped_duplicate']], ['Skipped - article missing', $stats['skipped_article']], ['Skipped - empty body', $stats['skipped_empty']], ['Imported with unmapped user', $stats['users_unmapped']], ['Errors', $stats['errors']], ] ); return $stats['errors'] > 0 ? self::FAILURE : self::SUCCESS; } private function resolveLegacyTable(): ?string { $configured = trim((string) $this->option('table')); if ($configured !== '') { return DB::connection('legacy')->getSchemaBuilder()->hasTable($configured) ? $configured : null; } foreach (['news_comment', 'news_comments'] as $candidate) { if (DB::connection('legacy')->getSchemaBuilder()->hasTable($candidate)) { return $candidate; } } return null; } private function normalizeTimestamp(mixed $value): string { $raw = trim((string) ($value ?? '')); if ($raw === '' || str_starts_with($raw, '0000-00-00')) { return now()->toDateTimeString(); } try { return Carbon::parse($raw)->toDateTimeString(); } catch (\Throwable) { return now()->toDateTimeString(); } } }