companies: Befehl zum Deaktivieren der Legacy-Linkspam-Registrierungen
4.391 aktive Firmen (2010-2022) tragen bbcode-/HTML-Linkspam in den Stammdaten und haben nie eine Meldung veroeffentlicht. Sie werden deaktiviert, nicht geloescht; IDs landen in storage/app, Besitzerkonten bleiben unangetastet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
99ffd012d4
commit
713024be37
3 changed files with 161 additions and 0 deletions
106
app/Console/Commands/RetireLinkSpamCompanies.php
Normal file
106
app/Console/Commands/RetireLinkSpamCompanies.php
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Enums\ActivityType;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Scopes\PortalScope;
|
||||||
|
use App\Services\ActivityLog;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy-Registrierungen mit bbcode-/HTML-Linkspam in den Stammdaten
|
||||||
|
* (Befund 17.09.2026: 4.391 aktive Firmen, Moncler/Chanel-Outlet-Muster,
|
||||||
|
* 2010–2022, nie eine veröffentlichte Meldung) werden deaktiviert — nicht
|
||||||
|
* gelöscht. Sie haben keine Newsroom-Seite, verzerren aber Firmensuche und
|
||||||
|
* Statistik. Die Besitzerkonten bleiben unangetastet: 24 von 44 führen auch
|
||||||
|
* saubere Firmen.
|
||||||
|
*
|
||||||
|
* Reversibel: Die IDs landen in einer Datei unter storage/app, und
|
||||||
|
* `is_active` lässt sich zurücksetzen.
|
||||||
|
*/
|
||||||
|
class RetireLinkSpamCompanies extends Command
|
||||||
|
{
|
||||||
|
private const MARKERS = ['[url=', '<a href'];
|
||||||
|
|
||||||
|
protected $signature = 'companies:retire-link-spam
|
||||||
|
{--dry-run : Nur zählen und Beispiele zeigen, nichts schreiben}';
|
||||||
|
|
||||||
|
protected $description = 'Deaktiviert Firmen mit bbcode-/HTML-Linkspam in den Stammdaten, die nie eine Meldung veröffentlicht haben.';
|
||||||
|
|
||||||
|
public function handle(ActivityLog $activityLog): int
|
||||||
|
{
|
||||||
|
$query = $this->candidates();
|
||||||
|
$count = (clone $query)->count();
|
||||||
|
|
||||||
|
if ($count === 0) {
|
||||||
|
$this->info('Keine Linkspam-Firmen gefunden.');
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
['ID', 'Name', 'Angelegt', 'Adresse (Anfang)'],
|
||||||
|
(clone $query)->limit(10)->get()->map(fn (Company $company): array => [
|
||||||
|
$company->id,
|
||||||
|
mb_strimwidth((string) $company->name, 0, 50, '…'),
|
||||||
|
$company->created_at?->format('d.m.Y'),
|
||||||
|
mb_strimwidth(str_replace(["\r", "\n"], ' ', (string) $company->address), 0, 60, '…'),
|
||||||
|
])->all(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->option('dry-run')) {
|
||||||
|
$this->info(sprintf('[Dry-Run] %d Firmen würden deaktiviert (10 Beispiele oben).', $count));
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = (clone $query)->pluck('id');
|
||||||
|
$file = sprintf('companies-link-spam-%s.txt', now()->format('Y-m-d-His'));
|
||||||
|
Storage::disk('local')->put($file, $ids->implode("\n"));
|
||||||
|
|
||||||
|
$retired = 0;
|
||||||
|
|
||||||
|
foreach ($ids->chunk(500) as $chunk) {
|
||||||
|
$retired += Company::query()
|
||||||
|
->withoutGlobalScope(PortalScope::class)
|
||||||
|
->whereIn('id', $chunk->all())
|
||||||
|
->update(['is_active' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$activityLog->record(
|
||||||
|
ActivityType::Correction,
|
||||||
|
sprintf('%d Legacy-Firmen mit Linkspam in den Stammdaten deaktiviert (IDs in storage/app/%s).', $retired, $file),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
['count' => $retired, 'file' => $file],
|
||||||
|
'system',
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->info(sprintf('%d Firmen deaktiviert. IDs: storage/app/%s', $retired, $file));
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Builder<Company>
|
||||||
|
*/
|
||||||
|
private function candidates(): Builder
|
||||||
|
{
|
||||||
|
return Company::query()
|
||||||
|
->withoutGlobalScope(PortalScope::class)
|
||||||
|
->where('is_active', true)
|
||||||
|
->whereNull('gone_at')
|
||||||
|
->where(function (Builder $query): void {
|
||||||
|
foreach (['address', 'boilerplate', 'name'] as $column) {
|
||||||
|
foreach (self::MARKERS as $marker) {
|
||||||
|
$query->orWhere($column, 'like', '%'.$marker.'%');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->whereDoesntHave('pressReleases', fn (Builder $q) => $q->withoutGlobalScope(PortalScope::class)->published())
|
||||||
|
->orderBy('id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1527,3 +1527,13 @@ wird der Host zum Namen („ak-autos.de"). Bestehende Website bleibt.
|
||||||
|
|
||||||
Tests: `CompanyNameNormalizerTest` (11). Live: `companies:strip-links-from-names
|
Tests: `CompanyNameNormalizerTest` (11). Live: `companies:strip-links-from-names
|
||||||
--dry-run`, Liste ansehen, dann ohne Flag.
|
--dry-run`, Liste ansehen, dann ohne Flag.
|
||||||
|
|
||||||
|
**bbcode-Leichen (Nachtrag, auf Wunsch):** `companies:retire-link-spam
|
||||||
|
{--dry-run}` deaktiviert aktive Firmen mit `[url=` oder `<a href` in
|
||||||
|
Adresse, Beschreibung oder Name, die nie eine Meldung veröffentlicht haben
|
||||||
|
— kein Löschen, `is_active = false`, IDs in `storage/app/companies-link-
|
||||||
|
spam-<zeitstempel>.txt`, ein ActivityLog-Eintrag mit Zahl. Vorher geprüft:
|
||||||
|
Die 1.302 davon mit Meldungen haben nur `rejected` (bis 2019) und
|
||||||
|
`archived` (bis 2022), nichts Öffentliches. Die 44 Besitzerkonten bleiben
|
||||||
|
unangetastet — 24 führen auch saubere Firmen. Dry-Run Dev-DB: 4.391.
|
||||||
|
Test: `RetireLinkSpamCompaniesTest`.
|
||||||
|
|
|
||||||
45
tests/Feature/RetireLinkSpamCompaniesTest.php
Normal file
45
tests/Feature/RetireLinkSpamCompaniesTest.php
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\Portal;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\PressRelease;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy-Registrierungen mit bbcode-/HTML-Linkspam werden deaktiviert —
|
||||||
|
* nur ohne veröffentlichte Meldung, nie gelöscht (17.09.2026).
|
||||||
|
*/
|
||||||
|
test('link spam companies without a published release are deactivated, the rest stays', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
Storage::fake('local');
|
||||||
|
|
||||||
|
$spam = Company::factory()->businessportal24()->create(['address' => '<strong><a href="http://www.outlet.example/">bags</a></strong>']);
|
||||||
|
$bbcode = Company::factory()->presseecho()->create(['boilerplate' => '[b][url=http://www.copy.example]replica[/url][/b]']);
|
||||||
|
$publicWithSpam = Company::factory()->businessportal24()->create(['address' => '[url=http://www.x.example]x[/url]']);
|
||||||
|
PressRelease::factory()->published()->forPortal(Portal::Businessportal24)->create(['company_id' => $publicWithSpam->id]);
|
||||||
|
$clean = Company::factory()->businessportal24()->create(['address' => 'Musterstraße 1, 12345 Musterstadt']);
|
||||||
|
|
||||||
|
$this->artisan('companies:retire-link-spam', ['--dry-run' => true])
|
||||||
|
->expectsOutputToContain('[Dry-Run] 2 Firmen würden deaktiviert')
|
||||||
|
->assertSuccessful();
|
||||||
|
|
||||||
|
expect($spam->fresh()->is_active)->toBeTrue();
|
||||||
|
|
||||||
|
$this->artisan('companies:retire-link-spam')
|
||||||
|
->expectsOutputToContain('2 Firmen deaktiviert.')
|
||||||
|
->assertSuccessful();
|
||||||
|
|
||||||
|
expect($spam->fresh()->is_active)->toBeFalse()
|
||||||
|
->and($bbcode->fresh()->is_active)->toBeFalse()
|
||||||
|
->and($publicWithSpam->fresh()->is_active)->toBeTrue()
|
||||||
|
->and($clean->fresh()->is_active)->toBeTrue()
|
||||||
|
->and($spam->fresh()->deleted_at)->toBeNull();
|
||||||
|
|
||||||
|
$files = Storage::disk('local')->files();
|
||||||
|
expect($files)->toHaveCount(1)
|
||||||
|
->and(Storage::disk('local')->get($files[0]))->toBe($spam->id."\n".$bbcode->id);
|
||||||
|
|
||||||
|
// Zweiter Lauf findet nichts mehr.
|
||||||
|
$this->artisan('companies:retire-link-spam')->expectsOutputToContain('Keine Linkspam-Firmen gefunden.');
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue