Symfony 2.5.6 breaking login? - symfony

I'm following the login/security conventions, everything ok with Symfony 2.3 to 2.55 , until I updated to 2.5.6. Now the Login url is displaying:
Neither the property "_username" nor one of the methods "getUsername()", "username()", "isUsername()", "hasUsername()", "__get()" exist and have public access in class "Acme\DemoBundle\Entity\Login".
Any idea to solve this?
namespace Acme\DemoBundle\Entity;
class Login {
protected $_username;
protected $_password;
protected $_remember_me;
public function get_Username() {
return $this->_username;
}
public function set_Username($username) {
$this->_username = $username;
}
public function get_Password() {
return $this->_password;
}
public function set_Password($password) {
$this->_password = $password;
}
public function get_RememberMe() {
return $this->_remember_me;
}
public function set_RememberMe($remember) {
$this->_remember_me = $remember;
}
}

The name of the fields is configurable. You can change _username and _password fields to anything you want and set them in security.yml. For example as I use custom LoginType (alias login_form), I have this in security.yml:
security:
firewalls:
secured_area:
username_parameter: login_form[username]
password_parameter: login_form[password]
This allows me to have $username and $password variables in the Login Model.

Symfony standard Login needs that you submit a _username and _password named inputs.
Initially my entity user properties were username and password.
Unfortunately I found that twig rendered the inputs ignoring the attr{name='_username'} sent from the formtype builder. So I took the shortcut of renaming the entities properties to the underscore version.
Then everything worked fine until I updated Symfony to 2.5.6 version.
Then as Cerad suggested, I got rid of the entity underscores and form id... but still needed to pass the input names with the underscores. So I overrode this Wig block, just changing the name assignation from the first line to the last one:
{% block widget_attributes -%}
id="{{ id }}"
{%- if read_only %} readonly="readonly"{% endif -%}
{%- if disabled %} disabled="disabled"{% endif -%}
{%- if required %} required="required"{% endif -%}
{%- for attrname, attrvalue in attr -%}
{{- " " -}}
{%- if attrname in ['placeholder', 'title'] -%}
{{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}"
{%- elseif attrvalue is sameas(true) -%}
{{- attrname }}="{{ attrname }}"
{%- elseif attrvalue is not sameas(false) -%}
{{- attrname }}="{{ attrvalue }}"
{%- endif -%}
{%- endfor -%}
name={{full_name}}
{%- endblock widget_attributes %}

Related

EasyAdmin 3.X - How to see related entities `toString` instead of the number of association in the list?

I have an entity Product with a ManyToMany relation to an entity Category
/**
* #ORM\ManyToMany(targetEntity="App\Domain\Category", inversedBy="stalls")
*/
private $categories;
//...
/**
* #return Collection|Category[]
*/
public function getCategories(): Collection
{
return $this->categories;
}
In the ProductCrudController class I have the following configureFields method:
public function configureFields(string $pageName): iterable
{
return [
Field::new('name'),
Field::new('description'),
AssociationField::new('categories'),
];
}
When creating/editing a Product everything works as expected in the relation, but in the list of products instead of showing the related categories I see the number of categories the product has. How can I change this behaviour?
In the following image the first product has 1 category and the second one in the list has 2 different categories. I would like the name of the categories to be shown here.
As a side note: Category class has a __toString method returning the name of the category.
EDIT:
The behaviour I am looking for is the same as the Tags column in the following image:
You can make a template for that like so:
// somewhere here templates/admin/field/category.html.twig
{% for category in field.value %}
{%- set url = ea_url()
.setController('Path\\To\\Your\\CategoryCrudController')
.setAction('detail')
.setEntityId(category.id)
-%}
<a href="{{ url }}">
{{ category.name }}{% if not loop.last %}, {% endif %}
</a>
{% else %}
<span class="badge badge-secondary">None</span>
{% endfor %}
And just add it to the field
// in ProductCrudController
AssociationField::new('categories')->setTemplatePath('admin/field/category.html.twig'),
You can format the value using the method formatValue like this :
->formatValue(function ($value, $entity) {
$str = $entity->getCategories()[0];
for ($i = 1; $i < $entity->getCategories()->count(); $i++) {
$str = $str . ", " . $entity->getCategories()[$i];
}
return $str;
})
I had the same issue on my detail page. So instead of a template, I change the field type depending on the pagename
if (Crud::PAGE_DETAIL === $pageName) {
$field = ArrayField::new('field')->setLabel('label');
} else {
$field = AssociationField::new('field')->setLabel('label');
}
I will do that way :
->formatValue(function ($value, $entity) {
return implode(",",$entity->getCategories()->toArray());
})
Building on top of the most upvoted answer, you could make the Twig snippet universal like this:
{% for member in field.value %}
{%- if field.customOption('crudControllerFqcn') is not empty -%}
{%- set url = ea_url()
.setController(field.customOption('crudControllerFqcn'))
.setAction('detail')
.setEntityId(member.id)
-%}
<a href="{{ url }}">
{{ member }}
</a>
{%- else -%}
{{ member }}
{%- endif -%}
{%- if not loop.last %}, {% endif -%}
{% else %}
<span class="badge badge-secondary">None</span>
{% endfor %}
This way you could set the link destination in your CRUD controller and use the template everywhere. When no CRUD controller is not set, you will still have the individual items enumerated, but not as links.
In my TeamCrudController, I'm doing this:
public function configureFields(string $pageName): \Generator
{
yield TextField::new('name')
->setDisabled()
;
$members = AssociationField::new('members')
->hideOnForm()
;
if (Crud::PAGE_DETAIL === $pageName) { // I want to see the number on INDEX, but the list on DETAIL
$members
->setCrudController(EmployeeCrudController::class)
->setTemplatePath('admin/field/expanded_association_field.html.twig')
;
}
yield $members;
}
Alternatively, if you would like to become the default behaviour, override the EasyAdminBundle template by placing the following content in templates/bundles/EasyAdminBundle/crud/field/association.html.twig:
{# #var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# #var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
{# #var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{% if 'toMany' == field.customOptions.get('associationType') %}
{% if ea.crud.currentPage == 'detail' %}
{% for member in field.value %}
{%- if field.customOption('crudControllerFqcn') is not empty -%}
{%- set url = ea_url()
.setController(field.customOption('crudControllerFqcn'))
.setAction('detail')
.setEntityId(member.id)
-%}
<a href="{{ url }}">
{{- member -}}
</a>
{%- else -%}
{{ member }}
{%- endif -%}
{%- if not loop.last %}, {% endif -%}
{% else %}
<span class="badge badge-secondary">None</span>
{% endfor %}
{% else %}
<span class="badge badge-secondary">{{ field.formattedValue }}</span>
{% endif %}
{% else %}
{% if field.customOptions.get('relatedUrl') is not null %}
{{ field.formattedValue }}
{% else %}
{{ field.formattedValue }}
{% endif %}
{% endif %}

Sonata admin bundle: exclude custom admin from global search

Within my Symfony 3.4 project, I have 2 custom admins. Specially created for reporting services. Those admins do not have specific entities.
For the custom admins, I followed the Symfony recipe:
https://symfony.com/doc/3.x/bundles/SonataAdminBundle/cookbook/recipe_custom_view.html
Now, when searching items through the sonata global search, I get a
"Class does not exist" error in
vendor/sonata-project/admin-bundle/src/Resources/views/Core/search.html.twig.
This error is related to the custom admins.
Is there a solution to exclude these custom admins from the global search or to resolve this error?
Admin:
<?php
namespace MainBundle\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Route\RouteCollection;
class AccessRightsAdmin extends AbstractAdmin
{
protected $baseRoutePattern = 'accessrights';
protected $baseRouteName = 'Accessrights';
protected function configureRoutes(RouteCollection $collection)
{
$collection->clearExcept(array('list'));
$collection->add('accesRights', 'accessrights');
}
}
Service
services:
system.admin.accessrights:
class: MainBundle\Admin\AccessRightsAdmin
arguments: [~, ~, MainBundle:AccessRightsAdmin]
tags:
- { name: sonata.admin, manager_type: orm, group: sonata.admin.group.System, label: Accessrights }
calls:
- [ setTranslationDomain, [SonataAdminBundle]]
public: true
I found a solution and I'm going to leave it here in case someone need it.
The solution basically is to override the search.html.twig and ignore the admin you want from the search like so:
{% extends base_template %}
{% block title %}{{ 'title_search_results'|trans({'%query%': query}, 'SonataAdminBundle') }}{% endblock %}
{% block breadcrumb %}{% endblock %}
{% block content %}
<h2 class="page-header">{{ 'title_search_results'|trans({'%query%': query}, 'SonataAdminBundle') }}</h2>
{% if query is defined and query is not same as(false) %}
{% set count = 0 %}
<div class="row" data-masonry='{ "itemSelector": ".search-box-item" }'>
{% for group in groups %}
{% set display = group.roles is empty or is_granted(sonata_admin.adminPool.getOption('role_super_admin')) or group.roles|filter(role => is_granted(role))|length > 0 %}
{% if display %}
{% for admin in group.items %}
{% set count = count + 1 %}
{% if admin.code != 'bundle.admin.admin_to_ignore' %}{# in this line right here add the admin you want to ignore in your search #}
{% if admin.hasRoute('create') and admin.hasAccess('create') or admin.hasRoute('list') and admin.hasAccess('list') %}
{{ sonata_block_render({
'type': 'sonata.admin.block.search_result'
}, {
'query': query,
'admin_code': admin.code,
'page': 0,
'per_page': 10,
'icon': group.icon
}) }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endblock %}
To override the file you need to put it under the following path:
app -> Resources -> SonataAdminBundle -> views -> Core -> search.html.twig

Exception while rendering form to insert new entity [symfony2]

I have problem to render view for creating new entity and can't find existing problem here being answered so I am going to ask...
My app has entity X that belongs to one entity Y and can have many entities Z.
When console runs, it executes well with all those relations.
php app\console doctrine:schema:update --force,
After crud generating for entity X, listing page shows fine, but page for creating new record throws following exception:
An exception has been thrown during the rendering of a template
("Warning: call_user_func_array() expects parameter 1 to be a valid
callback, class 'Symfony\Bridge\Twig\Extension\FormExtension' does not
have a method 'renderer->humanize' in
%path_to_app%\app\cache\dev\twig\16\16033db1d32d7d10db7a0d24db2f49938a4b2e9a63d231d90bf70d1969563fd0.php
line 880") in form_div_layout.html.twig at line 232.
What could be the problem?
Update 1:
Exception's trigger lays in twig file where data is passed from controller.
At {{ form_widget(form) }}
Update 2:
//controller's method
/**
*
*
* #Route("/new", name="class_new")
* #Template()
*/
public function newAction()
{
$entity = new Class();
$form = $this->createForm(new ClassType(), $entity);
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
//Form class
class ClassType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Admin\MainBundle\Entity\Class'
));
}
public function getName()
{
return 'admin_mainbundle_classtype';
}
}
// view
<form action="{{ path('class_create') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<p>
<button type="submit">Create</button>
</p>
</form>
Try to empty the cache : php bin/console cache:clear and rerun your command.
If problem persit: look at in your controller if you are used a undefined function for exemple renderer->humanize and look layout.html.twig at line 232.
I don't see any problem in your controller and view.
So i tried to look for in form_div_layout.html.twig file and i found that function humanize is used there and the exception says that function isn't defined.
So can you update your project by : composer install
form_div_layout.html.twig :
{%- block form_label -%}
{% if label is not same as(false) -%}
{% if not compound -%}
{% set label_attr = label_attr|merge({'for': id}) %}
{%- endif -%}
{% if required -%}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
{%- endif -%}
{% if label is empty -%}
{%- if label_format is not empty -%}
{% set label = label_format|replace({
'%name%': name,
'%id%': id,
}) %}
{%- else -%}
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}</label>
{%- endif -%}
{%- endblock form_label -%}
{%- block button_label -%}{%- endblock -%}
I solved this few weeks ago but now I spotted time for answering.
This was giving me headache before moving the app version from 2.1 to 2.8. Nowadays it works well.

How do you check if a form field has data in twig template that is rendering a controller

This is a follow up question to this one . What I want to know is how to check if the entity variable exist/is defined/not null. I thought I could do this:
{% if entity.orgId is defined %}
{{ render(controller(
'CompanyNameofBundle:OrgMember:test', {'orgid':entity.orgId})) }}
{% endif %}
But if entity.orgId is null I get an exception has been thrown during the rendering of a template ("The product does not exist").
Change your controller to return null instead of exception:
public function testAction($orgid = null) {
if (!$orgid) { return null; }
// Rest of code.
}
You have two options:
Don't call the render controller using the check
{% if entity.orgId is defined and entity.orgId is not null %}
Make the testAction in the OrgMemberController null-safe (check if the parameter orgid is null)
Try this:
{% if entity.orgId is defined %}
{% if entity.orgId is null %}
{# do something #}
{% else %}
{# do anythingelse #}
{% endif %}
{% endif %}

Get ROLE of a user not logged in TWIG Symfony2

I would like to know how can i know if a user is granted when it's not the current user in twig.
I use this code for the current user:
{% if is_granted('ROLE_USER') %}
Delete
{% endif %}
But i would like to be able to do the same thing with ohter users that are not logged in at the moment.
Thank you.
Edit:
In fact i think there isn't a direct way with twig to test role of a user that is not authenticated.
So i did it directly in the twig template, test if a user is admin or not, then set var.
(in my question i was searching how to do in a list of users.)
{% set from_user_is_admin = false %}
{% for role in from_user.getRoles() %}
{% if role == 'ROLE_ADMIN' %}{% set from_user_admin = true %}{% endif %}
{% if role == 'ROLE_SUPER_ADMIN' %}{% set from_user_admin = true %}{% endif %}
{% endfor %}
{% if from_user_admin == false %}THIS USER IS NOT ADMIN{% endif %}
I think it would be much easier if you implemented an isGranted function in the User entity:
Class User implements UserInterface {
...
public function isGranted($role)
{
return in_array($role, $this->getRoles());
}
}
You can now easily check for granted roles in every layer of your application.
In PHP:
$user->isGranted("USER_ADMIN")
Or in Twig:
user.granted("USER_ADMIN")
If you need to check a role for the current user, you can do this in Twig:
app.user.granted("USER_ADMIN")
Note: the variable "app" is globally defined.
Note 2: this code may throw an exception if you use it outside the secured area of your app, since app.user would be NULL.
You can use similar statement to the above with "not" :
{% if not is_granted('ROLE_USER') %}
Delete
{% endif %}
or use else statement:
{% if is_granted('ROLE_USER') %}
Delete
{% else %}
{# something else for guest user, not logged in #}
{% endif %}
You should create either a twig macro, or a twig function.
Creating a macro is very simple, using your code:
{% macro admin_status(from_user) %}
{% set from_user_is_admin = false %}
{% for role in from_user.getRoles() %}
{% if role == 'ROLE_ADMIN' %}{% set from_user_admin = true %}{% endif %}
{% if role == 'ROLE_SUPER_ADMIN' %}{% set from_user_admin = true %}{% endif %}
{% endfor %}
{% if from_user_admin == false %}THIS USER IS NOT ADMIN{% endif %}
{% endmacro %}
You can then use it in the same file as {% _self.admin_status(user) %}. You may also move it to a separate file, and use twig's import tag to gain access to it.
Creating a twig function is a better option, for details see extending twig. It boils down to creating a regular function, that may be called from twig, so code like this becomes possible:
{% if user_is_admin(user) %}
You'll also need to read enabling custom twig extensions.
i did it this way, have this snippet in the global twig file, in my case layout.html.twig
{% set is_admin = false %}
{% if app.security.token.user.roles is iterable %}
{% for role in app.security.token.user.roles %}
{% if role == 'ROLE_ADMIN' or role == 'ROLE_SUPER_ADMIN' %}
{% set is_admin = true %}
{% endif %}
{% endfor %}
{% endif %}
then i can use anywhere
{% if is_admin %}....{% endif %}

Resources