vendor/shopware/core/Content/Seo/HreflangLoader.php line 107

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Seo;
  3. use Doctrine\DBAL\ArrayParameterType;
  4. use Doctrine\DBAL\Connection;
  5. use Shopware\Core\Content\Seo\Hreflang\HreflangCollection;
  6. use Shopware\Core\Content\Seo\Hreflang\HreflangStruct;
  7. use Shopware\Core\Framework\Log\Package;
  8. use Shopware\Core\Framework\Uuid\Uuid;
  9. use Symfony\Component\Routing\RouterInterface;
  10. #[Package('sales-channel')]
  11. class HreflangLoader implements HreflangLoaderInterface
  12. {
  13.     /**
  14.      * @internal
  15.      */
  16.     public function __construct(
  17.         private readonly RouterInterface $router,
  18.         private readonly Connection $connection
  19.     ) {
  20.     }
  21.     public function load(HreflangLoaderParameter $parameter): HreflangCollection
  22.     {
  23.         $salesChannelContext $parameter->getSalesChannelContext();
  24.         if (!$salesChannelContext->getSalesChannel()->isHreflangActive()) {
  25.             return new HreflangCollection();
  26.         }
  27.         $domains $this->fetchSalesChannelDomains($salesChannelContext->getSalesChannel()->getId());
  28.         if ($parameter->getRoute() === 'frontend.home.page') {
  29.             return $this->getHreflangForHomepage($domains$salesChannelContext->getSalesChannel()->getHreflangDefaultDomainId());
  30.         }
  31.         $pathInfo $this->router->generate($parameter->getRoute(), $parameter->getRouteParameters(), RouterInterface::ABSOLUTE_PATH);
  32.         $languageToDomainMapping $this->getLanguageToDomainMapping($domains);
  33.         $seoUrls $this->fetchSeoUrls($pathInfo$salesChannelContext->getSalesChannel()->getId(), array_keys($languageToDomainMapping));
  34.         // We need at least two links
  35.         if (\count($seoUrls) <= 1) {
  36.             return new HreflangCollection();
  37.         }
  38.         $hreflangCollection = new HreflangCollection();
  39.         /** @var array{seoPathInfo: string, languageId: string} $seoUrl */
  40.         foreach ($seoUrls as $seoUrl) {
  41.             /** @var array{languageId: string, id: string, url: string, locale: string, onlyLocale: bool} $domain */
  42.             foreach ($languageToDomainMapping[$seoUrl['languageId']] as $domain) {
  43.                 $this->addHreflangForDomain(
  44.                     $domain,
  45.                     $seoUrl,
  46.                     $salesChannelContext->getSalesChannel()->getHreflangDefaultDomainId(),
  47.                     $hreflangCollection
  48.                 );
  49.             }
  50.         }
  51.         return $hreflangCollection;
  52.     }
  53.     /**
  54.      * @param list<array{languageId: string, id: string, url: string, locale: string, onlyLocale: bool}> $domains
  55.      */
  56.     private function getHreflangForHomepage(array $domains, ?string $defaultDomainId): HreflangCollection
  57.     {
  58.         $collection = new HreflangCollection();
  59.         if (\count($domains) <= 1) {
  60.             return new HreflangCollection();
  61.         }
  62.         /** @var array{languageId: string, id: string, url: string, locale: string, onlyLocale: bool} $domain */
  63.         foreach ($domains as $domain) {
  64.             $this->addHreflangForDomain(
  65.                 $domain,
  66.                 null,
  67.                 $defaultDomainId,
  68.                 $collection
  69.             );
  70.         }
  71.         return $collection;
  72.     }
  73.     /**
  74.      * @return list<array{languageId: string, id: string, url: string, locale: string, onlyLocale: bool}>
  75.      */
  76.     private function fetchSalesChannelDomains(string $salesChannelId): array
  77.     {
  78.         /** @var list<array{languageId: string, id: string, url: string, locale: string, onlyLocale: bool}> $result */
  79.         $result $this->connection->fetchAllAssociative(
  80.             'SELECT `domain`.`language_id` AS languageId,
  81.                           `domain`.`id` AS id,
  82.                           `domain`.`url` AS url,
  83.                           `domain`.`hreflang_use_only_locale` AS onlyLocale,
  84.                           `locale`.`code` AS locale
  85.             FROM `sales_channel_domain` AS `domain`
  86.             INNER JOIN `language` ON `language`.`id` = `domain`.`language_id`
  87.             INNER JOIN `locale` ON `locale`.`id` = `language`.`locale_id`
  88.             WHERE `domain`.`sales_channel_id` = :salesChannelId',
  89.             ['salesChannelId' => Uuid::fromHexToBytes($salesChannelId)]
  90.         );
  91.         return $result;
  92.     }
  93.     /**
  94.      * @param list<array{languageId: string, id: string, url: string, locale: string}> $domains
  95.      *
  96.      * @return array<string, list<array{languageId: string, id: string, url: string, locale: string}>>
  97.      */
  98.     private function getLanguageToDomainMapping(array $domains): array
  99.     {
  100.         $mapping = [];
  101.         foreach ($domains as $domain) {
  102.             $mapping[$domain['languageId']][] = $domain;
  103.         }
  104.         return $mapping;
  105.     }
  106.     /**
  107.      * @param array{languageId: string, id: string, url: string, locale: string, onlyLocale: bool} $domain
  108.      * @param array{seoPathInfo: string, languageId: string}|null $seoUrl
  109.      */
  110.     private function addHreflangForDomain(
  111.         array $domain,
  112.         ?array $seoUrl,
  113.         ?string $defaultDomainId,
  114.         HreflangCollection $collection
  115.     ): void {
  116.         $hrefLang = new HreflangStruct();
  117.         $hrefLang->setUrl($domain['url']);
  118.         if ($seoUrl) {
  119.             $hrefLang->setUrl($domain['url'] . '/' $seoUrl['seoPathInfo']);
  120.         }
  121.         $locale $domain['locale'];
  122.         if ($domain['onlyLocale']) {
  123.             $locale mb_substr($locale02);
  124.         }
  125.         if (Uuid::fromBytesToHex($domain['id']) === $defaultDomainId) {
  126.             $mainLang = clone $hrefLang;
  127.             $mainLang->setLocale('x-default');
  128.             $collection->add($mainLang);
  129.         }
  130.         $hrefLang->setLocale($locale);
  131.         $collection->add($hrefLang);
  132.     }
  133.     /**
  134.      * @param array<string> $languageIds
  135.      *
  136.      * @return list<array{seoPathInfo: string, languageId: string}>
  137.      */
  138.     private function fetchSeoUrls(string $pathInfostring $salesChannelId, array $languageIds): array
  139.     {
  140.         /** @var list<array{seoPathInfo: string, languageId: string}> $result */
  141.         $result $this->connection->fetchAllAssociative(
  142.             'SELECT `seo_path_info` AS seoPathInfo, `language_id` AS languageId
  143.             FROM `seo_url`
  144.             WHERE `path_info` = :pathInfo AND `is_canonical` = 1 AND
  145.                   `sales_channel_id` = :salesChannelId AND `language_id` IN (:languageIds)',
  146.             ['pathInfo' => $pathInfo'salesChannelId' => Uuid::fromHexToBytes($salesChannelId), 'languageIds' => $languageIds],
  147.             ['languageIds' => ArrayParameterType::STRING]
  148.         );
  149.         return $result;
  150.     }
  151. }