src/Controller/ProfileListController.php line 138

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