src/Repository/ProfileRepository.php line 858

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-03-19
  5.  * Time: 22:23
  6.  */
  7. namespace App\Repository;
  8. use App\Entity\Location\City;
  9. use App\Entity\Location\MapCoordinate;
  10. use App\Entity\Profile\Genders;
  11. use App\Entity\Profile\Photo;
  12. use App\Entity\Profile\Profile;
  13. use App\Entity\Sales\Profile\AdBoardPlacement;
  14. use App\Entity\Sales\Profile\AdBoardPlacementType;
  15. use App\Entity\Sales\Profile\PlacementHiding;
  16. use App\Entity\User;
  17. use App\Repository\ReadModel\CityReadModel;
  18. use App\Repository\ReadModel\ProfileApartmentPricingReadModel;
  19. use App\Repository\ReadModel\ProfileListingReadModel;
  20. use App\Repository\ReadModel\ProfileMapReadModel;
  21. use App\Repository\ReadModel\ProfilePersonParametersReadModel;
  22. use App\Repository\ReadModel\ProfilePlacementHidingDetailReadModel;
  23. use App\Repository\ReadModel\ProfilePlacementPriceDetailReadModel;
  24. use App\Repository\ReadModel\ProfileTakeOutPricingReadModel;
  25. use App\Repository\ReadModel\ProvidedServiceReadModel;
  26. use App\Repository\ReadModel\StationLineReadModel;
  27. use App\Repository\ReadModel\StationReadModel;
  28. use App\Service\Features;
  29. use App\Specification\Profile\ProfileIdINOrderedByINValues;
  30. use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
  31. use Doctrine\ORM\AbstractQuery;
  32. use Doctrine\Persistence\ManagerRegistry;
  33. use Doctrine\DBAL\Statement;
  34. use Doctrine\ORM\QueryBuilder;
  35. use Happyr\DoctrineSpecification\Filter\Filter;
  36. use Happyr\DoctrineSpecification\Query\QueryModifier;
  37. use Porpaginas\Doctrine\ORM\ORMQueryResult;
  38. class ProfileRepository extends ServiceEntityRepository
  39. {
  40.     use SpecificationTrait;
  41.     use EntityIteratorTrait;
  42.     private Features $features;
  43.     private DistrictRepository $districts;
  44.     public function __construct(ManagerRegistry $registryFeatures $featuresDistrictRepository $districts)
  45.     {
  46.         parent::__construct($registryProfile::class);
  47.         $this->features $features;
  48.         $this->districts $districts;
  49.     }
  50.     /**
  51.      * Возвращает итератор по данным, необходимым для генерации файлов sitemap, в виде массивов с
  52.      * следующими ключами:
  53.      *  - id
  54.      *  - uri
  55.      *  - updatedAt
  56.      *  - city_uri
  57.      *
  58.      * @return iterable<array{id: int, uri: string, updatedAt: \DateTimeImmutable, city_uri: string}>
  59.      */
  60.     public function sitemapItemsIterator(): iterable
  61.     {
  62.         $qb $this->createQueryBuilder('profile')
  63.             ->select('profile.id, profile.uriIdentity AS uri, profile.updatedAt, city.uriIdentity AS city_uri')
  64.             ->join('profile.city''city')
  65.             ->andWhere('profile.deletedAt IS NULL');
  66.         $this->addModerationFilterToQb($qb'profile');
  67.         return $qb->getQuery()->toIterable([], AbstractQuery::HYDRATE_ARRAY);
  68.     }
  69.     protected function addModerationFilterToQb(QueryBuilder $qbstring $dqlAlias): void
  70.     {
  71.         if ($this->features->hard_moderation()) {
  72.             $qb->leftJoin(sprintf('%s.owner'$dqlAlias), 'owner');
  73.             $qb->andWhere(
  74.                 $qb->expr()->orX(
  75.                     sprintf('%s.moderationStatus = :status_passed'$dqlAlias),
  76.                     $qb->expr()->andX(
  77.                         sprintf('%s.moderationStatus = :status_waiting'$dqlAlias),
  78.                         'owner.trusted = true'
  79.                     )
  80.                 )
  81.             );
  82.             $qb->setParameter('status_passed'Profile::MODERATION_STATUS_APPROVED);
  83.             $qb->setParameter('status_waiting'Profile::MODERATION_STATUS_WAITING);
  84.         } else {
  85.             $qb->andWhere(sprintf('%s.moderationStatus IN (:statuses)'$dqlAlias));
  86.             $qb->setParameter('statuses', [Profile::MODERATION_STATUS_NOT_PASSEDProfile::MODERATION_STATUS_WAITINGProfile::MODERATION_STATUS_APPROVED]);
  87.         }
  88.     }
  89.     public function ofUriIdentityWithinCity(string $uriIdentityCity $city): ?Profile
  90.     {
  91.         return $this->findOneBy([
  92.             'uriIdentity' => $uriIdentity,
  93.             'city' => $city,
  94.         ]);
  95.     }
  96.     /**
  97.      * Метод проверки уникальности анкет по URI не должен использовать никаких фильтров, кроме URI и города,
  98.      * поэтому QueryBuilder не используется
  99.      * @see https://redminez.net/issues/27310
  100.      */
  101.     public function isUniqueUriIdentityExistWithinCity(string $uriIdentityCity $city): bool
  102.     {
  103.         $connection $this->_em->getConnection();
  104.         $stmt $connection->executeQuery('SELECT COUNT(id) FROM profiles WHERE uri_identity = ? AND city_id = ?', [$uriIdentity$city->getId()]);
  105.         $count $stmt->fetchOne();
  106.         return $count 0;
  107.     }
  108.     public function countByCity(): array
  109.     {
  110.         $qb $this->createQueryBuilder('profile')
  111.             ->select('IDENTITY(profile.city), COUNT(profile.id)')
  112.             ->groupBy('profile.city');
  113.         $this->addFemaleGenderFilterToQb($qb'profile');
  114.         $this->addModerationFilterToQb($qb'profile');
  115.         //$this->excludeHavingPlacementHiding($qb, 'profile');
  116.         $this->havingAdBoardPlacement($qb'profile');
  117.         $query $qb->getQuery()
  118.             ->useResultCache(true)
  119.             ->setResultCacheLifetime(120);
  120.         $rawResult $query->getScalarResult();
  121.         $indexedResult = [];
  122.         foreach ($rawResult as $row) {
  123.             $indexedResult[$row[1]] = $row[2];
  124.         }
  125.         return $indexedResult;
  126.     }
  127.     protected function addFemaleGenderFilterToQb(QueryBuilder $qbstring $alias): void
  128.     {
  129.         $this->addGenderFilterToQb($qb$alias, [Genders::FEMALE]);
  130.     }
  131.     protected function addGenderFilterToQb(QueryBuilder $qbstring $alias, array $genders = [Genders::FEMALE]): void
  132.     {
  133.         $qb->andWhere(sprintf('%s.personParameters.gender IN (:genders)'$alias));
  134.         $qb->setParameter('genders'$genders);
  135.     }
  136.     private function havingAdBoardPlacement(QueryBuilder $qbstring $alias): void
  137.     {
  138.         $qb->join(sprintf('%s.adBoardPlacement'$alias), 'adboard_placement');
  139.     }
  140.     public function countByStations(): array
  141.     {
  142.         $qb $this->createQueryBuilder('profiles')
  143.             ->select('stations.id, COUNT(profiles.id) as cnt')
  144.             ->join('profiles.stations''stations')
  145.             //это условие сильно затормжаживает запрос, но оно и не нужно при условии, что чужих(от других городов) станций у анкеты нет
  146.             //->where('profiles.city = stations.city')
  147.             ->groupBy('stations.id');
  148.         $this->addFemaleGenderFilterToQb($qb'profiles');
  149.         $this->addModerationFilterToQb($qb'profiles');
  150.         //$this->excludeHavingPlacementHiding($qb, 'profiles');
  151.         $this->havingAdBoardPlacement($qb'profiles');
  152.         $query $qb->getQuery()
  153.             ->useResultCache(true)
  154.             ->setResultCacheLifetime(120);
  155.         $rawResult $query->getScalarResult();
  156.         $indexedResult = [];
  157.         foreach ($rawResult as $row) {
  158.             $indexedResult[$row['id']] = $row['cnt'];
  159.         }
  160.         return $indexedResult;
  161.     }
  162.     public function countByDistricts(): array
  163.     {
  164.         $qb $this->createQueryBuilder('profiles')
  165.             ->select('districts.id, COUNT(profiles.id) as cnt')
  166.             ->join('profiles.stations''stations')
  167.             ->join('stations.district''districts')
  168.             ->groupBy('districts.id');
  169.         $this->addFemaleGenderFilterToQb($qb'profiles');
  170.         $this->addModerationFilterToQb($qb'profiles');
  171.         //$this->excludeHavingPlacementHiding($qb, 'profiles');
  172.         $this->havingAdBoardPlacement($qb'profiles');
  173.         $query $qb->getQuery()
  174.             ->useResultCache(true)
  175.             ->setResultCacheLifetime(120);
  176.         $rawResult $query->getScalarResult();
  177.         $indexedResult = [];
  178.         foreach ($rawResult as $row) {
  179.             $indexedResult[$row['id']] = $row['cnt'];
  180.         }
  181.         return $indexedResult;
  182.     }
  183.     public function countByCounties(): array
  184.     {
  185.         $qb $this->createQueryBuilder('profiles')
  186.             ->select('counties.id, COUNT(profiles.id) as cnt')
  187.             ->join('profiles.stations''stations')
  188.             ->join('stations.district''districts')
  189.             ->join('districts.county''counties')
  190.             ->groupBy('counties.id');
  191.         $this->addFemaleGenderFilterToQb($qb'profiles');
  192.         $this->addModerationFilterToQb($qb'profiles');
  193.         //$this->excludeHavingPlacementHiding($qb, 'profiles');
  194.         $this->havingAdBoardPlacement($qb'profiles');
  195.         $query $qb->getQuery()
  196.             ->useResultCache(true)
  197.             ->setResultCacheLifetime(120);
  198.         $rawResult $query->getScalarResult();
  199.         $indexedResult = [];
  200.         foreach ($rawResult as $row) {
  201.             $indexedResult[$row['id']] = $row['cnt'];
  202.         }
  203.         return $indexedResult;
  204.     }
  205.     /**
  206.      * @param array|int[] $ids
  207.      * @return Profile[]
  208.      */
  209.     public function findByIds(array $ids): array
  210.     {
  211.         return $this->createQueryBuilder('profile')
  212.             ->andWhere('profile.id IN (:ids)')
  213.             ->setParameter('ids'$ids)
  214.             ->orderBy('FIELD(profile.id,:ids2)')
  215.             ->setParameter('ids2'$ids)
  216.             ->getQuery()
  217.             ->getResult();
  218.     }
  219.     public function findByIdsIterate(array $ids): iterable
  220.     {
  221.         $qb $this->createQueryBuilder('profile')
  222.             ->andWhere('profile.id IN (:ids)')
  223.             ->setParameter('ids'$ids)
  224.             ->orderBy('FIELD(profile.id,:ids2)')
  225.             ->setParameter('ids2'$ids);
  226.         return $this->iterateQueryBuilder($qb);
  227.     }
  228.     /**
  229.      * Список анкет указанного типа (массажистки или нет), привязанных к аккаунту
  230.      */
  231.     public function ofOwnerAndTypePaged(User $ownerbool $masseurs): ORMQueryResult
  232.     {
  233.         $qb $this->createQueryBuilder('profile')
  234.             ->andWhere('profile.owner = :owner')
  235.             ->setParameter('owner'$owner)
  236.             ->andWhere('profile.masseur = :is_masseur')
  237.             ->setParameter('is_masseur'$masseurs);
  238.         return new ORMQueryResult($qb);
  239.     }
  240.     /**
  241.      * Список активных анкет, привязанных к аккаунту
  242.      */
  243.     public function activeAndOwnedBy(User $owner): ORMQueryResult
  244.     {
  245.         $qb $this->createQueryBuilder('profile')
  246.             ->join('profile.adBoardPlacement''profile_adboard_placement')
  247.             ->andWhere('profile.owner = :owner')
  248.             ->setParameter('owner'$owner);
  249.         return new ORMQueryResult($qb);
  250.     }
  251.     /**
  252.      * Список активных или скрытых анкет, привязанных к аккаунту
  253.      *
  254.      * @return Profile[]|ORMQueryResult
  255.      */
  256.     public function activeOrHiddenAndOwnedBy(User $owner): ORMQueryResult
  257.     {
  258.         $qb $this->createQueryBuilder('profile')
  259.             ->leftJoin('profile.adBoardPlacement''profile_adboard_placement')
  260.             ->leftJoin('profile.placementHiding''placement_hiding')
  261.             ->andWhere('profile_adboard_placement IS NOT NULL OR placement_hiding IS NOT NULL')
  262.             ->andWhere('profile.owner = :owner')
  263.             ->setParameter('owner'$owner);
  264.         return new ORMQueryResult($qb);
  265.     }
  266.     public function activePaidAdBoardPlacementAndOwnedBy(User $owner): ORMQueryResult
  267.     {
  268.         $qb $this->createQueryBuilder('profile')
  269.             ->addSelect('profile_adboard_placement''placement_price''city''owner')
  270.             ->join('profile.adBoardPlacement''profile_adboard_placement')
  271.             ->leftJoin('profile_adboard_placement.placementPrice''placement_price')
  272.             ->join('profile.city''city')
  273.             ->join('profile.owner''owner')
  274.             ->andWhere('profile_adboard_placement.type <> :free_placement_type')
  275.             ->andWhere('profile.owner = :owner')
  276.             ->setParameter('free_placement_type'AdBoardPlacementType::FREE)
  277.             ->setParameter('owner'$owner);
  278.         return new ORMQueryResult($qb);
  279.     }
  280.     public function paidAdBoardPlacementChargeRowsOfOwner(User $owner): array
  281.     {
  282.         $qb $this->createQueryBuilder('profile')
  283.             ->select([
  284.                 'profile.id AS profile_id',
  285.                 'profile.approved AS approved',
  286.                 'profile.masseur AS is_masseur',
  287.                 'profile.personParameters.gender AS gender',
  288.                 'profile_adboard_placement.type AS placement_type',
  289.                 'profile_adboard_placement.planManaged AS plan_managed',
  290.                 'placement_price.id AS placement_price_id',
  291.                 'placement_price.priceAmount AS price_amount',
  292.                 'placement_price.duration AS duration',
  293.                 'placement_price.currency AS currency',
  294.                 'placement_price.dynamicPriceMatrix AS dynamic_price_matrix',
  295.                 'city.id AS city_id',
  296.                 'city.cityPriceCategory AS city_price_category',
  297.                 'city.timezone AS timezone',
  298.                 'owner.currencyCode AS owner_currency',
  299.             ])
  300.             ->join('profile.adBoardPlacement''profile_adboard_placement')
  301.             ->leftJoin('profile_adboard_placement.placementPrice''placement_price')
  302.             ->join('profile.city''city')
  303.             ->join('profile.owner''owner')
  304.             ->andWhere('profile_adboard_placement.type <> :free_placement_type')
  305.             ->andWhere('profile.owner = :owner')
  306.             ->setParameter('free_placement_type'AdBoardPlacementType::FREE)
  307.             ->setParameter('owner'$owner);
  308.         return $qb->getQuery()->getArrayResult();
  309.     }
  310.     public function currentChargeableAndOwnedBy(User $owner): ORMQueryResult
  311.     {
  312.         $qb $this->createQueryBuilder('profile')
  313.             ->addSelect('profile_adboard_placement''placement_price''placement_hiding''city''owner')
  314.             ->leftJoin('profile.adBoardPlacement''profile_adboard_placement')
  315.             ->leftJoin('profile_adboard_placement.placementPrice''placement_price')
  316.             ->leftJoin('profile.placementHiding''placement_hiding')
  317.             ->join('profile.city''city')
  318.             ->join('profile.owner''owner')
  319.             ->andWhere('(profile_adboard_placement IS NOT NULL AND profile_adboard_placement.type <> :free_placement_type) OR placement_hiding IS NOT NULL')
  320.             ->andWhere('profile.owner = :owner')
  321.             ->setParameter('free_placement_type'AdBoardPlacementType::FREE)
  322.             ->setParameter('owner'$owner);
  323.         return new ORMQueryResult($qb);
  324.     }
  325.     public function countFreeUnapprovedLimited(): int
  326.     {
  327.         $qb $this->createQueryBuilder('profile')
  328.             ->select('count(profile)')
  329.             ->join('profile.adBoardPlacement''placement')
  330.             ->andWhere('placement.type = :placement_type')
  331.             ->setParameter('placement_type'AdBoardPlacementType::FREE)
  332.             ->leftJoin('profile.placementHiding''hiding')
  333.             ->andWhere('hiding IS NULL')
  334.             ->andWhere('profile.approved = false');
  335.         return (int)$qb->getQuery()->getSingleScalarResult();
  336.     }
  337.     public function iterateFreeUnapprovedLimited(int $limit): iterable
  338.     {
  339.         $qb $this->createQueryBuilder('profile')
  340.             ->join('profile.adBoardPlacement''placement')
  341.             ->andWhere('placement.type = :placement_type')
  342.             ->setParameter('placement_type'AdBoardPlacementType::FREE)
  343.             ->leftJoin('profile.placementHiding''hiding')
  344.             ->andWhere('hiding IS NULL')
  345.             ->andWhere('profile.approved = false')
  346.             ->setMaxResults($limit);
  347.         return $this->iterateQueryBuilder($qb);
  348.     }
  349.     /**
  350.      * Число активных анкет, привязанных к аккаунту
  351.      */
  352.     public function countActiveOfOwner(User $owner, ?bool $isMasseur false): int
  353.     {
  354.         $qb $this->createQueryBuilder('profile')
  355.             ->select('COUNT(profile.id)')
  356.             ->join('profile.adBoardPlacement''profile_adboard_placement')
  357.             ->andWhere('profile.owner = :owner')
  358.             ->setParameter('owner'$owner);
  359.         if ($this->features->hard_moderation()) {
  360.             $qb->leftJoin('profile.owner''owner');
  361.             $qb->andWhere(
  362.                 $qb->expr()->orX(
  363.                     'profile.moderationStatus = :status_passed',
  364.                     $qb->expr()->andX(
  365.                         'profile.moderationStatus = :status_waiting',
  366.                         'owner.trusted = true'
  367.                     )
  368.                 )
  369.             );
  370.             $qb->setParameter('status_passed'Profile::MODERATION_STATUS_APPROVED);
  371.             $qb->setParameter('status_waiting'Profile::MODERATION_STATUS_WAITING);
  372.         } else {
  373.             $qb->andWhere('profile.moderationStatus IN (:statuses)')
  374.                 ->setParameter('statuses', [Profile::MODERATION_STATUS_NOT_PASSEDProfile::MODERATION_STATUS_WAITINGProfile::MODERATION_STATUS_APPROVED]);
  375.         }
  376.         if (null !== $isMasseur) {
  377.             $qb->andWhere('profile.masseur = :is_masseur')
  378.                 ->setParameter('is_masseur'$isMasseur);
  379.         }
  380.         return (int)$qb->getQuery()->getSingleScalarResult();
  381.     }
  382.     /**
  383.      * Число всех анкет, привязанных к аккаунту
  384.      */
  385.     public function countAllOfOwnerNotDeleted(User $owner, ?bool $isMasseur false): int
  386.     {
  387.         $qb $this->createQueryBuilder('profile')
  388.             ->select('COUNT(profile.id)')
  389.             ->andWhere('profile.owner = :owner')
  390.             ->setParameter('owner'$owner)
  391.             //потому что используется в т.ч. на тех страницах, где отключен фильтр вывода "только неудаленных"
  392.             ->andWhere('profile.deletedAt IS NULL');
  393.         if (null !== $isMasseur) {
  394.             $qb->andWhere('profile.masseur = :is_masseur')
  395.                 ->setParameter('is_masseur'$isMasseur);
  396.         }
  397.         return (int)$qb->getQuery()->getSingleScalarResult();
  398.     }
  399.     public function getTimezonesListByUser(User $owner): array
  400.     {
  401.         $q $this->_em->createQuery(sprintf("
  402.                 SELECT c
  403.                 FROM %s c
  404.                 WHERE c.id IN (
  405.                     SELECT DISTINCT(c2.id) 
  406.                     FROM %s p
  407.                     JOIN p.city c2
  408.                     WHERE p.owner = :user
  409.                 )
  410.             "$this->_em->getClassMetadata(City::class)->name$this->_em->getClassMetadata(Profile::class)->name))
  411.             ->setParameter('user'$owner);
  412.         return $q->getResult();
  413.     }
  414.     /**
  415.      * Список анкет, привязанных к аккаунту
  416.      *
  417.      * @return Profile[]
  418.      */
  419.     public function ofOwner(User $owner): array
  420.     {
  421.         $qb $this->createQueryBuilder('profile')
  422.             ->andWhere('profile.owner = :owner')
  423.             ->setParameter('owner'$owner);
  424.         return $qb->getQuery()->getResult();
  425.     }
  426.     public function ofOwnerPaged(User $owner, array $genders = [Genders::FEMALE]): ORMQueryResult
  427.     {
  428.         $qb $this->createQueryBuilder('profile')
  429.             ->andWhere('profile.owner = :owner')
  430.             ->setParameter('owner'$owner)
  431.             ->andWhere('profile.personParameters.gender IN (:genders)')
  432.             ->setParameter('genders'$genders);
  433.         return new ORMQueryResult($qb);
  434.     }
  435.     public function ofOwnerAndMasseurTypeWithPlacementFilterAndNameFilterIterateAll(User $ownerstring $placementTypeFilter, ?string $nameFilter, ?bool $isMasseur null): \Generator
  436.     {
  437.         $query $this->queryBuilderOfOwnerAndMasseurTypeWithPlacementFilterAndNameFilter($owner$placementTypeFilter$nameFilter$isMasseur)->getQuery();
  438.         foreach ($query->iterate() as $row) {
  439.             yield $row[0];
  440.         }
  441.     }
  442.     private function queryBuilderOfOwnerAndMasseurTypeWithPlacementFilterAndNameFilter(User $ownerstring $placementTypeFilter, ?string $nameFilter, ?bool $isMasseur null): QueryBuilder
  443.     {
  444.         $qb $this->createQueryBuilder('profile')
  445.             ->andWhere('profile.owner = :owner')
  446.             ->setParameter('owner'$owner);
  447.         switch ($placementTypeFilter) {
  448.             case 'paid':
  449.                 $qb->join('profile.adBoardPlacement''placement')
  450.                     ->andWhere('placement.type != :placement_type')
  451.                     ->setParameter('placement_type'AdBoardPlacementType::FREE);
  452.                 break;
  453.             case 'free':
  454.                 $qb->join('profile.adBoardPlacement''placement')
  455.                     ->andWhere('placement.type = :placement_type')
  456.                     ->setParameter('placement_type'AdBoardPlacementType::FREE);
  457.                 break;
  458.             case 'ultra-vip':
  459.                 $qb->join('profile.adBoardPlacement''placement')
  460.                     ->andWhere('placement.type = :placement_type')
  461.                     ->setParameter('placement_type'AdBoardPlacementType::ULTRA_VIP);
  462.                 break;
  463.             case 'vip':
  464.                 $qb->join('profile.adBoardPlacement''placement')
  465.                     ->andWhere('placement.type = :placement_type')
  466.                     ->setParameter('placement_type'AdBoardPlacementType::VIP);
  467.                 break;
  468.             case 'standard':
  469.                 $qb->join('profile.adBoardPlacement''placement')
  470.                     ->andWhere('placement.type = :placement_type')
  471.                     ->setParameter('placement_type'AdBoardPlacementType::STANDARD);
  472.                 break;
  473.             case 'hidden':
  474.                 $qb->join('profile.placementHiding''placement_hiding');
  475.                 break;
  476.             case 'all':
  477.             default:
  478.                 break;
  479.         }
  480.         if ($nameFilter) {
  481.             $nameExpr $qb->expr()->orX(
  482.                 'LOWER(JSON_UNQUOTE(JSON_EXTRACT(profile.name, :jsonPath))) LIKE :name_filter',
  483.                 \sprintf("REGEXP_REPLACE(profile.phoneNumber, '-| ', '') LIKE :name_filter"),
  484.                 'LOWER(profile.phoneNumber) LIKE :name_filter',
  485.                 \sprintf("REGEXP_REPLACE(profile.phoneNumber, '\+7', '8') LIKE :name_filter"),
  486.             );
  487.             $qb->setParameter('jsonPath''$.ru');
  488.             $qb->setParameter('name_filter''%' addcslashes(mb_strtolower(str_replace(['('')'' ''-'], ''$nameFilter)), '%_') . '%');
  489.             $qb->andWhere($nameExpr);
  490.         }
  491.         if (null !== $isMasseur) {
  492.             $qb->andWhere('profile.masseur = :is_masseur')
  493.                 ->setParameter('is_masseur'$isMasseur);
  494.         }
  495.         return $qb;
  496.     }
  497.     public function ofOwnerAndMasseurTypeWithPlacementFilterAndNameFilterPaged(User $ownerstring $placementTypeFilter, ?string $nameFilter, ?bool $isMasseur null): ORMQueryResult
  498.     {
  499.         $qb $this->queryBuilderOfOwnerAndMasseurTypeWithPlacementFilterAndNameFilter($owner$placementTypeFilter$nameFilter$isMasseur);
  500.         //сортируем анкеты по статусу UltraVip->Vip->Standard->Free->Hidden
  501.         $aliases $qb->getAllAliases();
  502.         if (false == in_array('placement'$aliases))
  503.             $qb->leftJoin('profile.adBoardPlacement''placement');
  504.         if (false == in_array('placement_hiding'$aliases))
  505.             $qb->leftJoin('profile.placementHiding''placement_hiding');
  506.         $qb->addSelect('IF(placement_hiding.id IS NULL, 0, 1) as HIDDEN is_hidden');
  507.         $qb->addOrderBy('placement.type''DESC');
  508.         $qb->addOrderBy('placement.placedAt''DESC');
  509.         $qb->addOrderBy('is_hidden''ASC');
  510.         return new ORMQueryResult($qb);
  511.     }
  512.     public function idsOfOwnerAndMasseurTypeWithPlacementFilterAndNameFilter(User $ownerstring $placementTypeFilter, ?string $nameFilter, ?bool $isMasseur null): array
  513.     {
  514.         $qb $this->queryBuilderOfOwnerAndMasseurTypeWithPlacementFilterAndNameFilter($owner$placementTypeFilter$nameFilter$isMasseur);
  515.         $qb->select('profile.id');
  516.         return $qb->getQuery()->getResult('column_hydrator');
  517.     }
  518.     public function countOfOwnerAndMasseurTypeWithPlacementFilterAndNameFilter(User $ownerstring $placementTypeFilter, ?string $nameFilter, ?bool $isMasseur null): int
  519.     {
  520.         $qb $this->queryBuilderOfOwnerAndMasseurTypeWithPlacementFilterAndNameFilter($owner$placementTypeFilter$nameFilter$isMasseur);
  521.         $qb->select('count(profile.id)')
  522.             ->setMaxResults(1);
  523.         return (int)$qb->getQuery()->getSingleScalarResult();
  524.     }
  525.     /**
  526.      * @deprecated
  527.      */
  528.     public function hydrateProfileRow(array $row): ProfileListingReadModel
  529.     {
  530.         $profile = new ProfileListingReadModel();
  531.         $profile->id $row['id'];
  532.         $profile->city $row['city'];
  533.         $profile->uriIdentity $row['uriIdentity'];
  534.         $profile->name $row['name'];
  535.         $profile->description $row['description'];
  536.         $profile->phoneNumber $row['phoneNumber'];
  537.         $profile->approved $row['approved'];
  538.         $now = new \DateTimeImmutable('now');
  539.         $hasRunningTopPlacement false;
  540.         foreach ($row['topPlacements'] as $topPlacement) {
  541.             if ($topPlacement['placedAt'] <= $now && $now <= $topPlacement['expiresAt'])
  542.                 $hasRunningTopPlacement true;
  543.         }
  544.         $profile->active null !== $row['adBoardPlacement'] || $hasRunningTopPlacement;
  545.         $profile->hidden null != $row['placementHiding'];
  546.         $profile->personParameters = new ProfilePersonParametersReadModel();
  547.         $profile->personParameters->age $row['personParameters.age'];
  548.         $profile->personParameters->height $row['personParameters.height'];
  549.         $profile->personParameters->weight $row['personParameters.weight'];
  550.         $profile->personParameters->breastSize $row['personParameters.breastSize'];
  551.         $profile->personParameters->bodyType $row['personParameters.bodyType'];
  552.         $profile->personParameters->hairColor $row['personParameters.hairColor'];
  553.         $profile->personParameters->privateHaircut $row['personParameters.privateHaircut'];
  554.         $profile->personParameters->nationality $row['personParameters.nationality'];
  555.         $profile->personParameters->hasTattoo $row['personParameters.hasTattoo'];
  556.         $profile->personParameters->hasPiercing $row['personParameters.hasPiercing'];
  557.         $profile->stations $row['stations'];
  558.         $profile->avatar $row['avatar'];
  559.         foreach ($row['photos'] as $photo)
  560.             if ($photo['main'])
  561.                 $profile->mainPhoto $photo;
  562.         $profile->mainPhoto null;
  563.         $profile->photos = [];
  564.         $profile->selfies = [];
  565.         foreach ($row['photos'] as $photo) {
  566.             if ($photo['main'])
  567.                 $profile->mainPhoto $photo;
  568.             if ($photo['type'] == Photo::TYPE_PHOTO)
  569.                 $profile->photos[] = $photo;
  570.             if ($photo['type'] == Photo::TYPE_SELFIE)
  571.                 $profile->selfies[] = $photo;
  572.         }
  573.         $profile->videos $row['videos'];
  574.         $profile->comments $row['comments'];
  575.         $profile->apartmentsPricing = new ProfileApartmentPricingReadModel();
  576.         $profile->apartmentsPricing->oneHourPrice $row['apartmentsPricing.oneHourPrice'];
  577.         $profile->apartmentsPricing->twoHoursPrice $row['apartmentsPricing.twoHoursPrice'];
  578.         $profile->apartmentsPricing->nightPrice $row['apartmentsPricing.nightPrice'];
  579.         $profile->takeOutPricing = new ProfileTakeOutPricingReadModel();
  580.         $profile->takeOutPricing->oneHourPrice $row['takeOutPricing.oneHourPrice'];
  581.         $profile->takeOutPricing->twoHoursPrice $row['takeOutPricing.twoHoursPrice'];
  582.         $profile->takeOutPricing->nightPrice $row['takeOutPricing.nightPrice'];
  583.         return $profile;
  584.     }
  585.     public function deletedByPeriod(\DateTimeInterface $start\DateTimeInterface $end): array
  586.     {
  587.         $qb $this->createQueryBuilder('profile')
  588.             ->join('profile.city''city')
  589.             ->select('profile.uriIdentity _profile')
  590.             ->addSelect('city.uriIdentity _city')
  591.             ->andWhere('profile.deletedAt >= :start')
  592.             ->andWhere('profile.deletedAt <= :end')
  593.             ->setParameter('start'$start)
  594.             ->setParameter('end'$end);
  595.         return $qb->getQuery()->getResult();
  596.     }
  597.     public function listForMapMatchingSpec(Filter|QueryModifier $specificationint $coordinatesRoundPrecision 3): array
  598.     {
  599.         $this->getEntityManager()->getConnection()->executeQuery("
  600.             SET SESSION group_concat_max_len = 100000;
  601.         ");
  602.         /** @var QueryBuilder $qb */
  603.         $qb $this->createQueryBuilder($dqlAlias 'p');
  604.         $qb->select(sprintf('GROUP_CONCAT(p.id), CONCAT(ROUND(MIN(p.mapCoordinate.latitude),5),\',\',ROUND(MIN(p.mapCoordinate.longitude),5)), count(p.id), CONCAT(ROUND(p.mapCoordinate.latitude,%1$s),\',\',ROUND(p.mapCoordinate.longitude,%1$s)) as coords, GROUP_CONCAT(p.masseur)'$coordinatesRoundPrecision));
  605.         $qb->groupBy('coords');
  606.         $specification->modify($qb$dqlAlias);
  607.         $qb->andWhere($specification->getFilter($qb$dqlAlias));
  608.         return $qb->getQuery()->getResult();
  609.     }
  610.     public function fetchListingByIds(ProfileIdINOrderedByINValues $specification): array
  611.     {
  612.         $ids implode(','$specification->getIds());
  613.         $mediaType $this->features->crop_avatar() ? Photo::TYPE_AVATAR Photo::TYPE_PHOTO;
  614.         $mediaIsMain $this->features->crop_avatar() ? 1;
  615.         $sql "
  616.             SELECT 
  617.                 p.*, JSON_UNQUOTE(JSON_EXTRACT(p.name, '$.ru')) 
  618.                     as `name`, 
  619.                 JSON_UNQUOTE(JSON_EXTRACT(p.description, '$.ru')) 
  620.                     as `description`,
  621.                 (SELECT path FROM profile_media_files pmf_avatar WHERE p.id = pmf_avatar.profile_id AND pmf_avatar.type = '{$mediaType}' AND pmf_avatar.is_main = {$mediaIsMain} LIMIT 1) 
  622.                     as `avatar_path`,
  623.                 (SELECT type FROM profile_adboard_placements pap WHERE p.id = pap.profile_id LIMIT 1) 
  624.                     as `adboard_placement_type`,
  625.                 (SELECT position FROM profile_adboard_placements pap WHERE p.id = pap.profile_id LIMIT 1) 
  626.                     as `adboard_placement_position`,
  627.                 c.id 
  628.                     as `city_id`, 
  629.                 JSON_UNQUOTE(JSON_EXTRACT(c.name, '$.ru')) 
  630.                     as `city_name`, 
  631.                 c.uri_identity 
  632.                     as `city_uri_identity`,
  633.                 c.country_code 
  634.                     as `city_country_code`,
  635.                 EXISTS(SELECT * FROM profile_top_placements ptp WHERE p.id = ptp.profile_id AND (NOW() BETWEEN ptp.placed_at AND ptp.expires_at))
  636.                     as `has_top_placement`,
  637.                 EXISTS(SELECT * FROM placement_hidings ph WHERE p.id = ph.profile_id AND ph.entity_type = 'profile') 
  638.                     as `has_placement_hiding`,
  639.                 EXISTS(SELECT * FROM profile_comments pc WHERE p.id = pc.profile_id AND pc.deleted_at is NULL) 
  640.                     as `has_comments`,
  641.                 EXISTS(SELECT * FROM profile_media_files pmf_video WHERE p.id = pmf_video.profile_id AND pmf_video.type = 'video') 
  642.                     as `has_videos`,
  643.                 EXISTS(SELECT * FROM profile_media_files pmf_selfie WHERE p.id = pmf_selfie.profile_id AND pmf_selfie.type = 'selfie') 
  644.                     as `has_selfies`
  645.             FROM profiles `p`
  646.             JOIN cities `c` ON c.id = p.city_id 
  647.             WHERE p.id IN ($ids)
  648.             ORDER BY FIELD(p.id,$ids)";
  649.         $connection $this->getEntityManager()->getConnection();
  650.         $result $connection->executeQuery($sql);
  651.         $profiles $result->fetchAllAssociative();
  652.         $sql "SELECT 
  653.                     cs.id 
  654.                         as `id`,
  655.                     JSON_UNQUOTE(JSON_EXTRACT(cs.name, '$.ru')) 
  656.                         as `name`, 
  657.                     cs.uri_identity 
  658.                         as `uriIdentity`, 
  659.                     ps.profile_id
  660.                         as `profile_id`,
  661.                     csl.name
  662.                         as `line_name`,
  663.                     csl.color
  664.                         as `line_color`,
  665.                     cs.county_id, cs.district_id
  666.                 FROM profile_stations ps
  667.                 JOIN city_stations cs ON ps.station_id = cs.id 
  668.                 LEFT JOIN city_subway_station_lines cssl ON cssl.station_id = cs.id
  669.                 LEFT JOIN city_subway_lines csl ON csl.id = cssl.line_id
  670.                 WHERE ps.profile_id IN ($ids)";
  671.         $result $connection->executeQuery($sql);
  672.         $stations $result->fetchAllAssociative();
  673.         $districtIds array_unique(array_column($stations'district_id'));
  674.         $districts $this->districts->ofIds($districtIds);
  675.         $sql "SELECT 
  676.                     s.id 
  677.                         as `id`,
  678.                     JSON_UNQUOTE(JSON_EXTRACT(s.name, '$.ru')) 
  679.                         as `name`, 
  680.                     s.group 
  681.                         as `group`, 
  682.                     s.uri_identity 
  683.                         as `uriIdentity`,
  684.                     pps.profile_id
  685.                         as `profile_id`,
  686.                     pps.service_condition
  687.                         as `condition`,
  688.                     pps.extra_charge
  689.                         as `extra_charge`,
  690.                     pps.comment
  691.                         as `comment`
  692.                 FROM profile_provided_services pps
  693.                 JOIN services s ON pps.service_id = s.id 
  694.                 WHERE pps.profile_id IN ($ids)
  695.                 ORDER BY s.group ASC, s.sort ASC, s.id ASC";
  696.         $result $connection->executeQuery($sql);
  697.         $providedServices $result->fetchAllAssociative();
  698.         $result array_map(function ($profile) use ($stations$districts$providedServices): ProfileListingReadModel {
  699.             return $this->hydrateProfileRow2($profile$stations$districts$providedServices);
  700.         }, $profiles);
  701.         return $result;
  702.     }
  703.     public function hydrateProfileRow2(array $row, array $stations, array $districts, array $services): ProfileListingReadModel
  704.     {
  705.         $profile = new ProfileListingReadModel();
  706.         $profile->id $row['id'];
  707.         $profile->moderationStatus $row['moderation_status'];
  708.         $profile->city = new CityReadModel();
  709.         $profile->city->id $row['city_id'];
  710.         $profile->city->name $row['city_name'];
  711.         $profile->city->uriIdentity $row['city_uri_identity'];
  712.         $profile->city->countryCode $row['city_country_code'];
  713.         $profile->uriIdentity $row['uri_identity'];
  714.         $profile->name $row['name'];
  715.         $profile->description $row['description'];
  716.         $profile->phoneNumber $row['phone_number'];
  717.         $profile->approved = (bool)$row['is_approved'];
  718.         $profile->isUltraVip $row['adboard_placement_type'] == AdBoardPlacement::POSITION_GROUP_ULTRA_VIP;
  719.         $profile->isVip $row['adboard_placement_type'] == AdBoardPlacement::POSITION_GROUP_VIP;
  720.         $profile->isStandard false !== array_search(
  721.                 $row['adboard_placement_type'],
  722.                 [
  723.                     AdBoardPlacement::POSITION_GROUP_STANDARD_APPROVEDAdBoardPlacement::POSITION_GROUP_STANDARD,
  724.                     AdBoardPlacement::POSITION_GROUP_WITHOUT_OWNER_APPROVEDAdBoardPlacement::POSITION_GROUP_WITHOUT_OWNER
  725.                 ]
  726.             );
  727.         $profile->position $row['adboard_placement_position'];
  728.         $profile->active null !== $row['adboard_placement_type'] || $row['has_top_placement'];
  729.         $profile->hidden $row['has_placement_hiding'] == true;
  730.         $profile->personParameters = new ProfilePersonParametersReadModel();
  731.         $profile->personParameters->age $row['person_age'];
  732.         $profile->personParameters->height $row['person_height'];
  733.         $profile->personParameters->weight $row['person_weight'];
  734.         $profile->personParameters->breastSize $row['person_breast_size'];
  735.         $profile->personParameters->bodyType $row['person_body_type'];
  736.         $profile->personParameters->hairColor $row['person_hair_color'];
  737.         $profile->personParameters->privateHaircut $row['person_private_haircut'];
  738.         $profile->personParameters->nationality $row['person_nationality'];
  739.         $profile->personParameters->hasTattoo $row['person_has_tattoo'];
  740.         $profile->personParameters->hasPiercing $row['person_has_piercing'];
  741.         $profile->stations = [];
  742.         $profile->districts = [];
  743.         $profile->counties = [];
  744.         foreach ($stations as $station) {
  745.             if ($profile->id !== $station['profile_id'])
  746.                 continue;
  747.             $profileStation $profile->stations[$station['id']] ?? new StationReadModel($station['id'], $station['uriIdentity'], $station['name'], []);
  748.             if (null !== $station['line_name']) {
  749.                 $profileStation->lines[] = new StationLineReadModel($station['line_name'], $station['line_color']);
  750.             }
  751.             $profile->stations[$station['id']] = $profileStation;
  752.             if (array_key_exists($station['district_id'] ?? 0$districts) && !array_key_exists($station['district_id'], $profile->districts)) {
  753.                 $profile->districts[$station['district_id']] = $districts[$station['district_id']];
  754.             }
  755.         }
  756.         $primaryId = (int)$row['primary_station_id'];
  757.         if (!empty($profile->stations)) {
  758.             uasort($profile->stations, function (StationReadModel $aStationReadModel $b) use ($primaryId) {
  759.                 $aPrimary $a->id === $primaryId;
  760.                 $bPrimary $b->id === $primaryId;
  761.                 if ($aPrimary !== $bPrimary) {
  762.                     return $aPrimary ? -1;
  763.                 }
  764.                 return strnatcasecmp($a->name$b->name);
  765.             });
  766.         }
  767.         $profile->providedServices = [];
  768.         foreach ($services as $service) {
  769.             if ($profile->id !== $service['profile_id'])
  770.                 continue;
  771.             $providedService $profile->providedServices[$service['id']] ?? new ProvidedServiceReadModel(
  772.                 $service['id'], $service['name'], $service['group'], $service['uriIdentity'],
  773.                 $service['condition'], $service['extra_charge'], $service['comment']
  774.             );
  775.             $profile->providedServices[$service['id']] = $providedService;
  776.         }
  777.         $profile->selfies $row['has_selfies'] ? [1] : [];
  778.         $profile->videos $row['has_videos'] ? [1] : [];
  779.         $avatar = [
  780.             'path' => $row['avatar_path'] ?? '',
  781.             'type' => $this->features->crop_avatar() ? Photo::TYPE_AVATAR Photo::TYPE_PHOTO
  782.         ];
  783.         if ($this->features->crop_avatar()) {
  784.             $profile->avatar $avatar;
  785.         } else {
  786.             $profile->mainPhoto $avatar;
  787.             $profile->photos = [];
  788.         }
  789.         $profile->comments $row['has_comments'] ? [1] : [];
  790.         $profile->apartmentsPricing = new ProfileApartmentPricingReadModel();
  791.         $profile->apartmentsPricing->oneHourPrice $row['apartments_one_hour_price'];
  792.         $profile->apartmentsPricing->twoHoursPrice $row['apartments_two_hours_price'];
  793.         $profile->apartmentsPricing->nightPrice $row['apartments_night_price'];
  794.         $profile->takeOutPricing = new ProfileTakeOutPricingReadModel();
  795.         $profile->takeOutPricing->oneHourPrice $row['take_out_one_hour_price'];
  796.         $profile->takeOutPricing->twoHoursPrice $row['take_out_two_hours_price'];
  797.         $profile->takeOutPricing->nightPrice $row['take_out_night_price'];
  798.         $profile->takeOutPricing->locations $row['take_out_locations'] ? array_map('intval'explode(','$row['take_out_locations'])) : [];
  799.         $profile->seo $row['seo'] ? json_decode($row['seo'], true) : null;
  800.         return $profile;
  801.     }
  802.     public function fetchMapProfilesByIds(ProfileIdINOrderedByINValues $specification): array
  803.     {
  804.         $ids implode(','$specification->getIds());
  805.         $mediaType $this->features->crop_avatar() ? Photo::TYPE_AVATAR Photo::TYPE_PHOTO;
  806.         $mediaIsMain $this->features->crop_avatar() ? 1;
  807.         $sql "
  808.             SELECT 
  809.                 p.id, p.uri_identity, p.map_latitude, p.map_longitude, p.phone_number, p.is_masseur, p.is_approved,
  810.                 p.person_age, p.person_breast_size, p.person_height, p.person_weight, pap.type as placement_type, p.primary_station_id,
  811.                 JSON_UNQUOTE(JSON_EXTRACT(p.name, '$.ru')) 
  812.                     as `name`,
  813.                 (SELECT path FROM profile_media_files pmf_avatar WHERE p.id = pmf_avatar.profile_id AND pmf_avatar.type = '{$mediaType}' AND pmf_avatar.is_main = {$mediaIsMain} LIMIT 1) 
  814.                     as `avatar_path`,
  815.                 p.apartments_one_hour_price, p.apartments_two_hours_price, p.apartments_night_price, p.take_out_one_hour_price, p.take_out_two_hours_price, p.take_out_night_price,
  816.                 GROUP_CONCAT(ps.station_id) as `stations`,
  817.                 GROUP_CONCAT(pps.service_id) as `services`,
  818.                 EXISTS(SELECT * FROM profile_comments pc WHERE p.id = pc.profile_id AND pc.deleted_at is NULL) 
  819.                     as `has_comments`,
  820.                 EXISTS(SELECT * FROM profile_media_files pmf_video WHERE p.id = pmf_video.profile_id AND pmf_video.type = 'video') 
  821.                     as `has_videos`,
  822.                 EXISTS(SELECT * FROM profile_media_files pmf_selfie WHERE p.id = pmf_selfie.profile_id AND pmf_selfie.type = 'selfie') 
  823.                     as `has_selfies`,
  824.                 EXISTS(SELECT * FROM profile_top_placements ptp WHERE p.id = ptp.profile_id AND (NOW() BETWEEN ptp.placed_at AND ptp.expires_at))
  825.                     as `has_top_placement`
  826.             FROM profiles `p`
  827.             LEFT JOIN profile_stations ps ON ps.profile_id = p.id
  828.             LEFT JOIN profile_provided_services pps ON pps.profile_id = p.id
  829.             LEFT JOIN profile_adboard_placements pap ON pap.profile_id = p.id
  830.             WHERE p.id IN ($ids)
  831.             GROUP BY p.id
  832.             "// AND p.map_latitude IS NOT NULL AND p.map_longitude IS NOT NULL; ORDER BY FIELD(p.id,$ids)
  833.         $result $this->getEntityManager()->getConnection()->executeQuery($sql);
  834.         $profiles $result->fetchAllAssociative();
  835.         $result array_map(function ($profile): ProfileMapReadModel {
  836.             return $this->hydrateMapProfileRow($profile);
  837.         }, $profiles);
  838.         return $result;
  839.     }
  840.     public function hydrateMapProfileRow(array $row): ProfileMapReadModel
  841.     {
  842.         $profile = new ProfileMapReadModel();
  843.         $profile->id $row['id'];
  844.         $profile->uriIdentity $row['uri_identity'];
  845.         $profile->name $row['name'];
  846.         $profile->phoneNumber $row['phone_number'];
  847.         $profile->avatar = ['path' => $row['avatar_path'] ?? '''type' => $this->features->crop_avatar() ? Photo::TYPE_AVATAR Photo::TYPE_PHOTO];
  848.         $profile->mapLatitude $row['map_latitude'];
  849.         $profile->mapLongitude $row['map_longitude'];
  850.         $profile->age $row['person_age'];
  851.         $profile->breastSize $row['person_breast_size'];
  852.         $profile->height $row['person_height'];
  853.         $profile->weight $row['person_weight'];
  854.         $profile->isMasseur $row['is_masseur'];
  855.         $profile->isApproved $row['is_approved'];
  856.         $profile->hasComments $row['has_comments'];
  857.         $profile->hasSelfies $row['has_selfies'];
  858.         $profile->hasVideos $row['has_videos'];
  859.         $profile->apartmentOneHourPrice $row['apartments_one_hour_price'];
  860.         $profile->apartmentTwoHoursPrice $row['apartments_two_hours_price'];
  861.         $profile->apartmentNightPrice $row['apartments_night_price'];
  862.         $profile->takeOutOneHourPrice $row['take_out_one_hour_price'];
  863.         $profile->takeOutTwoHoursPrice $row['take_out_two_hours_price'];
  864.         $profile->takeOutNightPrice $row['take_out_night_price'];
  865.         $profile->station $row['primary_station_id'] ?? ($row['stations'] ? explode(','$row['stations'])[0] : null);
  866.         $profile->services $row['services'] ? array_unique(explode(','$row['services'])) : [];
  867.         $profile->isPaid $row['placement_type'] >= AdBoardPlacement::POSITION_GROUP_STANDARD || $row['has_top_placement'] !== null;
  868. //        $prices = [ $row['apartments_one_hour_price'], $row['apartments_two_hours_price'], $row['apartments_night_price'],
  869. //            $row['take_out_one_hour_price'], $row['take_out_two_hours_price'], $row['take_out_night_price'] ];
  870. //        $prices = array_filter($prices, function($item) {
  871. //            return $item != null;
  872. //        });
  873. //        $profile->price = count($prices) ? min($prices) : null;
  874.         return $profile;
  875.     }
  876.     public function fetchAccountProfileListByIds(ProfileIdINOrderedByINValues $specification): array
  877.     {
  878.         $ids implode(','$specification->getIds());
  879.         $mediaType $this->features->crop_avatar() ? Photo::TYPE_AVATAR Photo::TYPE_PHOTO;
  880.         $mediaIsMain $this->features->crop_avatar() ? 1;
  881.         $sql "
  882.             SELECT 
  883.                 p.*, JSON_UNQUOTE(JSON_EXTRACT(p.name, '$.ru')) 
  884.                     as `name`, 
  885.                 JSON_UNQUOTE(JSON_EXTRACT(p.description, '$.ru')) 
  886.                     as `description`,
  887.                 (SELECT path FROM profile_media_files pmf_avatar WHERE p.id = pmf_avatar.profile_id AND pmf_avatar.type = '{$mediaType}' AND pmf_avatar.is_main = {$mediaIsMain} LIMIT 1) 
  888.                     as `avatar_path`,
  889.                 (SELECT type FROM profile_adboard_placements pap WHERE p.id = pap.profile_id LIMIT 1) 
  890.                     as `adboard_placement_type`,
  891.                 c.id 
  892.                     as `city_id`, 
  893.                 JSON_UNQUOTE(JSON_EXTRACT(c.name, '$.ru')) 
  894.                     as `city_name`, 
  895.                 c.uri_identity 
  896.                     as `city_uri_identity`,
  897.                 c.country_code 
  898.                     as `city_country_code`,
  899.                 EXISTS(SELECT * FROM profile_top_placements ptp WHERE p.id = ptp.profile_id AND (NOW() BETWEEN ptp.placed_at AND ptp.expires_at))
  900.                     as `has_top_placement`,
  901.                 EXISTS(SELECT * FROM placement_hidings ph WHERE p.id = ph.profile_id AND ph.entity_type = 'profile') 
  902.                     as `has_placement_hiding`,
  903.                 EXISTS(SELECT * FROM profile_comments pc WHERE p.id = pc.profile_id AND pc.deleted_at is NULL) 
  904.                     as `has_comments`,
  905.                 EXISTS(SELECT * FROM profile_media_files pmf_video WHERE p.id = pmf_video.profile_id AND pmf_video.type = 'video') 
  906.                     as `has_videos`,
  907.                 EXISTS(SELECT * FROM profile_media_files pmf_selfie WHERE p.id = pmf_selfie.profile_id AND pmf_selfie.type = 'selfie') 
  908.                     as `has_selfies`
  909.             FROM profiles `p`
  910.             JOIN cities `c` ON c.id = p.city_id 
  911.             WHERE p.id IN ($ids)
  912.             ORDER BY FIELD(p.id,$ids)";
  913.         $connection $this->getEntityManager()->getConnection();
  914.         $result $connection->executeQuery($sql);
  915.         $profiles $result->fetchAllAssociative();
  916.         $sql "SELECT 
  917.                     JSON_UNQUOTE(JSON_EXTRACT(cs.name, '$.ru')) 
  918.                         as `name`, 
  919.                     cs.uri_identity 
  920.                         as `uriIdentity`, 
  921.                     ps.profile_id
  922.                         as `profile_id`,
  923.                     cs.district_id, cs.county_id
  924.                 FROM profile_stations ps
  925.                 JOIN city_stations cs ON ps.station_id = cs.id                 
  926.                 WHERE ps.profile_id IN ($ids)";
  927.         $result $connection->executeQuery($sql);
  928.         $stations $result->fetchAllAssociative();
  929.         $districtIds array_unique(array_column($stations'district_id'));
  930.         $districts $this->districts->ofIds($districtIds);
  931.         $sql "SELECT 
  932.                     s.id 
  933.                         as `id`,
  934.                     JSON_UNQUOTE(JSON_EXTRACT(s.name, '$.ru')) 
  935.                         as `name`, 
  936.                     s.group 
  937.                         as `group`, 
  938.                     s.uri_identity 
  939.                         as `uriIdentity`,
  940.                     pps.profile_id
  941.                         as `profile_id`,
  942.                     pps.service_condition
  943.                         as `condition`,
  944.                     pps.extra_charge
  945.                         as `extra_charge`,
  946.                     pps.comment
  947.                         as `comment`
  948.                 FROM profile_provided_services pps
  949.                 JOIN services s ON pps.service_id = s.id 
  950.                 WHERE pps.profile_id IN ($ids)
  951.                 ORDER BY s.group ASC, s.sort ASC, s.id ASC";
  952.         $result $connection->executeQuery($sql);
  953.         $providedServices $result->fetchAllAssociative();
  954.         $result array_map(function ($profile) use ($stations$districts$providedServices): ProfileListingReadModel {
  955.             return $this->hydrateProfileRow2($profile$stations$districts$providedServices);
  956.         }, $profiles);
  957.         return $result;
  958.     }
  959.     public function getCommentedProfilesPaged(User $owner): ORMQueryResult
  960.     {
  961.         $qb $this->createQueryBuilder('profile')
  962.             ->join('profile.comments''comment')
  963.             ->andWhere('profile.owner = :owner')
  964.             ->setParameter('owner'$owner)
  965.             ->orderBy('comment.createdAt''DESC');
  966.         return new ORMQueryResult($qb);
  967.     }
  968.     /**
  969.      * @return ProfilePlacementPriceDetailReadModel[]
  970.      */
  971.     public function fetchOfOwnerPlacedPriceDetails(User $owner): array
  972.     {
  973.         $sql "
  974.             SELECT 
  975.                 p.id, p.is_approved, psp.price_amount
  976.             FROM profiles `p`
  977.             JOIN profile_adboard_placements pap ON pap.profile_id = p.id AND pap.placement_price_id IS NOT NULL
  978.             JOIN paid_service_prices psp ON pap.placement_price_id = psp.id
  979.             WHERE p.user_id = {$owner->getId()}
  980.         ";
  981.         $result $this->getEntityManager()->getConnection()->executeQuery($sql);
  982.         $profiles $result->fetchAllAssociative();
  983.         return array_map(function (array $row): ProfilePlacementPriceDetailReadModel {
  984.             return new ProfilePlacementPriceDetailReadModel(
  985.                 $row['id'], $row['is_approved'], $row['price_amount'] / 24
  986.             );
  987.         }, $profiles);
  988.     }
  989.     /**
  990.      * @return ProfilePlacementHidingDetailReadModel[]
  991.      */
  992.     public function fetchOfOwnerHiddenDetails(User $owner): array
  993.     {
  994.         $sql "
  995.             SELECT 
  996.                 p.id, p.is_approved
  997.             FROM profiles `p`
  998.             JOIN placement_hidings ph ON ph.profile_id = p.id
  999.             WHERE p.user_id = {$owner->getId()}
  1000.         ";
  1001.         $result $this->getEntityManager()->getConnection()->executeQuery($sql);
  1002.         $profiles $result->fetchAllAssociative();
  1003.         return array_map(function (array $row): ProfilePlacementHidingDetailReadModel {
  1004.             return new ProfilePlacementHidingDetailReadModel(
  1005.                 $row['id'], $row['is_approved'], true
  1006.             );
  1007.         }, $profiles);
  1008.     }
  1009.     protected function modifyListingQueryBuilder(QueryBuilder $qbstring $alias): void
  1010.     {
  1011.         $qb
  1012.             ->addSelect('city')
  1013.             ->addSelect('station')
  1014.             ->addSelect('photo')
  1015.             ->addSelect('video')
  1016.             ->addSelect('comment')
  1017.             ->addSelect('avatar')
  1018.             ->join(sprintf('%s.city'$alias), 'city');
  1019.         if (!in_array('station'$qb->getAllAliases()))
  1020.             $qb->leftJoin(sprintf('%s.stations'$alias), 'station');
  1021.         if (!in_array('photo'$qb->getAllAliases()))
  1022.             $qb->leftJoin(sprintf('%s.photos'$alias), 'photo');
  1023.         if (!in_array('video'$qb->getAllAliases()))
  1024.             $qb->leftJoin(sprintf('%s.videos'$alias), 'video');
  1025.         if (!in_array('avatar'$qb->getAllAliases()))
  1026.             $qb->leftJoin(sprintf('%s.avatar'$alias), 'avatar');
  1027.         if (!in_array('comment'$qb->getAllAliases()))
  1028.             $qb->leftJoin(sprintf('%s.comments'$alias), 'comment');
  1029.         $this->addFemaleGenderFilterToQb($qb$alias);
  1030.         //TODO убрать, если все ок
  1031.         //$this->excludeHavingPlacementHiding($qb, $alias);
  1032.         if (!in_array('profile_adboard_placement'$qb->getAllAliases())) {
  1033.             $qb
  1034.                 ->leftJoin(sprintf('%s.adBoardPlacement'$alias), 'profile_adboard_placement');
  1035.         }
  1036.         $qb->addSelect('profile_adboard_placement');
  1037.         if (!in_array('profile_top_placement'$qb->getAllAliases())) {
  1038.             $qb
  1039.                 ->leftJoin(sprintf('%s.topPlacements'$alias), 'profile_top_placement');
  1040.         }
  1041.         $qb->addSelect('profile_top_placement');
  1042.         //if($this->features->free_profiles()) {
  1043.         if (!in_array('placement_hiding'$qb->getAllAliases())) {
  1044.             $qb
  1045.                 ->leftJoin(sprintf('%s.placementHiding'$alias), 'placement_hiding');
  1046.         }
  1047.         $qb->addSelect('placement_hiding');
  1048.         //}
  1049.     }
  1050.     protected function addActiveFilterToQb(QueryBuilder $qbstring $dqlAlias)
  1051.     {
  1052.         if (!in_array('profile_adboard_placement'$qb->getAllAliases())) {
  1053.             $qb
  1054.                 ->join(sprintf('%s.adBoardPlacement'$dqlAlias), 'profile_adboard_placement');
  1055.         }
  1056.     }
  1057.     private function excludeHavingPlacementHiding(QueryBuilder $qb$alias): void
  1058.     {
  1059.         if ($this->features->free_profiles()) {
  1060. //            if (!in_array('placement_hiding', $qb->getAllAliases())) {
  1061. //                $qb
  1062. //                    ->leftJoin(sprintf('%s.placementHiding', $alias), 'placement_hiding')
  1063. //                    ->andWhere(sprintf('placement_hiding IS NULL'))
  1064. //                ;
  1065. //        }
  1066.             $sub = new QueryBuilder($qb->getEntityManager());
  1067.             $sub->select("exclude_hidden_placement_hiding");
  1068.             $sub->from($qb->getEntityManager()->getClassMetadata(PlacementHiding::class)->name"exclude_hidden_placement_hiding");
  1069.             $sub->andWhere(sprintf('exclude_hidden_placement_hiding.profile = %s'$alias));
  1070.             $qb->andWhere($qb->expr()->not($qb->expr()->exists($sub->getDQL())));
  1071.         }
  1072.     }
  1073. }