Abrufe öffentlicher Meldungen durch bekannte Such- und KI-Dienste werden
je Meldung mit Dienst, gekürztem User-Agent und Zeitpunkt protokolliert —
keine IP. Dienste und Muster stehen in config/crawlers.php. Aufzeichnung
wie der Aufruf-Zähler per defer() und fehlertolerant; Bereinigung hängt
an press-releases:purge-events.
VisibilityStats liefert die Abrufe je Dienst (letzter Abruf, Anzahl) und
verliert den site:-Suchlink (Stufe 1 verspricht nichts, was von Dritten
abhängt).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Tarife & Preise" als erster Punkt; x-web.static-page bekommt einen Slot
`aboveTitle`, die drei Transparenzseiten tragen dieselbe Leiste mit
markierter aktueller Seite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
x-web.transparency-nav (Label + drei Chips) statt der kleinen Zeile unter
dem USt-Hinweis; Komponente kennt `current` fuer spaetere Wiederverwendung.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drei Seiten mit unterschiedlicher Suchintention, DE/EN, Hub + Spokes:
- /was-kostet-eine-pressemitteilung: kurze Antwort im ersten Bildschirm,
vier Abrechnungsmodelle ohne Anbieternamen und fremde Preise, sechs
Prueffragen fuer Folgekosten, "Was bei uns gilt" (sieben Zusagen mit
Einloesung), Rechenbeispiel Einzelabrechnung gegen Starter-Monate mit
Zeilen fuer beide Seiten.
- /pressemitteilung-veroeffentlichen-ohne-abo: Ablauf, enthalten / nicht
enthalten, nichts verfaellt, wann sich ein Tarif lohnt.
- /pressemitteilung-loeschen-oder-aendern-lassen: fuenf Wege als
Tabelle (Korrektur, Aenderung, DSGVO, Depublikation, DSA), Antragsweg
ueber /melden/pm, Korrektur statt Loeschung, Absatz fuer Meldungen auf
fremden Portalen ohne Zusagen ueber Dritte.
PricingTransparencyData liefert alle Zahlen aus config/billing, credits,
legal und dem Starter-Tarif; TransparencyPageController leitet die Marke
aus dem Host ab. Tabellen-Typografie fuer .pm-body. Verlinkt aus
Preisuebersicht, Hilfe-Center, Sitemaps und llms.txt.
Test TransparencyPagesTest (9) erzwingt u. a., dass im Rechenbeispiel
beide Modelle Zeilen gewinnen. Protokoll Teil 5, Roadmap 2.4 abgehakt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- /de/category/{slug}/archiv/{jahr} (CategoryArchivePageData,
CategoryArchiveController, View kategorie-archiv): 30 Meldungen je
Seite, neueste zuerst, Jahresleiste mit Zahlen; ohne Jahr das juengste
Jahr mit Meldungen. Canonical mit ?page=N auf Folgeseiten, hreflang
nur auf Seite 1, CollectionPage + BreadcrumbList. Gecacht bis Seite 3.
Leere Jahre, unbekannte Kategorien, leere Folgeseiten: 404.
- Branchenseite: Jahresleiste unter dem Feed (archiveYears, eine
gruppierte Query); Sitemap listet alle Jahresarchive.
- categories:seed-descriptions {--dry-run} {--force}: fuellt leere DE/EN
Beschreibungen aus database/seeders/data/category-descriptions.php
(14 Kategorien, sachlich, ohne Werbeaussagen), leert den Seiten-Cache.
- Branchenseite: Titel trug config('app.name') ("– pressekonto") statt
des Portalnamens — jetzt presseecho/businessportal24.
- Protokoll Teil 5 (2.3 mit Befund: 0 von 14 Beschreibungen, Sonstiges =
28 % des Bestands), Roadmap 2.3 abgehakt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- App\Support\StructuredData: Portal-Organization als Publisher mit @id
und Logo (ImageObject), Newsroom-@id; NewsArticle.author auf der
Meldung und Organization auf der Newsroom-Seite beschreiben jetzt
dieselbe Entitaet. Startseiten nutzen denselben Publisher.
- /sitemap-news.xml je Spoke (NewsSitemapController): im Archiv
sichtbare Meldungen der letzten 48 h, max. 1.000, news:publication
mit Portalname und Sprache; 15 min gecacht, mit den anderen Sitemaps
geleert; in robots.txt der Spokes. Hub liefert leere Liste.
- /llms.txt je Host (LlmsTxtController): sachliche Beschreibung,
URL-Aufbau, aktive Kategorien, Seiten fuer Unternehmen, Rechtliches;
Hub verweist auf beide Portale. Keine Aussagen ueber KI-Nennungen.
- Protokoll Teil 5 (Block 2) inkl. Betreiber-Schritten fuer das
Publisher Center und Befund zu 2.5/E5; Roadmap 2.1/2.7 abgehakt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ohne kommerziellen Anteil (E6): Anlass, Aufbau, Sprache, Pruefablauf mit
konfigurierbarer Versuchszahl, die sieben Qualitaetsdimensionen offen,
Gewichtung nicht (Offenlegungsgrenze), Pressebilder, Partner als BALD.
DE und EN, Sitemap, Hilfe-Center. Die Ablehnungsmail verlinkt die Seite
auf dem Portal der Meldung mit Herkunft mail_reject.
Config: yellow_to_manual_queue Vorgabe false (Entscheidung 16.09.2026),
max_automatic_attempts = 3 (E3).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- VisibilityStats: Firmen-Kennzahlen ueber alle Meldungen, Aufrufe je
Meldung (30 Tage), lueckenlose Tagesreihe (Tage vor der Zaehlung sind
null, nicht 0), Sitemap-seit und Google-Suchlink (Indexierung Variante B).
- Firmenseite: alle Meldungen der Firma statt nur eigene (Beansprucher
sahen eine leere Liste), Aufrufe-Spalten, echtes Statistik-Panel.
- Meldungsseite: Panel Sichtbarkeit mit SVG-Tagesbalken 30/90 Tage.
- Claim aus der Veroeffentlichungsmail landet auf der Meldung
(payload press_release_id), Mail-Hinweis erweitert, ein Element (E4).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die beiden Hinweise unter der Mitteilung („Inhalt melden“, „mit der
hinterlegten E-Mail anmelden“) waren Textzeilen in 11,5 px und wurden
überlesen. Jetzt zwei klar getrennte Felder mit Buttons; der
Verwaltungsblock nennt die maskierte Empfängeradresse und führt auf die
Claim-Seite des Newsrooms. Derselbe Hinweis noch einmal in der
Seitenleiste unter „Weiterführend“, wie in der Veröffentlichungsmail.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Neue Seite /newsroom/{slug}/verwalten auf den Web-Portalen: schickt den
Anmeldelink an die hinterlegte Firmen-E-Mail (sonst Pressekontakte), zeigt
sie nur maskiert, und funktioniert auch für Firmen ohne veröffentlichte
Meldung. Konto, Zuordnung und Login laufen durch ContactAccessService —
neu dort: Herkunft (profile_claim auf dem Token) und Ziel-Firma, auf deren
Kundenseite der Login landet. 410-entfernte bleiben Tombstone, aufgegangene
Slugs führen zum Überlebenden (MergedNewsroomLookup, geteilt mit der
Newsroom-Seite). 3 Links je Newsroom und Tag, 15 je IP und Stunde.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Sichtbarkeitsregel bestand bereits (Detailseite 404, Verzeichnis und
Sitemap nur mit veröffentlichter Meldung). Neu: archive(),
changeStatusFromAdmin() weg von published und deleteFromAdmin() einer
veröffentlichten Meldung leeren den Seiten-Cache — vorher blieb die
Newsroom-Seite bis zu eine Stunde stehen, während die Depublikation längst
sofort griff.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rechtlich oder redaktionell veranlasste Entfernungen brauchten bisher die
Löschung — danach antwortet die URL mit 404, als hätte es die Meldung nie
gegeben. Newsroom und Pressemitteilung lassen sich jetzt stattdessen mit
gone_at markieren: Der Inhalt verschwindet aus allen öffentlichen Listen, die
bestehende URL liefert aber einen Tombstone mit Status 410. Die vollständige
Löschung bleibt unverändert daneben stehen und erzeugt weiterhin 404.
Umkehrbar heißt hier wirklich umkehrbar. markGone() überschreibt zwei Felder —
companies.is_active (damit der Newsroom ohne Sonderbehandlung aus
Newsroom-Liste und Sitemap fällt, die beide darauf filtern) und
press_releases.no_export. Beide Vorzustände werden gesichert und beim Aufheben
zurückgestellt, sonst stünde eine schon vorher deaktivierte Firma danach
öffentlich in der Übersicht und eine Mitteilung dauerhaft als "kein Export" da,
ohne dass das je jemand gewählt hätte.
Der Auslieferungs-Cache kennt keine Tags, und die Keys der Listenseiten sind
über Portal, Sprache, Seite und Filter verstreut. Eine Entfernung erhöht
deshalb eine Generations-Nummer im Cache-Key und verwirft damit alle
Seiten-Caches auf einen Schlag — grob, aber Entfernungen sind selten und dürfen
nicht bis zu einer Stunde nachhängen. Der Zähler liegt bewusst außerhalb des
Namensraums, den er invalidiert, und wird zwischen zwei Queue-Jobs neu
gelesen: Ein Worker lebt über beliebig viele Jobs und arbeitete sonst dauerhaft
mit dem Stand seines ersten Zugriffs.
Beim Newsroom läuft die Tombstone-Prüfung bewusst VOR dem Seiten-Cache — eine
Entfernung muss sofort greifen, auch wenn für den Slug noch ein Stale-Eintrag
liegt. Kaskadiert man die Entfernung auf die Meldungen einer Firma, wird der
Cache einmal am Ende verworfen statt einmal je Meldung; sonst zwänge eine
Bestandsfirma mit dreistellig vielen Meldungen die gesamte öffentliche Seite
ebenso oft in den Neuaufbau.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bisher lief pro Seitenaufruf ein UPDATE press_releases SET hits = hits + 1 im
Request. Auf der über 1 GB großen Tabelle blockiert dieser Schreibzugriff,
sobald dort eine Schemaänderung läuft — die wartenden Requests belegten dabei
PHP-FPM-Worker und DB-Verbindungen, bis die ganze Seite stand.
Der Request schreibt jetzt nur noch in die kleine Append-Tabelle
press_release_events, und selbst dabei wird jeder Fehler geschluckt und
gemeldet: Ein Aufruf-Zähler darf eine Seite niemals langsamer oder kaputt
machen. Die Summierung in press_releases.hits übernimmt gebündelt der
Scheduler.
Der Fortschritt läuft über eine Wasserstandsmarke (höchste bereits verbuchte
Event-ID) statt über einen Zwischenspeicher, damit bei einem Neustart kein
Aufruf verloren geht. Zähler und Marke werden je Durchgang gemeinsam
committet — sonst wäre nur der Durchgang wiederanlaufsicher, nicht der Abbruch
darin, und der nächste Lauf verbuchte dieselben Aufrufe doppelt. Eine Migration
setzt die Marke beim Deploy auf den Ist-Stand; ohne sie würde der erste Lauf
den gesamten Altbestand ein zweites Mal aufaddieren.
Weil das Log damit mit dem Traffic wächst und als einzige Log-Tabelle des
Systems keine Bereinigung hatte, begrenzt press-releases:purge-events es auf
einen Aufbewahrungszeitraum (Default 395 Tage = 13 Monate, damit der
Vorjahresmonats-Vergleich möglich bleibt). Gelöscht wird ausschließlich
unterhalb der Wasserstandsmarke — alles darüber ist noch nicht in hits verbucht
und wäre sonst dauerhaft verloren. Fehlt die Marke, bricht der Lauf ab, statt
zu raten.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 7 (Spur D, E2 revidiert): KI-Vorprüfung VOR der Zahlung im Modal —
rot lehnt ohne Zahlung ab, gelb/grün führt zum Stripe-Checkout
(SinglePurchaseType::Change, 10 € netto), nach Zahlungseingang übernimmt
der Webhook die geprüfte Fassung sofort (applyPaid: Snapshot, Sperre,
Verlauf). Nur einmal je PM; Obergrenze legal.change_max_distance_percent
(50 %) verhindert die verdeckte Selbst-Depublizierung.
Phase 5 (Spur B): strukturiertes Schwärzungsformular mit Pflicht-Vorschau
im verifizierten Web-Bereich UND als Modal im Kunden-Panel; Admin-Editor
wird aus payload.redaction vorbefüllt. Umsetzungsmail LegalRequestExecuted
an Antragsteller, Bestätigungsmail PressReleaseChangeApplied (Spur A/D,
neuer abbestellbarer Typ press_release_corrected).
Außerdem:
- Re-Sync-Sperre + Sitemap-Invalidierung fest in depublish() verankert
(Lücke im bezahlten Webhook-Pfad geschlossen)
- Öffentliche „Rechtliches"-Seite als zweistufige Triage: primär der
Portal-Anmeldelink, Formular nur Fallback mit Identifizierungs-Hinweis;
Store auf report/dsgvo/personal_rights gehärtet
- Panel-Button „Ändern/Melden" öffnet Anliegen-Modal (4 Spuren) statt
des Web-Formulars
- Pressekontakt-Fallback für Legacy-Bestände (30k PMs ohne Verknüpfung):
Detailseite + pressContactEmail() nutzen PM-Kontakt → Firmen-Kontakt →
Firmendaten
- Korrektur-Gate entity-fest (Editor-Normalisierung zählt nicht als
Änderung), toEditorHtml() in Admin-Edit + Korrektur-Panel,
Wochenlimit-Hinweis in Text und Bremsen-Meldung
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Unterseite /admin/newsletter/abmeldungen listet Newsletter-Opt-outs mit
Zeitpunkt, Suche und Link zur User-Detailseite
- Kampagnen-Filter: Empfänger einer früheren Kampagne ausschließen
(Abgleich per User-ID und E-Mail-Snapshot) für Follow-up-Mailings
- Login-Filter kann Monats-Scheiben (von–bis) abgrenzen, um große
Listen Monat für Monat abzuarbeiten
- Versand läuft in Wellen (Standard 50 Mails / 15 Min, per .env
justierbar) gegen SMTP-Limits und Blocklisten; Hinweis im Senden-Modal
- web: Firmen-Website im Newsroom & Co. als absolute URL verlinkt
(Company::websiteUrl), statt relativ auf /newsroom/www.example.de
- kunde: API-Doku-Button öffnet die /de/api-Seite; OpenAPI-Spezifikation
als eigener, benannter Download (pressekonto-api-v1.yml)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ploi kann nur Pfad-Redirects, daher als App-Middleware (früh im globalen
Stack): GET/HEAD auf www.<domain> → 301 auf die Apex-Domain mit Pfad und
Query. Unsichere Methoden werden nicht umgeleitet (301 verwirft den Body)
— die beantwortet die App dank Host-Normalisierung weiterhin selbst.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Der Livewire-Feed lief auf jeder Startseiten-Impression ungecacht; seine
ORDER-BY-published_at-Query kann wegen portal IN (…) keinen bestehenden
Index zur Sortierung nutzen und filesortete ~10-20k Zeilen (live gemessen
2,3 s pro Aufruf — die Startseite blieb dadurch trotz Seitendaten-Cache
bei 6-9 s TTFB). Zwei Maßnahmen:
- Neuer Index (language, status, deleted_at, published_at): Backward
Index Scan statt Filesort, Query 2 ms statt 2,3 s — greift auch für
ungecachte Livewire-Interaktionen (Zeitraum-Filter, tiefe Seiten).
- Feed-Seiten 1-3 laufen über WebPageCache (SWR wie die übrigen
Seitendaten); gecacht werden nur items + total, der Paginator wird pro
Request neu gebaut (keine Host-URLs des Cache-Erzeugers im Cache).
Unbekannte timeframe-Werte aus der URL fallen auf 'all' zurück und
erreichen weder Query noch Cache-Key. Der doppelte Gesamt-COUNT pro
Request entfällt; der 'Mehr laden'-Zähler nutzt jetzt den gefilterten
Total des Paginators.
Lokal mit Live-Datenbestand: Startseite kalt 875 ms (vorher 2,5 s),
warm 11 ms (vorher 72 ms).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Start-/Kategorieseiten bauten nach Ablauf der 5-Minuten-TTL im Besucher-
Request neu (~25-30 Archiv-Queries über ~100k PMs) — Long-Tail-Rubriken
waren dadurch praktisch bei jedem Aufruf langsam. WebPageCache nutzt jetzt
Cache::flexible([300, 3600]): Innerhalb 5 Min frisch wie bisher, danach
wird bis zu 1 h sofort der alte Stand serviert und der Neuaufbau läuft
nach der Response (deferred). HomePageData/CategoryPageData/TickerData
laufen dafür über die gemeinsame WebPageCache-Konvention (Keys unverändert),
die Hub-Kennzahlen über flexible([3600, 14400]).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Inhaltliche Korrektur (8 Credits) und Update/Ergänzung (4 Credits)
existieren als buchbare Leistungen noch nicht — sie folgen erst mit dem
Bookings-/Magic-Link-Rework und fliegen bis dahin von der Preisliste.
Depublizieren wird als Direktkauf in Euro abgerechnet (Stripe-Checkout
über billing.depublication_price_cents), nicht in Credits: Die Seite
zeigt jetzt 35 € aus derselben Preisquelle wie der Kauf-Flow statt der
falschen 35 Credits. Ohne konfigurierten Preis verschwindet der Block
komplett (wie das Angebot auf der PM-Detailseite).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ergänzt die bislang fehlenden strukturierten Daten auf den Presse-Seiten:
- PM-Detailseite: NewsArticle + BreadcrumbList als JSON-LD sowie
article:published_time/-section/-tag-Metas und og:image:alt — nur auf
der öffentlichen Seite, die noindex-Vorschau bleibt ohne Markup.
- Kategorie-Seiten: BreadcrumbList plus hreflang de/en über die
lokalisierten Slugs (CategoryPageData liefert dafür neu
categoryAlternateSlugs, mit View-Fallback für gecachte Alt-Daten).
- Newsroom: Organization-Markup (Logo, Website als sameAs, Boilerplate).
- Portal-Homepages: WebSite mit SearchAction (Sitelinks-Searchbox) und
Organization.
- Layout: og:locale und twitter:image ergänzt; neue Sections meta_extra
und structured_data.
Neue Komponente x-web.structured-data rendert JSON-LD mit JSON_HEX_TAG,
damit "</script>" in Titeln/Teasern das Script-Tag nicht aufbrechen kann.
JSON-LD-URLs werden wie der Canonical aus der Portal-Basis gebaut
(unabhängig von forceRootUrl), damit Host und Canonical übereinstimmen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Navigations-Chrome (CategoryNavigation), Suche- und Newsroom-Seitendaten
300s gecacht; gemeinsamer WebPageCache-Helper (eine TTL/Key-Konvention).
- Cache-Poisoning über ?page= geschlossen (Key an Paginator ausgerichtet,
kein withQueryString im Cache); Newsrooms cachet nur den ungefilterten
Fall und die ersten Seiten (kein unbegrenzter Key-Raum).
- Detailseiten schreiben Hits/View-Events deferred nach der Response
(M12); OpenAI-/Markt-/USt-ID-Aufrufe mit Connect-Timeouts und
begrenzten Retries; Admin-Memo-Caches konsistent invalidiert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Magic-Link-Login übergibt bei aktiviertem 2FA an die 2FA-Challenge,
statt sie zu umgehen (M1).
- Bestätigungs-/Benachrichtigungs-Mails (Kontakt, Recht, Kündigung,
Widerruf, Magic-Link) laufen als ShouldQueue+afterCommit über die Queue,
damit die Response nicht am SMTP-Roundtrip hängt.
Hinweis: Der Queue-Worker muss im Betrieb laufen und überwacht werden, da
fristrelevante Bestätigungen darüber zugestellt werden (siehe
Betriebsüberwachung).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
page_url aus dem anonymen Kontaktformular wird auf url:http,https
validiert; das Admin-Backend rendert nur http(s)-Links klickbar, sonst
als Text. Verhindert javascript:-URIs, die beim Admin-Klick im
Admin-Kontext liefen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entscheidung 13.07.: verkauft wird nur, was sichtbar ist — max. 5
gleichzeitige Boosts je Startseite, 3 je Branchenseite (= die sichtbaren
Plaetze; Hero-Slider damit vertagt). Neue Stufe Kategorie-Boost (nur
Branchenseite, 6/10/18 Credits = 50 % aufgerundet); der Startseiten-Boost
(12/20/35) erscheint weiter auf Start- UND Branchenseite.
- boosts.placement (home/category, Bestand = home) + BoostPlacement-Enum;
Preise/Slots in config/credits.php (boost_category, boost_slots)
- BoostService::slotAvailability() je Ausgabe (Portal x Sprache, Kategorie
zusaetzlich je Rubrik); ausgebucht -> soldOut-Exception mit fruehestem
freien Termin; Verlaengerungen halten ihren Slot, Stufen verlaengern
unabhaengig; Kategorie-Boost invalidiert nur Kategorie-Caches
- Startseite zeigt nur Home-Boosts; Branchenseite: Kategorie-Boosts mit
Vorrang (ihre 3 Slots sind die Garantie), Home-Boosts fuellen den Rest
- Add-on-Panel der PM-Seite: zwei Boost-Angebote, bei vollem Inventar
"Derzeit ausgebucht — frei ab" statt Kauf-Buttons (serverseitig gegated);
Buchungsseiten-Preisliste um die Kategorie-Staffel ergaenzt
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Statt bis zu 300 s Cache-TTL zu warten, verwirft der BoostService nach
der Buchung die Home-/Kategorie-Caches der betroffenen Ausgaben (inkl.
Eltern-Rubrik) — die bezahlte Platzierung ist direkt sichtbar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Geboostete PMs besetzen Lead- und Top-Slots vor der Chronologie
(HomePageData/CategoryPageData ueber boosted()-Scope) und tragen das
Badge "Top-Platzierung". Abgelaufene Boosts wirken nicht.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Die im Spoke-Frontend-Audit entfernten Anzeige-Flaechen (focus-ad,
feed-ad, Inline-Ad) kehren als dezente Eigenwerbung zurueck:
- neue Komponente x-web.ad-slot im frueheren "ANZEIGE"-Stil mit Hinweis
"Hier koennte Ihre Werbung stehen" (DE/EN)
- eingebaut an den drei frueheren Stellen: Hero-Sidebar der Startseiten,
Feed nach der dritten Meldung, PM-Detailseite nach der
Unternehmensbeschreibung (nicht in der privaten Vorschau)
- Klick oeffnet das schwebende Kontakt-Widget mit vorgewaehltem neuen
Thema "Werbung buchen" (ContactMessageTopic::Advertising); Anfragen
landen wie gewohnt in der Admin-Queue "Kontakt"
Co-authored-by: Cursor <cursoragent@cursor.com>
Submit-Modal auf zwei Auswahloptionen reduziert (kein Label vs.
"Ganz oder ueberwiegend mit generativer KI erstellt"); alle vier
Enum-Werte bleiben fuer API/Bestand gueltig und mappen per formOption()
auf die zwei Gruppen. Oeffentliche Detailseite rendert jetzt Text-Label
("KI-generiert: Nach Angaben ...") und Bild-Badge (KI-generiert/
KI-bearbeitet, abgedunkelter Hintergrund) — fehlte dort bisher komplett.
Bildnachweis-Vorschlag ohne "Foto:"-Praefix (stand sonst doppelt).
Einreich-Toast + FAQ: automatische Pruefung in Minuten statt 24 h.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- press_release_events (type view/share, channel, ohne Personenbezug):
Model/Enums/Factory, Cascade-Delete an der PM
- Detailaufruf loggt View-Event zusaetzlich zum Legacy-hits-Zaehler
(bleibt bewusst einfacher Page-Request-Zaehler, User-Vorgabe 10.07.)
- POST press-release/{slug}/share (throttle 30/min, nur archiv-sichtbare
PMs des Portals) loggt Teilen-Klicks je Kanal (LinkedIn/X/E-Mail/Copy)
- Kundenpanel-PM-Seite: neue Kachel Geteilt mit Kanal-Aufschluesselung
- lang: web.detail.views (de/en)
- Tests: Web/PressReleaseTrackingTest (7)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A3 des Umbaus Agentur-Attribution → User-Profil: Eager Loading der
Detailseite/Vorschau lädt user:id,agency_name; die Legacy-Relation
PressRelease::submittingAgency() (Pseudo-Firma type=agency via
owner_user_id) entfällt. Tests decken Verteiler-Fall, Namensgleichheit
und PM ohne Übermittler ab.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Archiv-Suche lief auf grossen Datenbestaenden langsam und war im Browser
komplett tot: app.js startete ein eigenes Alpine, Livewire injizierte
zusaetzlich seins - der DOM-Morph brach mit "Alpine.transaction is not a
function" ab (keine Rubrik klickbar, Suchbegriff nicht aenderbar).
- app.js buendelt jetzt Livewire + Alpine aus livewire.esm,
@livewireScriptConfig im Web-Layout stoppt die Auto-Injektion
- MySQL-Volltext-Index (Boolean-Mode, +wort*) statt LIKE ueber alles;
LIKE-Fallback fuer SQLite-Tests und kurze Tokens
- Firmen-/Rubrik-Treffer als ID-Listen statt orWhereHas (OR + EXISTS
hebelte den Volltext-Index aus)
Co-authored-by: Cursor <cursoragent@cursor.com>
Frontend-Umbau (Kevin, 06.07.2026), Ergebnis des businessportal24-Audits:
- Fake-Anzeigen und Mock-/Fallback-Daten entfernt (focus-ad, feed-ad,
highlight-card, press-release-card, featured-releases, press-releases-grid);
Sektionen rendern nur noch mit echten Daten oder gar nicht
- Newsletter-Strip und Newsletter-Box zurueckgebaut (Feature nachgelagert,
vor Reaktivierung DOI-Flow noetig)
- Echte Boersendaten: MarketDataService (Yahoo-Finance-Quotes, config/market.php,
Cache) + market:refresh alle 15 Min. im Scheduler; live-ticker blendet sich
ohne Daten aus
- EN-Lokalisierung der Kern-Seiten via lang/de/web.php + lang/en/web.php;
Carbon-Locale folgt app()->getLocale()
- Web-Tests auf den neuen Frontend-Stand nachgezogen (Suite: 993 gruen)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Die Scope-Doku fuehrte den /thumbnails-Redirect als "ANGEPASST", im Code
existierte er nicht — alte Symfony-Bild-URLs (/thumbnails/..., /uploads/
...) liefen ins 404. Neuer LegacyMediaRedirectController mappt den
Original-Dateinamen (bleibt beim Medien-Import erhalten) auf die neue
Storage-URL fuer PM-Bilder und Firmenlogos; unbekannte Dateien bleiben
404. Gefunden bei der R6.1-Vorpruefung.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Default 3500 Cent in config/billing.php (per Env ueberschreibbar,
0 deaktiviert) — Checkout und /melden-Preisanzeige (Doppelausweis
35,00 netto / 41,65 brutto) sind damit ohne weiteren Env-Handgriff
aktiv. O6 aus der Entscheidungsliste geschlossen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Zwei eigenstaendige Formulare unter /melden/rechtswidrig (DSA Art. 16:
URL, Name, E-Mail, Begruendung, Wahrheitsversicherung) und
/melden/kontaktdaten (DSGVO-Antrag) auf allen drei Domains. Gemeldete
URLs werden automatisch der passenden veroeffentlichten PM zugeordnet;
ohne Treffer bleibt die URL im payload. Jede Meldung erhaelt eine
Vorgangsnummer (M-{id}, analog K-{id} der Kuendigung), die meldende
Person unverzueglich eine Eingangsbestaetigung (DSA Art. 16 Abs. 4) und
das Melde-Postfach einen internen Hinweis — auch im bestehenden
Per-PM-Flow. Admin-Queue zeigt Vorgangsnummer, Name und gemeldete URL;
/melden verlinkt die neuen Formulare als ersten Weg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unter dem Artikelinhalt steht jetzt eine eigene Zeile mit "Inhalt melden"
und "Eigene Daten entfernen" (inkl. Kostenlos-Hinweis) — bisher waren die
Wege nur in der Sidebar-Box "Weiterfuehrend" auffindbar. Erfuellt die
DSA-Anforderung eines leicht zugaenglichen Meldewegs direkt am Inhalt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/richtlinien rendert jetzt alle aktiven Kategorien aus der neuen
rejection_categories-Tabelle (stabiler code als Anker, public_label/desc,
optionale Beispiele) statt der statischen K1-K6-Liste. Kategorien werden
nie geloescht, nur deaktiviert; neue Kategorien erscheinen ohne
Textaenderung und ohne neue AGB-Version (AGB par. 7 Abs. 3 verweist nicht
abschliessend). Seeder legt K1-K6 idempotent an; Versionszeile der Seite
kommt aus legal_documents. Go-Live-Checkliste um beide Seeder ergaenzt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Selbst gehostetes Umami (sys.adametz.media) je Brand: Website-IDs aus
dev/release/umami.md in config/domains.php, Snippet via x-web.umami im
web-master-Layout auf allen oeffentlichen Seiten der drei Domains.
UMAMI_ENABLED=false haelt es aus, bis die Webseiten live gehen
(User-Vorgabe 06.07.; Go-Live-Checkliste WS-7 ergaenzt). Damit stimmt
Datenschutz Abschnitt 5 mit dem Code ueberein. User bestaetigt ausserdem:
Log-/Backup-Fristen und Stripe-Entitaet passen wie eingetragen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User-Entscheidung 06.07.: info@pressekonto.com ist die Haupt-E-Mail fuer
alle rechtlichen Anliegen. Zentral in config/legal.php (LEGAL_CONTACT_EMAIL),
verwendet von Impressum, AGB, Datenschutz, /melden und /richtlinien auf
allen drei Domains. Support-Kontakt bleibt je Brand (CONTACT_EMAIL_*),
REPORT_EMAIL_* ist nur noch optionales separates DSA-Postfach. Nebenbei
den verunglueckten Enterprise-Hinweis auf der Buchungsseite repariert
(E-Mail war ohne Trennung angeklebt).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- GET kontakt/ueber-uns -> 301 auf die Ausgaben-Startseite;
kontakt.store-POST bleibt fuer das Hub-Kontakt-Modal
- Legacy-Aliases (contact/kontakt/ueber-uns) buendeln auf die Startseite
- Spoke-Footer: Kontakt als mailto (Betreiber-Fallback bis R2.1),
Ueber-uns-Link raus, Selfservice-Kuendigungslink ergaenzt
- Sitemap-Konstanten bereinigt, Tests angepasst
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
team/partner/karriere/presse (erfundene Personen, Fake-Partner,
Bewerben-Buttons ohne Ziel) samt Routen entfernt (User-Entscheidung
03.07.: lieber weniger Seiten, aber ehrlich). Altes Scaffold
(livewire web.burger-menu/header/footer) wurde nur von diesen Seiten
genutzt und ist mit raus. Legacy-.html-Links: team/partner/karriere
-> 301 Startseite; presse/* behaelt die bestehende 301-Regel auf /faq.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Oeffentliche Bestaetigungsseite /de/kuendigung ohne Login: Art der
Kuendigung (Grund nur bei ausserordentlich), Zuordnungsdaten,
Beendigungszeitpunkt; Honeypot + Rate-Limit wie beim Widerruf.
Sofortige automatische Zugangsbestaetigung per E-Mail (Par. 312k
Abs. 4 BGB) + Betreiber-Mail. Kuendigungen laufen in der
Recht-&-Compliance-Queue unter den Widerrufen auf (K-Vorgaenge,
eigener Filter, Erledigt/Wieder-oeffnen, vierte KPI-Karte).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>