Files
SkinbaseNova/app/Console/Commands/ExportLegacyNewsCommentsSqlCommand.php

186 lines
6.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class ExportLegacyNewsCommentsSqlCommand extends Command
{
protected $signature = 'news:comments-export-legacy-sql
{--path=database/sql/news_article_comments_legacy_import.sql : Output SQL file path}
{--skip-empty : Skip comments with empty or whitespace-only content}
{--table= : Override legacy source table name (defaults to auto-detect news_comment/news_comments)}';
protected $description = 'Generate a production-safe SQL file for legacy news comments import';
public function handle(): int
{
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;
}
$outputPath = $this->resolveOutputPath((string) $this->option('path'));
$skipEmpty = (bool) $this->option('skip-empty');
$directory = dirname($outputPath);
if (! is_dir($directory)) {
mkdir($directory, 0777, true);
}
$handle = fopen($outputPath, 'wb');
if ($handle === false) {
$this->error('Unable to write SQL file: ' . $outputPath);
return self::FAILURE;
}
$written = 0;
$skippedEmpty = 0;
$legacyNewsIds = [];
fwrite($handle, "-- Legacy news comments import generated at " . now()->toDateTimeString() . PHP_EOL);
fwrite($handle, "START TRANSACTION;" . PHP_EOL . PHP_EOL);
DB::connection('legacy')
->table($legacyTable)
->orderBy('comment_id')
->chunk(500, function ($rows) use ($handle, $skipEmpty, &$written, &$skippedEmpty, &$legacyNewsIds): void {
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 || $legacyNewsId < 1) {
continue;
}
if ($body === '') {
if ($skipEmpty) {
$skippedEmpty++;
continue;
}
$body = '[no content]';
}
$legacyNewsIds[$legacyNewsId] = $legacyNewsId;
$authorName = trim((string) ($row->author ?? ''));
$timestamp = $this->normalizeTimestamp($row->posted ?? null);
$renderedBody = nl2br(e($body));
$userExpression = $legacyUserId > 0
? "CASE WHEN EXISTS (SELECT 1 FROM users WHERE users.id = {$legacyUserId} AND users.deleted_at IS NULL) THEN {$legacyUserId} ELSE NULL END"
: 'NULL';
$statement = "INSERT IGNORE INTO news_article_comments (legacy_id, legacy_user_id, article_id, user_id, parent_id, author_name, body, rendered_body, status, legacy_posted_at, created_at, updated_at, deleted_at)\n"
. "SELECT {$legacyId}, " . ($legacyUserId > 0 ? (string) $legacyUserId : 'NULL') . ", news_articles.id, {$userExpression}, NULL, " . $this->quote($authorName !== '' ? $authorName : null) . ", " . $this->quote($body) . ", " . $this->quote($renderedBody) . ", 'visible', " . $this->quote($timestamp) . ", " . $this->quote($timestamp) . ", " . $this->quote($timestamp) . ", NULL\n"
. "FROM news_articles\n"
. "WHERE news_articles.legacy_news_id = {$legacyNewsId}\n"
. "LIMIT 1;\n\n";
fwrite($handle, $statement);
$written++;
}
});
if ($legacyNewsIds !== []) {
foreach (array_chunk(array_values($legacyNewsIds), 250) as $chunk) {
fwrite($handle, 'UPDATE news_articles SET comments_enabled = 1 WHERE legacy_news_id IN (' . implode(', ', array_map('intval', $chunk)) . ');' . PHP_EOL);
}
fwrite($handle, PHP_EOL);
}
fwrite($handle, 'COMMIT;' . PHP_EOL);
fclose($handle);
$this->info('SQL export written to ' . $outputPath);
$this->table(
['Result', 'Count'],
[
['Statements written', $written],
['Skipped - empty body', $skippedEmpty],
['Articles enabled for comments', count($legacyNewsIds)],
]
);
return 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 resolveOutputPath(string $path): string
{
$trimmed = trim($path);
if ($trimmed === '') {
return base_path('database/sql/news_article_comments_legacy_import.sql');
}
if (preg_match('/^[A-Za-z]:\\\\|^\\\\\\\\|^\//', $trimmed) === 1) {
return $trimmed;
}
return base_path(str_replace(['/', '\\\\'], DIRECTORY_SEPARATOR, $trimmed));
}
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();
}
}
private function quote(?string $value): string
{
if ($value === null) {
return 'NULL';
}
$escaped = str_replace(
["\\", "\0", "\n", "\r", "\x1a", "'"],
["\\\\", "\\0", "\\n", "\\r", "\\Z", "\\'"],
$value,
);
return "'{$escaped}'";
}
}