R3.2: Kontakt-/Ueber-uns-Routen zurueckgebaut, Spoke-Footer bereinigt

- 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>
This commit is contained in:
Kevin Adametz 2026-07-03 15:36:13 +00:00
parent 73fc3827c8
commit 0645d1d855
6 changed files with 55 additions and 25 deletions

View file

@ -16,21 +16,22 @@ use Illuminate\Support\Facades\Cache;
* Host-aware XML-Sitemap: Der Hub listet Landing- und Rechts-/Info-Seiten,
* die Spokes (presseecho, businessportal24) zusätzlich Kategorien, Newsrooms
* und alle im Archiv sichtbaren Pressemitteilungen ihres Portals. Marketing-
* Platzhalterseiten (team, presse, karriere, partner) und die Suche bleiben
* bewusst draußen (R2.4/R3.2). Pro Host eine Stunde gecacht.
* Platzhalterseiten (team, presse, karriere, partner, kontakt, ueber-uns)
* und die Suche bleiben bewusst draußen (R2.4/R3.2). Pro Host eine Stunde
* gecacht.
*/
class SitemapController extends Controller
{
/** @var list<string> */
private const HUB_PAGES = [
'faq', 'kontakt', 'ueber-uns', 'hilfe', 'api', 'kuendigung',
'faq', 'hilfe', 'api', 'kuendigung',
'impressum', 'datenschutz', 'agb', 'cookies',
];
/** @var list<string> */
private const PORTAL_PAGES = [
'preise', 'veroeffentlichen', 'newsrooms', 'faq', 'kontakt',
'ueber-uns', 'hilfe', 'api', 'impressum', 'datenschutz', 'agb', 'cookies',
'preise', 'veroeffentlichen', 'newsrooms', 'faq',
'hilfe', 'api', 'impressum', 'datenschutz', 'agb', 'cookies',
];
public function __invoke(Request $request): Response

View file

@ -17,7 +17,9 @@
$brandAccent = $brand['accent'] ?? '';
$brandTagline = $brand['tagline_short'] ?? 'Pressemitteilungen · DACH';
$brandTaglineLong = $brand['tagline_long'] ?? 'Pressemitteilungen aus dem DACH-Raum.';
$aboutLabel = $brand['about_label'] ?? ('Über '.$brandName.$brandAccent);
// Betreiber-Postfach (adametz.media) als Fallback, bis die portal-
// spezifischen CONTACT_EMAIL_*-Adressen gesetzt sind (R2.1).
$contactEmail = $brand['contact_email'] ?? 'info@businessportal24.com';
$legal = str_replace(':year', (string) now()->format('Y'), $brand['footer_legal'] ?? '© :year · Alle Rechte vorbehalten');
@endphp
@ -59,10 +61,10 @@
<div>
<div class="eyebrow mb-3.5">Unternehmen</div>
<ul class="space-y-2 text-[13px] text-white/75 list-none p-0 m-0">
<li><a href="{{ route('ueber-uns') }}" class="cursor-pointer hover:text-white transition-colors">{{ $aboutLabel }}</a></li>
<li><a href="{{ route('kontakt') }}" class="cursor-pointer hover:text-white transition-colors">Kontakt</a></li>
<li><a href="mailto:{{ $contactEmail }}" class="cursor-pointer hover:text-white transition-colors">Kontakt</a></li>
<li><a href="{{ route('faq') }}" class="cursor-pointer hover:text-white transition-colors">FAQ</a></li>
<li><a href="{{ route('hilfe') }}" class="cursor-pointer hover:text-white transition-colors">Hilfe-Center</a></li>
<li><a href="{{ route('kuendigung') }}" class="cursor-pointer hover:text-white transition-colors">Verträge hier kündigen</a></li>
<li><a href="{{ route('impressum') }}" class="cursor-pointer hover:text-white transition-colors">Impressum</a></li>
<li><a href="{{ route('datenschutz') }}" class="cursor-pointer hover:text-white transition-colors">Datenschutz</a></li>
</ul>

View file

@ -118,22 +118,25 @@ $htmlRedirect = static function (string $edition, string $path) {
}
// Legacy-Slugs des Altsystems (meist englisch) -> neue deutsche Pfade.
// team/partner/karriere: Seiten wurden zurückgebaut (R3.2) -> Startseite.
// team/partner/karriere/kontakt/ueber-uns: Seiten wurden zurückgebaut
// (R3.2 bzw. Platzhalter-Inhalte) -> Startseite.
$staticAliases = [
'pricelist' => 'preise',
'service' => 'veroeffentlichen',
'contact' => 'kontakt',
'contact' => '',
'imprint' => 'impressum',
'terms' => 'agb',
'privacypolicy' => 'datenschutz',
'team' => '',
'partner' => '',
'karriere' => '',
'kontakt' => '',
'ueber-uns' => '',
];
// Bereits saubere Zielpfade der neuen Struktur (Pfad bleibt erhalten).
$staticPages = [
'preise', 'faq', 'kontakt', 'ueber-uns', 'veroeffentlichen',
'preise', 'faq', 'veroeffentlichen',
'suche', 'newsrooms', 'api',
'hilfe', 'impressum', 'datenschutz', 'agb', 'cookies',
];
@ -194,13 +197,15 @@ Route::prefix('{edition}')->whereIn('edition', SetEdition::EDITIONS)->group(func
// Preise / Tarife (datengetrieben: Plan + config/billing + config/credits)
Route::get('preise', PreiseController::class)->name('preise');
// Statische Seiten
// Statische Seiten. Kontakt- und Über-uns-Seite wurden zurückgebaut
// (Platzhalter-Adresse/-Team); Alt-Links landen per 301 auf der Startseite.
// Der Kontakt-POST bleibt für das Hub-Kontakt-Modal erhalten.
Route::view('faq', 'web.faq')->name('faq');
Route::view('kontakt', 'web.kontakt')->name('kontakt');
Route::get('kontakt', fn (string $edition) => redirect(request()->getSchemeAndHttpHost().'/'.$edition, 301));
Route::get('ueber-uns', fn (string $edition) => redirect(request()->getSchemeAndHttpHost().'/'.$edition, 301));
Route::post('kontakt', ContactController::class)
->middleware('throttle:10,1')
->name('kontakt.store');
Route::view('ueber-uns', 'web.ueber-uns')->name('ueber-uns');
Route::get('veroeffentlichen', VeroeffentlichenController::class)->name('veroeffentlichen');
Route::get('suche', SucheController::class)->name('suche');
Route::get('newsrooms', NewsroomsController::class)->name('newsrooms');

View file

@ -4,9 +4,11 @@ use App\Mail\ContactRequestSubmitted;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
test('the contact page renders the form', function () {
test('the hub landing renders the contact modal form', function () {
/** @var TestCase $this */
$this->get('https://businessportal24.test/de/kontakt')
// Die frühere Kontakt-Seite wurde zurückgebaut; das Formular lebt im
// Kontakt-Modal des Hubs und postet weiter auf kontakt.store.
$this->get('https://pressekonto.test/')
->assertSuccessful()
->assertSee('name="name"', false)
->assertSee('name="email"', false)
@ -19,13 +21,13 @@ test('a valid contact request sends a mail to the configured recipient', functio
Mail::fake();
config()->set('domains.domains.businessportal24.brand.contact_email', 'redaktion@businessportal24.de');
$this->from('https://businessportal24.test/de/kontakt')
$this->from('https://businessportal24.test/de')
->post('https://businessportal24.test/de/kontakt', [
'name' => 'Paula Presse',
'email' => 'paula@example.test',
'message' => 'Ich habe eine Frage zu Ihrer Plattform und bitte um Rückruf.',
])
->assertRedirect('https://businessportal24.test/de/kontakt')
->assertRedirect('https://businessportal24.test/de')
->assertSessionHas('contact-status');
Mail::assertSent(ContactRequestSubmitted::class, function (ContactRequestSubmitted $mail) {

View file

@ -19,12 +19,24 @@ test('legacy static slugs of the old site redirect to the new german paths', fun
})->with([
'pricelist -> preise' => ['pricelist.html', 'preise'],
'service -> veroeffentlichen' => ['service.html', 'veroeffentlichen'],
'contact -> kontakt' => ['contact.html', 'kontakt'],
'imprint -> impressum' => ['imprint.html', 'impressum'],
'terms -> agb' => ['terms.html', 'agb'],
'privacypolicy -> datenschutz' => ['privacypolicy.html', 'datenschutz'],
]);
test('retired static pages redirect to the edition home', function (string $legacy) {
/** @var TestCase $this */
// kontakt/ueber-uns wurden zurückgebaut (Platzhalter-Inhalte); die
// Alt-Slugs landen wie team/partner/karriere auf der Startseite.
$this->get('https://businessportal24.test/de/'.$legacy)
->assertStatus(301)
->assertRedirect('https://businessportal24.test/de');
})->with([
'contact' => ['contact.html'],
'kontakt' => ['kontakt.html'],
'ueber-uns' => ['ueber-uns.html'],
]);
test('legacy account urls redirect to the publisher hub', function (string $legacy, string $hubPath) {
/** @var TestCase $this */
$hubUrl = rtrim((string) config('domains.domain_portal_url'), '/');
@ -104,9 +116,9 @@ test('dropped language/region variants redirect to the default edition', functio
test('legacy aliases also work without an edition prefix', function () {
/** @var TestCase $this */
$this->get('https://businessportal24.test/contact.html')
$this->get('https://businessportal24.test/terms.html')
->assertStatus(301)
->assertRedirect('https://businessportal24.test/de/kontakt');
->assertRedirect('https://businessportal24.test/de/agb');
$this->get('https://businessportal24.test/imprint.html')
->assertStatus(301)

View file

@ -9,9 +9,7 @@ dataset('static pages', [
'cookies' => ['de/cookies', 'Cookie-Richtlinien'],
'faq' => ['de/faq', 'Häufig gestellte Fragen'],
'hilfe' => ['de/hilfe', 'Hilfe-Center'],
'kontakt' => ['de/kontakt', 'Kontakt'],
'ueber-uns' => ['de/ueber-uns', 'Über uns'],
'api' => ['de/api', 'API & Integrationen'],
'api' => ['de/api', 'API-Dokumentation'],
]);
test('static pages render in the editorial design', function (string $path, string $heading) {
@ -32,9 +30,19 @@ test('the faq page lists questions and links to support', function () {
->assertSuccessful()
->assertSeeText('Wie veröffentliche ich meine erste Pressemitteilung?')
->assertSeeText('Frage nicht gefunden?')
->assertSee(route('kontakt'), false);
// Kontakt läuft nach dem Rückbau der Kontakt-Seite über das
// Support-Postfach und die Kündigung über die eigene Seite.
->assertSee('mailto:', false)
->assertSee(route('kuendigung', ['edition' => 'de']), false);
});
test('the retired contact and about pages redirect to the edition home', function (string $path) {
/** @var TestCase $this */
$this->get('https://businessportal24.test/de/'.$path)
->assertStatus(301)
->assertRedirect('https://businessportal24.test/de');
})->with(['kontakt', 'ueber-uns']);
test('the help center shows quick links and topics', function () {
/** @var TestCase $this */
$this->get('https://businessportal24.test/de/hilfe')