src/Controller/ProfileListController.php line 478

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