vendor/ezsystems/ezplatform-kernel/eZ/Publish/Core/MVC/Symfony/Routing/Generator/UrlAliasGenerator.php line 20

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4.  * @license For full copyright and license information view LICENSE file distributed with this source code.
  5.  */
  6. namespace eZ\Publish\Core\MVC\Symfony\Routing\Generator;
  7. use eZ\Publish\API\Repository\Repository;
  8. use eZ\Publish\API\Repository\Values\Content\Location;
  9. use eZ\Publish\Core\MVC\ConfigResolverInterface;
  10. use eZ\Publish\Core\MVC\Symfony\Routing\Generator;
  11. use Symfony\Component\Routing\RouterInterface;
  12. /**
  13.  * URL generator for UrlAlias based links.
  14.  *
  15.  * @see \eZ\Publish\Core\MVC\Symfony\Routing\UrlAliasRouter
  16.  */
  17. class UrlAliasGenerator extends Generator
  18. {
  19.     const INTERNAL_CONTENT_VIEW_ROUTE '_ez_content_view';
  20.     /** @var \eZ\Publish\Core\Repository\Repository */
  21.     private $repository;
  22.     /**
  23.      * The default router (that works with declared routes).
  24.      *
  25.      * @var \Symfony\Component\Routing\RouterInterface
  26.      */
  27.     private $defaultRouter;
  28.     /** @var int */
  29.     private $rootLocationId;
  30.     /** @var array */
  31.     private $excludedUriPrefixes = [];
  32.     /** @var array */
  33.     private $pathPrefixMap = [];
  34.     /** @var \eZ\Publish\Core\MVC\ConfigResolverInterface */
  35.     private $configResolver;
  36.     /**
  37.      * Array of characters that are potentially unsafe for output for (x)html, json, etc,
  38.      * and respective url-encoded value.
  39.      *
  40.      * @var array
  41.      */
  42.     private $unsafeCharMap;
  43.     public function __construct(Repository $repositoryRouterInterface $defaultRouterConfigResolverInterface $configResolver, array $unsafeCharMap = [])
  44.     {
  45.         $this->repository $repository;
  46.         $this->defaultRouter $defaultRouter;
  47.         $this->configResolver $configResolver;
  48.         $this->unsafeCharMap $unsafeCharMap;
  49.     }
  50.     /**
  51.      * Generates the URL from $urlResource and $parameters.
  52.      * Entries in $parameters will be added in the query string.
  53.      *
  54.      * @param \eZ\Publish\API\Repository\Values\Content\Location $location
  55.      * @param array $parameters
  56.      *
  57.      * @return string
  58.      */
  59.     public function doGenerate($location, array $parameters)
  60.     {
  61.         $siteAccess $parameters['siteaccess'] ?? null;
  62.         unset($parameters['language'], $parameters['contentId'], $parameters['siteaccess']);
  63.         $pathString $this->createPathString($location$siteAccess);
  64.         $queryString $this->createQueryString($parameters);
  65.         $url $pathString $queryString;
  66.         return $this->filterCharactersOfURL($url);
  67.     }
  68.     /**
  69.      * Injects current root locationId that will be used for link generation.
  70.      *
  71.      * @param int $rootLocationId
  72.      */
  73.     public function setRootLocationId($rootLocationId)
  74.     {
  75.         $this->rootLocationId $rootLocationId;
  76.     }
  77.     /**
  78.      * @param array $excludedUriPrefixes
  79.      */
  80.     public function setExcludedUriPrefixes(array $excludedUriPrefixes)
  81.     {
  82.         $this->excludedUriPrefixes $excludedUriPrefixes;
  83.     }
  84.     /**
  85.      * Returns path corresponding to $rootLocationId.
  86.      *
  87.      * @param int $rootLocationId
  88.      * @param array $languages
  89.      * @param string $siteaccess
  90.      *
  91.      * @return string
  92.      */
  93.     public function getPathPrefixByRootLocationId($rootLocationId$languages null$siteaccess null)
  94.     {
  95.         if (!$rootLocationId) {
  96.             return '';
  97.         }
  98.         if (!isset($this->pathPrefixMap[$siteaccess])) {
  99.             $this->pathPrefixMap[$siteaccess] = [];
  100.         }
  101.         if (!isset($this->pathPrefixMap[$siteaccess][$rootLocationId])) {
  102.             $this->pathPrefixMap[$siteaccess][$rootLocationId] = $this->repository
  103.                 ->getURLAliasService()
  104.                 ->reverseLookup(
  105.                     $this->loadLocation($rootLocationId),
  106.                     null,
  107.                     false,
  108.                     $languages
  109.                 )
  110.                 ->path;
  111.         }
  112.         return $this->pathPrefixMap[$siteaccess][$rootLocationId];
  113.     }
  114.     /**
  115.      * Checks if passed URI has an excluded prefix, when a root location is defined.
  116.      *
  117.      * @param string $uri
  118.      *
  119.      * @return bool
  120.      */
  121.     public function isUriPrefixExcluded($uri)
  122.     {
  123.         foreach ($this->excludedUriPrefixes as $excludedPrefix) {
  124.             $excludedPrefix '/' trim($excludedPrefix'/');
  125.             if (mb_stripos($uri$excludedPrefix) === 0) {
  126.                 return true;
  127.             }
  128.         }
  129.         return false;
  130.     }
  131.     /**
  132.      * Loads a location by its locationId, regardless to user limitations since the router is invoked BEFORE security (no user authenticated yet).
  133.      * Not to be used for link generation.
  134.      *
  135.      * @param int $locationId
  136.      *
  137.      * @return \eZ\Publish\Core\Repository\Values\Content\Location
  138.      */
  139.     public function loadLocation($locationId)
  140.     {
  141.         return $this->repository->sudo(
  142.             function (Repository $repository) use ($locationId) {
  143.                 /* @var $repository \eZ\Publish\Core\Repository\Repository */
  144.                 return $repository->getLocationService()->loadLocation($locationId);
  145.             }
  146.         );
  147.     }
  148.     /**
  149.      * @param \eZ\Publish\API\Repository\Values\Content\Location $location
  150.      * @param string|null $siteAccess
  151.      *
  152.      * @return string
  153.      */
  154.     private function createPathString(Location $location, ?string $siteAccess null): string
  155.     {
  156.         $urlAliasService $this->repository->getURLAliasService();
  157.         if ($siteAccess) {
  158.             // We generate for a different SiteAccess, so potentially in a different language.
  159.             $languages $this->configResolver->getParameter('languages'null$siteAccess);
  160.             $urlAliases $urlAliasService->listLocationAliases($locationfalsenullnull$languages);
  161.             // Use the target SiteAccess root location
  162.             $rootLocationId $this->configResolver->getParameter('content.tree_root.location_id'null$siteAccess);
  163.         } else {
  164.             $languages null;
  165.             $urlAliases $urlAliasService->listLocationAliases($locationfalse);
  166.             $rootLocationId $this->rootLocationId;
  167.         }
  168.         if (!empty($urlAliases)) {
  169.             $path $urlAliases[0]->path;
  170.             // Remove rootLocation's prefix if needed.
  171.             if ($rootLocationId !== null) {
  172.                 $pathPrefix $this->getPathPrefixByRootLocationId($rootLocationId$languages$siteAccess);
  173.                 // "/" cannot be considered as a path prefix since it's root, so we ignore it.
  174.                 if ($pathPrefix !== '/' && ($path === $pathPrefix || mb_stripos($path$pathPrefix '/') === 0)) {
  175.                     $path mb_substr($pathmb_strlen($pathPrefix));
  176.                 } elseif ($pathPrefix !== '/' && !$this->isUriPrefixExcluded($path) && $this->logger !== null) {
  177.                     // Location path is outside configured content tree and doesn't have an excluded prefix.
  178.                     // This is most likely an error (from content edition or link generation logic).
  179.                     $this->logger->warning("Generating a link to a location outside root content tree: '$path' is outside tree starting to location #$rootLocationId");
  180.                 }
  181.             }
  182.         } else {
  183.             $path $this->defaultRouter->generate(
  184.                 self::INTERNAL_CONTENT_VIEW_ROUTE,
  185.                 ['contentId' => $location->contentId'locationId' => $location->id]
  186.             );
  187.         }
  188.         return $path ?: '/';
  189.     }
  190.     /**
  191.      * Creates query string from parameters. If `_fragment` parameter is provided then
  192.      * fragment identifier is added at the end of the URL.
  193.      *
  194.      * @param array $parameters
  195.      *
  196.      * @return string
  197.      */
  198.     private function createQueryString(array $parameters): string
  199.     {
  200.         $queryString '';
  201.         $fragment null;
  202.         if (isset($parameters['_fragment'])) {
  203.             $fragment $parameters['_fragment'];
  204.             unset($parameters['_fragment']);
  205.         }
  206.         if (!empty($parameters)) {
  207.             $queryString '?' http_build_query($parameters'''&');
  208.         }
  209.         if ($fragment) {
  210.             // logic aligned with Symfony 3.4: \Symfony\Component\Routing\Generator\UrlGenerator::doGenerate
  211.             $queryString .= '#' strtr(rawurlencode($fragment), ['%2F' => '/''%3F' => '?']);
  212.         }
  213.         return $queryString;
  214.     }
  215.     /**
  216.      * Replace potentially unsafe characters with url-encoded counterpart.
  217.      *
  218.      * @param string $url
  219.      *
  220.      * @return string
  221.      */
  222.     private function filterCharactersOfURL(string $url): string
  223.     {
  224.         return strtr($url$this->unsafeCharMap);
  225.     }
  226. }