src/Controller/ProfileListController.php line 402

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