getUser(); $email = $user->getEmail(); $password = $this->crypt->decrypt($user->getPassword()); $personalData = $this->loadPersonalData($user); $newsletterSubscribed = $this->newsletterManager->hasConfirmedOptIn($email); $newsletterPendingConfirmation = $this->newsletterManager->hasPendingConfirmation($email); // The e-mail is deliberately locked here: BPN stores it on the address, where it doubles as // the login identity of any person on that address. Accepting a submitted value would let // one household member overwrite another's login. It is only ever echoed back to BPN as read. $personalDataForm = $this->createForm(PersonalDataType::class, $personalData, [ 'attr' => ['novalidate' => 'novalidate'], 'validation_groups' => ['personal_data'], 'email_editable' => false, ]); // A failed load yields an empty PersonalData. Updating from it would send blank values for // fields we never read - including the e-mail - so the form must not accept a submission. if (null !== $personalData->addressId) { $personalDataForm->handleRequest($request); } if ($personalDataForm->isSubmitted() && $personalDataForm->isValid()) { try { $updateResult = $this->apiClient->updatePersonalData($email, $password, $personalData); if ($updateResult instanceof Notification) { $this->logger->error('Personal data update rejected by BusProNet', [ 'email' => $user->getEmail(), 'code' => $updateResult->code, 'error' => $updateResult->message, ]); $this->addFlash('error', 'Deine persönlichen Daten konnten nicht aktualisiert werden. Bitte versuche es erneut.'); return $this->redirectToRoute('app_personal_data'); } if (false === $updateResult->changed) { $this->logger->error('BPN did not apply personal data update', [ 'email' => $user->getEmail(), 'addressId' => $updateResult->addressId, ]); $this->addFlash('error', 'Deine persönlichen Daten konnten nicht aktualisiert werden. Bitte versuche es erneut.'); return $this->redirectToRoute('app_personal_data'); } // Invalidate cached bookings to ensure edit mode shows updated applicant data $this->dataLoader->invalidateUserBookingCaches($user); // Update profile completeness flag on user entity $isComplete = $this->completenessChecker->isComplete($personalData); $user ->setProfileComplete($isComplete) // Otherwise a rename here would stay invisible in the backend until the next // login, which is the only other place the name is synced from BPN. ->setFirstName($personalData->firstName) ->setLastName($personalData->name) ; $this->entityManager->flush(); $this->addFlash('success', 'Deine persönlichen Daten wurden aktualisiert'); $this->logger->info('Updated personal data', [ 'email' => $user->getEmail(), ]); // Handle profile completion redirect $session = $request->getSession(); $redirectUrl = $session->get(self::SESSION_REDIRECT_KEY); if (null !== $redirectUrl && true === $isComplete) { $session->remove(self::SESSION_REDIRECT_KEY); return $this->redirect($redirectUrl); } } catch (ApiClientException $e) { $this->addFlash('error', $e->getMessage()); } return $this->redirectToRoute('app_personal_data'); } return $this->render('account/personal_data.html.twig', [ 'personalData' => $personalData, 'personalDataForm' => $personalDataForm, 'newsletterSubscribed' => $newsletterSubscribed, 'newsletterPendingConfirmation' => $newsletterPendingConfirmation, ]); } #[Route('/personal-data/newsletter', name: 'app_personal_data_newsletter', methods: ['POST'])] #[IsGranted('ROLE_USER')] public function newsletter(Request $request): Response { /** @var User $user */ $user = $this->getUser(); $email = $user->getEmail(); $personalData = $this->loadPersonalData($user); $hasPendingConfirmation = $this->newsletterManager->hasPendingConfirmation($email); try { if (true === $hasPendingConfirmation) { $this->newsletterManager->requestConfirmation($email, $personalData->firstName, $personalData->name); $this->addFlash('success', 'Wir haben dir eine neue Bestätigungs-E-Mail gesendet.'); } else { $result = $this->newsletterManager->requestDefaultListSubscription($email, $personalData->firstName, $personalData->name); $this->addFlash(...$this->newsletterRequestFlash($result)); } } catch (NewsletterProviderException|\InvalidArgumentException $e) { $this->addFlash('error', 'Die Newsletter-Aktion konnte gerade nicht verarbeitet werden. Bitte versuche es erneut.'); $this->logger->warning('Newsletter action failed', [ 'email' => $email, 'error' => $e->getMessage(), ]); } return $this->htmxRedirect($request, $this->generateUrl('app_personal_data')); } /** * @return array{string, string} */ private function newsletterRequestFlash(NewsletterSubscriptionRequestResult $result): array { $states = array_unique($result->listStates); if ( NewsletterSubscriptionRequestResult::STATE_SUBSCRIBED === $result->state && [NewsletterSubscriptionRequestResult::LIST_STATE_ALREADY_REGISTERED] === array_values($states) ) { return ['info', 'Du bist bereits zum Newsletter angemeldet.']; } return match ($result->state) { NewsletterSubscriptionRequestResult::STATE_SUBSCRIBED => ['success', 'Deine Newsletter-Anmeldung wurde aktualisiert.'], NewsletterSubscriptionRequestResult::STATE_PENDING_CONFIRMATION => ['info', 'Deine Anmeldung muss noch bestätigt werden. Bitte nutze den Link aus der Bestätigungs-E-Mail.'], default => ['success', 'Bitte bestätige deine Newsletter-Anmeldung über den Link in der E-Mail.'], }; } private function loadPersonalData(User $user): PersonalData { $email = $user->getEmail(); $password = $this->crypt->decrypt($user->getPassword()); try { $personalData = $this ->apiClient ->getPersonalData($email, $password); } catch (ApiClientException $e) { $this->addFlash('error', 'Deine persönlichen Daten konnten nicht abgerufen werden'); return new PersonalData(); } if ($personalData instanceof Notification) { $this->logger->error('Unable to fetch personal data', [ 'code' => $personalData->code, 'error' => $personalData->message, ]); $this->addFlash('error', 'Deine persönlichen Daten konnten nicht abgerufen werden'); return new PersonalData(); } return $personalData; } }