src/EventSubscriber/SitemapSubscriber.php line 104

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-04-18
  5.  * Time: 11:17
  6.  */
  7. namespace App\EventSubscriber;
  8. use App\Entity\EnumTrait;
  9. use App\Entity\Location\City;
  10. use App\Entity\Profile\Profile;
  11. use App\Entity\Profile\ProfileInventoryItem;
  12. use App\Entity\Profile\ProfileInventoryItemType;
  13. use App\Entity\Service;
  14. use App\Entity\Saloon\Saloon;
  15. use App\Repository\CityRepository;
  16. use App\Repository\PageMetadataRepository;
  17. use App\Repository\ProfileInventoryItemRepository;
  18. use App\Repository\ProfileRepository;
  19. use App\Repository\SaloonRepository;
  20. use App\Repository\ServiceRepository;
  21. use App\Routing\DynamicRouter;
  22. use App\Service\Features;
  23. use App\Service\ProfileNameLandingProvider;
  24. use App\Service\SeoRouteTemplateMtimesProvider;
  25. use Carbon\Carbon;
  26. use Carbon\CarbonImmutable;
  27. use Doctrine\Persistence\ManagerRegistry;
  28. use GuzzleHttp\ClientInterface;
  29. use Presta\SitemapBundle\Event\SitemapPopulateEvent;
  30. use Presta\SitemapBundle\Service\UrlContainerInterface;
  31. use Presta\SitemapBundle\Sitemap\Url\UrlConcrete;
  32. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  33. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  34. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  35. use Symfony\Component\Routing\RouterInterface;
  36. use Symfony\Component\Yaml\Yaml;
  37. class SitemapSubscriber implements EventSubscriberInterface
  38. {
  39.     public const ROUTE_SITEMAP_OPTION = 'sitemap.custom';
  40.     protected CityRepository $cityRepository;
  41.     protected ProfileRepository $profileRepository;
  42.     protected ProfileInventoryItemRepository $profileInventoryItemRepository;
  43.     protected SaloonRepository $saloonRepository;
  44.     protected ServiceRepository $serviceRepository;
  45.     protected string $defaultCity;
  46.     protected ClientInterface $httpClient;
  47.     private array $sitemapConfig;
  48.     private ?array $routeLocales;
  49.     /** @var array<string, \DateTimeImmutable> */
  50.     private array $uriLastModifiedMap = [];
  51.     /** @var array<string, \DateTimeImmutable> */
  52.     private array $routeLastModifiedMap = [];
  53.     public function __construct(
  54.         protected RouterInterface $router,
  55.         private Features          $features,
  56.         ManagerRegistry           $registry,
  57.         ParameterBagInterface     $parameterBag,
  58.         ClientInterface           $apiDomainTimelineClient,
  59.         protected string          $sitemapConfigPath,
  60.         protected PageMetadataRepository $pageMetadataRepository,
  61.         protected SeoRouteTemplateMtimesProvider $seoRouteTemplateMtimesProvider,
  62.         private ProfileNameLandingProvider $profileNameLandingProvider,
  63.     )
  64.     {
  65.         $this->defaultCity = $parameterBag->get('default_city');
  66.         $this->cityRepository = $registry->getManagerForClass(City::class)->getRepository(City::class);
  67.         $this->profileRepository = $registry->getManagerForClass(Profile::class)->getRepository(Profile::class);
  68.         $this->profileInventoryItemRepository = $registry->getManagerForClass(ProfileInventoryItem::class)->getRepository(ProfileInventoryItem::class);
  69.         $this->saloonRepository = $registry->getManagerForClass(Saloon::class)->getRepository(Saloon::class);
  70.         $this->serviceRepository = $registry->getManagerForClass(Service::class)->getRepository(Service::class);
  71.         $this->httpClient = $apiDomainTimelineClient;
  72.         if ($this->features->has_translations()) {
  73.             $this->routeLocales = $this->features->sitemap_multiple_locales()
  74.                 ? ['ru', 'en']
  75.                 : ['ru'];
  76.         } else {
  77.             $this->routeLocales = null;
  78.         }
  79.     }
  80.     /**
  81.      * @inheritDoc
  82.      */
  83.     public static function getSubscribedEvents()
  84.     {
  85.         return [
  86.             SitemapPopulateEvent::ON_SITEMAP_POPULATE => 'populate',
  87.         ];
  88.     }
  89.     public function populate(SitemapPopulateEvent $event): void
  90.     {
  91.         $this->prepareConfig();
  92.         $this->uriLastModifiedMap = $this->pageMetadataRepository->findUriLastModifiedMap();
  93.         $this->routeLastModifiedMap = $this->seoRouteTemplateMtimesProvider->getRouteMtimes();
  94.         $urlContainer = $event->getUrlContainer();
  95.         $this->registerHomepage($urlContainer);
  96.         $this->registerStaticUrls($urlContainer);
  97.         $this->registerCityUrls($urlContainer);
  98.         $this->registerProfileUrls($urlContainer);
  99.         if ($this->features->has_saloons()) {
  100.             $this->registerSaloonUrls($urlContainer);
  101.         }
  102.     }
  103.     private function dateMutable(?\DateTimeImmutable $dateImmutable): ?\DateTime
  104.     {
  105.         if (null === $dateImmutable) {
  106.             return Carbon::now();
  107.         }
  108.         return Carbon::createFromTimestampUTC($dateImmutable->getTimestamp());
  109.     }
  110.     private function normalizeUrlPath(string $absoluteUrl): string
  111.     {
  112.         $path = parse_url($absoluteUrl, PHP_URL_PATH) ?? '/';
  113.         $path = str_replace('/en', '', $path);
  114.         if (false !== $pos = strpos($path, '?')) {
  115.             $path = substr($path, 0, $pos);
  116.         }
  117.         return $path;
  118.     }
  119.     private function resolveListingLastModified(string $absoluteUrl, string $canonicalRoute, string $section): \DateTime
  120.     {
  121.         $path = $this->normalizeUrlPath($absoluteUrl);
  122.         if (isset($this->uriLastModifiedMap[$path])) {
  123.             return $this->dateMutable($this->uriLastModifiedMap[$path]);
  124.         }
  125.         if (isset($this->routeLastModifiedMap[$canonicalRoute])) {
  126.             return $this->dateMutable($this->routeLastModifiedMap[$canonicalRoute]);
  127.         }
  128.         return $this->getSectionLastModified($section);
  129.     }
  130.     private function addListingUrl(
  131.         UrlContainerInterface $urlContainer,
  132.         string $absoluteUrl,
  133.         string $canonicalRoute,
  134.         string $section,
  135.     ): void {
  136.         $urlContainer->addUrl(
  137.             new UrlConcrete($absoluteUrl, $this->resolveListingLastModified($absoluteUrl, $canonicalRoute, $section)),
  138.             $this->getSitemapSectionName($section)
  139.         );
  140.     }
  141.     private function generateLocalizedUrls(string $canonicalRoute, array $routeParameters): iterable
  142.     {
  143.         if (null === $this->routeLocales) {
  144.             yield $this->router->generate($canonicalRoute, $routeParameters, UrlGeneratorInterface::ABSOLUTE_URL);
  145.         } else {
  146.             foreach ($this->routeLocales as $routeLocale) {
  147.                 yield $this->router->generate("$canonicalRoute.$routeLocale", $routeParameters, UrlGeneratorInterface::ABSOLUTE_URL);
  148.             }
  149.         }
  150.     }
  151.     protected function registerHomepage(UrlContainerInterface $urlContainer): void
  152.     {
  153.         foreach ($this->generateLocalizedUrls('homepage', []) as $url) {
  154.             $this->addListingUrl($urlContainer, $url, 'homepage', 'geo');
  155.         }
  156.     }
  157.     protected function registerStaticUrls(UrlContainerInterface $urlContainer): void
  158.     {
  159.         $lastModified = $this->getSectionLastModified('categories');
  160.         foreach ($this->findSitemapRoutesBySection('static') as $route => $parameters) {
  161.             foreach ($this->generateLocalizedUrls($route, $parameters) as $url) {
  162.                 $urlContainer->addUrl(new UrlConcrete(
  163.                     $url, $lastModified
  164.                 ), $this->getSitemapSectionName('categories'));
  165.             }
  166.         }
  167.     }
  168.     protected function registerCityUrls(UrlContainerInterface $urlContainer): void
  169.     {
  170.         $homepageAsCityList = $this->features->homepage_as_city_list();
  171.         foreach ($this->cityRepository->iterateAll() as $city) {
  172.             /** @var City $city */
  173.             // Если включена фича вывода списка городов на главной странице, добавляем в sitemap для всех городов (в том числе и для дефолтного) страницу фильтра по городу;
  174.             // Если фича выключена, то для дефолтного города не добавляем страницу фильтра анкет по городу - она уже будет добавлена как роут "homepage".
  175.             if ($homepageAsCityList || !$city->equals($this->defaultCity)) {
  176.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_city', ['city' => $city->getUriIdentity()]) as $url) {
  177.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_city', 'geo');
  178.                 }
  179.             }
  180.             $this->registerCityLocationUrls($urlContainer, $city);
  181.             $this->registerCityStaticUrls($urlContainer, $city);
  182.             $this->registerCityNameUrls($urlContainer, $city);
  183.         }
  184.     }
  185.     protected function registerCityNameUrls(UrlContainerInterface $urlContainer, City $city): void
  186.     {
  187.         if (!$this->features->list_names()) {
  188.             return;
  189.         }
  190.         foreach ($this->generateLocalizedUrls('profile_list.list_names', ['city' => $city->getUriIdentity()]) as $url) {
  191.             $this->addListingUrl($urlContainer, $url, 'profile_list.list_names', 'profiles');
  192.         }
  193.         foreach ($this->profileNameLandingProvider->namesByCity($city) as $name) {
  194.             foreach ($this->generateLocalizedUrls('profile_list.list_by_name', [
  195.                 'city' => $city->getUriIdentity(),
  196.                 'name' => $name['uriIdentity'],
  197.             ]) as $url) {
  198.                 $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_name', 'profiles');
  199.             }
  200.         }
  201.     }
  202.     protected function registerCityLocationUrls(UrlContainerInterface $urlContainer, City $city): void
  203.     {
  204.         foreach ($city->getCounties() as $county) {
  205.             foreach ($this->generateLocalizedUrls('profile_list.list_by_county', ['city' => $city->getUriIdentity(), 'county' => $county->getUriIdentity()]) as $url) {
  206.                 $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_county', 'geo');
  207.             }
  208.         }
  209.         foreach ($city->getDistricts() as $district) {
  210.             foreach ($this->generateLocalizedUrls('profile_list.list_by_district', ['city' => $city->getUriIdentity(), 'district' => $district->getUriIdentity()]) as $url) {
  211.                 $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_district', 'geo');
  212.             }
  213.         }
  214.         foreach ($city->getStations() as $station) {
  215.             foreach ($this->generateLocalizedUrls('profile_list.list_by_station', ['city' => $city->getUriIdentity(), 'station' => $station->getUriIdentity()]) as $url) {
  216.                 $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_station', 'geo');
  217.             }
  218.         }
  219.         if ($this->features->extra_category_eromassage()) {
  220.             foreach ($city->getCounties() as $county) {
  221.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_county_eromassage', ['city' => $city->getUriIdentity(), 'county' => $county->getUriIdentity()]) as $url) {
  222.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_county_eromassage', 'categories');
  223.                 }
  224.             }
  225.             foreach ($city->getDistricts() as $district) {
  226.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_district_eromassage', ['city' => $city->getUriIdentity(), 'district' => $district->getUriIdentity()]) as $url) {
  227.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_district_eromassage', 'categories');
  228.                 }
  229.             }
  230.             foreach ($city->getStations() as $station) {
  231.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_station_eromassage', ['city' => $city->getUriIdentity(), 'station' => $station->getUriIdentity()]) as $url) {
  232.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_station_eromassage', 'categories');
  233.                 }
  234.             }
  235.         }
  236.         if ($this->features->extra_category_verified()) {
  237.             foreach ($city->getCounties() as $county) {
  238.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_county_verified', ['city' => $city->getUriIdentity(), 'county' => $county->getUriIdentity()]) as $url) {
  239.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_county_verified', 'categories');
  240.                 }
  241.             }
  242.             foreach ($city->getDistricts() as $district) {
  243.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_district_verified', ['city' => $city->getUriIdentity(), 'district' => $district->getUriIdentity()]) as $url) {
  244.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_district_verified', 'categories');
  245.                 }
  246.             }
  247.             foreach ($city->getStations() as $station) {
  248.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_station_verified', ['city' => $city->getUriIdentity(), 'station' => $station->getUriIdentity()]) as $url) {
  249.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_station_verified', 'categories');
  250.                 }
  251.             }
  252.         }
  253.         if ($this->features->extra_category_cheap()) {
  254.             foreach ($city->getCounties() as $county) {
  255.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_county_cheap', ['city' => $city->getUriIdentity(), 'county' => $county->getUriIdentity()]) as $url) {
  256.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_county_cheap', 'categories');
  257.                 }
  258.             }
  259.             foreach ($city->getDistricts() as $district) {
  260.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_district_cheap', ['city' => $city->getUriIdentity(), 'district' => $district->getUriIdentity()]) as $url) {
  261.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_district_cheap', 'categories');
  262.                 }
  263.             }
  264.             foreach ($city->getStations() as $station) {
  265.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_station_cheap', ['city' => $city->getUriIdentity(), 'station' => $station->getUriIdentity()]) as $url) {
  266.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_station_cheap', 'categories');
  267.                 }
  268.             }
  269.         }
  270.         if ($this->features->extra_category_mature()) {
  271.             foreach ($city->getCounties() as $county) {
  272.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_county_mature', ['city' => $city->getUriIdentity(), 'county' => $county->getUriIdentity()]) as $url) {
  273.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_county_mature', 'categories');
  274.                 }
  275.             }
  276.             foreach ($city->getDistricts() as $district) {
  277.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_district_mature', ['city' => $city->getUriIdentity(), 'district' => $district->getUriIdentity()]) as $url) {
  278.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_district_mature', 'categories');
  279.                 }
  280.             }
  281.             foreach ($city->getStations() as $station) {
  282.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_station_mature', ['city' => $city->getUriIdentity(), 'station' => $station->getUriIdentity()]) as $url) {
  283.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_station_mature', 'categories');
  284.                 }
  285.             }
  286.         }
  287.         if ($this->features->extra_category_uzbek()) {
  288.             foreach ($city->getCounties() as $county) {
  289.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_county_uzbek', ['city' => $city->getUriIdentity(), 'county' => $county->getUriIdentity()]) as $url) {
  290.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_county_uzbek', 'categories');
  291.                 }
  292.             }
  293.             foreach ($city->getDistricts() as $district) {
  294.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_district_uzbek', ['city' => $city->getUriIdentity(), 'district' => $district->getUriIdentity()]) as $url) {
  295.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_district_uzbek', 'categories');
  296.                 }
  297.             }
  298.             foreach ($city->getStations() as $station) {
  299.                 foreach ($this->generateLocalizedUrls('profile_list.list_by_station_uzbek', ['city' => $city->getUriIdentity(), 'station' => $station->getUriIdentity()]) as $url) {
  300.                     $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_station_uzbek', 'categories');
  301.                 }
  302.             }
  303.         }
  304.         foreach ($this->generateLocalizedUrls('map.page', ['city' => $city->getUriIdentity()]) as $url) {
  305.             $this->addListingUrl($urlContainer, $url, 'map.page', 'geo');
  306.         }
  307.     }
  308.     protected function registerCityStaticUrls(UrlContainerInterface $urlContainer, City $city): void
  309.     {
  310.         if ($this->features->has_masseurs()) {
  311.             foreach ($this->generateLocalizedUrls('masseur_list.page', ['city' => $city->getUriIdentity()]) as $url) {
  312.                 $this->addListingUrl($urlContainer, $url, 'masseur_list.page', 'categories');
  313.             }
  314.         }
  315.         if ($this->features->has_saloons()) {
  316.             foreach ($this->generateLocalizedUrls('saloon_list.list_by_city', ['city' => $city->getUriIdentity()]) as $url) {
  317.                 $this->addListingUrl($urlContainer, $url, 'saloon_list.list_by_city', 'categories');
  318.             }
  319.         }
  320.         /*
  321.         if ($this->features->has_archive_page()) {
  322.             foreach ($this->generateLocalizedUrls('profile_list.list_archived', ['city' => $city->getUriIdentity()]) as $url) {
  323.                 $this->addListingUrl($urlContainer, $url, 'profile_list.list_archived', 'categories');
  324.             }
  325.         }
  326.         */
  327.         foreach ($this->serviceRepository->iterateAll() as $service) {
  328.             /** @var \App\Entity\Service $service */
  329.             foreach ($this->generateLocalizedUrls('profile_list.list_by_provided_service', ['city' => $city->getUriIdentity(), 'service' => $service->getUriIdentity()]) as $url) {
  330.                 $this->addListingUrl($urlContainer, $url, 'profile_list.list_by_provided_service', 'categories');
  331.             }
  332.         }
  333.         foreach ($this->profileInventoryItemRepository->iterateAll() as $item) {
  334.             if (!$this->isInventoryItemTypeEnabled($item)) {
  335.                 continue;
  336.             }
  337.             foreach ($this->generateLocalizedUrls('profile_list.list_by_inventory_item', ['city' => $city->getUriIdentity(), 'item' => $item->getUriIdentity()]) as $url) {
  338.                 $urlContainer->addUrl(new UrlConcrete(
  339.                     $url, $lastModified
  340.                 ), $this->getSitemapSectionName('categories'));
  341.             }
  342.         }
  343.         foreach ($this->findSitemapRoutesBySection('categories') as $route => $parameters) {
  344.             $parameters['city'] = $city->getUriIdentity();
  345.             foreach ($this->generateLocalizedUrls($route, $parameters) as $url) {
  346.                 $this->addListingUrl($urlContainer, $url, $route, 'categories');
  347.             }
  348.         }
  349.     }
  350.     private function isInventoryItemTypeEnabled(ProfileInventoryItem $item): bool
  351.     {
  352.         return match ($item->getType()) {
  353.             ProfileInventoryItemType::SEX_TOY => $this->features->sex_toys(),
  354.             ProfileInventoryItemType::CLOTHES_AND_UNDERWEAR => $this->features->clothes_and_underwear(),
  355.             ProfileInventoryItemType::ROLE_PLAYING_COSTUME => $this->features->role_playing_costumes(),
  356.             default => false,
  357.         };
  358.     }
  359.     protected function registerProfileUrls(UrlContainerInterface $urlContainer): void
  360.     {
  361.         foreach ($this->profileRepository->sitemapItemsIterator() as $profile) {
  362.             foreach ($this->generateLocalizedUrls('profile_preview.page', ['city' => $profile['city_uri'], 'profile' => $profile['uri']]) as $url) {
  363.                 $urlContainer->addUrl(new UrlConcrete(
  364.                     $url, $this->dateMutable($profile['updatedAt'])
  365.                 ), $this->getSitemapSectionName('profiles'));
  366.             }
  367.         }
  368.     }
  369.     protected function registerSaloonUrls(UrlContainerInterface $urlContainer): void
  370.     {
  371.         foreach ($this->saloonRepository->sitemapItemsIterator() as $saloon) {
  372.             foreach ($this->generateLocalizedUrls('saloon_preview.page', ['city' => $saloon['city_uri'], 'saloon' => $saloon['uri']]) as $url) {
  373.                 $urlContainer->addUrl(new UrlConcrete(
  374.                     $url, $this->dateMutable($saloon['updatedAt'])
  375.                 ), $this->getSitemapSectionName('saloons'));
  376.             }
  377.         }
  378.     }
  379.     /**
  380.      * Return overridden section name for sitemap file
  381.      */
  382.     protected function getSitemapSectionName(string $name): string
  383.     {
  384.         return $this->sitemapConfig['sections'][$name] ?? $name;
  385.     }
  386.     protected function findSitemapRoutesBySection(string $section): iterable
  387.     {
  388.         $processedRoutes = [];
  389.         foreach ($this->router->getRouteCollection() as $name => $route) {
  390.             if (true === $route->getDefault('_route_disabled')) {
  391.                 continue;
  392.             }
  393.             if (str_starts_with($name, DynamicRouter::DEFAULT_CITY_ROUTE_PREFIX)
  394.                 || str_starts_with($name, DynamicRouter::OVERRIDDEN_ROUTE_PREFIX)
  395.                 || str_ends_with($name, DynamicRouter::PAGINATION_ROUTE_POSTFIX)) {
  396.                 continue;
  397.             }
  398.             $config = $route->getOption(self::ROUTE_SITEMAP_OPTION);
  399.             if (empty($config) || $section !== ($config['section'] ?? null)) {
  400.                 continue;
  401.             }
  402.             if (null !== $this->features) {
  403.                 $routeFeature = $route->getDefault('_feature');
  404.                 if (null !== $routeFeature && !$this->features->isActive($routeFeature)) {
  405.                     continue;
  406.                 }
  407.             }
  408.             $canonical = $route->getDefault('_canonical_route');
  409.             if (null !== $canonical) {
  410.                 $name = $canonical;
  411.             }
  412.             if (array_key_exists($name, $processedRoutes)) {
  413.                 continue;
  414.             }
  415.             $processedRoutes[$name] = true;
  416.             $controller = $route->getDefault('_controller');
  417.             if (is_array($controller)) {
  418.                 $controller = "$controller[0]::$controller[1]";
  419.             }
  420.             if (null === $controller || !str_contains($controller, '::')) {
  421.                 continue;
  422.             }
  423.             [$class, ] = explode('::', $controller, 2);
  424.             if (!class_exists($class)) {
  425.                 continue;
  426.             }
  427.             if (!empty($config['data'])) {
  428.                 foreach ($this->resolveRouteEnumParameters($config['data']) as $parameters) {
  429.                     yield $name => $parameters;
  430.                 }
  431.             } else {
  432.                 yield $name => [];
  433.             }
  434.         }
  435.     }
  436.     private function prepareConfig(): void
  437.     {
  438.         $this->sitemapConfig = [];
  439.         if (!file_exists($this->sitemapConfigPath)) {
  440.             return;
  441.         }
  442.         try {
  443.             $this->sitemapConfig = Yaml::parseFile($this->sitemapConfigPath);
  444.         } catch (\Exception $e) {
  445.             trigger_error($e->getMessage(), E_USER_WARNING);
  446.         }
  447.     }
  448.     private function resolveRouteEnumParameters(array $data): iterable
  449.     {
  450.         $hasEnum = false;
  451.         $firstRow = $data[0];
  452.         foreach ($firstRow as $parameterName => $enumClass) {
  453.             if (is_string($enumClass) && in_array(EnumTrait::class, class_uses($enumClass), true)) {
  454.                 $hasEnum = true;
  455.                 foreach ($enumClass::getUriLocations() as $uri) {
  456.                     yield [$parameterName => $uri];
  457.                 }
  458.                 break;
  459.             }
  460.         }
  461.         if (!$hasEnum) {
  462.             foreach ($data as $parameters) {
  463.                 yield $parameters;
  464.             }
  465.         }
  466.     }
  467.     private function getSectionLastModified(string $section): \DateTime
  468.     {
  469.         // Дефолтные дни месяца для LastModified секций
  470.         $defaults = [
  471.             'geo' => 5,
  472.             'categories' => 20,
  473.         ];
  474.         if (!isset($defaults[$section])) {
  475.             throw new \InvalidArgumentException("Unknown section: $section");
  476.         }
  477.         $defaultLastModified = Carbon::create(null, null, $defaults[$section]);
  478.         if ($defaultLastModified->isFuture()) {
  479.             $defaultLastModified->subMonth();
  480.         }
  481.         $lastSwitch = $this->getLastDomainSwitch();
  482.         if (null !== $lastSwitch && $lastSwitch > $defaultLastModified) {
  483.             return $this->dateMutable($lastSwitch);
  484.         }
  485.         return $defaultLastModified;
  486.     }
  487.     private function getLastDomainSwitch(): ?\DateTimeImmutable
  488.     {
  489.         static $lastSwitch = null;
  490.         static $calledPreviously = false;
  491.         if (!$calledPreviously) {
  492.             try {
  493.                 $calledPreviously = true;
  494.                 $response = $this->httpClient->request('GET', '');
  495.                 $data = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
  496.                 if (null === $data) { // empty timeline, no switches history
  497.                     $lastSwitch = null;
  498.                 } else {
  499.                     $lastSwitch = CarbonImmutable::parse($data['switchedAt']);
  500.                 }
  501.             } catch (\Exception $ex) {
  502.                 trigger_error('Failed to get last domain switch date. '.$ex->getMessage(), E_USER_WARNING);
  503.                 $lastSwitch = null;
  504.             }
  505.         }
  506.         return $lastSwitch;
  507.     }
  508. }