<?php
namespace App\Actimage\CaptchaBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Validator\Constraints\Expression;
use Symfony\Component\Form\CallbackTransformer;
use App\Actimage\CaptchaBundle\Service\ActimageCaptchaService;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
/**
* @author Actimage - Marc Dornier
*/
class ActimageCaptchaType extends AbstractType
{
protected $requestStack;
protected $session;
protected $actimageCaptchaService;
protected $captchaText;
protected $cipherCaptcha;
public function __construct(RequestStack $requestStack, ActimageCaptchaService $actimageCaptchaService)
{
$this->requestStack = $requestStack;
$this->session = $this->requestStack->getSession();
$this->session->start();
$this->actimageCaptchaService = $actimageCaptchaService;
// Génération du captcha et encodage de ce dernier pour appeler les services de génération d'image et de son
// depuis le JS sans divulguer le code dans les URLs
$this->captchaText = $this->actimageCaptchaService->secure_generate_string();
$this->cipherCaptcha = $this->actimageCaptchaService->cipherCaptcha($this->captchaText);
if($this->session->get('allCaptcha', []) == []){
$arrayAllCaptcha = [];
array_push($arrayAllCaptcha, strtolower($this->captchaText));
$this->session->set('allCaptcha', $arrayAllCaptcha);
}
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$encodedCaptcha = $this->cipherCaptcha;
$builder
->add('captcha', TextType::class, [
'attr' => ['autocomplete' => 'off', 'encodedCaptcha' => $this->cipherCaptcha],
'label_attr' => ['class' => 'mb-0'],
'help_attr' => ['class' => 'mb-1'],
'help' => '(les lettres peuvent être retranscrites indifféremment en minuscule ou en majuscule)',
'required' => true
])
->addEventListener(FormEvents::PRE_SUBMIT , function (FormEvent $event) use ($encodedCaptcha) {
$data = $event->getData();
$form = $event->getForm();
if (!$data) {
return;
}
if (isset($data['captcha'])) {
// On stock plusieurs captcha en session et on les comparent tous avec la saisie utilisateur
// pour éviter les problème de mauvais captcha comparé lors du double chargement du formulaire
// (formulaire chargé 2 fois à cause de Symfony, et donc session écrasée lors du 2eme chargement)
$storedCaptcha = $this->actimageCaptchaService->decipherCaptcha($encodedCaptcha);
$allCaptcha = $this->session->get('allCaptcha', []);
array_push($allCaptcha, strtolower($storedCaptcha));
if(count($allCaptcha) > 3){
array_shift($allCaptcha);
}
$this->session->set('allCaptcha', $allCaptcha);
// On stock la saisie utilisateur en session pour la comparer aux captchas stockées
$this->session->set('captchaSent', $data['captcha']);
}
// Ajout de la contrainte de validation lors du PRE_SUBMIT pour avoir la valeur de saisie utilisateur captcha déclarée en session lors du submit d'avant
foreach($event->getForm()->all() as $child){
$childName = $child->getName();
$type = get_class($child->getConfig()->getType()->getInnerType());
$options = $child->getConfig()->getOptions();
$options['constraints'] = [
new Expression([
'expression' => "'ok' == captcha_text",
'values' => ['captcha_text' => $this->isCaptchaValid()],
'message' => 'Le captcha doit être valide '
])
];
$form->add($childName,$type,$options);
}
})
->get('captcha')
->addModelTransformer(new CallbackTransformer(
function ($data) {
return $data;
},
function ($data) {
// met la saisie utilisateur en lowercase pour faire la comparaison avec le code captcha sans se soucier de la casse
$data = strtolower($data);
return $data;
}
))
;
}
/**
* Vérification de la saisie utilisateur captcha
*/
public function isCaptchaValid(){
// Si le captcha est valide = OK, si le captcha n'est pas valide ou non submit alors NULL
$allCaptcha = $this->session->get('allCaptcha');
$captchaSent = $this->session->get('captchaSent', null);
if($captchaSent != null && in_array(strtolower($captchaSent), $allCaptcha)){
return 'ok';
}
return null;
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
// $view->vars['captcha_text'] = $this->cipherCaptcha.' '.$this->captchaText;
}
}