- R-02/K-03: Aufrufaggregation und Merge ändern updated_at nicht mehr; neue Spalte press_releases.content_changed_at (INSTANT), contentModifiedAt() für Sitemap-lastmod, dateModified und article:modified_time - R-03: Sitemap und Legacy-view_url mit /en/ für englische Meldungen - R-04/O01: X-Pk-Ref je Empfänger per HMAC signiert; auf eine Sperre zählt — hart wie weich — nur ein gültig signierter Rückläufer, unsignierte standardmäßig nur zur Sichtung (BOUNCE_UNSIGNED_KIND=policy) - R-05/R-06: Unmerge setzt nur unveränderte Füllwerte zurück (company_merge_moves.merged_value), Canonical-Neubewertung nach Merge und Unmerge, assignAll setzt veraltete Verweise zurück - R-07 bis R-11: Crawler zählen nicht als Aufruf, Mail-Klick gedrosselt und verzögert geschrieben, Claim-Zähler erst nach Versand, email_bounces nach 395 Tagen gelöscht, Canonical-Portal über Kategorie-ID, llms-Cache geleert, Feed-Teaser ohne Doppel-Escaping, Merge-Ketten aufgelöst - N17/N18: kein falsches „gelistet seit", Rollen im Indexbericht wie Sitemap - OPT-1/OPT-2: topFeeds in SQL, purge-events löscht nach Grenz-ID Enthält aus PressRelease.php auch die Card-Bild-Helfer (B-01) und wasEverPublished()/neverPublished() (A-05), weil die Datei ungeteilt committet ist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
137 lines
7.4 KiB
PHP
137 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Web;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Category;
|
|
use App\Support\StaticPageCanonical;
|
|
use App\Support\WebPortalContext;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
/**
|
|
* `/llms.txt` (Roadmap 2.7): eine kurze, sachliche Beschreibung des Hosts
|
|
* für KI-Systeme — was die Seite ist, wie die URLs aufgebaut sind, wo die
|
|
* strukturierten Daten und Sitemaps liegen. Keine Werbeaussagen, keine
|
|
* Versprechen über Nennungen in KI-Antworten (Formulierungsgrenze 2.7).
|
|
* Hub und Spokes bekommen je eine eigene Fassung; pro Host eine Stunde
|
|
* gecacht.
|
|
*/
|
|
class LlmsTxtController extends Controller
|
|
{
|
|
public function __invoke(Request $request): Response
|
|
{
|
|
$host = $request->getHost();
|
|
$base = rtrim($request->getSchemeAndHttpHost(), '/');
|
|
$isHub = str_contains($host, 'pressekonto');
|
|
|
|
$text = Cache::remember('web.llms.'.$host, 3600, fn (): string => $isHub ? $this->hub($base) : $this->portal($base));
|
|
|
|
return response($text, 200, ['Content-Type' => 'text/plain; charset=UTF-8']);
|
|
}
|
|
|
|
private function hub(string $base): string
|
|
{
|
|
// Seiten, die auf allen drei Hosts liegen, werden mit der führenden
|
|
// Adresse genannt (Anhang 2, E10) — eine Adresse je Text.
|
|
$shared = static fn (string $route): string => StaticPageCanonical::baseFor($route) ?? $base;
|
|
$presseecho = rtrim((string) config('domains.domains.presseecho.url'), '/');
|
|
$businessportal = rtrim((string) config('domains.domains.businessportal24.url'), '/');
|
|
|
|
return implode("\n", [
|
|
'# pressekonto',
|
|
'',
|
|
'> Publisher-Bereich der Presseportale presseecho und businessportal24: Unternehmen schreiben hier Pressemitteilungen, lassen sie automatisiert prüfen und veröffentlichen sie auf beiden Portalen.',
|
|
'',
|
|
'Die Meldungen selbst erscheinen nicht auf dieser Domain, sondern auf den beiden Portalen. Jede Meldung trägt dort NewsArticle-Markup (schema.org), jeder Newsroom Organization-Markup; Urheber einer Meldung ist immer das genannte Unternehmen, nicht das Portal.',
|
|
'',
|
|
'## Informationen',
|
|
'',
|
|
'- [Hilfe]('.$base.'/de/hilfe): Ablauf von der Registrierung bis zur Veröffentlichung',
|
|
'- [Was eine gute Pressemitteilung ausmacht]('.$base.'/de/gute-pressemitteilung): Anlass, Aufbau, Sprache, Prüfablauf',
|
|
'- [FAQ]('.$base.'/de/faq)',
|
|
'- [Richtlinien]('.$base.'/de/richtlinien): Was veröffentlicht wird und was nicht',
|
|
'- [Was kostet eine Pressemitteilung?]('.$base.'/de/was-kostet-eine-pressemitteilung): Abrechnungsmodelle, Prüfliste für Folgekosten, Rechenbeispiel',
|
|
'- [Pressemitteilung löschen oder ändern lassen]('.$base.'/de/pressemitteilung-loeschen-oder-aendern-lassen)',
|
|
'- [Pressemitteilung kostenlos veröffentlichen?]('.$base.'/de/pressemitteilung-kostenlos-veroeffentlichen): Warum es hier keine kostenlose Einreichung gibt, was kostenlose Portale leisten',
|
|
'- [API]('.$base.'/de/api): Einlieferung per REST-API; OpenAPI-Spezifikation unter '.$base.'/docs/api/v1',
|
|
'',
|
|
'## Portale',
|
|
'',
|
|
'- [presseecho]('.$presseecho.'/llms.txt): Pressemitteilungen aus Wirtschaft und Industrie',
|
|
'- [businessportal24]('.$businessportal.'/llms.txt): Pressemitteilungen aus Deutschland, Österreich und der Schweiz',
|
|
'',
|
|
'## Rechtliches',
|
|
'',
|
|
'- [Impressum]('.$base.'/de/impressum)',
|
|
'- [Datenschutz]('.$base.'/de/datenschutz)',
|
|
'- [AGB]('.$base.'/de/agb)',
|
|
'- [Inhalte melden]('.$base.'/de/melden)',
|
|
'',
|
|
]);
|
|
}
|
|
|
|
private function portal(string $base): string
|
|
{
|
|
// Wie im Hub: gemeinsame Seiten mit der führenden Adresse nennen.
|
|
$shared = static fn (string $route): string => StaticPageCanonical::baseFor($route) ?? $base;
|
|
$context = WebPortalContext::fromRequest();
|
|
$brand = (array) ($context->domainConfig['brand'] ?? []);
|
|
$name = ($brand['name'] ?? '').($brand['accent'] ?? '');
|
|
$hub = rtrim((string) config('domains.domains.pressekonto.url'), '/');
|
|
|
|
$categories = Category::query()
|
|
->where('is_active', true)
|
|
->whereIn('portal', $context->portalValues)
|
|
->with('translations')
|
|
->get()
|
|
->map(fn (Category $category) => $category->translations->firstWhere('locale', 'de'))
|
|
->filter()
|
|
->sortBy('name')
|
|
->map(fn ($translation) => '- ['.$translation->name.']('.$base.'/de/category/'.$translation->slug.')')
|
|
->values()
|
|
->all();
|
|
|
|
return implode("\n", [
|
|
'# '.$name,
|
|
'',
|
|
'> '.($brand['tagline_long'] ?? 'Presseportal für Pressemitteilungen.'),
|
|
'',
|
|
'Pressemitteilungen werden von Unternehmen eingereicht und vor der Veröffentlichung geprüft. Urheber einer Meldung ist das im Kopf genannte Unternehmen; das Portal ist Herausgeber. Meldungen tragen NewsArticle-Markup (schema.org) mit dem Newsroom des Unternehmens als author, Newsrooms tragen Organization-Markup.',
|
|
'',
|
|
'## Aufbau',
|
|
'',
|
|
'- Meldung: '.$base.'/de/press-release/{slug} — englischsprachige Meldungen unter '.$base.'/en/press-release/{slug}',
|
|
'- Newsroom eines Unternehmens: '.$base.'/de/newsroom/{slug} — Übersicht unter '.$base.'/de/newsrooms',
|
|
'- Kategorie: '.$base.'/de/category/{slug}',
|
|
'- Sitemap: '.$base.'/sitemap.xml — News-Sitemap (Meldungen der letzten 48 Stunden): '.$base.'/sitemap-news.xml',
|
|
'- RSS-Feeds: alle Meldungen '.$base.'/de/feed — je Kategorie '.$base.'/de/category/{slug}/feed — je Newsroom '.$base.'/de/newsroom/{slug}/feed — Übersicht: '.$base.'/de/rss',
|
|
'',
|
|
'## Kategorien',
|
|
'',
|
|
...$categories,
|
|
'',
|
|
'## Für Unternehmen',
|
|
'',
|
|
'- [Veröffentlichen]('.$base.'/de/veroeffentlichen): Wie eine Meldung auf das Portal kommt',
|
|
'- [Preise]('.$base.'/de/preise)',
|
|
'- [Was kostet eine Pressemitteilung?]('.$base.'/de/was-kostet-eine-pressemitteilung): Abrechnungsmodelle, Prüfliste für Folgekosten, Rechenbeispiel',
|
|
'- [Pressemitteilung veröffentlichen ohne Abo]('.$base.'/de/pressemitteilung-veroeffentlichen-ohne-abo)',
|
|
'- [Pressemitteilung löschen oder ändern lassen]('.$base.'/de/pressemitteilung-loeschen-oder-aendern-lassen): Korrektur, Änderung, DSGVO, Depublikation',
|
|
'- [Pressemitteilung kostenlos veröffentlichen?]('.$base.'/de/pressemitteilung-kostenlos-veroeffentlichen): Warum es hier keine kostenlose Einreichung gibt, was kostenlose Portale leisten',
|
|
'- [Was eine gute Pressemitteilung ausmacht]('.$base.'/de/gute-pressemitteilung)',
|
|
'- [Hilfe]('.$base.'/de/hilfe)',
|
|
'- [API]('.$base.'/de/api): Einlieferung per REST-API über den Publisher-Bereich '.$hub,
|
|
'',
|
|
'## Rechtliches',
|
|
'',
|
|
'- [Impressum]('.$base.'/de/impressum)',
|
|
'- [Datenschutz]('.$base.'/de/datenschutz)',
|
|
'- [AGB]('.$base.'/de/agb)',
|
|
'- [Richtlinien]('.$base.'/de/richtlinien)',
|
|
'- [Inhalte melden]('.$base.'/de/melden)',
|
|
'',
|
|
]);
|
|
}
|
|
}
|