src/Controller/ProfileListController.php line 1197

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