newsroom: Hinweis „Diesen Newsroom verwalten“ auf der Seite und in der Mail (Roadmap 1b.4, E4)

E4 (15.09.2026): Die Veröffentlichungsmail trägt den Hinweis am Ende, als
Hinweis, nicht als Angebot. Auf der Newsroom-Seite eine Zeile unter dem
Profil. Der Mail-Link ist mit src=mail_publish getaggt; die Claim-Seite
reicht die Herkunft bis auf das Token durch, sodass die Leiter „aus der
Mail beansprucht“ von „auf der Seite entdeckt“ unterscheidet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-15 11:05:53 +00:00
parent 0c1c072487
commit 7f714fb664
11 changed files with 141 additions and 5 deletions

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Web;
use App\Enums\ConversionSource;
use App\Http\Controllers\Controller;
use App\Models\Company;
use App\Scopes\PortalScope;
@ -68,7 +69,7 @@ class NewsroomClaimController extends Controller
// Enumeration-sicher: dieselbe Meldung, ob ein Konto deaktiviert ist
// oder nicht — die Adresse ist ohnehin nur maskiert sichtbar.
$this->claims->request($company, $request->ip());
$this->claims->request($company, $request->ip(), $this->sourceOf($request));
return back()->with('newsroom-claim-status', __('static.newsroom_claim.flash_sent'));
}
@ -101,6 +102,20 @@ class NewsroomClaimController extends Controller
abort(404);
}
/**
* Kam der Besucher aus einer Mail (`src` aus dem Link, per Formular
* weitergereicht), trägt das Token diese Herkunft — sonst ist es der
* Hinweis auf der Newsroom-Seite.
*/
private function sourceOf(Request $request): ConversionSource
{
$source = ConversionSource::parse($request->filled('src') ? (string) $request->input('src') : null);
return in_array($source, [ConversionSource::MailPublish, ConversionSource::MailReject], true)
? $source
: ConversionSource::ProfileClaim;
}
private function ensureNotRateLimited(Request $request, int $companyId): void
{
$perDay = (int) config('newsroom.claim.links_per_day', 3);

View file

@ -33,6 +33,12 @@ class PressReleasePublished extends Mailable
*/
public string $recipientName;
/**
* „Diesen Newsroom verwalten" (Roadmap 1b.4, Entscheidung E4 vom
* 15.09.2026): Hinweis am Ende der Mail, kein Angebot. Null ohne Firma.
*/
public ?string $manageNewsroomUrl = null;
public function __construct(
public readonly User $user,
public readonly PressRelease $pressRelease,
@ -54,9 +60,30 @@ class PressReleasePublished extends Mailable
$pressRelease->publicPortalLinks(),
);
$this->manageNewsroomUrl = $this->manageNewsroomUrl($pressRelease);
$this->configureUnsubscribe($user, EmailNotificationType::PressReleasePublished);
}
/**
* Verwaltungsseite des Newsrooms auf dem Portal, auf dem die Meldung
* erschienen ist (bei beiden: presseecho). Nur für Firmen, die noch als
* Newsroom existieren — nicht 410-entfernt.
*/
private function manageNewsroomUrl(PressRelease $pressRelease): ?string
{
$company = $pressRelease->company;
if ($company === null || ! $company->slug || $company->isGone() || $this->portalLinks === []) {
return null;
}
$host = rtrim((string) parse_url($this->portalLinks[0]['url'], PHP_URL_SCHEME).'://'.parse_url($this->portalLinks[0]['url'], PHP_URL_HOST), '/');
$edition = in_array($pressRelease->language, ['de', 'en'], true) ? $pressRelease->language : 'de';
return self::tagged(sprintf('%s/%s/newsroom/%s/verwalten', $host, $edition, $company->slug), $pressRelease->id);
}
public function envelope(): Envelope
{
return new Envelope(

View file

@ -77,12 +77,12 @@ class NewsroomClaimService
* Verschickt die Anmeldelinks. Gibt zurück, an wie viele Adressen
* tatsächlich etwas ging — deaktivierte Konten bekommen nichts.
*/
public function request(Company $company, ?string $ip): int
public function request(Company $company, ?string $ip, ConversionSource $source = ConversionSource::ProfileClaim): int
{
$sent = 0;
foreach ($this->recipients($company) as $email) {
if ($this->contactAccess->requestMagicAccess($email, $ip, ConversionSource::ProfileClaim, $company->getKey())) {
if ($this->contactAccess->requestMagicAccess($email, $ip, $source, $company->getKey())) {
$sent++;
}
}