vendor/ezsystems/ezplatform-kernel/eZ/Publish/Core/Repository/URLAliasService.php line 28

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. declare(strict_types=1);
  7. namespace eZ\Publish\Core\Repository;
  8. use eZ\Publish\API\Repository\LanguageResolver;
  9. use eZ\Publish\API\Repository\PermissionResolver;
  10. use eZ\Publish\API\Repository\URLAliasService as URLAliasServiceInterface;
  11. use eZ\Publish\API\Repository\Repository as RepositoryInterface;
  12. use eZ\Publish\SPI\Persistence\Content\UrlAlias\Handler;
  13. use eZ\Publish\API\Repository\Values\Content\Location;
  14. use eZ\Publish\API\Repository\Values\Content\URLAlias;
  15. use eZ\Publish\SPI\Persistence\Content\URLAlias as SPIURLAlias;
  16. use eZ\Publish\Core\Base\Exceptions\NotFoundException;
  17. use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
  18. use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
  19. use eZ\Publish\API\Repository\Exceptions\ForbiddenException;
  20. use Exception;
  21. /**
  22.  * @internal Type-hint \eZ\Publish\API\Repository\URLAliasService instead.
  23.  */
  24. class URLAliasService implements URLAliasServiceInterface
  25. {
  26.     /** @var \eZ\Publish\API\Repository\Repository */
  27.     protected $repository;
  28.     /** @var \eZ\Publish\SPI\Persistence\Content\UrlAlias\Handler */
  29.     protected $urlAliasHandler;
  30.     /** @var \eZ\Publish\Core\Repository\Helper\NameSchemaService */
  31.     protected $nameSchemaService;
  32.     /** @var \eZ\Publish\API\Repository\PermissionResolver */
  33.     private $permissionResolver;
  34.     /** @var \eZ\Publish\API\Repository\LanguageResolver */
  35.     private $languageResolver;
  36.     public function __construct(
  37.         RepositoryInterface $repository,
  38.         Handler $urlAliasHandler,
  39.         Helper\NameSchemaService $nameSchemaService,
  40.         PermissionResolver $permissionResolver,
  41.         LanguageResolver $languageResolver
  42.     ) {
  43.         $this->repository $repository;
  44.         $this->urlAliasHandler $urlAliasHandler;
  45.         $this->nameSchemaService $nameSchemaService;
  46.         $this->permissionResolver $permissionResolver;
  47.         $this->languageResolver $languageResolver;
  48.     }
  49.     /**
  50.      * Create a user chosen $alias pointing to $location in $languageCode.
  51.      *
  52.      * This method runs URL filters and transformers before storing them.
  53.      * Hence the path returned in the URLAlias Value may differ from the given.
  54.      * $alwaysAvailable makes the alias available in all languages.
  55.      *
  56.      * @param \eZ\Publish\API\Repository\Values\Content\Location $location
  57.      * @param string $path
  58.      * @param string $languageCode the languageCode for which this alias is valid
  59.      * @param bool $forwarding if true a redirect is performed
  60.      * @param bool $alwaysAvailable
  61.      *
  62.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create url alias
  63.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the path already exists for the given context
  64.      * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
  65.      *
  66.      * @return \eZ\Publish\API\Repository\Values\Content\URLAlias
  67.      */
  68.     public function createUrlAlias(
  69.         Location $location,
  70.         string $path,
  71.         string $languageCode,
  72.         bool $forwarding false,
  73.         bool $alwaysAvailable false
  74.     ): URLAlias {
  75.         if (!$this->permissionResolver->canUser('content''urltranslator'$location)) {
  76.             throw new UnauthorizedException('content''urltranslator');
  77.         }
  78.         $path $this->cleanUrl($path);
  79.         $this->repository->beginTransaction();
  80.         try {
  81.             $spiUrlAlias $this->urlAliasHandler->createCustomUrlAlias(
  82.                 $location->id,
  83.                 $path,
  84.                 $forwarding,
  85.                 $languageCode,
  86.                 $alwaysAvailable
  87.             );
  88.             $this->repository->commit();
  89.         } catch (ForbiddenException $e) {
  90.             $this->repository->rollback();
  91.             throw new InvalidArgumentException(
  92.                 '$path',
  93.                 $e->getMessage(),
  94.                 $e
  95.             );
  96.         } catch (Exception $e) {
  97.             $this->repository->rollback();
  98.             throw $e;
  99.         }
  100.         return $this->buildUrlAliasDomainObject($spiUrlAlias$path);
  101.     }
  102.     /**
  103.      * Create a user chosen $alias pointing to a resource in $languageCode.
  104.      *
  105.      * This method does not handle location resources - if a user enters a location target
  106.      * the createCustomUrlAlias method has to be used.
  107.      * This method runs URL filters and and transformers before storing them.
  108.      * Hence the path returned in the URLAlias Value may differ from the given.
  109.      *
  110.      * $alwaysAvailable makes the alias available in all languages.
  111.      *
  112.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create global
  113.      *          url alias
  114.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the path already exists for the given
  115.      *         language or if resource is not valid
  116.      *
  117.      * @param string $resource
  118.      * @param string $path
  119.      * @param string $languageCode
  120.      * @param bool $forwarding
  121.      * @param bool $alwaysAvailable
  122.      *
  123.      * @return \eZ\Publish\API\Repository\Values\Content\URLAlias
  124.      */
  125.     public function createGlobalUrlAlias(
  126.         string $resource,
  127.         string $path,
  128.         string $languageCode,
  129.         bool $forwarding false,
  130.         bool $alwaysAvailable false
  131.     ): URLAlias {
  132.         if ($this->permissionResolver->hasAccess('content''urltranslator') === false) {
  133.             throw new UnauthorizedException('content''urltranslator');
  134.         }
  135.         if (!preg_match('#^([a-zA-Z0-9_]+):(.+)$#'$resource$matches)) {
  136.             throw new InvalidArgumentException('$resource''argument is not valid');
  137.         }
  138.         $path $this->cleanUrl($path);
  139.         if ($matches[1] === 'eznode' || === strpos($resource'module:content/view/full/')) {
  140.             if ($matches[1] === 'eznode') {
  141.                 $locationId $matches[2];
  142.             } else {
  143.                 $resourcePath explode('/'$matches[2]);
  144.                 $locationId end($resourcePath);
  145.             }
  146.             $location $this->repository->getLocationService()->loadLocation((int)$locationId);
  147.             if (!$this->permissionResolver->canUser('content''urltranslator'$location)) {
  148.                 throw new UnauthorizedException('content''urltranslator');
  149.             }
  150.             return $this->createUrlAlias(
  151.                 $location,
  152.                 $path,
  153.                 $languageCode,
  154.                 $forwarding,
  155.                 $alwaysAvailable
  156.             );
  157.         }
  158.         $this->repository->beginTransaction();
  159.         try {
  160.             $spiUrlAlias $this->urlAliasHandler->createGlobalUrlAlias(
  161.                 $matches[1] . ':' $this->cleanUrl($matches[2]),
  162.                 $path,
  163.                 $forwarding,
  164.                 $languageCode,
  165.                 $alwaysAvailable
  166.             );
  167.             $this->repository->commit();
  168.         } catch (ForbiddenException $e) {
  169.             $this->repository->rollback();
  170.             throw new InvalidArgumentException('$path'$e->getMessage(), $e);
  171.         } catch (Exception $e) {
  172.             $this->repository->rollback();
  173.             throw $e;
  174.         }
  175.         return $this->buildUrlAliasDomainObject($spiUrlAlias$path);
  176.     }
  177.     /**
  178.      * List of url aliases pointing to $location, sorted by language priority.
  179.      *
  180.      * @param \eZ\Publish\API\Repository\Values\Content\Location $location
  181.      * @param bool $custom if true the user generated aliases are listed otherwise the autogenerated
  182.      * @param string $languageCode filters those which are valid for the given language
  183.      * @param bool|null $showAllTranslations If enabled will include all alias as if they where always available.
  184.      * @param string[]|null $prioritizedLanguages If set used as prioritized language codes, returning first match.
  185.      *
  186.      * @return \eZ\Publish\API\Repository\Values\Content\URLAlias[]
  187.      */
  188.     public function listLocationAliases(
  189.         Location $location,
  190.         ?bool $custom true,
  191.         ?string $languageCode null,
  192.         ?bool $showAllTranslations null,
  193.         ?array $prioritizedLanguages null
  194.     ): iterable {
  195.         $spiUrlAliasList $this->urlAliasHandler->listURLAliasesForLocation(
  196.             $location->id,
  197.             $custom
  198.         );
  199.         if ($showAllTranslations === null) {
  200.             $showAllTranslations $this->languageResolver->getShowAllTranslations();
  201.         }
  202.         if ($prioritizedLanguages === null) {
  203.             $prioritizedLanguages $this->languageResolver->getPrioritizedLanguages();
  204.         }
  205.         $urlAliasList = [];
  206.         foreach ($spiUrlAliasList as $spiUrlAlias) {
  207.             if (
  208.             !$this->isUrlAliasLoadable(
  209.                 $spiUrlAlias,
  210.                 $languageCode,
  211.                 $showAllTranslations,
  212.                 $prioritizedLanguages
  213.             )
  214.             ) {
  215.                 continue;
  216.             }
  217.             $path $this->extractPath(
  218.                 $spiUrlAlias,
  219.                 $languageCode,
  220.                 $showAllTranslations,
  221.                 $prioritizedLanguages
  222.             );
  223.             if ($path === false) {
  224.                 continue;
  225.             }
  226.             $urlAliasList[$spiUrlAlias->id] = $this->buildUrlAliasDomainObject($spiUrlAlias$path);
  227.         }
  228.         $prioritizedAliasList = [];
  229.         foreach ($prioritizedLanguages as $prioritizedLanguageCode) {
  230.             foreach ($urlAliasList as $urlAlias) {
  231.                 foreach ($urlAlias->languageCodes as $aliasLanguageCode) {
  232.                     if ($aliasLanguageCode === $prioritizedLanguageCode) {
  233.                         $prioritizedAliasList[$urlAlias->id] = $urlAlias;
  234.                         break;
  235.                     }
  236.                 }
  237.             }
  238.         }
  239.         // Add aliases not matched by prioritized language to the end of the list
  240.         return array_values($prioritizedAliasList $urlAliasList);
  241.     }
  242.     /**
  243.      * Determines alias language code.
  244.      *
  245.      * Method will return false if language code can't be matched against alias language codes or language settings.
  246.      *
  247.      * @param \eZ\Publish\SPI\Persistence\Content\URLAlias $spiUrlAlias
  248.      * @param string|null $languageCode
  249.      * @param bool $showAllTranslations
  250.      * @param string[] $prioritizedLanguageList
  251.      *
  252.      * @return string|bool
  253.      */
  254.     protected function selectAliasLanguageCode(
  255.         SPIURLAlias $spiUrlAlias,
  256.         ?string $languageCode,
  257.         bool $showAllTranslations,
  258.         array $prioritizedLanguageList
  259.     ) {
  260.         if (isset($languageCode) && !in_array($languageCode$spiUrlAlias->languageCodes)) {
  261.             return false;
  262.         }
  263.         foreach ($prioritizedLanguageList as $prioritizedLanguageCode) {
  264.             if (in_array($prioritizedLanguageCode$spiUrlAlias->languageCodes)) {
  265.                 return $prioritizedLanguageCode;
  266.             }
  267.         }
  268.         if ($spiUrlAlias->alwaysAvailable || $showAllTranslations) {
  269.             $lastLevelData end($spiUrlAlias->pathData);
  270.             reset($lastLevelData['translations']);
  271.             return key($lastLevelData['translations']);
  272.         }
  273.         return false;
  274.     }
  275.     /**
  276.      * Returns path extracted from normalized path data returned from persistence, using language settings.
  277.      *
  278.      * Will return false if path could not be determined.
  279.      *
  280.      * @param \eZ\Publish\SPI\Persistence\Content\URLAlias $spiUrlAlias
  281.      * @param string $languageCode
  282.      * @param bool $showAllTranslations
  283.      * @param string[] $prioritizedLanguageList
  284.      *
  285.      * @return string|bool
  286.      */
  287.     protected function extractPath(
  288.         SPIURLAlias $spiUrlAlias,
  289.         $languageCode,
  290.         $showAllTranslations,
  291.         array $prioritizedLanguageList
  292.     ) {
  293.         $pathData = [];
  294.         $pathLevels count($spiUrlAlias->pathData);
  295.         foreach ($spiUrlAlias->pathData as $level => $levelEntries) {
  296.             if ($level === $pathLevels 1) {
  297.                 $prioritizedLanguageCode $this->selectAliasLanguageCode(
  298.                     $spiUrlAlias,
  299.                     $languageCode,
  300.                     $showAllTranslations,
  301.                     $prioritizedLanguageList
  302.                 );
  303.             } else {
  304.                 $prioritizedLanguageCode $this->choosePrioritizedLanguageCode(
  305.                     $levelEntries,
  306.                     $showAllTranslations,
  307.                     $prioritizedLanguageList
  308.                 );
  309.             }
  310.             if ($prioritizedLanguageCode === false) {
  311.                 return false;
  312.             }
  313.             $pathData[$level] = $levelEntries['translations'][$prioritizedLanguageCode];
  314.         }
  315.         return implode('/'$pathData);
  316.     }
  317.     /**
  318.      * Returns language code with highest priority.
  319.      *
  320.      * Will return false if language code could not be matched with language settings in place.
  321.      *
  322.      * @param array $entries
  323.      * @param bool $showAllTranslations
  324.      * @param string[] $prioritizedLanguageList
  325.      *
  326.      * @return string|bool
  327.      */
  328.     protected function choosePrioritizedLanguageCode(array $entries$showAllTranslations, array $prioritizedLanguageList)
  329.     {
  330.         foreach ($prioritizedLanguageList as $prioritizedLanguageCode) {
  331.             if (isset($entries['translations'][$prioritizedLanguageCode])) {
  332.                 return $prioritizedLanguageCode;
  333.             }
  334.         }
  335.         if ($entries['always-available'] || $showAllTranslations) {
  336.             reset($entries['translations']);
  337.             return key($entries['translations']);
  338.         }
  339.         return false;
  340.     }
  341.     /**
  342.      * Matches path string with normalized path data returned from persistence.
  343.      *
  344.      * Returns matched path string (possibly case corrected) and array of corresponding language codes or false
  345.      * if path could not be matched.
  346.      *
  347.      * @param \eZ\Publish\SPI\Persistence\Content\URLAlias $spiUrlAlias
  348.      * @param string $path
  349.      * @param string $languageCode
  350.      *
  351.      * @return array
  352.      */
  353.     protected function matchPath(SPIURLAlias $spiUrlAlias$path$languageCode)
  354.     {
  355.         $matchedPathElements = [];
  356.         $matchedPathLanguageCodes = [];
  357.         $pathElements explode('/'$path);
  358.         $pathLevels count($spiUrlAlias->pathData);
  359.         foreach ($pathElements as $level => $pathElement) {
  360.             if ($level === $pathLevels 1) {
  361.                 $matchedLanguageCode $this->selectAliasLanguageCode(
  362.                     $spiUrlAlias,
  363.                     $languageCode,
  364.                     $this->languageResolver->getShowAllTranslations(),
  365.                     $this->languageResolver->getPrioritizedLanguages()
  366.                 );
  367.             } else {
  368.                 $matchedLanguageCode $this->matchLanguageCode($spiUrlAlias->pathData[$level], $pathElement);
  369.             }
  370.             if ($matchedLanguageCode === false) {
  371.                 return [falsefalse];
  372.             }
  373.             $matchedPathLanguageCodes[] = $matchedLanguageCode;
  374.             $matchedPathElements[] = $spiUrlAlias->pathData[$level]['translations'][$matchedLanguageCode];
  375.         }
  376.         return [implode('/'$matchedPathElements), $matchedPathLanguageCodes];
  377.     }
  378.     /**
  379.      * @param array $pathElementData
  380.      * @param string $pathElement
  381.      *
  382.      * @return string|bool
  383.      */
  384.     protected function matchLanguageCode(array $pathElementData$pathElement)
  385.     {
  386.         foreach ($this->sortTranslationsByPrioritizedLanguages($pathElementData['translations']) as $translationData) {
  387.             if (strtolower($pathElement) === strtolower($translationData['text'])) {
  388.                 return $translationData['lang'];
  389.             }
  390.         }
  391.         return false;
  392.     }
  393.     /**
  394.      * Needed when translations for the part of the alias are the same for multiple languages.
  395.      * In that case we need to ensure that more prioritized language is chosen.
  396.      *
  397.      * @param array $translations
  398.      *
  399.      * @return array
  400.      */
  401.     private function sortTranslationsByPrioritizedLanguages(array $translations)
  402.     {
  403.         $sortedTranslations = [];
  404.         foreach ($this->languageResolver->getPrioritizedLanguages() as $languageCode) {
  405.             if (isset($translations[$languageCode])) {
  406.                 $sortedTranslations[] = [
  407.                     'lang' => $languageCode,
  408.                     'text' => $translations[$languageCode],
  409.                 ];
  410.                 unset($translations[$languageCode]);
  411.             }
  412.         }
  413.         foreach ($translations as $languageCode => $translation) {
  414.             $sortedTranslations[] = [
  415.                 'lang' => $languageCode,
  416.                 'text' => $translation,
  417.             ];
  418.         }
  419.         return $sortedTranslations;
  420.     }
  421.     /**
  422.      * Returns true or false depending if URL alias is loadable or not for language settings in place.
  423.      *
  424.      * @param \eZ\Publish\SPI\Persistence\Content\URLAlias $spiUrlAlias
  425.      * @param string|null $languageCode
  426.      * @param bool $showAllTranslations
  427.      * @param string[] $prioritizedLanguageList
  428.      *
  429.      * @return bool
  430.      */
  431.     protected function isUrlAliasLoadable(
  432.         SPIURLAlias $spiUrlAlias,
  433.         ?string $languageCode,
  434.         bool $showAllTranslations,
  435.         array $prioritizedLanguageList
  436.     ) {
  437.         if (isset($languageCode) && !in_array($languageCode$spiUrlAlias->languageCodes)) {
  438.             return false;
  439.         }
  440.         if ($showAllTranslations) {
  441.             return true;
  442.         }
  443.         foreach ($spiUrlAlias->pathData as $levelPathData) {
  444.             if ($levelPathData['always-available']) {
  445.                 continue;
  446.             }
  447.             foreach ($levelPathData['translations'] as $translationLanguageCode => $translation) {
  448.                 if (in_array($translationLanguageCode$prioritizedLanguageList)) {
  449.                     continue 2;
  450.                 }
  451.             }
  452.             return false;
  453.         }
  454.         return true;
  455.     }
  456.     /**
  457.      * Returns true or false depending if URL alias is loadable or not for language settings in place.
  458.      *
  459.      * @param array $pathData
  460.      * @param array $languageCodes
  461.      *
  462.      * @return bool
  463.      */
  464.     protected function isPathLoadable(array $pathData, array $languageCodes)
  465.     {
  466.         if ($this->languageResolver->getShowAllTranslations()) {
  467.             return true;
  468.         }
  469.         foreach ($pathData as $level => $levelPathData) {
  470.             if ($levelPathData['always-available']) {
  471.                 continue;
  472.             }
  473.             if (in_array($languageCodes[$level], $this->languageResolver->getPrioritizedLanguages())) {
  474.                 continue;
  475.             }
  476.             return false;
  477.         }
  478.         return true;
  479.     }
  480.     /**
  481.      * List global aliases.
  482.      *
  483.      * @param string $languageCode filters those which are valid for the given language
  484.      * @param int $offset
  485.      * @param int $limit
  486.      *
  487.      * @return \eZ\Publish\API\Repository\Values\Content\URLAlias[]
  488.      */
  489.     public function listGlobalAliases(?string $languageCode nullint $offset 0int $limit = -1): iterable
  490.     {
  491.         $urlAliasList = [];
  492.         $spiUrlAliasList $this->urlAliasHandler->listGlobalURLAliases(
  493.             $languageCode,
  494.             $offset,
  495.             $limit
  496.         );
  497.         foreach ($spiUrlAliasList as $spiUrlAlias) {
  498.             $path $this->extractPath(
  499.                 $spiUrlAlias,
  500.                 $languageCode,
  501.                 $this->languageResolver->getShowAllTranslations(),
  502.                 $this->languageResolver->getPrioritizedLanguages()
  503.             );
  504.             if ($path === false) {
  505.                 continue;
  506.             }
  507.             $urlAliasList[] = $this->buildUrlAliasDomainObject($spiUrlAlias$path);
  508.         }
  509.         return $urlAliasList;
  510.     }
  511.     /**
  512.      * Removes urls aliases.
  513.      *
  514.      * This method does not remove autogenerated aliases for locations.
  515.      *
  516.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove url alias
  517.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if alias list contains
  518.      *         autogenerated alias
  519.      *
  520.      * @param \eZ\Publish\API\Repository\Values\Content\URLAlias[] $aliasList
  521.      */
  522.     public function removeAliases(array $aliasList): void
  523.     {
  524.         if ($this->permissionResolver->hasAccess('content''urltranslator') === false) {
  525.             throw new UnauthorizedException('content''urltranslator');
  526.         }
  527.         $spiUrlAliasList = [];
  528.         foreach ($aliasList as $alias) {
  529.             if (!$alias->isCustom) {
  530.                 throw new InvalidArgumentException(
  531.                     '$aliasList',
  532.                     'The alias list contains an autogenerated alias'
  533.                 );
  534.             }
  535.             $spiUrlAliasList[] = $this->buildSPIUrlAlias($alias);
  536.         }
  537.         $this->repository->beginTransaction();
  538.         try {
  539.             $this->urlAliasHandler->removeURLAliases($spiUrlAliasList);
  540.             $this->repository->commit();
  541.         } catch (Exception $e) {
  542.             $this->repository->rollback();
  543.             throw $e;
  544.         }
  545.     }
  546.     /**
  547.      * Builds persistence domain object.
  548.      *
  549.      * @param \eZ\Publish\API\Repository\Values\Content\URLAlias $urlAlias
  550.      *
  551.      * @return \eZ\Publish\SPI\Persistence\Content\URLAlias
  552.      */
  553.     protected function buildSPIUrlAlias(URLAlias $urlAlias)
  554.     {
  555.         return new SPIURLAlias(
  556.             [
  557.                 'id' => $urlAlias->id,
  558.                 'type' => $urlAlias->type,
  559.                 'destination' => $urlAlias->destination,
  560.                 'isCustom' => $urlAlias->isCustom,
  561.             ]
  562.         );
  563.     }
  564.     /**
  565.      * looks up the URLAlias for the given url.
  566.      *
  567.      * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the path does not exist or is not valid for the given language
  568.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the path exceeded maximum depth level
  569.      *
  570.      * @param string $url
  571.      * @param string|null $languageCode
  572.      *
  573.      * @return \eZ\Publish\API\Repository\Values\Content\URLAlias
  574.      */
  575.     public function lookup(string $url, ?string $languageCode null): URLAlias
  576.     {
  577.         $url $this->cleanUrl($url);
  578.         $spiUrlAlias $this->urlAliasHandler->lookup($url);
  579.         list($path$languageCodes) = $this->matchPath($spiUrlAlias$url$languageCode);
  580.         if ($path === false || !$this->isPathLoadable($spiUrlAlias->pathData$languageCodes)) {
  581.             throw new NotFoundException('URLAlias'$url);
  582.         }
  583.         return $this->buildUrlAliasDomainObject($spiUrlAlias$path);
  584.     }
  585.     /**
  586.      * Returns the URL alias for the given location in the given language.
  587.      *
  588.      * If $languageCode is null the method returns the url alias in the most prioritized language.
  589.      *
  590.      * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if no url alias exist for the given language
  591.      *
  592.      * @param \eZ\Publish\API\Repository\Values\Content\Location $location
  593.      * @param string|null $languageCode
  594.      * @param bool|null $showAllTranslations
  595.      * @param string[]|null $prioritizedLanguageList
  596.      *
  597.      * @return \eZ\Publish\API\Repository\Values\Content\URLAlias
  598.      */
  599.     public function reverseLookup(
  600.         Location $location,
  601.         ?string $languageCode null,
  602.         ?bool $showAllTranslations null,
  603.         ?array $prioritizedLanguageList null
  604.     ): URLAlias {
  605.         if ($showAllTranslations === null) {
  606.             $showAllTranslations $this->languageResolver->getShowAllTranslations();
  607.         }
  608.         if ($prioritizedLanguageList === null) {
  609.             $prioritizedLanguageList $this->languageResolver->getPrioritizedLanguages();
  610.         }
  611.         $urlAliases $this->listLocationAliases(
  612.             $location,
  613.             false,
  614.             $languageCode,
  615.             $showAllTranslations,
  616.             $prioritizedLanguageList
  617.         );
  618.         foreach ($prioritizedLanguageList as $prioritizedLanguageCode) {
  619.             foreach ($urlAliases as $urlAlias) {
  620.                 if (in_array($prioritizedLanguageCode$urlAlias->languageCodes)) {
  621.                     return $urlAlias;
  622.                 }
  623.             }
  624.         }
  625.         foreach ($urlAliases as $urlAlias) {
  626.             if ($urlAlias->alwaysAvailable) {
  627.                 return $urlAlias;
  628.             }
  629.         }
  630.         if (!empty($urlAliases) && $showAllTranslations) {
  631.             return reset($urlAliases);
  632.         }
  633.         throw new NotFoundException('URLAlias'$location->id);
  634.     }
  635.     /**
  636.      * Loads URL alias by given $id.
  637.      *
  638.      * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
  639.      *
  640.      * @param string $id
  641.      *
  642.      * @return \eZ\Publish\API\Repository\Values\Content\URLAlias
  643.      */
  644.     public function load(string $id): URLAlias
  645.     {
  646.         $spiUrlAlias $this->urlAliasHandler->loadUrlAlias($id);
  647.         $path $this->extractPath(
  648.             $spiUrlAlias,
  649.             null,
  650.             $this->languageResolver->getShowAllTranslations(),
  651.             $this->languageResolver->getPrioritizedLanguages()
  652.         );
  653.         if ($path === false) {
  654.             throw new NotFoundException('URLAlias'$id);
  655.         }
  656.         return $this->buildUrlAliasDomainObject($spiUrlAlias$path);
  657.     }
  658.     /**
  659.      * Refresh all system URL aliases for the given Location (and historize outdated if needed).
  660.      *
  661.      * @param \eZ\Publish\API\Repository\Values\Content\Location $location
  662.      *
  663.      * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
  664.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
  665.      * @throws \Exception any unexpected exception that might come from custom Field Type implementation
  666.      */
  667.     public function refreshSystemUrlAliasesForLocation(Location $location): void
  668.     {
  669.         if (!$this->repository->getPermissionResolver()->canUser('content''urltranslator'$location)) {
  670.             throw new UnauthorizedException('content''urltranslator');
  671.         }
  672.         $this->repository->beginTransaction();
  673.         try {
  674.             $content $location->getContent();
  675.             $urlAliasNames $this->nameSchemaService->resolveUrlAliasSchema($content);
  676.             foreach ($urlAliasNames as $languageCode => $name) {
  677.                 $this->urlAliasHandler->publishUrlAliasForLocation(
  678.                     $location->id,
  679.                     $location->parentLocationId,
  680.                     $name,
  681.                     $languageCode,
  682.                     $content->contentInfo->alwaysAvailable
  683.                 );
  684.             }
  685.             $this->urlAliasHandler->repairBrokenUrlAliasesForLocation($location->id);
  686.             // handle URL aliases for missing Translations
  687.             $this->urlAliasHandler->archiveUrlAliasesForDeletedTranslations(
  688.                 $location->id,
  689.                 $location->parentLocationId,
  690.                 $content->getVersionInfo()->languageCodes
  691.             );
  692.             $this->repository->commit();
  693.         } catch (Exception $e) {
  694.             $this->repository->rollback();
  695.             throw $e;
  696.         }
  697.     }
  698.     /**
  699.      * Delete global, system or custom URL alias pointing to non-existent Locations.
  700.      *
  701.      * @return int Number of removed URL aliases
  702.      *
  703.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
  704.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
  705.      */
  706.     public function deleteCorruptedUrlAliases(): int
  707.     {
  708.         if ($this->repository->getPermissionResolver()->hasAccess('content''urltranslator') === false) {
  709.             throw new UnauthorizedException('content''urltranslator');
  710.         }
  711.         return $this->urlAliasHandler->deleteCorruptedUrlAliases();
  712.     }
  713.     /**
  714.      * @param string $url
  715.      *
  716.      * @return string
  717.      */
  718.     protected function cleanUrl(string $url): string
  719.     {
  720.         return trim($url'/ ');
  721.     }
  722.     /**
  723.      * Builds API UrlAlias object from given SPI UrlAlias object.
  724.      *
  725.      * @param \eZ\Publish\SPI\Persistence\Content\URLAlias $spiUrlAlias
  726.      * @param string $path
  727.      *
  728.      * @return \eZ\Publish\API\Repository\Values\Content\URLAlias
  729.      */
  730.     protected function buildUrlAliasDomainObject(SPIURLAlias $spiUrlAliasstring $path): URLAlias
  731.     {
  732.         return new URLAlias(
  733.             [
  734.                 'id' => $spiUrlAlias->id,
  735.                 'type' => $spiUrlAlias->type,
  736.                 'destination' => $spiUrlAlias->destination,
  737.                 'languageCodes' => $spiUrlAlias->languageCodes,
  738.                 'alwaysAvailable' => $spiUrlAlias->alwaysAvailable,
  739.                 'path' => '/' $path,
  740.                 'isHistory' => $spiUrlAlias->isHistory,
  741.                 'isCustom' => $spiUrlAlias->isCustom,
  742.                 'forward' => $spiUrlAlias->forward,
  743.             ]
  744.         );
  745.     }
  746. }