<?php
namespace App\Controller;
use App\Entity\Accounts\Users;
use App\Entity\Accounts\UserRecovery;
use App\Repository\Accounts\UsersRepository;
use App\Repository\Accounts\UserRecoveryRepository;
use App\Form\ChangePasswordType;
use App\Form\PasswordRecoveryType;
use App\Repository\Accounts\UserTokensRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Header\PathHeader;
class ProfileController extends AbstractController
{
private EntityManagerInterface $em;
private TranslatorInterface $translator;
private KernelInterface $kernel;
private TokenStorageInterface $tokenStorage;
public function __construct(EntityManagerInterface $em, TranslatorInterface $translator, KernelInterface $kernel, TokenStorageInterface $tokenStorage)
{
$this->em = $em;
$this->translator = $translator;
$this->kernel = $kernel;
$this->tokenStorage = $tokenStorage;
}
#[Route(path: '/acc/profile', name: 'acc_profile')]
public function profile(Request $request, UsersRepository $usersRepository): Response
{
/** @var $user \App\Entity\Accounts\Users */
$user = $this->getUser();
if (!$user instanceof UserInterface) {
return new RedirectResponse($this->generateUrl('acc_login'));
}
$currentLocale = $request->getLocale(); // Або $this->translator->getLocale();
$avatar = $request->files->get('avatar');
if ($avatar) {
$mimeType = $avatar->getMimeType();
if (in_array($mimeType, ['image/jpeg', 'image/png'])) {
$ch = curl_init('https://www.turpravda.com/acc/profile/avatar?json=1');
//curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
//curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
//curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, 'otpusk.com');
$base64Image = 'data:'. $avatar->getMimeType() .';base64,' . base64_encode(file_get_contents($avatar->getPathname()));
$data = [
'avatar' => $base64Image,
'image' => new \CURLFile($avatar->getPathname(), $avatar->getMimeType(), $avatar->getClientOriginalName()),
'token' => 'C04JG_9ECNG_ZF2SR_GE86G_0F7ZM',
'user_id' => $user->getId(),
'json' => 1,
];
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$json = json_decode($response,1);
if ($this->kernel->getEnvironment() !== 'prod') {
$user->setImage($user->getImageAvatar());
//$imageInfo = getimagesize($avatar->getPathname());
//if ($imageInfo !== false) $user->setImageSize($imageInfo[0] . 'x' . $imageInfo[1]);
$this->em->persist($user);
$this->em->flush();
}
if (curl_errno($ch) || $code!=200 || $json['error']==true) {
$this->addFlash('error', $this->translator->trans('profile.upload_failed'));
} else {
$this->addFlash('success', $this->translator->trans('profile.upload_successful'));
}
} else {
$this->addFlash('error', $this->translator->trans('profile.invalid_image_format'));
}
}
if ($request->isMethod('POST')) {
$action = $request->request->get('action', 'edit');
if ($action == 'delete_avatar') {
$user->setImage('');
$user->setImageSize('');
$this->em->persist($user);
$this->em->flush();
$this->addFlash('success', $this->translator->trans('profile.delete_avatar'));
} else {
$name = $request->request->get('name');
$surname = $request->request->get('surname');
$middleName = $request->request->get('middleName');
$address = $request->request->get('address');
$birthday = $request->request->get('birthday', false);
$sex = $request->request->get('sex');
$email = $request->request->get('email');
$phone = $request->request->get('phone');
$phone2 = $request->request->get('phone2');
if ($user instanceof Users) {
$user->setName($name . ($surname ? ' ' . $surname : '') . ($middleName ? ' ' . $middleName : ''));
$user->setText($address);
//$user->setSurname($surname);
//$user->setMiddleName($middleName);
//$user->setAddress($address);
if ($birthday) $user->setBirthDay(new \DateTime($birthday));
else $user->setBirthDay(null);
$user->setSex($sex);
$user->setMail($email);
//$user->setPhone($phone);
if ($phone2 && $phone!=$phone2) $user->setPhoneAdd($phone2);
$this->em->persist($user);
$this->em->flush();
$usersRepository->getCrmUserId($user->getId(), $user->getMail(), $user->getPhone());
$this->addFlash('success', $this->translator->trans('profile.update_successful'));
}
}
return new RedirectResponse($this->generateUrl('acc_profile'));
}
list($name, $surname, $middleName) = array_pad(explode(' ', $user->getName(), 3), 3, null);
list($phone, $phone2) = array_pad(explode(',', $user->getPhone(), 2), 2, null);
return $this->render('profile/index.html.twig', [
'user' => $user,
'currentLocale' => $currentLocale,
'name' => $name??'',
'surname' => $surname??'',
'middleName' => $middleName??'',
'phone' => $user->getPhone()??'',
'phoneNoVerifySms' => $user->getPhoneSms()!='verify',
'phone2' => $user->getPhoneAdd()??$phone2,
'phone2NoVerifySms' => $user->getPhoneAddSms()!='verify',
]);
}
#[Route(path: '/acc/api/profile', name: 'acc_api_profile_get', methods: ['GET'])]
public function apiProfileGet(Request $request, TranslatorInterface $translator): JsonResponse
{
/** @var Users|null $user */
$user = $this->getUser();
if (!$user instanceof UserInterface) {
return new JsonResponse([
'error' => true,
'message' => $translator->trans('verify.user_not_authorized')
], 401);
}
return new JsonResponse([
'success' => true,
'user' => [
'id' => $user->getId(),
'name' => $user->getName(),
'email' => $user->getEmail(),
'phone' => $user->getPhone(),
'phoneVerifySms' => $user->getPhone()&&$user->getPhoneSms()=='verify',
'phone2' => $user->getPhoneAdd(),
'phone2VerifySms' => $user->getPhoneAdd()&&$user->getPhoneAddSms()=='verify',
'sex' => $user->getSex(),
'birthday' => $user->getBirthDay()?->format('Y-m-d'),
'address' => $user->getText(),
],
], 200);
}
#[Route(path: '/acc/api/profile', name: 'acc_api_profile', methods: ['POST'])]
public function apiProfile(Request $request, TranslatorInterface $translator, UsersRepository $usersRepository): JsonResponse
{
/** @var Users|null $user */
$user = $this->getUser();
if (!$user instanceof UserInterface) {
return new JsonResponse([
'error' => true,
'message' => $translator->trans('verify.user_not_authorized')
], 401);
}
$data = json_decode($request->getContent(), true);
if (is_array($data)) {
$name = $data['name'] ?? false;
$address = $data['address'] ?? false;
$birthday = $data['birthday'] ?? false;
$sex = $data['sex'] ?? false;
$email = $data['email'] ?? false;
$phone = $data['phone'] ?? false;
} else {
$name = $request->request->get('name', false);
$address = $request->request->get('address', false);
$birthday = $request->request->get('birthday', false);
$sex = $request->request->get('sex', false);
$email = $request->request->get('email', false);
$phone = $request->request->get('phone', false);
}
if (!$name) {
return new JsonResponse([
'error' => true,
'message' => $translator->trans('profile.form.name_empty'),
], 400);
}
$user->setName($name);
if (!$email) {
return new JsonResponse([
'error' => true,
'message' => $translator->trans('profile.form.email_empty'),
], 400);
}
$existingUser = $this->em->getRepository(Users::class)->findOneBy(['mail' => $email]);
if ($existingUser && $existingUser->getId() !== $user->getId()) {
return new JsonResponse([
'error' => true,
'message' => $translator->trans('profile.form.email_in_use'),
], 400);
}
$user->setMail($email);
if ($address) $user->setText($address);
if ($birthday) $user->setBirthDay(new \DateTime($birthday));
if ($sex) $user->setSex($sex);
if (!$phone) {
return new JsonResponse([
'error' => true,
'message' => $translator->trans('profile.form.phone_empty'),
], 400);
}
$user->setPhone($phone);
try {
$this->em->persist($user);
$this->em->flush();
$usersRepository->saveCrmUserProfile($user->getId(), $user->getMail(), $user->getPhone());
} catch (\Exception $e) {
return new JsonResponse([
'error' => true,
'message' => $e->getMessage(),
], 400);
}
return new JsonResponse([
'success' => true,
'user' => [
'id'=>$user->getId(),
'name'=>$user->getName(),
'email'=>$user->getEmail(),
'phone'=>$user->getPhone(),
],
'message' => $translator->trans('profile.update_successful'),
], 200);
}
#[Route(path: '/acc/api/sendsms', name: 'acc_api_sendsms', methods: ['POST'])]
public function sendSms(Request $request): JsonResponse
{
/** @var Users|null $user */
$user = $this->getUser();
if (!$user instanceof Users) {
return new JsonResponse([
'error' => true,
'message' => 'User not authorized',
], 401);
}
$phone = $request->request->get('phone');
$phone2 = $request->request->get('phone2');
if ($phone2) $phone = $phone2;
if (!$phone) {
return new JsonResponse([
'error' => true,
'message' => 'Phone number is required',
], 400);
}
if ($phone2 && $user->getPhone() == $phone2 && $user->getPhoneAddSms()=='verify') {
return new JsonResponse([
'error' => false,
'message' => 'SMS sent successfully',
], 200);
} elseif ($user->getPhone() == $phone && $user->getPhoneSms()=='verify'){
return new JsonResponse([
'error' => false,
'message' => 'SMS sent successfully',
], 200);
}
//$user->setPhone($phone);
$code = random_int(100000, 999999);
if ($this->kernel->getEnvironment() === 'dev') {
$code = 123456;
}
if ($phone2) $user->setPhoneAddSms($code);
else $user->setPhoneSms($code);
try {
$this->em->persist($user);
$this->em->flush();
} catch (\Exception $e) {
return new JsonResponse([
'error' => true,
'message' => 'Failed to save verification code',
], 500);
}
if ($this->kernel->getEnvironment() !== 'dev') {
$this->messengersRepository->addNew($phone, 'Ваш код: ' . $code);
}
return new JsonResponse([
'error' => false,
'message' => 'SMS sent successfully',
], 200);
}
#[Route(path: '/acc/api/verifysms', name: 'acc_api_verifysms', methods: ['POST'])]
public function verifySms(Request $request, UsersRepository $usersRepository): JsonResponse
{
$phone = $request->request->get('phone');
$phone2 = $request->request->get('phone2');
$code = $request->request->get('code');
if ($phone2) $phone = $phone2;
if (!$phone || !$code) {
return new JsonResponse([
'error' => true,
'message' => 'Phone number and code are required',
], 400);
}
/** @var Users|null $user */
$user = $this->getUser();
if (!$user instanceof Users/* || $user->getPhone() !== $phone)*/) {
return new JsonResponse([
'error' => true,
'message' => 'User not authorized or phone number mismatch',
], 401);
}
if ($phone2){
if ($user->getPhoneAddSms() !== $code) {
return new JsonResponse([
'error' => true,
'message' => 'Invalid verification code',
], 400);
}
} elseif ($user->getPhoneSms() !== $code) {
return new JsonResponse([
'error' => true,
'message' => 'Invalid verification code',
], 400);
}
if ($phone2) {
$user->setPhoneAdd($phone2);
$user->setPhoneAddSms('verify');
} else {
$user->setPhone($phone);
$user->setPhoneSms('verify');
}
try {
$this->em->persist($user);
$this->em->flush();
} catch (\Exception $e) {
return new JsonResponse([
'error' => true,
'message' => 'Failed to save phone',
], 500);
}
return new JsonResponse([
'error' => false,
'message' => 'Verification successful',
], 200);
}
#[Route(path: '/acc/change-password', name: 'acc_change_password')]
public function changePassword(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, EntityManagerInterface $em, FormFactoryInterface $formFactory): Response
{
/** @var \App\Entity\Accounts\Users $user */
$user = $this->getUser();
if (!$user) {
$this->addFlash('error', $translator->trans('profile.error_auth_required'));
return $this->redirectToRoute('acc_login');
}
$form = $formFactory->create(ChangePasswordType::class, null, [
'email' => $user->getMail(),
'is_api' => false,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
if (!$passwordHasher->isPasswordValid($user, $data['old_password'])) {
$this->addFlash('error', $translator->trans('profile.error_old_password_invalid'));
} else {
$hashedPassword = $passwordHasher->hashPassword($user, $data['new_password']);
$user->setPassword($hashedPassword);
$em->flush();
$this->addFlash('success', $translator->trans('profile.password_changed_success'));
return $this->redirectToRoute('acc_change_password');
}
}
return $this->render('profile/change_password.html.twig', [
'form' => $form->createView(),
'user' => $user,
]);
}
#[Route(path: '/acc/api/change-password', name: 'acc_api_change_password', methods: ['POST'])]
public function apiChangePassword(Request $request, UsersRepository $usersRepository, UserRecoveryRepository $userRecoveryRepository, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, EntityManagerInterface $em): JsonResponse
{
$data = $request->request->all();
if (empty($data)) {
return new JsonResponse([
'error' => true,
'message' => $translator->trans('profile.error_old_password_empty'),
], 400);
}
$form = $this->createForm(ChangePasswordType::class, null, [
'is_api' => true,
]);
$expectedFields = ['email', 'old_password', 'new_password', 'confirm_password'];
$data = array_intersect_key($data, array_flip($expectedFields));
$form->submit($data);
if (!$form->isValid()) {
$errors = $form->getErrors(true);
$fieldErrors = [];
foreach ($errors as $error) {
$field = $error->getOrigin()->getName();
$message = $error->getMessage();
$fieldErrors[$field][] = $message;
}
return new JsonResponse([
'error' => true,
'field' => $field,
'message' => $message,
//'message' => $this->translator->trans('registration.invalid_data'),
'field_errors' => $fieldErrors,
], 400);
}
$email = $data['email'] ?? null;
$oldPassword = $data['old_password'] ?? null;
$newPassword = $data['new_password'] ?? null;
$user = $usersRepository->findOneBy(['mail' => $email]);
if (!$user) {
return new JsonResponse([
'error' => true,
'message' => $translator->trans('profile.error_email'),
], 400);
}
if (!$passwordHasher->isPasswordValid($user, $oldPassword)) {
return new JsonResponse([
'error' => true,
'message' => $translator->trans('profile.error_old_password_invalid'),
], 400);
}
$hashedPassword = $passwordHasher->hashPassword($user, $newPassword);
$user->setPassword($hashedPassword);
$recovery = $userRecoveryRepository->findOneBy(
['userId' => $user->getId(), 'used' => null],
['date' => 'DESC']
);
if (!$recovery) {
//$hash = hash('ripemd128', $user->getId() . $user->getLogin() . $user->getMail() . time() . $_ENV['APP_SECRET']);
$hash = UserTokensRepository::genToken($user);
$recovery = new UserRecovery();
$recovery->setUser($user);
$recovery->setHash($hash);
$recovery->setIp($request->getClientIp() ?? '0');
$recovery->setUseragent($request->headers->get('User-Agent') ?? 'unknown');
$recovery->setDate(new \DateTime());
$em->persist($recovery);
}
$em->flush();
$response = new JsonResponse([
'error' => false,
'message' => $translator->trans('profile.password_changed_success'),
], 200);
$response->headers->set('X-Access-Login', $user->getLogin());
$response->headers->set('X-Access-Email', $user->getEmail());
$response->headers->set('X-Access-Token', $recovery->getHash());
$response->headers->set('X-Access-Avatar', $user->getImageAvatar());
return $response;
}
/*
#[Route(path: '/acc/changepassword2', name: 'acc_change_password2')]
public function changePassword2(UsersRepository $usersRepository, Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator): Response
{
$user = $this->getUser();
$isJsonRequest = $request->headers->get('Accept') === 'application/json';
if ($request->isMethod('POST')) {
$email = $request->request->get('email', false);
$oldPassword = $request->request->get('old_password', false);
if ($email && $oldPassword) {
$userCheck = $usersRepository->findOneBy(['mail' => $email]);
if (!$userCheck) {
$this->addFlash('error', $translator->trans('profile.error_email'));
if ($isJsonRequest) return $this->jsonResultFromFlash(400);
}
if ($passwordHasher->isPasswordValid($userCheck, $oldPassword)) $user = $userCheck;
else {
$this->addFlash('error', $translator->trans('profile.error_old_password_invalid'));
if ($isJsonRequest) return $this->jsonResultFromFlash(400);
}
unset($userCheck);
}
}
if (!$user) {
$this->addFlash('error', $translator->trans('profile.error_auth_required'));
if ($isJsonRequest) return $this->jsonResultFromFlash(401);
return $this->redirectToRoute('acc_login');
}
$currentLocale = $request->getLocale();
if ($request->isMethod('POST')) {
$newPassword = $request->request->get('new_password');
$confirmPassword = $request->request->get('new_password');
if (!$oldPassword) {
$this->addFlash('error', $translator->trans('profile.error_old_password_empty'));
}
elseif ($user && !$passwordHasher->isPasswordValid($user, $oldPassword)) {
$this->addFlash('error', $translator->trans('profile.error_old_password_invalid'));
}
elseif (!preg_match('/^(?=.*[A-Z])(?=.*\d)[A-Za-z\d!@#$%^&*()_+]{8,}$/', $newPassword)) {
$this->addFlash('error', $translator->trans('profile.error_password_rules'));
}
elseif (!$newPassword || !$confirmPassword) {
$this->addFlash('error', $translator->trans('profile.error_new_password_empty'));
}
elseif ($newPassword !== $confirmPassword) {
$this->addFlash('error', $translator->trans('profile.error_password_mismatch'));
}
else {
$hashedPassword = $passwordHasher->hashPassword($user, $newPassword);
$user->setPassword($hashedPassword);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->flush();
$this->addFlash('success', $translator->trans('profile.password_changed_success'));
if ($isJsonRequest) {
return $this->jsonResultFromFlash(200);
}
return $this->redirectToRoute('acc_change_password');
}
}
if ($isJsonRequest) {
return $this->jsonResultFromFlash();
}
return $this->render('profile/change_password.html.twig', [
'user' => $user,
'currentLocale' => $currentLocale,
]);
}
*/
#[Route(path: '/acc/forgot', name: 'acc_forgot', methods: ['GET', 'POST'])]
public function forgot(Request $request, UsersRepository $usersRepository, UserRecoveryRepository $userRecoveryRepository, UrlGeneratorInterface $urlGenerator, MailerInterface $mailer): Response
{
/** @var $user \App\Entity\Accounts\Users */
$user = $this->getUser();
if ($user instanceof UserInterface) {
return $this->redirectToRoute('acc_profile');
}
$currentLocale = $request->getLocale();
if ($request->isMethod('GET')) {
$last_email = $request->getSession()->get(Security::LAST_USERNAME, '');
return $this->render('profile/forgot.html.twig', [
'last_email' => $last_email,
'title' => $this->translator->trans('profile.forgot.title', [], 'messages', $currentLocale),
]);
}
$email = $request->request->get('email');
if (!$email) {
$this->addFlash('error', $this->translator->trans('profile.forgot.email_required'));
return $this->redirectToRoute('acc_forgot');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->addFlash('error', $this->translator->trans('profile.form.email_error'));
return $this->redirectToRoute('acc_forgot');
}
$user = $usersRepository->findOneBy(['mail' => $email]);
if (!$user) {
$this->addFlash('error', $this->translator->trans('profile.forgot.user_not_found'));
return $this->redirectToRoute('acc_forgot');
}
$recovery = $userRecoveryRepository->findOneBy(
['userId' => $user->getId(), 'used' => null],
['date' => 'DESC']
);
if (!$recovery || $recovery->getDate() < (new \DateTime('-1 day'))) {
//$hash = hash('ripemd128', $user->getId() . $user->getLogin() . $user->getMail() . time() . $_ENV['APP_SECRET']);
$hash = UserTokensRepository::genToken($user);
$recovery = new UserRecovery();
$recovery->setUser($user);
//$recovery->setUserId($user->getId());
$recovery->setHash($hash);
$recovery->setIp($request->getClientIp() ?? '0');
$recovery->setUseragent($request->headers->get('User-Agent') ?? 'unknown');
$recovery->setDate(new \DateTime());
$this->em->persist($recovery);
$this->em->flush();
} else {
$this->addFlash('success', $this->translator->trans('profile.forgot.password_reset_email_sent'));
return $this->redirectToRoute('acc_forgot');
}
$this->sendEmail(false, $user, $recovery, $urlGenerator, $mailer);
$this->addFlash('success', $this->translator->trans('profile.forgot.password_reset_email_sent'));
return $this->redirectToRoute('acc_forgot');
}
#[Route(path: '/acc/api/forgot', name: 'acc_api_forgot', methods: ['POST'])]
public function forgotPassword(Request $request, UsersRepository $usersRepository, UserRecoveryRepository $userRecoveryRepository, UrlGeneratorInterface $urlGenerator, MailerInterface $mailer): JsonResponse
{
/** @var $user \App\Entity\Accounts\Users */
$user = $this->getUser();
if ($user instanceof UserInterface) {
return new JsonResponse([
'error' => true,
'message' => $this->translator->trans('profile.you_auth'),
], 400);
}
$email = $request->request->get('email');
if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
return new JsonResponse([
'error' => true,
'message' => $this->translator->trans('profile.forgot.invalid_email'),
], 400);
}
$user = $usersRepository->findOneBy(['mail' => $email]);
if (!$user) {
return new JsonResponse([
'error' => true,
'message' => $this->translator->trans('profile.forgot.user_not_found'),
], 404);
}
$recovery = $userRecoveryRepository->findOneBy(
['userId' => $user->getId(), 'used' => null],
['date' => 'DESC']
);
if (!$recovery || $recovery->getDate() < (new \DateTime('-1 day'))) {
//$hash = hash('ripemd128', $user->getId() . $user->getLogin() . $user->getMail() . time() . $_ENV['APP_SECRET']);
$hash = UserTokensRepository::genToken($user);
$recovery = new UserRecovery();
$recovery->setUser($user);
$recovery->setHash($hash);
$recovery->setIp($request->getClientIp() ?? '0');
$recovery->setUseragent($request->headers->get('User-Agent') ?? 'unknown');
$recovery->setDate(new \DateTime());
$this->em->persist($recovery);
$this->em->flush();
} else {
return new JsonResponse([
'error' => false,
'message' => $this->translator->trans('profile.forgot.password_reset_email_sent'),
], 200);
}
$this->sendEmail(true, $user, $recovery, $urlGenerator, $mailer);
$response = new JsonResponse([
'error' => false,
'message' => $this->translator->trans('profile.forgot.password_reset_email_sent'),
], 200);
$response->headers->set('X-Access-Login', $user->getLogin());
$response->headers->set('X-Access-Email', $user->getMail());
$response->headers->set('X-Access-Token', $hash);
$response->headers->set('X-Access-Avatar', $user->getImageAvatar());
return $response;
}
private function sendEmail($api = false, Users $user, UserRecovery $recovery, UrlGeneratorInterface $urlGenerator, MailerInterface $mailer): void
{
$html = $this->renderView('profile/forgot_email.html.twig', [
'login' => $user->getLogin()??$user->getName(),
'forgotLink' => $urlGenerator->generate($api? 'acc_api_recovery': 'acc_recovery', [
'autoaction' => 'change-password',
'email' => $user->getMail(),
'token' => $recovery->getHash(),
'utm_source'=>'otpusk-mailings',
'utm_medium'=>'email',
'utm_campaign'=>'recovery',
'utm_content'=>'otpusk',
], UrlGeneratorInterface::ABSOLUTE_URL
),
'confirm_code' => $recovery->getHash(),
'year' => date('Y'),
]);
$email = (new Email())
->from('no-reply@otpusk.com')
->to($user->getMail())
->subject($this->translator->trans('profile.forgot.title'))
->html($html);
$returnPathAddress = new Address(sprintf('%d@return.otpusk.com', $user->getId()));
$email->getHeaders()->add(new PathHeader('Return-Path', $returnPathAddress));
if ($this->getParameter('kernel.environment') !== 'prod') {
// зберігаємо письма локально
$mimeMessage = $email->toString();
$directory = $this->getParameter('kernel.project_dir') . '/var/mails';
if (!is_dir($directory)) {
mkdir($directory, 0775, true);
}
$filePath = $directory . '/' . date('YmdHis') . '_' . uniqid('email_', true) . '.eml';
file_put_contents($filePath, $mimeMessage);
} else {
$mailer->send($email);
}
}
#[Route(path: '/acc/recovery', name: 'acc_recovery', methods: ['GET', 'POST'])]
public function recovery(Request $request, UserRecoveryRepository $userRecoveryRepository, UsersRepository $usersRepository, EntityManagerInterface $em, TranslatorInterface $translator): Response
{
/** @var $user \App\Entity\Accounts\Users */
$user = $this->getUser();
if ($user instanceof UserInterface) {
return $this->redirectToRoute('acc_profile');
}
$form = $this->createForm(PasswordRecoveryType::class, [
'email' => $request->query->get('email', ''),
'token' => $request->query->get('token', ''),
], [
'is_api' => false,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
if ($data['password'] !== $data['confirm_password']) {
$this->addFlash('error', $translator->trans('profile.error_password_mismatch'));
} else {
$recovery = $userRecoveryRepository->findOneBy([
'hash' => $data['token'],
//'used' => null,
]
);
if (!$recovery) {
$this->addFlash('error', $translator->trans('profile.recovery.token_invalid'));
} else {
$user = $usersRepository->find($recovery->getUserId());
if (!$user) {
$this->addFlash('error', $translator->trans('profile.forgot.user_not_found'));
} else {
$user->setPassword(password_hash($data['password'], PASSWORD_BCRYPT));
$em->persist($user);
$recovery->setUsed(new \DateTime());
$em->persist($recovery);
$em->flush();
$this->addFlash('success', $translator->trans('profile.password_changed_success'));
$token = new UsernamePasswordToken($user, null, $user->getRoles());
$this->tokenStorage->setToken($token);
return $this->redirectToRoute('acc_recovery', [
'email' => $data['email'] ?? '',
'token' => $data['token'] ?? '',
]);
}
}
}
}
return $this->render('profile/recovery.html.twig', [
'form' => $form->createView(),
]);
}
#[Route(path: '/acc/api/recovery', name: 'acc_api_recovery', methods: ['POST'])]
public function apiRecovery(Request $request, UserRecoveryRepository $userRecoveryRepository, UsersRepository $usersRepository, EntityManagerInterface $em, TranslatorInterface $translator): JsonResponse
{
/** @var $user \App\Entity\Accounts\Users */
$user = $this->getUser();
if ($user instanceof UserInterface) {
return new JsonResponse([
'error' => true,
'message' => $this->translator->trans('profile.you_auth'),
], 400);
}
$form = $this->createForm(PasswordRecoveryType::class, null, [
'is_api' => true,
]);
$data = $request->request->all();
$data['password'] = base64_decode($data['password']);
$data['confirm_password'] = base64_decode($data['confirm_password']);
$form->submit($data);
if (!$form->isValid()) {
$errors = $form->getErrors(true);
$fieldErrors = [];
foreach ($errors as $error) {
$field = $error->getOrigin()->getName();
$message = $error->getMessage();
$fieldErrors[$field][] = $message;
}
return new JsonResponse([
'error' => true,
'field' => $field,
'message' => $message,
//'message' => $this->translator->trans('registration.invalid_data'),
'field_errors' => $fieldErrors,
], 400);
}
$data = $form->getData();
if ($data['password'] !== $data['confirm_password']) {
return new JsonResponse([
'error' => true,
'data' => $data,
'message' => $this->translator->trans('profile.error_password_mismatch'),
], 400);
}
$recovery = $userRecoveryRepository->findOneBy([
'hash' => $data['token'],
//'used' => null,
]);
if (!$recovery) {
return new JsonResponse([
'error' => true,
'message' => $this->translator->trans('profile.user_not_found'),
], 404);
}
$user = $usersRepository->find($recovery->getUserId());
if (!$user) {
return new JsonResponse([
'error' => true,
'message' => $this->translator->trans('profile.user_not_found'),
], 404);
}
$user->setPassword(password_hash($data['password'], PASSWORD_BCRYPT));
$this->em->persist($user);
$recovery->setUsed(new \DateTime());
$this->em->persist($recovery);
$em->flush();
$token = new UsernamePasswordToken($user, null, $user->getRoles());
$this->tokenStorage->setToken($token);
$response = new JsonResponse([
'error' => false,
'message' => $translator->trans('profile.password_changed_success'),
], 200);
$response->headers->set('X-Access-Login', $user->getLogin());
$response->headers->set('X-Access-Email', $user->getMail());
$response->headers->set('X-Access-Token', $recovery->getHash());
$response->headers->set('X-Access-Avatar', $user->getImageAvatar());
return $response;
}
#[Route(path: '/acc/delete', name: 'acc_delete_account')]
public function deleteAccount(Request $request, TranslatorInterface $translator): Response
{
/** @var \App\Entity\Accounts\Users $user */
$user = $this->getUser();
if (!$user) {
$this->addFlash('error', 'profile.error_auth_required');
return $this->redirectToRoute('acc_login');
}
$currentLocale = $request->getLocale();
// на деві не вимикаємо користувачів
if ($this->getParameter('kernel.environment') === 'prod') {
$user->setActive('no');
$entityManager = $this->getDoctrine()->getManager();
$entityManager->flush();
}
$this->container->get('security.token_storage')->setToken(null);
//$request->getSession()->invalidate(); // не треба прибирати сесію
return $this->render('profile/delete_account.html.twig', [
'user' => false,
'currentLocale' => $currentLocale,
]);
}
private function jsonResultFromFlash(int $httpCode = 400): JsonResponse
{
$errors = $this->get('session')->getFlashBag()->get('error', []);
$success = $this->get('session')->getFlashBag()->get('success', []);
if (!empty($errors)) {
return new JsonResponse([
'error' => true,
'message' => implode(PHP_EOL, $errors),
], $httpCode);
}
if (!empty($success)) {
return new JsonResponse([
'error' => false,
'message' => implode(PHP_EOL, $success),
], 200);
}
return new JsonResponse([
'error' => false,
'message' => '',
], 200);
}
}