Sonata admin bundle: exclude custom admin from global search - symfony

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

Related

Sonata Admin Bundle : change page title

A little question : how can I change < title > < /title > with Sonata Admin ?
Everywhere there is "Admin...". And when I override a page with sonata template I got "Admin".
Thank's.
You can easy do that by override the sonata admin file translation
1- Create folder structure in traslations like this :
2- then in the SonataAdminBundle.[your-language-code].yml file add :
Admin: "You Custom title Here"
You have to override Sonata admin's standard_layout.html.twig file to achieve this.
First, define the file in sonata admin config file.
config/packages/sonata_admin.yaml
sonata_admin:
templates:
layout: 'sonata_admin/layout.html.twig'
Create a twig file named layout.html.twig inside templates/sonata_admin/ and just paste the sonata_head_title block from the bundle.
{% extends '#SonataAdmin/standard_layout.html.twig' %}
{% block sonata_head_title %}
{{ 'Admin'|trans({}, 'SonataAdminBundle') }} //remove this line to get rid of text "Admin"
{% if _title is not empty %}
{{ _title|striptags|raw }}
{% else %}
{% if action is defined %}
-
{% for menu in breadcrumbs_builder.breadcrumbs(admin, action) %}
{% if not loop.first %}
{% if loop.index != 2 %}
>
{% endif %}
{%- set translation_domain = menu.extra('translation_domain', 'messages') -%}
{%- set label = menu.label -%}
{%- if translation_domain is not same as(false) -%}
{%- set label = label|trans(menu.extra('translation_params', {}), translation_domain) -%}
{%- endif -%}
{{ label }}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endblock %}

SonataAdminBundle templates - list and show fields template content duplication

My list and show fields contain the same content, but due the extending of base_list_field and base_show_field templates I have to create two separate templates.
list/location.html.twig:
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field %}
{{ object.getCity }}, {{ object.getCountry.getName }}
{% endblock %}
show/location.html.twig:
{% extends 'SonataAdminBundle:CRUD:base_show_field.html.twig' %}
{% block field %}
{{ object.getCity }}, {{ object.getCountry.getName }}
{% endblock %}
As you can see huge part of code is duplicated. Is there a way to check the page I am currently in in twig and then extend certain base template? In this case I would be able to use one file instead of two for same content.
In twig it's possible to extend/include a dynamic template :
{# do test here on which page you are or pass through controller #}
{% if true %}
{% set template = 'SonataAdminBundle:CRUD:base_show_field.html.twig' %}
{% else %}
{% set template = 'SonataAdminBundle:CRUD:base_list_field.html.twig' %
{% endif %}
{% extends template %}
{% block field %}
{{ object.getCity }}, {{ object.getCountry.getName }}
{% endblock %}
(edit) if you don't want the hardcoded if I would pass the template variable through the controller and change the twig template to something like
{% extends template|default('SonataAdminBundle:CRUD:base_show_field.html.twig') %}

Toggle html validation globally

I've made a couple of twig extensions but I'm stumped on this one.
I have the following template logic that I want to make into an extension.
I need reuse this logic into many different forms instead of copying and pasting the following code everywhere:
{% if html5validation is not defined %}
{{ form_start(some_form) }}
{% else %}
{% if html5validation %}
{{ form_start(some_form) }}
{% else %}
{{ form_start
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
With the above code from the controller I can do the following to turn the html5 validator on and off:
$this->render(..., array(html5validation => false));
I want put the template logic into the twig extension below...
I just don't know if it's possible to implement what I've done above in a twig extension.
class HTML5Validation extends \Twig_Extension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('html5validation', array($this, 'setValidation')),
);
}
public function setValidation($boolean)
{
//Implement the same logic as the twig template.
}
public function getName()
{
return 'html5validator';
}
}
The short answer is no - you can't do this using a twig extension, it's not what they're meant for.
Looking at your template fragment I'd say you need to customise the form_start block. To do this see Symfony Form Theming and How to customise form rendering.
EDIT: This solution does not work if your customised code requires local twig variables - only global twig variables are available for form theming. You can define your own twig globals in config.yml or in a twig extension.
For example, to override form_start globally, you find the default definition of the form_start block in form_div_layout.html.twig, copy it into your own form theme file e.g. YourBundle/Form/fields.html.twig, modify it as required and and update the twig configuration to apply your form theme file. Something like this:
{# src/YourBundle/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation is not defined %}
{{ parent() }}
{% else %}
{% if html5validation %}
{{ parent() }}
{% else %}
{{ parent
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
{%- endblock form_start %}
Config:
# app/config/config.yml
twig:
form:
resources:
- 'YourBundle:Form:fields.html.twig'
I actually found a better way to do what I wanted.
As a plus it works globally instead of having to populate more fields into your controller!
In YourBundle/Resources/views/validation.toggle.html.twig
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation is defined and html5validation == false %}
{% set attr = attr|merge({'novalidate': 'novalidate'}) %}
{% endif %}
{{ parent() }}
{%- endblock form_start %}
Then if you want to turn off html5 validation across the whole website:
# app/config/config.yml
twig:
global:
html5validation: false
Or
Even better just use it in your dev_config.yml if you want validation on by default on production mode but the ability to toggle validation on and off for dev mode.
# app/config/dev_config.yml
twig:
global:
html5validation: false
resources:
- 'YourBundle::validation.toggle.html.twig'
Finally use it in your twig template normally:
{% form_theme your_form 'YourBundle::validation.toggle.html.twig' %}
form_start(your_form)
Reusable and non invasive, exactly like I wanted it. :)
I got the hint from:
https://github.com/symfony/symfony/issues/11409#issuecomment-49358377
In the absence of a more elegant solution, you can always put the twig fragment given in your question into a separate file and use twig include from your various forms. The included fragment has access to the variables from the surrounding context:
{# YourBundle/Resources/views/form_start.html.twig #}
{% if html5validation is not defined %}
{{ form_start(some_form) }}
{% else %}
{% if html5validation %}
{{ form_start(some_form) }}
{% else %}
{{ form_start
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
Then in the twig file for the form:
{% include 'YourBundle::form_start.html.twig' %}
If you typically pass a 'form' variable into render() in your controller(s) then you can use that in your form_start fragment. Otherwise you can pass the appropriate form in as a variable:
{% include 'YourBundle::form_start.html.twig' with {'form': localForm} %}

symfony2: Applying theme to individual field for collection type

Just applying a theme to form field is easy e.g.:
{% form_theme form _self %}
{% block _product_name_widget %}
{{ block('field_widget') }}
{% endblock %}
but what if the form field is of collection type? E.g.: product['models'][1][comment,
I have no ideas how to customize it. (This may be the first kind question here)
UPDATE: anwser here: Symfony2: collection form field type with data-prototype
As of Symfony 2.1 (it may work as well for 2.0 but not sure) to apply theme for collection you do this way:
Lets say we have a collection of products (we have multiple Product entities)
Controller:
$repository = $this->getDoctrine()->getRepository('ExampleBundle:Product');
$products = $repository->findAll();
$productCollection = new Products;
foreach ($products as $product) {
$productCollection->getProducts()->add($product);
}
$collection = $this->createForm(new ProductsType, $productCollection);
return $this->render('ExampleBundle:Default:index.html.twig', array(
'collection' => $collection->createView()
));
Your theme can look like this:
{% block _productsType_products_entry_name_row %}
<div class="yourDivName">{{ block('form_widget') }}</div>
{% endblock %}
{% block _productsType_products_entry_description_row %}
<div class="yourDivDescription">{{ block('form_widget') }}</div>
{% endblock %}
The trick is with "entry" where twig will do the job for you to apply above changes to each row and for each field you specify
Hope that helps!
You can do with override collection_widget block:
{% block collection_widget %}
{% spaceless %}
{% if prototype is defined %}
{% set attr = attr|merge({'data-prototype': form_row(prototype) }) %}
{% endif %}
{{ block('form_widget') }}
{% endspaceless %}
{% endblock collection_widget %}
If you want how to customize form collection type try to look at this Product Bundle
You can override the collection_widget theme for a single field by referencing the widget like this as well.
For example, if "categories" is a "collection" widget on a ProductType form, you can do this.
{% block _product_categories_widget %}
{% for child in form %}
{{- form_row(child) -}}
<hr>
{% endfor %}
{% endblock %}

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