Disable default constraints defined in FOSUserBundle - symfony

I'm currently working on a Symfony2 project which uses Sonata.
Previous developers have made modifications inside the vendors directly which is obviously a huge mistake so I'm in the process of refactoring this. I'm currently stuck with some modifications that have been made to constraint mapping from the FOSUserBundle.
The file is: /vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Resources/config/validation/orm.xml
They actually disabled the UniqueEntity constraint on the class FOS\UserBundle\Model\User based on the field usernameCanonical, like this:
<?xml version="1.0" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="FOS\UserBundle\Model\User">
<!-- <constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">usernameCanonical</option>
<option name="errorPath">username</option>
<option name="message">fos_user.username.already_used</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
</option>
</constraint> -->
<constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">emailCanonical</option>
<option name="errorPath">email</option>
<option name="message">fos_user.email.already_used</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
</option>
</constraint>
</class>
</constraint-mapping>
How can I reproduce this change in the overriden FOSUserBundle, or the overriden SonataUserBundle ?

I have a solution, but don't know if it will fit your needs.
You can change de validation groups name in your config.yml for fos_user.
For example :
fos_user:
...
registration:
...
form:
validation_groups: [YourBundleNameRegistration, Default] #before it was [Registration, Default]
profile:
...
form:
...
validation_groups: [YourBundleNameProfile, Default] #before it was [Profile, Default]
Then the validation constraints defined in /vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Resources/config/validation/orm.xml will not applied anymore.
You have to copy the original orm.xml, and paste it in your YourBundle/Resources/config directory. And in this copy you replace Registration and Profile by YourBundleNameRegistration and YourBundleNameProfile. And then you remove the unique constraint on usernameCanonical.

As much as the answer from #Nico pointed me to the right direction, here is the complete setup I've had to do in order to integrate it into the SonataUserBundle and have my custom validation groups taken into account.
As a matter of fact, it seemed that my custom orm.xml file was not loaded. So after changing the validation groups, no more validation occurred at all.
1. Create a file orm.xml
In Application\Sonata\UserBundle\Resources\config\validation\:
<?xml version="1.0" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="FOS\UserBundle\Model\User">
<!-- <constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">usernameCanonical</option>
<option name="errorPath">username</option>
<option name="message">fos_user.username.already_used</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
</option>
</constraint> -->
<constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">emailCanonical</option>
<option name="errorPath">email</option>
<option name="message">fos_user.email.already_used</option>
<option name="groups">
<value>CustomRegistration</value>
<value>CustomProfile</value>
</option>
</constraint>
</class>
</constraint-mapping>
2. Tell FOSUserBundle and SonataUserBundle
To use the custom validation groups (I've stripped down the non-relevant configuration lines):
app/config/fos/fos_user.yml:
fos_user:
registration:
form:
validation_groups:
- CustomRegistration
profile:
form:
validation_groups:
- CustomProfile
app/config/sonata/sonata_user.yml:
sonata_user:
profile:
form:
validation_groups:
- CustomProfile
3. Tell the compiler to load the "overriden" validation file
Add Application\Sonata\UserBundle\DependencyInjection\Compiler\ValidationPass.php class. This is based on the original class from FOSUserBundle:
class ValidationPass implements CompilerPassInterface
{
/**
* {#inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasParameter('fos_user.storage')) {
return;
}
$storage = $container->getParameter('fos_user.storage');
if ('custom' === $storage) {
return;
}
$validationFile = __DIR__ . '/../../Resources/config/validation/' . $storage . '.xml';
if ($container->hasDefinition('validator.builder')) {
// Symfony 2.5+
$container->getDefinition('validator.builder')
->addMethodCall('addXmlMapping', array($validationFile));
return;
}
}
}
Edit Application\Sonata\UserBundle\ApplicationSonataUserBundle:
class ApplicationSonataUserBundle extends Bundle
{
/**
* {#inheritdoc}
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ValidationPass());
}
}
4. Override the default validation groups in the UserAdmin class of Sonata (within an overloaded UserAdmin class)
/**
* {#inheritdoc}
*/
public function getFormBuilder()
{
$this->formOptions['data_class'] = $this->getClass();
$options = $this->formOptions;
$options['validation_groups'] =
(!$this->getSubject() || is_null($this->getSubject()->getId())) ? 'CustomRegistration' : 'CustomProfile';
$formBuilder = $this->getFormContractor()->getFormBuilder( $this->getUniqid(), $options);
$this->defineFormBuilder($formBuilder);
return $formBuilder;
}
I don't know if there was a simpler way to achieve this. Nevertheless, it works for me.

Related

Symfony - Sonata admin - override validation

i'm using sonata admin, i tried to override max length allowed for name of categorie
I have an entity MyEntity who extend Application\Sonata\ClassificationBundle\Entity\Category
// MyEntity admin class
I put this following function, regarding https://sonata-project.org/bundles/core/master/doc/reference/conditional_validation.html#inline-validation
public function validate(\Sonata\Form\Validator\ErrorElement $errorElement, $object)
{
parent::validate($errorElement, $object);
$errorElement->with('name')
->assertLength(['max' => 100])
;
}
Current display
Expected to get ride of this 32 max length on name's field
Thx for helping
It looks like what you need to do instead, is override this validation config: https://github.com/sonata-project/SonataClassificationBundle/blob/3.x/src/Resources/config/validation.xml
<class name="Sonata\ClassificationBundle\Model\Category">
<property name="name">
<constraint name="NotBlank"/>
<constraint name="Length">
<option name="min">2</option>
<option name="max">32</option>
</constraint>
</property>
</class>

Adding a search box to the homepage of a website built on Sylius

Could someone please explain how I can add a search box to the homepage of my website built on Sylius?
I have tried using the SyliusSearchBundle but that seems to be very out of data and the sample config settings throw errors.
I'd be grateful for any help.
Disclaimer
Steps below work at the time I'm writing. SyliusElasticSearchBundle plugin is in a heavy development state, some of the things crash now, but will be fixed for sure.
I've created a demo repository here: https://github.com/mheki/sylius-search-demo
You need running ElasticSearch and a SyliusElasticSearchBundle plugin.
Follow installation instructions from readme: https://github.com/Lakion/SyliusElasticSearchBundle
Just bear in mind to install sylius dev-master (circular dependency...)
Import bundle's config, routing, enable it in AppKernel.
At the moment I'm writing the bundle needed filter_sets config, otherwise it crashed
So start with the simple search by product name:
lakion_sylius_elastic_search:
filter_sets:
default:
filters:
name:
type: string
Populate elastic index with:
bin/console fos:elastic:pop
Override original routing for lakion_elastic_search_shop_product_index - use filter_set: for your channel code.
lakion_elastic_search_shop_product_index:
path: /products
methods: [GET]
defaults:
_controller: lakion_sylius_elastic_search.controller.search:filterAction
_sylius:
template: "#LakionSyliusElasticSearch/Product/index.html.twig"
resource_class: "%sylius.model.product.class%"
filter_set: default
requirements:
slug: .+
Original product index page was failing for me and I had to remove
{{ form_row(form.search) }}
from it. So copied #LakionSyliusElasticSearch/Product/index.html.twig into Resources directory:
Resources\LakionSyliusElasticSearchBundle\views\Product\index.html.twig and made that change.
Now the last thing is to create a form, for example copying the file _security.html.twig from SyliusShopBundle. Add something like this:
<div class="item">
<form action="{{ path('lakion_elastic_search_shop_product_index') }}" method="get">
<div class="ui icon input">
<input type="text" placeholder="{{ 'sylius.ui.search'|trans }}..." name="filter_set[name]" />
<button type="submit" class="ui button mini">
<i class="search icon"></i>
</button>
</div>
</form>
</div>
and here we go :)
Alternatively, if you don't want to use the SyliusElasticSearchBundle plugin, you can implement a simple search on your own (which search inside the product name, description and category name) :
Create a classic html search form, for example:
<form role="search" method="get" action="{{path('app_search_results') }}">
<input type="search" id="site-search" name="q"
placeholder="{{ 'walrus.search.input.placeholder'|trans }}"
aria-label="{{ 'walrus.search.aria.label'|trans }}">
<button>{{ 'walrus.search.aria.button'|trans }}</button>
</form>
Create a controller action for the route 'app_search_results' in order to get the search request sent by your search form :
<?php
namespace AppBundle\Controller;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Locale\Context\LocaleContextInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use \Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class SearchController extends Controller
{
/**
* #var LocaleContextInterface
*/
private $locale;
/**
* #var ChannelContextInterface
*/
private $channelContext;
/**
* #var RepositoryInterface
*/
private $productRepository;
public function __construct(
LocaleContextInterface $locale,
ChannelContextInterface $channelContext,
RepositoryInterface $productRepository
)
{
$this->locale = $locale;
$this->channelContext = $channelContext;
$this->productRepository = $productRepository;
}
/**
* #Route("/search", name="app_search_results")
*/
public function searchAction(Request $request) : Response
{
$searchTerm = $request->query->get('q');
$channel = $this->channelContext->getChannel();
$localeCode = $this->locale->getLocaleCode();
$products = $this->productRepository->findByTerm($channel, $localeCode, $searchTerm);
return $this->render(
'#App/search/searchListProducts.html.twig',
[
'products' => $products
]
);
}
}
Don't forget to manually wire the dependency injected through the constructor.
Override the product repository in order to implements the findByTerm() method :
<?php
namespace AppBundle\Repository;
use Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductRepository as
BaseProductRepository;
use Sylius\Component\Core\Model\ChannelInterface;
class ProductRepository extends BaseProductRepository
{
public function findByTerm(ChannelInterface $channel, string $locale, $searchTerm): array
{
$qb = $this->createQueryBuilder('p')
->addSelect('translation')
// get the translated product for the product regarding the current locale
->innerJoin('p.translations', 'translation', 'WITH', 'translation.locale = :locale')
->orWhere('translation.name LIKE :searchTerm')
->orWhere('translation.description LIKE :searchTerm')
// get the taxons of the product
->innerJoin('p.productTaxons', 'productTaxon')
->innerJoin('productTaxon.taxon', 'taxon')
// get the translated taxon
->innerJoin('taxon.translations', 'taxonTranslation', 'WITH', 'taxonTranslation.locale = :locale')
->orWhere('taxonTranslation.name LIKE :searchTerm')
->andWhere(':channel MEMBER OF p.channels')
->andWhere('p.enabled = true')
->setParameter('searchTerm', '%'.$searchTerm.'%')
->setParameter('locale', $locale)
->setParameter('channel', $channel)
->getQuery();
return $qb->getResult();
}
}
You just need now to create the page rendering the list of product (the #App/search/searchListProducts.html.twig). And to tell Sylius to use your custom HomepageController instead of the original one. Same goes for your Product Repository. This can be done inside the services.yml file of you app folder :
services:
sylius.controller.shop.homepage:
public: true
class: AppBundle\Controller\Shop\HomepageController
arguments:
- '#templating'
- '#sylius.context.locale'
- '#sylius.repository.taxon'
sylius_product:
resources:
product:
classes:
repository: AppBundle\Repository\ProductRepository

Duplicated password validation messages ONLY IF new password is 1 char long

My problem is password validation message appears twice in Registration and Change Password forms. As most posts/solutions points out "groups", how do I implement it into my code below? I tried to implement other solved examples as shown below but I cannot get it working. Maybe because I never worked FOSUserBundle before.
FOSUserBundle - Validation for username, password or email
fields
Duplicated errors for constraints when using several validation
groups
Symfony2 FOSUserBundle extending registration form causes duplicate
email to validate
validating fosuserbundle registration form
Validation of a form - I'm getting the labels two times
myapp/app/config/config.yml
fos_user:
db_driver: orm
firewall_name: main
user_class: WebsiteBundle\Entity\User
model_manager_name: websitemanager
registration:
form:
type: website_user_registration
change_password:
form:
type: fos_user_change_password
validation_groups: [ChangePassword, Default]
WebsiteBundle/Resources/translations/validators.en.yml
fos_user:
password:
short: "[-Inf,Inf]The password must contain at least 8 characters"
services.xml
<service id="myapp_website.registration.form.type"
class="myapp\WebsiteBundle\Form\Type\RegistrationFormType">
<tag name="form.type" alias="website_user_registration" />
<argument>myapp\WebsiteBundle\Entity\User</argument>
</service>
WebsiteBundle/Form/Type/RegistrationFormType.php
namespace myapp\WebsiteBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use Symfony\Component\Validator\Constraints\IsTrue;
class RegistrationFormType extends BaseType
{
public function __construct($class)
{
parent::__construct($class);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->add(
'terms',
'checkbox',
[
'label' => 'Older than 18',
'constraints' => [
new IsTrue([
'message' => 'Are you older than 18?',
]),
],
'required' => true,
'mapped' => false,
]
);
}
public function getName()
{
return 'website_user_registration';
}
}
validation.xml
<?xml version="1.0" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="FOS\UserBundle\Model\User">
<property name="plainPassword">
<constraint name="NotBlank">
<option name="message">fos_user.password.blank</option>
<option name="groups">
<value>Registration</value>
<value>ResetPassword</value>
<value>ChangePassword</value>
</option>
</constraint>
<constraint name="Length">
<option name="min">8</option>
<option name="minMessage">fos_user.password.short</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
<value>ResetPassword</value>
<value>ChangePassword</value>
</option>
</constraint>
</property>
<property name="email">
<constraint name="NotBlank">
<option name="message">Please enter your email address</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
</option>
</constraint>
</property>
<property name="username">
<constraint name="NotBlank">
<option name="message">Please enter your name</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
</option>
</constraint>
</property>
</class>
</constraint-mapping>
Twig
{% trans_default_domain 'FOSUserBundle' %}
<form action="{{ path('fos_user_change_password') }}" {{ form_enctype(form) }} method="POST">
{{ form_row(form.current_password, {'label': 'Current Password'}) }}
{{ form_row(form.plainPassword.first, {'label': 'New Password'}) }}
{{ form_row(form.plainPassword.second, {'label': 'Confirm Password'}) }}
{{ form_rest(form) }}
<br /><input id="submit" type="submit" value="Change password" />
</form>
HTML result
<label for="fos_user_registration_form_plainPassword_first">Password</label>
<input type="password" id="fos_user_registration_form_plainPassword_first" name="fos_user_registration_form[plainPassword][first]" required="required" />
<label for="fos_user_registration_form_plainPassword_second">Confirm password</label>
<input type="password" id="fos_user_registration_form_plainPassword_second" name="fos_user_registration_form[plainPassword][second]" required="required" />
Errors
When I have validators.en.yml in code-base.
When I remove validators.en.yml from code-base.
Have a look at the group_sequence feature for validations.
# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\User:
group_sequence:
- User
- Strict
getters:
passwordLegal:
- 'IsTrue':
message: 'The password cannot match your username'
groups: [Strict]
properties:
username:
- NotBlank: ~
password:
- NotBlank: ~
In this example, it will first validate all constraints in the group
User (which is the same as the Default group). Only if all constraints
in that group are valid, the second group, Strict, will be validated.
This is taken straight of the Symfony documentation at http://symfony.com/doc/current/book/validation.html#group-sequence
However personally I could never get this feature to work.

fosuser bundle - changing password pattern

I want to change the password pattern of FosUser Bundle.. (lets say, i will require the password to be minumum of 6 characters which also require 1 letter and 1 number. Where do i set this?
Yuo can do this in FosUserBundle validator.
Take a look at this file:
/vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Resources/config/validation.xml
Particularly:
<property name="plainPassword">
<constraint name="NotBlank">
<option name="message">fos_user.password.blank</option>
<option name="groups">Registration</option>
</constraint>
<constraint name="MinLength">
<option name="limit">2</option>
<option name="message">fos_user.password.short</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
</option>
</constraint>
</property>
I don't know (but I suppose that I'm right) if exists a constraint of type "1 letter and 1 number".
In that case, you have to "build" it yourself and use in the same way you use the NotBlank and MinLenght constraint of this example
I've add into User entity
// src/Acme/AcmeBundle/Entity/User.php
....
class User extends BaseUser{
....
/**
* #Assert\NotBlank(message="fos_user.username.blank", groups={"Registration", "Profile"})
* #Assert\Length(min=5, max="255",
* minMessage="fos_user.username.short", maxMessage="fos_user.username.long",
* groups={"Registration", "Profile"})
*/
protected $username;
/**
* #Assert\NotBlank(message="fos_user.password.blank", groups={"Registration", "ResetPassword", "ChangePassword"})
* #Assert\Length(min=6,
* minMessage="fos_user.password.short",
* groups={"Registration", "Profile", "ResetPassword", "ChangePassword"})
*/
protected $plainPassword;

symfony2 override model validation

i'm using FOS User Bundle and I want to override the validation file FOS/UserBundle/Resources/config/validaiton.xml:
<constraint name="FOS\UserBundle\Validator\Unique">
<option name="property">usernameCanonical</option>
<option name="message">fos_user.username.already_used</option>
<option name="groups">
<!-- <value>Registration</value> -->
<value>Profile</value>
</option>
</constraint>
username is not in my Registration form (I just set it to hidden), that's the validation should not produce any error...
Maybe there is a better way to remove the username of the form...
To remove the username field of the form in a proper way, you should override the RegistrationFormType, creating your own and extending it from the original FOSUserBundle
<?php
namespace Acme\UserBundle\Form\Type;
use Symfony\Component\Form\FormBuilder;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
class RegistrationFormType extends BaseType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('email', 'email')
->add('plainPassword', 'repeated', array('type' => 'password'))
// your other custom fields, if any.
}
}
Now, you should declare the overrided form as a service, and then tell to FOSUserBundle configuration file that you are using now an overrided form. Here is the complete documentation.
You can put entity validation info in any validation.yml file. So you can do
#validation.yml
FQCN\Of\User\Entity:
constraints:
- FOS\UserBundle\Validator\Unique:
property: usernameCanonical
groups: [Profile]
message: fos_user.username.already_used
properties:
# property validations here

Resources