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>
74 lines
2.6 KiB
PHP
74 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Rules;
|
|
|
|
use App\Services\PressRelease\BlacklistService;
|
|
use Closure;
|
|
use Illuminate\Contracts\Validation\ValidationRule;
|
|
|
|
/**
|
|
* Kurzfelder, die öffentlich oder in Mails erscheinen (Firmenname, Adresse,
|
|
* Pressekontakt), sind keine Werbefläche: keine Links, keine
|
|
* E-Mail-Adressen, kein Blacklist-Treffer, keine Zeichenorgien.
|
|
*
|
|
* Bewusst deterministisch und eng — eine KI für sechs Wörter wäre langsam,
|
|
* und falsche Treffer kosten hier echte Firmen den Namen. Nackte Domains
|
|
* („meinshop.de GmbH") bleiben deshalb erlaubt: Sie sind Marke, und die
|
|
* Website ist ohnehin das eine systemseitig ausgezeichnete Linkfeld.
|
|
* Der Fließtext („Über das Unternehmen") läuft separat durch die Rot-Ebene
|
|
* der Meldungsprüfung (CompanyProfileCheck).
|
|
*/
|
|
class NoPromotionalText implements ValidationRule
|
|
{
|
|
/** Schema, www. — oder eine nackte Domain mit Pfad („spam.example/deal"). */
|
|
private const LINK_PATTERN = '~(?:https?://|www\.|\b[a-z0-9-]+\.[a-z]{2,}/\S)~iu';
|
|
|
|
private const EMAIL_PATTERN = '~[^\s@]+@[^\s@]+\.[a-z]{2,}~iu';
|
|
|
|
/** Drei und mehr Satz-/Währungszeichen am Stück: „!!!", „€€€", „$$$". */
|
|
private const NOISE_PATTERN = '~[!?$€£%*#]{3,}~u';
|
|
|
|
/** Drei und mehr Emoji/Symbole am Stück. */
|
|
private const EMOJI_PATTERN = '~(?:[\x{1F300}-\x{1FAFF}\x{2600}-\x{27BF}]\x{FE0F}?){3,}~u';
|
|
|
|
public function __construct(private readonly BlacklistService $blacklist) {}
|
|
|
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
|
{
|
|
if (! is_string($value) || trim($value) === '') {
|
|
return;
|
|
}
|
|
|
|
$reason = self::reasonFor($value, $this->blacklist);
|
|
|
|
if ($reason !== null) {
|
|
$fail($reason);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Grund der Ablehnung als Nutzertext, oder null, wenn der Text sauber ist.
|
|
*/
|
|
public static function reasonFor(string $value, BlacklistService $blacklist): ?string
|
|
{
|
|
if (preg_match(self::LINK_PATTERN, $value) === 1) {
|
|
return __('Links sind in diesem Feld nicht erlaubt. Die Website gehört in das Feld „Website".');
|
|
}
|
|
|
|
if (preg_match(self::EMAIL_PATTERN, $value) === 1) {
|
|
return __('E-Mail-Adressen gehören in das dafür vorgesehene Feld.');
|
|
}
|
|
|
|
if (preg_match(self::NOISE_PATTERN, $value) === 1 || preg_match(self::EMOJI_PATTERN, $value) === 1) {
|
|
return __('Bitte ohne Sonderzeichen-Ketten oder Emoji-Reihen.');
|
|
}
|
|
|
|
$word = $blacklist->find($value);
|
|
|
|
if ($word !== null) {
|
|
return __('Der Text enthält einen unzulässigen Begriff.');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|