src/Controller/ProfileController.php line 761

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Accounts\Users;
  4. use App\Entity\Accounts\UserRecovery;
  5. use App\Repository\Accounts\UsersRepository;
  6. use App\Repository\Accounts\UserRecoveryRepository;
  7. use App\Form\ChangePasswordType;
  8. use App\Form\PasswordRecoveryType;
  9. use App\Repository\Accounts\UserTokensRepository;
  10. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  11. use Symfony\Component\Form\FormFactoryInterface;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpFoundation\RedirectResponse;
  15. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  16. use Symfony\Component\Routing\Annotation\Route;
  17. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  18. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  19. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  20. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  21. use Symfony\Component\Security\Core\Security;
  22. use Symfony\Component\Security\Core\User\UserInterface;
  23. use Doctrine\ORM\EntityManagerInterface;
  24. use Symfony\Contracts\Translation\TranslatorInterface;
  25. use Symfony\Component\HttpKernel\KernelInterface;
  26. use Symfony\Component\HttpFoundation\JsonResponse;
  27. use Symfony\Component\Mailer\MailerInterface;
  28. use Symfony\Component\Mime\Email;
  29. use Symfony\Component\Mime\Address;
  30. use Symfony\Component\Mime\Header\PathHeader;
  31. class ProfileController extends AbstractController
  32. {
  33.     private EntityManagerInterface $em;
  34.     private TranslatorInterface $translator;
  35.     private KernelInterface $kernel;
  36.     private TokenStorageInterface $tokenStorage;
  37.     public function __construct(EntityManagerInterface $emTranslatorInterface $translatorKernelInterface $kernelTokenStorageInterface $tokenStorage)
  38.     {
  39.         $this->em $em;
  40.         $this->translator $translator;
  41.         $this->kernel $kernel;
  42.         $this->tokenStorage $tokenStorage;
  43.     }
  44.     #[Route(path'/acc/profile'name'acc_profile')]
  45.     public function profile(Request $requestUsersRepository $usersRepository): Response
  46.     {
  47.         /** @var $user \App\Entity\Accounts\Users */
  48.         $user $this->getUser();
  49.         if (!$user instanceof UserInterface) {
  50.             return new RedirectResponse($this->generateUrl('acc_login'));
  51.         }
  52.         $currentLocale $request->getLocale(); // Або $this->translator->getLocale();
  53.         $avatar $request->files->get('avatar');
  54.         if ($avatar) {
  55.             $mimeType $avatar->getMimeType();
  56.             if (in_array($mimeType, ['image/jpeg''image/png'])) {
  57.                 $ch curl_init('https://www.turpravda.com/acc/profile/avatar?json=1');
  58.                 //curl_setopt($ch, CURLOPT_VERBOSE, true);
  59.                 curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
  60.                 curl_setopt($chCURLOPT_FOLLOWLOCATIONfalse);
  61.                 //curl_setopt($ch, CURLOPT_FAILONERROR, true);
  62.                 curl_setopt($chCURLOPT_SSL_VERIFYHOSTfalse);
  63.                 curl_setopt($chCURLOPT_SSL_VERIFYPEERfalse);
  64.                 //curl_setopt($ch, CURLOPT_HEADER, true);
  65.                 curl_setopt($chCURLOPT_RETURNTRANSFER1);
  66.                 curl_setopt($chCURLOPT_USERAGENT'otpusk.com');
  67.                 $base64Image 'data:'$avatar->getMimeType() .';base64,' base64_encode(file_get_contents($avatar->getPathname()));
  68.                 $data = [
  69.                     'avatar' => $base64Image,
  70.                     'image' => new \CURLFile($avatar->getPathname(), $avatar->getMimeType(), $avatar->getClientOriginalName()),
  71.                     'token' => 'C04JG_9ECNG_ZF2SR_GE86G_0F7ZM',
  72.                     'user_id' => $user->getId(),
  73.                     'json' => 1,
  74.                 ];
  75.                 curl_setopt($chCURLOPT_POST1);
  76.                 curl_setopt($chCURLOPT_POSTFIELDS$data);
  77.                 $response curl_exec($ch);
  78.                 $code curl_getinfo($chCURLINFO_HTTP_CODE);
  79.                 curl_close($ch);
  80.                 $json json_decode($response,1);
  81.                 if ($this->kernel->getEnvironment() !== 'prod') {
  82.                     $user->setImage($user->getImageAvatar());
  83.                     //$imageInfo = getimagesize($avatar->getPathname());
  84.                     //if ($imageInfo !== false) $user->setImageSize($imageInfo[0] . 'x' . $imageInfo[1]);
  85.                     $this->em->persist($user);
  86.                     $this->em->flush();
  87.                 }
  88.                 if (curl_errno($ch) || $code!=200 || $json['error']==true) {
  89.                     $this->addFlash('error'$this->translator->trans('profile.upload_failed'));
  90.                 } else {
  91.                     $this->addFlash('success'$this->translator->trans('profile.upload_successful'));
  92.                 }
  93.             } else {
  94.                 $this->addFlash('error'$this->translator->trans('profile.invalid_image_format'));
  95.             }
  96.         }
  97.         if ($request->isMethod('POST')) {
  98.             $action $request->request->get('action''edit');
  99.             if ($action == 'delete_avatar') {
  100.                 $user->setImage('');
  101.                 $user->setImageSize('');
  102.                 $this->em->persist($user);
  103.                 $this->em->flush();
  104.                 $this->addFlash('success'$this->translator->trans('profile.delete_avatar'));
  105.             } else {
  106.                 $name $request->request->get('name');
  107.                 $surname $request->request->get('surname');
  108.                 $middleName $request->request->get('middleName');
  109.                 $address $request->request->get('address');
  110.                 $birthday $request->request->get('birthday'false);
  111.                 $sex $request->request->get('sex');
  112.                 $email $request->request->get('email');
  113.                 $phone $request->request->get('phone');
  114.                 $phone2 $request->request->get('phone2');
  115.                 if ($user instanceof Users) {
  116.                     $user->setName($name . ($surname ' ' $surname '') . ($middleName ' ' $middleName ''));
  117.                     $user->setText($address);
  118.                     //$user->setSurname($surname);
  119.                     //$user->setMiddleName($middleName);
  120.                     //$user->setAddress($address);
  121.                     if ($birthday$user->setBirthDay(new \DateTime($birthday));
  122.                     else $user->setBirthDay(null);
  123.                     $user->setSex($sex);
  124.                     $user->setMail($email);
  125.                     //$user->setPhone($phone);
  126.                     if ($phone2 && $phone!=$phone2$user->setPhoneAdd($phone2);
  127.                     $this->em->persist($user);
  128.                     $this->em->flush();
  129.                     $usersRepository->getCrmUserId($user->getId(), $user->getMail(), $user->getPhone());
  130.                     $this->addFlash('success'$this->translator->trans('profile.update_successful'));
  131.                 }
  132.             }
  133.             return new RedirectResponse($this->generateUrl('acc_profile'));
  134.         }
  135.         list($name$surname$middleName) = array_pad(explode(' '$user->getName(), 3), 3null);
  136.         list($phone$phone2) = array_pad(explode(','$user->getPhone(), 2), 2null);
  137.         return $this->render('profile/index.html.twig', [
  138.             'user' => $user,
  139.             'currentLocale' => $currentLocale,
  140.             'name' => $name??'',
  141.             'surname' => $surname??'',
  142.             'middleName' => $middleName??'',
  143.             'phone' => $user->getPhone()??'',
  144.             'phoneNoVerifySms' => $user->getPhoneSms()!='verify',
  145.             'phone2' => $user->getPhoneAdd()??$phone2,
  146.             'phone2NoVerifySms' => $user->getPhoneAddSms()!='verify',
  147.         ]);
  148.     }
  149.     #[Route(path'/acc/api/profile'name'acc_api_profile_get'methods: ['GET'])]
  150.     public function apiProfileGet(Request $requestTranslatorInterface $translator): JsonResponse
  151.     {
  152.         /** @var Users|null $user */
  153.         $user $this->getUser();
  154.         if (!$user instanceof UserInterface) {
  155.             return new JsonResponse([
  156.                 'error'   => true,
  157.                 'message' => $translator->trans('verify.user_not_authorized')
  158.             ], 401);
  159.         }
  160.         return new JsonResponse([
  161.             'success' => true,
  162.             'user' => [
  163.                 'id'     => $user->getId(),
  164.                 'name'   => $user->getName(),
  165.                 'email'  => $user->getEmail(),
  166.                 'phone'  => $user->getPhone(),
  167.                 'phoneVerifySms' => $user->getPhone()&&$user->getPhoneSms()=='verify',
  168.                 'phone2' => $user->getPhoneAdd(),
  169.                 'phone2VerifySms' => $user->getPhoneAdd()&&$user->getPhoneAddSms()=='verify',
  170.                 'sex'    => $user->getSex(),
  171.                 'birthday' => $user->getBirthDay()?->format('Y-m-d'),
  172.                 'address'  => $user->getText(),
  173.             ],
  174.         ], 200);
  175.     }
  176.     #[Route(path'/acc/api/profile'name'acc_api_profile'methods: ['POST'])]
  177.     public function apiProfile(Request $requestTranslatorInterface $translatorUsersRepository $usersRepository): JsonResponse
  178.     {
  179.         /** @var Users|null $user */
  180.         $user $this->getUser();
  181.         if (!$user instanceof UserInterface) {
  182.             return new JsonResponse([
  183.                 'error'   => true,
  184.                 'message' => $translator->trans('verify.user_not_authorized')
  185.             ], 401);
  186.         }
  187.         $data json_decode($request->getContent(), true);
  188.         if (is_array($data)) {
  189.             $name       $data['name'] ?? false;
  190.             $address    $data['address'] ?? false;
  191.             $birthday   $data['birthday'] ?? false;
  192.             $sex        $data['sex'] ?? false;
  193.             $email      $data['email'] ?? false;
  194.             $phone      $data['phone'] ?? false;
  195.         } else {
  196.             $name       $request->request->get('name'false);
  197.             $address    $request->request->get('address'false);
  198.             $birthday   $request->request->get('birthday'false);
  199.             $sex        $request->request->get('sex'false);
  200.             $email      $request->request->get('email'false);
  201.             $phone      $request->request->get('phone'false);
  202.         }
  203.         if (!$name) {
  204.             return new JsonResponse([
  205.                 'error' => true,
  206.                 'message' => $translator->trans('profile.form.name_empty'),
  207.             ], 400);
  208.         }
  209.         $user->setName($name);
  210.         if (!$email) {
  211.             return new JsonResponse([
  212.                 'error' => true,
  213.                 'message' => $translator->trans('profile.form.email_empty'),
  214.             ], 400);
  215.         }
  216.         $existingUser $this->em->getRepository(Users::class)->findOneBy(['mail' => $email]);
  217.         if ($existingUser && $existingUser->getId() !== $user->getId()) {
  218.             return new JsonResponse([
  219.                 'error'   => true,
  220.                 'message' => $translator->trans('profile.form.email_in_use'),
  221.             ], 400);
  222.         }
  223.         $user->setMail($email);
  224.         if ($address$user->setText($address);
  225.         if ($birthday$user->setBirthDay(new \DateTime($birthday));
  226.         if ($sex$user->setSex($sex);
  227.         if (!$phone) {
  228.             return new JsonResponse([
  229.                 'error' => true,
  230.                 'message' => $translator->trans('profile.form.phone_empty'),
  231.             ], 400);
  232.         }
  233.         $user->setPhone($phone);
  234.         try {
  235.             $this->em->persist($user);
  236.             $this->em->flush();
  237.             $usersRepository->saveCrmUserProfile($user->getId(), $user->getMail(), $user->getPhone());
  238.         } catch (\Exception $e) {
  239.             return new JsonResponse([
  240.                 'error' => true,
  241.                 'message' => $e->getMessage(),
  242.             ], 400);
  243.         }
  244.         return new JsonResponse([
  245.             'success' => true,
  246.             'user' => [
  247.                 'id'=>$user->getId(),
  248.                 'name'=>$user->getName(),
  249.                 'email'=>$user->getEmail(),
  250.                 'phone'=>$user->getPhone(),
  251.             ],
  252.             'message' => $translator->trans('profile.update_successful'),
  253.         ], 200);
  254.     }
  255.     #[Route(path'/acc/api/sendsms'name'acc_api_sendsms'methods: ['POST'])]
  256.     public function sendSms(Request $request): JsonResponse
  257.     {
  258.         /** @var Users|null $user */
  259.         $user $this->getUser();
  260.         if (!$user instanceof Users) {
  261.             return new JsonResponse([
  262.                 'error' => true,
  263.                 'message' => 'User not authorized',
  264.             ], 401);
  265.         }
  266.         $phone $request->request->get('phone');
  267.         $phone2 $request->request->get('phone2');
  268.         if ($phone2$phone $phone2;
  269.         if (!$phone) {
  270.             return new JsonResponse([
  271.                 'error' => true,
  272.                 'message' => 'Phone number is required',
  273.             ], 400);
  274.         }
  275.         if ($phone2 && $user->getPhone() == $phone2 && $user->getPhoneAddSms()=='verify') {
  276.             return new JsonResponse([
  277.                 'error' => false,
  278.                 'message' => 'SMS sent successfully',
  279.             ], 200);
  280.         } elseif ($user->getPhone() == $phone && $user->getPhoneSms()=='verify'){
  281.             return new JsonResponse([
  282.                 'error' => false,
  283.                 'message' => 'SMS sent successfully',
  284.             ], 200);
  285.         }
  286.         //$user->setPhone($phone);
  287.         $code random_int(100000999999);
  288.         if ($this->kernel->getEnvironment() === 'dev') {
  289.             $code 123456;
  290.         }
  291.         if ($phone2$user->setPhoneAddSms($code);
  292.         else $user->setPhoneSms($code);
  293.         try {
  294.             $this->em->persist($user);
  295.             $this->em->flush();
  296.         } catch (\Exception $e) {
  297.             return new JsonResponse([
  298.                 'error' => true,
  299.                 'message' => 'Failed to save verification code',
  300.             ], 500);
  301.         }
  302.         if ($this->kernel->getEnvironment() !== 'dev') {
  303.             $this->messengersRepository->addNew($phone'Ваш код: ' $code);
  304.         }
  305.         return new JsonResponse([
  306.             'error' => false,
  307.             'message' => 'SMS sent successfully',
  308.         ], 200);
  309.     }
  310.     #[Route(path'/acc/api/verifysms'name'acc_api_verifysms'methods: ['POST'])]
  311.     public function verifySms(Request $requestUsersRepository $usersRepository): JsonResponse
  312.     {
  313.         $phone $request->request->get('phone');
  314.         $phone2 $request->request->get('phone2');
  315.         $code $request->request->get('code');
  316.         if ($phone2$phone $phone2;
  317.         if (!$phone || !$code) {
  318.             return new JsonResponse([
  319.                 'error' => true,
  320.                 'message' => 'Phone number and code are required',
  321.             ], 400);
  322.         }
  323.         /** @var Users|null $user */
  324.         $user $this->getUser();
  325.         if (!$user instanceof Users/* || $user->getPhone() !== $phone)*/) {
  326.             return new JsonResponse([
  327.                 'error'   => true,
  328.                 'message' => 'User not authorized or phone number mismatch',
  329.             ], 401);
  330.         }
  331.         if ($phone2){
  332.             if ($user->getPhoneAddSms() !== $code) {
  333.                 return new JsonResponse([
  334.                     'error' => true,
  335.                     'message' => 'Invalid verification code',
  336.                 ], 400);
  337.             }
  338.         } elseif ($user->getPhoneSms() !== $code) {
  339.             return new JsonResponse([
  340.                 'error'   => true,
  341.                 'message' => 'Invalid verification code',
  342.             ], 400);
  343.         }
  344.         if ($phone2) {
  345.             $user->setPhoneAdd($phone2);
  346.             $user->setPhoneAddSms('verify');
  347.         } else {
  348.             $user->setPhone($phone);
  349.             $user->setPhoneSms('verify');
  350.         }
  351.         try {
  352.             $this->em->persist($user);
  353.             $this->em->flush();
  354.         } catch (\Exception $e) {
  355.             return new JsonResponse([
  356.                 'error' => true,
  357.                 'message' => 'Failed to save phone',
  358.             ], 500);
  359.         }
  360.         return new JsonResponse([
  361.             'error'   => false,
  362.             'message' => 'Verification successful',
  363.         ], 200);
  364.     }
  365.     #[Route(path'/acc/change-password'name'acc_change_password')]
  366.     public function changePassword(Request $requestUserPasswordHasherInterface $passwordHasherTranslatorInterface $translatorEntityManagerInterface $emFormFactoryInterface $formFactory): Response
  367.     {
  368.         /** @var \App\Entity\Accounts\Users $user */
  369.         $user $this->getUser();
  370.         if (!$user) {
  371.             $this->addFlash('error'$translator->trans('profile.error_auth_required'));
  372.             return $this->redirectToRoute('acc_login');
  373.         }
  374.         $form $formFactory->create(ChangePasswordType::class, null, [
  375.             'email' => $user->getMail(),
  376.             'is_api' => false,
  377.         ]);
  378.         $form->handleRequest($request);
  379.         if ($form->isSubmitted() && $form->isValid()) {
  380.             $data $form->getData();
  381.             if (!$passwordHasher->isPasswordValid($user$data['old_password'])) {
  382.                 $this->addFlash('error'$translator->trans('profile.error_old_password_invalid'));
  383.             } else {
  384.                 $hashedPassword $passwordHasher->hashPassword($user$data['new_password']);
  385.                 $user->setPassword($hashedPassword);
  386.                 $em->flush();
  387.                 $this->addFlash('success'$translator->trans('profile.password_changed_success'));
  388.                 return $this->redirectToRoute('acc_change_password');
  389.             }
  390.         }
  391.         return $this->render('profile/change_password.html.twig', [
  392.             'form' => $form->createView(),
  393.             'user' => $user,
  394.         ]);
  395.     }
  396.     #[Route(path'/acc/api/change-password'name'acc_api_change_password'methods: ['POST'])]
  397.     public function apiChangePassword(Request $requestUsersRepository $usersRepositoryUserRecoveryRepository $userRecoveryRepositoryUserPasswordHasherInterface $passwordHasherTranslatorInterface $translatorEntityManagerInterface $em): JsonResponse
  398.     {
  399.         $data $request->request->all();
  400.         if (empty($data)) {
  401.             return new JsonResponse([
  402.                 'error' => true,
  403.                 'message' => $translator->trans('profile.error_old_password_empty'),
  404.             ], 400);
  405.         }
  406.         $form $this->createForm(ChangePasswordType::class, null, [
  407.             'is_api' => true,
  408.         ]);
  409.         $expectedFields = ['email''old_password''new_password''confirm_password'];
  410.         $data array_intersect_key($dataarray_flip($expectedFields));
  411.         $form->submit($data);
  412.         if (!$form->isValid()) {
  413.             $errors $form->getErrors(true);
  414.             $fieldErrors = [];
  415.             foreach ($errors as $error) {
  416.                 $field $error->getOrigin()->getName();
  417.                 $message $error->getMessage();
  418.                 $fieldErrors[$field][] = $message;
  419.             }
  420.             return new JsonResponse([
  421.                 'error' => true,
  422.                 'field' => $field,
  423.                 'message' => $message,
  424.                 //'message' => $this->translator->trans('registration.invalid_data'),
  425.                 'field_errors' => $fieldErrors,
  426.             ], 400);
  427.         }
  428.         $email $data['email'] ?? null;
  429.         $oldPassword $data['old_password'] ?? null;
  430.         $newPassword $data['new_password'] ?? null;
  431.         $user $usersRepository->findOneBy(['mail' => $email]);
  432.         if (!$user) {
  433.             return new JsonResponse([
  434.                 'error' => true,
  435.                 'message' => $translator->trans('profile.error_email'),
  436.             ], 400);
  437.         }
  438.         if (!$passwordHasher->isPasswordValid($user$oldPassword)) {
  439.             return new JsonResponse([
  440.                 'error' => true,
  441.                 'message' => $translator->trans('profile.error_old_password_invalid'),
  442.             ], 400);
  443.         }
  444.         $hashedPassword $passwordHasher->hashPassword($user$newPassword);
  445.         $user->setPassword($hashedPassword);
  446.         $recovery $userRecoveryRepository->findOneBy(
  447.             ['userId' => $user->getId(), 'used' => null],
  448.             ['date' => 'DESC']
  449.         );
  450.         if (!$recovery) {
  451.             //$hash = hash('ripemd128', $user->getId() . $user->getLogin() . $user->getMail() . time() . $_ENV['APP_SECRET']);
  452.             $hash UserTokensRepository::genToken($user);
  453.             $recovery = new UserRecovery();
  454.             $recovery->setUser($user);
  455.             $recovery->setHash($hash);
  456.             $recovery->setIp($request->getClientIp() ?? '0');
  457.             $recovery->setUseragent($request->headers->get('User-Agent') ?? 'unknown');
  458.             $recovery->setDate(new \DateTime());
  459.             $em->persist($recovery);
  460.         }
  461.         $em->flush();
  462.         $response = new JsonResponse([
  463.             'error' => false,
  464.             'message' => $translator->trans('profile.password_changed_success'),
  465.         ], 200);
  466.         $response->headers->set('X-Access-Login'$user->getLogin());
  467.         $response->headers->set('X-Access-Email'$user->getEmail());
  468.         $response->headers->set('X-Access-Token'$recovery->getHash());
  469.         $response->headers->set('X-Access-Avatar'$user->getImageAvatar());
  470.         return $response;
  471.     }
  472.     /*
  473.     #[Route(path: '/acc/changepassword2', name: 'acc_change_password2')]
  474.     public function changePassword2(UsersRepository $usersRepository, Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator): Response
  475.     {
  476.         $user = $this->getUser();
  477.         $isJsonRequest = $request->headers->get('Accept') === 'application/json';
  478.         if ($request->isMethod('POST')) {
  479.             $email = $request->request->get('email', false);
  480.             $oldPassword = $request->request->get('old_password', false);
  481.             if ($email && $oldPassword) {
  482.                 $userCheck = $usersRepository->findOneBy(['mail' => $email]);
  483.                 if (!$userCheck) {
  484.                     $this->addFlash('error', $translator->trans('profile.error_email'));
  485.                     if ($isJsonRequest) return $this->jsonResultFromFlash(400);
  486.                 }
  487.                 if ($passwordHasher->isPasswordValid($userCheck, $oldPassword)) $user = $userCheck;
  488.                 else {
  489.                     $this->addFlash('error', $translator->trans('profile.error_old_password_invalid'));
  490.                     if ($isJsonRequest) return $this->jsonResultFromFlash(400);
  491.                 }
  492.                 unset($userCheck);
  493.             }
  494.         }
  495.         if (!$user) {
  496.             $this->addFlash('error', $translator->trans('profile.error_auth_required'));
  497.             if ($isJsonRequest) return $this->jsonResultFromFlash(401);
  498.             return $this->redirectToRoute('acc_login');
  499.         }
  500.         $currentLocale = $request->getLocale();
  501.         if ($request->isMethod('POST')) {
  502.             $newPassword = $request->request->get('new_password');
  503.             $confirmPassword = $request->request->get('new_password');
  504.             if (!$oldPassword) {
  505.                 $this->addFlash('error', $translator->trans('profile.error_old_password_empty'));
  506.             }
  507.             elseif ($user && !$passwordHasher->isPasswordValid($user, $oldPassword)) {
  508.                 $this->addFlash('error', $translator->trans('profile.error_old_password_invalid'));
  509.             }
  510.             elseif (!preg_match('/^(?=.*[A-Z])(?=.*\d)[A-Za-z\d!@#$%^&*()_+]{8,}$/', $newPassword)) {
  511.                 $this->addFlash('error', $translator->trans('profile.error_password_rules'));
  512.             }
  513.             elseif (!$newPassword || !$confirmPassword) {
  514.                 $this->addFlash('error', $translator->trans('profile.error_new_password_empty'));
  515.             }
  516.             elseif ($newPassword !== $confirmPassword) {
  517.                 $this->addFlash('error', $translator->trans('profile.error_password_mismatch'));
  518.             }
  519.             else {
  520.                 $hashedPassword = $passwordHasher->hashPassword($user, $newPassword);
  521.                 $user->setPassword($hashedPassword);
  522.                 $entityManager = $this->getDoctrine()->getManager();
  523.                 $entityManager->flush();
  524.                 $this->addFlash('success', $translator->trans('profile.password_changed_success'));
  525.                 if ($isJsonRequest) {
  526.                     return $this->jsonResultFromFlash(200);
  527.                 }
  528.                 return $this->redirectToRoute('acc_change_password');
  529.             }
  530.         }
  531.         if ($isJsonRequest) {
  532.             return $this->jsonResultFromFlash();
  533.         }
  534.         return $this->render('profile/change_password.html.twig', [
  535.             'user' => $user,
  536.             'currentLocale' => $currentLocale,
  537.         ]);
  538.     }
  539.     */
  540.     #[Route(path'/acc/forgot'name'acc_forgot'methods: ['GET''POST'])]
  541.     public function forgot(Request $requestUsersRepository $usersRepositoryUserRecoveryRepository $userRecoveryRepositoryUrlGeneratorInterface $urlGeneratorMailerInterface $mailer): Response
  542.     {
  543.         /** @var $user \App\Entity\Accounts\Users */
  544.         $user $this->getUser();
  545.         if ($user instanceof UserInterface) {
  546.             return $this->redirectToRoute('acc_profile');
  547.         }
  548.         $currentLocale $request->getLocale();
  549.         if ($request->isMethod('GET')) {
  550.             $last_email $request->getSession()->get(Security::LAST_USERNAME'');
  551.             return $this->render('profile/forgot.html.twig', [
  552.                 'last_email' => $last_email,
  553.                 'title' => $this->translator->trans('profile.forgot.title', [], 'messages'$currentLocale),
  554.             ]);
  555.         }
  556.         $email $request->request->get('email');
  557.         if (!$email) {
  558.             $this->addFlash('error'$this->translator->trans('profile.forgot.email_required'));
  559.             return $this->redirectToRoute('acc_forgot');
  560.         }
  561.         if (!filter_var($emailFILTER_VALIDATE_EMAIL)) {
  562.             $this->addFlash('error'$this->translator->trans('profile.form.email_error'));
  563.             return $this->redirectToRoute('acc_forgot');
  564.         }
  565.         $user $usersRepository->findOneBy(['mail' => $email]);
  566.         if (!$user) {
  567.             $this->addFlash('error'$this->translator->trans('profile.forgot.user_not_found'));
  568.             return $this->redirectToRoute('acc_forgot');
  569.         }
  570.         $recovery $userRecoveryRepository->findOneBy(
  571.             ['userId' => $user->getId(), 'used' => null],
  572.             ['date' => 'DESC']
  573.         );
  574.         if (!$recovery || $recovery->getDate() < (new \DateTime('-1 day'))) {
  575.             //$hash = hash('ripemd128', $user->getId() . $user->getLogin() . $user->getMail() . time() . $_ENV['APP_SECRET']);
  576.             $hash UserTokensRepository::genToken($user);
  577.             $recovery = new UserRecovery();
  578.             $recovery->setUser($user);
  579.             //$recovery->setUserId($user->getId());
  580.             $recovery->setHash($hash);
  581.             $recovery->setIp($request->getClientIp() ?? '0');
  582.             $recovery->setUseragent($request->headers->get('User-Agent') ?? 'unknown');
  583.             $recovery->setDate(new \DateTime());
  584.             $this->em->persist($recovery);
  585.             $this->em->flush();
  586.         } else {
  587.             $this->addFlash('success'$this->translator->trans('profile.forgot.password_reset_email_sent'));
  588.             return $this->redirectToRoute('acc_forgot');
  589.         }
  590.         $this->sendEmail(false$user$recovery$urlGenerator$mailer);
  591.         $this->addFlash('success'$this->translator->trans('profile.forgot.password_reset_email_sent'));
  592.         return $this->redirectToRoute('acc_forgot');
  593.     }
  594.     #[Route(path'/acc/api/forgot'name'acc_api_forgot'methods: ['POST'])]
  595.     public function forgotPassword(Request $requestUsersRepository $usersRepositoryUserRecoveryRepository $userRecoveryRepositoryUrlGeneratorInterface $urlGeneratorMailerInterface $mailer): JsonResponse
  596.     {
  597.         /** @var $user \App\Entity\Accounts\Users */
  598.         $user $this->getUser();
  599.         if ($user instanceof UserInterface) {
  600.             return new JsonResponse([
  601.                 'error' => true,
  602.                 'message' => $this->translator->trans('profile.you_auth'),
  603.             ], 400);
  604.         }
  605.         $email $request->request->get('email');
  606.         if (!$email || !filter_var($emailFILTER_VALIDATE_EMAIL)) {
  607.             return new JsonResponse([
  608.                 'error' => true,
  609.                 'message' => $this->translator->trans('profile.forgot.invalid_email'),
  610.             ], 400);
  611.         }
  612.         $user $usersRepository->findOneBy(['mail' => $email]);
  613.         if (!$user) {
  614.             return new JsonResponse([
  615.                 'error' => true,
  616.                 'message' => $this->translator->trans('profile.forgot.user_not_found'),
  617.             ], 404);
  618.         }
  619.         $recovery $userRecoveryRepository->findOneBy(
  620.             ['userId' => $user->getId(), 'used' => null],
  621.             ['date' => 'DESC']
  622.         );
  623.         if (!$recovery || $recovery->getDate() < (new \DateTime('-1 day'))) {
  624.             //$hash = hash('ripemd128', $user->getId() . $user->getLogin() . $user->getMail() . time() . $_ENV['APP_SECRET']);
  625.             $hash UserTokensRepository::genToken($user);
  626.             $recovery = new UserRecovery();
  627.             $recovery->setUser($user);
  628.             $recovery->setHash($hash);
  629.             $recovery->setIp($request->getClientIp() ?? '0');
  630.             $recovery->setUseragent($request->headers->get('User-Agent') ?? 'unknown');
  631.             $recovery->setDate(new \DateTime());
  632.             $this->em->persist($recovery);
  633.             $this->em->flush();
  634.         } else {
  635.             return new JsonResponse([
  636.                 'error' => false,
  637.                 'message' => $this->translator->trans('profile.forgot.password_reset_email_sent'),
  638.             ], 200);
  639.         }
  640.         $this->sendEmail(true$user$recovery$urlGenerator$mailer);
  641.         $response = new JsonResponse([
  642.             'error' => false,
  643.             'message' => $this->translator->trans('profile.forgot.password_reset_email_sent'),
  644.         ], 200);
  645.         $response->headers->set('X-Access-Login'$user->getLogin());
  646.         $response->headers->set('X-Access-Email'$user->getMail());
  647.         $response->headers->set('X-Access-Token'$hash);
  648.         $response->headers->set('X-Access-Avatar'$user->getImageAvatar());
  649.         return $response;
  650.     }
  651.     private function sendEmail($api falseUsers $userUserRecovery $recoveryUrlGeneratorInterface $urlGeneratorMailerInterface $mailer): void
  652.     {
  653.         $html $this->renderView('profile/forgot_email.html.twig', [
  654.             'login' => $user->getLogin()??$user->getName(),
  655.             'forgotLink' => $urlGenerator->generate($api'acc_api_recovery''acc_recovery', [
  656.                 'autoaction' => 'change-password',
  657.                 'email' => $user->getMail(),
  658.                 'token' => $recovery->getHash(),
  659.                 'utm_source'=>'otpusk-mailings',
  660.                 'utm_medium'=>'email',
  661.                 'utm_campaign'=>'recovery',
  662.                 'utm_content'=>'otpusk',
  663.             ], UrlGeneratorInterface::ABSOLUTE_URL
  664.             ),
  665.             'confirm_code' => $recovery->getHash(),
  666.             'year' => date('Y'),
  667.         ]);
  668.         $email = (new Email())
  669.             ->from('no-reply@otpusk.com')
  670.             ->to($user->getMail())
  671.             ->subject($this->translator->trans('profile.forgot.title'))
  672.             ->html($html);
  673.         $returnPathAddress = new Address(sprintf('%d@return.otpusk.com'$user->getId()));
  674.         $email->getHeaders()->add(new PathHeader('Return-Path'$returnPathAddress));
  675.         if ($this->getParameter('kernel.environment') !== 'prod') {
  676.             // зберігаємо письма локально
  677.             $mimeMessage $email->toString();
  678.             $directory $this->getParameter('kernel.project_dir') . '/var/mails';
  679.             if (!is_dir($directory)) {
  680.                 mkdir($directory0775true);
  681.             }
  682.             $filePath $directory '/' date('YmdHis') . '_' uniqid('email_'true) . '.eml';
  683.             file_put_contents($filePath$mimeMessage);
  684.         } else {
  685.             $mailer->send($email);
  686.         }
  687.     }
  688.     #[Route(path'/acc/recovery'name'acc_recovery'methods: ['GET''POST'])]
  689.     public function recovery(Request $requestUserRecoveryRepository $userRecoveryRepositoryUsersRepository $usersRepositoryEntityManagerInterface $emTranslatorInterface $translator): Response
  690.     {
  691.         /** @var $user \App\Entity\Accounts\Users */
  692.         $user $this->getUser();
  693.         if ($user instanceof UserInterface) {
  694.             return $this->redirectToRoute('acc_profile');
  695.         }
  696.         $form $this->createForm(PasswordRecoveryType::class,  [
  697.             'email' => $request->query->get('email'''),
  698.             'token' => $request->query->get('token'''),
  699.         ], [
  700.             'is_api' => false,
  701.         ]);
  702.         $form->handleRequest($request);
  703.         if ($form->isSubmitted() && $form->isValid()) {
  704.             $data $form->getData();
  705.             if ($data['password'] !== $data['confirm_password']) {
  706.                 $this->addFlash('error'$translator->trans('profile.error_password_mismatch'));
  707.             } else {
  708.                 $recovery $userRecoveryRepository->findOneBy([
  709.                         'hash' => $data['token'],
  710.                         //'used' => null,
  711.                     ]
  712.                 );
  713.                 if (!$recovery) {
  714.                     $this->addFlash('error'$translator->trans('profile.recovery.token_invalid'));
  715.                 } else {
  716.                     $user $usersRepository->find($recovery->getUserId());
  717.                     if (!$user) {
  718.                         $this->addFlash('error'$translator->trans('profile.forgot.user_not_found'));
  719.                     } else {
  720.                         $user->setPassword(password_hash($data['password'], PASSWORD_BCRYPT));
  721.                         $em->persist($user);
  722.                         $recovery->setUsed(new \DateTime());
  723.                         $em->persist($recovery);
  724.                         $em->flush();
  725.                         $this->addFlash('success'$translator->trans('profile.password_changed_success'));
  726.                         $token = new UsernamePasswordToken($usernull$user->getRoles());
  727.                         $this->tokenStorage->setToken($token);
  728.                         return $this->redirectToRoute('acc_recovery', [
  729.                             'email' => $data['email'] ?? '',
  730.                             'token' => $data['token'] ?? '',
  731.                         ]);
  732.                     }
  733.                 }
  734.             }
  735.         }
  736.         return $this->render('profile/recovery.html.twig', [
  737.             'form' => $form->createView(),
  738.         ]);
  739.     }
  740.     #[Route(path'/acc/api/recovery'name'acc_api_recovery'methods: ['POST'])]
  741.     public function apiRecovery(Request $requestUserRecoveryRepository $userRecoveryRepositoryUsersRepository $usersRepositoryEntityManagerInterface $emTranslatorInterface $translator): JsonResponse
  742.     {
  743.         /** @var $user \App\Entity\Accounts\Users */
  744.         $user $this->getUser();
  745.         if ($user instanceof UserInterface) {
  746.             return new JsonResponse([
  747.                 'error' => true,
  748.                 'message' => $this->translator->trans('profile.you_auth'),
  749.             ], 400);
  750.         }
  751.         $form $this->createForm(PasswordRecoveryType::class, null, [
  752.             'is_api' => true,
  753.         ]);
  754.         $data $request->request->all();
  755.         $data['password'] = base64_decode($data['password']);
  756.         $data['confirm_password'] = base64_decode($data['confirm_password']);
  757.         $form->submit($data);
  758.         if (!$form->isValid()) {
  759.             $errors $form->getErrors(true);
  760.             $fieldErrors = [];
  761.             foreach ($errors as $error) {
  762.                 $field $error->getOrigin()->getName();
  763.                 $message $error->getMessage();
  764.                 $fieldErrors[$field][] = $message;
  765.             }
  766.             return new JsonResponse([
  767.                 'error' => true,
  768.                 'field' => $field,
  769.                 'message' => $message,
  770.                 //'message' => $this->translator->trans('registration.invalid_data'),
  771.                 'field_errors' => $fieldErrors,
  772.             ], 400);
  773.         }
  774.         $data $form->getData();
  775.         if ($data['password'] !== $data['confirm_password']) {
  776.             return new JsonResponse([
  777.                 'error' => true,
  778.                 'data' => $data,
  779.                 'message' => $this->translator->trans('profile.error_password_mismatch'),
  780.             ], 400);
  781.         }
  782.         $recovery $userRecoveryRepository->findOneBy([
  783.             'hash' => $data['token'],
  784.             //'used' => null,
  785.         ]);
  786.         if (!$recovery) {
  787.             return new JsonResponse([
  788.                 'error' => true,
  789.                 'message' => $this->translator->trans('profile.user_not_found'),
  790.             ], 404);
  791.         }
  792.         $user $usersRepository->find($recovery->getUserId());
  793.         if (!$user) {
  794.             return new JsonResponse([
  795.                 'error' => true,
  796.                 'message' => $this->translator->trans('profile.user_not_found'),
  797.             ], 404);
  798.         }
  799.         $user->setPassword(password_hash($data['password'], PASSWORD_BCRYPT));
  800.         $this->em->persist($user);
  801.         $recovery->setUsed(new \DateTime());
  802.         $this->em->persist($recovery);
  803.         $em->flush();
  804.         $token = new UsernamePasswordToken($usernull$user->getRoles());
  805.         $this->tokenStorage->setToken($token);
  806.         $response = new JsonResponse([
  807.             'error' => false,
  808.             'message' => $translator->trans('profile.password_changed_success'),
  809.         ], 200);
  810.         $response->headers->set('X-Access-Login'$user->getLogin());
  811.         $response->headers->set('X-Access-Email'$user->getMail());
  812.         $response->headers->set('X-Access-Token'$recovery->getHash());
  813.         $response->headers->set('X-Access-Avatar'$user->getImageAvatar());
  814.         return $response;
  815.     }
  816.     #[Route(path'/acc/delete'name'acc_delete_account')]
  817.     public function deleteAccount(Request $requestTranslatorInterface $translator): Response
  818.     {
  819.         /** @var \App\Entity\Accounts\Users $user */
  820.         $user $this->getUser();
  821.         if (!$user) {
  822.             $this->addFlash('error''profile.error_auth_required');
  823.             return $this->redirectToRoute('acc_login');
  824.         }
  825.         $currentLocale $request->getLocale();
  826.         // на деві не вимикаємо користувачів
  827.         if ($this->getParameter('kernel.environment') === 'prod') {
  828.             $user->setActive('no');
  829.             $entityManager $this->getDoctrine()->getManager();
  830.             $entityManager->flush();
  831.         }
  832.         $this->container->get('security.token_storage')->setToken(null);
  833.         //$request->getSession()->invalidate(); // не треба прибирати сесію
  834.         return $this->render('profile/delete_account.html.twig', [
  835.             'user' => false,
  836.             'currentLocale' => $currentLocale,
  837.         ]);
  838.     }
  839.     private function jsonResultFromFlash(int $httpCode 400): JsonResponse
  840.     {
  841.         $errors $this->get('session')->getFlashBag()->get('error', []);
  842.         $success $this->get('session')->getFlashBag()->get('success', []);
  843.         if (!empty($errors)) {
  844.             return new JsonResponse([
  845.                 'error' => true,
  846.                 'message' => implode(PHP_EOL$errors),
  847.             ], $httpCode);
  848.         }
  849.         if (!empty($success)) {
  850.             return new JsonResponse([
  851.                 'error' => false,
  852.                 'message' => implode(PHP_EOL$success),
  853.             ], 200);
  854.         }
  855.         return new JsonResponse([
  856.             'error' => false,
  857.             'message' => '',
  858.         ], 200);
  859.     }
  860. }