Symfony Easyadmin - How to add a custom action near "btn action-new"? - symfony

I would like to add a custom action near " btn action-new" in list page.
I try:
entities:
Pratiquant:
class: AppBundle/Entity/Pratiquant
actions:
- {name: 'fichePresence', type: 'method', action: 'fichePresence', label: 'fiche de presence' }
I don't need this:
entities:
Pratiquant:
class: AppBundle/Entity/Pratiquant
list:
actions:
- {name: 'fichePresence', type: 'method', action: 'fichePresence', label: 'fiche de presence' }
Hope someone understand me!

Your configuration is correct ... but it doesn't do what you want to achieve. Right now, all the actions configured for the list view are considered actions for the items displayed in the listing. There is no built-in way to define "global actions" for list view.
In any case, you can do what you want by overriding a small fragment of the list template. To do so, create the following Twig template (it's very important to store it in that exact location):
{# app/Resources/views/easy_admin/Pratiquant/list.html.twig #}
{% extends '#EasyAdmin/default/list.html.twig' %}
{% block view_actions %}
{{ parent() }}
Fiche de presence
{% endblock %}
This will execute the fichePresenceAction() method of your custom AdminController.

With EasyAdmin 3 :
public function configureActions(Actions $actions): Actions
{
$fichePresence = Action::new('fichePresence', 'fiche de presence', 'fa fa-download')
->linkToCrudAction('fichePresenceAction')
->createAsGlobalAction();
return $actions
->add(Crud::PAGE_INDEX, Action::DETAIL)
->add(Crud::PAGE_INDEX, $fichePresence);
}
See documentation

Related

Allow only ROLE_ADMIN to manage users in Sonata Admin

I have a Symfony 5.4 project using sonata-project/admin-bundle 4.9 and sonata-project/user-bundle 5.0.0-rc.1 and I want to let only the users with role ROLE_ADMIN to manage the users (CREATE/LIST/EDIT/DELETE), for other roles I want to hide the navbar menu entry and the dashboard entry for "Users".
In my config/packages/sonata_admin.yml I tried to specify a sonata.user.block.menu entry as found in some old questions but it seems that it does not exists anymore as the following error is thrown:
An exception has been thrown during the rendering of a template ("The block type "sonata.user.block.menu" does not exist").
As default I had only one admin_list block, I tried adding a sonata.block.service.rss and it is shown correctly in the dashboard but I can't find how to manage the Users block.
sonata_admin:
title: 'Sonata Admin'
dashboard:
blocks:
- { type: sonata.admin.block.admin_list, position: left }
#- { type: sonata.user.block.menu, position: right, roles: [ROLE_ADMIN]}
#- { type: sonata.block.service.rss, position: right, roles: [ROLE_ADMIN]}
templates:
layout: sonataLayout.html.twig
sonata_block:
blocks:
sonata.admin.block.admin_list:
contexts: [admin]
Any hints? Explicative picture following.
Note. "Disabled both based on Role". It`s not "just add/change 2 lines"
IMHO. "Basic" Sonata Admin`s configuration isn't too obvious. Therefore customize as much as possible -> to have more control.
I only could suggest U -> go this "right way" (surely,IMHO)
Create/manage the admin-menu with a event listener.
U may read/check good example there Using events to allow a menu to be extended and the official -> Sonata Admin -> KnpMenu
With such approach -> U can easy manage menu items by your Roles. + other advantages surely
From the very beginning -> create the custom templates. If U follow Flex & /templates/admin is the folder for Sonata Admin:
// config/packages/sonata_admin:
sonata_admin
....
templates:
....
layout: '/admin/standard_layout.html.twig'
knp_menu_template: '/admin/menu/knp_menu.html.twig'
dashboard: 'admin...
2* E.g. to extend the default layout. If your specific template do:
{% extends '#SonataAdmin/standard_layout.html.twig' %}
{% block sonata_nav %}
...
After these steps -> much more easy to control views by your Roles
So, as per documentation I added an event listener for the Menu and I was able to remove the entry from the left panel like this
//src/EventListener/MenuBuilderListener.php
<?php
namespace App\EventListener;
use Sonata\AdminBundle\Event\ConfigureMenuEvent;
use Symfony\Component\Security\Core\Security;
final class MenuBuilderListener
{
private $security;
public function __construct( Security $security)
{
$this->security = $security;
}
public function manageMenuItems(ConfigureMenuEvent $event): void
{
$menu = $event->getMenu();
$user = $this->security->getUser();
if(!$user->hasRole("ROLE_ADMIN")){
$menu->removeChild('sonata_user');
}
}
}
With the service registered here
//config/services.yaml
app.menu_listener:
class: App\EventListener\MenuBuilderListener
tags:
- { name: kernel.event_listener, event: sonata.admin.event.configure.menu.sidebar, method: manageMenuItems }
Then I added a firewall entry to manage permissions
//config/packages/security.yaml
access_control:
- { path: ^/admin/app/sonatauseruser/, role: [ROLE_ADMIN]}
Till now I was not able to remove the Users entry from the dashboard, I tried to extend the dashboard twig template but it seems that the Users entry is added somehow later.
{% extends '#SonataAdmin/Core/dashboard.html.twig' %}
{% block content %}
{% set has_left = false %}
{% dump(blocks.left) %} //this shows only the admin group and not the user group
{% for block in blocks.left %}
{% if not has_left and (block.roles|length == 0 or is_granted_affirmative(block.roles)) %}
{% set has_left = true %}
{% endif %}
{% endfor %}
....
{{ sonata_block_render_event('sonata.admin.dashboard.top') }}
....
{{ sonata_block_render_event('sonata.admin.dashboard.bottom') }}
I also tried to bind both the render events .top and .bottom to a ConfigureEvent Listener but they are not fired (not sure if this is the right class of listener).
For now I found a sub optimal solution, I did an override of the userAdmin class like this
<?php
namespace App\Admin;
use Sonata\UserBundle\Admin\Model\UserAdmin as BaseType;
class UserAdmin extends BaseType
{
protected function configureDashboardActions(array $actions): array
{
$actions = parent::configureDashboardActions($actions);
unset($actions['list']);
unset($actions['create']);
return $actions;
}
}
And registered it in
//config/packages/sonata_user.yaml
sonata_user:
admin:
user:
class: App\Admin\UserAdmin
controller: SonataAdminBundle:CRUD
So the "Users" dashboard element is shown without any action available.

Symfony twig controller route

I am begginer and I have little problem.
I have:
I have one primary menu with links to Controller main actions (index) and one secondary menu with links to other actions like create / edit / delete.
My problem is:
I don't know how to tell twig, that links in primary menu are active when any action in that controller is called.
In secondary menu I have:
{% set route_name = app.request.attributes.get('_route') %}
<a class="{% if route_name == item.route %} active {% endif %}">Link</a>
I have tried:
I tried to do it with prefixes on Controller, but it didn't worked.
Any hints or tips please? Thank you!
If your menu has only 2 nested levels, you can set a #Route annotation in the whole controller:
/**
* #Route(path="admin/pegass", name="admin_pegass_")
*/
class PegassController extends BaseController
{
For every action in your controller, you just need to set the end of the name and path:
/**
* #Route(name="list_users", path="/list-users")
*/
public function userList()
{
Finally, when rendering the first level of your menu, you can use:
{% set route_name = app.request.attributes.get('_route') %}
<a class="{% if route_name[0:13] == 'admin_pegass_' %} active {% endif %}">Link</a>
For more complex menus, breadcrumbs are not trivial, it will be easier to create an object representation of it in order to traverse it and perform operations in a more generic way (you can use a bundle like KnpMenu).

How to include a reusable widget in Symfony (Twig)?

So, I'm still fairly new to Symfony and Twig. I was wondering how to best include/create a snippet of reusable code in the templates. Say, for example, that you have a sidebar that you want to show on every page.
{% extends 'AppBundle::base.html.twig' %}
{% block body %}
<div id="wrapper">
<div id="content-container">
{# Main content... #}
</div>
<div id="sidebar">
{% include 'sidebar.html.twig' %}
</div>
</div>
{% endblock %}
And that in that sidebar are a couple of widgets that all do their own logic. How you do go about creating/including those widgets?
So far, I've come across several solutions.
As a controller
The first was to embed the widget as a controller(s) in Twig.
class WidgetController extends Controller
{
public function recentArticlesWidgetAction()
{
// some logic to generate to required widget data
// ...
// Render custom widget template with data
return $this->render('widgets/recentArticles.html.twig', array('data' => $data)
);
}
public function subscribeButtonWidgetAction()
{
// ...
return $this->render('widgets/subscribeButton.html.twig', array('data' => $data)
}
// Many more widgets
// ...
}
And include that in 'sidebar.html.twig' like so
<div id="sidebar">
{# Recent Articles widget #}
{{ render(controller('AppBundle:Widget:recentArticlesWidget' )) }}
{# Subscribe-Button widget #}
{{ render(controller('AppBundle:Widget:subscribeButtonWidget' )) }}
{# and so on #}
</div>
As a service
I've also seen some people register widgets as services (that can be used in Twig directly). With the widget main class
// src/AppBundle/Service/RecentArticlesWidget.php
namespace AppBundle\Service;
use Symfony\Component\DependencyInjection\ContainerInterface;
class RecentArticlesWidget
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getRecentArticles()
{
// do some logic (use container for doctrine etc.)
}
}
that is then registered as a service,
# src/AppBundle/Resources/config/services.yml
services:
recentArticlesWidget:
class: AppBundle\Service\RecentArticlesWidget
arguments: ["#service_container"]
passed to the template in the controller,
namespace AppBundle\Controller;
class SidebarController {
public function showAction($request) {
// Get the widget(s)
$recentArticlesWidget = $this->get('recentArticlesWidget');
// Pass it (them) along
return $this->render('sidebar.html.twig', array('recentArticlesWidget' => $recentArticlesWidget));
}
}
so it can simply be used like this in Twig
{# sidebar.html.twig #}
{{ recentArticlesWidget.getRecentArticles()|raw }}
Alternatively, you can also add your service to the Twig global variables directly by adding it to the Twig config. This way, it won't need to be passed into the view by the controller.
#app/config/config.yml
twig:
globals:
# twig_var_name: symfony_service
recentArticlesWidget: "#recentArticlesWidget"
As a Twig Extension
This one is very similar to using a service above (see the documentation). You create an a twig extension class that is almost identical to the service shown previously
// src/AppBundle/Twig/RecentArticlesWidgetExtension.php
namespace AppBundle\Twig;
class RecentArticlesWidgetExtension extends \Twig_Extension
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFunctions()
{
return array(
"getRecentArticles" => new Twig_Function_Method($this, "getRecentArticles")
// register more functions
);
}
public function getRecentArticles()
{
// do some logic (use container for doctrine etc.)
}
// Some more functions...
public function getName()
{
return 'WidgetExtension';
}
}
Register that as a service with an added tag
# src/AppBundle/Resources/config/services.yml
services:
recentArticlesWidget:
class: AppBundle\Twig\RecentArticlesWidgetExtension
arguments: [#service_container]
tags:
- { name: twig.extension }
and simply use it like a global function in Twig
{# sidebar.html.twig #}
{{ getRecentArticles() }}
Thoughts
One thing I noticed is that with the last two methods is that the logic and the view don't seem to be seperated at all anymore. You basically write a widget function and have that function output the complete html for the widget. This seems to go against the modularity and patterns Symfony tries to enforce.
On the other hand, calling a distinct controller or controller action (with their own twig renders) for every single widget seems like it could take more processing than might be needed. I'm not sure if it actually slows anything down, but I do wonder if its excessive.
Long story short, is there a best practice for using reusable widgets in Symfony? I'm sure some of these methods can also be mixed, so I was just wondering how to best go about this.
Twig extension and Twig macro should point you in the right direction.
Use the macro for the view and extension for the business logic.
On a side note in your Twig extension example, it's probably a good idea to only pass in services that you are using instead of the whole service container.
I would rather use blocks and a parent template. Simply put, insert the side bar in the main layout and have all other templates that require the side bar
inherit from it.
Something like this:
layout.html.twig will be something like this:
{% block title}
// title goes here
{%endblock%}
<div id="wrapper">
<div id="content-container">
{% block pageContent %}
{% endblock %}
</div>
<div id="sidebar">
// Side bar html goes here
</div>
</div>
Now all pages will inherit from this layout.html.twig. Say for example a page called home.html.twig will be:
home.html.twig
{% extends 'AppBundle::layout.html.twig' %}
{% block title%}
// this page title goes here
{% endblock %}
{% block pageContent %}
//This page content goes here
{% endblock %}
You can add as many blocks as needed, for example css and js blocks for each page.
Hope this helps!
I think the simplest way is defining a block in a template and then extending that template to render blocks like so:
#reusable.html.twig
{% block reusable_code %}
...
{% endblock %}
And
#reused.html.twig
{% extends 'reusable.html.twig' %}
{{ block('reusable_code') }}
If you want more reusability than that or your block contains business logic or model calls a twig extension is the way to go

remove submit from query parameter

I have a search form. Problem is when I post it it creates a variable for the submit button
?search=key&submit=
I cant seem to figure out how to remove this and only show
?search=key
Any Ideas? Thanks
private function createSearchForm()
{
$builder = $this->get('form.factory')->createNamedBuilder(null, 'form',null, array('csrf_protection' => false))
->setAction($this->generateUrl('trips'))
->setMethod('GET')
->getForm()
->add('search', 'text', array('required' => false, 'label' => false))
->add('submit', 'submit')
;
return $builder;
}
I think it would be nicer to use POST for forms.
So I explain to you below how you can have the same functionality (including permalink to searchResult) with POST. The URLs will even look nicer: Instead of ?search=bla it will be /search/bla.
Route setup:
search:
path: /search/
defaults: { _controller: "AcmeDemoBundle:Search:searchRedirect" }
requirements: { _method: POST }
search_result:
path: /search/{search}
defaults: { _controller: "AcmeDemoBundle:Search:search" }
requirements: { _method: GET}
And then in searchRedirectAction(Request $request) just redirect to search_result
return $this->redirect(
$this->generateUrl('search_result', array(
'search'=>$request->get('search', '')
))
);
All form fields with name attribute are in query string. So easy way is just manually render submit in template and omit name.
I solved this a little differently since I had a lot of forms that were going to need this functionality.
Symfony 3.x:
I created a form_theme that overrides 1 block
{# app/Resources/views/twig/form_themes/overrides.html.twig #}
{%- block button_attributes -%}
id="{{ id }}" {% if type|default('button') != 'submit' %}name="{{ full_name }}"{% endif %}{% if disabled %} disabled="disabled"{% endif -%}
{{ block('attributes') }}
{%- endblock button_attributes -%}
Then in my config:
twig:
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
form_themes:
- 'bootstrap_3_layout.html.twig'
- 'twig/form_themes/overrides.html.twig'
This will basically remove the name field from all submit type buttons, and therefor wont be included in your query params for GET requests.
** This may have some unintended side effects like not being able to run ->isClicked() on a submit button. But for my case I dont need that.

How can I make a custom field type in symfony2?

I want to make a custom form field in symfony2 named daterange, which will extends the default symfony date type form field and take date range(start date and end date) into two different text-box.
Cause I don't like twig template engine this example only for PHP templating
What you need is to make:
New TestBundle\Form\Extension\Core\Type\DateRangeType which extends Symfony\Component\Form\AbstractType
Here you should:
a. write your own getParent, getName, buildForm methods
b. getParent return 'field'
c. getName return 'daterange'
d. buildForm has $builder->add('start', ...)->add('end', ...)->setAttribute('widget', 'daterange')
Add it to the DI (config.yml as example)
services:
form.type.daterange:
class: TestBundle\Form\Extension\Core\Type\DateRangeType
tags:
- { name: form.type, alias: daterange }
Create new widget for it in TestBundle/Resources/views/Form/daterange_widget.html.php
you can take date widget as example. Src/vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php
Add to config (config.yml as example)
framework:
templating:
form:
resources:
- 'TestBundle:Form'
And for more widget customization as nefo_x said check form customization.
In order to do that, you need to add the following lines into app/config/config.yml
twig:
form:
resources:
- 'YourSuperBundle:Form:fields.html.twig'
then in src/Your/SuperBundle/Resources/views/Form/fields.html.twig:
{% extends 'form_div_layout.html.twig' %}
{% block daterange_widget %}
... do the customization.
{% endblock %}
For additional reference please read form customization of Symfony 2.0 book.
There is a good entry in official cookbook on creating custom field type

Resources