vendor/easycorp/easyadmin-bundle/src/Factory/ActionFactory.php line 217

Open in your IDE?
  1. <?php
  2. namespace EasyCorp\Bundle\EasyAdminBundle\Factory;
  3. use EasyCorp\Bundle\EasyAdminBundle\Collection\ActionCollection;
  4. use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
  5. use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
  6. use EasyCorp\Bundle\EasyAdminBundle\Config\Option\EA;
  7. use EasyCorp\Bundle\EasyAdminBundle\Dto\ActionConfigDto;
  8. use EasyCorp\Bundle\EasyAdminBundle\Dto\ActionDto;
  9. use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
  10. use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
  11. use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGeneratorInterface;
  12. use EasyCorp\Bundle\EasyAdminBundle\Security\Permission;
  13. use EasyCorp\Bundle\EasyAdminBundle\Translation\TranslatableMessageBuilder;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  16. use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
  17. use function Symfony\Component\Translation\t;
  18. use Symfony\Contracts\Translation\TranslatableInterface;
  19. /**
  20.  * @author Javier Eguiluz <javier.eguiluz@gmail.com>
  21.  */
  22. final class ActionFactory
  23. {
  24.     private AdminContextProvider $adminContextProvider;
  25.     private AuthorizationCheckerInterface $authChecker;
  26.     private AdminUrlGeneratorInterface $adminUrlGenerator;
  27.     private ?CsrfTokenManagerInterface $csrfTokenManager;
  28.     public function __construct(AdminContextProvider $adminContextProviderAuthorizationCheckerInterface $authCheckerAdminUrlGeneratorInterface $adminUrlGenerator, ?CsrfTokenManagerInterface $csrfTokenManager null)
  29.     {
  30.         $this->adminContextProvider $adminContextProvider;
  31.         $this->authChecker $authChecker;
  32.         $this->adminUrlGenerator $adminUrlGenerator;
  33.         $this->csrfTokenManager $csrfTokenManager;
  34.     }
  35.     public function processEntityActions(EntityDto $entityDtoActionConfigDto $actionsDto): void
  36.     {
  37.         $currentPage $this->adminContextProvider->getContext()->getCrud()->getCurrentPage();
  38.         $entityActions = [];
  39.         foreach ($actionsDto->getActions()->all() as $actionDto) {
  40.             if (!$actionDto->isEntityAction()) {
  41.                 continue;
  42.             }
  43.             if (false === $this->authChecker->isGranted(Permission::EA_EXECUTE_ACTION, ['action' => $actionDto'entity' => $entityDto])) {
  44.                 continue;
  45.             }
  46.             if (false === $actionDto->isDisplayed($entityDto)) {
  47.                 continue;
  48.             }
  49.             // if CSS class hasn't been overridden, apply the default ones
  50.             if ('' === $actionDto->getCssClass()) {
  51.                 $defaultCssClass 'action-'.$actionDto->getName();
  52.                 if (Crud::PAGE_INDEX !== $currentPage) {
  53.                     $defaultCssClass .= ' btn';
  54.                 }
  55.                 $actionDto->setCssClass($defaultCssClass);
  56.             }
  57.             // these are the additional custom CSS classes defined via addCssClass()
  58.             // which are always appended to the CSS classes (default ones or custom ones)
  59.             if ('' !== $addedCssClass $actionDto->getAddedCssClass()) {
  60.                 $actionDto->setCssClass($actionDto->getCssClass().' '.$addedCssClass);
  61.             }
  62.             $entityActions[$actionDto->getName()] = $this->processAction($currentPage$actionDto$entityDto);
  63.         }
  64.         $entityDto->setActions(ActionCollection::new($entityActions));
  65.     }
  66.     public function processGlobalActions(?ActionConfigDto $actionsDto null): ActionCollection
  67.     {
  68.         if (null === $actionsDto) {
  69.             $actionsDto $this->adminContextProvider->getContext()->getCrud()->getActionsConfig();
  70.         }
  71.         $currentPage $this->adminContextProvider->getContext()->getCrud()->getCurrentPage();
  72.         $globalActions = [];
  73.         foreach ($actionsDto->getActions()->all() as $actionDto) {
  74.             if (!$actionDto->isGlobalAction() && !$actionDto->isBatchAction()) {
  75.                 continue;
  76.             }
  77.             if (false === $this->authChecker->isGranted(Permission::EA_EXECUTE_ACTION, ['action' => $actionDto'entity' => null])) {
  78.                 continue;
  79.             }
  80.             if (false === $actionDto->isDisplayed()) {
  81.                 continue;
  82.             }
  83.             if (Crud::PAGE_INDEX !== $currentPage && $actionDto->isBatchAction()) {
  84.                 throw new \RuntimeException(sprintf('Batch actions can be added only to the "index" page, but the "%s" batch action is defined in the "%s" page.'$actionDto->getName(), $currentPage));
  85.             }
  86.             // if CSS class hasn't been overridden, apply the default ones
  87.             if ('' === $actionDto->getCssClass()) {
  88.                 $actionDto->setCssClass('btn action-'.$actionDto->getName());
  89.             }
  90.             // these are the additional custom CSS classes defined via addCssClass()
  91.             // which are always appended to the CSS classes (default ones or custom ones)
  92.             if ('' !== $addedCssClass $actionDto->getAddedCssClass()) {
  93.                 $actionDto->setCssClass($actionDto->getCssClass().' '.$addedCssClass);
  94.             }
  95.             $globalActions[$actionDto->getName()] = $this->processAction($currentPage$actionDto);
  96.         }
  97.         return ActionCollection::new($globalActions);
  98.     }
  99.     private function processAction(string $pageNameActionDto $actionDto, ?EntityDto $entityDto null): ActionDto
  100.     {
  101.         $adminContext $this->adminContextProvider->getContext();
  102.         $translationDomain $adminContext->getI18n()->getTranslationDomain();
  103.         $defaultTranslationParameters $adminContext->getI18n()->getTranslationParameters();
  104.         $actionDto->setHtmlAttribute('data-action-name'$actionDto->getName());
  105.         if (false === $actionDto->getLabel()) {
  106.             $actionDto->setHtmlAttribute('title'$actionDto->getName());
  107.         } elseif (!$actionDto->getLabel() instanceof TranslatableInterface) {
  108.             $translationParameters array_merge(
  109.                 $defaultTranslationParameters,
  110.                 $actionDto->getTranslationParameters()
  111.             );
  112.             $label $actionDto->getLabel();
  113.             $translatableActionLabel = (null === $label || '' === $label) ? $label t($label$translationParameters$translationDomain);
  114.             $actionDto->setLabel($translatableActionLabel);
  115.         } else {
  116.             $actionDto->setLabel(TranslatableMessageBuilder::withParameters($actionDto->getLabel(), $defaultTranslationParameters));
  117.         }
  118.         $defaultTemplatePath $adminContext->getTemplatePath('crud/action');
  119.         $actionDto->setTemplatePath($actionDto->getTemplatePath() ?? $defaultTemplatePath);
  120.         $actionDto->setLinkUrl($this->generateActionUrl($adminContext->getRequest(), $actionDto$entityDto));
  121.         if (!$actionDto->isGlobalAction() && \in_array($pageName, [Crud::PAGE_EDITCrud::PAGE_NEW], true)) {
  122.             $actionDto->setHtmlAttribute('form'sprintf('%s-%s-form'$pageName$entityDto->getName()));
  123.         }
  124.         if (Action::DELETE === $actionDto->getName()) {
  125.             $actionDto->addHtmlAttributes([
  126.                 'formaction' => $this->adminUrlGenerator->setController($adminContext->getCrud()->getControllerFqcn())->setAction(Action::DELETE)->setEntityId($entityDto->getPrimaryKeyValue())->removeReferrer()->generateUrl(),
  127.                 'data-bs-toggle' => 'modal',
  128.                 'data-bs-target' => '#modal-delete',
  129.             ]);
  130.         }
  131.         if ($actionDto->isBatchAction()) {
  132.             $actionDto->addHtmlAttributes([
  133.                 'data-bs-toggle' => 'modal',
  134.                 'data-bs-target' => '#modal-batch-action',
  135.                 'data-action-csrf-token' => $this->csrfTokenManager?->getToken('ea-batch-action-'.$actionDto->getName()),
  136.                 'data-action-batch' => 'true',
  137.                 'data-entity-fqcn' => $adminContext->getCrud()->getEntityFqcn(),
  138.                 'data-action-url' => $actionDto->getLinkUrl(),
  139.             ]);
  140.         }
  141.         return $actionDto;
  142.     }
  143.     private function generateActionUrl(Request $requestActionDto $actionDto, ?EntityDto $entityDto null): string
  144.     {
  145.         $entityInstance $entityDto?->getInstance();
  146.         if (null !== $url $actionDto->getUrl()) {
  147.             if (\is_callable($url)) {
  148.                 return null !== $entityDto $url($entityInstance) : $url();
  149.             }
  150.             return $url;
  151.         }
  152.         if (null !== $routeName $actionDto->getRouteName()) {
  153.             $routeParameters $actionDto->getRouteParameters();
  154.             if (\is_callable($routeParameters) && null !== $entityInstance) {
  155.                 $routeParameters $routeParameters($entityInstance);
  156.             }
  157.             return $this->adminUrlGenerator->unsetAllExcept(EA::FILTERSEA::PAGEEA::QUERYEA::SORT)->setRoute($routeName$routeParameters)->generateUrl();
  158.         }
  159.         $requestParameters = [
  160.             // when using pretty URLs, the data is in the request attributes instead of the query string
  161.             EA::CRUD_CONTROLLER_FQCN => $request->attributes->get(EA::CRUD_CONTROLLER_FQCN) ?? $request->query->get(EA::CRUD_CONTROLLER_FQCN),
  162.             EA::CRUD_ACTION => $actionDto->getCrudActionName(),
  163.         ];
  164.         if (\in_array($actionDto->getName(), [Action::INDEXAction::NEW, Action::SAVE_AND_ADD_ANOTHER], true)) {
  165.             $requestParameters[EA::ENTITY_ID] = null;
  166.         } elseif (null !== $entityDto) {
  167.             $requestParameters[EA::ENTITY_ID] = $entityDto->getPrimaryKeyValueAsString();
  168.         }
  169.         $urlParametersToKeep = [EA::FILTERSEA::QUERYEA::SORTEA::BATCH_ACTION_CSRF_TOKENEA::BATCH_ACTION_ENTITY_IDSEA::BATCH_ACTION_NAMEEA::BATCH_ACTION_URL];
  170.         // when creating a new entity, keeping the selected page number is usually confusing:
  171.         // 1. the user filters/searches/sorts/paginates the results and then creates a new entity
  172.         // 2. if we keep the page number, when the backend returns to the listing, it's very probable
  173.         //    that the user doesn't see the new entity, so they might think that it wasn't created
  174.         // 3. if we keep the other parameters, it's probable that the new entity is shown (sometimes it won't)
  175.         if (Action::NEW !== $actionDto->getName()) {
  176.             $urlParametersToKeep[] = EA::PAGE;
  177.         }
  178.         return $this->adminUrlGenerator->unsetAllExcept(...$urlParametersToKeep)->setAll($requestParameters)->generateUrl();
  179.     }
  180. }