I need to migrate a really old version of Knp Menu to a newest one. The real problem is here
$collapse = new CollapseItem($group,$router->generate('seguridad_group_list'),array('class'=>'submenu'),'Primicia\SeguridadBundle\Menu\CollapseItem');
$collapse->setIcon('sp sp-ico-menu-grupo sp-icon-display');
$this->addChild($collapse);
How can I make it following the menu-as-service-way in the version 2 of KnpMenu?
The rest of code is this
The menu service implementation:
namespace Primicia\SeguridadBundle\Menu;
use Knp\Bundle\MenuBundle\Menu;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Router;
use Knp\Bundle\MenuBundle\Renderer\RendererInterface;
class SeguridadMenu extends Menu {
/**
* #param Request $request
* #param Router $router
* #param Container $container
*/
public function __construct(Request $request, Router $router, $container)
{
parent::__construct();
$this->setCurrentUri($request->getRequestUri());
$this->setAttribute('class','nav nav-list menu_lateral');
$translator = $container->get('translator');
if($container->get('security.context')->isGranted('ROLE_ADMIN'))
{
$user = $translator->trans('menu.user.titles',array(),'SeguridadBundle');
$signal = new CollapseItem($user,$router->generate('seguridad_user_list'),array('class'=>'submenu'),'Primicia\SeguridadBundle\Menu\CollapseItem');
$signal->setIcon('sp sp-ico-menu-usuario sp-icon-display');
$this->addChild($signal);
$group = $translator->trans('menu.group.titles',array(),'SeguridadBundle');
$collapse = new CollapseItem($group,$router->generate('seguridad_group_list'),array('class'=>'submenu'),'Primicia\SeguridadBundle\Menu\CollapseItem');
$collapse->setIcon('sp sp-ico-menu-grupo sp-icon-display');
$this->addChild($collapse);
}
}
/**
* Gets renderer which is used to render menu items.
*
* #return RendererInterface $renderer Renderer.
*/
public function getRenderer()
{
if(null === $this->renderer) {
if($this->isRoot()) {
$this->setRenderer(new ApcRenderer());
}
else {
return $this->getParent()->getRenderer();
}
}
return $this->renderer;
}
}
The CollapseItem class, which is used in the $router->generate
namespace Primicia\SeguridadBundle\Menu;
use Knp\Menu\MenuItem;
class CollapseItem extends MenuItem
{
protected $hasIcon;
public function renderLink()
{
$label = $this->renderLabel();
$uri = $this->getUri();
if (!$uri) {
die;
return sprintf('<a class="dropdown-toggle" href="#">%s</a>', $label);
}
return sprintf('<a class="dropdown-toggle" href="%s">%s</a>', $uri, $label);
}
public function setIcon($icon)
{
$this->hasIcon=$icon;
return $this;
}
public function getIcon()
{
return $this->hasIcon;
}
}
Related
I would like to know if it is possible to disable the subscriber inside a command (only during this command) with Symfony.
I need to update a database but, each time an entity is updated, it will trigger the subscriber and take to much time.
The subscriber is useful for a small amount of modification, but not when the entire database is updated.
I use prePersist, preUpdate, postUpdate and postPersist. There is some example :
<?php
namespace App\EventSubscriber\ElasticSearch;
use App\Entity\Product;
use App\Entity\ProductTranslation;
use App\Service\ElasticSearch\ElasticSearchProductService;
use Cocur\Slugify\Slugify;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
class ProductSubscriber implements EventSubscriber
{
private ElasticSearchProductService $elasticSearchProductService;
public function __construct(ElasticSearchProductService $elasticSearchProductService)
{
$this->elasticSearchProductService = $elasticSearchProductService;
}
/**
* #return array
*/
public function getSubscribedEvents(): array
{
return [
Events::prePersist,
Events::preUpdate,
Events::postPersist,
Events::postUpdate,
];
}
/**
* #param LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args): void
{
$product = $args->getObject();
if (!$product instanceof Product) {
return;
}
$this->slugProduct($product);
}
/**
* #param LifecycleEventArgs $args
*/
public function preUpdate(LifecycleEventArgs $args): void
{
$product = $args->getObject();
if (!$product instanceof Product) {
return;
}
$this->slugProduct($product);
}
/**
* #param LifecycleEventArgs $args
*/
public function postUpdate(LifecycleEventArgs $args): void
{
$this->initializeElasticSearch($args);
}
/**
* #param LifecycleEventArgs $args
*/
public function postPersist(LifecycleEventArgs $args): void
{
$this->initializeElasticSearch($args);
}
/**
* #param Product $product
*/
private function slugProduct(Product $product): void
{
$product->setSlug((new Slugify())->slugify($product->getTranslations()['fr']->getName() . '-' . $product->getEan() . '-' . $product->getPickRef()));
}
/**
* #param LifecycleEventArgs $args
*/
private function initializeElasticSearch(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
if (!($entity instanceof Product || $entity instanceof ProductTranslation)) {
return;
}
if ($entity instanceof ProductTranslation) {
$entity = $args->getObject()->getTranslatable();
}
//$this->elasticSearchProductService->initializeDocumentElasticSearch($entity);
}
}
EDIT : I have updated the example. This is the full subscriber. I update a Brand with for example :
$this->em->persist($brand);
$this->em->flush();
It will activate the subscriber who will update ElasticSearch. But, in one Symfony Command that I made,I have to update 40 000 brands. I would like to update elasticSearch at the end of the command and not update one by one with the subscriber. So I would like to disable (for only this Command) the subscriber.
This is a Doctrine EventSubscriber and I tried
$listeners = $this->em->getEventManager()->getListeners("productSubscriber");
foreach ($listeners as $key => $listener) {
if ($listener instanceof ProductSubscriber) {
$this->em->getEventManager()->removeEventListener($listener->getSubscribedEvents(), $listener);
}
}
But the subscriber is not removed.
I'm taking over someone's code and I don't understand something about the voting.
Here is the PhotosController class:
class PhotosController extends Controller
{
/**
* #Route("/dashboard/photos/{id}/view", name="dashboard_photos_view")
* #Security("is_granted('view.photo', photo)")
* #param Photo $photo
* #param PhotoRepository $photoRepository
*/
public function index(Photo $photo, PhotoRepository $photoRepository)
{
$obj = $photoRepository->getFileObjectFromS3($photo);
header("Content-Type: {$obj['ContentType']}");
echo $obj['Body'];
exit;
}
Here is the voter class:
class PhotoVoter extends Voter
{
const VIEW = 'view.photo';
protected function supports($attribute, $subject)
{
if (!$subject instanceof Photo) {
return false;
}
if (!in_array($attribute, array(self::VIEW))) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
return $subject->getUser()->getId() === $token->getUser()->getId();
}
}
I don't understand what the
, photo
is for in the PhotosController class. And in PhpStorm I get "cannot find declaration" when I try to go to the "is_granted" declaration.
I followed this documentation for Edit In Place, and setup the Activator, and it works!
However, I will be using this on the production site and allowing access via a ROLE_TRANSLATOR Authorization. This is also working, but I don't want the web interface always "on"
How would I go about enabling it via some sort of link or toggle?
My thoughts, it would be simple to just add a URL parameter, like ?trans=yes and then in the activator;
return ($this->authorizationChecker->isGranted(['ROLE_TRANSLATOR']) && $_GET['trans'] == 'yes');
Obviously, $_GET would not work, I didn't even try.
How do I generate a link to simply reload THIS page with the extra URL parameter
How do I check for that parameter within the "Activator"
or, is there a better way?
The "proper" way to do this, as I have discovered more about "services" is to do the logic diectly in the RoleActivator.php file.
referencing documentation for How to Inject Variables into all Templates via Referencing Services I came up with the following solution;
src/Security/RoleActivator.php
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
use Translation\Bundle\EditInPlace\ActivatorInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class RoleActivator implements ActivatorInterface
{
/**
* #var AuthorizationCheckerInterface
*/
private $authorizationChecker;
/**
* #var TranslatorInterface
*/
private $translate;
/**
* #var RequestStack
*/
private $request;
private $params;
private $path;
private $flag = null;
public function __construct(AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translate, RequestStack $request)
{
$this->authorizationChecker = $authorizationChecker;
$this->translate = $translate;
$this->request = $request;
}
/**
* {#inheritdoc}
*/
public function checkRequest(Request $request = null)
{
if ($this->flag === null) { $this->setFlag($request); }
try {
return ($this->authorizationChecker->isGranted(['ROLE_TRANSLATOR']) && $this->flag);
} catch (AuthenticationCredentialsNotFoundException $e) {
return false;
}
}
public function getText()
{
if ($this->flag === null) { $this->setFlag(); }
return ($this->flag) ? 'linkText.translate.finished' : 'linkText.translate.start'; // Translation key's returned
}
public function getHref()
{
if ($this->flag === null) { $this->setFlag(); }
$params = $this->params;
if ($this->flag) {
unset($params['trans']);
} else {
$params['trans'] = 'do';
}
$queryString = '';
if (!empty($params)) {
$queryString = '?';
foreach ($params as $key => $value) {
$queryString.= $key.'='.$value.'&';
}
$queryString = rtrim($queryString, '&');
}
return $this->path.$queryString;
}
private function setFlag(Request $request = null)
{
if ($request === null) {
$request = $this->request->getCurrentRequest();
}
$this->flag = $request->query->has('trans');
$this->params = $request->query->all();
$this->path = $request->getPathInfo();
}
}
config\packages\twig.yaml
twig:
# ...
globals:
EditInPlace: '#EditInPlace_RoleActivator'
config\services.yaml
services:
# ...
EditInPlace_RoleActivator:
class: App\Security\RoleActivator
arguments: ["#security.authorization_checker"]
So What I added over and above the php-translation example is the getText and getHref methods and corresponding private variables being set in the checkRequest and read there after.
Now in my twig template (in the header) I just use
{% if is_granted('ROLE_TRANSLATOR') %}
{{ EditInPlace.Text }}
{% endif %}
Add the new keys to the translation files and your done. the trans=do query parameter is toggled on and off with each click of the link. You could even add toggling styles with a class name, just copy the getText method to something like getClass and return string a or b with the Ternary.
I'm using the doctrine softdeleteable extension on a project and have my controller action set up as such.
/**
* #Route("address/{id}/")
* #Method("GET")
* #ParamConverter("address", class="MyBundle:Address")
* #Security("is_granted('view', address)")
*/
public function getAddressAction(Address $address)
{
This works great as it returns NotFound if the object is deleted, however I want to grant access to users with ROLE_ADMIN to be able to see soft deleted content.
Does there already exist a way to get the param converter to disable the filter or am I going to have to create my own custom param converter?
There are no existing ways to do it, but I've solved this problem by creating my own annotation, that disables softdeleteable filter before ParamConverter does its job.
AcmeBundle/Annotation/IgnoreSoftDelete.php:
namespace AcmeBundle\Annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* #Annotation
* #Target({"CLASS", "METHOD"})
*/
class IgnoreSoftDelete extends Annotation { }
AcmeBundle/EventListener/AnnotationListener.php:
namespace AcmeBundle\EventListener;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class AnnotationListener {
protected $reader;
public function __construct(Reader $reader) {
$this->reader = $reader;
}
public function onKernelController(FilterControllerEvent $event) {
if (!is_array($controller = $event->getController())) {
return;
}
list($controller, $method, ) = $controller;
$this->ignoreSoftDeleteAnnotation($controller, $method);
}
private function readAnnotation($controller, $method, $annotation) {
$classReflection = new \ReflectionClass(ClassUtils::getClass($controller));
$classAnnotation = $this->reader->getClassAnnotation($classReflection, $annotation);
$objectReflection = new \ReflectionObject($controller);
$methodReflection = $objectReflection->getMethod($method);
$methodAnnotation = $this->reader->getMethodAnnotation($methodReflection, $annotation);
if (!$classAnnotation && !$methodAnnotation) {
return false;
}
return [$classAnnotation, $classReflection, $methodAnnotation, $methodReflection];
}
private function ignoreSoftDeleteAnnotation($controller, $method) {
static $class = 'AcmeBundle\Annotation\IgnoreSoftDelete';
if ($this->readAnnotation($controller, $method, $class)) {
$em = $controller->get('doctrine.orm.entity_manager');
$em->getFilters()->disable('softdeleteable');
}
}
}
AcmeBundle/Resources/config/services.yml:
services:
acme.annotation_listener:
class: AcmeBundle\EventListener\AnnotationListener
arguments: [#annotation_reader]
tags:
- { name: kernel.event_listener, event: kernel.controller }
AcmeBundle/Controller/DefaultController.php:
namespace AcmeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use AcmeBundle\Annotation\IgnoreSoftDelete;
use AcmeBundle\Entity\User;
class DefaultController extends Controller {
/**
* #Route("/{id}")
* #IgnoreSoftDelete
* #Template
*/
public function indexAction(User $user) {
return ['user' => $user];
}
}
Annotation can be applied to individual action methods and to entire controller classes.
You can use #Entity for this, customizing a repository method like this:
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
/**
* #Route("/{id}")
* #Entity("post", expr="repository.findDisableFilter(id)")
*/
public function disable(Post $post): JsonResponse
{
...
}
and then in your repository class:
public function findDisableFilter(mixed $id): mixed
{
$filterName = 'your-filter-name';
$filters = $this->getEntityManager()->getFilters();
if ($filters->has($filterName) && $filters->isEnabled($filterName)) {
$filters->disable($filterName);
}
return $this->find($id);
}
For instance my bundle namespace is Facebook\Bundle\FacebookBundle\Extension.
Using this how can I create a twig extension ?
It's all here: How to write a custom Twig Extension.
1. Create the Extension:
// src/Facebook/Bundle/Twig/FacebookExtension.php
namespace Facebook\Bundle\Twig;
use Twig_Extension;
use Twig_Filter_Method;
class FacebookExtension extends Twig_Extension
{
public function getFilters()
{
return array(
'myfilter' => new Twig_Filter_Method($this, 'myFilter'),
);
}
public function myFilter($arg1, $arg2='')
{
return sprintf('something %s %s', $arg1, $arg2);
}
public function getName()
{
return 'facebook_extension';
}
}
2. Register an Extension as a Service
# src/Facebook/Bundle/Resources/config/services.yml
services:
facebook.twig.facebook_extension:
class: Facebook\Bundle\Twig\AcmeExtension
tags:
- { name: twig.extension }
3. Use it
{{ 'blah'|myfilter('somearg') }}
You can also create twig functions by using the getFunctions()
class FacebookExtension extends Twig_Extension
{
public function getFunctions()
{
return array(
'myFunction' => new Twig_Filter_Method($this, 'myFunction'),
);
}
public function myFunction($arg1)
{
return $arg1;
}
Use your function like this:
{{ myFunction('my_param') }}
The Twig_Filter_Method class is DEPRECATED since Symfony 2.1
Please use the Twig_SimpleFilter class instead as showed in the following example:
\src\Acme\Bundle\CoreBundle\Twig\DatetimeExtension.php
<?php
namespace Acme\Bundle\CoreBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DatetimeExtension extends \Twig_Extension
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFilters()
{
return array(
'dateFormat' => new \Twig_SimpleFilter('dateFormat', array($this, 'dateFormat')),
'datetimeFormat' => new \Twig_SimpleFilter('datetimeFormat', array($this, 'datetimeFormat'))
);
}
/**
* #param mixed $date
* #return string
*/
public function dateFormat($date)
{
$format = $this->container->getParameter('acme_core.date_format');
return $this->format($date, $format);
}
/**
* #param mixed $date
* #return string
*/
public function datetimeFormat($date)
{
$format = $this->container->getParameter('acme_core.datetime_format');
return $this->format($date, $format);
}
/**
* #param mixed $date
* #param string $format
* #throws \Twig_Error
* #return string
*/
private function format($date, $format)
{
if (is_int($date) || (is_string($date) && preg_match('/^[0-9]+$/iu', $date))) {
return date($format, intval($date, 10));
} else if (is_string($date) && !preg_match('/^[0-9]+$/', $date)) {
return date($format, strtotime($date));
} else if ($date instanceof \DateTime) {
return $date->format($format);
} else {
throw new \Twig_Error('Date or datetime parameter not valid');
}
}
public function getName()
{
return 'datetime_extension';
}
}
\src\Acme\Bundle\CoreBundle\Resources\config\services.yml
services:
acme_core.twig.datetime_extension:
class: Acme\Bundle\CoreBundle\Twig\DatetimeExtension
arguments: [#service_container]
tags:
- { name: twig.extension }
Usage example:
{{ value|datetimeFormat }}
Symfony documentation: http://symfony.com/doc/master/cookbook/templating/twig_extension.html
Twig documentation: http://twig.sensiolabs.org/doc/advanced.html#id3
None of the given answers worked for Symfony 3.4 and above.
For Symfony 3.4, 4.x
// src/TwigExtension/customFilters.php
namespace App\TwigExtension;
use Twig\TwigFilter;
class customFilters extends \Twig_Extension {
public function getFilters() {
return array(
new TwigFilter('base64_encode', array($this, 'base64_en'))
);
}
public function base64_en($input) {
return base64_encode($input);
}
}
And then in your twig template you can do
{{ 'hello world' | base64_encode }}
Thats it. For detailed explanation, you could check out the reference.
Reference: DigitalFortress