diff --git a/app/Http/Controllers/Web/NewsroomClaimController.php b/app/Http/Controllers/Web/NewsroomClaimController.php index c7aaa7d3..01082de7 100644 --- a/app/Http/Controllers/Web/NewsroomClaimController.php +++ b/app/Http/Controllers/Web/NewsroomClaimController.php @@ -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); diff --git a/app/Mail/PressReleasePublished.php b/app/Mail/PressReleasePublished.php index 00e38c1d..fbf5fef8 100644 --- a/app/Mail/PressReleasePublished.php +++ b/app/Mail/PressReleasePublished.php @@ -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( diff --git a/app/Services/Company/NewsroomClaimService.php b/app/Services/Company/NewsroomClaimService.php index c566141b..6adbf1a7 100644 --- a/app/Services/Company/NewsroomClaimService.php +++ b/app/Services/Company/NewsroomClaimService.php @@ -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++; } } diff --git a/dev/2026-09-01/roadmap-2026.md b/dev/2026-09-01/roadmap-2026.md index 159b8d17..02ad137e 100644 --- a/dev/2026-09-01/roadmap-2026.md +++ b/dev/2026-09-01/roadmap-2026.md @@ -121,7 +121,7 @@ Für selbst angelegte Firmendaten trägt der Nutzer die Verantwortung. Für die | ~~1b.1~~ | Sichtbarkeitsregel: Newsroom öffentlich erst ab der ersten veröffentlichten Meldung, sonst nur Datensatz — **bestand; 15.09.2026 Rückbau im Cache vervollständigt** | S | keine | | ~~1b.2~~ | Verwaltungszugang über Magic Link, unabhängig von der öffentlichen Sichtbarkeit — **gebaut 15.09.2026**: `/newsroom/{slug}/verwalten`, Link an die hinterlegte Firmen-E-Mail (maskiert), Herkunft `profile_claim`, Landung auf der Firmenseite; noch unverlinkt (→ 1b.4) | M | 1a.4 | | 1b.3 | Kostenlose Profilpflege: Beschreibung, Logo, Bilder, Pressekontakte | M | 1b.2 | -| 1b.4 | Hinweis "Diesen Newsroom verwalten" auf der Profilseite und in der Veröffentlichungsmail | S | 1b.2 | +| ~~1b.4~~ | Hinweis "Diesen Newsroom verwalten" auf der Profilseite und in der Veröffentlichungsmail — **gebaut 15.09.2026** (E4: Hinweis unten in der Mail; Herkunft `mail_publish` läuft bis auf das Token durch) | S | 1b.2 | | ~~1b.5~~ | Rückbau: fällt ein Newsroom durch Löschung oder Depublizierung auf null Meldungen, verschwindet die öffentliche Seite mit — **15.09.2026**: alle Admin-Wege (archivieren, Status ändern, löschen) leeren jetzt den Seiten-Cache | S | 1b.1 | | 1b.6 | Nicht erreichbare Newsrooms aus der Bounce-Suppression im Admin markieren, nicht beanspruchbar, aus allen Mailstrecken ausgenommen | S | Bounce-Suppression live | @@ -234,7 +234,7 @@ Braucht Betreuungszeit und ist teilweise von Dritten abhängig. | ~~E1~~ | **Entschieden 11.09.2026: 3** (drei Live-Läufe, siehe `basiswerte-block0.md` und `umsetzung-protokoll.md` Teil 3). Konfiguriert in `config/newsroom.php`. | 1a.2 | | ~~E2~~ | **Empfehlung 01.09.2026: Rohzahlen** — kein Bodensatz, Verteilung symmetrisch, Einordnung gegen den Schnitt wäre wegen der Wachstumskurve falsch (siehe `basiswerte-block0.md`). | 1c.1 | | E3 | Anzahl kostenloser Prüfläufe und Preis der weiteren in Credits | 1d.4 | -| E4 | Trägt die Veröffentlichungsmail den Magic-Link-Hinweis oder den Link auf die Infoseite; ein Element pro Mail | 1b.4, 1d.2 | +| ~~E4~~ | **Entschieden 15.09.2026: der Magic-Link-Hinweis, am Ende der Mail, als Hinweis, nicht als Angebot.** Umgesetzt in 1b.4. Der Link auf die Infoseite (1d.2) bekommt damit keinen Platz in der Veröffentlichungsmail — ein Element pro Mail. | 1b.4, 1d.2 | | E5 | Werden die bestehenden Ratgeberseiten konsolidiert oder ergänzt | 2.5 | | E6 | Bleibt die Ablehnungsstrecke dauerhaft frei von kommerziellen Angeboten | 1d.1, 1d.6 | diff --git a/dev/2026-09-01/umsetzung-protokoll.md b/dev/2026-09-01/umsetzung-protokoll.md index 165c08bc..3506b629 100644 --- a/dev/2026-09-01/umsetzung-protokoll.md +++ b/dev/2026-09-01/umsetzung-protokoll.md @@ -955,3 +955,25 @@ Adresse, E-Mail, Telefon, Website, Land, Logo und Pressekontakte pflegen. Was 1b.3 ergänzt: Beschreibungstext (durch die Rot-Ebene), Bilder (mit KI-Deklaration und Audit-Log), und die Leitplanke, dass Links im Profiltext systemseitig gesetzt werden. + +## 1b.4 — Hinweise (E4 entschieden 15.09.2026) + +**E4:** Die Veröffentlichungsmail trägt den Magic-Link-Hinweis — am Ende, als +Hinweis, nicht als Angebot. Damit bekommt der Link auf die Infoseite (1d.2) +keinen Platz in dieser Mail (ein Element pro Mail). + +- **Newsroom-Seite:** eine Zeile unter dem Firmenprofil („Sie vertreten + …? Profil, Logo und Pressekontakte lassen sich kostenlos pflegen. Diesen + Newsroom verwalten"), `rel="nofollow"`, 12 px, ink-3. +- **Veröffentlichungsmail:** ein Absatz vor dem Hilfe-Hinweis, in derselben + grauen Kleinschrift wie der bestehende Login-Hinweis. Link auf + `/newsroom/{slug}/verwalten` des Portals, auf dem die Meldung erschien + (bei beiden: presseecho), getaggt mit `src=mail_publish&pr=`. Kein Hinweis + ohne Firma oder bei 410-entfernter Firma. +- **Herkunft läuft durch:** Die Claim-Seite reicht `src` aus dem Mail-Link + per verstecktem Feld weiter; das Token trägt dann `mail_publish` statt + `profile_claim`. Stufe 1 (Klick) zeichnet die globale Middleware beim + Aufruf der Claim-Seite ohnehin auf. So unterscheidet die Leiter „aus der + Mail beansprucht" von „auf der Seite entdeckt". + +Tests: drei weitere in `NewsroomClaimTest` (Seite, Mail, Herkunft). diff --git a/lang/de/web.php b/lang/de/web.php index 05574812..4de4287c 100644 --- a/lang/de/web.php +++ b/lang/de/web.php @@ -284,6 +284,8 @@ return [ 'total_hits_sub' => 'über alle Meldungen', 'latest' => 'Aktuellste Meldung', 'empty' => 'Aktuell keine weiteren Meldungen in diesem Newsroom.', + 'manage_hint' => 'Sie vertreten :company? Profil, Logo und Pressekontakte lassen sich kostenlos pflegen.', + 'manage_link' => 'Diesen Newsroom verwalten', 'own_title' => 'Eigenen Newsroom starten', 'own_text' => 'Bündeln Sie alle Ihre Pressemitteilungen unter einer eigenen Marken-URL – mit Logo, Branchen-Zuordnung und SEO-Vorteil.', 'own_cta' => 'Newsroom anlegen', diff --git a/lang/en/web.php b/lang/en/web.php index 87305edd..475e2e7c 100644 --- a/lang/en/web.php +++ b/lang/en/web.php @@ -279,6 +279,8 @@ return [ 'total_hits_sub' => 'across all releases', 'latest' => 'Latest release', 'empty' => 'No further releases in this newsroom at the moment.', + 'manage_hint' => 'You represent :company? Profile, logo and press contacts can be maintained free of charge.', + 'manage_link' => 'Manage this newsroom', 'own_title' => 'Start your own newsroom', 'own_text' => 'Bundle all your press releases under your own branded URL – with logo, industry mapping and SEO benefit.', 'own_cta' => 'Create newsroom', diff --git a/resources/views/emails/press-release/published.blade.php b/resources/views/emails/press-release/published.blade.php index 3d37350d..4005cfd5 100644 --- a/resources/views/emails/press-release/published.blade.php +++ b/resources/views/emails/press-release/published.blade.php @@ -35,6 +35,16 @@ dort mit Ihrem Passwort oder per Magic-Link (Anmelde-Link per E-Mail) möglich.
+ {{-- 3) Verwaltungszugang zum Newsroom (Roadmap 1b.4, E4): ein Hinweis + am Ende, kein Angebot — der Zugang ist kostenlos. --}} + @if ($manageNewsroomUrl) +
+ Hinweis: Der Newsroom von {{ $pressRelease->company->name }} lässt sich kostenlos pflegen —
+ Profil, Logo und Pressekontakte. Den Zugang erhält die hinterlegte Firmen-E-Mail:
+ Diesen Newsroom verwalten
+
Bei Fragen erreichen Sie uns über den Hilfe-Bereich auf pressekonto.