vendor/easycorp/easyadmin-bundle/src/Router/AdminUrlGenerator.php line 176

  1. <?php
  2. namespace EasyCorp\Bundle\EasyAdminBundle\Router;
  3. use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
  4. use EasyCorp\Bundle\EasyAdminBundle\Config\Option\EA;
  5. use EasyCorp\Bundle\EasyAdminBundle\Contracts\Controller\DashboardControllerInterface;
  6. use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
  7. use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
  8. use EasyCorp\Bundle\EasyAdminBundle\Registry\DashboardControllerRegistry;
  9. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  10. /**
  11.  * @author Javier Eguiluz <javier.eguiluz@gmail.com>
  12.  */
  13. final class AdminUrlGenerator implements AdminUrlGeneratorInterface
  14. {
  15.     private bool $isInitialized false;
  16.     private AdminContextProvider $adminContextProvider;
  17.     private UrlGeneratorInterface $urlGenerator;
  18.     private DashboardControllerRegistry $dashboardControllerRegistry;
  19.     private ?string $dashboardRoute null;
  20.     private ?bool $includeReferrer null;
  21.     private array $routeParameters = [];
  22.     private ?string $currentPageReferrer null;
  23.     private ?string $customPageReferrer null;
  24.     public function __construct(AdminContextProvider $adminContextProviderUrlGeneratorInterface $urlGeneratorDashboardControllerRegistry $dashboardControllerRegistry)
  25.     {
  26.         $this->adminContextProvider $adminContextProvider;
  27.         $this->urlGenerator $urlGenerator;
  28.         $this->dashboardControllerRegistry $dashboardControllerRegistry;
  29.     }
  30.     /**
  31.      * @return AdminUrlGenerator
  32.      */
  33.     public function setDashboard(string $dashboardControllerFqcn): AdminUrlGeneratorInterface
  34.     {
  35.         $this->setRouteParameter(EA::DASHBOARD_CONTROLLER_FQCN$dashboardControllerFqcn);
  36.         return $this;
  37.     }
  38.     /**
  39.      * @return AdminUrlGenerator
  40.      */
  41.     public function setController(string $crudControllerFqcn): AdminUrlGeneratorInterface
  42.     {
  43.         $this->setRouteParameter(EA::CRUD_CONTROLLER_FQCN$crudControllerFqcn);
  44.         $this->unset(EA::ROUTE_NAME);
  45.         $this->unset(EA::ROUTE_PARAMS);
  46.         return $this;
  47.     }
  48.     /**
  49.      * @return AdminUrlGenerator
  50.      */
  51.     public function setAction(string $action): AdminUrlGeneratorInterface
  52.     {
  53.         $this->setRouteParameter(EA::CRUD_ACTION$action);
  54.         $this->unset(EA::ROUTE_NAME);
  55.         $this->unset(EA::ROUTE_PARAMS);
  56.         return $this;
  57.     }
  58.     /**
  59.      * @return AdminUrlGenerator
  60.      */
  61.     public function setRoute(string $routeName, array $routeParameters = []): AdminUrlGeneratorInterface
  62.     {
  63.         $this->unsetAllExcept(EA::DASHBOARD_CONTROLLER_FQCN);
  64.         $this->setRouteParameter(EA::ROUTE_NAME$routeName);
  65.         $this->setRouteParameter(EA::ROUTE_PARAMS$routeParameters);
  66.         return $this;
  67.     }
  68.     /**
  69.      * @return AdminUrlGenerator
  70.      */
  71.     public function setEntityId($entityId): AdminUrlGeneratorInterface
  72.     {
  73.         $this->setRouteParameter(EA::ENTITY_ID$entityId);
  74.         return $this;
  75.     }
  76.     public function get(string $paramName): mixed
  77.     {
  78.         if (false === $this->isInitialized) {
  79.             $this->initialize();
  80.         }
  81.         return $this->routeParameters[$paramName] ?? null;
  82.     }
  83.     /**
  84.      * @return AdminUrlGenerator
  85.      */
  86.     public function set(string $paramName$paramValue): AdminUrlGeneratorInterface
  87.     {
  88.         if (\in_array($paramName, [EA::MENU_INDEXEA::SUBMENU_INDEX], true)) {
  89.             trigger_deprecation(
  90.                 'easycorp/easyadmin-bundle',
  91.                 '4.5.0',
  92.                 'Using the "%s" query parameter is deprecated. Menu items are now highlighted automatically based on the Request data, so you don\'t have to deal with menu items manually anymore.',
  93.                 $paramName,
  94.             );
  95.         }
  96.         $this->setRouteParameter($paramName$paramValue);
  97.         return $this;
  98.     }
  99.     /**
  100.      * @return AdminUrlGenerator
  101.      */
  102.     public function setAll(array $routeParameters): AdminUrlGeneratorInterface
  103.     {
  104.         foreach ($routeParameters as $paramName => $paramValue) {
  105.             $this->setRouteParameter($paramName$paramValue);
  106.         }
  107.         return $this;
  108.     }
  109.     /**
  110.      * @return AdminUrlGenerator
  111.      */
  112.     public function unset(string $paramName): AdminUrlGeneratorInterface
  113.     {
  114.         if (false === $this->isInitialized) {
  115.             $this->initialize();
  116.         }
  117.         unset($this->routeParameters[$paramName]);
  118.         return $this;
  119.     }
  120.     /**
  121.      * @return AdminUrlGenerator
  122.      */
  123.     public function unsetAll(): AdminUrlGeneratorInterface
  124.     {
  125.         if (false === $this->isInitialized) {
  126.             $this->initialize();
  127.         }
  128.         $this->routeParameters = [];
  129.         return $this;
  130.     }
  131.     /**
  132.      * @return AdminUrlGenerator
  133.      */
  134.     public function unsetAllExcept(string ...$namesOfParamsToKeep): AdminUrlGeneratorInterface
  135.     {
  136.         if (false === $this->isInitialized) {
  137.             $this->initialize();
  138.         }
  139.         $this->routeParameters array_intersect_key($this->routeParametersarray_flip($namesOfParamsToKeep));
  140.         return $this;
  141.     }
  142.     /**
  143.      * @return AdminUrlGenerator
  144.      */
  145.     public function includeReferrer(): AdminUrlGeneratorInterface
  146.     {
  147.         trigger_deprecation(
  148.             'easycorp/easyadmin-bundle',
  149.             '4.9.0',
  150.             'Adding the referrer argument in the admin URLs via the AdminUrlGenerator::includeReferrer() method is deprecated and it will be removed in 5.0.0. The referrer will now be determined automatically based on the current request.',
  151.         );
  152.         if (false === $this->isInitialized) {
  153.             $this->initialize();
  154.         }
  155.         $this->includeReferrer true;
  156.         return $this;
  157.     }
  158.     /**
  159.      * @return AdminUrlGenerator
  160.      */
  161.     public function removeReferrer(): AdminUrlGeneratorInterface
  162.     {
  163.         if (false === $this->isInitialized) {
  164.             $this->initialize();
  165.         }
  166.         $this->includeReferrer false;
  167.         return $this;
  168.     }
  169.     /**
  170.      * @return AdminUrlGenerator
  171.      */
  172.     public function setReferrer(string $referrer): AdminUrlGeneratorInterface
  173.     {
  174.         trigger_deprecation(
  175.             'easycorp/easyadmin-bundle',
  176.             '4.9.0',
  177.             'Adding the referrer argument in the admin URLs via the AdminUrlGenerator::setReferrer() method is deprecated and it will be removed in 5.0.0. The referrer will now be determined automatically based on the current request.',
  178.         );
  179.         if (false === $this->isInitialized) {
  180.             $this->initialize();
  181.         }
  182.         $this->includeReferrer true;
  183.         $this->customPageReferrer $referrer;
  184.         return $this;
  185.     }
  186.     /**
  187.      * @return AdminUrlGenerator
  188.      */
  189.     public function addSignature(bool $addSignature true): AdminUrlGeneratorInterface
  190.     {
  191.         trigger_deprecation(
  192.             'easycorp/easyadmin-bundle',
  193.             '4.1.0',
  194.             'EasyAdmin URLs no longer include signatures because they don\'t provide any additional security. Calling the "%s" method has no effect, so you can stop calling it. This method will be removed in future EasyAdmin versions.',
  195.             __METHOD__,
  196.         );
  197.         return $this;
  198.     }
  199.     public function getSignature(): string
  200.     {
  201.         trigger_deprecation(
  202.             'easycorp/easyadmin-bundle',
  203.             '4.1.0',
  204.             'EasyAdmin URLs no longer include signatures because they don\'t provide any additional security. Calling the "%s" method will always return an empty string, so you can stop calling it. This method will be removed in future EasyAdmin versions.',
  205.             __METHOD__,
  206.         );
  207.         return '';
  208.     }
  209.     // this method allows to omit the 'generateUrl()' call in templates, making code more concise
  210.     public function __toString(): string
  211.     {
  212.         return $this->generateUrl();
  213.     }
  214.     public function generateUrl(): string
  215.     {
  216.         if (false === $this->isInitialized) {
  217.             $this->initialize();
  218.         }
  219.         if (true === $this->includeReferrer) {
  220.             $this->setRouteParameter(EA::REFERRER$this->customPageReferrer ?? $this->currentPageReferrer);
  221.         }
  222.         // this avoids forcing users to always be explicit about the action to execute
  223.         if (null !== $this->get(EA::CRUD_CONTROLLER_FQCN) && null === $this->get(EA::CRUD_ACTION)) {
  224.             $this->set(EA::CRUD_ACTIONAction::INDEX);
  225.         }
  226.         // if the Dashboard FQCN is defined, find its route and use it to override
  227.         // the current route (this is needed to allow generating links to different dashboards)
  228.         if (null !== $dashboardControllerFqcn $this->get(EA::DASHBOARD_CONTROLLER_FQCN)) {
  229.             if (null === $dashboardRoute $this->dashboardControllerRegistry->getRouteByControllerFqcn($dashboardControllerFqcn)) {
  230.                 throw new \InvalidArgumentException(sprintf('The given "%s" class is not a valid Dashboard controller. Make sure it extends from "%s" or implements "%s".'$dashboardControllerFqcnAbstractDashboardController::class, DashboardControllerInterface::class));
  231.             }
  232.             $this->dashboardRoute $dashboardRoute;
  233.             $this->unset(EA::DASHBOARD_CONTROLLER_FQCN);
  234.         }
  235.         // this happens when generating URLs from outside EasyAdmin (AdminContext is null) and
  236.         // no Dashboard FQCN has been defined explicitly
  237.         if (null === $this->dashboardRoute) {
  238.             if ($this->dashboardControllerRegistry->getNumberOfDashboards() > 1) {
  239.                 throw new \RuntimeException('When generating admin URLs from outside EasyAdmin or without a related HTTP request (e.g. in tests, console commands, etc.), if your application has more than one Dashboard, you must associate the URL to a specific Dashboard using the "setDashboard()" method.');
  240.             }
  241.             $this->dashboardRoute $this->dashboardControllerRegistry->getFirstDashboardRoute();
  242.         }
  243.         // if present, remove the suffix of i18n route names (it's the content after the last dot
  244.         // in the route name; e.g. 'dashboard.en' -> remove '.en', 'admin.index.en_US' -> remove '.en_US')
  245.         $this->dashboardRoute preg_replace('~\.[a-z]{2}(_[A-Z]{2})?$~'''$this->dashboardRoute);
  246.         // this removes any parameter with a NULL value
  247.         $routeParameters array_filter(
  248.             $this->routeParameters,
  249.             static fn ($parameterValue): bool => null !== $parameterValue
  250.         );
  251.         ksort($routeParameters\SORT_STRING);
  252.         $context $this->adminContextProvider->getContext();
  253.         $urlType null !== $context && false === $context->getAbsoluteUrls() ? UrlGeneratorInterface::RELATIVE_PATH UrlGeneratorInterface::ABSOLUTE_URL;
  254.         $url $this->urlGenerator->generate($this->dashboardRoute$routeParameters$urlType);
  255.         $url '' === $url '?' $url;
  256.         // this is important to start the generation a each URL from the same initial state
  257.         // otherwise, some parameters used when generating some URL could leak to other URLs
  258.         $this->isInitialized false;
  259.         return $url;
  260.     }
  261.     private function setRouteParameter(string $paramName$paramValue): void
  262.     {
  263.         if (false === $this->isInitialized) {
  264.             $this->initialize();
  265.         }
  266.         if (\is_resource($paramValue)) {
  267.             throw new \InvalidArgumentException(sprintf('The value of the "%s" parameter is a PHP resource, which is not supported as a route parameter.'$paramName));
  268.         }
  269.         if (\is_object($paramValue)) {
  270.             if (method_exists($paramValue'__toString')) {
  271.                 $paramValue = (string) $paramValue;
  272.             } else {
  273.                 throw new \InvalidArgumentException(sprintf('The object passed as the value of the "%s" parameter must implement the "__toString()" method to allow using its value as a route parameter.'$paramName));
  274.             }
  275.         }
  276.         $this->routeParameters[$paramName] = $paramValue;
  277.     }
  278.     private function initialize(): void
  279.     {
  280.         $this->isInitialized true;
  281.         $adminContext $this->adminContextProvider->getContext();
  282.         if (null === $adminContext) {
  283.             $this->dashboardRoute null;
  284.             $currentRouteParameters $routeParametersForReferrer = [];
  285.             $this->currentPageReferrer null;
  286.         } else {
  287.             $this->dashboardRoute $adminContext->getDashboardRouteName();
  288.             $currentRouteParameters $routeParametersForReferrer $adminContext->getRequest()->query->all();
  289.             unset($routeParametersForReferrer[EA::REFERRER]);
  290.             $this->currentPageReferrer sprintf('%s%s?%s'$adminContext->getRequest()->getBaseUrl(), $adminContext->getRequest()->getPathInfo(), http_build_query($routeParametersForReferrer));
  291.         }
  292.         $this->includeReferrer null;
  293.         $this->customPageReferrer null;
  294.         $this->routeParameters $currentRouteParameters;
  295.     }
  296. }