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:
parent
4fd1db7825
commit
87ca9082a9
5 changed files with 246 additions and 7 deletions
74
app/Rules/NoPromotionalText.php
Normal file
74
app/Rules/NoPromotionalText.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue