- /{edition}/newsroom/{slug}/feed: Sichtbarkeit wie die Newsroom-Seite
(aktiv, nicht entfernt, mindestens eine Meldung), Prüfung vor dem Cache,
301 bei zusammengeführter Firma; Autodiscovery und Link „RSS-Feed" auf
der Newsroom-Seite; Hub leitet auf das Portal der Firma.
- /{edition}/rss (RssPageController, static.rss DE/EN): Was RSS ist, wie
ein Reader funktioniert, Hauptfeed, alle Branchen-Feeds mit Adresse,
Muster des Newsroom-Feeds, Hinweise. Portalseite mit Portal-Kontext,
Hub → presseecho, in Sitemap und llms.txt.
- Topleiste, Burger-Menü und Footer verlinken RSS neben FAQ/Hilfe/API.
- FeedController in portal/category/newsroom aufgeteilt; Panel
„Auslieferung" nennt zusätzlich den Newsroom-Feed.
- Tests: FeedTest (+3), RssPageTest (4), StaticPagesTest, VisibilityStatsTest.
- dev/2026-09-01: Nachtrag in Protokoll Teil 11 und Roadmap 3.1.
Co-Authored-By: Claude Fable 5.1 <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}',
|
|
'- 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)',
|
|
'',
|
|
]);
|
|
}
|
|
}
|