src/Repository/ProfileRepository.php line 147

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