<?php
namespace App\Controller;
use App\Entity\ResetPassword;
use App\Entity\Users;
use App\Entity\UserAdditionalDetails;
use App\Entity\ProfileClassification;
use App\Entity\UsersCategories;
use App\Entity\VehicleClients;
use App\Form\ResetPasswordType;
use App\Form\UserRegistrationType;
use Gregwar\Captcha\CaptchaBuilder;
use Gregwar\Captcha\PhraseBuilder;
use Psr\Log\LoggerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Unirest;
use Symfony\Component\Form\FormError;
use App\Service\SMSHelper;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Class SecurityController
* @package App\Controller
*/
class SecurityController extends AbstractController
{
/**
* @param Request $request
* @param AuthenticationUtils $authenticationUtils
*
* @Route("/login", name="login")
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function loginAction(Security $security, AuthenticationUtils $authenticationUtils): Response
{
// redirect to profile if user logged
if($security->getUser()){
return $this->redirectToRoute('my_profile');
}
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', array(
'last_username' => $lastUsername,
'error' => $error
));
}
/**
* @Route("/logout", name="logout")
*
* @throws \Exception
*/
public function logoutAction()
{
throw new \Exception('This should never be reached!');
}
/**
* @param Request $request
* @param UserPasswordEncoderInterface $passwordEncoder
*
* @Route("/reg/getCaptcha", name="get_captcha_ajax")
* @Method("POST")
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
* @throws Unirest\Exception
*/
public function regenerateCaptchaAjaxAction(Request $request)
{
if ($request->isXMLHttpRequest()) {
$session = $request->getSession();
$builder = $this->createCaptcha($session);
return new JsonResponse(['builder' => $builder->inline()]);
}
}
/**
* @param Request $request
* @param UserPasswordEncoderInterface $passwordEncoder
*
* @Route("/register", name="user_registration")
* @Route("/reg", name="user_reg")
* @Method({"GET","POST"})
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
* @throws Unirest\Exception
*/
public function registerAction(Request $request, UserPasswordEncoderInterface $passwordEncoder, LoggerInterface $logger, TranslatorInterface $translator)
{
// redirect to profile if user logged
if($this->getUser()){
return $this->redirectToRoute('my_profile');
}
$session = $request->getSession();
$locale = $request->getLocale();
$errorCaptcha = false;
// 1) build the form
$user = new Users();
$form = $this->createForm(UserRegistrationType::class, $user, ['locale' => $locale]);
// 2) handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// refresh CSRF token (form_intention)
$this->get("security.csrf.token_manager")->refreshToken("form_intention");
$postedData = $request->request->all();
$code = $postedData['sms_code'];
$userCaptcha = $postedData['userCaptcha'];
if ($userCaptcha == $session->get('captchaPhrase')) {
if ($code == $session->get('verificationCode')) {
$password = rand(1001, 9998);
$mobileNumber = $user->getMobileNumber();
preg_match('/^(?:0|966)*(5[0-9]{8})$/', $mobileNumber, $mobNumChunks);
$user->setMobileNumber($mobNumChunks[1]); //remove leading zero or KSA country code if any exists
$username = $user->getUserIdentityInfo()->getIdentificationNumber();
// 3) Encode the password (you could also do this via Doctrine listener)
$hashedPassword = $passwordEncoder->encodePassword($user, $password);
$user->setPassword($hashedPassword);
// 4) save the User!
$em = $this->getDoctrine()->getManager();
$category = $em->getRepository(UsersCategories::class)->find(1);
$userIdentityInfo = $user->getUserIdentityInfo();
$user->setUserIdentityInfo(null);
$user->setCategory($category);
$user->setIsReceiveSms(true); //the self-registered users by default accept to receive our SMS
$user->setUsername($userIdentityInfo->getIdentificationNumber());
$user->setProfileOrigin("self registered");
$em->persist($user);
//if image uploading is required, so this is the place where to add upload code
$em->flush($user);
// saving user identity info
$user->setUserIdentityInfo($userIdentityInfo);
$userIdentityInfo->setUser($user);
$userAdditionalDetails = new UserAdditionalDetails();
$registeredClassification = $em->getRepository(ProfileClassification::class)->findBy(['nameEn'=> 'Registered']);
$userAdditionalDetails->setClassification($registeredClassification[0]);
$userAdditionalDetails->setUser($user);
$user->setUserAdditionalDetails($userAdditionalDetails);
$em->persist($userAdditionalDetails);
$em->flush($userAdditionalDetails);
// Create vehicle client account
$vehicleClient = new VehicleClients();
$vehicleClient->setUser($user);
$vehicleClient->setClientId($user->getUserId());
$vehicleClient->setIsActive(1);
$em->persist($vehicleClient);
$em->persist($userIdentityInfo);
$em->flush();
$message = $translator->trans('your login info ID and password are',
['%{username}'=> $username, '%{password}'=> $password, '%{newline}'=> "\n"], 'clients');
if ($this->sendSMSToUser($message, $mobileNumber, 'randum generated password', $logger)) {
return $this->redirectToRoute('user_thanking');
} else {
// password did not sent to customer
$session->getFlashBag()->add('info',
$translator->trans('no password sent', array(), 'clients')
);
$logger->info('Register Action: no password sent to mobile num: '. $user->getMobileNumber());
return $this->redirectToRoute('login');
}
} else {
// activation code is wrong
$errorMessage = $translator->trans('you have entered a wrong verification code please enter the correct one', array(''), 'validators');
$form->addError(new FormError($errorMessage)); //this is how to add an error to Symfony form
$logger->info('Register Action: you have entered a wrong verification code please enter the correct one, mobile num is '. $user->getMobileNumber());
}
} else {
// captcha code is wrong
$errorMessage = $translator->trans('captcha code is wrong', array(''), 'validators');
$form->addError(new FormError($errorMessage)); //this is how to add an error to Symfony form
$logger->info('Register Action: you have entered a wrong captcha code, mobile num is '. $user->getMobileNumber());
$errorCaptcha = $translator->trans('captcha code is wrong', array(''), 'validators');
}
}
$builder = $this->createCaptcha($session);
return $this->render('security/register.html.twig', array(
'form' => $form->createView(),
'locale' => $locale,
'builder' => $builder,
'errorCaptcha' => $errorCaptcha,
));
}
/**
* @param Request $request
*
* @Route("forgotPassword", name="forgot_password")
* @Route("/getCaptchaForgot", name="get_captcha_forgot_ajax")
* @Method({"GET", "POST"})
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
* @throws Unirest\Exception
*/
public function forgotPasswordAction(Request $request, LoggerInterface $logger, TranslatorInterface $translator)
{
$session = $request->getSession();
//for ajax request
if ($request->isXMLHttpRequest()) {
$builder = $this->createCaptcha($session);
return new JsonResponse(['builder' => $builder->inline()]);
}
if ($request->isMethod('post')) {
//if post, validate then send verification code
$postedData = $request->request->all();
$userCaptcha = $postedData['userCaptcha'];
$mobileNumber = $postedData['mobileNumber'];
// if the mobile number starts 0 , remove it
if(substr($mobileNumber,0,1) == 0){
$mobileNumber = substr($mobileNumber,1);
}
if ($userCaptcha == $session->get('captchaPhrase')) {
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository(Users::class)->findOneBy(['mobileNumber' => $mobileNumber]);
if ($user) {
$code = rand(101, 998);
$session->set('activationCode', $code);
$message = $translator->trans('to reset your password enter this code', array(), 'clients') . ': '. $code.
"\n" . $translator->trans('your ID is', [], 'clients') . ': ' . $user->getUsername();
if ($this->sendSMSToUser($message, $mobileNumber, 'forgot password code', $logger)) {
$session->set('mobileNumber', $mobileNumber);
return $this->redirectToRoute('reset_password');
} else {
// code did not sent to customer
$session->getFlashBag()->add('info',
$translator->trans('no code sent', array(), 'clients')
);
return $this->redirectToRoute('forgot_password');
}
} else {
// no user registered with this mobile number
$errorCode = $translator->trans('no user registered by this mobile number', array(''), 'validators');
$builder = $this->createCaptcha($session);
}
} else {
// captcha code is wrong
$errorCode = $translator->trans('you have enterd a wrong captcha code', array(''), 'validators');
$builder = $this->createCaptcha($session);
}
} else {
$builder = $this->createCaptcha($session);
$errorCode = null;
$mobileNumber = '';
}
return $this->render('security/forgot_password.html.twig', compact('builder', 'errorCode', 'mobileNumber'));
}
/**
* @param Request $request
* @param UserPasswordEncoderInterface $passwordEncoder
* @param $mobileNumber
*
* @Route("/resetPassword", name="reset_password")
* @Method({"GET", "POST"})
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function resetPasswordAction(Request $request, UserPasswordEncoderInterface $passwordEncoder, TranslatorInterface $translator)
{
$session = $request->getSession();
$resetPassword = new ResetPassword();
$errorCode = '';
$mobileNumber= $session->get('mobileNumber');
$form = $this->createForm(ResetPasswordType::class, $resetPassword, ['capital' => false]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository(Users::class)->findOneBy(['mobileNumber' => $mobileNumber]);
if ($user) {
$code = $resetPassword->getSmsCode();
if ($code == $session->get('activationCode')) {
$newPassword = $resetPassword->getNewPassword();
//$user = $this->getUser();
$hashedPassword = $passwordEncoder->encodePassword($user, $newPassword);
$user->setPassword($hashedPassword);
$em->flush($user);
$session->getFlashBag()->add('success',
$translator->trans('password changed successfully', array(), 'clients')
);
return $this->redirectToRoute('login');
} else {
// activation code is wrong
$errorCode = $translator->trans('you have entered a wrong activation code please enter the correct one', array(''), 'validators');
}
} else {
// no user registered by his mobile number
$errorCode = $translator->trans('no user registered by this mobile number', array(''), 'validators');
$builder = $this->createCaptcha($session);
}
}
return $this->render('security/reset_password.html.twig', array(
'form' => $form->createView(),
'errorCode' => $errorCode
));
}
/**
* @param $passwordOrCode
* @param $mobileNumber
* @param $smsType
* @param null $username
*
* @return bool
* @throws Unirest\Exception
*/
private function sendSMSToUser($message, $mobileNumber, $smsType, LoggerInterface $logger)
{
if ( !preg_match('/^(?:0|966)*(5[0-9]{8})$/', $mobileNumber, $mobNumChunks) ) {
$logger->info( "Unable to send $smsType SMS due to wrong mobile num :". $mobileNumber);
return false;
}
$mobileNumber = '966'. $mobNumChunks[1]; //add KSA country code
//send the code into an SMS
$smsHelper = new SMSHelper();
$sendStatus= $smsHelper->sendSms( $message , $mobileNumber);
$logger->info(\Doctrine\Common\Util\Debug::dump($smsHelper->getRawResponse(), 2, true, false));
switch ($sendStatus){
case $smsHelper::STATUS['sms_seems_sent'];
$logger->info( "sending $smsType SMS to mobile num :". $mobileNumber. " sent!");
return true;
break;
case $smsHelper::STATUS['sms_send_failed'];
case $smsHelper::STATUS['request_failed'];
$logger->info( "Failed sending $smsType SMS to mobile num :". $mobileNumber. " due to this exception: ". $smsHelper->getException());
return false;
break;
}
}
/**
* @return CaptchaBuilder
*/
private function createCaptcha($session)
{
// Will build phrases of 4 characters, only digits
$phraseBuilder = new PhraseBuilder(4, '0123456789');
$builder = new CaptchaBuilder(null, $phraseBuilder);
$builder->build(115, 50);
$session->set('captchaPhrase', $builder->getPhrase());
return $builder;
}
}