Files
SkinbaseNova/app/Console/Commands/MigrateSmileys.php
2026-02-26 21:12:32 +01:00

144 lines
5.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Console\Commands;
use App\Services\LegacySmileyMapper;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* php artisan skinbase:migrate-smileys
*
* Scans artworks.description, artwork_comments.content, and forum_posts.content,
* replaces legacy smiley codes (:beer, :lol, etc.) with Unicode emoji.
*
* Options:
* --dry-run Show what would change without writing to DB
* --chunk=200 Rows processed per batch (default 200)
* --table=artworks Limit scan to one table
*/
class MigrateSmileys extends Command
{
protected $signature = 'skinbase:migrate-smileys
{--dry-run : Preview changes without writing to the database}
{--chunk=200 : Number of rows to process per batch}
{--table= : Limit scan to a single table (artworks|artwork_comments|forum_posts)}';
protected $description = 'Convert legacy :smiley: codes to Unicode emoji in content fields.';
/** Tables and their content columns to scan. */
private const TARGETS = [
'artworks' => 'description',
'artwork_comments' => 'content',
'forum_posts' => 'content',
];
public function handle(): int
{
$dryRun = (bool) $this->option('dry-run');
$chunk = max(1, (int) $this->option('chunk'));
$tableOpt = $this->option('table');
$targets = self::TARGETS;
if ($tableOpt) {
if (! isset($targets[$tableOpt])) {
$this->error("Unknown table: {$tableOpt}. Allowed: " . implode(', ', array_keys($targets)));
return self::FAILURE;
}
$targets = [$tableOpt => $targets[$tableOpt]];
}
if ($dryRun) {
$this->warn('DRY-RUN mode — no changes will be written.');
}
$totalChanged = 0;
$totalRows = 0;
foreach ($targets as $table => $column) {
$this->line("Scanning <info>{$table}.{$column}</info>…");
[$changed, $rows] = $this->processTable($table, $column, $chunk, $dryRun);
$totalChanged += $changed;
$totalRows += $rows;
$this->line("{$rows} rows scanned, {$changed} updated.");
}
$this->newLine();
$this->info("Summary: {$totalRows} rows scanned, {$totalChanged} rows " . ($dryRun ? 'would be ' : '') . 'updated.');
return self::SUCCESS;
}
private function processTable(
string $table,
string $column,
int $chunk,
bool $dryRun
): array {
$totalChanged = 0;
$totalRows = 0;
DB::table($table)
->whereNotNull($column)
->orderBy('id')
->chunk($chunk, function ($rows) use ($table, $column, $dryRun, &$totalChanged, &$totalRows) {
foreach ($rows as $row) {
$original = $row->$column ?? '';
$converted = LegacySmileyMapper::convert($original);
// Collapse emoji flood runs BEFORE size/DB checks so that
// rows like ":beer :beer :beer …" (×500) don't exceed MEDIUMTEXT.
$collapsed = LegacySmileyMapper::collapseFlood($converted);
if ($collapsed !== $converted) {
$beforeBytes = mb_strlen($converted, '8bit');
$afterBytes = mb_strlen($collapsed, '8bit');
$floodMsg = "[{$table}#{$row->id}] Emoji flood collapsed "
. "({$beforeBytes} bytes \u{2192} {$afterBytes} bytes).";
$this->warn(" {$floodMsg}");
Log::warning($floodMsg);
$converted = $collapsed;
}
$totalRows++;
if ($converted === $original) {
continue;
}
$totalChanged++;
$codes = LegacySmileyMapper::detect($original);
$msg = "[{$table}#{$row->id}] Converting: " . implode(', ', $codes);
$this->line(" {$msg}");
Log::info($msg);
if (! $dryRun) {
// Guard: MEDIUMTEXT max is 16,777,215 bytes.
if (mb_strlen($converted, '8bit') > 16_777_215) {
$warn = "[{$table}#{$row->id}] SKIP — converted content exceeds MEDIUMTEXT limit (" . mb_strlen($converted, '8bit') . " bytes). Row left unchanged.";
$this->warn(" {$warn}");
Log::warning($warn);
continue;
}
try {
DB::table($table)
->where('id', $row->id)
->update([$column => $converted]);
} catch (\Throwable $e) {
$err = "[{$table}#{$row->id}] DB error: {$e->getMessage()}";
$this->warn(" {$err}");
Log::error($err);
}
}
}
});
return [$totalChanged, $totalRows];
}
}