input('website')) || filled($request->input('homepage'))) { return $this->done(); } $validated = $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'email', 'max:255'], 'message' => ['required', 'string', 'min:10', 'max:5000'], 'topic' => ['nullable', Rule::enum(ContactMessageTopic::class)], 'page_url' => ['nullable', 'string', 'max:500'], ], [ 'name.required' => __('web.launcher.validation_name'), 'name.max' => __('web.launcher.validation_name'), 'email.required' => __('web.launcher.validation_email'), 'email.email' => __('web.launcher.validation_email'), 'email.max' => __('web.launcher.validation_email'), 'message.required' => __('web.launcher.validation_message'), 'message.min' => __('web.launcher.validation_message_min'), 'message.max' => __('web.launcher.validation_message_max'), ]); $this->ensureNotRateLimited($request); RateLimiter::hit($this->throttleKey($request), 3600); $topic = ContactMessageTopic::tryFrom($validated['topic'] ?? '') ?? ContactMessageTopic::Contact; ContactMessage::create([ 'portal' => $this->portal($request), 'topic' => $topic, 'name' => $validated['name'], 'email' => $validated['email'], 'message' => $validated['message'], 'page_url' => $validated['page_url'] ?? null, 'requester_ip' => $request->ip(), 'user_id' => $request->user()?->id, ]); Mail::to($this->recipient())->send(new ContactRequestSubmitted( senderName: $validated['name'], senderEmail: $validated['email'], messageText: $validated['message'], host: $request->getHost(), topicLabel: $topic->label(), pageUrl: $validated['page_url'] ?? null, )); return $this->done(); } private function done(): RedirectResponse { return back()->with('contact-status', __('web.launcher.success')); } /** * Portal-Kennung für die Admin-Queue, aus dem Host abgeleitet: * Hub (pressekonto) vs. Spoke-Portale. */ private function portal(Request $request): string { if (str_contains($request->getHost(), 'pressekonto')) { return 'pressekonto'; } return WebPortalContext::portalFromHost()->value; } private function recipient(): string { // Auch die Hub-Domain (pressekonto) nutzt das Formular — sie ist kein // Spoke-Portal, daher vor dem WebPortalContext am Host unterscheiden. if (str_contains(request()->getHost(), 'pressekonto')) { $brand = config('domains.domains.pressekonto.brand', []); } else { $brand = WebPortalContext::fromRequest()->domainConfig['brand'] ?? []; } return $brand['contact_email'] ?? config('mail.from.address'); } private function ensureNotRateLimited(Request $request): void { if (! RateLimiter::tooManyAttempts($this->throttleKey($request), 5)) { return; } $seconds = RateLimiter::availableIn($this->throttleKey($request)); throw ValidationException::withMessages([ 'message' => __('web.launcher.throttle', [ 'minutes' => max(1, (int) ceil($seconds / 60)), ]), ]); } private function throttleKey(Request $request): string { return 'contact-form|'.$request->ip(); } }