src/Controller/MainController.php line 192

  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Expense;
  4. use App\Entity\FinancialPart;
  5. use App\Entity\FixedPages;
  6. use App\Entity\Letter;
  7. use App\Entity\News;
  8. use App\Entity\Page;
  9. use App\Entity\PictureLead;
  10. use App\Entity\Santa;
  11. use App\Entity\Services;
  12. use App\Entity\User;
  13. use App\Entity\UserTemplate;
  14. use App\Enum\IncomeEnum;
  15. use App\Enum\PdfConverter;
  16. use App\Enum\SantaEnum;
  17. use App\Repository\MetaRepository;
  18. use App\Repository\PageRepository;
  19. use App\Services\SpacesUploader;
  20. use GuzzleHttp\ClientInterface;
  21. use Symfony\Bridge\Twig\Mime\TemplatedEmail;
  22. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  23. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  24. use Symfony\Component\HttpFoundation\JsonResponse;
  25. use Symfony\Component\HttpFoundation\Request;
  26. use Symfony\Component\HttpFoundation\RequestStack;
  27. use Symfony\Component\HttpFoundation\Response;
  28. use Symfony\Component\Mime\Address;
  29. use Symfony\Component\Mime\Email;
  30. use Symfony\Component\Routing\Annotation\Route;
  31. use Symfony\Component\Mailer\MailerInterface;
  32. use App\Entity\Gallery;
  33. use Doctrine\ORM\EntityManagerInterface;
  34. use Liip\ImagineBundle\Imagine\Cache\CacheManager;
  35. use Symfony\Component\Validator\Constraints\Uuid;
  36. use Symfony\Contracts\HttpClient\HttpClientInterface;
  37. use Symfony\Contracts\Translation\TranslatorInterface;
  38. use Gedmo\Translatable\TranslatableListener;
  39. use PhpOffice\PhpSpreadsheet\IOFactory;
  40. class MainController extends AbstractController
  41. {
  42.     private EntityManagerInterface $entityManager;
  43.     private TranslatableListener $translatableListener;
  44.     public function __construct(EntityManagerInterface $entityManager,
  45.                                 TranslatableListener $translatableListener
  46.     )
  47.     {
  48.         $this->translatableListener $translatableListener;
  49.         $this->entityManager $entityManager;
  50.     }
  51.     #[Route('/{_locale}/ipad'name'page_ipad'requirements: ['_locale' => 'en|ru|hy'], defaults: ['_locale' => 'en'])]
  52.     public function iphone(MetaRepository $metaRepositoryPageRepository $pageRepository)
  53.     {
  54.         $page $pageRepository->find(11);
  55.         $editable false;
  56.         $pantum false;
  57.         return $this->render('@web/page/template.html.twig', [
  58.             'page' => $page,
  59.             'editable' => $editable,
  60.             'ipad' => true,
  61.         ]);
  62.     }
  63.     #[Route('/{_locale}/links'name'page_links'requirements: ['_locale' => 'en|ru|hy'], defaults: ['_locale' => 'en'])]
  64.     public function links()
  65.     {
  66.         return $this->render('@web/page/links.html.twig', [
  67.         ]);
  68.     }
  69.     #[Route('/{_locale}/pantum'name'page_pantum'requirements: ['_locale' => 'en|ru|hy'], defaults: ['_locale' => 'en'])]
  70.     public function pantum(MetaRepository $metaRepositoryPageRepository $pageRepository)
  71.     {
  72.         $page $pageRepository->find(11);
  73.         $editable false;
  74.         $pantum true;
  75.         return $this->render('@web/page/template.html.twig', [
  76.             'page' => $page,
  77.             'editable' => $editable,
  78.             'pantum' => $pantum
  79.         ]);
  80.     }
  81.     #[Route('/{_locale}/ipad/may'name'page_mayis9'requirements: ['_locale' => 'en|ru|hy'], defaults: ['_locale' => 'en'])]
  82.     public function page_mayis9(MetaRepository $metaRepositoryPageRepository $pageRepository)
  83.     {
  84.         $page $pageRepository->find(4);
  85.         $editable false;
  86.         return $this->render('@web/page/template.html.twig', [
  87.             'page' => $page,
  88.             'editable' => $editable
  89.         ]);
  90.     }
  91.     #[Route('/iphone'name'page_iphone'requirements: ['_locale' => 'en|ru|hy'], defaults: ['_locale' => 'en'])]
  92.     public function ipad(MetaRepository $metaRepository)
  93.     {
  94.         return $this->render('@web/page/iphone.html.twig');
  95.     }
  96.     #[Route('/gallery'name'page_gallery')]
  97.     public function index(MetaRepository $metaRepository)
  98.     {
  99.         $gallery $this->entityManager->getRepository(Gallery::class)->findBy(['isShow' => true], ['id' => 'DESC'], 20);
  100.         return $this->render('@web/page/gallery.html.twig', ['gallery' => $gallery]);
  101.     }
  102.     #[Route('/{_locale}'name'page_index'requirements: ['_locale' => 'en|ru|hy|es|it|fr|el|ua|ka|de'], defaults: ['_locale' => 'hy'])]
  103.     public function empty(MetaRepository $metaRepository)
  104.     {
  105.         $banner $this->entityManager->getRepository(FixedPages::class)->find(1);
  106.         $services $this->entityManager->getRepository(Services::class)->findAll();
  107.         $news $this->entityManager->getRepository(News::class)->findAll();
  108.         return $this->render('@web/page/index.html.twig', [
  109.             'banner' => $banner,
  110.             'services' => $services,
  111.             'news' => $news
  112.         ]);
  113.     }
  114.     #[Route('{_locale}/services'name'page_services')]
  115.     public function services(MetaRepository $metaRepository)
  116.     {
  117.         $services $this->entityManager->getRepository(Services::class)->findAll();
  118.         return $this->render('@web/page/services.html.twig', [
  119.             'services' => $services
  120.         ]);
  121.     }
  122.     #[Route('{_locale}/service/view/{id}'name'page_service')]
  123.     public function service(Services $services)
  124.     {
  125.         return $this->render('@web/page/service.html.twig', [
  126.             'service' => $services
  127.         ]);
  128.     }
  129.     #[Route('{_locale}/partner/managment'name'page_partner_nviraberir')]
  130.     public function partnerNviraberir()
  131.     {
  132.         return $this->render('@web/page/partner-nviraberir.html.twig');
  133.     }
  134.     #[Route('{_locale}/wine'name'page_wine')]
  135.     public function wine()
  136.     {
  137.         return $this->render('@web/page/wine.html.twig', [
  138.             'editable' => false
  139.         ]);
  140.     }
  141.     #[Route('{_locale}/privacy-policy'name'page_policy')]
  142.     public function policy()
  143.     {
  144.         $page $this->entityManager->getRepository(FixedPages::class)->find(2);
  145.         return $this->render('@web/page/fixed.html.twig', [
  146.             'page' => $page
  147.         ]);
  148.     }
  149.     #[Route('{_locale}/terms-of-use'name'page_terms')]
  150.     public function terms()
  151.     {
  152.         $page $this->entityManager->getRepository(FixedPages::class)->find(3);
  153.         return $this->render('@web/page/fixed.html.twig', [
  154.             'page' => $page
  155.         ]);
  156.     }
  157.     #[Route('{_locale}/about-us'name'page_about')]
  158.     public function aboutUs(MetaRepository $metaRepository)
  159.     {
  160.         $page $this->entityManager->getRepository(FixedPages::class)->find(4);
  161.         return $this->render('@web/page/about.html.twig', [
  162.             'page' => $page
  163.         ]);
  164.     }
  165.     #[Route('{_locale}/support'name'page_contact_us')]
  166.     public function contactUs(MetaRepository $metaRepository)
  167.     {
  168.         return $this->render('@web/page/contact-us.html.twig');
  169.     }
  170.     #[Route('/pyunik/view'name'page_pyunik_view'requirements: ['_locale' => 'en|ru|hy'], defaults: ['_locale' => 'ru'])]
  171.     public function pagePyunik(PageRepository $pageRepository)
  172.     {
  173.         $editable false;
  174.         return $this->render('@web/page/template-pyunik.html.twig', [
  175.             'editable' => $editable
  176.         ]);
  177.     }
  178.     #[Route('{_locale}/view-image'name'page_view_image'requirements: ['_locale' => 'en|ru|hy'], defaults: ['_locale' => 'en'])]
  179.     public function page_view_image(Request $request)
  180.     {
  181.         $template null;
  182.         $image null;
  183.         $code $request->get('code');
  184.         if ($code) {
  185.             $image $this->entityManager
  186.                 ->getRepository(Gallery::class)
  187.                 ->findOneBy(['documentNumber' => $code]);
  188.             if ($image) {
  189.                 $template $this->entityManager
  190.                     ->getRepository(Page::class)
  191.                     ->find($image->getTemplateId());
  192.             } else {
  193.                 return $this->redirectToRoute('page_index');
  194.             }
  195.         }
  196.         // Redirect to Armenian locale if template ID is 62 and current locale is not hy
  197.         if ($template->getId() == 62 && $request->getLocale() !== $image->getLocale()) {
  198.             return $this->redirectToRoute('page_view_image', [
  199.                 '_locale' => $image->getLocale(),
  200.                 'code' => $code
  201.             ]);
  202.         }
  203.         // Render templates
  204.         [$templateName$params] = $this->resolveViewImageTemplateData($template$image);
  205.         return $this->render($templateName$params);
  206.     }
  207.     private function resolveViewImageTemplateData(Page $templateGallery $image): array
  208.     {
  209.         if ($template->getId() == 62) {
  210.             $letter $this->entityManager->getRepository(Letter::class)->find($image->getLetterId());
  211.             return ['@web/page/template-christmas.html.twig', [
  212.                 'editable' => false,
  213.                 'viewImage' => true,
  214.                 'page' => $template,
  215.                 'letter' => $letter,
  216.                 'image' => $image
  217.             ]];
  218.         } elseif ($template->getId() == 56) {
  219.             return ['@web/page/template-dalma.html.twig', [
  220.                 'editable' => false,
  221.                 'dalma' => true,
  222.                 'page' => $template,
  223.                 'image' => $image
  224.             ]];
  225.         } elseif ($template->getId() == 60) {
  226.             return ['@web/page/template-sherep.html.twig', [
  227.                 'editable' => false,
  228.                 'dalma' => true,
  229.                 'page' => $template,
  230.                 'image' => $image
  231.             ]];
  232.         } elseif (str_contains($template->getSlug(), 'violations')) {
  233.             return ['@web/page/view-violations.html.twig', [
  234.                 'editable' => false,
  235.                 'dalma' => true,
  236.                 'page' => $template,
  237.                 'image' => $image
  238.             ]];
  239.         }elseif (str_contains($template->getSlug(), 'engagement')) {
  240.             return ['@web/page/view-engagement.html.twig', [
  241.                 'editable' => false,
  242.                 'dalma' => true,
  243.                 'page' => $template,
  244.                 'image' => $image
  245.             ]];
  246.         } else {
  247.             return ['@web/page/view-image.html.twig', [
  248.                 'editable' => false,
  249.                 'page' => $template,
  250.                 'image' => $image
  251.             ]];
  252.         }
  253.     }
  254.     #[Route('{_locale}/request-picture-email'name'page_request_picture_email'methods: ['POST'])]
  255.     public function requestPictureEmail(Request $requestMailerInterface $mailerParameterBagInterface $parameterBag): JsonResponse
  256.     {
  257.         $code trim((string)$request->request->get('code'));
  258.         $email trim((string)$request->request->get('email'));
  259.         if (!$code || !$email || !filter_var($emailFILTER_VALIDATE_EMAIL)) {
  260.             return new JsonResponse(['success' => false'error' => 'invalid'], 400);
  261.         }
  262.         $image $this->entityManager->getRepository(Gallery::class)->findOneBy(['documentNumber' => $code]);
  263.         if (!$image) {
  264.             return new JsonResponse(['success' => false'error' => 'not_found'], 404);
  265.         }
  266.         $template $this->entityManager->getRepository(Page::class)->find($image->getTemplateId());
  267.         if (!$template) {
  268.             return new JsonResponse(['success' => false'error' => 'not_found'], 404);
  269.         }
  270.         [$templateName$params] = $this->resolveViewImageTemplateData($template$image);
  271.         $html $this->renderView($templateName$params);
  272.         // Capture the lead first so a PDF/mail hiccup never loses the email address.
  273.         $lead = new PictureLead();
  274.         $lead->setEmail($email);
  275.         $lead->setCode($code);
  276.         $lead->setLocale($request->getLocale());
  277.         $this->entityManager->persist($lead);
  278.         $this->entityManager->flush();
  279.         try {
  280.             $filename 'lead_' time() . '_' bin2hex(random_bytes(4));
  281.             $htmlDir $parameterBag->get('kernel.project_dir') . '/public/uploads/html/' $filename '.html';
  282.             file_put_contents($htmlDir$html);
  283.             $pdfDir $parameterBag->get('kernel.project_dir') . '/public/uploads/pdf/' $filename '.pdf';
  284.             PdfConverter::convertHtmlToPdf($htmlDir$pdfDir);
  285.             $mail = (new Email())
  286.                 ->from(new Address('support@infinitemoment.life''The Infinite Moment'))
  287.                 ->to($email)
  288.                 ->subject('Your Infinite Moment Gazette')
  289.                 ->html($this->renderView('email/picture_delivery.html.twig', ['code' => $code]))
  290.                 ->attachFromPath($pdfDir$filename '.pdf''application/pdf');
  291.             $mailer->send($mail);
  292.         } catch (\Throwable $e) {
  293.             return new JsonResponse(['success' => false'error' => 'mail_failed'], 200);
  294.         }
  295.         return new JsonResponse(['success' => true]);
  296.     }
  297.     #[Route('/image/test')]
  298.     public function test(ParameterBagInterface $parameterBagCacheManager $cacheManager)
  299.     {
  300.         $imagePath 'https://infinitemoment.nyc3.cdn.digitaloceanspaces.com/691df26f50fe7.jpg';
  301.         $filteredPath $cacheManager->resolve($imagePath'small_thumb');
  302.         $page $this->entityManager->getRepository(Page::class)->find(10);
  303.         $origFile "https://infinitemoment.nyc3.cdn.digitaloceanspaces.com/6912214177e31.jpg";
  304.         $imgName pathinfo($origFilePATHINFO_BASENAME);
  305.         $destImg '/var/www/infinitemoment/public/uploads/img/' $imgName;
  306.         PdfConverter::resizeImage($origFile$destImg);
  307.         $html $this->renderView('@web/page/template-tmp.html.twig', [
  308.             'page' => $page,
  309.             'preview' => false,
  310.             'image' => 'https://infinitemoment.am/uploads/img/' $imgName,
  311.             'editable' => false
  312.         ]);
  313.         $filename = (string)time();
  314.         $htmlDir $parameterBag->get('kernel.project_dir') . '/public/uploads/html/' $filename '.html';
  315.         file_put_contents($htmlDir$html);
  316.         $pdfDir $parameterBag->get('kernel.project_dir') . '/public/uploads/pdf/' $filename '.pdf';
  317.         PdfConverter::convertHtmlToPdf($htmlDir$pdfDir);
  318.     }
  319.     #[Route('/image/delete'name'image_delete'methods: ['GET'])]
  320.     public function delete(Request $request)
  321.     {
  322.         $ids = ['54302020250427''21442120250427''05461720250501''14131420250509''15221620250601''45452020250811''48002120250901''35391420250904'];
  323.         if (!empty($ids)) {
  324.             foreach ($ids as $id) {
  325.                 $image $this->entityManager->getRepository(Gallery::class)->findOneBy(['documentNumber' => $id]);
  326.                 if ($image) {
  327.                     // Delete file from filesystem
  328.                     $filePath $this->getParameter('brochures_directory') . '/' $image->getImage();
  329.                     if (file_exists($filePath)) {
  330.                         unlink($filePath);
  331.                     }
  332.                     // Remove entity
  333.                     $this->entityManager->remove($image);
  334.                 }
  335.             }
  336.             $this->entityManager->flush();
  337.             $this->addFlash('success''Selected images deleted successfully.');
  338.         } else {
  339.             $this->addFlash('warning''No images selected.');
  340.         }
  341.         die;
  342.     }
  343.     #[Route('{_locale}/santa'name'page_santa'requirements: ['_locale' => 'en|ru|hy|es|it|fr|el|ua|ka|de'], defaults: ['_locale' => 'hy'])]
  344.     public function pageSanta(
  345.         Request $request,
  346.                 $_locale,
  347.         SpacesUploader $spacesUploader,
  348.         TranslatorInterface $translator,
  349.         MetaRepository $metaRepository,
  350.         MailerInterface $mailer
  351.     ) {
  352.         if ($request->isMethod('POST')) {
  353.             $action $request->request->get('action');
  354.             $santa = new Santa();
  355.             $brochureFile $request->files->get('image');
  356.             $brochureFileName $spacesUploader->upload($brochureFile);
  357.             $gift $request->get('gift') ?: time();
  358.             $santa->setEmail($request->get('email'));
  359.             $santa->setFromName($request->get('fromName'));
  360.             $santa->setFromEmail($request->get('fromEmail'));
  361.             $santa->setGift($gift);
  362.             $santa->setPhone($request->get('phone'));
  363.             $santa->setMessage($request->get('comment'));
  364.             $santa->setImage($brochureFileName);
  365.             $santa->setLocale($_locale);
  366.             $santa->setStatus(SantaEnum::SANTA_PROGRESS);
  367.             $santa->setName($request->get('name'));
  368.             $this->entityManager->persist($santa);
  369.             $this->entityManager->flush();
  370.             if ($action === 'pay_now') {
  371.                 if ($this->getUser()) {
  372.                     return $this->redirect('/payment/confirm?santa_id=' $santa->getId());
  373.                 }
  374.                 $message = empty($request->get('comment')) ? 2;
  375.                 return $this->redirect(
  376.                     'https://anyrentalcars.com/armenia/infinite?' .
  377.                     'santa_id=' $santa->getId() .
  378.                     '&locale=' $santa->getLocale() .
  379.                     '&message=' $message
  380.                 );
  381.             }
  382.             if ($action === 'request') {
  383.                 $santa->setStatus(SantaEnum::SANTA_REQUEST);
  384.                 $this->entityManager->flush();
  385.                 $this->addFlash('success'$translator->trans('Your request has been sent successfully!'));
  386.                 return $this->redirectToRoute('page_index');
  387.             }
  388.         }
  389.         return $this->render('@web/page/santa.html.twig');
  390.     }
  391.     #[Route('/payment/reject'name'page_reject')]
  392.     public function pageReject(Request $request)
  393.     {
  394.         if ($request->get('santa_id')) {
  395.             $santa $this->entityManager->getRepository(Santa::class)->find($request->get('santa_id'));
  396.             $santa->setStatus(SantaEnum::SANTA_REJECTED);
  397.             return $this->render('@web/page/payment_reject.html.twig');
  398.         }
  399.         return $this->redirectToRoute('page_index');
  400.     }
  401.     #[Route('/payment/confirm'name'page_confirm')]
  402.     public function pageConfirm(Request $requestHttpClientInterface $client)
  403.     {
  404.         if ($request->get('santa_id')) {
  405.             $santa $this->entityManager->getRepository(Santa::class)->find($request->get('santa_id'));
  406.             $santa->setStatus(SantaEnum::SANTA_PAID);
  407.             $this->entityManager->flush();
  408.             $payload = [
  409.                 'id' => $santa->getId(),
  410.                 'name' => $santa->getName(),
  411.                 'locale' => $santa->getLocale(),
  412.                 'gift' => $santa->getGift(),
  413.                 'message' => $santa->getMessage(),
  414.             ];
  415.             // Pass the payload to Twig
  416.             return $this->render('@web/page/payment_confirm.html.twig', [
  417.                 'payload' => json_encode($payload),
  418.             ]);
  419.         }
  420.         return $this->redirectToRoute('page_index');
  421.     }
  422.     #[Route('/send_santa_api'name'send_santa_api')]
  423.     public function sendSantaApi(Request $requestHttpClientInterface $clientRequestStack $requestStackParameterBagInterface $parameterBagMailerInterface $mailerTranslatorInterface $translator)
  424.     {
  425.         $payload json_decode($request->getContent(), true);
  426.         $santa $this->entityManager->getRepository(Santa::class)->find($payload['id']);
  427.         $santa->setStatus(SantaEnum::SANTA_PROCESS);
  428.         $this->entityManager->flush();
  429. // Call the other API
  430.         $response $client->request('POST''https://santa2025.store/receive_letter_api', [
  431.             'json' => $payload,
  432.             'headers' => [
  433.                 'Accept' => 'application/json',
  434.                 'Content-Type' => 'application/json',
  435.                 'User-Agent' => 'Mozilla/5.0 (Symfony Server)',
  436.                 'Referer' => 'https://santa2025.store',
  437.             ],
  438.             'timeout' => 1500,
  439.         ]);
  440.         $data $response->toArray(); // The API response
  441.         $videoUrl $data['video_url'] ?? null;
  442.         $santa->setStatus(SantaEnum::SANTA_COMPLETED);
  443.         $this->entityManager->flush();
  444.         $letter $this->entityManager->getRepository(Letter::class)->findOneBy(['externalId' => $data['letter_id']]);
  445.         $gallery $this->entityManager->getRepository(Gallery::class)->findOneBy(['letterId' => $letter->getId()]);
  446.         $this->entityManager->persist($gallery);
  447.         $this->entityManager->flush();
  448.         $preview false;
  449.         $viewImage false;
  450.         $current $requestStack->getCurrentRequest();
  451.         $main $requestStack->getMainRequest(); // Symfony 5.3+; may be null
  452.         $locale $gallery->getLocale();
  453.         $this->translatableListener->setTranslatableLocale($locale);
  454.         $page $this->entityManager->getRepository(Page::class)
  455.             ->createQueryBuilder('p')
  456.             ->where('p.id = :id')
  457.             ->setParameter('id'$gallery->getTemplateId())
  458.             ->getQuery()
  459.             ->getOneOrNullResult();
  460.         $requestToUse $main ?? $current ?? $request// fallback to injected $request param
  461.         if ($requestToUse) {
  462.             // The important one: attributes _locale (LocaleListener checks this)
  463.             $requestToUse->attributes->set('_locale'$locale);
  464.             // Also set request locale and session (if available)
  465.             $requestToUse->setLocale($locale);
  466.             if ($requestToUse->hasSession()) {
  467.                 $requestToUse->getSession()->set('_locale'$locale);
  468.             }
  469.         }
  470. // 2) Set translator locale
  471.         $translator->setLocale($locale);
  472. // Optional: also set locale on PHP intl (not usually required)
  473.         \Locale::setDefault($locale);
  474.         $html $this->renderView('@web/page/template-santa-tmp.html.twig', [
  475.             'page' => $page,
  476.             'preview' => $preview,
  477.             'viewImage' => $viewImage,
  478.             'letter' => $letter,
  479.             'gallery' => $gallery,
  480.             'image' => $gallery->getImage(),
  481.             'editable' => false
  482.         ]);
  483.         $filename = (string)time();
  484.         $htmlDir $parameterBag->get('kernel.project_dir') . '/public/uploads/html/' $filename '.html';
  485.         file_put_contents($htmlDir$html);
  486.         $pdfDir $parameterBag->get('kernel.project_dir') . '/public/uploads/pdf/' $filename '.pdf';
  487.         PdfConverter::convertHtmlToPdf($htmlDir$pdfDir);
  488.         $subject $translator->trans('email_subject', [
  489.             '%number%' => $gallery->getDocumentNumber(),
  490.             '%name%' => $santa->getName(),
  491.         ]);
  492.         $email = (new Email())
  493.             ->from(new Address('support@infinitemoment.life''Infinite Moment Santa'))
  494.             ->to($santa->getEmail())
  495.             ->subject($subject)
  496.             ->html(
  497.                 $this->renderView(
  498.                     'email/santa_'.$santa->getLocale().'.html.twig',
  499.                     ['subject' => $santa'video_url' => $videoUrl]
  500.                 )
  501.             )
  502.         ->attachFromPath($pdfDir$filename '.pdf''application/pdf');
  503.         $mailer->send($email);
  504.         if ($santa->getFromEmail()) {
  505.             $email = (new Email())
  506.                 ->from(new Address('support@infinitemoment.life''Infinite Moment Santa'))
  507.                 ->to(
  508.                     $santa->getFromEmail(),
  509.                     'developerhovsepyan@gmail.com'
  510.                 )
  511.                 ->subject($subject)
  512.                 ->html(
  513.                     $this->renderView(
  514.                         'email/santa_'.$santa->getLocale().'.html.twig',
  515.                         [
  516.                             'subject' => $santa,
  517.                             'video_url' => $videoUrl
  518.                         ]
  519.                     )
  520.                 )
  521.                 ->attachFromPath($pdfDir$filename '.pdf''application/pdf');
  522.             $mailer->send($email);
  523.         }
  524.         return new JsonResponse([
  525.             'video_url' => $videoUrl
  526.         ]);
  527.     }
  528.     private const ADMIN_ID 7;
  529.     #[Route('/import-excel-public'name'import_excel_public')]
  530.     public function importFromPublic(EntityManagerInterface $em): Response
  531.     {
  532.         $filePath $this->getParameter('kernel.project_dir') . '/public/uploads/Infinite July.xlsx';
  533.         // $filePath = '/mnt/data/Infinite July.xlsx'; // sandbox
  534.         if (!file_exists($filePath)) {
  535.             return new Response("Excel NOT FOUND: " $filePath);
  536.         }
  537.         $spreadsheet IOFactory::load($filePath);
  538.         // ✅ Your file has sheet name "July"
  539.         $sheet $spreadsheet->getSheetByName('July') ?? $spreadsheet->getSheet(0);
  540.         $maxRow = (int)$sheet->getHighestRow();
  541.         // coin value => index in entity fields
  542.         $coinIndexMap = [
  543.             20000 => 110000 => 25000 => 32000 => 41000 => 5,
  544.             500 => 6200 => 7100 => 850 => 920 => 1010 => 11=> 15
  545.         ];
  546.         $userAliases = [
  547.             'anahit' => 'anahit old',
  548.         ];
  549.         $created 0;
  550.         $skippedNoLocation 0;
  551.         $skippedNoDate 0;
  552.         $skippedNoPage 0;
  553.         $skippedDuplicate 0;
  554.         $skippedNoUserMatch 0;
  555.         /**
  556.          * ✅ Your Excel structure is fixed blocks:
  557.          * Header row: 2,17,32,... (step 15)
  558.          * Coins: row+1..row+11 (10 AMD row is row+11)
  559.          * Total row: row+12
  560.          */
  561.         for ($row 2$row <= $maxRow$row += 15) {
  562.             // Location is in C(row)
  563.             $location trim((string)($sheet->getCell("C$row")->getValue() ?? ''));
  564.             if ($location === '') {
  565.                 $skippedNoLocation++;
  566.                 continue;
  567.             }
  568.             // Date is in E(row)
  569.             $rawDate $sheet->getCell("E$row")->getValue();
  570.             // fallback for formulas / calculated cells
  571.             if ($rawDate === null || $rawDate === '') {
  572.                 $rawDate $sheet->getCell("E$row")->getCalculatedValue();
  573.             }
  574.             $currentDay $this->parseExcelDate2025($rawDate);
  575.             if (!$currentDay) {
  576.                 $skippedNoDate++;
  577.                 continue;
  578.             }
  579.             // Find Page by name = location
  580.             $page $em->getRepository(Page::class)
  581.                 ->createQueryBuilder('p')
  582.                 ->where('LOWER(p.name) = LOWER(:name)')
  583.                 ->setParameter('name'$location)
  584.                 ->setMaxResults(1)
  585.                 ->getQuery()
  586.                 ->getOneOrNullResult();
  587.             if (!$page) {
  588.                 $skippedNoPage++;
  589.                 continue;
  590.             }
  591.             $type 1// Day
  592.             // Skip duplicates
  593.             $existing $em->getRepository(FinancialPart::class)->findOneBy([
  594.                 'page' => $page,
  595.                 'currentDay' => $currentDay,
  596.                 'type' => $type,
  597.             ]);
  598.             if ($existing) {
  599.                 $skippedDuplicate++;
  600.                 continue;
  601.             }
  602.             $fp = new FinancialPart();
  603.             $fp->setCreatedAt(new \DateTime());
  604.             $fp->setCurrentDay($currentDay);
  605.             $fp->setType($type);
  606.             $fp->setPage($page);
  607.             // currency like admin
  608.             $this->setCurrency($fp);
  609.             // init quantities
  610.             foreach ([1,2,3,4,5,6,7,8,9,10,11,15] as $i) {
  611.                 $setCash "setCoinQuantity$i";
  612.                 $setTr   "setTransferQuantity$i";
  613.                 if (method_exists($fp$setCash)) $fp->$setCash(0);
  614.                 if (method_exists($fp$setTr))   $fp->$setTr(0);
  615.             }
  616.             // ✅ Coins are on next rows: row+1 .. row+11
  617.             for ($r $row 1$r <= $row 11 && $r <= $maxRow$r++) {
  618.                 $coinType $this->toIntDigits($sheet->getCell("C$r")->getValue());
  619.                 $qty      $this->toIntDigits($sheet->getCell("D$r")->getValue());
  620.                 // Some rows have qty empty but value 0 -> ignore
  621.                 if ($coinType <= || $qty <= 0) continue;
  622.                 if (!isset($coinIndexMap[$coinType])) continue;
  623.                 $idx $coinIndexMap[$coinType];
  624.                 $setCash "setCoinQuantity$idx";
  625.                 if (method_exists($fp$setCash)) {
  626.                     $fp->$setCash($qty);
  627.                 }
  628.             }
  629.             // ✅ Users are on row+1 in columns H..K
  630.             $userNames = [];
  631.             $usersRow $row 1;
  632.             if ($usersRow <= $maxRow) {
  633.                 foreach (['H','I','J','K'] as $col) {
  634.                     $name trim((string)($sheet->getCell($col $usersRow)->getValue() ?? ''));
  635.                     if ($name !== ''$userNames[] = $name;
  636.                 }
  637.             }
  638.             $userNames array_values(array_unique($userNames));
  639.             $userCount count($userNames);
  640.             $percentPerUser = match ($userCount) {
  641.                 => 66,
  642.                 => 33,
  643.                 => 25,
  644.                 default => 0,
  645.             };
  646.             $matchedUsers 0;
  647.             $managerUser null// first matched = manager
  648.             foreach ($userNames as $excelUserName) {
  649.                 $user $this->findUserByExcelName($em$excelUserName$userAliases);
  650.                 if (!$user) continue;
  651.                 if ($managerUser === null$managerUser $user;
  652.                 $us = new UserSalary();
  653.                 $us->setUser($user);
  654.                 $us->setPercent($percentPerUser);
  655.                 $us->setSalary(0);
  656.                 $us->setExpense(0);
  657.                 $fp->addUserSalary($us);
  658.                 $em->persist($us);
  659.                 $matchedUsers++;
  660.             }
  661.             if ($matchedUsers === || $managerUser === null) {
  662.                 $skippedNoUserMatch++;
  663.                 continue;
  664.             }
  665.             // ✅ same as FinancialPartAdmin
  666.             $this->ensureAdminAndManager($fp$em$managerUser);
  667.             $this->calculateSalaryLikeAdmin($fp$managerUser);
  668.             $em->persist($fp);
  669.             $created++;
  670.         }
  671.         $em->flush();
  672.         return new Response(sprintf(
  673.             "Import done. Created: %d | Skipped(no Location): %d | Skipped(no Date): %d | Skipped(no Page): %d | Skipped(duplicate): %d | Skipped(no user match): %d",
  674.             $created$skippedNoLocation$skippedNoDate$skippedNoPage$skippedDuplicate$skippedNoUserMatch
  675.         ));
  676.     }
  677.     private function ensureAdminAndManager(FinancialPart $fpEntityManagerInterface $emUser $managerUser): void
  678.     {
  679.         $adminExists false;
  680.         $managerExists false;
  681.         foreach ($fp->getUserSalary() as $salary) {
  682.             $uid $salary->getUser()?->getId();
  683.             if ($uid === self::ADMIN_ID$adminExists true;
  684.             if ($uid === $managerUser->getId()) $managerExists true;
  685.         }
  686.         if (!$adminExists) {
  687.             $adminSalary = new UserSalary();
  688.             $adminSalary->setUser($em->getReference(User::class, self::ADMIN_ID));
  689.             $adminSalary->setPercent(40);
  690.             $adminSalary->setSalary(0);
  691.             $adminSalary->setExpense(0);
  692.             $fp->addUserSalary($adminSalary);
  693.             $em->persist($adminSalary);
  694.         }
  695.         if (!$managerExists) {
  696.             $managerSalary = new UserSalary();
  697.             $managerSalary->setUser($em->getReference(User::class, $managerUser->getId()));
  698.             $managerSalary->setPercent(60);
  699.             $managerSalary->setSalary(0);
  700.             $managerSalary->setExpense(0);
  701.             $fp->addUserSalary($managerSalary);
  702.             $em->persist($managerSalary);
  703.         }
  704.     }
  705.     private function calculateSalaryLikeAdmin(FinancialPart $fpUser $managerUser): void
  706.     {
  707.         $this->setCurrency($fp);
  708.         foreach ($fp->getUserSalary() as $salary) {
  709.             $salary->setSalary(0);
  710.             $salary->setExpense(0);
  711.         }
  712.         $coins = [
  713.             => 20000=> 10000=> 5000=> 2000=> 1000,
  714.             => 500=> 200=> 100=> 5010 => 2011 => 1015 => 1,
  715.         ];
  716.         $totalPrice 0;
  717.         foreach ($coins as $i => $value) {
  718.             $cash method_exists($fp"getCoinQuantity$i") ? ((int)($fp->{"getCoinQuantity$i"}() ?? 0)) : 0;
  719.             $transfer method_exists($fp"getTransferQuantity$i") ? ((int)($fp->{"getTransferQuantity$i"}() ?? 0)) : 0;
  720.             $totalPrice += ($cash $transfer) * $value;
  721.         }
  722.         $adminExpense $totalPrice 0.05;
  723.         $currentPrice $totalPrice $adminExpense;
  724.         $managerProportion $currentPrice 0.60;
  725.         $managerSalaryPrice $managerProportion;
  726.         $managerSalaryRow null;
  727.         foreach ($fp->getUserSalary() as $salary) {
  728.             $uid $salary->getUser()?->getId();
  729.             if ($uid === self::ADMIN_ID) {
  730.                 $salary->setSalary($currentPrice $managerProportion);
  731.                 $salary->setExpense($adminExpense);
  732.                 continue;
  733.             }
  734.             if ($uid === $managerUser->getId()) {
  735.                 $managerSalaryRow $salary;
  736.                 continue;
  737.             }
  738.             $userSalaryPrice $managerProportion * (((int)($salary->getPercent() ?? 0)) / 100);
  739.             $salary->setSalary($userSalaryPrice);
  740.             $managerSalaryPrice -= $userSalaryPrice;
  741.         }
  742.         if ($managerSalaryRow) {
  743.             $managerSalaryRow->setSalary($managerSalaryPrice);
  744.         }
  745.         $fp->setTotalPrice((int)$totalPrice);
  746.     }
  747.     private function setCurrency(FinancialPart $fp): void
  748.     {
  749.         $fp->setCoinType1(20000);
  750.         $fp->setCoinType2(10000);
  751.         $fp->setCoinType3(5000);
  752.         $fp->setCoinType4(2000);
  753.         $fp->setCoinType5(1000);
  754.         $fp->setCoinType6(500);
  755.         $fp->setCoinType7(200);
  756.         $fp->setCoinType8(100);
  757.         $fp->setCoinType9(50);
  758.         $fp->setCoinType10(20);
  759.         $fp->setCoinType11(10);
  760.         $fp->setCoinType15(1);
  761.     }
  762.     private function toIntDigits(mixed $v): int
  763.     {
  764.         if ($v === null) return 0;
  765.         if (is_int($v)) return $v;
  766.         if (is_float($v)) return (int)round($v);
  767.         $s trim((string)$v);
  768.         if ($s === '') return 0;
  769.         $digits preg_replace('/\D+/'''$s);
  770.         if ($digits === '' || $digits === null) return 0;
  771.         return (int)$digits;
  772.     }
  773.     private function normalizeName(string $s): string
  774.     {
  775.         $s mb_strtolower(trim($s));
  776.         $s preg_replace('/\s+/'' '$s);
  777.         return $s ?? '';
  778.     }
  779.     private function findUserByExcelName(EntityManagerInterface $emstring $excelName, array $aliases): ?User
  780.     {
  781.         $norm $this->normalizeName($excelName);
  782.         if (isset($aliases[$norm])) {
  783.             $target $aliases[$norm];
  784.             $u $em->getRepository(User::class)->createQueryBuilder('u')
  785.                 ->where('LOWER(u.name) = LOWER(:name)')
  786.                 ->setParameter('name'$target)
  787.                 ->setMaxResults(1)
  788.                 ->getQuery()
  789.                 ->getOneOrNullResult();
  790.             if ($u) return $u;
  791.         }
  792.         $u $em->getRepository(User::class)->createQueryBuilder('u')
  793.             ->where('LOWER(u.name) = LOWER(:name)')
  794.             ->setParameter('name'$excelName)
  795.             ->setMaxResults(1)
  796.             ->getQuery()
  797.             ->getOneOrNullResult();
  798.         if ($u) return $u;
  799.         return $em->getRepository(User::class)->createQueryBuilder('u')
  800.             ->where('LOWER(u.name) LIKE :q')
  801.             ->setParameter('q''%' $norm '%')
  802.             ->setMaxResults(1)
  803.             ->getQuery()
  804.             ->getOneOrNullResult();
  805.     }
  806.     // ✅ Works for your file (Excel numeric dates + DateTime + strings)
  807.     private function parseExcelDate2025(mixed $raw): ?\DateTime
  808.     {
  809.         if ($raw === null || $raw === '') return null;
  810.         if ($raw instanceof \DateTimeInterface) {
  811.             $dt = new \DateTime($raw->format('Y-m-d'));
  812.             $dt->setDate(2025, (int)$dt->format('m'), (int)$dt->format('d'));
  813.             $dt->setTime(000);
  814.             return $dt;
  815.         }
  816.         if (is_numeric($raw)) {
  817.             try {
  818.                 $dt ExcelDate::excelToDateTimeObject((float)$raw);
  819.                 $dt->setDate(2025, (int)$dt->format('m'), (int)$dt->format('d'));
  820.                 $dt->setTime(000);
  821.                 return $dt;
  822.             } catch (\Throwable) {}
  823.         }
  824.         $s trim((string)$raw);
  825.         if ($s === '') return null;
  826.         $formats = ['d.m.Y','d/m/Y','Y-m-d','d-m-Y','m/d/Y'];
  827.         foreach ($formats as $fmt) {
  828.             $dt \DateTime::createFromFormat($fmt$s);
  829.             if ($dt instanceof \DateTime) {
  830.                 $dt->setDate(2025, (int)$dt->format('m'), (int)$dt->format('d'));
  831.                 $dt->setTime(000);
  832.                 return $dt;
  833.             }
  834.         }
  835.         $ts strtotime($s);
  836.         if ($ts !== false) {
  837.             $dt = (new \DateTime())->setTimestamp($ts);
  838.             $dt->setDate(2025, (int)$dt->format('m'), (int)$dt->format('d'));
  839.             $dt->setTime(000);
  840.             return $dt;
  841.         }
  842.         return null;
  843.     }
  844.     #[Route('/user-account'name'user_account'methods: ['GET'])]
  845.     public function userAccount(Request $request): Response
  846.     {
  847.         if (!$this->getUser()){
  848.             return $this->redirectToRoute('app_login');
  849.         }
  850.         $templates $this->entityManager->getRepository(UserTemplate::class)->findBy(['user' => $this->getUser()]);
  851.         return $this->render('@web/page/user-account.html.twig',[
  852.             'templates' => $templates,
  853.             'editable' => false,
  854.         ]);
  855.     }
  856.     #[Route('/user-gallery'name'user_gallery'methods: ['GET'])]
  857.     public function userGallery(Request $request): Response
  858.     {
  859.         if (!$this->getUser()){
  860.             return $this->redirectToRoute('app_login');
  861.         }
  862.         $gallery $this->entityManager->getRepository(Gallery::class)->findBy(['user' => $this->getUser()],['id' => 'DESC'],20);
  863.         return $this->render('@web/page/user-gallery.html.twig',[
  864.             'gallery' => $gallery,
  865.             'editable' => false,
  866.         ]);
  867.     }
  868.     #[Route('/user-sallary'name'user_sallary'methods: ['GET'])]
  869.     public function userSallary(Request $request): Response
  870.     {
  871.         if (!$this->getUser()){
  872.             return $this->redirectToRoute('app_login');
  873.         }
  874.         $stats $this->entityManager->getRepository(FinancialPart::class)->searchStats($request->query->all(), [IncomeEnum::TYPE_DAILYIncomeEnum::TYPE_ADDITIONAL]);
  875.         $statsDebt $this->entityManager->getRepository(FinancialPart::class)->searchStats($request->query->all(), [IncomeEnum::TYPE_DEBT]);
  876.         $statsExpense $this->entityManager->getRepository(Expense::class)->searchStats($request->query->all());
  877.         return $this->render('@web/page/user-sallary.html.twig',[
  878.             'editable' => false,
  879.             'stats' => $stats,
  880.             'statsDebt' => $statsDebt,
  881.             'statsExpense' => $statsExpense,
  882.         ]);
  883.     }
  884.     #[Route('/setData'name'setData'methods: ['POST'])]
  885.     public function setData(Request $request): Response
  886.     {
  887.         $json $request->getContent();
  888.         $file $this->getParameter('kernel.project_dir')
  889.             . '/var/data_' date('Y-m-d_H-i-s') . '.json';
  890.         file_put_contents($file$json);
  891.         return new Response('Saved to ' basename($file));
  892.     }
  893. }