user() !== null; } public function rules(): array { return [ 'headline' => 'nullable|string|max:500', 'intro_text' => 'nullable|string|max:65000', 'itinerary_text' => 'nullable|string|max:65000', 'closing_text' => 'nullable|string|max:65000', 'valid_until' => 'nullable|date|after_or_equal:today', 'template_id' => [ 'nullable', 'integer', Rule::exists('offer_templates', 'id')->whereNull('deleted_at'), ], 'template_document_ids' => 'nullable|array', 'template_document_ids.*' => 'integer', 'items' => 'nullable|array|max:200', 'items.*' => 'array', 'items.*.id' => 'nullable|integer|exists:offer_items,id', 'items.*.type' => ['required', Rule::in(OfferItem::TYPES)], 'items.*.title' => 'required|string|max:1000', 'items.*.description' => 'nullable|string|max:20000', 'items.*.quantity' => 'required|integer|min:1', 'items.*.price_per_unit' => 'required|numeric', 'items.*.travel_program_id' => 'nullable|integer|min:0', 'items.*.fewo_lodging_id' => 'nullable|integer|min:0', 'items.*.metadata' => 'nullable|array', ]; } public function withValidator(Validator $validator): void { $validator->after(function (Validator $v) { if ($v->fails()) { return; } $items = $this->input('items', []); foreach (array_values($items) as $i => $row) { if (! is_array($row)) { continue; } $type = $row['type'] ?? null; $p = $row['price_per_unit'] ?? null; if ($type === null || $p === null) { continue; } if (in_array($type, [OfferItem::TYPE_TRAVEL, OfferItem::TYPE_SERVICE, OfferItem::TYPE_OPTION, OfferItem::TYPE_INSURANCE, OfferItem::TYPE_CUSTOM], true)) { if ((float) $p < 0) { $v->errors()->add("items.$i.price_per_unit", 'Der Einzelpreis muss mindestens 0 sein (außer bei Rabatten).'); } } } }); } protected function prepareForValidation(): void { if ($this->has('headline') && is_string($this->input('headline'))) { $this->merge(['headline' => strip_tags($this->input('headline'))]); } $this->merge($this->sanitizeWysiwygFields([ 'intro_text' => $this->input('intro_text'), 'itinerary_text' => $this->input('itinerary_text'), 'closing_text' => $this->input('closing_text'), ])); } /** * Einfache WYSIWYG-Whitelist (TinyMCE/TipTap): gefährliche Tags raus, Basis-Formatierung behalten. * * @param array $fields * @return array */ protected function sanitizeWysiwygFields(array $fields): array { $allowed = '