4

I am currently overloading SonataUser registration form so that I can have my own custom form when people create an account.

I have overloaded everything properly (the handler, the form type, the controller and the twig template). However, when I send the form, I only get the data back and a new user is not created. Therefore, I investigated and I found out that when I echoed this

var_dump($this->form->getErrors());

I obtained an error saying the CSRF token is invalid. I am using Symfony 2.4.2 and sonata user 2.2.x-dev.

I will show you all the classes that I have overloaded. For now, they are mostly copy and pasted from their parents.

Here is my form handler

<?php

/*
 * This file is part of the Sonata package.
 *
 * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 */

namespace Application\Sonata\UserBundle\Form\Handler;

use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;

use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Form\Handler\RegistrationFormHandler as BaseHandler;
use Symfony\Component\Form\FormInterface;


use FOS\UserBundle\Mailer\MailerInterface;
use FOS\UserBundle\Util\TokenGeneratorInterface;


/**
 *
 * This file is an adapted version of FOS User Bundle RegistrationFormHandler class
 *
 *    (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 */
class RegistrationFormHandler extends BaseHandler
{
    protected $request;
    protected $userManager;
    protected $form;
    protected $mailer;
    protected $tokenGenerator;

    public function __construct(FormInterface $form, Request $request, UserManagerInterface $userManager, MailerInterface $mailer, TokenGeneratorInterface $tokenGenerator)
    {

        $this->form = $form;
        $this->request = $request;
        $this->userManager = $userManager;
        $this->mailer = $mailer;
        $this->tokenGenerator = $tokenGenerator;
    }

    /**
     * @param boolean $confirmation
     */
    public function process($confirmation = false)
    {

        $user = $this->createUser();
        $this->form->setData($user);

        if ('POST' === $this->request->getMethod()) {

            $this->form->bind($this->request);
            if ($this->form->isValid()) {
                var_dump('working !!');
                $this->onSuccess($user, $confirmation);

                return true;
            }
            var_dump($this->form->getErrors());
        }

        return false;
    }

    /**
     * @param boolean $confirmation
     */
    protected function onSuccess(UserInterface $user, $confirmation)
    {
        if ($confirmation) {
            $user->setEnabled(false);
            if (null === $user->getConfirmationToken()) {
                $user->setConfirmationToken($this->tokenGenerator->generateToken());
            }

            $this->mailer->sendConfirmationEmailMessage($user);
        } else {
            $user->setEnabled(true);
        }

        $this->userManager->updateUser($user);
    }

    /**
     * @return UserInterface
     */
    protected function createUser()
    {
        return $this->userManager->createUser();
    }
}

Here is my form type:

<?php

/*
 * This file is part of the FOSUserBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Application\Sonata\UserBundle\Form\Type;

use Entities\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Sonata\UserBundle\Model\UserInterface;

class RegistrationFormType extends AbstractType
{
    private $class;

    /**
     * @var array
     */
    protected $mergeOptions;

    /**
     * @param string $class        The User class name
     * @param array  $mergeOptions Add options to elements
     */
    public function __construct($class, array $mergeOptions = array())
    {

        $this->class        = $class;
        $this->mergeOptions = $mergeOptions;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('username', null, array_merge(array(
                'label' => 'form.username',
                'translation_domain' => 'SonataUserBundle',
            ), $this->mergeOptions))
            ->add('email', 'email', array_merge(array(
                'label' => 'form.email',
                'translation_domain' => 'SonataUserBundle',
            ), $this->mergeOptions))

            ->add('plainPassword', 'repeated', array_merge(array(
                'type' => 'password',
                'required' => true,
                'options' => array('translation_domain' => 'SonataUserBundle'),
                'first_options' => array_merge(array(
                    'label' => 'form.password',
                ), $this->mergeOptions),
                'second_options' => array_merge(array(
                    'label' => 'form.password_confirmation',
                ), $this->mergeOptions),
                'invalid_message' => 'fos_user.password.mismatch',
            ), $this->mergeOptions))

            ->add('lastName', null, array_merge(array(
                'label' => 'form.label_lastname',
                'translation_domain' => 'SonataUserBundle',
            ), $this->mergeOptions))
            ->add('firstName', null, array_merge(array(
                'label' => 'form.label_firstname',
                'translation_domain' => 'SonataUserBundle',
            ), $this->mergeOptions))
            ->add('date_of_birth', 'birthday', array_merge(array(
                'label' => 'form.label_date_of_birth',
                'translation_domain' => 'SonataUserBundle',
            ), $this->mergeOptions))
            ->add('gender', 'sonata_user_gender', array(
                'label'    => 'form.label_gender',
                'required' => true,
                'translation_domain' => 'SonataUserBundle',
                'choices' => array(
                    UserInterface::GENDER_FEMALE => 'gender_female',
                    UserInterface::GENDER_MALE   => 'gender_male',
                )
            ))
            ->add('phone', null, array_merge(array(
                'label' => 'form.label_phone',
                'translation_domain' => 'SonataUserBundle',
            ), $this->mergeOptions))

            ->add('address', null, array_merge(array(
                'label' => 'form.address',
                'translation_domain' => 'SonataUserBundle',
            ), $this->mergeOptions))

            ->add('city', null, array_merge(array(
                'label' => 'form.city',
                'translation_domain' => 'SonataUserBundle',
            ), $this->mergeOptions))

            ->add('state', 'choice', array_merge(array(
                'label' => 'form.state',
                'translation_domain' => 'SonataUserBundle',
                'multiple' => false,
                'expanded' => false
            ), $this->mergeOptions))

            ->add('country', 'choice', array_merge(array(
                'label' => 'form.country',
                'translation_domain' => 'SonataUserBundle',
                'multiple' => false,
                'expanded' => false
            ), $this->mergeOptions))

            ->add('postalCode', null, array_merge(array(
                'label' => 'form.postalCode',
                'translation_domain' => 'SonataUserBundle',
            ), $this->mergeOptions))

//            ->add('children', 'collection', array_merge(array(
//                'type' => new ChildFormType('Application\Sonata\UserBundle\Entity\User'),
//                'translation_domain' => 'SonataUserBundle',
//                'allow_add' => true,
//                'allow_delete' => true,
//                'by_reference' => false,
//            ), $this->mergeOptions))




        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => $this->class,
            'intention'  => 'registration',
        ));
    }

    public function getName()
    {
        return 'sonata_user_registration';
    }
}

Here is my registration controller

<?php
/*
 * This file is part of the Sonata package.
 *
 * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */


namespace Application\Sonata\UserBundle\Controller;

use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AccountStatusException;
use FOS\UserBundle\Model\UserInterface;

/**
 * Class SonataRegistrationController
 *
 * This class is inspired from the FOS RegistrationController
 *
 * @package Sonata\UserBundle\Controller
 *
 * @author Hugo Briand <briand@ekino.com>
 */
class RegistrationFOSUser1Controller extends ContainerAware
{
    public function registerAction()
    {
        $user = $this->container->get('security.context')->getToken()->getUser();

        if ($user instanceof UserInterface && 'POST' === $this->container->get('request')->getMethod()) {
            $this->container->get('session')->getFlashBag()->set('sonata_user_error', 'sonata_user_already_authenticated');
            $url = $this->container->get('router')->generate('sonata_user_profile_show');

            return new RedirectResponse($url);
        }

        $form = $this->container->get('sonata.user.registration.form');
        $formHandler = $this->container->get('sonata.user.registration.form.handler');
        $confirmationEnabled = $this->container->getParameter('fos_user.registration.confirmation.enabled');

        $process = $formHandler->process($confirmationEnabled);
        var_dump(0);
        if ($process) {
            var_dump(1);
            exit();
            $user = $form->getData();

            $authUser = false;
            if ($confirmationEnabled) {
                $this->container->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
                $route = 'fos_user_registration_check_email';
            } else {
                $authUser = true;
                $route = $this->container->get('session')->get('sonata_basket_delivery_redirect', 'sonata_user_profile_show');
                $this->container->get('session')->remove('sonata_basket_delivery_redirect');
            }

            $this->setFlash('fos_user_success', 'registration.flash.user_created');
            $url = $this->container->get('session')->get('sonata_user_redirect_url');

            if (null === $url || "" === $url) {
                $url = $this->container->get('router')->generate($route);
            }

            $response = new RedirectResponse($url);

            if ($authUser) {
                $this->authenticateUser($user, $response);
            }

            return $response;
        }

        $this->container->get('session')->set('sonata_user_redirect_url', $this->container->get('request')->headers->get('referer'));

        return $this->container->get('templating')->renderResponse('FOSUserBundle:Registration:register.html.'.$this->getEngine(), array(
            'form' => $form->createView(),
        ));
    }

    /**
     * Tell the user to check his email provider
     */
    public function checkEmailAction()
    {
        $email = $this->container->get('session')->get('fos_user_send_confirmation_email/email');
        $this->container->get('session')->remove('fos_user_send_confirmation_email/email');
        $user = $this->container->get('fos_user.user_manager')->findUserByEmail($email);

        if (null === $user) {
            throw new NotFoundHttpException(sprintf('The user with email "%s" does not exist', $email));
        }

        return $this->container->get('templating')->renderResponse('FOSUserBundle:Registration:checkEmail.html.'.$this->getEngine(), array(
            'user' => $user,
        ));
    }

    /**
     * Receive the confirmation token from user email provider, login the user
     */
    public function confirmAction($token)
    {
        $user = $this->container->get('fos_user.user_manager')->findUserByConfirmationToken($token);

        if (null === $user) {
            throw new NotFoundHttpException(sprintf('The user with confirmation token "%s" does not exist', $token));
        }

        $user->setConfirmationToken(null);
        $user->setEnabled(true);
        $user->setLastLogin(new \DateTime());

        $this->container->get('fos_user.user_manager')->updateUser($user);
        if ($redirectRoute = $this->container->getParameter('sonata.user.register.confirm.redirect_route')) {
            $response = new RedirectResponse($this->container->get('router')->generate($redirectRoute, $this->container->getParameter('sonata.user.register.confirm.redirect_route_params')));
        } else {
            $response = new RedirectResponse($this->container->get('router')->generate('fos_user_registration_confirmed'));
        }

        $this->authenticateUser($user, $response);

        return $response;
    }

    /**
     * Tell the user his account is now confirmed
     */
    public function confirmedAction()
    {
        $user = $this->container->get('security.context')->getToken()->getUser();
        if (!is_object($user) || !$user instanceof UserInterface) {
            throw new AccessDeniedException('This user does not have access to this section.');
        }

        return $this->container->get('templating')->renderResponse('FOSUserBundle:Registration:confirmed.html.'.$this->getEngine(), array(
            'user' => $user,
        ));
    }

    /**
     * Authenticate a user with Symfony Security
     *
     * @param \FOS\UserBundle\Model\UserInterface        $user
     * @param \Symfony\Component\HttpFoundation\Response $response
     */
    protected function authenticateUser(UserInterface $user, Response $response)
    {
        try {
            $this->container->get('fos_user.security.login_manager')->loginUser(
                $this->container->getParameter('fos_user.firewall_name'),
                $user,
                $response);
        } catch (AccountStatusException $ex) {
            // We simply do not authenticate users which do not pass the user
            // checker (not enabled, expired, etc.).
        }
    }

    /**
     * @param string $action
     * @param string $value
     */
    protected function setFlash($action, $value)
    {
        $this->container->get('session')->getFlashBag()->set($action, $value);
    }

    protected function getEngine()
    {
        return $this->container->getParameter('fos_user.template.engine');
    }
}

Here are my services:

sonata.user.registration.form.type:
    class: Application\Sonata\UserBundle\Form\Type\RegistrationFormType
    arguments: [ "%fos_user.model.user.class%"]
    tags:
        - { name: form.type, alias: sonata_user_registration }

sonata.child.registration.form.type:
    class: Application\Sonata\UserBundle\Form\Type\ChildFormType
    arguments: [ "%fos_user.model.user.class%"]
    tags:
        - { name: form.type, alias: sonata_child_registration }

sonata.user.registration.form.handler.default:
    class: Application\Sonata\UserBundle\Form\Handler\RegistrationFormHandler
    scope: request
    public: false
    arguments: [@fos_user.registration.form, @request, @fos_user.user_manager, @fos_user.mailer, @fos_user.util.token_generator]

Here is my sonata user configuration(app/config/config.yml)

sonata_user:
    security_acl:           false

    manager_type: orm # Can be orm for mongodb

    table:
        user_group: "my_custom_user_group_association_table_name"

    impersonating:
        route:                page_slug
        parameters:           { path: / }

    class:                  # Entity Classes
        user:               Application\Sonata\UserBundle\Entity\User
        group:              Application\Sonata\UserBundle\Entity\Group

    admin:                  # Admin Classes
        user:
            class:          Sonata\UserBundle\Admin\Entity\UserAdmin
            controller:     SonataAdminBundle:CRUD
            translation:    SonataUserBundle

        group:
            class:          Sonata\UserBundle\Admin\Entity\GroupAdmin
            controller:     SonataAdminBundle:CRUD
            translation:    SonataUserBundle

    profile:  # Profile Form (firstname, lastname, etc ...)
        form:
            type:               sonata_user_profile
            handler:            sonata.user.profile.form.handler.default
            name:               sonata_user_profile_form
            validation_groups:  [Profile]

        register:
            # You may customize the registration forms over here
            form:
                type:                 sonata_user_registration
                handler:              sonata.user.registration.form.handler.default
                name:                 sonata_user_registration_form
                validation_groups:

                    # Defaults:
                    - Registration
                    - Default

My twig rendering:

{% block fos_user_content %}
    <br>
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <div class="well">
                <div class="panel-heading">
                    <h3>{{ 'title_user_registration'|trans({}, 'SonataUserBundle') }}</h3>
                </div>
                <div class="panel-body">
                    <form ng-app="userRegistrationApp" action="{{ path('fos_user_registration_register') }}" {{ form_enctype(form) }} method="POST" class="fos_user_registration_register form-horizontal">

                        <h4>{{ 'General'|trans({}, 'SonataUserBundle') }}</h4>
                        <hr>


                        <div class="form-group">
                            {{ form_label(form.username, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.username, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.email, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.email, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <br>
                        <h4>{{ 'form.label_plain_password'|trans({}, 'SonataUserBundle') }}</h4>
                        <hr>

                        <div class="form-group">
                            {{ form_label(form.plainPassword.first, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.plainPassword.first, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.plainPassword.second, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.plainPassword.second, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <br>
                        <h4>{{ 'Profile'|trans({}, 'SonataUserBundle') }}</h4>
                        <hr>

                        <div class="form-group">
                            {{ form_label(form.lastName, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.lastName, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.firstName, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.firstName, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.date_of_birth, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.date_of_birth, {'attr': {'class': '' }}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.gender, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.gender, {'attr': {'class': ''}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.phone, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.phone, {'attr': {'class': 'form-control bfh-phone', 'data-country':'sonata_user_registration_form_country'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.address, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.address, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.city, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.city, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.country, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.country, {'attr': {'class': 'form-control bfh-countries', ' data-country':'US'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.state, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.state, {'attr': {'class': 'form-control bfh-states', 'data-country':'sonata_user_registration_form_country'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form_label(form.postalCode, null, {'label_attr': {'class': 'col-xs-4 control-label'}}) }}
                            <div class="col-xs-8">
                                {{ form_widget(form.postalCode, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <br>

                        {{ form_rest(form) }}


                        {#<a href="#Children" class="btn btn-link" ng-click="userRegistrationService.addEmptyChild()"><span class="glyphicon glyphicon-plus-sign"></span> {{ 'AddAChildren'|trans({}, 'SonataUserBundle') }}</a>#}


                        <div class="form-actions">
                            <button type="submit" class="btn btn-success pull-right">{{ 'registration.submit'|trans({}, 'FOSUserBundle') }}</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
{% endblock fos_user_content %}

I really don't know why I get the error:

array (size=1)
  0 => 
    object(Symfony\Component\Form\FormError)[1125]
      private 'message' => string 'Le jeton CSRF est invalide. Veuillez renvoyer le formulaire.' (length=60)
      protected 'messageTemplate' => string 'Le jeton CSRF est invalide. Veuillez renvoyer le formulaire.' (length=60)
      protected 'messageParameters' => 
        array (size=0)
          empty
      protected 'messagePluralization' => null

because I have in my page {{form_rest(form)}} and the token field is present...

UPDATE I created a github repository so that my configuration can be pulled so you can see the problems for yourself. https://github.com/ima-tech/testSonataUser

Etienne Noël
  • 5,988
  • 6
  • 48
  • 75
  • can you by any chance reproduce this by integrating in fresh symfony and sonata setup setup and upload your code to github. Would be easier to pull and fix your problem for us – vishal Apr 11 '14 at 04:49
  • Alright, so I finally got the time to redo it and I have a github repo, here it is: https://github.com/ima-tech/testSonataUser The problem unfortunately still appears... – Etienne Noël Apr 12 '14 at 17:58
  • @user1191081 it appears that the problem is still there even after a clean install.... – Etienne Noël Apr 14 '14 at 11:18

1 Answers1

3

Alright, when I cloned your github repo, I was able to remove the CSRF error by tweaking a bit your services.

parameters:
#    osc_default.example.class: OSC\DefaultBundle\Example

services:
#    osc_default.example:
#        class: %osc_default.example.class%
#        arguments: [@service_id, "plain_value", %parameter%]

    sonata.user.registration.form.type:
        class: Application\Sonata\UserBundle\Form\Type\RegistrationFormType
        arguments: [ "%fos_user.model.user.class%"]
        tags:
            - { name: form.type, alias: sonata_user_registration }

    sonata.child.registration.form.type:
        class: Application\Sonata\UserBundle\Form\Type\ChildFormType
        arguments: [ "%fos_user.model.user.class%"]
        tags:
            - { name: form.type, alias: sonata_child_registration }

    sonata.user.registration.form.handler.default:
        class: Application\Sonata\UserBundle\Form\Handler\RegistrationFormHandler
        scope: request
        public: false
        arguments: [@sonata.user.registration.form, @request, @sonata.user.user_manager, @fos_user.mailer, @fos_user.util.token_generator]

You see, under sonata.user.registration.form.handler.default: in the arguments, you need to set @sonata.user.registration.form and @sonata.user.user_manager and not @fos_user.registration.form and @fos_user.user_manager

etiennenoel
  • 575
  • 2
  • 15