src/Controller/ProfileListController.php line 263

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-03-19
  5.  * Time: 22:28
  6.  */
  7. namespace App\Controller;
  8. use App\Bridge\Porpaginas\Doctrine\ORM\FakeORMQueryPage;
  9. use App\Entity\Location\City;
  10. use App\Entity\Location\County;
  11. use App\Entity\Location\District;
  12. use App\Entity\Location\Station;
  13. use App\Entity\Profile\BodyTypes;
  14. use App\Entity\Profile\BreastTypes;
  15. use App\Entity\Profile\Genders;
  16. use App\Entity\Profile\HairColors;
  17. use App\Entity\Profile\Nationalities;
  18. use App\Entity\Profile\PrivateHaircuts;
  19. use App\Entity\Service;
  20. use App\Entity\ServiceGroups;
  21. use App\Entity\TakeOutLocations;
  22. use App\Repository\ServiceRepository;
  23. use App\Repository\StationRepository;
  24. use App\Service\CountryCurrencyResolver;
  25. use App\Service\DefaultCityProvider;
  26. use App\Service\Features;
  27. use App\Service\ListingRotationApi;
  28. use App\Service\ListingService;
  29. use App\Service\ProfileList;
  30. use App\Service\ProfileListingDataCreator;
  31. use App\Service\ProfileListSpecificationService;
  32. use App\Service\ProfileFilterService;
  33. use App\Specification\ElasticSearch\ISpecification;
  34. use App\Specification\Profile\ProfileHasApartments;
  35. use App\Specification\Profile\ProfileHasComments;
  36. use App\Specification\Profile\ProfileHasVideo;
  37. use App\Specification\Profile\ProfileIdIn;
  38. use App\Specification\Profile\ProfileIdINOrderedByINValues;
  39. use App\Specification\Profile\ProfileIdNotIn;
  40. use App\Specification\Profile\ProfileIsApproved;
  41. use App\Specification\Profile\ProfileIsElite;
  42. use App\Specification\Profile\ProfileIsLocated;
  43. use App\Specification\Profile\ProfileIsProvidingOneOfServices;
  44. use App\Specification\Profile\ProfileIsProvidingTakeOut;
  45. use App\Specification\Profile\ProfileWithAge;
  46. use App\Specification\Profile\ProfileWithBodyType;
  47. use App\Specification\Profile\ProfileWithBreastType;
  48. use App\Specification\Profile\ProfileWithHairColor;
  49. use App\Specification\Profile\ProfileWithNationality;
  50. use App\Specification\Profile\ProfileWithApartmentsOneHourPrice;
  51. use App\Specification\Profile\ProfileWithPrivateHaircut;
  52. use Flagception\Bundle\FlagceptionBundle\Annotations\Feature;
  53. use Happyr\DoctrineSpecification\Filter\Filter;
  54. use Happyr\DoctrineSpecification\Logic\OrX;
  55. use Porpaginas\Doctrine\ORM\ORMQueryResult;
  56. use Porpaginas\Page;
  57. use Psr\Cache\CacheItemPoolInterface;
  58. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
  59. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
  60. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  61. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  62. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  63. use Symfony\Component\HttpFoundation\Request;
  64. use Happyr\DoctrineSpecification\Spec;
  65. use Symfony\Component\HttpFoundation\RequestStack;
  66. use Symfony\Component\HttpFoundation\Response;
  67. #[Cache(maxage60, public: true)]
  68. class ProfileListController extends AbstractController
  69. {
  70.     use ExtendedPaginationTrait;
  71.     use SpecTrait;
  72.     use ProfileMinPriceTrait;
  73.     use ResponseTrait;
  74.     const ENTRIES_ON_PAGE 36;
  75.     const RESULT_SOURCE_COUNTY 'county';
  76.     const RESULT_SOURCE_DISTRICT 'district';
  77.     const RESULT_SOURCE_STATION 'station';
  78.     const RESULT_SOURCE_APPROVED 'approved';
  79.     const RESULT_SOURCE_WITH_COMMENTS 'with_comments';
  80.     const RESULT_SOURCE_WITH_VIDEO 'with_video';
  81.     const RESULT_SOURCE_WITH_SELFIE 'with_selfie';
  82.     const RESULT_SOURCE_ELITE 'elite';
  83.     const RESULT_SOURCE_MASSEURS 'masseurs';
  84.     const RESULT_SOURCE_MASSAGE_SERVICE 'massage_service';
  85.     const RESULT_SOURCE_BY_PARAMS 'by_params';
  86.     const RESULT_SOURCE_SERVICE 'service';
  87.     const RESULT_SOURCE_CITY 'city';
  88.     const RESULT_SOURCE_COUNTRY 'country';
  89.     const CACHE_ITEM_STATION_ADDED_PROFILES 'station_added_profiles_ids_';
  90.     private ?string $source null;
  91.     public function __construct(
  92.         private RequestStack $requestStack,
  93.         private ProfileList $profileList,
  94.         private CountryCurrencyResolver $countryCurrencyResolver,
  95.         private ServiceRepository $serviceRepository,
  96.         private ListingService $listingService,
  97.         private Features $features,
  98.         private ProfileFilterService $profilesFilterService,
  99.         private ProfileListSpecificationService $profileListSpecificationService,
  100.         private ProfileListingDataCreator $profileListingDataCreator,
  101.         private CacheItemPoolInterface $stationAddedProfilesCache,
  102.         private ParameterBagInterface $parameterBag,
  103.         private ListingRotationApi $listingRotationApi,
  104.     ) {}
  105.     /**
  106.      * @Feature("has_masseurs")
  107.      */
  108.     #[ParamConverter("city"converter"city_converter")]
  109.     public function listForMasseur(City $cityServiceRepository $serviceRepository): Response
  110.     {
  111.         $specs $this->profileListSpecificationService->listForMasseur($city);
  112.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  113.         $massageGroupServices $serviceRepository->findBy(['group' => ServiceGroups::MASSAGE]);
  114.         $orX $this->getORSpecForItemsArray([$massageGroupServices], function($item): ProfileIsProvidingOneOfServices {
  115.             return new ProfileIsProvidingOneOfServices($item);
  116.         });
  117.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_MASSAGE_SERVICE);
  118.         return $this->render('ProfileList/list.html.twig', [
  119.             'profiles' => $result,
  120.             'source' => $this->source,
  121.             'source_default' => self::RESULT_SOURCE_MASSEURS,
  122.             'recommendationSpec' => $specs->recommendationSpec(),
  123.         ]);
  124.     }
  125.     public function listByDefaultCity(ParameterBagInterface $parameterBagRequest $request): Response
  126.     {
  127.         $controller get_class($this).'::listByCity';
  128.         $path = [
  129.             'city' => $parameterBag->get('default_city'),
  130.             'subRequest' => true,
  131.         ];
  132.         //чтобы в обработчике можно было понять, по какому роуту зашли
  133.         $request->request->set('_route''profile_list.list_by_city');
  134.         return $this->forward($controller$path);
  135.     }
  136.     #[ParamConverter("city"converter"city_converter")]
  137.     public function listByCity(ParameterBagInterface $parameterBagRequest $requestCity $citybool $subRequest false): Response
  138.     {
  139.         $page $this->getCurrentPageNumber();
  140.         if ($this->features->redirect_default_city_to_homepage() && false === $subRequest && $city->equals($parameterBag->get('default_city')) && $page 2) {
  141.             return $this->redirectToRoute('homepage', [], 301);
  142.         }
  143.         $specs $this->profileListSpecificationService->listByCity();
  144.         $response null;
  145.         try {
  146.             $result $this->listingRotationApi->paginate(['city' => $city->getId()], $page);
  147.             $response = new Response();
  148.             $response->setMaxAge(10);
  149.         } catch (\Exception) {
  150.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  151.         }
  152.         return $this->render('ProfileList/list.html.twig', [
  153.             'profiles' => $result,
  154.             'recommendationSpec' => $specs->recommendationSpec(),
  155.         ], response$response);
  156.     }
  157.     #[ParamConverter("city"converter"city_converter")]
  158.     #[Entity("county"expr:"repository.ofUriIdentityWithinCity(county, city)")]
  159.     public function listByCounty(Request $requestCity $cityCounty $county): Response
  160.     {
  161.         if (!$city->hasCounty($county)) {
  162.             throw $this->createNotFoundException();
  163.         }
  164.         $specs $this->profileListSpecificationService->listByCounty($county);
  165.         $response null;
  166.         try {
  167.             $result $this->listingRotationApi->paginate(['city' => $city->getId(), 'county' => $county->getId()], $this->getCurrentPageNumber());
  168.             $response = new Response();
  169.             $response->setMaxAge(10);
  170.         } catch (\Exception) {
  171.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  172.         }
  173.         $prevCount $result->count();
  174.         $result $this->checkEmptyResultNotMasseur($result$citySpec::orX(ProfileIsLocated::withinCounties($city$city->getCounties()->toArray())), self::RESULT_SOURCE_COUNTY);
  175.         if ($result->count() > $prevCount) {
  176.             $response?->setMaxAge(60);
  177.         }
  178.         return $this->render('ProfileList/list.html.twig', [
  179.             'profiles' => $result,
  180.             'source' => $this->source,
  181.             'source_default' => self::RESULT_SOURCE_COUNTY,
  182.             'county' => $county,
  183.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  184.                 'city' => $city->getUriIdentity(),
  185.                 'county' => $county->getUriIdentity(),
  186.                 'page' => $this->getCurrentPageNumber()
  187.             ]),
  188.             'recommendationSpec' => $specs->recommendationSpec(),
  189.         ], response$response);
  190.     }
  191.     #[ParamConverter("city"converter"city_converter")]
  192.     #[Entity("district"expr:"repository.ofUriIdentityWithinCity(district, city)")]
  193.     public function listByDistrict(Request $requestCity $cityDistrict $district): Response
  194.     {
  195.         if (!$city->hasDistrict($district)) {
  196.             throw $this->createNotFoundException();
  197.         }
  198.         $specs $this->profileListSpecificationService->listByDistrict($district);
  199.         $response null;
  200.         try {
  201.             $result $this->listingRotationApi->paginate(['city' => $city->getId(), 'district' => $district->getId()], $this->getCurrentPageNumber());
  202.             $response = new Response();
  203.             $response->setMaxAge(10);
  204.         } catch (\Exception) {
  205.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  206.         }
  207.         $prevCount $result->count();
  208.         $result $this->checkEmptyResultNotMasseur($result$citySpec::orX(ProfileIsLocated::withinDistricts($city$city->getDistricts()->toArray())), self::RESULT_SOURCE_DISTRICT);
  209.         if ($result->count() > $prevCount) {
  210.             $response?->setMaxAge(60);
  211.         }
  212.         return $this->render('ProfileList/list.html.twig', [
  213.             'profiles' => $result,
  214.             'source' => $this->source,
  215.             'source_default' => self::RESULT_SOURCE_DISTRICT,
  216.             'district' => $district,
  217.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  218.                 'city' => $city->getUriIdentity(),
  219.                 'district' => $district->getUriIdentity(),
  220.                 'page' => $this->getCurrentPageNumber()
  221.             ]),
  222.             'recommendationSpec' => $specs->recommendationSpec(),
  223.         ], response$response);
  224.     }
  225.     #[ParamConverter("city"converter"city_converter")]
  226.     #[Entity("station"expr:"repository.ofUriIdentityWithinCity(station, city)")]
  227.     public function listByStation(Request $requestCity $cityStation $station): Response
  228.     {
  229.         if (!$city->hasStation($station)) {
  230.             throw $this->createNotFoundException();
  231.         }
  232.         $specs $this->profileListSpecificationService->listByStation($station);
  233.         $response null;
  234.         try {
  235.             $result $this->listingRotationApi->paginate(['city' => $city->getId(), 'station' => $station->getId()], $this->getCurrentPageNumber());
  236.             $response = new Response();
  237.             $response->setMaxAge(10);
  238.         } catch (\Exception) {
  239.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  240.         }
  241.         $prevCount $result->count();
  242.         if(true === $this->features->station_page_add_profiles()) {
  243.             $spread $this->parameterBag->get('app.profile.station_page.added_profiles.spread');
  244.             $result $this->addSinglePageStationResults($result$city$station$spread ?: 5);
  245.         }
  246.         if (null !== $station->getDistrict()) {
  247.             $result $this->checkEmptyResultNotMasseur($result$citySpec::orX(ProfileIsLocated::nearStations($city$station->getDistrict()->getStations()->toArray())), self::RESULT_SOURCE_STATION);
  248.         } else {
  249.             $result $this->checkCityAndCountrySource($result$city);
  250.         }
  251.         if ($result->count() > $prevCount) {
  252.             $response?->setMaxAge(60);
  253.         }
  254.         return $this->render('ProfileList/list.html.twig', [
  255.             'profiles' => $result,
  256.             'source' => $this->source,
  257.             'source_default' => self::RESULT_SOURCE_STATION,
  258.             'station' => $station,
  259.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  260.                 'city' => $city->getUriIdentity(),
  261.                 'station' => $station->getUriIdentity(),
  262.                 'page' => $this->getCurrentPageNumber()
  263.             ]),
  264.             'recommendationSpec' => $specs->recommendationSpec(),
  265.         ], response$response);
  266.     }
  267.     private function addSinglePageStationResults(Page $resultCity $cityStation $stationint $spread): Page
  268.     {
  269.         if($result->totalCount() >= $result->getCurrentLimit()) {
  270.             return $result;
  271.         }
  272.         $addedProfileIds $this->stationAddedProfilesCache->get(self::CACHE_ITEM_STATION_ADDED_PROFILES $station->getId(), function() use ($result$city$station$spread): array {
  273.             $currentSpread rand(0$spread);
  274.             $plannedTotalCount $result->getCurrentLimit() - $spread $currentSpread;
  275.             $result iterator_to_array($result->getIterator());
  276.             $originalProfileIds array_map(fn($item) => $item->id$result);
  277.             if($station->getDistrict()) {
  278.                 $result $this->addSinglePageResultsUptoAmount($result$citySpec::orX(ProfileIsLocated::withinDistrict($station->getDistrict())), $plannedTotalCount);
  279.             }
  280.             if($station->getDistrict()?->getCounty()) {
  281.                 $result $this->addSinglePageResultsUptoAmount($result$citySpec::orX(ProfileIsLocated::withinCounty($station->getDistrict()->getCounty())), $plannedTotalCount);
  282.             }
  283.             $result $this->addSinglePageResultsUptoAmount($result$citySpec::orX(ProfileIsLocated::withinCity($city)), $plannedTotalCount);
  284.             $result array_map(fn($item) => $item->id$result);
  285.             return array_diff($result$originalProfileIds);
  286.         });
  287.         $addedProfileIds array_slice($addedProfileIds0$result->getCurrentLimit() - $result->totalCount());
  288.         $originalProfiles iterator_to_array($result->getIterator());
  289.         $addedProfiles $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited($city, new ProfileIdIn($addedProfileIds), null, [Genders::FEMALE], count($addedProfileIds));
  290.         $newResult array_merge($originalProfiles$addedProfiles);
  291.         return new FakeORMQueryPage(01$result->getCurrentLimit(), count($newResult), $newResult);
  292.     }
  293.     private function addSinglePageResultsUptoAmount(array $resultCity $city, ?Filter $specsint $totalCount): array
  294.     {
  295.         $toAdd $totalCount count($result);
  296.         $currentResultIds array_map(fn($profile) => $profile->id$result);
  297.         $resultsToAdd $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited($city$specs, [new ProfileIdNotIn($currentResultIds)], [Genders::FEMALE], $toAdd);
  298.         $result array_merge($result$resultsToAdd);
  299.         return $result;
  300.     }
  301.     #[ParamConverter("city"converter"city_converter")]
  302.     public function listByStations(City $citystring $stationsStationRepository $stationRepository): Response
  303.     {
  304.         $stationIds explode(','$stations);
  305.         $stations $stationRepository->findBy(['uriIdentity' => $stationIds]);
  306.         $specs $this->profileListSpecificationService->listByStations($stations);
  307.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  308.         return $this->render('ProfileList/list.html.twig', [
  309.             'profiles' => $result,
  310.             'recommendationSpec' => $specs->recommendationSpec(),
  311.         ]);
  312.     }
  313.     #[ParamConverter("city"converter"city_converter")]
  314.     public function listApproved(Request $requestCity $city): Response
  315.     {
  316.         $specs $this->profileListSpecificationService->listApproved();
  317.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  318.         if($this->features->fill_empty_profile_list() && $result->count() == 0) {
  319.             $this->source self::RESULT_SOURCE_WITH_COMMENTS;
  320.             $result $this->listRandomSinglePage($citynull, new ProfileHasComments(), nulltruefalse);
  321.             if($result->count() == 0) {
  322.                 $this->source self::RESULT_SOURCE_WITH_VIDEO;
  323.                 $result $this->listRandomSinglePage($citynull, new ProfileHasVideo(), nulltruefalse);
  324.             }
  325.             if($result->count() == 0) {
  326.                 $this->source self::RESULT_SOURCE_ELITE;
  327.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  328.             }
  329.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  330.         }
  331.         return $this->render('ProfileList/list.html.twig', [
  332.             'profiles' => $result,
  333.             'source' => $this->source,
  334.             'source_default' => self::RESULT_SOURCE_APPROVED,
  335.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  336.                 'city' => $city->getUriIdentity(),
  337.                 'page' => $this->getCurrentPageNumber()
  338.             ]),
  339.             'recommendationSpec' => $specs->recommendationSpec(),
  340.         ]);
  341.     }
  342.     #[ParamConverter("city"converter"city_converter")]
  343.     public function listWithComments(Request $requestCity $city): Response
  344.     {
  345.         $specs $this->profileListSpecificationService->listWithComments();
  346.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  347.         if($this->features->fill_empty_profile_list() && $result->count() == 0) {
  348.             $this->source self::RESULT_SOURCE_APPROVED;
  349.             $result $this->listRandomSinglePage($citynull, new ProfileIsApproved(), nulltruefalse);
  350.             if ($result->count() == 0) {
  351.                 $this->source self::RESULT_SOURCE_WITH_VIDEO;
  352.                 $result $this->listRandomSinglePage($citynull, new ProfileHasVideo(), nulltruefalse);
  353.             }
  354.             if ($result->count() == 0) {
  355.                 $this->source self::RESULT_SOURCE_ELITE;
  356.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  357.             }
  358.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  359.         }
  360.         return $this->render('ProfileList/list.html.twig', [
  361.             'profiles' => $result,
  362.             'source' => $this->source,
  363.             'source_default' => self::RESULT_SOURCE_WITH_COMMENTS,
  364.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  365.                 'city' => $city->getUriIdentity(),
  366.                 'page' => $this->getCurrentPageNumber()
  367.             ]),
  368.             'recommendationSpec' => $specs->recommendationSpec(),
  369.         ]);
  370.     }
  371.     #[ParamConverter("city"converter"city_converter")]
  372.     public function listWithVideo(Request $requestCity $city): Response
  373.     {
  374.         $specs $this->profileListSpecificationService->listWithVideo();
  375.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  376.         if($this->features->fill_empty_profile_list() && $result->count() == 0) {
  377.             $this->source self::RESULT_SOURCE_APPROVED;
  378.             $result $this->listRandomSinglePage($citynull, new ProfileIsApproved(), nulltruefalse);
  379.             if($result->count() == 0) {
  380.                 $this->source self::RESULT_SOURCE_WITH_COMMENTS;
  381.                 $result $this->listRandomSinglePage($citynull, new ProfileHasComments(), nulltruefalse);
  382.             }
  383.             if($result->count() == 0) {
  384.                 $this->source self::RESULT_SOURCE_ELITE;
  385.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  386.             }
  387.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  388.         }
  389.         return $this->render('ProfileList/list.html.twig', [
  390.             'profiles' => $result,
  391.             'source' => $this->source,
  392.             'source_default' => self::RESULT_SOURCE_WITH_VIDEO,
  393.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  394.                 'city' => $city->getUriIdentity(),
  395.                 'page' => $this->getCurrentPageNumber()
  396.             ]),
  397.             'recommendationSpec' => $specs->recommendationSpec(),
  398.         ]);
  399.     }
  400.     #[ParamConverter("city"converter"city_converter")]
  401.     public function listWithSelfie(Request $requestCity $city): Response
  402.     {
  403.         $specs $this->profileListSpecificationService->listWithSelfie();
  404.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  405.         if($this->features->fill_empty_profile_list() && $result->count() == 0) {
  406.             $this->source self::RESULT_SOURCE_WITH_VIDEO;
  407.             $result $this->listRandomSinglePage($citynull, new ProfileHasVideo(), nulltruefalse);
  408.             if ($result->count() == 0) {
  409.                 $this->source self::RESULT_SOURCE_APPROVED;
  410.                 $result $this->listRandomSinglePage($citynull, new ProfileIsApproved(), nulltruefalse);
  411.             }
  412.             if ($result->count() == 0) {
  413.                 $this->source self::RESULT_SOURCE_ELITE;
  414.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  415.             }
  416.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  417.         }
  418.         return $this->render('ProfileList/list.html.twig', [
  419.             'profiles' => $result,
  420.             'source' => $this->source,
  421.             'source_default' => self::RESULT_SOURCE_WITH_SELFIE,
  422.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  423.                 'city' => $city->getUriIdentity(),
  424.                 'page' => $this->getCurrentPageNumber()
  425.             ]),
  426.             'recommendationSpec' => $specs->recommendationSpec(),
  427.         ]);
  428.     }
  429.     #[ParamConverter("city"converter"city_converter")]
  430.     public function listByPrice(Request $requestCountryCurrencyResolver $countryCurrencyResolverCity $citystring $priceTypeint $minPrice nullint $maxPrice null): Response
  431.     {
  432.         $specs $this->profileListSpecificationService->listByPrice($city$priceType$minPrice$maxPrice);
  433.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  434.         if($this->features->fill_empty_profile_list() && $result->count() == 0) {
  435.             $result $this->processListByPriceEmptyResult($result$city$priceType$minPrice$maxPrice);
  436.         }
  437.         return $this->render('ProfileList/list.html.twig', [
  438.             'profiles' => $result,
  439.             'source' => $this->source,
  440.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  441.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  442.                 'city' => $city->getUriIdentity(),
  443.                 'priceType' => $priceType,
  444.                 'minPrice' => $minPrice,
  445.                 'maxPrice' => $maxPrice,
  446.                 'page' => $this->getCurrentPageNumber()
  447.             ]),
  448.             'recommendationSpec' => $specs->recommendationSpec(),
  449.         ]);
  450.     }
  451.     private function processListByPriceEmptyResult(Page $resultCity $citystring $priceTypeint $minPrice nullint $maxPrice null)
  452.     {
  453.         if(!$this->features->fill_empty_profile_list())
  454.             return $result;
  455.         $this->source self::RESULT_SOURCE_BY_PARAMS;
  456.         if($this->countryCurrencyResolver->getCurrencyFor($city->getCountryCode()) == 'RUB') {
  457.             if ($minPrice && $maxPrice) {
  458.                 if ($minPrice == 2000 && $maxPrice == 3000) {
  459.                     $priceSpec = [
  460.                         ProfileWithApartmentsOneHourPrice::range(15002000),
  461.                         ProfileWithApartmentsOneHourPrice::range(30004000),
  462.                     ];
  463.                 } else if ($minPrice == 3000 && $maxPrice == 4000) {
  464.                     $priceSpec = [
  465.                         ProfileWithApartmentsOneHourPrice::range(20003000),
  466.                         ProfileWithApartmentsOneHourPrice::range(40005000),
  467.                     ];
  468.                 } else if ($minPrice == 4000 && $maxPrice == 5000) {
  469.                     $priceSpec = [
  470.                         ProfileWithApartmentsOneHourPrice::range(30004000),
  471.                         ProfileWithApartmentsOneHourPrice::range(50006000),
  472.                     ];
  473.                 } else if ($minPrice == 5000 && $maxPrice == 6000) {
  474.                     $priceSpec = [
  475.                         ProfileWithApartmentsOneHourPrice::range(4000999999)
  476.                     ];
  477.                 } else {
  478.                     $priceSpec = [
  479.                         ProfileWithApartmentsOneHourPrice::range($minPrice$maxPrice)
  480.                     ];
  481.                 }
  482.                 $result $this->listRandomSinglePage($citynullnull$priceSpectruefalse);
  483.             } elseif ($maxPrice) {
  484.                 if ($maxPrice == 500) {
  485.                     $priceSpec ProfileWithApartmentsOneHourPrice::cheaperThan(1500);
  486.                     $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  487.                     if ($result->count() == 0) {
  488.                         $priceSpec ProfileWithApartmentsOneHourPrice::range(15002000);
  489.                         $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  490.                     }
  491.                 } else if ($maxPrice == 1500) {
  492.                     $priceSpec ProfileWithApartmentsOneHourPrice::range(15002000);
  493.                     $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  494.                     if ($result->count() == 0) {
  495.                         $priceSpec ProfileWithApartmentsOneHourPrice::range(20003000);
  496.                         $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  497.                     }
  498.                 }
  499.             } else {
  500.                 switch ($priceType) {
  501.                     case 'not_expensive':
  502.                         $priceSpec ProfileWithApartmentsOneHourPrice::cheaperThan(2000);
  503.                         break;
  504.                     case 'high':
  505.                         $priceSpec ProfileWithApartmentsOneHourPrice::range(30006000);
  506.                         break;
  507.                     case 'low':
  508.                         $priceSpec ProfileWithApartmentsOneHourPrice::cheaperThan(2000);
  509.                         break;
  510.                     case 'elite':
  511.                         $priceSpec ProfileWithApartmentsOneHourPrice::moreExpensiveThan(6000);
  512.                         break;
  513.                     default:
  514.                         throw new \LogicException('Unknown price type');
  515.                         break;
  516.                 }
  517.                 $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  518.             }
  519.         }
  520.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  521.         return $result;
  522.     }
  523.     #[ParamConverter("city"converter"city_converter")]
  524.     public function listByAge(Request $requestCity $citystring $ageTypeint $minAge nullint $maxAge null): Response
  525.     {
  526.         $specs $this->profileListSpecificationService->listByAge($ageType$minAge$maxAge);
  527.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  528.         if($this->features->fill_empty_profile_list() && $result->count() == 0) {
  529.             $filled $this->processListByAgeEmptyResult($result$city$ageType$minAge$maxAge);
  530.             if($filled)
  531.                 $result $filled;
  532.         }
  533.         return $this->render('ProfileList/list.html.twig', [
  534.             'profiles' => $result,
  535.             'source' => $this->source,
  536.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  537.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  538.                 'city' => $city->getUriIdentity(),
  539.                 'ageType' => $ageType,
  540.                 'minAge' => $minAge,
  541.                 'maxAge' => $maxAge,
  542.                 'page' => $this->getCurrentPageNumber()
  543.             ]),
  544.             'recommendationSpec' => $specs->recommendationSpec(),
  545.         ]);
  546.     }
  547.     private function processListByAgeEmptyResult(Page $resultCity $citystring $ageTypeint $minAge nullint $maxAge null)
  548.     {
  549.         if(!$this->features->fill_empty_profile_list())
  550.             return $result;
  551.         $this->source self::RESULT_SOURCE_BY_PARAMS;
  552.         if ($minAge && !$maxAge) {
  553.             $startMinAge $minAge;
  554.             do {
  555.                 $startMinAge -= 2;
  556.                 $ageSpec ProfileWithAge::olderThan($startMinAge);
  557.                 $result $this->listRandomSinglePage($citynull$ageSpecnulltruefalse);
  558.             } while($result->count() == && $startMinAge >= 18);
  559.         } else if($ageType == 'young') {
  560.             $startMaxAge 20;
  561.             do {
  562.                 $startMaxAge += 2;
  563.                 $ageSpec ProfileWithAge::youngerThan($startMaxAge);
  564.                 $result $this->listRandomSinglePage($citynull$ageSpecnulltruefalse);
  565.             } while($result->count() == && $startMaxAge <= 100);
  566.         }
  567.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  568.         return $result;
  569.     }
  570.     #[ParamConverter("city"converter"city_converter")]
  571.     public function listByHeight(Request $requestCity $citystring $heightType): Response
  572.     {
  573.         $specs $this->profileListSpecificationService->listByHeight($heightType);
  574.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  575.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  576.         return $this->render('ProfileList/list.html.twig', [
  577.             'profiles' => $result,
  578.             'source' => $this->source,
  579.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  580.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  581.                 'city' => $city->getUriIdentity(),
  582.                 'heightType' => $heightType,
  583.                 'page' => $this->getCurrentPageNumber()
  584.             ]),
  585.             'recommendationSpec' => $specs->recommendationSpec(),
  586.         ]);
  587.     }
  588.     #[ParamConverter("city"converter"city_converter")]
  589.     public function listByBreastType(Request $requestCity $citystring $breastType): Response
  590.     {
  591.         if(null === BreastTypes::getValueByUriIdentity($breastType))
  592.             throw $this->createNotFoundException();
  593.         $specs $this->profileListSpecificationService->listByBreastType($breastType);
  594.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  595.         $orX $this->getORSpecForItemsArray(BreastTypes::getList(), function($item): ProfileWithBreastType {
  596.             return new ProfileWithBreastType($item);
  597.         });
  598.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  599.         return $this->render('ProfileList/list.html.twig', [
  600.             'profiles' => $result,
  601.             'source' => $this->source,
  602.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  603.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  604.                 'city' => $city->getUriIdentity(),
  605.                 'breastType' => $breastType,
  606.                 'page' => $this->getCurrentPageNumber()
  607.             ]),
  608.             'recommendationSpec' => $specs->recommendationSpec(),
  609.         ]);
  610.     }
  611.     #[ParamConverter("city"converter"city_converter")]
  612.     public function listByHairColor(Request $requestCity $citystring $hairColor): Response
  613.     {
  614.         if(null === HairColors::getValueByUriIdentity($hairColor))
  615.             throw $this->createNotFoundException();
  616.         $specs $this->profileListSpecificationService->listByHairColor($hairColor);
  617.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  618.         $orX $this->getORSpecForItemsArray(HairColors::getList(), function($item): ProfileWithHairColor {
  619.             return new ProfileWithHairColor($item);
  620.         });
  621.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  622.         return $this->render('ProfileList/list.html.twig', [
  623.             'profiles' => $result,
  624.             'source' => $this->source,
  625.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  626.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  627.                 'city' => $city->getUriIdentity(),
  628.                 'hairColor' => $hairColor,
  629.                 'page' => $this->getCurrentPageNumber()
  630.             ]),
  631.             'recommendationSpec' => $specs->recommendationSpec(),
  632.         ]);
  633.     }
  634.     #[ParamConverter("city"converter"city_converter")]
  635.     public function listByBodyType(Request $requestCity $citystring $bodyType): Response
  636.     {
  637.         if(null === BodyTypes::getValueByUriIdentity($bodyType))
  638.             throw $this->createNotFoundException();
  639.         $specs $this->profileListSpecificationService->listByBodyType($bodyType);
  640.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  641.         $orX $this->getORSpecForItemsArray(BodyTypes::getList(), function($item): ProfileWithBodyType {
  642.             return new ProfileWithBodyType($item);
  643.         });
  644.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  645.         return $this->render('ProfileList/list.html.twig', [
  646.             'profiles' => $result,
  647.             'source' => $this->source,
  648.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  649.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  650.                 'city' => $city->getUriIdentity(),
  651.                 'bodyType' => $bodyType,
  652.                 'page' => $this->getCurrentPageNumber()
  653.             ]),
  654.             'recommendationSpec' => $specs->recommendationSpec(),
  655.         ]);
  656.     }
  657.     #[ParamConverter("city"converter"city_converter")]
  658.     public function listByPlace(Request $requestCity $citystring $placeTypestring $takeOutLocation null): Response
  659.     {
  660.         $specs $this->profileListSpecificationService->listByPlace($placeType$takeOutLocation);
  661.         if(null === $specs)
  662.             throw $this->createNotFoundException();
  663.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  664.         $orX $this->getORSpecForItemsArray(TakeOutLocations::getList(), function($item): ProfileIsProvidingTakeOut {
  665.             return new ProfileIsProvidingTakeOut($item);
  666.         });
  667.         if($placeType == 'take-out')
  668.             $orX->orX(new ProfileHasApartments());
  669.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  670.         return $this->render('ProfileList/list.html.twig', [
  671.             'profiles' => $result,
  672.             'source' => $this->source,
  673.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  674.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  675.                 'city' => $city->getUriIdentity(),
  676.                 'placeType' => $placeType,
  677.                 'takeOutLocation' => TakeOutLocations::getUriIdentity(TakeOutLocations::getValueByUriIdentity($takeOutLocation)),
  678.                 'page' => $this->getCurrentPageNumber()
  679.             ]),
  680.             'recommendationSpec' => $specs->recommendationSpec(),
  681.         ]);
  682.     }
  683.     #[ParamConverter("city"converter"city_converter")]
  684.     public function listByPrivateHaircut(Request $requestCity $citystring $privateHaircut): Response
  685.     {
  686.         if(null === PrivateHaircuts::getValueByUriIdentity($privateHaircut))
  687.             throw $this->createNotFoundException();
  688.         $specs $this->profileListSpecificationService->listByPrivateHaircut($privateHaircut);
  689.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  690.         $orX $this->getORSpecForItemsArray(PrivateHaircuts::getList(), function($item): ProfileWithPrivateHaircut {
  691.             return new ProfileWithPrivateHaircut($item);
  692.         });
  693.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  694.         return $this->render('ProfileList/list.html.twig', [
  695.             'profiles' => $result,
  696.             'source' => $this->source,
  697.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  698.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  699.                 'city' => $city->getUriIdentity(),
  700.                 'privateHaircut' => $privateHaircut,
  701.                 'page' => $this->getCurrentPageNumber()
  702.             ]),
  703.             'recommendationSpec' => $specs->recommendationSpec(),
  704.         ]);
  705.     }
  706.     #[ParamConverter("city"converter"city_converter")]
  707.     public function listByNationality(Request $requestCity $citystring $nationality): Response
  708.     {
  709.         if(null === Nationalities::getValueByUriIdentity($nationality))
  710.             throw $this->createNotFoundException();
  711.         $specs $this->profileListSpecificationService->listByNationality($nationality);
  712.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  713.         $orX $this->getORSpecForItemsArray(Nationalities::getList(), function($item): ProfileWithNationality {
  714.             return new ProfileWithNationality($item);
  715.         });
  716.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  717.         return $this->render('ProfileList/list.html.twig', [
  718.             'profiles' => $result,
  719.             'source' => $this->source,
  720.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  721.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  722.                 'city' => $city->getUriIdentity(),
  723.                 'nationality' => $nationality,
  724.                 'page' => $this->getCurrentPageNumber()
  725.             ]),
  726.             'recommendationSpec' => $specs->recommendationSpec(),
  727.         ]);
  728.     }
  729.     #[ParamConverter("city"converter"city_converter")]
  730.     #[ParamConverter("service"options: ['mapping' => ['service' => 'uriIdentity']])]
  731.     public function listByProvidedService(Request $requestCity $cityService $service): Response
  732.     {
  733.         $specs $this->profileListSpecificationService->listByProvidedService($service$city);
  734.         $response null;
  735.         try {
  736.             $result $this->listingRotationApi->paginate(['city' => $city->getId(), 'service' => $service->getId()], $this->getCurrentPageNumber());
  737.             $response = new Response();
  738.             $response->setMaxAge(10);
  739.         } catch (\Exception) {
  740.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  741.         }
  742.         $prevCount $result->count();
  743.         $sameGroupServices $this->serviceRepository->findBy(['group' => $service->getGroup()]);
  744.         $orX $this->getORSpecForItemsArray([$sameGroupServices], function($item): ProfileIsProvidingOneOfServices {
  745.             return new ProfileIsProvidingOneOfServices($item);
  746.         });
  747.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_SERVICE);
  748.         if ($result->count() > $prevCount) {
  749.             $response?->setMaxAge(60);
  750.         }
  751.         return $this->render('ProfileList/list.html.twig', [
  752.             'profiles' => $result,
  753.             'source' => $this->source,
  754.             'source_default' => self::RESULT_SOURCE_SERVICE,
  755.             'service' => $service,
  756.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  757.                 'city' => $city->getUriIdentity(),
  758.                 'service' => $service->getUriIdentity(),
  759.                 'page' => $this->getCurrentPageNumber()
  760.             ]),
  761.             'recommendationSpec' => $specs->recommendationSpec(),
  762.         ], response$response);
  763.     }
  764.     /**
  765.      * @Feature("has_archive_page")
  766.      */
  767.     #[ParamConverter("city"converter"city_converter")]
  768.     public function listArchived(Request $requestCity $city): Response
  769.     {
  770.         $result $this->profileList->list($citynullnullnullfalsenullProfileList::ORDER_BY_UPDATED);
  771.         return $this->render('ProfileList/list.html.twig', [
  772.             'profiles' => $result,
  773.             'recommendationSpec' => new \App\Specification\ElasticSearch\ProfileIsNotArchived(), //ProfileIsArchived, согласно https://redminez.net/issues/28305 в реках выводятся неарзивные
  774.         ]);
  775.     }
  776.     #[ParamConverter("city"converter"city_converter")]
  777.     public function listNew(City $cityint $weeks 2): Response
  778.     {
  779.         $specs $this->profileListSpecificationService->listNew($weeks);
  780.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  781.         return $this->render('ProfileList/list.html.twig', [
  782.             'profiles' => $result,
  783.             'recommendationSpec' => $specs->recommendationSpec(),
  784.         ]);
  785.     }
  786.     #[ParamConverter("city"converter"city_converter")]
  787.     public function listByNoRetouch(City $city): Response
  788.     {
  789.         $specs $this->profileListSpecificationService->listByNoRetouch();
  790.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  791.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  792.         return $this->render('ProfileList/list.html.twig', [
  793.             'profiles' => $result,
  794.             'source' => $this->source,
  795.             'recommendationSpec' => $specs->recommendationSpec(),
  796.         ]);
  797.     }
  798.     #[ParamConverter("city"converter"city_converter")]
  799.     public function listByNice(City $city): Response
  800.     {
  801.         $specs $this->profileListSpecificationService->listByNice();
  802.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  803.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  804.         return $this->render('ProfileList/list.html.twig', [
  805.             'profiles' => $result,
  806.             'source' => $this->source,
  807.             'recommendationSpec' => $specs->recommendationSpec(),
  808.         ]);
  809.     }
  810.     #[ParamConverter("city"converter"city_converter")]
  811.     public function listByOnCall(City $city): Response
  812.     {
  813.         $specs $this->profileListSpecificationService->listByOnCall();
  814.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  815.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  816.         return $this->render('ProfileList/list.html.twig', [
  817.             'profiles' => $result,
  818.             'source' => $this->source,
  819.             'recommendationSpec' => $specs->recommendationSpec(),
  820.         ]);
  821.     }
  822.     #[ParamConverter("city"converter"city_converter")]
  823.     public function listForHour(City $city): Response
  824.     {
  825.         $specs $this->profileListSpecificationService->listForHour();
  826.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  827.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  828.         return $this->render('ProfileList/list.html.twig', [
  829.             'profiles' => $result,
  830.             'source' => $this->source,
  831.             'recommendationSpec' => $specs->recommendationSpec(),
  832.         ]);
  833.     }
  834.     #[ParamConverter("city"converter"city_converter")]
  835.     public function listForNight(City $city): Response
  836.     {
  837.         $specs $this->profileListSpecificationService->listForNight();
  838.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  839.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  840.         return $this->render('ProfileList/list.html.twig', [
  841.             'profiles' => $result,
  842.             'source' => $this->source,
  843.             'recommendationSpec' => $specs->recommendationSpec(),
  844.         ]);
  845.     }
  846.     private function getSpecForEliteGirls(City $city):Filter
  847.     {
  848.         $minPrice $this->countryCurrencyResolver->getValueByCountryCode($city->getCountryCode(), [
  849.             'RUB' => 5000,
  850.             'UAH' => 1500,
  851.             'USD' => 100,
  852.             'EUR' => 130,
  853.         ]);
  854.         return new ProfileIsElite($minPrice);
  855.     }
  856.     private function getElasticSearchSpecForEliteGirls(City $city): ISpecification
  857.     {
  858.         $minPrice $this->countryCurrencyResolver->getValueByCountryCode($city->getCountryCode(), [
  859.             'RUB' => 5000,
  860.             'UAH' => 1500,
  861.             'USD' => 100,
  862.             'EUR' => 130,
  863.         ]);
  864.         return new \App\Specification\ElasticSearch\ProfileIsElite($minPrice);
  865.     }
  866.     #[ParamConverter("city"converter"city_converter")]
  867.     public function listForEliteGirls(CountryCurrencyResolver $countryCurrencyResolverRequest $requestCity $city): Response
  868.     {
  869.         $specs $this->profileListSpecificationService->listForEliteGirls($city);
  870.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  871.         if($this->features->fill_empty_profile_list() && $result->count() == 0) {
  872.             $prices = [
  873.                 'RUB' => 5000,
  874.                 'UAH' => 1500,
  875.                 'USD' => 100,
  876.                 'EUR' => 130,
  877.             ];
  878.             $currency $countryCurrencyResolver->getCurrencyFor($city->getCountryCode());
  879.             if(isset($prices[$currency])) {
  880.                 $minPrice $prices[$currency];
  881.                 switch ($currency) {
  882.                     case 'RUB'$diff 1000; break;
  883.                     case 'UAH'$diff 500; break;
  884.                     case 'USD':
  885.                     case 'EUR'$diff 20; break;
  886.                     default:
  887.                         throw new \LogicException('Unexpected currency code');
  888.                 }
  889.                 while ($minPrice >= $diff) {
  890.                     $minPrice -= $diff;
  891.                     $result $this->listRandomSinglePage($citynullProfileWithApartmentsOneHourPrice::moreExpensiveThan($minPrice), nulltruefalse);
  892.                     if ($result->count() > 0) {
  893.                         $this->source self::RESULT_SOURCE_BY_PARAMS;
  894.                         break;
  895.                     }
  896.                 }
  897.                 $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  898.             }
  899.         }
  900.         return $this->render('ProfileList/list.html.twig', [
  901.             'profiles' => $result,
  902.             'source' => $this->source,
  903.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  904.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  905.                 'city' => $city->getUriIdentity(),
  906.                 'page' => $this->getCurrentPageNumber()
  907.             ]),
  908.             'recommendationSpec' => $specs->recommendationSpec(),
  909.         ]);
  910.     }
  911.     #[ParamConverter("city"converter"city_converter")]
  912.     public function listForRealElite(CountryCurrencyResolver $countryCurrencyResolverCity $city): Response
  913.     {
  914.         $specs $this->profileListSpecificationService->listForRealElite($city);
  915.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  916.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  917.         return $this->render('ProfileList/list.html.twig', [
  918.             'profiles' => $result,
  919.             'source' => $this->source,
  920.             'recommendationSpec' => $specs->recommendationSpec(),
  921.         ]);
  922.     }
  923.     #[ParamConverter("city"converter"city_converter")]
  924.     public function listForVipPros(CountryCurrencyResolver $countryCurrencyResolverCity $city): Response
  925.     {
  926.         $specs $this->profileListSpecificationService->listForVipPros($city);
  927.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  928.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  929.         return $this->render('ProfileList/list.html.twig', [
  930.             'profiles' => $result,
  931.             'source' => $this->source,
  932.             'recommendationSpec' => $specs->recommendationSpec(),
  933.         ]);
  934.     }
  935.     #[ParamConverter("city"converter"city_converter")]
  936.     public function listForVipIndividual(CountryCurrencyResolver $countryCurrencyResolverCity $city): Response
  937.     {
  938.         $specs $this->profileListSpecificationService->listForVipIndividual($city);
  939.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  940.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  941.         return $this->render('ProfileList/list.html.twig', [
  942.             'profiles' => $result,
  943.             'source' => $this->source,
  944.             'recommendationSpec' => $specs->recommendationSpec(),
  945.         ]);
  946.     }
  947.     #[ParamConverter("city"converter"city_converter")]
  948.     public function listForVipGirlsCity(City $city): Response
  949.     {
  950.         $specs $this->profileListSpecificationService->listForVipGirlsCity($city);
  951.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  952.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  953.         return $this->render('ProfileList/list.html.twig', [
  954.             'profiles' => $result,
  955.             'source' => $this->source,
  956.             'recommendationSpec' => $specs->recommendationSpec(),
  957.         ]);
  958.     }
  959.     #[ParamConverter("city"converter"city_converter")]
  960.     public function listOfGirlfriends(City $city): Response
  961.     {
  962.         $specs $this->profileListSpecificationService->listOfGirlfriends();
  963.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  964.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  965.         return $this->render('ProfileList/list.html.twig', [
  966.             'profiles' => $result,
  967.             'source' => $this->source,
  968.             'recommendationSpec' => $specs->recommendationSpec(),
  969.         ]);
  970.     }
  971.     #[ParamConverter("city"converter"city_converter")]
  972.     public function listOfMostExpensive(City $city): Response
  973.     {
  974.         $specs $this->profileListSpecificationService->listOfMostExpensive($city);
  975.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  976.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  977.         return $this->render('ProfileList/list.html.twig', [
  978.             'profiles' => $result,
  979.             'source' => $this->source,
  980.             'recommendationSpec' => $specs->recommendationSpec(),
  981.         ]);
  982.     }
  983.     #[ParamConverter("city"converter"city_converter")]
  984.     public function listBdsm(City $cityServiceRepository $serviceRepositoryParameterBagInterface $parameterBag): Response
  985.     {
  986.         $specs $this->profileListSpecificationService->listBdsm();
  987.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec(), $specs->additionalSpecs());
  988.         $bdsmIds $serviceRepository->findBy(['group' => ServiceGroups::BDSM]);
  989.         return $this->render('ProfileList/list.html.twig', [
  990.             'profiles' => $result,
  991.             'recommendationSpec' => $specs->recommendationSpec(),
  992.         ]);
  993.     }
  994.     #[ParamConverter("city"converter"city_converter")]
  995.     public function listByGender(City $citystring $genderDefaultCityProvider $defaultCityProvider): Response
  996.     {
  997.         if($city->getId() != $defaultCityProvider->getDefaultCity()->getId()) {
  998.             throw $this->createNotFoundException();
  999.         }
  1000.         if(null === Genders::getValueByUriIdentity($gender))
  1001.             throw $this->createNotFoundException();
  1002.         $specs $this->profileListSpecificationService->listByGender($gender);
  1003.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec(), $specs->additionalSpecs(), $specs->genders());
  1004.         return $this->render('ProfileList/list.html.twig', [
  1005.             'profiles' => $result,
  1006.             'recommendationSpec' => $specs->recommendationSpec(),
  1007.         ]);
  1008.     }
  1009.     protected function checkCityAndCountrySource(Page $resultCity $city): Page
  1010.     {
  1011.         if(($result && $result->count() != 0) || false == $this->features->fill_empty_profile_list())
  1012.             return $result;
  1013.         $this->source self::RESULT_SOURCE_CITY;
  1014.         $result $this->listRandomSinglePage($citynullnullnulltruefalse);
  1015.         if($result->count() == 0) {
  1016.             $this->source self::RESULT_SOURCE_COUNTRY;
  1017.             $result $this->listRandomSinglePage($city$city->getCountryCode(), nullnulltruefalse);
  1018.         }
  1019.         return $result;
  1020.     }
  1021.     protected function checkEmptyResultNotMasseur(Page $resultCity $city, ?OrX $alternativeSpecstring $source): Page
  1022.     {
  1023.         if($result->count() != || false == $this->features->fill_empty_profile_list())
  1024.             return $result;
  1025.         if(null != $alternativeSpec) {
  1026.             $this->source $source;
  1027.             $result $this->listRandomSinglePage($citynull$alternativeSpecnulltruefalse);
  1028.         }
  1029.         if($result->count() == 0)
  1030.             $result $this->checkCityAndCountrySource($result$city);
  1031.         return $result;
  1032.     }
  1033.     /**
  1034.      * Сейчас не используется, решили доставать их всех соседних подкатегорий разом.
  1035.      * Пока оставил, вдруг передумают.
  1036.      * @deprecated
  1037.      */
  1038.     public function listByNextSimilarCategories(callable $listMethod$requestCategory, array $similarItems): ORMQueryResult
  1039.     {
  1040.         $similarItems array_filter($similarItems, function($item) use ($requestCategory): bool {
  1041.             return $item != $requestCategory;
  1042.         });
  1043.         //shuffle($similarItems);
  1044.         $item null$result null;
  1045.         do {
  1046.             $item $item == null current($similarItems) : next($similarItems);
  1047.             if(false === $item)
  1048.                 return $result;
  1049.             $result $listMethod($item);
  1050.         } while($result->count() == 0);
  1051.         return $result;
  1052.     }
  1053.     protected function getCurrentPageNumber(): int
  1054.     {
  1055.         $page = (int) $this->requestStack->getCurrentRequest()?->get($this->pageParameter1);
  1056.         if ($page 1) {
  1057.             $page 1;
  1058.         }
  1059.         return $page;
  1060.     }
  1061.     protected function render(string $view, array $parameters = [], Response $response null): Response
  1062.     {
  1063.         $this->listingService->setCurrentListingPage($parameters['profiles']);
  1064.         $requestAttrs $this->requestStack->getCurrentRequest();
  1065.         $listing $requestAttrs->get('_controller');
  1066.         $listing is_array($listing) ? $listing[count($listing) - 1] : $listing;
  1067.         $listing preg_replace('/[^:]+::/'''$listing);
  1068.         $listingParameters $requestAttrs->get('_route_params');
  1069.         $listingParameters is_array($listingParameters) ? $listingParameters : [];
  1070.         $mainRequestHasPageParam = isset(($this->requestStack->getMainRequest()->get('_route_params') ?? [])['page']);
  1071.         if($this->requestStack->getCurrentRequest()->isXmlHttpRequest()) {
  1072.             $view = (
  1073.                 str_starts_with($listing'list')
  1074.                 && 'ProfileList/list.html.twig' === $view
  1075.                 && $mainRequestHasPageParam //isset($listingParameters['page'])
  1076.             )
  1077.                 ? 'ProfileList/list.profiles.html.twig'
  1078.                 $view
  1079.             ;
  1080.             return $this->prepareForXhr(parent::render($view$parameters$response));
  1081.             //return $this->getJSONResponse($parameters);
  1082.         } else {
  1083.             $parameters array_merge($parameters, [
  1084.                 'listing' => $listing,
  1085.                 'listing_parameters' => $listingParameters,
  1086.             ]);
  1087.             return parent::render($view$parameters$response);
  1088.         }
  1089.     }
  1090.     private function listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement(
  1091.         City $city, ?Filter $spec, array $additionalSpecs null, array $genders = [Genders::FEMALE]
  1092.     ): array|Page
  1093.     {
  1094.         return $this->profileList->listActiveWithinCityOrderedByStatusWithSpec($city$spec$additionalSpecs$genders$this->getCurrentPageNumber() < 2);
  1095.     }
  1096.     private function listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited(
  1097.         City $city, ?Filter $spec, array $additionalSpecs null, array $genders = [Genders::FEMALE], int $limit 0,
  1098.     ): array|Page
  1099.     {
  1100.         return $this->profileList->listActiveWithinCityOrderedByStatusWithSpecLimited($city$spec$additionalSpecs$genderstrue$limit);
  1101.     }
  1102.     private function listRandomSinglePage(
  1103.         City $city, ?string $country, ?Filter $spec, ?array $additionalSpecsbool $active, ?bool $masseur false,
  1104.         array $genders = [Genders::FEMALE]
  1105.     ): Page
  1106.     {
  1107.         return $this->profileList->listRandom($city$country$spec$additionalSpecs$active$masseur$genderstrue);
  1108.     }
  1109. //    protected function getJSONResponse(array $parameters)
  1110. //    {
  1111. //        $request = $this->request;
  1112. //        $data = json_decode($request->getContent(), true);
  1113. //
  1114. //        $imageSize = !empty($data['imageSize']) ? $data['imageSize'] : "357x500";
  1115. //
  1116. //        /** @var FakeORMQueryPage $queryPage */
  1117. //        $queryPage = $parameters['profiles'];
  1118. //
  1119. //        $profiles = array_map(function(ProfileListingReadModel $profile) use ($imageSize) {
  1120. //            $profile->stations = array_values($profile->stations);
  1121. //            $profile->avatar['path'] = $this->responsiveAssetsService->getResponsiveImageUrl($profile->avatar['path'], 'profile_media', $imageSize, 'jpg');
  1122. //            $profile->uri = $this->generateUrl('profile_preview.page', ['city' => $profile->city->uriIdentity, 'profile' => $profile->uriIdentity]);
  1123. //            return $profile;
  1124. //        }, $queryPage->getArray());
  1125. //
  1126. //        return new JsonResponse([
  1127. //            'profiles' => $profiles,
  1128. //            'currentPage' => $queryPage->getCurrentPage(),
  1129. //        ], Response::HTTP_OK);
  1130. //    }
  1131. }