I have a little problem with sonata admin on Symfony.
I would like to change the default admin label in the breadcrumb:
but I can't find any solution. Can someone help me?
I found this function, but it doesn't work. It looks like that this function is not called.
public function buildBreadcrumbs($action, MenuItemInterface $menu = null) {
$breadCrumb = parent::buildBreadcrumbs($action, $menu);
return $breadCrumb;
}
I use Symfony 2.8.
Try to override classNameLabel property in your admin class:
// in your ProductAdmin class
public function configure()
{
$this->classnameLabel = "Products";
}
The simplest way to achieve what you want is to change translations messages.
If you really want to change the labels you can implement your own label generation strategy.
namespace Blast\CoreBundle\Translator;
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
/**
* Class LibrinfoLabelTranslatorStrategy.
*
* Provides a specific label translation strategy for Librinfo.
* It is based on UnderscoreLabelTranslatorStrategy, but without the context,
* and labels are prefixed by "librinfo.label."
*
* i.e. isValid => librinfo.label.is_valid
*/
class LibrinfoLabelTranslatorStrategy implements LabelTranslatorStrategyInterface
{
/**
* {#inheritdoc}
*/
public function getLabel($label, $context = '', $type = '')
{
$label = str_replace('.', '_', $label);
return sprintf('%s.%s.%s', "librinfo", $type, strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $label)));
}
}
define it as a service
blast_core.label.strategy.librinfo:
class: Blast\CoreBundle\Translator\LibrinfoLabelTranslatorStrategy
then pass it to the definition of your admin service like so:
crm.organism:
class: Librinfo\CRMBundle\Admin\OrganismAdmin
arguments: [~, Librinfo\CRMBundle\Entity\Organism, LibrinfoCRMBundle:OrganismAdmin]
tags:
- name: sonata.admin
manager_type: orm
group: Customers Relationship Management
label_translator_strategy: blast_core.label.strategy.librinfo
You will have full control of your admin labels
Also see: SonataAdmin: replace ID in breadcrumbs
Related
I want set up Symfony 5 to use "human readable" roles for display purposes. When displaying user information in TWIG I can use {{ user.roles[0] }}. However, this displays (for example) "ROLE_ACCOUNTADMIN" (as is saved against the user in the database), but I would like it to display as "Account Administrator". With Symfony 3 where a user_roles table was used, there was a "role" and a "name" field, but this has been removed. Is it possible to accomplish this in Symfony 5 without having to define / include an array whenever I want to use this?
You can create an extension twig and you have many solutions ...,
but for the solution that I see the simplest, just put a field on the entity and initialize it with the main role, so you will have the info on the api and twig, mailing ...
<?php
namespace App\Entity;
// ...
class User
{
public static $USER_ROLE_AS_HUMAN_READABLE_INDEX = [
'ROLE_ACCOUNTADMIN' => 'Account Administrator',
'OTHER_ROLE' => 'Description',
// ...
]
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// without mapping ..
private $roleHumanReadable
// ...
public function __construct() {
$this->initializeRoleHumanReadable();
}
public function getRoleHumanReadable():?string
{
return $this->roleHumanReadable;
}
public function initializeRoleHumanReadable():void
{
$rolePrincipal = $this->getRoles()[0] ?? null;
if (!isset(static::$USER_ROLE_AS_HUMAN_READABLE_INDEX[$rolePrincipal])) {
return;
}
$this->roleHumanReadable = static::$USER_ROLE_AS_HUMAN_READABLE_INDEX[$rolePrincipal];
}
}
I am using API Platform and I followed this tutorial to add a custom serialized field which relies on an external service. The avatar property needs to be exposed using the Packages class.
<?php
namespace App\Serializer;
use App\Entity\User;
use Symfony\Component\Asset\Packages;
use Symfony\Component\HttpFoundation\UrlHelper;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
class UserNormalizer implements ContextAwareNormalizerInterface
{
/**
* #var Packages
*/
private $packages;
/**
* #var UrlHelper
*/
private $urlHelper;
/**
* #var ObjectNormalizer
*/
private $normalizer;
public function __construct(Packages $packages, UrlHelper $urlHelper, ObjectNormalizer $normalizer)
{
$this->packages = $packages;
$this->normalizer = $normalizer;
$this->urlHelper = $urlHelper;
}
public function normalize($user, $format = null, array $context = [])
{
/** #var array */
$data = $this->normalizer->normalize($user, $format, $context);
$avatar = null;
if ($user->getAvatarFilename()) {
$path = $this->packages->getUrl('uploads/avatars/'.$user->getAvatarFilename());
$avatar = $this->urlHelper->getAbsoluteUrl($path);
}
$data['avatar'] = $avatar;
return $data;
}
public function supportsNormalization($data, $format = null, array $context = [])
{
return $data instanceof User;
}
}
The problem is that this property doesn't appear in the documentation as it's added by the custom normalizer. How can I add documentation for it (eg. type, example etc...)?
if this still relevant for you or somebody else:
You can add a custom field to the openapi model with this: https://api-platform.com/docs/core/swagger/#overriding-the-openapi-specification
You need to add $avatar field to your entity.
Like you suggest in the comments you could add a not mapped property to your entity and document it in the annotations, and yes it's hacky like they said here!... it is suggested in the SymfonyCast tutorials
Just remember the downside to this approach: our documentation has no
idea that this isMe field exists. If we refresh this page and open the
docs for fetching a single User... yep! There's no mention of isMe. Of
course, you could add a public function isMe() in User, put it in the
user:read group, always return false, then override the isMe key in
your normalizer with the real value. That would give you the custom
field and the docs. But sheesh... that's... getting kinda hacky.
I try to override layout template in Sonata Admin but depends of logged user. If logged user belong to group customers has some ROLE - show other layout.
I want change -
layout" => "#SonataAdmin/standard_layout.html.twig"
Where is best place to do it ?
I found that i can do this in admin class - override getTemplate.
But is possible to do this is some listener and switch globaly without edit admin classes ?
UPDATE 1
i create class
class SonataTemplateRegistry implements MutableTemplateRegistryInterface
{
/**
* #var string[]
*/
private $templates = [];
/**
* #param string[] $templates
* #param ContactService $contactService
*/
public function __construct(array $templates = [], ContactService $contactService)
{
$templates['layout']= '#SonataAdmin/layout1.html.twig';
// $templates['layout']= '#SonataAdmin/standard_layout.html.twig';
// echo '<pre>'; var_dump($templates); die();
$this->templates = $templates;
}
register it
sonata.admin.global_template_registry:
class: App\Service\SonataTemplateRegistry
public: true
arguments: ['%sonata.admin.configuration.templates%', '#mea.contact']
class is fired - die() show templates but main template is not changed when i change here.
Update 2
in admin class when i get layout template i get correct #SonataAdmin/layout1.html.twig
protected function configureListFields(ListMapper $listMapper)
{
var_dump($this->configurationPool->getTemplate('layout'));
but it is not loaded, still see #SonataAdmin/standard_layout.html.twig
UPDATE 3
I found a strange behavior - main page sonata admin - switching template works but already under the pages use the default template
UPDATE 4
I found something interesting , each admin panel has sub service like here :
php bin/console debug:container |grep app.admin.social
app.admin.social.accounts App\SocialManager\Admin\SocialAccountAdmin
app.admin.social.accounts.template_registry Sonata\AdminBundle\Templating\TemplateRegistry
app.admin.social.order App\SocialManager\Admin\SocialManagementOrderAdmin
app.admin.social.order.template_registry Sonata\AdminBundle\Templating\TemplateRegistry
i override parameters :
parameters:
sonata.admin.global_template_registry: App\Service\SonataTemplateRegistry
and service
sonata.admin.global_template_registry:
class: App\Service\SonataTemplateRegistry
public: true
arguments: ['%sonata.admin.configuration.templates%', '#mea.contact']
so why sonata still use Sonata\AdminBundle\Templating\TemplateRegistry
protected function configureListFields(ListMapper $listMapper)
{
$this->getTemplateRegistry()
give Sonata\AdminBundle\Templating\TemplateRegistry
You can use workaround by service tags.
First create your own Template registry and implement MutableTemplateRegistryInterface.
Then create compiler pass.
<?php
namespace App\DependencyInjection\Compiler;
use App\Registry\TemplateRegistry;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Class SonataAdminTemplateRegistryPass
* #package App\DependencyInjection\Compiler
*/
class SonataAdminTemplateRegistryPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
foreach ($container->findTaggedServiceIds('sonata.admin.template_registry') as $id => $tags)
{
//because #see src/vendor/sonata-project/admin-bundle/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php:405
$adminServiceId = str_replace(".template_registry", "", $id);
$def = $container->getDefinition($adminServiceId);
$def->removeMethodCall('setTemplateRegistry');
$def->addMethodCall('setTemplateRegistry', [new Reference(TemplateRegistry::class)]);
}
}
}
then add compiler pass to src/Kernel.php like this
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(new SonataAdminTemplateRegistryPass());
}
And you will get always override TemplateRegistry in any admin class.
So you can implement your own logic in your own TemplateRegistry :)
You can do this using single Twig template:
{# layout.html.twig #}
{# mutatis mutandis #}
{% extends app.user ?
('ROLE_ADMIN' in app.user.role ?
'admin_layout.html.twig' :
'customer_layout.html.twig'
) :
'fallback.html.twig'
%}
Does somebody know how to create a notification system for the menu items in the sidebar?
For example if you have a sidebar entry
Articles
and in the background, a new article has been added (e.g. by importing via a sql script). Then the menu entry should be displayed as
Articles (1)
Is there a tutorial for my concern?
You can modify the sidebar menu items.
To do this, you must create a listener that configure the menu in the manner that you want. You can do this with this code:
app.menu_listener:
class: AppBundle\EventListener\MenuBuilderListener
tags:
- { name: kernel.event_listener, event: sonata.admin.event.configure.menu.sidebar, method: addMenuItems }
calls:
- [ setDependencies, [ #doctrine.orm.entity_manager ] ]
After that you can write the class that modify the menu:
namespace AppBundle\EventListener;
use AppBundle\Entity\Configuration;
use Sonata\AdminBundle\Event\ConfigureMenuEvent;
use Doctrine\ORM\EntityManager;
class MenuBuilderListener {
/** #var EntityManager $em */
private $em;
public function addMenuItems(ConfigureMenuEvent $event)
{
$articles = $this->em->getRepo('AppBundle:Article')->findAll();
$menu = $event->getMenu();
$articleMenu = $menu->getChild('sonata.admin.group.articles');
$articleMenu->setLabel('Articles <span>' . $articles->count() . '</span>')
}
public function setDependencies(EntityManager $em, Translator $translator) {
$this->em = $em;
}
}
This is only an example, but is the way that i will take if i need to do this feature, i hope this may help you
You have more info about this here: https://sonata-project.org/bundles/admin/master/doc/cookbook/recipe_knp_menu.html
Let's just say i have entity named Offer.
I want to make multiple list views for Offer entity. Each list view should contain offers with different states (i.e. draft, active, inactive...).
So far I created two offer admins: DraftOfferAdmin and ActiveOfferAdmin. Here I defined custom queries:
public function createQuery($context = 'list')
{
/** #var ModelManager $modelManager */
$modelManager = $this->getModelManager();
$entityManager = $modelManager->getEntityManager($this->getClass());
$queryBuilder = $entityManager->createQueryBuilder();
$queryBuilder
->select('o')
->from($this->getClass(), 'o')
->where('o.state = :state')
->setParameter('state', 'draft');
$query = new ProxyQuery($queryBuilder);
foreach ($this->extensions as $extension) {
$extension->configureQuery($this, $query, $context);
}
return $query;
}
Query seems to be working fine!
I defined Admins in services:
services:
admin.draft_offer:
class: IndexBundle\Admin\Offer\DraftOfferAdmin
arguments:
- null
- IndexBundle\Entity\Offer
- IndexBundle:CRUD
tags:
- { name: sonata.admin, manager_type: orm, group: Offers, label: Draft Offers }
admin.unverified_offer:
class: IndexBundle\Admin\Offer\UnverifiedOfferAdmin
arguments:
- null
- IndexBundle\Entity\Offer
- IndexBundle:CRUD
tags:
- { name: sonata.admin, manager_type: orm, group: Offers, label: Unverified Offers }
But both list view pages share the same URL http://domain.com/admin/index/offer/list. Any ideas what do I miss in my configurations?
This happens probably because while the admin classes are different, your entity class is the same. I`d recommend this article in order to achieve the functionality you require, more user friendly too.
Now that I need exactly this functionality in one project :
In your admin class you need to set the route and the route pattern like
class ClassAdmin extends AbstractAdmin
{
protected $baseRoutePattern = 'class-route';
protected $baseRouteName = 'class-route';
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
$query->join($query->getRootAlias() . '.status', 'st');
$query->andWhere('st.id = :status')
->setParameter('status', $statis);
return $query;
}
//admin class code..
}
and include it the standart way..