companies: Kurzfelder des Profils ohne Links, Adressen und Spam (NoPromotionalText)

Firmenname, Adresse und Pressekontakt erscheinen oeffentlich bzw. in
Mails, wurden aber nicht geprueft — nur die Beschreibung lief durch die
Rot-Ebene. Neue deterministische Regel: keine Links, keine E-Mail-
Adressen, keine Zeichen-/Emoji-Ketten, kein Blacklist-Treffer; nackte
Domains ohne Pfad bleiben erlaubt (Marke). Adresse zusaetzlich durch die
Rot-Ebene wie die Beschreibung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-17 07:55:07 +00:00
parent 4dffb1368f
commit 5d8e379e85
5 changed files with 246 additions and 7 deletions

View file

@ -3,6 +3,7 @@
use App\Enums\CompanyType;
use App\Enums\Portal;
use App\Models\Company;
use App\Rules\NoPromotionalText;
use Flux\Flux;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
@ -37,7 +38,7 @@ new #[Layout('components.layouts.app'), Title('Neue Firma anlegen')] class exten
{
try {
$validated = $this->validate([
'name' => ['required', 'string', 'max:255'],
'name' => ['required', 'string', 'max:255', app(NoPromotionalText::class)],
// „Beide Portale" ist bei der Firmen-Anlage bewusst nicht
// wählbar: PMs übernehmen das Portal der Firma und würden
// sonst automatisch auf beiden Portalen veröffentlicht.
@ -45,7 +46,7 @@ new #[Layout('components.layouts.app'), Title('Neue Firma anlegen')] class exten
Portal::Presseecho->value,
Portal::Businessportal24->value,
])],
'address' => ['nullable', 'string', 'max:1000'],
'address' => ['nullable', 'string', 'max:1000', app(NoPromotionalText::class)],
'email' => ['nullable', 'email', 'max:190'],
'phone' => ['nullable', 'string', 'max:40'],
'website' => ['nullable', 'url', 'max:190'],

View file

@ -3,6 +3,7 @@
use App\Models\Company;
use App\Models\Contact;
use App\Models\PressRelease;
use App\Rules\NoPromotionalText;
use App\Services\Company\CompanyProfileCheck;
use App\Services\Conversion\ConversionRecorder;
use App\Services\Customer\CustomerCompanyContext;
@ -100,8 +101,10 @@ new #[Layout('components.layouts.app'), Title('Firma')] class extends Component
$this->authorize('update', $company);
$validated = $this->validate([
'companyName' => ['required', 'string', 'max:255'],
'companyAddress' => ['nullable', 'string', 'max:1000'],
// Öffentliche Kurzfelder sind keine Werbefläche: keine Links,
// keine Adressen, kein Blacklist-Treffer (NoPromotionalText).
'companyName' => ['required', 'string', 'max:255', app(NoPromotionalText::class)],
'companyAddress' => ['nullable', 'string', 'max:1000', app(NoPromotionalText::class)],
'companyEmail' => ['nullable', 'email', 'max:190'],
'companyPhone' => ['nullable', 'string', 'max:40'],
'companyWebsite' => ['nullable', 'url', 'max:190'],
@ -114,14 +117,22 @@ new #[Layout('components.layouts.app'), Title('Firma')] class extends Component
]);
$boilerplate = trim(strip_tags((string) $validated['companyBoilerplate']));
$address = trim((string) $validated['companyAddress']);
// Rot-Ebene der bestehenden Prüfung — dieselbe wie für eine Meldung.
// Gilt für beide Fließtextfelder, jeweils nur bei Änderung.
if ($boilerplate !== '' && $boilerplate !== trim((string) $company->boilerplate) && $profileCheck->isRed($company, $boilerplate)) {
throw ValidationException::withMessages([
'companyBoilerplate' => __('Die Beschreibung enthält unzulässige Inhalte und kann so nicht gespeichert werden.'),
]);
}
if ($address !== '' && $address !== trim((string) $company->address) && $profileCheck->isRed($company, $address)) {
throw ValidationException::withMessages([
'companyAddress' => __('Die Adresse enthält unzulässige Inhalte und kann so nicht gespeichert werden.'),
]);
}
$company->fill([
'name' => $validated['companyName'],
'address' => $validated['companyAddress'] ?: null,
@ -195,9 +206,9 @@ new #[Layout('components.layouts.app'), Title('Firma')] class extends Component
$this->authorize('update', $company);
$validated = $this->validate([
'contactFirstName' => ['nullable', 'string', 'max:80'],
'contactLastName' => ['nullable', 'string', 'max:80'],
'contactResponsibility' => ['nullable', 'string', 'max:255'],
'contactFirstName' => ['nullable', 'string', 'max:80', app(NoPromotionalText::class)],
'contactLastName' => ['nullable', 'string', 'max:80', app(NoPromotionalText::class)],
'contactResponsibility' => ['nullable', 'string', 'max:255', app(NoPromotionalText::class)],
'contactEmail' => ['required', 'email', 'max:255'],
'contactPhone' => ['nullable', 'string', 'max:40'],
]);