src/Repository/ProfileRepository.php line 202

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