presseportale/app/Http/Controllers/Web/LlmsTxtController.php
Kevin Adametz 106edb1384 web: Transparenzseiten — was kostet, ohne Abo, loeschen/aendern (Roadmap 2.4)
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>
2026-09-17 12:00:28 +00:00

128 lines
6.3 KiB
PHP

<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Models\Category;
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
{
$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)',
'- [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
{
$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',
'',
'## 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',
'- [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)',
'',
]);
}
}