src/Controller/ProfileListController.php line 1073

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