src/StartPlatz/Bundle/EventBundle/Controller/EventRegistrationController.php line 471

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace App\StartPlatz\Bundle\EventBundle\Controller;
  3. /**
  4.  * CRITICAL DEPENDENCY WARNING - APPLICATION SYSTEM INTEGRATION
  5.  *
  6.  * This controller handles the event/signup REGISTRATION flow.
  7.  * It has HEAVY DEPENDENCIES on the Application system from StartupBundle.
  8.  *
  9.  * DO NOT modify Application-related code without testing ALL event registration scenarios:
  10.  * - Anonymous (non-member) event registration
  11.  * - Member event registration
  12.  * - Paid event registration with Stripe
  13.  * - Free event registration
  14.  * - Multi-batch event registration
  15.  * - Event registration with validation workflows
  16.  *
  17.  * Extracted from DefaultController (B-60 Phase 2). Uses shared services for dependencies.
  18.  */
  19. use App\StartPlatz\Bundle\EventBundle\Entity\Event;
  20. use App\StartPlatz\Bundle\EventBundle\Entity\EventRepository;
  21. use App\StartPlatz\Bundle\EventBundle\Entity\EventStripePayment;
  22. use App\StartPlatz\Bundle\EventBundle\Service\EventContentResolver;
  23. use App\StartPlatz\Bundle\EventBundle\Service\EventNotificationService;
  24. use App\StartPlatz\Bundle\EventBundle\Service\EventPaymentService;
  25. use App\StartPlatz\Bundle\EventBundle\Service\EventRegistrationService;
  26. use App\StartPlatz\Bundle\EventBundle\Service\EventViewHelper;
  27. use App\StartPlatz\Bundle\EventBundle\Service\MetaConversionsApiService;
  28. use App\StartPlatz\Bundle\EventBundle\Service\SelfCheckinService;
  29. use App\StartPlatz\Bundle\EventBundle\Service\InvalidCheckinException;
  30. use App\StartPlatz\Bundle\MailBundle\Service\MailService;
  31. use App\StartPlatz\Bundle\MemberBundle\Entity\Member;
  32. use App\StartPlatz\Bundle\MemberBundle\Entity\Option;
  33. use App\StartPlatz\Bundle\MemberBundle\Entity\Product;
  34. use App\StartPlatz\Bundle\MemberBundle\Entity\Service;
  35. use App\StartPlatz\Bundle\MonsumBundle\Entity\Customer;
  36. use App\StartPlatz\Bundle\StartupBundle\Entity\Application;
  37. use App\StartPlatz\Bundle\StartupBundle\Entity\Batch;
  38. use App\StartPlatz\Bundle\StartupBundle\Entity\Program;
  39. use App\StartPlatz\Bundle\StartupBundle\Service\ApplicationSpamFilterService;
  40. use App\StartPlatz\Bundle\EventBundle\Form\EventApplicationType;
  41. use App\StartPlatz\Bundle\StartupBundle\Form\ApplicationType;
  42. use App\StartPlatz\Bundle\UserBundle\Entity\User;
  43. use App\StartPlatz\Bundle\UserBundle\LoginService;
  44. use App\StartPlatz\Bundle\MemberBundle\Entity\Team;
  45. use App\StartPlatz\Bundle\WebsiteBundle\MenuTranslationService;
  46. use App\StartPlatz\Bundle\WebsiteBundle\Utility\Utility;
  47. use App\StartPlatz\Ecosystem\Config\Runtime\Service\AlertRecipientService;
  48. use DateTime;
  49. use Doctrine\ORM\EntityManagerInterface;
  50. use Psr\Log\LoggerInterface;
  51. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  52. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  53. use Symfony\Component\HttpFoundation\Request;
  54. use Symfony\Component\HttpFoundation\Response;
  55. use Symfony\Component\Routing\Annotation\Route;
  56. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  57. class EventRegistrationController extends AbstractController
  58. {
  59.     public function __construct(
  60.         private readonly MailService $mailService,
  61.         private readonly LoginService $loginService,
  62.         private readonly LoggerInterface $logger,
  63.         private readonly EventNotificationService $eventNotificationService,
  64.         private readonly EventPaymentService $eventPaymentService,
  65.         private readonly EventRegistrationService $eventRegistrationService,
  66.         private readonly EventViewHelper $eventViewHelper,
  67.         private readonly EntityManagerInterface $entityManager,
  68.         private readonly MenuTranslationService $menuTranslationService,
  69.         private readonly AlertRecipientService $alertRecipientService,
  70.         private readonly MetaConversionsApiService $metaConversionsApiService,
  71.         private readonly SelfCheckinService $selfCheckinService,
  72.         private readonly ApplicationSpamFilterService $spamFilter,
  73.     ) {
  74.     }
  75.     #[Route('/signup/{slug}'name'signup_show_single')]
  76.     public function signupSingleAction(Request $request$slug)
  77.     {
  78.         $em $this->entityManager;
  79.         /** @var Program $program */
  80.         if (!$program $em->getRepository(Program::class /*Startup Program*/)->findOneBy(['slug' => 'sign-up'])) {
  81.             $this->addFlash('notice''ERROR Sorry, no program found');
  82.             return $this->redirect($this->generateUrl('apply_home', []));
  83.         }
  84.         /** @var Batch $batch */
  85.         if (!$batch $em->getRepository(Batch::class)->findOneBy(['slug' => $slug'program' => $program])) {
  86.             $this->addFlash('notice''ERROR Sorry, no batch found');
  87.             return $this->redirect($this->generateUrl('apply_home', []));
  88.         }
  89.         // Retrieve the UTM parameters from the URL
  90.         $utmParameters = [
  91.             'utmSource'   => $request->query->get('utm_source'),
  92.             'utmMedium'   => $request->query->get('utm_medium'),
  93.             'utmCampaign' => $request->query->get('utm_campaign'),
  94.             'utmTerm'     => $request->query->get('utm_term'),
  95.             'utmContent'  => $request->query->get('utm_content'),
  96.         ];
  97.         $lang 'DE';
  98.         if ($targetPath $request->server->get('REDIRECT_URL')) {
  99.             $lang $this->menuTranslationService->getLang($targetPath);
  100.         }
  101.         $batchIsOpen $batch && $em->getRepository(Batch::class)->isBatchApplicationOpen($batch);
  102.         $form    null;
  103.         $program null;
  104.         $member  null;
  105.         $application null;
  106.         $product     null;
  107.         $editFeedback "";
  108.         $settings     = [];
  109.         $action       "";
  110.         $customersArr = [];
  111.         if ($batchIsOpen) {
  112.             $program $batch->getProgram();
  113.             $settings $this->eventViewHelper->getProgramSettings($program$batch);
  114.             $batch $em->getRepository(Batch::class)->updateNumbers($batch);
  115.             /** @var User $user */
  116.             if ($user $this->getUser()) {
  117.                 $member $em->getRepository(Member::class)->find($user->getMemberId());
  118.             }
  119.             $action   $request->get('action');
  120.             $application $this->eventRegistrationService->getOrCreateApplication($request$batch$program$member$lang$settings$utmParametersnull);
  121.             if ($batch->getPriceInEuroCent() > 0) {
  122.                 $route                   "signup_show_single";
  123.                 $routeParameters["slug"] = $batch->getSlug();
  124.                 $application $this->handleAction($request$member$application$batch$lang$route$routeParameters$settings);
  125.                 if (is_string($application)) {
  126.                     // Handle redirect
  127.                     return $this->redirect($application);
  128.                 }
  129.             }
  130.             if ($user && in_array($action, ["needsloginvalidation""needsemailverification"])) {
  131.                 $response $this->validateEventApplication($application$member$settings$batch$lang);
  132.                 $application $response['data']['application'];
  133.             }
  134.             if ($batch->getPriceInEuroCent() > && $batch->getProductId()) {
  135.                 if (!$product $em->getRepository(Product::class /*Product Member*/)->find($batch->getProductId())) {
  136.                     $this->addFlash('notice''ERROR configuration not found');
  137.                     return $this->redirect($this->generateUrl('events_list'));
  138.                 }
  139.                 if ($application->getId() && isset($member) && $member->getId()) {
  140.                     $customersArr $em->getRepository(Customer::class)->findValidMonsumCustomersByMemberAndAccount($member$product->getAccount(), false);
  141.                 }
  142.             }
  143.             $form $this->createForm(ApplicationType::class, $application, ['settings' => $settings'program' => $program'batch' => $batch]);
  144.             $form->handleRequest($request);
  145.             ##############################################################
  146.             ##   form validation starts here                            ##
  147.             ##############################################################
  148.             if ($form->isSubmitted() && $form->isValid()) {
  149.                 $application $em->getRepository(Application::class)->cleanApplicationData($application);
  150.                 $adminEmail  $application->getEmail();
  151.                 if ($member) {
  152.                     $memberToBeRegistered $member;
  153.                     $isExistingMember     true;
  154.                 } else {
  155.                     $response $this->eventRegistrationService->resolveOrCreateMember($application);
  156.                     $isExistingMember     $response['data']['isExistingMember'];
  157.                     $memberToBeRegistered $response['data']['memberToBeRegistered'];
  158.                 }
  159.                 $memberToBeRegistered->setIsExistingMember($isExistingMember);
  160.                 // fill application with memberId and teamId and discount information
  161.                 $application $this->eventRegistrationService->bindMemberToApplication($application$memberToBeRegistered$batch);
  162.                 if (!$batch->isMultipleApplicationsPerMember()) {
  163.                     // check if there is already an application to this event == batch with that batchId and memberId
  164.                     if ($existingApplication $em->getRepository(Application::class)->findOneBy(['batchId' => $batch->getId(), 'memberId' => $memberToBeRegistered->getId()])) {
  165.                         // there are two applications - the latest application will always win
  166.                         $fields     Utility::getEntityFieldsArray($application, ['id','assessments''votesRegistered''applicationStatus''editStatus''editMessage''lastChangeUser','history','extraFields','lastModified','createdAt']);
  167.                         $storedData Utility::fillDataByObject($existingApplication$fields);
  168.                         $updatedData Utility::fillDataByObject($application$fields);
  169.                         $differences array_diff_assoc($updatedData$storedData);
  170.                         $application $existingApplication;
  171.                         $application $this->updateApplication($application$differences);
  172.                         $application->setIsExistingApplication(true);
  173.                     }
  174.                 }
  175.                 if (isset($settings['extraFields'])) {
  176.                     $application $this->eventViewHelper->getExtraFieldsData($application$settings$form);
  177.                 }
  178.                 $dummyEvent = new Event();
  179.                 $isPaidEvent $this->eventPaymentService->isPaidEvent($application$batch$dummyEvent);
  180.                 $hasToPay    $this->eventPaymentService->isPaymentRequired($application$memberToBeRegistered$dummyEvent$settings$batch);
  181.                 if ($isPaidEvent && $hasToPay) {
  182.                     // falls es bereits einen zugänglichen Monsum Account für diese Person gibt
  183.                     $customersArr $em->getRepository(Customer::class)->findValidMonsumCustomersByMemberAndAccount($memberToBeRegistered$product->getAccount(), false);
  184.                     $dummyEvent = new Event();
  185.                     $application  $this->eventPaymentService->updateApplicationWithStepOneInfo($application$memberToBeRegistered$dummyEvent);
  186.                     // Application in Datenbank schreiben, sonst kann im nächsten Schritt keine appId übergeben werden
  187.                     $application $em->getRepository(Application::class)->setApplication($application);
  188.                 } else {
  189.                     $route 'signup_show_single';
  190.                     $routeParameters $request->get('_route_params');
  191.                     $response $this->eventRegistrationService->processRegistrationWithoutPayment($isExistingMember$user$memberToBeRegistered$application$settings$lang$adminEmail$batch$route$routeParameters);
  192.                     // Bei SignUps: Prüfe ob goToPage gesetzt ist
  193.                     if ($batch->getGoToPage()) {
  194.                         // Weiterleitung zu goToPage (kann URL oder interner Pfad sein)
  195.                         return $this->redirect($batch->getGoToPage());
  196.                     }
  197.                     // Ansonsten auf SignUp-Seite bleiben - keine Weiterleitung
  198.                     $application  $response['data']['application'];
  199.                     $member       $response['data']['member'];
  200.                 }
  201.             }
  202.         }
  203.         // Default to legacy template for backward compatibility
  204.         $view '@StartPlatzEventBundle/Default/signup-single.html.twig';
  205.         // Check for pageTemplate field (new approach)
  206.         if ($batch->getPageTemplate()) {
  207.             switch ($batch->getPageTemplate()) {
  208.                 case 'modern':
  209.                 case 'flexible':
  210.                     $view '@StartPlatzEventBundle/Default/signup-single.modern.html.twig';
  211.                     break;
  212.                 case 'petition':
  213.                     $view '@StartPlatzEventBundle/Default/signup-single.deepSeekPetition.html.twig';
  214.                     break;
  215.                 case 'legacy':
  216.                 default:
  217.                     $view '@StartPlatzEventBundle/Default/signup-single.html.twig';
  218.                     break;
  219.             }
  220.         }
  221.         // Keep backward compatibility for existing petition SignUps using JSON settings
  222.         elseif (array_key_exists('signupTemplate'$settings)) {
  223.             $view '@StartPlatzEventBundle/Default/signup-single.deepSeekPetition.html.twig';
  224.         }
  225. // Build the metaData array using only the $batch object (landing page fields)
  226.         $metaData = [
  227.             // SEO Tag: Description (using landingPageDescription)
  228.             'description'       => $batch->getLandingPageDescription(),
  229.             // Canonical URL (the domain remains lowercase)
  230.             'canonical'         => 'https://www.startplatz.de/signup/' urlencode($batch->getSlug()),
  231.             // Open Graph Tags for Social Sharing (using landingPage fields)
  232.             'ogTitle'           => $batch->getLandingPageTitle(),
  233.             'ogDescription'     => $batch->getLandingPageDescription(),
  234.             'ogImage'           => $batch->getLandingPageSocialImage(),
  235.             'ogUrl'             => 'https://www.startplatz.de/signup/' urlencode($batch->getSlug()),
  236.             'ogType'            => 'website',
  237.             'ogSiteName'        => 'STARTPLATZ'// Protected trademark must be uppercase
  238.             // Twitter Card Tags (using landingPage fields)
  239.             'twitterCard'       => 'summary'// or "summary_large_image" if preferred
  240.             'twitterTitle'      => $batch->getLandingPageTitle(),
  241.             'twitterDescription'=> $batch->getLandingPageDescription(),
  242.             'twitterImage'      => $batch->getLandingPageSocialImage(),
  243.             'twitterSite'       => '@STARTPLATZ'// Protected trademark must be uppercase
  244.             // Title for the page (taken from landingPageTitle)
  245.             'title'             => $batch->getLandingPageTitle(),
  246.             // Additional Landing Page Fields
  247.             'landingPageCss'            => $batch->getLandingPageCss(),
  248.             'landingPageContent'        => $batch->getLandingPageContent(),
  249.             'landingPageJs'             => $batch->getLandingPageJs(),
  250.             'landingPageFooterTemplate' => $batch->getLandingPageFooterTemplate(),
  251.         ];
  252.         return $this->render($view, [
  253.             'batch'          => $batch,
  254.             'program'        => $program,
  255.             'settings'       => $settings,
  256.             'metaData'       => $metaData,
  257.             'form'           => $form $form->createView() : $form,
  258.             'application'    => $application,
  259.             'action'         => $action,
  260.             'batchIsOpen'    => $batchIsOpen,
  261.             'customersArr'   => $customersArr,
  262.             'member'         => $member,
  263.             'isMultiBatchEvent' => false,  // Signups have no event, so no multi-batch
  264.             'utmParameters'  => $utmParameters,
  265.             'phrases'        => $em->getRepository(Option::class)->getApplicationPhrases($program$batchfalse$lang),
  266.             'editFeedback'   => $editFeedback,
  267.             'templateVars'   => [],
  268.             'preview'             => $request->get('preview'),
  269.             'lang'            => $lang,
  270.             'targetRoute'     => "signup_show_single",
  271.             'targetRouteSlug' => $batch->getSlug(),
  272.             'targetPath'      => $targetPath,
  273.         ]);
  274.     }
  275.     ### section signup ends here ###
  276.     #[Route('/allmeda/event/application/send-confirmation-mail/{applicationId}'name'allmeda_event_send-confirmation-mail')]
  277.     /**
  278.      * @IsGranted("ROLE_ADMIN")
  279.      */
  280.     public function sendConfirmationMailViaPublicFunction($applicationId)
  281.     {
  282.         $em $this->entityManager;
  283.         $application $em->getRepository(Application::class)->find($applicationId);
  284.         $member $em->getRepository(Member::class)->find($application->getMemberId());
  285.         $batch $em->getRepository(Batch::class)->find($application->getBatchId());
  286.         $feedback $this->eventNotificationService->sendConfirmationMail(null$member, [], $application$batch);
  287.         $response "SUCCESS Confirmation Mail has been sent";
  288.         return new Response($response);
  289.     }
  290.     #[Route('/event-confirmation/{slug}/'name'event_confirmation')]
  291.     public function eventConfirmationAction(Request $request$slug)
  292.     {
  293.         $em $this->entityManager;
  294.         $event $this->findEventBySlug($slug);
  295.         if (!$event) {
  296.             $this->addFlash('notice''ERROR Event not found');
  297.             return $this->redirectToRoute('events_list');
  298.         }
  299.         $seriesEvents = [];
  300.         if ($seriesTag $event->getSeriesTag()) {
  301.             /** @var EventRepository $eventRepository */
  302.             $eventRepository $em->getRepository(Event::class);
  303.             $seriesEvents $eventRepository->findBySeriesTag($seriesTag'', ['startDate' => 'DESC']);
  304.         }
  305.         $embed $request->get('embed');
  306.         $batch $em->getRepository(Batch::class)->findOneBy(['programId'=>27'eventId'=>$event->getId(), 'status'=>'active''access' =>'anonymous']);
  307.         if (!$batch) {
  308.             $this->addFlash('notice''ERROR No active batch found for this event');
  309.             return $this->redirectToRoute('events_list');
  310.         }
  311.         $program $batch->getProgram();
  312.         $action   $request->get('action');
  313.         $applicationId $request->get('app');
  314.         $route                   "event_confirmation";
  315.         $routeParameters["slug"] = $event->getSlug();
  316.         $settings = [];
  317.         $member   false;
  318.         $application = new Application();
  319.         // Load application first, then determine language
  320.         if ($applicationId){
  321.             if (!$application $em->getRepository(Application::class)->find($applicationId)){
  322.                 $this->addFlash('notice''ERROR Application with ID '.$applicationId.' not found');
  323.                 return $this->redirectToRoute('events_list');
  324.             }
  325.         }
  326.         // Language detection: Priority is Application > Event > Default DE
  327.         // This ensures user's language choice persists through Stripe redirect
  328.         $lang = ($application && $application->getId() && $application->getLang())
  329.             ? $application->getLang()
  330.             : ($event->getLang() ?? 'DE');
  331.         // Handle action after language is determined
  332.         if ($applicationId && $action){
  333.             $application $this->handleAction($request$member$application$batch$lang$route$routeParameters$settings);
  334.             if (is_string($application)) {
  335.                 // Handle redirect
  336.                 return $this->redirect($application);
  337.             }
  338.         }
  339.         // Generate the absolute base URL based on the route and parameters
  340.         $baseURL $this->generateUrl('event_show_single', ['slug' => $slug], UrlGeneratorInterface::ABSOLUTE_URL);
  341.         // UTM parameters for the promotion URL
  342.         $promotionUtmParameters = [
  343.             'utm_source'   => 'affiliate',
  344.             'utm_medium'   => is_numeric($application->getMemberId()) ? $application->getMemberId() : '',
  345.             'utm_campaign' => 'event:' $event->getId(),
  346.             'utm_term'     => '',
  347.             'utm_content'  => '',
  348.         ];
  349.         $promotionUrl $em->getRepository(Batch::class)->generatePromotionUrl($baseURL$promotionUtmParameters);
  350.         $view '@StartPlatzEventBundle/Default/event-confirmation.html.twig';
  351.         if ($embed){
  352.             $view '@StartPlatzEventBundle/Default/event-confirmation.embed.html.twig';
  353.         }
  354.         if ($event->getTwigFile() == 'winter-2025' ){
  355.             $view '@StartPlatzEventBundle/Default/event-confirmation.winter-2025.html.twig';
  356.         }
  357.         if ($event->getTwigFile() == 'style-ecodynamics' ){
  358.             $view '@StartPlatzEventBundle/Default/event-confirmation.style-ecodynamics.html.twig';
  359.         }
  360.         if ($event->getTwigFile() == 'style-ai-hub' ){
  361.             $view '@StartPlatzEventBundle/Default/event-confirmation.style-ai-hub.html.twig';
  362.         }
  363.         if ($event->getTwigFile() == 'style-startplatz' ){
  364.             $view '@StartPlatzEventBundle/Default/event-confirmation.style-ai-hub.html.twig';
  365.         }
  366.         $resolved = new EventContentResolver($event$batch ?: null, ($lang ?? 'DE') === 'EN');
  367.         return $this->render($view, [
  368.             'event'         => $event,
  369.             'resolved'      => $resolved,
  370.             'seriesEvents'  => $seriesEvents,
  371.             'promotionUrl'   => $promotionUrl,
  372.             'batch'          => $batch,
  373.             'settings'       => $settings,
  374.             'application'    => $application,
  375.             'action'         => $action,
  376.             'member'         => $member,
  377.             'speakers'       => $this->eventViewHelper->getSpeakers($event),
  378.             'phrases'        => $em->getRepository(Option::class)->getApplicationPhrases($program$batchfalse$lang),
  379.             'templateVars'   => [],
  380.             'embed'          => $embed,
  381.             'lang'            => $lang,
  382.             'isMultiBatchEvent' => $event->isMultiBatchEvent(),
  383.         ]);
  384.     }
  385.     #[Route('/event/{slug}/'name'event_show_single')]
  386.     #[Route('/event/{slug}/{date}'name'event_show_single_by_date'requirements: ['date' => '\d{4}-\d{2}-\d{2}'])]
  387.     public function eventSingleAction(Request $request$slug$date null)
  388.     {
  389.         ##event-single
  390.         if ($slug == 'ai-bootcamp-koeln-2024-01-18') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'ai-bootcamp-koeln-2024-01-29')) );
  391.         if ($slug == 'ai-summer-school-koeln-2208') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'ai-bootcamp-koeln-1109')) );
  392.         if ($slug == 'ai-summer-school-duesseldorf-2908') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'ai-bootcamp-duesseldorf-2609')) );
  393.         if ($slug == 'ai-disruptme-marketing-in-unternehmen-koeln-2023-10-19') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'ai-disruptme-marketing-in-unternehmen-koeln-2024-01-31')) );
  394.         if ($slug == 'ai-disruptme-publisher-koeln-2023-10-18') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'ai-disruptme-publisher-duesseldorf-2024-03-20')) );
  395.         if ($slug == 'ai-disruptme-travel-tourismus-duesseldorf-2023-11-09') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'ai-disruptme-travel-tourismus-duesseldorf-2024-02-22')) );
  396.         if ($slug == 'genai-ki-app-no-code-backend-fuer-ki-anwendung-2024-05-28') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'genai-ki-anwendung-bauen-frontend-ux-ui-2024-05-21')) );
  397.         if ($slug == 'ai-barcamp-generative-ki') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'ai-barcamp-logistik')) );
  398.         if ($slug == 'genai-generative-ki-zertifikat-ki-kompetenz-erwerben-2025-05-20') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'ki-kompetenzpflicht-zertifikat-artikel-4-2025-05-20')) );
  399.         if ($slug == 'openclaw-installationstag') return $this->redirect$this->generateUrl('event_show_single', array('slug' => 'openclaw-installationstag-2026-05-08')) );
  400.         // Retrieve the UTM parameters from the URL
  401.         $utmParameters = [
  402.             'utmSource'   => $request->query->get('utm_source') ?? 'homepage',
  403.             'utmMedium'   => $request->query->get('utm_medium'),
  404.             'utmCampaign' => $request->query->get('utm_campaign'),
  405.             'utmTerm'     => $request->query->get('utm_term'),
  406.             'utmContent'  => $request->query->get('utm_content'),
  407.         ];
  408.         $em $this->entityManager;
  409.         /** @var Event $event */
  410.         $event $this->findEventBySlug($slug);
  411.         if (!$event) {
  412.             $this->addFlash('notice''ERROR Veranstaltung nicht gefunden');
  413.             return $this->redirectToRoute('events_list');
  414.         }
  415.         // Handle event visibility: redirect if not published or previewed without user session.
  416.         $response $this->handleEventStatus($event$request);
  417.         if ($response) {
  418.             return $response;
  419.         }
  420.         $seriesEvents = [];
  421.         if ($seriesTag $event->getSeriesTag()) {
  422.             /** @var EventRepository $eventRepository */
  423.             $eventRepository $em->getRepository(Event::class);
  424.             $seriesEvents $eventRepository->findBySeriesTag($seriesTag'', ['startDate' => 'DESC']);
  425.         }
  426.         // Language detection with priority: URL parameter > Event default
  427.         // This ensures ?lang=en parameter works for bilingual events
  428.         $lang strtoupper($request->query->get('lang''')) ?: ($event->getLang() ?? 'DE');
  429.         $embed $request->get('embed');
  430.         $eventbriteId   $event->getEventbriteId();
  431.         $isEventbriteOnly $event->getTicketing() > '';
  432.         $applicationUrl $event->getApplicationUrl();
  433.         // Multi-batch aware logic
  434.         $futureBatches = [];
  435.         $allBatches = [];
  436.         if ($event->isMultiBatchEvent()) {
  437.             $allBatches $em->getRepository(Batch::class)->findAllBatchesByEvent($event);
  438.             if ($date) {
  439.                 // User selected a specific date
  440.                 $batch $em->getRepository(Batch::class)->findBatchByEventAndDate($event$date);
  441.                 if (!$batch) {
  442.                     // Invalid date, redirect to main event page
  443.                     $this->addFlash('notice''Der gewählte Termin ist nicht verfügbar.');
  444.                     return $this->redirectToRoute('event_show_single', ['slug' => $slug]);
  445.                 }
  446.             } else {
  447.                 // No date selected, show next upcoming batch
  448.                 $batch $em->getRepository(Batch::class)->findNextOpenBatchByEvent($event);
  449.                 // Redirect to canonical date URL
  450.                 if ($batch && $batch->getStartDate()) {
  451.                     return $this->redirectToRoute('event_show_single_by_date', [
  452.                         'slug' => $slug,
  453.                         'date' => $batch->getStartDate()->format('Y-m-d'),
  454.                     ], 302);
  455.                 }
  456.                 // Fallback: no open batch found — use any batch for display context
  457.                 if (!$batch && !empty($allBatches)) {
  458.                     $batch $allBatches[0];
  459.                 }
  460.             }
  461.             // Get all future batches for the selector
  462.             $futureBatches $em->getRepository(Batch::class)->findFutureBatchesByEvent($event10);
  463.             // Get past batches for archive display
  464.             $pastBatches $em->getRepository(Batch::class)->findPastBatchesByEvent($event10);
  465.         } else {
  466.             // Single batch event - keep existing logic
  467.             // Try to find active batch first, then fall back to completed batch for display
  468.             $batch $em->getRepository(Batch::class)->findOneBy(['programId'=>27'eventId'=>$event->getId(), 'status'=>'active''access' =>'anonymous']);
  469.             if (!$batch) {
  470.                 // No active batch found, look for completed batch (for past events that need to display)
  471.                 $batch $em->getRepository(Batch::class)->findOneBy(['programId'=>27'eventId'=>$event->getId(), 'status'=>'completed''access' =>'anonymous']);
  472.             }
  473.             if ($batch) {
  474.                 $allBatches = [$batch];
  475.             }
  476.         }
  477.         $batchIsOpen $batch && $batch->isEventRegistrationOpen();
  478.         // Multi-batch: batch-level external URL overrides event-level
  479.         if ($batch && $batch->getExternalRegistrationUrl()) {
  480.             $applicationUrl $batch->getExternalRegistrationUrl();
  481.         }
  482.         $form    null;
  483.         $program null;
  484.         $application null;
  485.         $product     null;
  486.         $editFeedback "";
  487.         $settings     = [];
  488.         $action       "";
  489.         $customersArr = [];
  490.         $member null;
  491.         /** @var User $user */
  492.         if ($user $this->getUser()) {
  493.             $member $em->getRepository(Member::class)->find($user->getMemberId());
  494.         }
  495.         if ($batch && !$isEventbriteOnly) {
  496.             $program $batch->getProgram();
  497.             // Event-Batches gehoeren zu Program 27 — Fallback wenn programId fehlt
  498.             if (!$program && $batch->isEventBatch()) {
  499.                 $program $em->getRepository(Program::class)->find(27);
  500.             }
  501.         }
  502.         if ($batchIsOpen && !$isEventbriteOnly && $program) {
  503.             $settings $this->eventViewHelper->getProgramSettings($program$batch);
  504.             $batch $em->getRepository(Batch::class)->updateNumbers($batch);
  505.             $action   $request->get('action');
  506.             $referral $request->get('referral');
  507.             // do some exceptions
  508.             if ($member && isset($settings['batchType'])) {
  509.                 if ($settings['batchType'] == 'memberDocumentation'){
  510.                     // check if member
  511.                     if ($em->getRepository(Application::class)->findApplicationsByAiFridayBatchSlugAndMemberId($member->getId()))
  512.                     {
  513.                         if (!$em->getRepository(Application::class)->findOneBy(['memberId'=>$member->getId(), 'batchId' => $batch->getId()])){
  514.                             $memberTeam  $em->getRepository(Team::class)->find($member->getTeamId());
  515.                             $adminEmail  $member->getEmail();
  516.                             $application $em->getRepository(Application::class)->createApplicationIfNotExists($program$batch$memberTeam$member$adminEmail);
  517.                             $application->setBatchStatus('applicant');
  518.                             $application->setApplicationStatus('applied');
  519.                             $em->persist($application);
  520.                             $em->flush();
  521.                             $batch $em->getRepository(Batch::class)->updateNumbers($batch);
  522.                         }
  523.                     }
  524.                 }
  525.             }
  526.             $application $this->eventRegistrationService->getOrCreateApplication($request$batch$program$member$lang$settings$utmParameters$event);
  527.             if ($referral && $application->getRealPriceinEuroCent() > 0) {
  528.                 if ($recommendedByMember $em->getRepository(Member::class)->find($referral)) {
  529.                     if ($neededTag $settings['isRecommendedBy'] ?? '') {
  530.                         if (Utility::contains($recommendedByMember->getTags(), $neededTag)) {
  531.                             $application->setRealPriceinEuroCent(0);
  532.                             $application->setDiscountReason("geworben durch {$recommendedByMember->getName()}");
  533.                             $application->setDiscountPercent(100);
  534.                         }
  535.                     }
  536.                 }
  537.             }
  538.             if ($user && in_array($action, ["needsloginvalidation""needsemailverification"])) {
  539.                 $response $this->validateEventApplication($application$member$settings$batch$lang);
  540.                 $application $response['data']['application'];
  541.             }
  542.             // Only check for Monsum product if we're not using Stripe
  543.             $hasStripeConfig = ($batch && $batch->getStripePriceId()) || ($event && $event->getStripePriceId());
  544.             if ($batch->getPriceInEuroCent() > && $batch->getProductId() && !$hasStripeConfig) {
  545.                 if (!$product $em->getRepository(Product::class /*Product Member*/)->find($batch->getProductId())) {
  546.                     $this->addFlash('notice''ERROR configuration not found');
  547.                     return $this->redirect($this->generateUrl('events_list'));
  548.                 }
  549.             }
  550.             $formType $batch->isEventBatch() ? EventApplicationType::class : ApplicationType::class;
  551.             $form $this->createForm($formType$application, [
  552.                 'settings' => $settings,
  553.                 'program' => $program,
  554.                 'batch' => $batch,
  555.                 'csrf_protection' => $embed false true,
  556.             ]);
  557.             $form->handleRequest($request);
  558.             ##############################################################
  559.             ##   form validation starts here                            ##
  560.             ##############################################################
  561.             if ($form->isSubmitted() && $form->isValid()) {
  562.                 $application $em->getRepository(Application::class)->cleanApplicationData($application);
  563.                 $adminEmail $application->getEmail();
  564.                 $email $application->getEmail();
  565.                 // Tarpit: Spam-Treffer → still redirecten, kein Persist/Mail/Stripe.
  566.                 // Bot sieht scheinbaren Erfolg und versucht nicht weiter; B-129.
  567.                 $spamResult $this->spamFilter->checkSubmission([
  568.                     'firstName' => $application->getFirstName() ?? '',
  569.                     'lastName'  => $application->getLastName() ?? '',
  570.                     'email'     => $email ?? '',
  571.                 ], $request);
  572.                 if ($spamResult->isSpam) {
  573.                     $this->logger->info('Application spam dropped (tarpit)', [
  574.                         'batchId' => $batch->getId(),
  575.                         'eventId' => $event->getId(),
  576.                         'reason'  => $spamResult->reason,
  577.                         'score'   => $spamResult->score,
  578.                     ]);
  579.                     return $this->redirectToRoute('event_show_single', ['slug' => $slug]);
  580.                 }
  581.                 if (!Utility::validateEmail($email)){
  582.                     $this->addFlash('notice''ERROR Incorrect email adress. Please check your email adress!');
  583.                 } else {
  584.                     // maxApplicants
  585.                     if (isset($settings['maxApplicants'])) {
  586.                         $applicants $em->getRepository(Application::class)->findBy(['batchId' => $batch->getId(), 'applicationStatus' => 'applied']);
  587.                         $currentApplicantsCount count($applicants);
  588.                         if ($currentApplicantsCount >= $settings['maxApplicants']) {
  589.                             $this->addFlash('notice''ERROR Leider sind alle verfügbaren Plätze bereits vergeben.');
  590.                             return $this->redirectToRoute('event_show_single', ['slug' => $slug]);
  591.                         }
  592.                     }
  593.                     // isErrorDetectionOn
  594.                     if (isset($settings['isErrorDetectionOn']) and $settings['isErrorDetectionOn']) {
  595.                         $this->addFlash('notice''ERROR There was a problem in processing your application. Please contact support@startplatz.de');
  596.                         return $this->redirectToRoute('event_show_single', ['slug' => $slug]);
  597.                     }
  598.                     if ($member) {
  599.                         $memberToBeRegistered $member;
  600.                         $isExistingMember true;
  601.                     } else {
  602.                         $response $this->eventRegistrationService->resolveOrCreateMember($application);
  603.                         $isExistingMember $response['data']['isExistingMember'];
  604.                         $memberToBeRegistered $response['data']['memberToBeRegistered'];
  605.                     }
  606.                     $memberToBeRegistered->setIsExistingMember($isExistingMember);
  607.                     // fill application with memberId and teamId and discount information
  608.                     $application $this->eventRegistrationService->bindMemberToApplication($application$memberToBeRegistered$batch);
  609.                     if (!$batch->isMultipleApplicationsPerMember()) {
  610.                         // check if there is already an application to this event == batch with that batchId and memberId
  611.                         if ($existingApplication $em->getRepository(Application::class)->findOneBy(['batchId' => $batch->getId(), 'memberId' => $memberToBeRegistered->getId()])) {
  612.                             // there are two applications - the latest application will always win
  613.                             $fields Utility::getEntityFieldsArray($application, ['id''assessments''votesRegistered''applicationStatus''editStatus''editMessage''lastChangeUser''history''extraFields''lastModified''createdAt']);
  614.                             $storedData Utility::fillDataByObject($existingApplication$fields);
  615.                             $updatedData Utility::fillDataByObject($application$fields);
  616.                             $differences array_diff_assoc($updatedData$storedData);
  617.                             $application $existingApplication;
  618.                             $application $this->updateApplication($application$differences);
  619.                             $application->setIsExistingApplication(true);
  620.                         }
  621.                     }
  622.                     if (isset($settings['extraFields'])) {
  623.                         $application $this->eventViewHelper->getExtraFieldsData($application$settings$form);
  624.                     }
  625.                     // FEAT-010: Lead-Capture — Save + Track + Redirect, kein Payment
  626.                     if ($batch->getRegistrationMode() === 'lead-capture') {
  627.                         $application->setApplicationStatus('applied');
  628.                         $application $em->getRepository(Application::class)->setApplication($application$memberToBeRegistered);
  629.                         $this->metaConversionsApiService->sendCompleteRegistration($application$event$request$request->request->get('_meta_event_id'));
  630.                         if ($batch->getRegistrationRedirectUrl()) {
  631.                             return $this->redirect($batch->getRegistrationRedirectUrl());
  632.                         }
  633.                         return $this->redirectToRoute('event_show_single', ['slug' => $slug]);
  634.                     }
  635.                     $isPaidEvent $this->eventPaymentService->isPaidEvent($application$batch$event);
  636.                     // Debug logging for ki-campus payment exception
  637.                     $this->logger->info('Payment exception check', [
  638.                         'member_exists' => $member true false,
  639.                         'member_id' => $member $member->getId() : null,
  640.                         'member_email' => $member $member->getEmail() : null,
  641.                         'member_tags' => $member $member->getTags() : null,
  642.                         'memberToBeRegistered_id' => $memberToBeRegistered->getId(),
  643.                         'memberToBeRegistered_email' => $memberToBeRegistered->getEmail(),
  644.                         'memberToBeRegistered_tags' => $memberToBeRegistered->getTags(),
  645.                         'batch_isFreeForKiCampus' => $batch $batch->getIsFreeForKiCampus() : null,
  646.                         'event_isFreeForKiCampus' => $event $event->getIsFreeForKiCampus() : null,
  647.                     ]);
  648.                     // Always use memberToBeRegistered for payment exception check
  649.                     $hasToPay $this->eventPaymentService->isPaymentRequired($application$memberToBeRegistered$event$settings$batch);
  650.                     if ($isPaidEvent && $hasToPay) {
  651.                         $application $this->eventPaymentService->updateApplicationWithStepOneInfo($application$memberToBeRegistered$event);
  652.                         $isAdmin $user && $user->getIsAdmin();
  653.                         $discountCode $request->get('discount');
  654.                         $paymentLink $this->eventPaymentService->createStripeCheckoutUrl($application$memberToBeRegistered$event$batch$embed$isAdmin$discountCode);
  655.                         if (is_string($paymentLink)) {
  656.                             // B-128: Meta CAPI Lead (paid event, before Stripe redirect)
  657.                             $this->metaConversionsApiService->sendLead($application$event$request$request->request->get('_meta_event_id'));
  658.                             // call stripe and let applicant pay for the ticket
  659.                             if ($embed){
  660.                                 return $this->render('@StartPlatzEventBundle/Default/iframe_redirect.html.twig', [
  661.                                     'targetUrl' => $paymentLink
  662.                                 ]);
  663.                             } else {
  664.                                 return $this->redirect($paymentLink);
  665.                             }
  666.                         } elseif ($paymentLink === null) {
  667.                             // No Stripe config — check Monsum
  668.                             if ($product) {
  669.                                 $customersArr $em->getRepository(Customer::class)->findValidMonsumCustomersByMemberAndAccount($memberToBeRegistered$product->getAccount(), false);
  670.                             }
  671.                         }
  672.                         // paymentLink === false: Stripe was configured but failed — fall through to render page
  673.                         // Application in Datenbank schreiben, sonst kann im nächsten Schritt keine appId übergeben werden
  674.                         $application $em->getRepository(Application::class)->setApplication($application);
  675.                     } else {
  676.                         $route 'event_show_single';
  677.                         $routeParameters $request->get('_route_params');
  678.                         if (!array_key_exists('slug'$routeParameters)) {
  679.                             $routeParameters['slug'] = $event->getSlug();
  680.                         }
  681.                         $response $this->eventRegistrationService->processRegistrationWithoutPayment($isExistingMember$user$memberToBeRegistered$application$settings$lang$adminEmail$batch$route$routeParameters);
  682.                         if ($response['status'] == 'error') {
  683.                             $targetPath $response['data']['targetPath'];
  684.                             return $this->redirect($targetPath);
  685.                         }
  686.                         $application $response['data']['application'];
  687.                         $member $response['data']['member'];
  688.                         // B-128: Meta CAPI Lead (free event, after registration)
  689.                         $this->metaConversionsApiService->sendLead($application$event$request$request->request->get('_meta_event_id'));
  690.                         if ($this->shouldSetAiHubMembership($settings$application->getApplicationStatus())) {
  691.                             $aiHubStatus $settings["setAiHubMembership"];
  692.                             $service $this->configureServiceForAiHub($aiHubStatus$event->getStartDate());
  693.                             $reason "participated in event with id={$event->getId()} and title={$event->getTitle()}";
  694.                             $em->getRepository(Member::class)->setAiHubMembership($service$member$reason);
  695.                         }
  696.                     }
  697.                 }
  698.             }
  699.         }
  700.         $promotionUrl null;
  701.         if ($user && $batch) {
  702.             // Generate the absolute base URL based on the route and parameters
  703.             $baseURL $this->generateUrl('event_show_single', ['slug' => $slug], UrlGeneratorInterface::ABSOLUTE_URL);
  704.             // UTM parameters for the promotion URL
  705.             $promotionUtmParameters = [
  706.                 'utm_source' => 'affiliate',
  707.                 'utm_medium' => $member->getId(),
  708.                 'utm_campaign' => 'event:' $event->getId(),
  709.                 'utm_term'     => '',
  710.                 'utm_content'  => '',
  711.             ];
  712.             $promotionUrl $em->getRepository(Batch::class)->generatePromotionUrl($baseURL$promotionUtmParameters);
  713.         }
  714.         // Überprüfen Sie den Wert des Teaserfeldes, um showTeaser zu setzen
  715.         $showTeaser = !empty($event->getTeaser());
  716.         $paymentLink false;
  717.         $view '@StartPlatzEventBundle/Default/event-single.html.twig';
  718.         if (($event->isSinglePage() && !$event->isMultiBatchEvent()) or $embed){
  719.             $view '@StartPlatzEventBundle/Default/event-single.lp.html.twig';
  720.         }
  721.         if ($event->getTwigFile() == 'winter-2025' ){
  722.             $view '@StartPlatzEventBundle/Default/event-single.winter-2025.html.twig';
  723.         }
  724.         if ($event->getTwigFile() == 'style-ecodynamics' ){
  725.             $view '@StartPlatzEventBundle/Default/event-single.style-ecodynamics.html.twig';
  726.         }
  727.         if ($event->getTwigFile() == 'style-ai-hub' ){
  728.             $view '@StartPlatzEventBundle/Default/event-single.style-ai-hub.html.twig';
  729.         }
  730.         if ($event->getTwigFile() == 'style-startplatz' ){
  731.             $view '@StartPlatzEventBundle/Default/event-single.style-startplatz.html.twig';
  732.         }
  733.         if ($event->getTwigFile() == 'tailwind' ){
  734.             $view '@StartPlatzEventBundle/Default/event-single.tailwind.html.twig';
  735.         }
  736.         $resolved = new EventContentResolver($event$batch$lang === 'EN');
  737.         return $this->render($view, [
  738.             'event'         => $event,
  739.             'resolved'      => $resolved,
  740.             'seriesEvents'  => $seriesEvents,
  741.             'showTeaser'    => $showTeaser,
  742.             'utmParameters'  => $utmParameters,
  743.             'promotionUrl'   => $promotionUrl,
  744.             'eventbriteId'   => $eventbriteId,
  745.             'isEventbriteOnly' => $isEventbriteOnly,
  746.             'applicationUrl' => $applicationUrl,
  747.             'batch'          => $batch,
  748.             'settings'       => $settings,
  749.             'form'           => $form $form->createView() : $form,
  750.             'application'    => $application,
  751.             'action'         => $action,
  752.             'batchIsOpen'    => $batchIsOpen,
  753.             'customersArr'   => $customersArr,
  754.             'member'         => $member,
  755.             'paymentLink'    => $paymentLink,
  756.             // Multi-batch event support
  757.             'futureBatches'  => $futureBatches,
  758.             'pastBatches'    => $pastBatches ?? [],
  759.             'allBatches'     => $allBatches,
  760.             'selectedDate'   => $date,
  761.             'isMultiBatchEvent' => $event->isMultiBatchEvent(),
  762.             'speakers'       => $this->eventViewHelper->getSpeakers($event),
  763.             'phrases'        => $em->getRepository(Option::class)->getApplicationPhrases($program$batchfalse$lang),
  764.             'editFeedback'   => $editFeedback,
  765.             'templateVars'   => [],
  766.             'preview'             => $request->get('preview'),
  767.             'lang'           => $lang,
  768.             'isEnglish'      => $lang === 'EN',
  769.             'embed'          => $embed,
  770.             'targetRoute'     => "event_show_single",
  771.             'targetRouteSlug' => $event->getSlug(),
  772.         ]);
  773.     }
  774.     // ── Private Helpers ──────────────────────────────────────────────
  775.     private function findEventBySlug($slug)
  776.     {
  777.         /** @var EventRepository $eventRepository */
  778.         $eventRepository $this->entityManager->getRepository(Event::class);
  779.         $event $eventRepository->findOneBy(['slug' => $slug]);
  780.         if (!$event) {
  781.             $querySlug '/' $slug;
  782.             $event $eventRepository->findOneBy(['slug' => $querySlug]);
  783.             if (!$event) {
  784.                 return false;
  785.             } else {
  786.                 $em $this->entityManager;
  787.                 $event->setSlug($slug);
  788.                 $em->persist($event);
  789.                 $em->flush();
  790.             }
  791.         }
  792.         return $event;
  793.     }
  794.     private function handleEventStatus(Event $eventRequest $request)
  795.     {
  796.         // SEO-optimized handling: Archive events are displayed normally (no redirect)
  797.         // Template will show archive banner and hide registration forms
  798.         // This preserves 100% SEO juice and backlinks
  799.         if ($event->getStatus() != 'publish' and $event->getStatus() != 'archive' and !$request->get('preview')) {
  800.             // Draft and trash events require authentication
  801.             if (!$this->getUser()) {
  802.                 $this->addFlash('notice''ERROR Veranstaltung ist nicht verfügbar');
  803.                 return $this->redirectToRoute('events_list');
  804.             }
  805.         }
  806.         return null;
  807.     }
  808.     /**
  809.      * handleAction - Handles various actions for the application
  810.      *
  811.      * This function is called in the following contexts:
  812.      * 1. During Signup (signupSingleAction)
  813.      *    - With route "signup_show_single"
  814.      *    - Parameters include batch slug, language, etc.
  815.      *
  816.      * 2. During Event Handling (eventSingleAction / eventConfirmationAction)
  817.      *    - With route "event_show_single" or "event_confirmation"
  818.      *    - Parameters include event slug, language, etc.
  819.      */
  820.     private function handleAction(Request $request$memberApplication $applicationBatch $batch$lang$route$routeParameters$settings)
  821.     {
  822.         $action $request->get("action");
  823.         $em $this->entityManager;
  824.         switch ($action) {
  825.             case "checkSuccess":
  826.                 $provider $request->get('provider');
  827.                 $isVerifiedStripePayment false;
  828.                 if ($provider === 'stripe') {
  829.                     $payment $em->getRepository(EventStripePayment::class)
  830.                         ->findLatestSuccessfulByApplicationIds([$application->getId()])[$application->getId()] ?? null;
  831.                     if ($payment && in_array($payment->getPaymentStatus(), ['paid''refunded''partially_refunded'], true)) {
  832.                         $isVerifiedStripePayment true;
  833.                     }
  834.                     // Polling fallback: if webhook hasn't arrived yet, ask Stripe directly
  835.                     if (!$isVerifiedStripePayment) {
  836.                         $isVerifiedStripePayment $this->eventPaymentService->pollStripePaymentStatus($application->getId());
  837.                     }
  838.                 }
  839.                 // Guard: only process once — skip if already applied (prevents duplicate mails on reload)
  840.                 $alreadyProcessed $application->getApplicationStatus() === 'applied' && $application->getHasPaid();
  841.                 if (!$alreadyProcessed && ($provider !== 'stripe' || $isVerifiedStripePayment)) {
  842.                     if ($provider === 'stripe') {
  843.                         $application->setEditStatus("success:stripePaid");
  844.                     }
  845.                     $application->setApplicationStatus('applied');
  846.                     $application->setHasPaid(true);
  847.                     $application $em->getRepository(Application::class)->setApplication($application);
  848.                 }
  849.                 switch ($lang) {
  850.                     case "DE":
  851.                         $message $provider === 'stripe' && !$isVerifiedStripePayment
  852.                             "Deine Zahlung wird gerade geprüft.<br>Bitte aktualisiere die Seite in ein paar Sekunden erneut."
  853.                             "Danke für Deine Anmeldung.<br>Wir freuen uns auf Dich!";
  854.                         break;
  855.                     case "EN":
  856.                         $message $provider === 'stripe' && !$isVerifiedStripePayment
  857.                             "Your payment is being verified.<br>Please refresh this page again in a few seconds."
  858.                             "Thanks, Your application is now approved.<br>We are looking forward to having you!";
  859.                         break;
  860.                 }
  861.                 if ($productNumber $request->get('productnumber') or $provider == 'stripe'){
  862.                     ## productNumber is only given in case of sign up
  863.                     if (!($provider === 'stripe' && !$isVerifiedStripePayment)) {
  864.                         switch ($lang) {
  865.                             case "DE":
  866.                                 $message "Danke für Deine Buchung.<br>Wir freuen uns auf Dich!";
  867.                                 break;
  868.                             case "EN":
  869.                                 $message "Thanks, Your booking was successful.<br>We are looking forward to having you!";
  870.                                 break;
  871.                         }
  872.                     }
  873.                     $account      $request->get('account');
  874.                     $customerHash $request->get('customerId');
  875.                     $subscriptionHash $request->get('subscriptionId');
  876.                     if (!$member) {
  877.                         $member $em->getRepository(Member::class)->find($application->getMemberId());
  878.                     }
  879.                     if (array_key_exists("setAiHubMembership"$settings)) {
  880.                         // Hole den Status aus den Einstellungen
  881.                         $aiMembershipStatus $settings["setAiHubMembership"];
  882.                         // Setze das Start- und Enddatum basierend auf dem aktuellen Datum und dem Status
  883.                         $aiMembershipStartDate = new DateTime();  // Setze auf aktuelles Datum und Uhrzeit
  884.                         $aiMembershipEndDate = clone $aiMembershipStartDate;
  885.                         if ($aiMembershipStatus === 'trial') {
  886.                             $aiMembershipEndDate->modify('+1 month');
  887.                         } elseif ($aiMembershipStatus === 'active') {
  888.                             $aiMembershipEndDate->modify('+3 months');
  889.                         }
  890.                         // Setze den Grund für die Mitgliedschaft
  891.                         $reason "Buy of {$productNumber}";
  892.                         // Erstelle oder aktualisiere das Service-Objekt mit den neuen Daten
  893.                         // (Du müsstest diese Logik entsprechend deiner Anwendung implementieren)
  894.                         $service = new Service();  // oder hole ein existierendes Service-Objekt
  895.                         $service->setStatus($aiMembershipStatus);
  896.                         $service->setStartDate($aiMembershipStartDate);
  897.                         $service->setEndDate($aiMembershipEndDate);
  898.                         // Rufe die setAiHubMembership Funktion auf
  899.                         $successContent[] = $em->getRepository(Member::class)->setAiHubMembership($service$member$reason);
  900.                     }
  901.                     $loginLink null;
  902.                     $targetPath $this->generateUrl('x_membership_first-steps', ['productNumber' => $productNumber'account' => $account]);
  903.                     $this->addFlash('notice'$message);
  904.                     if ($provider === 'stripe' && !$isVerifiedStripePayment) {
  905.                         return $application;
  906.                     }
  907.                     // Send confirmation mail + monitoring only on first successful verification
  908.                     if (!$alreadyProcessed) {
  909.                         if (!$user $this->getUser()) {
  910.                             $loginLink $this->loginService->getLoginLinkByTargetPath($member->getEmail(), $targetPath);
  911.                             $user $em->getRepository(User::class)->findOneBy(['memberId'=>$member->getId()]);
  912.                         }
  913.                         $feedback $this->eventNotificationService->sendConfirmationMail($usernull$settings$application$batch);
  914.                         if ($provider == 'stripe'){
  915.                             $mailContent = [
  916.                                 'applicationId' => $application->getId(),
  917.                                 'memberId' => $application->getMemberId(),
  918.                                 'editStatus' => $application->getEditStatus(),
  919.                                 'applicationStatus' => $application->getApplicationStatus(),
  920.                                 'person' => $application->getPerson(),
  921.                                 'email' => $application->getEmail(),
  922.                                 'utmSource' => $application->getUtmSource(),
  923.                                 'batchSlug' => $application->getBatchSlug(),
  924.                             ];
  925.                             $response $this->mailService->send("stripe step two {$application->getBatchId()} {$application->getEmail()}"'support@startplatz.de''Monitoring Stripe'$this->alertRecipientService->get('alert.stripe_monitoring'), 'Lorenz'print_r($mailContent,true), false);
  926.                         }
  927.                     }
  928.                     if ($provider == 'stripe'){
  929.                         return $application;
  930.                     } else {
  931.                         return isset($loginLink) ? $loginLink $targetPath;
  932.                     }
  933.                 } else { // if productNumber is not defined
  934.                     if ($provider === 'stripe' && !$isVerifiedStripePayment) {
  935.                         $this->addFlash('notice'$message);
  936.                         return $application;
  937.                     }
  938.                     if (!$user $this->getUser()) {
  939.                         $user $em->getRepository(User::class)->findOneBy(['memberId'=>$member->getId()]);
  940.                     }
  941.                     $feedback $this->eventNotificationService->sendConfirmationMail($usernull$settings$application$batch);
  942.                 }
  943.                 break;
  944.             case "sendMagicLinkForPayment":
  945.                 // create LoginLink and send email
  946.                 $customerId  $request->get('customerId');
  947.                 $feedbackUrl $this->eventPaymentService->sendMagicLinkForPayment($application$batch$customerId$route$routeParameters);
  948.                 return $feedbackUrl;
  949.             case "createConsumerMonsumAccount":
  950.             case "useExistingCustomerAccount":
  951.                 $customerId  $request->get('customerId');
  952.                 $checkoutUrl $this->eventPaymentService->prepareMonsumCheckout($application$batch$action$route$routeParameters$customerId);
  953.                 return $checkoutUrl;
  954.         }
  955.         // Return the modified $application object to the main controller
  956.         return $application;
  957.     }
  958.     private function validateEventApplication(Application $applicationMember $member$settings$batchstring $lang): array
  959.     {
  960.         $em $this->entityManager;
  961.         $message "";
  962.         $status "";
  963.         // Check if a confirmation email needs to be sent
  964.         $shouldSendConfirmationMail false;
  965.         $feedback null;
  966.         switch ($application->getEditStatus()) {
  967.             case "error:validateLogin":
  968.                 $application->setEditStatus('success:validatedLogin');
  969.                 $application->setEditMessage("Application has been validated by {$member->getEmail()}");
  970.                 $application $em->getRepository(Application::class)->setHistory($application$application->getEditMessage());
  971.                 $application $em->getRepository(Application::class)->setApplication($application$member);
  972.                 $status "success";
  973.                 $shouldSendConfirmationMail true;
  974.                 break;
  975.             case "error:validateEmail":
  976.                 $application->setEditStatus('success:validatedEmail');
  977.                 $application->setEditMessage("Email of this Application has been validated by {$member->getEmail()}");
  978.                 $application $em->getRepository(Application::class)->setHistory($application$application->getEditMessage());
  979.                 if ($application->getApplicationStatus() == 'started') {
  980.                     $application->setApplicationStatus('applied');
  981.                 }
  982.                 $application $em->getRepository(Application::class)->setApplication($application$member);
  983.                 $status "success";
  984.                 $shouldSendConfirmationMail true;
  985.                 break;
  986.         }
  987.         if ($shouldSendConfirmationMail) {
  988.             // Logic to send confirmation mail
  989.             $feedback $this->eventNotificationService->sendConfirmationMail(null$member$settings$application$batch);
  990.         }
  991.         if ($status == "success") {
  992.             $application $em->getRepository(Application::class)->setApplicationApplied($application$member);
  993.             switch ($lang) {
  994.                 case "DE":
  995.                     $message "SUCCESS Danke, Deine Anmeldung ist jetzt bestätigt. Wir freuen uns auf Dich!";
  996.                     if ($feedback){
  997.                         $message .= "<br>Wir haben Dir eine Bestätigungsmail geschickt. Bitte prüfe Deinen Posteingang.";
  998.                     }
  999.                     break;
  1000.                 case "EN":
  1001.                     $message "SUCCESS Thanks, Your application is now approved. We are looking forward to having you!";
  1002.                     if ($feedback){
  1003.                         $message .= "<br>We have sent you a confirmation mail. Please check your inbox!";
  1004.                     }
  1005.                     break;
  1006.             }
  1007.             $this->addFlash('notice'$message);
  1008.         }
  1009.         return [
  1010.             'data' => [
  1011.                 'application' => $application,
  1012.             ],
  1013.             'message' => $message,
  1014.             'status' => $status,
  1015.         ];
  1016.     }
  1017.     private function shouldSetAiHubMembership($settings$applicationStatus)
  1018.     {
  1019.         return array_key_exists("setAiHubMembership"$settings) && $applicationStatus === 'applied';
  1020.     }
  1021.     private function configureServiceForAiHub($statusDateTime $startDate)
  1022.     {
  1023.         $service = new Service();  // oder hole ein existierendes Service-Objekt
  1024.         $endDate = clone $startDate;
  1025.         if ($status === 'trial') {
  1026.             $endDate->modify('+1 month');
  1027.         } elseif ($status === 'active') {
  1028.             $endDate->modify('+3 months');
  1029.         }
  1030.         $service->setStatus($status);
  1031.         $service->setStartDate($startDate);
  1032.         $service->setEndDate($endDate);
  1033.         return $service;
  1034.     }
  1035.     private function updateApplication(Application $application$differences)
  1036.     {
  1037.         foreach ($differences as $diff => $value) {
  1038.             $method 'set' ucfirst((string) $diff);
  1039.             if (method_exists($application$method)) {
  1040.                 $application->$method($value);
  1041.             }
  1042.         }
  1043.         return $application;
  1044.     }
  1045.     #[Route('/events/{slug}/self-checkin'name'event_self_checkin'methods: ['GET''POST'])]
  1046.     public function selfCheckin(string $slugRequest $request): Response
  1047.     {
  1048.         $token $request->query->get('token''');
  1049.         if ($request->isMethod('GET')) {
  1050.             // Validate token for GET request
  1051.             if (!$token) {
  1052.                 return $this->render('@StartPlatzEventBundle/Event/selfCheckin.html.twig', [
  1053.                     'slug' => $slug,
  1054.                     'error' => 'Ungültige Check-in Session. QR-Code scannen.',
  1055.                 ]);
  1056.             }
  1057.             return $this->render('@StartPlatzEventBundle/Event/selfCheckin.html.twig', [
  1058.                 'slug' => $slug,
  1059.                 'token' => $token,
  1060.             ]);
  1061.         }
  1062.         // POST
  1063.         if (!$token) {
  1064.             return $this->render('@StartPlatzEventBundle/Event/selfCheckin.html.twig', [
  1065.                 'slug' => $slug,
  1066.                 'error' => 'Check-in Session abgelaufen. QR-Code erneut scannen.',
  1067.             ]);
  1068.         }
  1069.         $code $request->request->get('code''');
  1070.         $email $request->request->get('email''');
  1071.         try {
  1072.             $checkinData $this->selfCheckinService->validateAndCheckin($slug$email$code$token);
  1073.             return $this->render('@StartPlatzEventBundle/Checkin/success.html.twig', [
  1074.                 'event' => $checkinData['event'],
  1075.                 'message' => 'Erfolgreich eingecheckt!',
  1076.             ]);
  1077.         } catch (InvalidCheckinException $e) {
  1078.             return $this->render('@StartPlatzEventBundle/Event/selfCheckin.html.twig', [
  1079.                 'slug' => $slug,
  1080.                 'token' => $token,
  1081.                 'error' => $e->getMessage(),
  1082.             ]);
  1083.         }
  1084.     }
  1085. }