messages implemented

This commit is contained in:
2026-02-26 21:12:32 +01:00
parent d0aefc5ddc
commit 15b7b77d20
168 changed files with 14728 additions and 6786 deletions

View File

@@ -0,0 +1,143 @@
<?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];
}
}