Die Mail „Ihre Pressemitteilung wird persönlich geprüft“ verlinkte auch bei API-Meldungen in den Kundenbereich. Empfänger ohne Konto landeten auf der Passwort-Anmeldung. Wie in der Veröffentlichungs- und Ablehnungsmail schickt jetzt ein Klick einen Anmeldelink an die Adresse der Mail; die Zugangsseite übernimmt die Herkunft mail_review für den Funnel. Changelog: Verbessert Public: Auch die Mail zur persönlichen Prüfung führt Pressekontakte ohne Passwort zu ihrer Meldung. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
2.2 KiB
PHP
63 lines
2.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Auth;
|
|
|
|
use App\Enums\ConversionSource;
|
|
use App\Models\PressRelease;
|
|
use Illuminate\Contracts\Encryption\DecryptException;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\URL;
|
|
|
|
/**
|
|
* „Anmeldelink an diese Adresse“ aus einer Kundenmail (Roadmap 6.17).
|
|
*
|
|
* Veröffentlichungs-, Ablehnungs- und Prüfmails zu API-Meldungen gehen an den
|
|
* Pressekontakt oder die Firmen-E-Mail — Empfänger, die aus ihrer Sicht kein
|
|
* Konto haben. Der Link führt auf eine Seite, die mit einem Klick einen
|
|
* Anmeldelink an genau die Adresse schickt, an die die Mail ging, und nach
|
|
* der Anmeldung auf der Meldung landet.
|
|
*
|
|
* Der Link meldet niemanden an: Er löst nur einen Anmeldelink an eine
|
|
* Adresse aus, die ihn ohnehin bekommen könnte (`/anmeldelink`). Signiert und
|
|
* befristet, die Adresse verschlüsselt — sie steht nicht lesbar in der URL,
|
|
* in Logs oder in der Webanalyse. `src` und `pr` stehen zusätzlich im Klartext
|
|
* für die Klickmessung (RecordMailLinkClick).
|
|
*/
|
|
class MailAccessLink
|
|
{
|
|
public const int VALID_DAYS = 30;
|
|
|
|
public function url(string $email, PressRelease $pressRelease, ConversionSource $source): string
|
|
{
|
|
return URL::temporarySignedRoute('mail-access', now()->addDays(self::VALID_DAYS), [
|
|
't' => Crypt::encryptString((string) json_encode([
|
|
'e' => mb_strtolower(trim($email)),
|
|
'p' => $pressRelease->getKey(),
|
|
])),
|
|
'src' => $source->value,
|
|
'pr' => $pressRelease->getKey(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return array{email: string, press_release_id: int}|null
|
|
*/
|
|
public function decode(?string $token): ?array
|
|
{
|
|
if (blank($token)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$data = json_decode(Crypt::decryptString((string) $token), true);
|
|
} catch (DecryptException) {
|
|
return null;
|
|
}
|
|
|
|
if (! is_array($data) || ! is_string($data['e'] ?? null) || ! filter_var($data['e'], FILTER_VALIDATE_EMAIL) || (int) ($data['p'] ?? 0) <= 0) {
|
|
return null;
|
|
}
|
|
|
|
return ['email' => $data['e'], 'press_release_id' => (int) $data['p']];
|
|
}
|
|
}
|