'pdf', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'excel', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'word', default => 'download', }; return $this->renderIcon($environment, $icon, $classes); } public function formatBytes(int $bytes, ?int $precision = 2): string { if (0 === $bytes) { return '0 B'; } $units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; $factor = (int) floor(log($bytes, 1024)); return sprintf("%.{$precision}f %s", $bytes / (1024 ** $factor), $units[$factor]); } public function formatMoney(int $amount): string { // Amounts are stored as integers so divide by 100 first $amount = $amount / 100; return $this->intlExtension->formatCurrency($amount, 'EUR'); } /** * Formats a service price, showing "inkl." for zero-priced (included) services. * * @param float|int|null $price The price to format * @param bool $showIncludedLabel Whether to show "inkl." for zero prices (default: true) * * @return string The formatted price, "inkl." for zero/null prices (if enabled), or empty string */ public function formatServicePrice(float|int|null $price, bool $showIncludedLabel = true): string { if (null === $price || 0 === $price || 0.0 === $price) { return $showIncludedLabel ? 'inkl.' : ''; } return $this->intlExtension->formatCurrency((float) $price, 'EUR'); } public function mapStatus(string $status): string { $status = strtoupper($status); return match ($status) { 'F', 'F/S' => 'Festbuchung', 'S' => 'Stornierung', 'O' => 'Option', 'U' => 'Umbuchung', 'A' => 'Anfrage', default => '', }; } public function mapGender(?string $gender): string { if (null === $gender) { return ''; } $gender = strtoupper($gender); return match ($gender) { 'M' => 'männlich', 'W' => 'weiblich', 'D' => 'divers', default => '', }; } public function mapCountry(?string $country): ?string { if (null === $country) { return null; } return $this->countryDataProvider->get($country)?->name; } public function mapNationality(?string $nationality): ?string { if (null === $nationality) { return null; } return $this->countryDataProvider->get($nationality)?->nationality; } public function isParticipantEligible(BookingDto $bookingDto, int $participantIndex): bool { return $this->participantEligibilityService->isParticipantEligible($bookingDto, $participantIndex); } /** * Renders a QA attribute for testing purposes. * * @param string $label The label for the QA attribute * @param string|null $value The value for the QA attribute * * @return string The rendered QA attribute */ public function renderQaAttribute(string $label, ?string $value = null): string { if ('prod' === $this->environment) { return ''; } if (null === $value) { return sprintf(' data-qa-%s', strtolower($label)); } return sprintf(' data-qa-%s="%s"', strtolower($label), $value); } /** * Checks if a form field should be rendered as static text. * * Accepts either a FormView (when field is in form structure) or a string field name * (when field is excluded from form but needs to be checked). * * @param FormView|string $field The FormView or field name * @param BookingDto $bookingDto The booking context (create or edit mode) * @param int $participantIndex The participant index * * @return bool True if field should render as static text */ public function isStaticText(FormView|string $field, BookingDto $bookingDto, int $participantIndex): bool { $fieldName = $field instanceof FormView ? $field->vars['name'] : $field; $provider = $this->getProvider($bookingDto); return $provider->shouldRenderAsStaticText($fieldName, $bookingDto, $participantIndex); } /** * Checks if a form field should be completely hidden (not rendered at all). * * Accepts either a FormView (when field is in form structure) or a string field name * (when field is excluded from form but needs to be checked). * * @param FormView|string $field The FormView or field name * @param BookingDto $bookingDto The booking context (create or edit mode) * @param int $participantIndex The participant index * * @return bool True if field should be hidden (not rendered) */ public function isHidden(FormView|string $field, BookingDto $bookingDto, int $participantIndex): bool { $fieldName = $field instanceof FormView ? $field->vars['name'] : $field; $provider = $this->getProvider($bookingDto); return !$provider->shouldIncludeField($fieldName, $bookingDto, $participantIndex); } /** * Recursively collects labels of invalid form fields. * * @param FormView $form The form view to check * * @return array Array of invalid field labels */ public function collectInvalidFieldLabels(FormView $form): array { $labels = []; foreach ($form->children as $child) { $hasErrors = \count($child->vars['errors']) > 0; $hasInvalidChildren = false === $child->vars['valid']; if ($hasErrors || $hasInvalidChildren) { $hasChildren = \count($child->children) > 0; $isExpanded = $child->vars['expanded'] ?? false; if ($hasChildren && false === $isExpanded && false === $hasErrors) { // Nested form without own errors (e.g., address, bodyDimensions) - recurse $labels = array_merge($labels, $this->collectInvalidFieldLabels($child)); } else { // Leaf field, expanded choice, or compound field with own errors $labels[] = $child->vars['label'] ?? $child->vars['name']; } } } return $labels; } /** * Returns the current booking theme from request attribute. * * The theme is resolved by DomainThemeListener based on the request host. * Defaults to 'base' if no theme is set. */ public function getBookingTheme(): string { return $this->getDomainConfig()->theme; } /** * Returns the GTM container ID for the current domain. * * Returns an empty string if no GTM ID is configured for this domain. */ public function getGtmId(): string { return $this->getDomainConfig()->gtmId; } /** * Returns the CMP (Consent Management Platform) URL for the current domain. * * Returns an empty string if no CMP URL is configured for this domain. */ public function getCmpUrl(): string { return $this->getDomainConfig()->cmpUrl; } private function getDomainConfig(): DomainConfig { $request = $this->requestStack->getCurrentRequest(); if (null === $request) { return DomainConfig::default(); } return $request->attributes->get( DomainThemeListener::DOMAIN_CONFIG_ATTRIBUTE, DomainConfig::default() ); } /** * Gets the appropriate field state provider based on booking mode. * * @param BookingDto $bookingDto The booking context * * @return CreateFieldStateProvider|EditFieldStateProvider The appropriate provider */ private function getProvider(BookingDto $bookingDto): CreateFieldStateProvider|EditFieldStateProvider { return BookingDto::MODE_CREATE === $bookingDto->getMode() ? $this->createFieldStateProvider : $this->editFieldStateProvider; } }