messages implemented
This commit is contained in:
188
app/Console/Commands/SanitizeContent.php
Normal file
188
app/Console/Commands/SanitizeContent.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\ContentSanitizer;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* php artisan skinbase:sanitize-content
|
||||
*
|
||||
* Scans legacy content for unsafe HTML, converts it to Markdown-safe text,
|
||||
* and populates the raw_content / rendered_content columns on artwork_comments.
|
||||
*
|
||||
* Options:
|
||||
* --dry-run Preview changes without writing
|
||||
* --chunk=200 Rows per batch
|
||||
* --table= Limit to one target
|
||||
* --artwork-id= Limit to a single artwork (filters artwork_comments by artwork_id, artworks by id)
|
||||
*/
|
||||
class SanitizeContent extends Command
|
||||
{
|
||||
protected $signature = 'skinbase:sanitize-content
|
||||
{--dry-run : Preview changes without writing to the database}
|
||||
{--chunk=200 : Number of rows per batch}
|
||||
{--table= : Limit scan to a single target (artwork_comments|artworks|forum_posts)}
|
||||
{--artwork-id= : Limit scan to a single artwork ID (skips forum_posts)}';
|
||||
|
||||
protected $description = 'Strip unsafe HTML from legacy content and populate sanitized columns.';
|
||||
|
||||
/**
|
||||
* table => [read_col, write_raw_col, write_rendered_col|null]
|
||||
*
|
||||
* For artwork_comments we write two columns; for the others we only sanitize in-place.
|
||||
*/
|
||||
private const TARGETS = [
|
||||
'artwork_comments' => [
|
||||
'read' => 'content',
|
||||
'write_raw' => 'raw_content',
|
||||
'write_rendered' => 'rendered_content',
|
||||
],
|
||||
'artworks' => [
|
||||
'read' => 'description',
|
||||
'write_raw' => 'description',
|
||||
'write_rendered' => null,
|
||||
],
|
||||
'forum_posts' => [
|
||||
'read' => 'content',
|
||||
'write_raw' => 'content',
|
||||
'write_rendered' => null,
|
||||
],
|
||||
];
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
$chunk = max(1, (int) $this->option('chunk'));
|
||||
$tableOpt = $this->option('table');
|
||||
$artworkId = $this->option('artwork-id');
|
||||
|
||||
if ($artworkId !== null) {
|
||||
if (! ctype_digit((string) $artworkId) || (int) $artworkId < 1) {
|
||||
$this->error("--artwork-id must be a positive integer. Got: {$artworkId}");
|
||||
return self::FAILURE;
|
||||
}
|
||||
$artworkId = (int) $artworkId;
|
||||
}
|
||||
|
||||
$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]];
|
||||
}
|
||||
|
||||
// --artwork-id removes forum_posts (no artwork FK) and informs the user.
|
||||
if ($artworkId !== null) {
|
||||
unset($targets['forum_posts']);
|
||||
$this->line("Filtering to artwork <info>#{$artworkId}</info> (forum_posts skipped).");
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn('DRY-RUN mode — no changes will be written.');
|
||||
}
|
||||
|
||||
$totalModified = 0;
|
||||
$totalRows = 0;
|
||||
|
||||
foreach ($targets as $table => $def) {
|
||||
$this->line("Processing <info>{$table}</info>…");
|
||||
|
||||
[$modified, $rows] = $this->processTable($table, $def, $chunk, $dryRun, $artworkId);
|
||||
$totalModified += $modified;
|
||||
$totalRows += $rows;
|
||||
|
||||
$this->line(" → {$rows} rows scanned, {$modified} modified.");
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info("Summary: {$totalRows} rows, {$totalModified} " . ($dryRun ? 'would be ' : '') . 'modified.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function processTable(
|
||||
string $table,
|
||||
array $def,
|
||||
int $chunk,
|
||||
bool $dryRun,
|
||||
?int $artworkId = null
|
||||
): array {
|
||||
$totalModified = 0;
|
||||
$totalRows = 0;
|
||||
|
||||
$readCol = $def['read'];
|
||||
$writeRawCol = $def['write_raw'];
|
||||
$writeRenderedCol = $def['write_rendered'];
|
||||
|
||||
DB::table($table)
|
||||
->whereNotNull($readCol)
|
||||
->when($artworkId !== null, function ($q) use ($table, $artworkId) {
|
||||
// artwork_comments has artwork_id; artworks is filtered by its own PK.
|
||||
$filterCol = $table === 'artwork_comments' ? 'artwork_id' : 'id';
|
||||
$q->where($filterCol, $artworkId);
|
||||
})
|
||||
->orderBy('id')
|
||||
->chunk($chunk, function ($rows) use (
|
||||
$table, $readCol, $writeRawCol, $writeRenderedCol,
|
||||
$dryRun, &$totalModified, &$totalRows
|
||||
) {
|
||||
foreach ($rows as $row) {
|
||||
$original = $row->$readCol ?? '';
|
||||
$stripped = ContentSanitizer::stripToPlain($original);
|
||||
|
||||
$totalRows++;
|
||||
|
||||
// Detect if content had HTML that we need to clean
|
||||
$hadHtml = $original !== $stripped && preg_match('/<[a-z][^>]*>/i', $original);
|
||||
|
||||
if ($writeRawCol === $readCol && ! $hadHtml) {
|
||||
// Same column, no HTML, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
$rendered = ContentSanitizer::render($stripped);
|
||||
$totalModified++;
|
||||
|
||||
if ($hadHtml) {
|
||||
$this->line(" [{$table}#{$row->id}] Stripped HTML from content.");
|
||||
Log::info("skinbase:sanitize-content stripped HTML from {$table}#{$row->id}");
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$update = [$writeRawCol => $stripped];
|
||||
|
||||
if ($writeRenderedCol) {
|
||||
$update[$writeRenderedCol] = $rendered;
|
||||
}
|
||||
|
||||
DB::table($table)->where('id', $row->id)->update($update);
|
||||
}
|
||||
|
||||
// Also populate rendered_content for rows that have raw_content but no rendered_content
|
||||
if ($writeRenderedCol && ! $dryRun) {
|
||||
DB::table($table)
|
||||
->whereNotNull($writeRawCol)
|
||||
->whereNull($writeRenderedCol)
|
||||
->orderBy('id')
|
||||
->chunk(200, function ($missing) use ($table, $writeRawCol, $writeRenderedCol) {
|
||||
foreach ($missing as $row) {
|
||||
$rendered = ContentSanitizer::render($row->$writeRawCol ?? '');
|
||||
DB::table($table)->where('id', $row->id)->update([
|
||||
$writeRenderedCol => $rendered,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return [$totalModified, $totalRows];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user