src/Controller/ProfileListController.php line 895

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-03-19
  5.  * Time: 22:28
  6.  */
  7. namespace App\Controller;
  8. use App\Bridge\Porpaginas\Doctrine\ORM\FakeORMQueryPage;
  9. use App\Entity\Location\City;
  10. use App\Entity\Location\County;
  11. use App\Entity\Location\District;
  12. use App\Entity\Location\Station;
  13. use App\Entity\Profile\BodyTypes;
  14. use App\Entity\Profile\BreastTypes;
  15. use App\Entity\Profile\Genders;
  16. use App\Entity\Profile\HairColors;
  17. use App\Entity\Profile\Nationalities;
  18. use App\Entity\Profile\PrivateHaircuts;
  19. use App\Entity\Service;
  20. use App\Entity\ServiceGroups;
  21. use App\Entity\TakeOutLocations;
  22. use App\Repository\ServiceRepository;
  23. use App\Repository\StationRepository;
  24. use App\Service\CountryCurrencyResolver;
  25. use App\Service\DefaultCityProvider;
  26. use App\Service\Features;
  27. use App\Service\ListingRotationApi;
  28. use App\Service\ListingService;
  29. use App\Service\ProfileList;
  30. use App\Service\ProfileListingDataCreator;
  31. use App\Service\ProfileListSpecificationService;
  32. use App\Service\ProfileFilterService;
  33. use App\Specification\ElasticSearch\ISpecification;
  34. use App\Specification\Profile\ProfileHasApartments;
  35. use App\Specification\Profile\ProfileHasComments;
  36. use App\Specification\Profile\ProfileHasVideo;
  37. use App\Specification\Profile\ProfileIdIn;
  38. use App\Specification\Profile\ProfileIdINOrderedByINValues;
  39. use App\Specification\Profile\ProfileIdNotIn;
  40. use App\Specification\Profile\ProfileIsApproved;
  41. use App\Specification\Profile\ProfileIsElite;
  42. use App\Specification\Profile\ProfileIsLocated;
  43. use App\Specification\Profile\ProfileIsProvidingOneOfServices;
  44. use App\Specification\Profile\ProfileIsProvidingTakeOut;
  45. use App\Specification\Profile\ProfileWithAge;
  46. use App\Specification\Profile\ProfileWithBodyType;
  47. use App\Specification\Profile\ProfileWithBreastType;
  48. use App\Specification\Profile\ProfileWithHairColor;
  49. use App\Specification\Profile\ProfileWithNationality;
  50. use App\Specification\Profile\ProfileWithApartmentsOneHourPrice;
  51. use App\Specification\Profile\ProfileWithPrivateHaircut;
  52. use Flagception\Bundle\FlagceptionBundle\Annotations\Feature;
  53. use Happyr\DoctrineSpecification\Filter\Filter;
  54. use Happyr\DoctrineSpecification\Logic\OrX;
  55. use Porpaginas\Doctrine\ORM\ORMQueryResult;
  56. use Porpaginas\Page;
  57. use Psr\Cache\CacheItemPoolInterface;
  58. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
  59. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
  60. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  61. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  62. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  63. use Symfony\Component\HttpFoundation\Request;
  64. use Happyr\DoctrineSpecification\Spec;
  65. use Symfony\Component\HttpFoundation\RequestStack;
  66. use Symfony\Component\HttpFoundation\Response;
  67. /**
  68.  * @see \App\Console\Export\ExportRotationListingsConfigCommand for listing API endpoints
  69.  */
  70. #[Cache(maxage60, public: true)]
  71. class ProfileListController extends AbstractController
  72. {
  73.     use ExtendedPaginationTrait;
  74.     use SpecTrait;
  75.     use ProfileMinPriceTrait;
  76.     use ResponseTrait;
  77.     const ENTRIES_ON_PAGE 36;
  78.     const RESULT_SOURCE_COUNTY 'county';
  79.     const RESULT_SOURCE_DISTRICT 'district';
  80.     const RESULT_SOURCE_STATION 'station';
  81.     const RESULT_SOURCE_APPROVED 'approved';
  82.     const RESULT_SOURCE_WITH_COMMENTS 'with_comments';
  83.     const RESULT_SOURCE_WITH_VIDEO 'with_video';
  84.     const RESULT_SOURCE_WITH_SELFIE 'with_selfie';
  85.     const RESULT_SOURCE_WITH_WHATSAPP 'with_whatsapp';
  86.     const RESULT_SOURCE_WITH_TELEGRAM 'with_telegram';
  87.     const RESULT_SOURCE_EIGHTEEN_YEARS_OLD 'eighteen_years_old';
  88.     const RESULT_SOURCE_WITHOUT_PREPAYMENT 'without_prepayment';
  89.     const RESULT_SOURCE_BIG_ASS 'big_ass';
  90.     const RESULT_SOURCE_WITH_TATTOO 'with_tattoo';
  91.     const RESULT_SOURCE_ELITE 'elite';
  92.     const RESULT_SOURCE_MASSEURS 'masseurs';
  93.     const RESULT_SOURCE_MASSAGE_SERVICE 'massage_service';
  94.     const RESULT_SOURCE_BY_PARAMS 'by_params';
  95.     const RESULT_SOURCE_SERVICE 'service';
  96.     const RESULT_SOURCE_CITY 'city';
  97.     const RESULT_SOURCE_COUNTRY 'country';
  98.     const CACHE_ITEM_STATION_ADDED_PROFILES 'station_added_profiles_ids_';
  99.     private ?string $source null;
  100.     public function __construct(
  101.         private RequestStack $requestStack,
  102.         private ProfileList $profileList,
  103.         private CountryCurrencyResolver $countryCurrencyResolver,
  104.         private ServiceRepository $serviceRepository,
  105.         private ListingService $listingService,
  106.         private Features $features,
  107.         private ProfileFilterService $profilesFilterService,
  108.         private ProfileListSpecificationService $profileListSpecificationService,
  109.         private ProfileListingDataCreator $profileListingDataCreator,
  110.         private CacheItemPoolInterface $stationAddedProfilesCache,
  111.         private ParameterBagInterface $parameterBag,
  112.         private ListingRotationApi $listingRotationApi,
  113.     ) {}
  114.     /**
  115.      * @Feature("extra_category_big_ass")
  116.      */
  117.     #[ParamConverter("city"converter"city_converter")]
  118.     public function listBigAss(Request $requestCity $city): Response
  119.     {
  120.         $specs $this->profileListSpecificationService->listBigAss();
  121.         $response null;
  122.         try {
  123.             $result $this->listingRotationApi->paginate('/city/{city}/category/bolshaya-jopa', ['city' => $city->getId()], $this->getCurrentPageNumber());
  124.             $response = new Response();
  125.             $response->setMaxAge(10);
  126.         } catch (\Exception) {
  127.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  128.         }
  129.         $request->attributes->set('profiles_count'$this->getTotalCount($result));
  130.         return $this->render('ProfileList/list.html.twig', [
  131.             'profiles' => $result,
  132.             'source' => $this->source,
  133.             'source_default' => 'big_ass',
  134.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  135.                 'city' => $city->getUriIdentity(),
  136.                 'page' => $this->getCurrentPageNumber(),
  137.             ]),
  138.             'recommendationSpec' => $specs->recommendationSpec(),
  139.         ], response$response);
  140.     }
  141.     /**
  142.      * @Feature("has_masseurs")
  143.      */
  144.     #[ParamConverter("city"converter"city_converter")]
  145.     public function listForMasseur(City $cityServiceRepository $serviceRepository): Response
  146.     {
  147.         $specs $this->profileListSpecificationService->listForMasseur($city);
  148.         $response null;
  149.         try {
  150.             $result $this->listingRotationApi->paginate('/city/{city}/masseur', ['city' => $city->getId()], $this->getCurrentPageNumber());
  151.             $response = new Response();
  152.             $response->setMaxAge(10);
  153.         } catch (\Exception) {
  154.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  155.         }
  156.         $massageGroupServices $serviceRepository->findBy(['group' => ServiceGroups::MASSAGE]);
  157.         $orX $this->getORSpecForItemsArray([$massageGroupServices], function ($item): ProfileIsProvidingOneOfServices {
  158.             return new ProfileIsProvidingOneOfServices($item);
  159.         });
  160.         $prevCount $result->count();
  161.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_MASSAGE_SERVICE);
  162.         if ($result->count() > $prevCount) {
  163.             $response?->setMaxAge(60);
  164.         }
  165.         return $this->render('ProfileList/list.html.twig', [
  166.             'profiles' => $result,
  167.             'source' => $this->source,
  168.             'source_default' => self::RESULT_SOURCE_MASSEURS,
  169.             'recommendationSpec' => $specs->recommendationSpec(),
  170.         ], response$response);
  171.     }
  172.     public function listByDefaultCity(ParameterBagInterface $parameterBagRequest $request): Response
  173.     {
  174.         $controller get_class($this) . '::listByCity';
  175.         $path = [
  176.             'city' => $parameterBag->get('default_city'),
  177.             'subRequest' => true,
  178.         ];
  179.         //чтобы в обработчике можно было понять, по какому роуту зашли
  180.         $request->request->set('_route''profile_list.list_by_city');
  181.         return $this->forward($controller$path);
  182.     }
  183.     #[ParamConverter("city"converter"city_converter")]
  184.     public function listByCity(ParameterBagInterface $parameterBagRequest $requestCity $citybool $subRequest false): Response
  185.     {
  186.         $page $this->getCurrentPageNumber();
  187.         if ($this->features->redirect_default_city_to_homepage() && false === $subRequest && $city->equals($parameterBag->get('default_city')) && $page 2) {
  188.             return $this->redirectToRoute('homepage', [], 301);
  189.         }
  190.         $specs $this->profileListSpecificationService->listByCity();
  191.         $response null;
  192.         try {
  193.             $result $this->listingRotationApi->paginate('/city/{city}', ['city' => $city->getId()], $page);
  194.             $response = new Response();
  195.             $response->setMaxAge(10);
  196.         } catch (\Exception) {
  197.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  198.         }
  199.         return $this->render('ProfileList/list.html.twig', [
  200.             'profiles' => $result,
  201.             'recommendationSpec' => $specs->recommendationSpec(),
  202.         ], response$response);
  203.     }
  204.     #[ParamConverter("city"converter"city_converter")]
  205.     #[Entity("county"expr"repository.ofUriIdentityWithinCity(county, city)")]
  206.     public function listByCounty(Request $requestCity $cityCounty $county): Response
  207.     {
  208.         if (!$city->hasCounty($county)) {
  209.             throw $this->createNotFoundException();
  210.         }
  211.         $specs $this->profileListSpecificationService->listByCounty($county);
  212.         $response null;
  213.         try {
  214.             $result $this->listingRotationApi->paginate('/city/{city}/county/{county}', ['city' => $city->getId(), 'county' => $county->getId()], $this->getCurrentPageNumber());
  215.             $response = new Response();
  216.             $response->setMaxAge(10);
  217.         } catch (\Exception) {
  218.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  219.         }
  220.         $prevCount $result->count();
  221.         $result $this->checkEmptyResultNotMasseur($result$citySpec::orX(ProfileIsLocated::withinCounties($city$city->getCounties()->toArray())), self::RESULT_SOURCE_COUNTY);
  222.         if ($result->count() > $prevCount) {
  223.             $response?->setMaxAge(60);
  224.         }
  225.         return $this->render('ProfileList/list.html.twig', [
  226.             'profiles' => $result,
  227.             'source' => $this->source,
  228.             'source_default' => self::RESULT_SOURCE_COUNTY,
  229.             'county' => $county,
  230.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  231.                 'city' => $city->getUriIdentity(),
  232.                 'county' => $county->getUriIdentity(),
  233.                 'page' => $this->getCurrentPageNumber()
  234.             ]),
  235.             'recommendationSpec' => $specs->recommendationSpec(),
  236.         ], response$response);
  237.     }
  238.     #[ParamConverter("city"converter"city_converter")]
  239.     #[Entity("district"expr"repository.ofUriIdentityWithinCity(district, city)")]
  240.     public function listByDistrict(Request $requestCity $cityDistrict $district): Response
  241.     {
  242.         if (!$city->hasDistrict($district)) {
  243.             throw $this->createNotFoundException();
  244.         }
  245.         $specs $this->profileListSpecificationService->listByDistrict($district);
  246.         $response null;
  247.         try {
  248.             $result $this->listingRotationApi->paginate('/city/{city}/district/{district}', ['city' => $city->getId(), 'district' => $district->getId()], $this->getCurrentPageNumber());
  249.             $response = new Response();
  250.             $response->setMaxAge(10);
  251.         } catch (\Exception) {
  252.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  253.         }
  254.         $prevCount $result->count();
  255.         $result $this->checkEmptyResultNotMasseur($result$citySpec::orX(ProfileIsLocated::withinDistricts($city$city->getDistricts()->toArray())), self::RESULT_SOURCE_DISTRICT);
  256.         if ($result->count() > $prevCount) {
  257.             $response?->setMaxAge(60);
  258.         }
  259.         return $this->render('ProfileList/list.html.twig', [
  260.             'profiles' => $result,
  261.             'source' => $this->source,
  262.             'source_default' => self::RESULT_SOURCE_DISTRICT,
  263.             'district' => $district,
  264.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  265.                 'city' => $city->getUriIdentity(),
  266.                 'district' => $district->getUriIdentity(),
  267.                 'page' => $this->getCurrentPageNumber()
  268.             ]),
  269.             'recommendationSpec' => $specs->recommendationSpec(),
  270.         ], response$response);
  271.     }
  272.     #[ParamConverter("city"converter"city_converter")]
  273.     #[Entity("station"expr"repository.ofUriIdentityWithinCity(station, city)")]
  274.     public function listByStation(Request $requestCity $cityStation $station): Response
  275.     {
  276.         if (!$city->hasStation($station)) {
  277.             throw $this->createNotFoundException();
  278.         }
  279.         $specs $this->profileListSpecificationService->listByStation($station);
  280.         $response null;
  281.         try {
  282.             $result $this->listingRotationApi->paginate('/city/{city}/station/{station}', ['city' => $city->getId(), 'station' => $station->getId()], $this->getCurrentPageNumber());
  283.             $response = new Response();
  284.             $response->setMaxAge(10);
  285.         } catch (\Exception) {
  286.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  287.         }
  288.         $prevCount $result->count();
  289.         if (true === $this->features->station_page_add_profiles()) {
  290.             $spread $this->parameterBag->get('app.profile.station_page.added_profiles.spread');
  291.             $result $this->addSinglePageStationResults($result$city$station$spread ?: 5);
  292.         }
  293.         if (null !== $station->getDistrict()) {
  294.             $result $this->checkEmptyResultNotMasseur($result$citySpec::orX(ProfileIsLocated::nearStations($city$station->getDistrict()->getStations()->toArray())), self::RESULT_SOURCE_STATION);
  295.         } else {
  296.             $result $this->checkCityAndCountrySource($result$city);
  297.         }
  298.         if ($result->count() > $prevCount) {
  299.             $response?->setMaxAge(60);
  300.         }
  301.         return $this->render('ProfileList/list.html.twig', [
  302.             'profiles' => $result,
  303.             'source' => $this->source,
  304.             'source_default' => self::RESULT_SOURCE_STATION,
  305.             'station' => $station,
  306.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  307.                 'city' => $city->getUriIdentity(),
  308.                 'station' => $station->getUriIdentity(),
  309.                 'page' => $this->getCurrentPageNumber()
  310.             ]),
  311.             'recommendationSpec' => $specs->recommendationSpec(),
  312.         ], response$response);
  313.     }
  314.     private function addSinglePageStationResults(Page $resultCity $cityStation $stationint $spread): Page
  315.     {
  316.         if ($result->totalCount() >= $result->getCurrentLimit()) {
  317.             return $result;
  318.         }
  319.         $addedProfileIds $this->stationAddedProfilesCache->get(self::CACHE_ITEM_STATION_ADDED_PROFILES $station->getId(), function () use ($result$city$station$spread): array {
  320.             $currentSpread rand(0$spread);
  321.             $plannedTotalCount $result->getCurrentLimit() - $spread $currentSpread;
  322.             $result iterator_to_array($result->getIterator());
  323.             $originalProfileIds array_map(fn($item) => $item->id$result);
  324.             if ($station->getDistrict()) {
  325.                 $result $this->addSinglePageResultsUptoAmount($result$citySpec::orX(ProfileIsLocated::withinDistrict($station->getDistrict())), $plannedTotalCount);
  326.             }
  327.             if ($station->getDistrict()?->getCounty()) {
  328.                 $result $this->addSinglePageResultsUptoAmount($result$citySpec::orX(ProfileIsLocated::withinCounty($station->getDistrict()->getCounty())), $plannedTotalCount);
  329.             }
  330.             $result $this->addSinglePageResultsUptoAmount($result$citySpec::orX(ProfileIsLocated::withinCity($city)), $plannedTotalCount);
  331.             $result array_map(fn($item) => $item->id$result);
  332.             return array_diff($result$originalProfileIds);
  333.         });
  334.         $addedProfileIds array_slice($addedProfileIds0$result->getCurrentLimit() - $result->totalCount());
  335.         $originalProfiles iterator_to_array($result->getIterator());
  336.         $addedProfiles $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited($city, new ProfileIdIn($addedProfileIds), null, [Genders::FEMALE], count($addedProfileIds));
  337.         $newResult array_merge($originalProfiles$addedProfiles);
  338.         return new FakeORMQueryPage(01$result->getCurrentLimit(), count($newResult), $newResult);
  339.     }
  340.     private function addSinglePageResultsUptoAmount(array $resultCity $city, ?Filter $specsint $totalCount): array
  341.     {
  342.         $toAdd $totalCount count($result);
  343.         $currentResultIds array_map(fn($profile) => $profile->id$result);
  344.         $resultsToAdd $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited($city$specs, [new ProfileIdNotIn($currentResultIds)], [Genders::FEMALE], $toAdd);
  345.         $result array_merge($result$resultsToAdd);
  346.         return $result;
  347.     }
  348.     private function getTotalCount(Page $result): int
  349.     {
  350.         if (method_exists($result'getTotalCount')) {
  351.             return (int)$result->getTotalCount();
  352.         }
  353.         if (method_exists($result'totalCount')) {
  354.             return (int)$result->totalCount();
  355.         }
  356.         return (int)$result->count();
  357.     }
  358.     #[ParamConverter("city"converter"city_converter")]
  359.     public function listByStations(City $citystring $stationsStationRepository $stationRepository): Response
  360.     {
  361.         $stationIds explode(','$stations);
  362.         $stations $stationRepository->findBy(['uriIdentity' => $stationIds]);
  363.         $specs $this->profileListSpecificationService->listByStations($stations);
  364.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  365.         return $this->render('ProfileList/list.html.twig', [
  366.             'profiles' => $result,
  367.             'recommendationSpec' => $specs->recommendationSpec(),
  368.         ]);
  369.     }
  370.     #[ParamConverter("city"converter"city_converter")]
  371.     public function listApproved(Request $requestCity $city): Response
  372.     {
  373.         $specs $this->profileListSpecificationService->listApproved();
  374.         $response null;
  375.         try {
  376.             $result $this->listingRotationApi->paginate('/city/{city}/approved', ['city' => $city->getId()], $this->getCurrentPageNumber());
  377.             $response = new Response();
  378.             $response->setMaxAge(10);
  379.         } catch (\Exception) {
  380.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  381.         }
  382.         $prevCount $result->count();
  383.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  384.             $this->source self::RESULT_SOURCE_WITH_COMMENTS;
  385.             $result $this->listRandomSinglePage($citynull, new ProfileHasComments(), nulltruefalse);
  386.             if ($result->count() == 0) {
  387.                 $this->source self::RESULT_SOURCE_WITH_VIDEO;
  388.                 $result $this->listRandomSinglePage($citynull, new ProfileHasVideo(), nulltruefalse);
  389.             }
  390.             if ($result->count() == 0) {
  391.                 $this->source self::RESULT_SOURCE_ELITE;
  392.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  393.             }
  394.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  395.         }
  396.         if ($result->count() > $prevCount) {
  397.             $response?->setMaxAge(60);
  398.         }
  399.         return $this->render('ProfileList/list.html.twig', [
  400.             'profiles' => $result,
  401.             'source' => $this->source,
  402.             'source_default' => self::RESULT_SOURCE_APPROVED,
  403.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  404.                 'city' => $city->getUriIdentity(),
  405.                 'page' => $this->getCurrentPageNumber()
  406.             ]),
  407.             'recommendationSpec' => $specs->recommendationSpec(),
  408.         ], response$response);
  409.     }
  410.     /**
  411.      * @Feature("extra_category_without_prepayment")
  412.      */
  413.     #[ParamConverter("city"converter"city_converter")]
  414.     public function listWithoutPrepayment(Request $requestCity $city): Response
  415.     {
  416.         $specs $this->profileListSpecificationService->listWithoutPrepayment();
  417.         $response null;
  418.         try {
  419.             $result $this->listingRotationApi->paginate('/city/{city}/category/without_prepayment', ['city' => $city->getId()], $this->getCurrentPageNumber());
  420.             $response = new Response();
  421.             $response->setMaxAge(10);
  422.         } catch (\Exception) {
  423.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  424.         }
  425.         $request->attributes->set('profiles_count'$this->getTotalCount($result));
  426.         return $this->render('ProfileList/list.html.twig', [
  427.             'profiles' => $result,
  428.             'source' => $this->source,
  429.             'source_default' => self::RESULT_SOURCE_WITHOUT_PREPAYMENT,
  430.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  431.                 'city' => $city->getUriIdentity(),
  432.                 'page' => $this->getCurrentPageNumber(),
  433.             ]),
  434.             'recommendationSpec' => $specs->recommendationSpec(),
  435.         ], response$response);
  436.     }
  437.     /**
  438.      * @Feature("extra_category_with_whatsapp")
  439.      */
  440.     #[ParamConverter("city"converter"city_converter")]
  441.     public function listWithWhatsapp(Request $requestCity $city): Response
  442.     {
  443.         $specs $this->profileListSpecificationService->listWithWhatsapp();
  444.         $response null;
  445.         try {
  446.             $result $this->listingRotationApi->paginate('/city/{city}/category/whatsapp', ['city' => $city->getId()], $this->getCurrentPageNumber());
  447.             $response = new Response();
  448.             $response->setMaxAge(10);
  449.         } catch (\Exception) {
  450.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  451.         }
  452.         $request->attributes->set('profiles_count'$this->getTotalCount($result));
  453.         return $this->render('ProfileList/list.html.twig', [
  454.             'profiles' => $result,
  455.             'source' => $this->source,
  456.             'source_default' => self::RESULT_SOURCE_WITH_WHATSAPP,
  457.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  458.                 'city' => $city->getUriIdentity(),
  459.                 'page' => $this->getCurrentPageNumber(),
  460.             ]),
  461.             'recommendationSpec' => $specs->recommendationSpec(),
  462.         ], response$response);
  463.     }
  464.     /**
  465.      * @Feature("extra_category_with_telegram")
  466.      */
  467.     #[ParamConverter("city"converter"city_converter")]
  468.     public function listWithTelegram(Request $requestCity $city): Response
  469.     {
  470.         $specs $this->profileListSpecificationService->listWithTelegram();
  471.         $response null;
  472.         try {
  473.             $result $this->listingRotationApi->paginate('/city/{city}/category/telegram', ['city' => $city->getId()], $this->getCurrentPageNumber());
  474.             $response = new Response();
  475.             $response->setMaxAge(10);
  476.         } catch (\Exception) {
  477.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  478.         }
  479.         $request->attributes->set('profiles_count'$this->getTotalCount($result));
  480.         return $this->render('ProfileList/list.html.twig', [
  481.             'profiles' => $result,
  482.             'source' => $this->source,
  483.             'source_default' => self::RESULT_SOURCE_WITH_TELEGRAM,
  484.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  485.                 'city' => $city->getUriIdentity(),
  486.                 'page' => $this->getCurrentPageNumber(),
  487.             ]),
  488.             'recommendationSpec' => $specs->recommendationSpec(),
  489.         ], response$response);
  490.     }
  491.     /**
  492.      * @Feature("extra_category_eighteen_years_old")
  493.      */
  494.     #[ParamConverter("city"converter"city_converter")]
  495.     public function listEighteenYearsOld(Request $requestCity $city): Response
  496.     {
  497.         $specs $this->profileListSpecificationService->listEighteenYearsOld();
  498.         $response null;
  499.         try {
  500.             $result $this->listingRotationApi->paginate('/city/{city}/category/eighteen_years_old', ['city' => $city->getId()], $this->getCurrentPageNumber());
  501.             $response = new Response();
  502.             $response->setMaxAge(10);
  503.         } catch (\Exception) {
  504.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  505.         }
  506.         $request->attributes->set('profiles_count'$this->getTotalCount($result));
  507.         return $this->render('ProfileList/list.html.twig', [
  508.             'profiles' => $result,
  509.             'source' => $this->source,
  510.             'source_default' => self::RESULT_SOURCE_EIGHTEEN_YEARS_OLD,
  511.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  512.                 'city' => $city->getUriIdentity(),
  513.                 'ageAlias' => $request->attributes->get('ageAlias'),
  514.                 'page' => $this->getCurrentPageNumber(),
  515.             ]),
  516.             'recommendationSpec' => $specs->recommendationSpec(),
  517.         ], response$response);
  518.     }
  519.     /**
  520.      * @Feature("extra_category_rublevskie")
  521.      */
  522.     #[ParamConverter("city"converter"city_converter")]
  523.     public function listRublevskie(Request $requestCity $city): Response
  524.     {
  525.         if ($city->getUriIdentity() !== 'moscow') {
  526.             throw $this->createNotFoundException();
  527.         }
  528.         $specs $this->profileListSpecificationService->listRublevskie($city);
  529.         $response null;
  530.         try {
  531.             $result $this->listingRotationApi->paginate('/city/{city}/rublevskie', ['city' => $city->getId()], $this->getCurrentPageNumber());
  532.             $response = new Response();
  533.             $response->setMaxAge(10);
  534.         } catch (\Exception) {
  535.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  536.         }
  537.         $request->attributes->set('profiles_count'$this->getTotalCount($result));
  538.         return $this->render('ProfileList/list.html.twig', [
  539.             'profiles' => $result,
  540.             'source' => $this->source,
  541.             'source_default' => 'rublevskie',
  542.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  543.                 'city' => $city->getUriIdentity(),
  544.                 'page' => $this->getCurrentPageNumber(),
  545.             ]),
  546.             'recommendationSpec' => $specs->recommendationSpec(),
  547.         ], response$response);
  548.     }
  549.     /**
  550.      * @Feature("extra_category_with_tattoo")
  551.      */
  552.     #[ParamConverter("city"converter"city_converter")]
  553.     public function listWithTattoo(Request $requestCity $city): Response
  554.     {
  555.         $specs $this->profileListSpecificationService->listWithTattoo();
  556.         $response null;
  557.         try {
  558.             $result $this->listingRotationApi->paginate('/city/{city}/category/tatuirovannye', ['city' => $city->getId()], $this->getCurrentPageNumber());
  559.             $response = new Response();
  560.             $response->setMaxAge(10);
  561.         } catch (\Exception) {
  562.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  563.         }
  564.         $request->attributes->set('profiles_count'$this->getTotalCount($result));
  565.         return $this->render('ProfileList/list.html.twig', [
  566.             'profiles' => $result,
  567.             'source' => $this->source,
  568.             'source_default' => self::RESULT_SOURCE_WITH_TATTOO,
  569.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  570.                 'city' => $city->getUriIdentity(),
  571.                 'page' => $this->getCurrentPageNumber(),
  572.             ]),
  573.             'recommendationSpec' => $specs->recommendationSpec(),
  574.         ], response$response);
  575.     }
  576.     #[ParamConverter("city"converter"city_converter")]
  577.     public function listWithComments(Request $requestCity $city): Response
  578.     {
  579.         $specs $this->profileListSpecificationService->listWithComments();
  580.         $response null;
  581.         try {
  582.             $result $this->listingRotationApi->paginate('/city/{city}/with_comments', ['city' => $city->getId()], $this->getCurrentPageNumber());
  583.             $response = new Response();
  584.             $response->setMaxAge(10);
  585.         } catch (\Exception) {
  586.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  587.         }
  588.         $prevCount $result->count();
  589.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  590.             $this->source self::RESULT_SOURCE_APPROVED;
  591.             $result $this->listRandomSinglePage($citynull, new ProfileIsApproved(), nulltruefalse);
  592.             if ($result->count() == 0) {
  593.                 $this->source self::RESULT_SOURCE_WITH_VIDEO;
  594.                 $result $this->listRandomSinglePage($citynull, new ProfileHasVideo(), nulltruefalse);
  595.             }
  596.             if ($result->count() == 0) {
  597.                 $this->source self::RESULT_SOURCE_ELITE;
  598.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  599.             }
  600.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  601.         }
  602.         if ($result->count() > $prevCount) {
  603.             $response?->setMaxAge(60);
  604.         }
  605.         return $this->render('ProfileList/list.html.twig', [
  606.             'profiles' => $result,
  607.             'source' => $this->source,
  608.             'source_default' => self::RESULT_SOURCE_WITH_COMMENTS,
  609.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  610.                 'city' => $city->getUriIdentity(),
  611.                 'page' => $this->getCurrentPageNumber()
  612.             ]),
  613.             'recommendationSpec' => $specs->recommendationSpec(),
  614.         ], response$response);
  615.     }
  616.     const RESULT_SOURCE_WITH_PIERCING 'with_piercing';
  617.     /**
  618.      * @Feature("extra_category_with_piercing")
  619.      */
  620.     #[ParamConverter("city"converter"city_converter")]
  621.     public function listWithPiercing(Request $requestCity $city): Response
  622.     {
  623.         $specs $this->profileListSpecificationService->listWithPiercing();
  624.         $response null;
  625.         try {
  626.             $result $this->listingRotationApi->paginate('/city/{city}/category/s-pirsingom', ['city' => $city->getId()], $this->getCurrentPageNumber());
  627.             $response = new Response();
  628.             $response->setMaxAge(10);
  629.         } catch (\Exception) {
  630.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  631.         }
  632.         $request->attributes->set('profiles_count'$this->getTotalCount($result));
  633.         return $this->render('ProfileList/list.html.twig', [
  634.             'profiles' => $result,
  635.             'source' => $this->source,
  636.             'source_default' => self::RESULT_SOURCE_WITH_PIERCING,
  637.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  638.                 'city' => $city->getUriIdentity(),
  639.                 'page' => $this->getCurrentPageNumber(),
  640.             ]),
  641.             'recommendationSpec' => $specs->recommendationSpec(),
  642.         ], response$response);
  643.     }
  644.     #[ParamConverter("city"converter"city_converter")]
  645.     public function listWithVideo(Request $requestCity $city): Response
  646.     {
  647.         $specs $this->profileListSpecificationService->listWithVideo();
  648.         $response null;
  649.         try {
  650.             $result $this->listingRotationApi->paginate('/city/{city}/with_video', ['city' => $city->getId()], $this->getCurrentPageNumber());
  651.             $response = new Response();
  652.             $response->setMaxAge(10);
  653.         } catch (\Exception) {
  654.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  655.         }
  656.         $prevCount $result->count();
  657.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  658.             $this->source self::RESULT_SOURCE_APPROVED;
  659.             $result $this->listRandomSinglePage($citynull, new ProfileIsApproved(), nulltruefalse);
  660.             if ($result->count() == 0) {
  661.                 $this->source self::RESULT_SOURCE_WITH_COMMENTS;
  662.                 $result $this->listRandomSinglePage($citynull, new ProfileHasComments(), nulltruefalse);
  663.             }
  664.             if ($result->count() == 0) {
  665.                 $this->source self::RESULT_SOURCE_ELITE;
  666.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  667.             }
  668.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  669.         }
  670.         if ($result->count() > $prevCount) {
  671.             $response?->setMaxAge(60);
  672.         }
  673.         return $this->render('ProfileList/list.html.twig', [
  674.             'profiles' => $result,
  675.             'source' => $this->source,
  676.             'source_default' => self::RESULT_SOURCE_WITH_VIDEO,
  677.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  678.                 'city' => $city->getUriIdentity(),
  679.                 'page' => $this->getCurrentPageNumber()
  680.             ]),
  681.             'recommendationSpec' => $specs->recommendationSpec(),
  682.         ], response$response);
  683.     }
  684.     const RESULT_SOURCE_ROUND_THE_CLOCK 'round_the_clock';
  685.     /**
  686.      * @Feature("extra_category_round_the_clock")
  687.      */
  688.     #[ParamConverter("city"converter"city_converter")]
  689.     public function listRoundTheClock(Request $requestCity $city): Response
  690.     {
  691.         $specs $this->profileListSpecificationService->listRoundTheClock();
  692.         $response null;
  693.         try {
  694.             $result $this->listingRotationApi->paginate('/city/{city}/category/kruglosutochno', ['city' => $city->getId()], $this->getCurrentPageNumber());
  695.             $response = new Response();
  696.             $response->setMaxAge(10);
  697.         } catch (\Exception) {
  698.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  699.         }
  700.         $request->attributes->set('profiles_count'$this->getTotalCount($result));
  701.         return $this->render('ProfileList/list.html.twig', [
  702.             'profiles' => $result,
  703.             'source' => $this->source,
  704.             'source_default' => self::RESULT_SOURCE_ROUND_THE_CLOCK,
  705.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  706.                 'city' => $city->getUriIdentity(),
  707.                 'page' => $this->getCurrentPageNumber(),
  708.             ]),
  709.             'recommendationSpec' => $specs->recommendationSpec(),
  710.         ], response$response);
  711.     }
  712.     #[ParamConverter("city"converter"city_converter")]
  713.     public function listWithSelfie(Request $requestCity $city): Response
  714.     {
  715.         $specs $this->profileListSpecificationService->listWithSelfie();
  716.         $response null;
  717.         try {
  718.             $result $this->listingRotationApi->paginate('/city/{city}/with_selfie', ['city' => $city->getId()], $this->getCurrentPageNumber());
  719.             $response = new Response();
  720.             $response->setMaxAge(10);
  721.         } catch (\Exception) {
  722.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  723.         }
  724.         $prevCount $result->count();
  725.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  726.             $this->source self::RESULT_SOURCE_WITH_VIDEO;
  727.             $result $this->listRandomSinglePage($citynull, new ProfileHasVideo(), nulltruefalse);
  728.             if ($result->count() == 0) {
  729.                 $this->source self::RESULT_SOURCE_APPROVED;
  730.                 $result $this->listRandomSinglePage($citynull, new ProfileIsApproved(), nulltruefalse);
  731.             }
  732.             if ($result->count() == 0) {
  733.                 $this->source self::RESULT_SOURCE_ELITE;
  734.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  735.             }
  736.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  737.         }
  738.         if ($result->count() > $prevCount) {
  739.             $response?->setMaxAge(60);
  740.         }
  741.         return $this->render('ProfileList/list.html.twig', [
  742.             'profiles' => $result,
  743.             'source' => $this->source,
  744.             'source_default' => self::RESULT_SOURCE_WITH_SELFIE,
  745.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  746.                 'city' => $city->getUriIdentity(),
  747.                 'page' => $this->getCurrentPageNumber()
  748.             ]),
  749.             'recommendationSpec' => $specs->recommendationSpec(),
  750.         ], response$response);
  751.     }
  752.     #[ParamConverter("city"converter"city_converter")]
  753.     public function listByPrice(Request $requestCountryCurrencyResolver $countryCurrencyResolverCity $citystring $priceTypeint $minPrice nullint $maxPrice null): Response
  754.     {
  755.         $specs $this->profileListSpecificationService->listByPrice($city$priceType$minPrice$maxPrice);
  756.         $response null;
  757.         try {
  758.             if (!in_array($priceType, ['low''high''elite'])) {
  759.                 throw new \LogicException(sprintf('Price type "%s" is not supported'$priceType));
  760.             }
  761.             $result $this->listingRotationApi->paginate('/city/{city}/price/' $priceType, ['city' => $city->getId()], $this->getCurrentPageNumber());
  762.             $response = new Response();
  763.             $response->setMaxAge(10);
  764.         } catch (\Exception) {
  765.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  766.         }
  767.         $prevCount $result->count();
  768.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  769.             $result $this->processListByPriceEmptyResult($result$city$priceType$minPrice$maxPrice);
  770.         }
  771.         if ($result->count() > $prevCount) {
  772.             $response?->setMaxAge(60);
  773.         }
  774.         return $this->render('ProfileList/list.html.twig', [
  775.             'profiles' => $result,
  776.             'source' => $this->source,
  777.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  778.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  779.                 'city' => $city->getUriIdentity(),
  780.                 'priceType' => $priceType,
  781.                 'minPrice' => $minPrice,
  782.                 'maxPrice' => $maxPrice,
  783.                 'page' => $this->getCurrentPageNumber()
  784.             ]),
  785.             'recommendationSpec' => $specs->recommendationSpec(),
  786.         ], response$response);
  787.     }
  788.     private function processListByPriceEmptyResult(Page $resultCity $citystring $priceTypeint $minPrice nullint $maxPrice null)
  789.     {
  790.         if (!$this->features->fill_empty_profile_list())
  791.             return $result;
  792.         $this->source self::RESULT_SOURCE_BY_PARAMS;
  793.         if ($this->countryCurrencyResolver->getCurrencyFor($city->getCountryCode()) == 'RUB') {
  794.             if ($minPrice && $maxPrice) {
  795.                 if ($minPrice == 2000 && $maxPrice == 3000) {
  796.                     $priceSpec = [
  797.                         ProfileWithApartmentsOneHourPrice::range(15002000),
  798.                         ProfileWithApartmentsOneHourPrice::range(30004000),
  799.                     ];
  800.                 } else if ($minPrice == 3000 && $maxPrice == 4000) {
  801.                     $priceSpec = [
  802.                         ProfileWithApartmentsOneHourPrice::range(20003000),
  803.                         ProfileWithApartmentsOneHourPrice::range(40005000),
  804.                     ];
  805.                 } else if ($minPrice == 4000 && $maxPrice == 5000) {
  806.                     $priceSpec = [
  807.                         ProfileWithApartmentsOneHourPrice::range(30004000),
  808.                         ProfileWithApartmentsOneHourPrice::range(50006000),
  809.                     ];
  810.                 } else if ($minPrice == 5000 && $maxPrice == 6000) {
  811.                     $priceSpec = [
  812.                         ProfileWithApartmentsOneHourPrice::range(4000999999)
  813.                     ];
  814.                 } else {
  815.                     $priceSpec = [
  816.                         ProfileWithApartmentsOneHourPrice::range($minPrice$maxPrice)
  817.                     ];
  818.                 }
  819.                 $result $this->listRandomSinglePage($citynullnull$priceSpectruefalse);
  820.             } elseif ($maxPrice) {
  821.                 if ($maxPrice == 500) {
  822.                     $priceSpec ProfileWithApartmentsOneHourPrice::cheaperThan(1500);
  823.                     $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  824.                     if ($result->count() == 0) {
  825.                         $priceSpec ProfileWithApartmentsOneHourPrice::range(15002000);
  826.                         $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  827.                     }
  828.                 } else if ($maxPrice == 1500) {
  829.                     $priceSpec ProfileWithApartmentsOneHourPrice::range(15002000);
  830.                     $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  831.                     if ($result->count() == 0) {
  832.                         $priceSpec ProfileWithApartmentsOneHourPrice::range(20003000);
  833.                         $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  834.                     }
  835.                 }
  836.             } else {
  837.                 switch ($priceType) {
  838.                     case 'not_expensive':
  839.                         $priceSpec ProfileWithApartmentsOneHourPrice::cheaperThan(2000);
  840.                         break;
  841.                     case 'high':
  842.                         $priceSpec ProfileWithApartmentsOneHourPrice::range(30006000);
  843.                         break;
  844.                     case 'low':
  845.                         $priceSpec ProfileWithApartmentsOneHourPrice::cheaperThan(2000);
  846.                         break;
  847.                     case 'elite':
  848.                         $priceSpec ProfileWithApartmentsOneHourPrice::moreExpensiveThan(6000);
  849.                         break;
  850.                     default:
  851.                         throw new \LogicException('Unknown price type');
  852.                         break;
  853.                 }
  854.                 $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  855.             }
  856.         }
  857.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  858.         return $result;
  859.     }
  860.     #[ParamConverter("city"converter"city_converter")]
  861.     public function listByAge(Request $requestCity $citystring $ageTypeint $minAge nullint $maxAge null): Response
  862.     {
  863.         $specs $this->profileListSpecificationService->listByAge($ageType$minAge$maxAge);
  864.         $response null;
  865.         try {
  866.             if (!in_array($ageType, ['young''old'])) {
  867.                 throw new \LogicException(sprintf('Age type "%s" is not supported'$ageType));
  868.             }
  869.             $result $this->listingRotationApi->paginate('/city/{city}/age/' $ageType, ['city' => $city->getId()], $this->getCurrentPageNumber());
  870.             $response = new Response();
  871.             $response->setMaxAge(10);
  872.         } catch (\Exception) {
  873.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  874.         }
  875.         $prevCount $result->count();
  876.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  877.             $filled $this->processListByAgeEmptyResult($result$city$ageType$minAge$maxAge);
  878.             if ($filled)
  879.                 $result $filled;
  880.         }
  881.         if ($result->count() > $prevCount) {
  882.             $response?->setMaxAge(60);
  883.         }
  884.         return $this->render('ProfileList/list.html.twig', [
  885.             'profiles' => $result,
  886.             'source' => $this->source,
  887.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  888.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  889.                 'city' => $city->getUriIdentity(),
  890.                 'ageType' => $ageType,
  891.                 'minAge' => $minAge,
  892.                 'maxAge' => $maxAge,
  893.                 'page' => $this->getCurrentPageNumber()
  894.             ]),
  895.             'recommendationSpec' => $specs->recommendationSpec(),
  896.         ], response$response);
  897.     }
  898.     private function processListByAgeEmptyResult(Page $resultCity $citystring $ageTypeint $minAge nullint $maxAge null)
  899.     {
  900.         if (!$this->features->fill_empty_profile_list())
  901.             return $result;
  902.         $this->source self::RESULT_SOURCE_BY_PARAMS;
  903.         if ($minAge && !$maxAge) {
  904.             $startMinAge $minAge;
  905.             do {
  906.                 $startMinAge -= 2;
  907.                 $ageSpec ProfileWithAge::olderThan($startMinAge);
  908.                 $result $this->listRandomSinglePage($citynull$ageSpecnulltruefalse);
  909.             } while ($result->count() == && $startMinAge >= 18);
  910.         } else if ($ageType == 'young') {
  911.             $startMaxAge 20;
  912.             do {
  913.                 $startMaxAge += 2;
  914.                 $ageSpec ProfileWithAge::youngerThan($startMaxAge);
  915.                 $result $this->listRandomSinglePage($citynull$ageSpecnulltruefalse);
  916.             } while ($result->count() == && $startMaxAge <= 100);
  917.         }
  918.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  919.         return $result;
  920.     }
  921.     #[ParamConverter("city"converter"city_converter")]
  922.     public function listByHeight(Request $requestCity $citystring $heightType): Response
  923.     {
  924.         $specs $this->profileListSpecificationService->listByHeight($heightType);
  925.         $response null;
  926.         try {
  927.             $result $this->listingRotationApi->paginate('/city/{city}/height/' $heightType, ['city' => $city->getId()], $this->getCurrentPageNumber());
  928.             $response = new Response();
  929.             $response->setMaxAge(10);
  930.         } catch (\Exception) {
  931.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  932.         }
  933.         $prevCount $result->count();
  934.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  935.         if ($result->count() > $prevCount) {
  936.             $response?->setMaxAge(60);
  937.         }
  938.         return $this->render('ProfileList/list.html.twig', [
  939.             'profiles' => $result,
  940.             'source' => $this->source,
  941.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  942.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  943.                 'city' => $city->getUriIdentity(),
  944.                 'heightType' => $heightType,
  945.                 'page' => $this->getCurrentPageNumber()
  946.             ]),
  947.             'recommendationSpec' => $specs->recommendationSpec(),
  948.         ], response$response);
  949.     }
  950.     #[ParamConverter("city"converter"city_converter")]
  951.     public function listByBreastType(Request $requestCity $citystring $breastType): Response
  952.     {
  953.         if (null === $type BreastTypes::getValueByUriIdentity($breastType))
  954.             throw $this->createNotFoundException();
  955.         $specs $this->profileListSpecificationService->listByBreastType($breastType);
  956.         $response null;
  957.         try {
  958.             $result $this->listingRotationApi->paginate('/city/{city}/breasttype/' $type, ['city' => $city->getId()], $this->getCurrentPageNumber());
  959.             $response = new Response();
  960.             $response->setMaxAge(10);
  961.         } catch (\Exception) {
  962.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  963.         }
  964.         $orX $this->getORSpecForItemsArray(BreastTypes::getList(), function ($item): ProfileWithBreastType {
  965.             return new ProfileWithBreastType($item);
  966.         });
  967.         $prevCount $result->count();
  968.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  969.         if ($result->count() > $prevCount) {
  970.             $response?->setMaxAge(60);
  971.         }
  972.         return $this->render('ProfileList/list.html.twig', [
  973.             'profiles' => $result,
  974.             'source' => $this->source,
  975.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  976.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  977.                 'city' => $city->getUriIdentity(),
  978.                 'breastType' => $breastType,
  979.                 'page' => $this->getCurrentPageNumber()
  980.             ]),
  981.             'recommendationSpec' => $specs->recommendationSpec(),
  982.         ], response$response);
  983.     }
  984.     #[ParamConverter("city"converter"city_converter")]
  985.     public function listByHairColor(Request $requestCity $citystring $hairColor): Response
  986.     {
  987.         if (null === $color HairColors::getValueByUriIdentity($hairColor))
  988.             throw $this->createNotFoundException();
  989.         $specs $this->profileListSpecificationService->listByHairColor($hairColor);
  990.         $response null;
  991.         try {
  992.             $result $this->listingRotationApi->paginate('/city/{city}/haircolor/' $color, ['city' => $city->getId()], $this->getCurrentPageNumber());
  993.             $response = new Response();
  994.             $response->setMaxAge(10);
  995.         } catch (\Exception) {
  996.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  997.         }
  998.         $orX $this->getORSpecForItemsArray(HairColors::getList(), function ($item): ProfileWithHairColor {
  999.             return new ProfileWithHairColor($item);
  1000.         });
  1001.         $prevCount $result->count();
  1002.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  1003.         if ($result->count() > $prevCount) {
  1004.             $response?->setMaxAge(60);
  1005.         }
  1006.         return $this->render('ProfileList/list.html.twig', [
  1007.             'profiles' => $result,
  1008.             'source' => $this->source,
  1009.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1010.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1011.                 'city' => $city->getUriIdentity(),
  1012.                 'hairColor' => $hairColor,
  1013.                 'page' => $this->getCurrentPageNumber()
  1014.             ]),
  1015.             'recommendationSpec' => $specs->recommendationSpec(),
  1016.         ], response$response);
  1017.     }
  1018.     #[ParamConverter("city"converter"city_converter")]
  1019.     public function listByBodyType(Request $requestCity $citystring $bodyType): Response
  1020.     {
  1021.         if (null === $type BodyTypes::getValueByUriIdentity($bodyType))
  1022.             throw $this->createNotFoundException();
  1023.         $specs $this->profileListSpecificationService->listByBodyType($bodyType);
  1024.         $response null;
  1025.         try {
  1026.             $result $this->listingRotationApi->paginate('/city/{city}/bodytype/' $type, ['city' => $city->getId()], $this->getCurrentPageNumber());
  1027.             $response = new Response();
  1028.             $response->setMaxAge(10);
  1029.         } catch (\Exception) {
  1030.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1031.         }
  1032.         $orX $this->getORSpecForItemsArray(BodyTypes::getList(), function ($item): ProfileWithBodyType {
  1033.             return new ProfileWithBodyType($item);
  1034.         });
  1035.         $prevCount $result->count();
  1036.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  1037.         if ($result->count() > $prevCount) {
  1038.             $response?->setMaxAge(60);
  1039.         }
  1040.         return $this->render('ProfileList/list.html.twig', [
  1041.             'profiles' => $result,
  1042.             'source' => $this->source,
  1043.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1044.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1045.                 'city' => $city->getUriIdentity(),
  1046.                 'bodyType' => $bodyType,
  1047.                 'page' => $this->getCurrentPageNumber()
  1048.             ]),
  1049.             'recommendationSpec' => $specs->recommendationSpec(),
  1050.         ], response$response);
  1051.     }
  1052.     #[ParamConverter("city"converter"city_converter")]
  1053.     public function listByPlace(Request $requestCity $citystring $placeTypestring $takeOutLocation null): Response
  1054.     {
  1055.         $specs $this->profileListSpecificationService->listByPlace($placeType$takeOutLocation);
  1056.         if (null === $specs)
  1057.             throw $this->createNotFoundException();
  1058.         $response null;
  1059.         try {
  1060.             $endpoint '/city/{city}/place/' $placeType;
  1061.             if (null !== $takeOutLocation) {
  1062.                 $endpoint .= '/' $takeOutLocation;
  1063.             }
  1064.             $result $this->listingRotationApi->paginate($endpoint, ['city' => $city->getId()], $this->getCurrentPageNumber());
  1065.             $response = new Response();
  1066.             $response->setMaxAge(10);
  1067.         } catch (\Exception) {
  1068.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1069.         }
  1070.         $orX $this->getORSpecForItemsArray(TakeOutLocations::getList(), function ($item): ProfileIsProvidingTakeOut {
  1071.             return new ProfileIsProvidingTakeOut($item);
  1072.         });
  1073.         if ($placeType == 'take-out')
  1074.             $orX->orX(new ProfileHasApartments());
  1075.         $prevCount $result->count();
  1076.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  1077.         if ($result->count() > $prevCount) {
  1078.             $response?->setMaxAge(60);
  1079.         }
  1080.         return $this->render('ProfileList/list.html.twig', [
  1081.             'profiles' => $result,
  1082.             'source' => $this->source,
  1083.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1084.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1085.                 'city' => $city->getUriIdentity(),
  1086.                 'placeType' => $placeType,
  1087.                 'takeOutLocation' => TakeOutLocations::getUriIdentity(TakeOutLocations::getValueByUriIdentity($takeOutLocation)),
  1088.                 'page' => $this->getCurrentPageNumber()
  1089.             ]),
  1090.             'recommendationSpec' => $specs->recommendationSpec(),
  1091.         ], response$response);
  1092.     }
  1093.     #[ParamConverter("city"converter"city_converter")]
  1094.     public function listByPrivateHaircut(Request $requestCity $citystring $privateHaircut): Response
  1095.     {
  1096.         if (null === $type PrivateHaircuts::getValueByUriIdentity($privateHaircut))
  1097.             throw $this->createNotFoundException();
  1098.         $specs $this->profileListSpecificationService->listByPrivateHaircut($privateHaircut);
  1099.         $response null;
  1100.         try {
  1101.             $result $this->listingRotationApi->paginate('/city/{city}/privatehaircut/' $type, ['city' => $city->getId()], $this->getCurrentPageNumber());
  1102.             $response = new Response();
  1103.             $response->setMaxAge(10);
  1104.         } catch (\Exception) {
  1105.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1106.         }
  1107.         $orX $this->getORSpecForItemsArray(PrivateHaircuts::getList(), function ($item): ProfileWithPrivateHaircut {
  1108.             return new ProfileWithPrivateHaircut($item);
  1109.         });
  1110.         $prevCount $result->count();
  1111.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  1112.         if ($result->count() > $prevCount) {
  1113.             $response?->setMaxAge(60);
  1114.         }
  1115.         return $this->render('ProfileList/list.html.twig', [
  1116.             'profiles' => $result,
  1117.             'source' => $this->source,
  1118.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1119.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1120.                 'city' => $city->getUriIdentity(),
  1121.                 'privateHaircut' => $privateHaircut,
  1122.                 'page' => $this->getCurrentPageNumber()
  1123.             ]),
  1124.             'recommendationSpec' => $specs->recommendationSpec(),
  1125.         ], response$response);
  1126.     }
  1127.     #[ParamConverter("city"converter"city_converter")]
  1128.     public function listByNationality(Request $requestCity $citystring $nationality): Response
  1129.     {
  1130.         if (null === $type Nationalities::getValueByUriIdentity($nationality))
  1131.             throw $this->createNotFoundException();
  1132.         $specs $this->profileListSpecificationService->listByNationality($nationality);
  1133.         $response null;
  1134.         try {
  1135.             $result $this->listingRotationApi->paginate('/city/{city}/nationality/' $type, ['city' => $city->getId()], $this->getCurrentPageNumber());
  1136.             $response = new Response();
  1137.             $response->setMaxAge(10);
  1138.         } catch (\Exception) {
  1139.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1140.         }
  1141.         $orX $this->getORSpecForItemsArray(Nationalities::getList(), function ($item): ProfileWithNationality {
  1142.             return new ProfileWithNationality($item);
  1143.         });
  1144.         $prevCount $result->count();
  1145.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  1146.         if ($result->count() > $prevCount) {
  1147.             $response?->setMaxAge(60);
  1148.         }
  1149.         return $this->render('ProfileList/list.html.twig', [
  1150.             'profiles' => $result,
  1151.             'source' => $this->source,
  1152.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1153.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1154.                 'city' => $city->getUriIdentity(),
  1155.                 'nationality' => $nationality,
  1156.                 'page' => $this->getCurrentPageNumber()
  1157.             ]),
  1158.             'recommendationSpec' => $specs->recommendationSpec(),
  1159.         ], response$response);
  1160.     }
  1161.     #[ParamConverter("city"converter"city_converter")]
  1162.     #[ParamConverter("service"options: ['mapping' => ['service' => 'uriIdentity']])]
  1163.     public function listByProvidedService(Request $requestCity $cityService $service): Response
  1164.     {
  1165.         $specs $this->profileListSpecificationService->listByProvidedService($service$city);
  1166.         $response null;
  1167.         try {
  1168.             $result $this->listingRotationApi->paginate('/city/{city}/service/{service}', ['city' => $city->getId(), 'service' => $service->getId()], $this->getCurrentPageNumber());
  1169.             $response = new Response();
  1170.             $response->setMaxAge(10);
  1171.         } catch (\Exception) {
  1172.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1173.         }
  1174.         $prevCount $result->count();
  1175.         $sameGroupServices $this->serviceRepository->findBy(['group' => $service->getGroup()]);
  1176.         $orX $this->getORSpecForItemsArray([$sameGroupServices], function ($item): ProfileIsProvidingOneOfServices {
  1177.             return new ProfileIsProvidingOneOfServices($item);
  1178.         });
  1179.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_SERVICE);
  1180.         if ($result->count() > $prevCount) {
  1181.             $response?->setMaxAge(60);
  1182.         }
  1183.         return $this->render('ProfileList/list.html.twig', [
  1184.             'profiles' => $result,
  1185.             'source' => $this->source,
  1186.             'source_default' => self::RESULT_SOURCE_SERVICE,
  1187.             'service' => $service,
  1188.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1189.                 'city' => $city->getUriIdentity(),
  1190.                 'service' => $service->getUriIdentity(),
  1191.                 'page' => $this->getCurrentPageNumber()
  1192.             ]),
  1193.             'recommendationSpec' => $specs->recommendationSpec(),
  1194.         ], response$response);
  1195.     }
  1196.     /**
  1197.      * @Feature("has_archive_page")
  1198.      */
  1199.     #[ParamConverter("city"converter"city_converter")]
  1200.     public function listArchived(Request $requestCity $city): Response
  1201.     {
  1202.         $result $this->profileList->list($citynullnullnullfalsenullProfileList::ORDER_BY_UPDATED);
  1203.         return $this->render('ProfileList/list.html.twig', [
  1204.             'profiles' => $result,
  1205.             'recommendationSpec' => new \App\Specification\ElasticSearch\ProfileIsNotArchived(), //ProfileIsArchived, согласно https://redminez.net/issues/28305 в реках выводятся неарзивные
  1206.         ]);
  1207.     }
  1208.     #[ParamConverter("city"converter"city_converter")]
  1209.     public function listNew(City $cityint $weeks 2): Response
  1210.     {
  1211.         $specs $this->profileListSpecificationService->listNew($weeks);
  1212.         $response null;
  1213.         try {
  1214.             $result $this->listingRotationApi->paginate('/city/{city}/recent', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1215.             $response = new Response();
  1216.             $response->setMaxAge(10);
  1217.         } catch (\Exception) {
  1218.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1219.         }
  1220.         return $this->render('ProfileList/list.html.twig', [
  1221.             'profiles' => $result,
  1222.             'recommendationSpec' => $specs->recommendationSpec(),
  1223.         ], response$response);
  1224.     }
  1225.     #[ParamConverter("city"converter"city_converter")]
  1226.     public function listByNoRetouch(City $city): Response
  1227.     {
  1228.         $specs $this->profileListSpecificationService->listByNoRetouch();
  1229.         $response null;
  1230.         try {
  1231.             $result $this->listingRotationApi->paginate('/city/{city}/noretouch', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1232.             $response = new Response();
  1233.             $response->setMaxAge(10);
  1234.         } catch (\Exception) {
  1235.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1236.         }
  1237.         $prevCount $result->count();
  1238.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1239.         if ($result->count() > $prevCount) {
  1240.             $response?->setMaxAge(60);
  1241.         }
  1242.         return $this->render('ProfileList/list.html.twig', [
  1243.             'profiles' => $result,
  1244.             'source' => $this->source,
  1245.             'recommendationSpec' => $specs->recommendationSpec(),
  1246.         ], response$response);
  1247.     }
  1248.     #[ParamConverter("city"converter"city_converter")]
  1249.     public function listByNice(City $city): Response
  1250.     {
  1251.         $specs $this->profileListSpecificationService->listByNice();
  1252.         $response null;
  1253.         try {
  1254.             $result $this->listingRotationApi->paginate('/city/{city}/nice', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1255.             $response = new Response();
  1256.             $response->setMaxAge(10);
  1257.         } catch (\Exception) {
  1258.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1259.         }
  1260.         $prevCount $result->count();
  1261.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1262.         if ($result->count() > $prevCount) {
  1263.             $response?->setMaxAge(60);
  1264.         }
  1265.         return $this->render('ProfileList/list.html.twig', [
  1266.             'profiles' => $result,
  1267.             'source' => $this->source,
  1268.             'recommendationSpec' => $specs->recommendationSpec(),
  1269.         ], response$response);
  1270.     }
  1271.     #[ParamConverter("city"converter"city_converter")]
  1272.     public function listByOnCall(City $city): Response
  1273.     {
  1274.         $specs $this->profileListSpecificationService->listByOnCall();
  1275.         $response null;
  1276.         try {
  1277.             $result $this->listingRotationApi->paginate('/city/{city}/oncall', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1278.             $response = new Response();
  1279.             $response->setMaxAge(10);
  1280.         } catch (\Exception) {
  1281.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1282.         }
  1283.         $prevCount $result->count();
  1284.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1285.         if ($result->count() > $prevCount) {
  1286.             $response?->setMaxAge(60);
  1287.         }
  1288.         return $this->render('ProfileList/list.html.twig', [
  1289.             'profiles' => $result,
  1290.             'source' => $this->source,
  1291.             'recommendationSpec' => $specs->recommendationSpec(),
  1292.         ], response$response);
  1293.     }
  1294.     #[ParamConverter("city"converter"city_converter")]
  1295.     public function listForHour(City $city): Response
  1296.     {
  1297.         $specs $this->profileListSpecificationService->listForHour();
  1298.         $response null;
  1299.         try {
  1300.             $result $this->listingRotationApi->paginate('/city/{city}/forhour', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1301.             $response = new Response();
  1302.             $response->setMaxAge(10);
  1303.         } catch (\Exception) {
  1304.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1305.         }
  1306.         $prevCount $result->count();
  1307.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1308.         if ($result->count() > $prevCount) {
  1309.             $response?->setMaxAge(60);
  1310.         }
  1311.         return $this->render('ProfileList/list.html.twig', [
  1312.             'profiles' => $result,
  1313.             'source' => $this->source,
  1314.             'recommendationSpec' => $specs->recommendationSpec(),
  1315.         ], response$response);
  1316.     }
  1317.     #[ParamConverter("city"converter"city_converter")]
  1318.     public function listForNight(City $city): Response
  1319.     {
  1320.         $specs $this->profileListSpecificationService->listForNight();
  1321.         $response null;
  1322.         try {
  1323.             $result $this->listingRotationApi->paginate('/city/{city}/fornight', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1324.             $response = new Response();
  1325.             $response->setMaxAge(10);
  1326.         } catch (\Exception) {
  1327.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1328.         }
  1329.         $prevCount $result->count();
  1330.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1331.         // Фильтрация: только анкеты с night_price в апартаментах или выезде
  1332.         $resultFiltered = [];
  1333.         foreach ($result as $profile) {
  1334.             $hasNight false;
  1335.             if (isset($profile->apartmentsPricing) && $profile->apartmentsPricing && !empty($profile->apartmentsPricing->nightPrice)) {
  1336.                 $hasNight true;
  1337.             }
  1338.             if (isset($profile->takeOutPricing) && $profile->takeOutPricing && !empty($profile->takeOutPricing->nightPrice)) {
  1339.                 $hasNight true;
  1340.             }
  1341.             if ($hasNight) {
  1342.                 $resultFiltered[] = $profile;
  1343.             }
  1344.         }
  1345.         // Обеспечиваем тип Page для совместимости с ListingService
  1346.         $filteredCount count($resultFiltered);
  1347.         $limit $filteredCount $filteredCount 1;
  1348.         $result = new \App\Bridge\Porpaginas\Doctrine\ORM\FakeORMQueryPage(01$limit$filteredCount$resultFiltered);
  1349.         if ($result->count() > $prevCount) {
  1350.             $response?->setMaxAge(60);
  1351.         }
  1352.         // Делаем переменную доступной для SEO-шаблона
  1353.         $request $this->container->get('request_stack')->getCurrentRequest();
  1354.         if ($request) {
  1355.             $request->attributes->set('profiles_count'$filteredCount);
  1356.         }
  1357.         return $this->render('ProfileList/list.html.twig', [
  1358.             'profiles' => $result,
  1359.             'source' => $this->source,
  1360.             'recommendationSpec' => $specs->recommendationSpec(),
  1361.         ], response$response);
  1362.     }
  1363.     private function getSpecForEliteGirls(City $city): Filter
  1364.     {
  1365.         $minPrice $this->countryCurrencyResolver->getValueByCountryCode($city->getCountryCode(), [
  1366.             'RUB' => 5000,
  1367.             'UAH' => 1500,
  1368.             'USD' => 100,
  1369.             'EUR' => 130,
  1370.         ]);
  1371.         return new ProfileIsElite($minPrice);
  1372.     }
  1373.     private function getElasticSearchSpecForEliteGirls(City $city): ISpecification
  1374.     {
  1375.         $minPrice $this->countryCurrencyResolver->getValueByCountryCode($city->getCountryCode(), [
  1376.             'RUB' => 5000,
  1377.             'UAH' => 1500,
  1378.             'USD' => 100,
  1379.             'EUR' => 130,
  1380.         ]);
  1381.         return new \App\Specification\ElasticSearch\ProfileIsElite($minPrice);
  1382.     }
  1383.     #[ParamConverter("city"converter"city_converter")]
  1384.     public function listForEliteGirls(CountryCurrencyResolver $countryCurrencyResolverRequest $requestCity $city): Response
  1385.     {
  1386.         $specs $this->profileListSpecificationService->listForEliteGirls($city);
  1387.         $response null;
  1388.         try {
  1389.             $result $this->listingRotationApi->paginate('/city/{city}/elite', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1390.             $response = new Response();
  1391.             $response->setMaxAge(10);
  1392.         } catch (\Exception) {
  1393.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1394.         }
  1395.         $prevCount $result->count();
  1396.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  1397.             $prices = [
  1398.                 'RUB' => 5000,
  1399.                 'UAH' => 1500,
  1400.                 'USD' => 100,
  1401.                 'EUR' => 130,
  1402.             ];
  1403.             $currency $countryCurrencyResolver->getCurrencyFor($city->getCountryCode());
  1404.             if (isset($prices[$currency])) {
  1405.                 $minPrice $prices[$currency];
  1406.                 switch ($currency) {
  1407.                     case 'RUB':
  1408.                         $diff 1000;
  1409.                         break;
  1410.                     case 'UAH':
  1411.                         $diff 500;
  1412.                         break;
  1413.                     case 'USD':
  1414.                     case 'EUR':
  1415.                         $diff 20;
  1416.                         break;
  1417.                     default:
  1418.                         throw new \LogicException('Unexpected currency code');
  1419.                 }
  1420.                 while ($minPrice >= $diff) {
  1421.                     $minPrice -= $diff;
  1422.                     $result $this->listRandomSinglePage($citynullProfileWithApartmentsOneHourPrice::moreExpensiveThan($minPrice), nulltruefalse);
  1423.                     if ($result->count() > 0) {
  1424.                         $this->source self::RESULT_SOURCE_BY_PARAMS;
  1425.                         break;
  1426.                     }
  1427.                 }
  1428.                 $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1429.             }
  1430.         }
  1431.         if ($result->count() > $prevCount) {
  1432.             $response?->setMaxAge(60);
  1433.         }
  1434.         return $this->render('ProfileList/list.html.twig', [
  1435.             'profiles' => $result,
  1436.             'source' => $this->source,
  1437.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1438.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1439.                 'city' => $city->getUriIdentity(),
  1440.                 'page' => $this->getCurrentPageNumber()
  1441.             ]),
  1442.             'recommendationSpec' => $specs->recommendationSpec(),
  1443.         ], response$response);
  1444.     }
  1445.     #[ParamConverter("city"converter"city_converter")]
  1446.     public function listForRealElite(CountryCurrencyResolver $countryCurrencyResolverCity $city): Response
  1447.     {
  1448.         $specs $this->profileListSpecificationService->listForRealElite($city);
  1449.         $response null;
  1450.         try {
  1451.             $result $this->listingRotationApi->paginate('/city/{city}/realelite', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1452.             $response = new Response();
  1453.             $response->setMaxAge(10);
  1454.         } catch (\Exception) {
  1455.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1456.         }
  1457.         $prevCount $result->count();
  1458.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1459.         if ($result->count() > $prevCount) {
  1460.             $response?->setMaxAge(60);
  1461.         }
  1462.         return $this->render('ProfileList/list.html.twig', [
  1463.             'profiles' => $result,
  1464.             'source' => $this->source,
  1465.             'recommendationSpec' => $specs->recommendationSpec(),
  1466.         ], response$response);
  1467.     }
  1468.     #[ParamConverter("city"converter"city_converter")]
  1469.     public function listForVipPros(CountryCurrencyResolver $countryCurrencyResolverCity $city): Response
  1470.     {
  1471.         $specs $this->profileListSpecificationService->listForVipPros($city);
  1472.         $response null;
  1473.         try {
  1474.             $result $this->listingRotationApi->paginate('/city/{city}/vip', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1475.             $response = new Response();
  1476.             $response->setMaxAge(10);
  1477.         } catch (\Exception) {
  1478.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1479.         }
  1480.         $prevCount $result->count();
  1481.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1482.         if ($result->count() > $prevCount) {
  1483.             $response?->setMaxAge(60);
  1484.         }
  1485.         return $this->render('ProfileList/list.html.twig', [
  1486.             'profiles' => $result,
  1487.             'source' => $this->source,
  1488.             'recommendationSpec' => $specs->recommendationSpec(),
  1489.         ], response$response);
  1490.     }
  1491.     #[ParamConverter("city"converter"city_converter")]
  1492.     public function listForVipIndividual(CountryCurrencyResolver $countryCurrencyResolverCity $city): Response
  1493.     {
  1494.         $specs $this->profileListSpecificationService->listForVipIndividual($city);
  1495.         $response null;
  1496.         try {
  1497.             $result $this->listingRotationApi->paginate('/city/{city}/vipindi', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1498.             $response = new Response();
  1499.             $response->setMaxAge(10);
  1500.         } catch (\Exception) {
  1501.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1502.         }
  1503.         $prevCount $result->count();
  1504.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1505.         if ($result->count() > $prevCount) {
  1506.             $response?->setMaxAge(60);
  1507.         }
  1508.         return $this->render('ProfileList/list.html.twig', [
  1509.             'profiles' => $result,
  1510.             'source' => $this->source,
  1511.             'recommendationSpec' => $specs->recommendationSpec(),
  1512.         ], response$response);
  1513.     }
  1514.     #[ParamConverter("city"converter"city_converter")]
  1515.     public function listForVipGirlsCity(City $city): Response
  1516.     {
  1517.         $specs $this->profileListSpecificationService->listForVipGirlsCity($city);
  1518.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1519.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1520.         return $this->render('ProfileList/list.html.twig', [
  1521.             'profiles' => $result,
  1522.             'source' => $this->source,
  1523.             'recommendationSpec' => $specs->recommendationSpec(),
  1524.         ]);
  1525.     }
  1526.     #[ParamConverter("city"converter"city_converter")]
  1527.     public function listOfGirlfriends(City $city): Response
  1528.     {
  1529.         $specs $this->profileListSpecificationService->listOfGirlfriends();
  1530.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1531.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1532.         return $this->render('ProfileList/list.html.twig', [
  1533.             'profiles' => $result,
  1534.             'source' => $this->source,
  1535.             'recommendationSpec' => $specs->recommendationSpec(),
  1536.         ]);
  1537.     }
  1538.     #[ParamConverter("city"converter"city_converter")]
  1539.     public function listOfMostExpensive(City $city): Response
  1540.     {
  1541.         $specs $this->profileListSpecificationService->listOfMostExpensive($city);
  1542.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1543.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1544.         return $this->render('ProfileList/list.html.twig', [
  1545.             'profiles' => $result,
  1546.             'source' => $this->source,
  1547.             'recommendationSpec' => $specs->recommendationSpec(),
  1548.         ]);
  1549.     }
  1550.     #[ParamConverter("city"converter"city_converter")]
  1551.     public function listBdsm(City $cityServiceRepository $serviceRepositoryParameterBagInterface $parameterBag): Response
  1552.     {
  1553.         $specs $this->profileListSpecificationService->listBdsm();
  1554.         $response null;
  1555.         try {
  1556.             $result $this->listingRotationApi->paginate('/city/{city}/bdsm', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1557.             $response = new Response();
  1558.             $response->setMaxAge(10);
  1559.         } catch (\Exception) {
  1560.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec(), $specs->additionalSpecs());
  1561.         }
  1562.         $bdsmIds $serviceRepository->findBy(['group' => ServiceGroups::BDSM]);
  1563.         return $this->render('ProfileList/list.html.twig', [
  1564.             'profiles' => $result,
  1565.             'recommendationSpec' => $specs->recommendationSpec(),
  1566.         ], response$response);
  1567.     }
  1568.     #[ParamConverter("city"converter"city_converter")]
  1569.     public function listByGender(City $citystring $genderDefaultCityProvider $defaultCityProvider): Response
  1570.     {
  1571.         if ($city->getId() != $defaultCityProvider->getDefaultCity()->getId()) {
  1572.             throw $this->createNotFoundException();
  1573.         }
  1574.         if (null === Genders::getValueByUriIdentity($gender))
  1575.             throw $this->createNotFoundException();
  1576.         $specs $this->profileListSpecificationService->listByGender($gender);
  1577.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec(), $specs->additionalSpecs(), $specs->genders());
  1578.         return $this->render('ProfileList/list.html.twig', [
  1579.             'profiles' => $result,
  1580.             'recommendationSpec' => $specs->recommendationSpec(),
  1581.         ]);
  1582.     }
  1583.     protected function checkCityAndCountrySource(Page $resultCity $city): Page
  1584.     {
  1585.         if (($result && $result->count() != 0) || false == $this->features->fill_empty_profile_list())
  1586.             return $result;
  1587.         $this->source self::RESULT_SOURCE_CITY;
  1588.         $result $this->listRandomSinglePage($citynullnullnulltruefalse);
  1589.         if ($result->count() == 0) {
  1590.             $this->source self::RESULT_SOURCE_COUNTRY;
  1591.             $result $this->listRandomSinglePage($city$city->getCountryCode(), nullnulltruefalse);
  1592.         }
  1593.         return $result;
  1594.     }
  1595.     protected function checkEmptyResultNotMasseur(Page $resultCity $city, ?OrX $alternativeSpecstring $source): Page
  1596.     {
  1597.         if ($result->count() != || false == $this->features->fill_empty_profile_list())
  1598.             return $result;
  1599.         if (null != $alternativeSpec) {
  1600.             $this->source $source;
  1601.             $result $this->listRandomSinglePage($citynull$alternativeSpecnulltruefalse);
  1602.         }
  1603.         if ($result->count() == 0)
  1604.             $result $this->checkCityAndCountrySource($result$city);
  1605.         return $result;
  1606.     }
  1607.     /**
  1608.      * Сейчас не используется, решили доставать их всех соседних подкатегорий разом.
  1609.      * Пока оставил, вдруг передумают.
  1610.      * @deprecated
  1611.      */
  1612.     public function listByNextSimilarCategories(callable $listMethod$requestCategory, array $similarItems): ORMQueryResult
  1613.     {
  1614.         $similarItems array_filter($similarItems, function ($item) use ($requestCategory): bool {
  1615.             return $item != $requestCategory;
  1616.         });
  1617.         //shuffle($similarItems);
  1618.         $item null;
  1619.         $result null;
  1620.         do {
  1621.             $item $item == null current($similarItems) : next($similarItems);
  1622.             if (false === $item)
  1623.                 return $result;
  1624.             $result $listMethod($item);
  1625.         } while ($result->count() == 0);
  1626.         return $result;
  1627.     }
  1628.     protected function getCurrentPageNumber(): int
  1629.     {
  1630.         $page = (int) $this->requestStack->getCurrentRequest()?->get($this->pageParameter1);
  1631.         if ($page 1) {
  1632.             $page 1;
  1633.         }
  1634.         return $page;
  1635.     }
  1636.     protected function render(string $view, array $parameters = [], Response $response null): Response
  1637.     {
  1638.         $this->listingService->setCurrentListingPage($parameters['profiles']);
  1639.         $requestAttrs $this->requestStack->getCurrentRequest();
  1640.         $listing $requestAttrs->get('_controller');
  1641.         $listing is_array($listing) ? $listing[count($listing) - 1] : $listing;
  1642.         $listing preg_replace('/[^:]+::/'''$listing);
  1643.         $listingParameters $requestAttrs->get('_route_params');
  1644.         $listingParameters is_array($listingParameters) ? $listingParameters : [];
  1645.         $mainRequestHasPageParam = isset(($this->requestStack->getMainRequest()->get('_route_params') ?? [])['page']);
  1646.         if ($this->requestStack->getCurrentRequest()->isXmlHttpRequest()) {
  1647.             $view = (
  1648.                 str_starts_with($listing'list')
  1649.                 && 'ProfileList/list.html.twig' === $view
  1650.                 && $mainRequestHasPageParam //isset($listingParameters['page'])
  1651.             )
  1652.                 ? 'ProfileList/list.profiles.html.twig'
  1653.                 $view;
  1654.             return $this->prepareForXhr(parent::render($view$parameters$response));
  1655.             //return $this->getJSONResponse($parameters);
  1656.         } else {
  1657.             $parameters array_merge($parameters, [
  1658.                 'listing' => $listing,
  1659.                 'listing_parameters' => $listingParameters,
  1660.             ]);
  1661.             return parent::render($view$parameters$response);
  1662.         }
  1663.     }
  1664.     private function listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement(
  1665.         City $city,
  1666.         ?Filter $spec,
  1667.         array $additionalSpecs null,
  1668.         array $genders = [Genders::FEMALE]
  1669.     ): array|Page {
  1670.         return $this->profileList->listActiveWithinCityOrderedByStatusWithSpec($city$spec$additionalSpecs$genders$this->getCurrentPageNumber() < 2);
  1671.     }
  1672.     private function listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited(
  1673.         City $city,
  1674.         ?Filter $spec,
  1675.         array $additionalSpecs null,
  1676.         array $genders = [Genders::FEMALE],
  1677.         int $limit 0,
  1678.     ): array|Page {
  1679.         return $this->profileList->listActiveWithinCityOrderedByStatusWithSpecLimited($city$spec$additionalSpecs$genderstrue$limit);
  1680.     }
  1681.     private function listRandomSinglePage(
  1682.         City $city,
  1683.         ?string $country,
  1684.         ?Filter $spec,
  1685.         ?array $additionalSpecs,
  1686.         bool $active,
  1687.         ?bool $masseur false,
  1688.         array $genders = [Genders::FEMALE]
  1689.     ): Page {
  1690.         return $this->profileList->listRandom($city$country$spec$additionalSpecs$active$masseur$genderstrue);
  1691.     }
  1692.     //    protected function getJSONResponse(array $parameters)
  1693.     //    {
  1694.     //        $request = $this->request;
  1695.     //        $data = json_decode($request->getContent(), true);
  1696.     //
  1697.     //        $imageSize = !empty($data['imageSize']) ? $data['imageSize'] : "357x500";
  1698.     //
  1699.     //        /** @var FakeORMQueryPage $queryPage */
  1700.     //        $queryPage = $parameters['profiles'];
  1701.     //
  1702.     //        $profiles = array_map(function(ProfileListingReadModel $profile) use ($imageSize) {
  1703.     //            $profile->stations = array_values($profile->stations);
  1704.     //            $profile->avatar['path'] = $this->responsiveAssetsService->getResponsiveImageUrl($profile->avatar['path'], 'profile_media', $imageSize, 'jpg');
  1705.     //            $profile->uri = $this->generateUrl('profile_preview.page', ['city' => $profile->city->uriIdentity, 'profile' => $profile->uriIdentity]);
  1706.     //            return $profile;
  1707.     //        }, $queryPage->getArray());
  1708.     //
  1709.     //        return new JsonResponse([
  1710.     //            'profiles' => $profiles,
  1711.     //            'currentPage' => $queryPage->getCurrentPage(),
  1712.     //        ], Response::HTTP_OK);
  1713.     //    }
  1714. }