src/Repository/ProfileRepository.php line 821

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