src/Aqarmap/Bundle/ListingBundle/Repository/LocationRepository.php line 30

  1. <?php
  2. namespace Aqarmap\Bundle\ListingBundle\Repository;
  3. use Aqarmap\Bundle\ListingBundle\Constant\ListingCategories;
  4. use Aqarmap\Bundle\ListingBundle\Constant\ListingStatus;
  5. use Aqarmap\Bundle\ListingBundle\Entity\Location;
  6. use Aqarmap\Bundle\ListingBundle\Entity\PropertyType;
  7. use Aqarmap\Bundle\MainBundle\Constant\Locales;
  8. use Aqarmap\Bundle\SearchBundle\Entity\SearchResultCombinations;
  9. use Aqarmap\Bundle\UserBundle\Entity\User;
  10. use Doctrine\ORM\EntityManagerInterface;
  11. use Doctrine\ORM\Event\LifecycleEventArgs;
  12. use Doctrine\ORM\Events;
  13. use Doctrine\ORM\Internal\Hydration\IterableResult;
  14. use Doctrine\ORM\NonUniqueResultException;
  15. use Doctrine\ORM\NoResultException;
  16. use Doctrine\ORM\Query;
  17. use Doctrine\ORM\Query\Expr;
  18. use Doctrine\ORM\QueryBuilder;
  19. use Gedmo\Translatable\Query\TreeWalker\TranslationWalker;
  20. use Gedmo\Translatable\TranslatableListener;
  21. use Gedmo\Tree\Entity\Repository\NestedTreeRepository;
  22. class LocationRepository extends NestedTreeRepository
  23. {
  24. public const RECENT_SEARCHED_LOCATIONS_COUNT = 16;
  25. public const ONE_RESULT_NUMBER = 1;
  26. public function __construct(EntityManagerInterface $manager)
  27. {
  28. parent::__construct($manager, $manager->getClassMetadata(Location::class));
  29. }
  30. public function search(array $criteria)
  31. {
  32. $QueryBuilder = $this->createQueryBuilder('lo');
  33. if (isset($criteria['is_disabled'])) {
  34. $QueryBuilder
  35. ->andWhere($QueryBuilder->expr()
  36. ->eq('lo.disabled', ':disabled'))
  37. ->setParameter('disabled', $criteria['is_disabled']);
  38. }
  39. if (isset($criteria['level'])) {
  40. $QueryBuilder
  41. ->andWhere($QueryBuilder->expr()->eq('lo.level', ':level'))
  42. ->setParameter('level', $criteria['level']);
  43. }
  44. // Search
  45. if (!empty($criteria['search']) && !empty($criteria['q'])) {
  46. $criteria['q'] = trim((string) $criteria['q']);
  47. switch ($criteria['search']) {
  48. case 'location_id':
  49. $QueryBuilder
  50. ->andWhere($QueryBuilder->expr()->in('lo.id', ':location_id'))
  51. ->setParameter('location_id', $criteria['q']);
  52. break;
  53. case 'parent_id':
  54. $QueryBuilder
  55. ->andWhere($QueryBuilder->expr()->in('lo.parent', ':parent_id'))
  56. ->setParameter('parent_id', $criteria['q']);
  57. break;
  58. case 'title':
  59. $QueryBuilder
  60. ->join('lo.translations', 'lot', Expr\Join::WITH, 'lot.field = :title_field')
  61. ->andWhere($QueryBuilder->expr()->orX(
  62. $QueryBuilder->expr()->like('lot.content', ':title'),
  63. $QueryBuilder->expr()->like('lo.title', ':title')
  64. ))
  65. ->setParameter('title_field', 'title')
  66. ->setParameter('title', "{$criteria['q']}%");
  67. break;
  68. }
  69. }
  70. return $QueryBuilder;
  71. }
  72. /**
  73. * @param bool $searchableOnly
  74. *
  75. * @internal param null $locale
  76. *
  77. * @return array
  78. */
  79. public function getLocationsList($searchableOnly = false, $estimatable = false)
  80. {
  81. $QueryBuilder = $this->createQueryBuilder('lo');
  82. $QueryBuilder
  83. ->andWhere($QueryBuilder->expr()->isNull('lo.parent'));
  84. $QueryBuilder
  85. ->andWhere($QueryBuilder->expr()->eq('lo.disabled', ':disabled'))
  86. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  87. if ($searchableOnly) {
  88. $QueryBuilder
  89. ->andWhere($QueryBuilder->expr()->eq('lo.searchable', ':is_searchable'))
  90. ->setParameter('is_searchable', true, \PDO::PARAM_BOOL);
  91. }
  92. if ($estimatable) {
  93. $QueryBuilder
  94. ->andWhere($QueryBuilder->expr()->eq('lo.estimate', ':is_estimatable'))
  95. ->setParameter('is_estimatable', true, \PDO::PARAM_BOOL);
  96. }
  97. $query = $QueryBuilder->getQuery();
  98. $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class);
  99. return $query;
  100. }
  101. public function getLocationFlat($criteria)
  102. {
  103. $criteria = array_merge([
  104. 'searchable' => null,
  105. 'estimatable' => null,
  106. 'level' => null,
  107. ], $criteria);
  108. $QueryBuilder = $this->createQueryBuilder('lo');
  109. $QueryBuilder
  110. ->andWhere($QueryBuilder->expr()->eq('lo.disabled', ':disabled'))
  111. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  112. if ((int) $criteria['searchable']) {
  113. $QueryBuilder
  114. ->andWhere($QueryBuilder->expr()->eq('lo.searchable', ':is_searchable'))
  115. ->setParameter('is_searchable', true, \PDO::PARAM_BOOL);
  116. }
  117. if ((int) $criteria['estimatable']) {
  118. $QueryBuilder
  119. ->andWhere($QueryBuilder->expr()->eq('lo.estimate', ':is_estimatable'))
  120. ->setParameter('is_estimatable', true, \PDO::PARAM_BOOL);
  121. }
  122. if (null !== $criteria['level']) {
  123. $QueryBuilder
  124. ->andWhere($QueryBuilder->expr()->eq('lo.level', ':level'))
  125. ->setParameter('level', $criteria['level']);
  126. }
  127. $query = $QueryBuilder->getQuery();
  128. $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class);
  129. return $query;
  130. }
  131. /**
  132. * Get location children flat.
  133. *
  134. * @return Location|array
  135. */
  136. public function getLocationChildrenFlat(Location $location)
  137. {
  138. $QueryBuilder = $this->createQueryBuilder('lo');
  139. $QueryBuilder
  140. ->andWhere($QueryBuilder->expr()->eq('lo.parent', ':parent_id'))
  141. ->setParameter('parent_id', $location);
  142. return $QueryBuilder->getQuery();
  143. }
  144. /**
  145. * Get location children flat Array of Results.
  146. *
  147. * @param string $locale
  148. *
  149. * @return Location|array
  150. */
  151. public function getLocationChildrenFlatResults(Location $location, $locale = null)
  152. {
  153. $QueryBuilder = $this->createQueryBuilder('lo');
  154. $QueryBuilder
  155. ->andWhere($QueryBuilder->expr()->eq('lo.parent', ':parent_id'))
  156. ->setParameter('parent_id', $location);
  157. $query = $QueryBuilder
  158. ->getQuery()
  159. ->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class);
  160. if ($locale) {
  161. $query->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale);
  162. }
  163. return $query->getArrayResult();
  164. }
  165. /**
  166. * @internal param null $locale
  167. *
  168. * @return Query
  169. *
  170. * @internal param User $user
  171. * @internal param bool $iteration
  172. */
  173. public function getSectionLocationsWithLiveListings(Section $section)
  174. {
  175. $QueryBuilder = $this->createQueryBuilder('lo');
  176. $QueryBuilder
  177. ->leftJoin('lo.listings', 'lol');
  178. $QueryBuilder
  179. ->andWhere($QueryBuilder->expr()->eq('lo.disabled', ':disabled'))
  180. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  181. if ($section) {
  182. $QueryBuilder
  183. ->andWhere($QueryBuilder->expr()->eq('lol.section', ':section'))
  184. ->setParameter('section', $section);
  185. }
  186. $QueryBuilder
  187. ->andWhere($QueryBuilder->expr()->eq('lol.status', ':status'))
  188. ->setParameter('status', ListingStatus::LIVE);
  189. $query = $QueryBuilder->getQuery();
  190. $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class);
  191. return $query->getResult();
  192. }
  193. /**
  194. * @return array
  195. *
  196. * @internal param int $level
  197. * @internal param bool $searchableOnly
  198. * @internal param null $locale
  199. */
  200. public function getLeveledLocations(array $criteria)
  201. {
  202. $QueryBuilder = $this->createQueryBuilder('lo');
  203. $QueryBuilder
  204. ->andWhere($QueryBuilder->expr()->eq('lo.disabled', ':disabled'))
  205. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  206. if (!empty($criteria['parent'])) {
  207. if ('no-parent' == $criteria['parent']) {
  208. $QueryBuilder
  209. ->andWhere($QueryBuilder->expr()->isNull('lo.parent'));
  210. } else {
  211. $QueryBuilder
  212. ->andWhere($QueryBuilder->expr()->eq('lo.parent', ':parent'))
  213. ->setParameter('parent', $criteria['parent']);
  214. }
  215. }
  216. if (isset($criteria['level'])) {
  217. $QueryBuilder
  218. ->andWhere($QueryBuilder->expr()->lte('lo.level', ':level'))
  219. ->setParameter('level', $criteria['level']);
  220. }
  221. $QueryBuilder->orderBy('lo.listingsCounter', 'desc');
  222. $query = $QueryBuilder->getQuery();
  223. $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class);
  224. return $query->getResult();
  225. }
  226. public function getLeveledLocationsArray(array $criteria)
  227. {
  228. $queryBuilder = $this->createQueryBuilder('location');
  229. $queryBuilder
  230. ->select('location.id as id, COALESCE(translation.content, location.title) as title, location.slug as slug')
  231. ->leftJoin(
  232. 'location.translations',
  233. 'translation',
  234. Expr\Join::WITH,
  235. 'translation.locale = :locale and translation.field = :title and translation.object = location.id'
  236. )
  237. ->andWhere($queryBuilder->expr()->eq('location.disabled', ':disabled'))
  238. ->setParameter('locale', $criteria['locale'])
  239. ->setParameter('title', 'title')
  240. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  241. if (!empty($criteria['parent'])) {
  242. if ('no-parent' == $criteria['parent']) {
  243. $queryBuilder->andWhere($queryBuilder->expr()->isNull('location.parent'));
  244. } else {
  245. $queryBuilder
  246. ->andWhere($queryBuilder->expr()->eq('location.parent', ':parent'))
  247. ->setParameter('parent', $criteria['parent']);
  248. }
  249. }
  250. if (!empty($criteria['level'])) {
  251. $queryBuilder
  252. ->andWhere($queryBuilder->expr()->lte('location.level', ':level'))
  253. ->setParameter('level', $criteria['level']);
  254. }
  255. if (!empty($criteria['searchResultsCombinationMinimumCount'])) {
  256. $queryBuilder->join(
  257. SearchResultCombinations::class,
  258. 'searchResultCombinations',
  259. Expr\Join::WITH,
  260. 'searchResultCombinations.location = location.id'
  261. )
  262. ->andWhere($queryBuilder->expr()->gte('searchResultCombinations.searchResultCount', ':searchResultCount'))
  263. ->setParameter('searchResultCount', $criteria['searchResultsCombinationMinimumCount']);
  264. if (isset($criteria['propertyType'])) {
  265. $queryBuilder->andWhere($queryBuilder->expr()->eq('searchResultCombinations.propertyType', ':propertyType'))
  266. ->setParameter('propertyType', $criteria['propertyType']);
  267. }
  268. if (isset($criteria['section'])) {
  269. $queryBuilder->andWhere($queryBuilder->expr()->eq('searchResultCombinations.section', ':section'))
  270. ->setParameter('section', $criteria['section']);
  271. }
  272. if (!empty($criteria['searchResultsCombinationMax'])) {
  273. $queryBuilder->addSelect('MAX(searchResultCombinations.searchResultCount) as searchResultCount');
  274. } else {
  275. $queryBuilder->addSelect('searchResultCombinations.searchResultCount as searchResultCount');
  276. }
  277. $queryBuilder->orderBy('searchResultCombinations.searchResultCount', 'desc');
  278. } else {
  279. $queryBuilder->orderBy('location.listingsCounter', 'desc');
  280. }
  281. return $queryBuilder;
  282. }
  283. /**
  284. * @return Location
  285. */
  286. public function getSearchableOrNeighbourhoodNearestParent(Location $location)
  287. {
  288. $QueryBuilder = $this->createQueryBuilder('lo');
  289. $QueryBuilder
  290. ->andWhere('lo.root = :root')
  291. ->andWhere('lo.left <= :left')
  292. ->andWhere('lo.right >= :right')
  293. ->setParameter('left', $location->getLeft())
  294. ->setParameter('right', $location->getRight())
  295. ->setParameter('root', $location->getRoot());
  296. $QueryBuilder
  297. ->andWhere('lo.neighborhoodFilter = :is_neighbourhood OR lo.searchable = :is_searchable')
  298. ->setParameter('is_neighbourhood', true, \PDO::PARAM_BOOL)
  299. ->setParameter('is_searchable', true, \PDO::PARAM_BOOL);
  300. $QueryBuilder
  301. ->orderBy('lo.level', 'DESC')
  302. ->setMaxResults(1);
  303. $query = $QueryBuilder->getQuery();
  304. $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class);
  305. return $query->getOneOrNullResult();
  306. }
  307. // --------------------------------------------------------------------
  308. /**
  309. * Get location children.
  310. *
  311. * @return Location|array
  312. */
  313. public function getLocationChildren(Location $location)
  314. {
  315. $locationChildren = $this->children($location);
  316. $location = array_merge([$location], $locationChildren);
  317. return $location;
  318. }
  319. /**
  320. * @return array
  321. */
  322. public function getLocationChildrenIds(Location $location)
  323. {
  324. $locationChildrenIds = $this->childrenIds($location);
  325. $location = array_merge([$location->getId()], array_column(array_values($locationChildrenIds), 'id'));
  326. return $location;
  327. }
  328. /**
  329. * @return array
  330. */
  331. public function childrenIds(Location $location)
  332. {
  333. $queryBuilder = $this->createQueryBuilder('location');
  334. $queryBuilder->select('location.id');
  335. $queryBuilder->where($queryBuilder->expr()->lt('location.right', $location->getRight()));
  336. $queryBuilder->andWhere($queryBuilder->expr()->gt('location.left', $location->getLeft()));
  337. $queryBuilder->andWhere($queryBuilder->expr()->eq('location.root', ':rid'));
  338. $queryBuilder->setParameter('rid', $location->getRoot());
  339. return $queryBuilder
  340. ->getQuery()
  341. ->enableResultCache(1800, sprintf('location_children_ids_%d_%d', $location->getRight(), $location->getLeft()))
  342. ->getResult();
  343. }
  344. // --------------------------------------------------------------------
  345. /**
  346. * Get neighbourhood locations.
  347. *
  348. * @return Location|array
  349. */
  350. public function getNeighbourhoodLocations($criteria = [])
  351. {
  352. $QueryBuilder = $this->createQueryBuilder('lo');
  353. $QueryBuilder
  354. ->andWhere($QueryBuilder->expr()->eq('lo.neighborhoodFilter', ':is_true'))
  355. ->setParameter('is_true', true, \PDO::PARAM_BOOL);
  356. $QueryBuilder
  357. ->andWhere($QueryBuilder->expr()->eq('lo.disabled', ':disabled'))
  358. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  359. if (isset($criteria['level'])) {
  360. $QueryBuilder
  361. ->andWhere($QueryBuilder->expr()->eq('lo.level', ':level'))
  362. ->setParameter('level', $criteria['level']);
  363. }
  364. if (isset($criteria['maxLevel'])) {
  365. $QueryBuilder
  366. ->andWhere($QueryBuilder->expr()->lte('lo.level', ':maxLevel'))
  367. ->setParameter('maxLevel', $criteria['maxLevel']);
  368. }
  369. $query = $QueryBuilder->getQuery();
  370. return $query->getResult();
  371. }
  372. /**
  373. * Get neighbourhood sub-locations.
  374. *
  375. * @return Location|array
  376. */
  377. public function getNeighbourhoodSubLocations(Location $location, PropertyType $propertyType)
  378. {
  379. $QueryBuilder = $this->createQueryBuilder('lo')
  380. ->join('lo.locationStatistics', 'location_statistics', Expr\Join::LEFT_JOIN);
  381. $QueryBuilder
  382. ->andWhere($QueryBuilder->expr()->eq('lo.parent', ':location'))
  383. ->setParameter('location', $location);
  384. $QueryBuilder
  385. ->andWhere($QueryBuilder->expr()->eq('lo.disabled', ':disabled'))
  386. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  387. $QueryBuilder
  388. ->andWhere($QueryBuilder->expr()->eq('location_statistics.propertyType', ':property_type'))
  389. ->setParameter('property_type', $propertyType);
  390. $QueryBuilder
  391. ->andWhere($QueryBuilder->expr()->eq('lo.neighborhoodFilter', ':is_true'))
  392. ->setParameter('is_true', true, \PDO::PARAM_BOOL);
  393. $QueryBuilder
  394. ->andWhere($QueryBuilder->expr()->isNotNull('location_statistics.avgPrice'));
  395. $QueryBuilder
  396. ->andWhere($QueryBuilder->expr()->neq('location_statistics.avgPrice', 0));
  397. $QueryBuilder
  398. ->orderBy('location_statistics.avgPrice', 'DESC');
  399. $query = $QueryBuilder->getQuery();
  400. return $query->getResult();
  401. }
  402. /**
  403. * Get neighbourhood Compounds.
  404. *
  405. * @return Location|array
  406. */
  407. public function getNeighbourhoodCompounds($locations = null, $state = null)
  408. {
  409. $QueryBuilder = $this->createQueryBuilder('lo')
  410. ->join('lo.locationCompound', 'lc', Expr\Join::INNER_JOIN);
  411. $QueryBuilder
  412. ->andWhere($QueryBuilder->expr()->eq('lo.compoundFilter', ':is_true'))
  413. ->setParameter('is_true', true, \PDO::PARAM_BOOL);
  414. if ($locations) {
  415. $QueryBuilder
  416. ->andWhere($QueryBuilder->expr()->orX(
  417. $QueryBuilder->expr()->in('lo.parent', ':locations')
  418. ))
  419. ->setParameter('locations', $locations);
  420. }
  421. if ($state) {
  422. $QueryBuilder
  423. ->andWhere($QueryBuilder->expr()->eq('lc.state', ':state'))
  424. ->setParameter('state', $state);
  425. }
  426. $query = $QueryBuilder->getQuery();
  427. return $query->getResult();
  428. }
  429. /**
  430. * @return array
  431. */
  432. public function generateNearestLocations(Location $location, $distance)
  433. {
  434. $QueryBuilder = $this->createQueryBuilder('lo');
  435. $QueryBuilder
  436. ->andWhere($QueryBuilder->expr()->eq('lo.disabled', ':disabled'))
  437. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  438. $QueryBuilder
  439. ->andWhere($QueryBuilder->expr()->eq('lo.level', ':level'))
  440. ->setParameter('level', $location->getLevel());
  441. $QueryBuilder
  442. ->andWhere($QueryBuilder->expr()->neq('lo', ':location'))
  443. ->setParameter('location', $location);
  444. if ($location->getParent()) {
  445. $QueryBuilder
  446. ->andWhere($QueryBuilder->expr()->eq('lo.parent', ':parent'))
  447. ->setParameter('parent', $location->getParent());
  448. }
  449. $QueryBuilder = $this->getNearestLocationsFormulaQuery($QueryBuilder, $location, $distance);
  450. $query = $QueryBuilder->getQuery();
  451. return $query->getResult();
  452. }
  453. /**
  454. * @return Query
  455. */
  456. public function generateNearestLocationsQuery(array $criteria)
  457. {
  458. /** @var Location $location */
  459. $location = $criteria['location'];
  460. $listingQueryBuilder = $this->_em->createQueryBuilder();
  461. $listingQueryBuilder = $listingQueryBuilder
  462. ->select('MAX(li.id)')
  463. ->from(Listing::class, 'li')
  464. ->andWhere($listingQueryBuilder->expr()->eq('li.status', ListingStatus::LIVE))
  465. ->andWhere($listingQueryBuilder->expr()->neq('li.category', ListingCategories::PROJECTS));
  466. if (isset($criteria['propertyType']) && $criteria['propertyType']) {
  467. $listingQueryBuilder->andWhere($listingQueryBuilder->expr()->in('li.propertyType', $criteria['propertyType']));
  468. }
  469. if (isset($criteria['section']) && $criteria['section']) {
  470. $listingQueryBuilder->andWhere($listingQueryBuilder->expr()->eq('li.section', $criteria['section']));
  471. }
  472. $queryBuilder = $this->createQueryBuilder('lo');
  473. $queryBuilder->addSelect('ST_Distance_Sphere( Point(:longitude, :latitude), Point(lo.lon, lo.lat)) AS distance');
  474. $queryBuilder
  475. ->andWhere($queryBuilder->expr()->eq('lo.disabled', ':disabled'))
  476. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  477. $queryBuilder->innerJoin(
  478. 'lo.listings',
  479. 'listings',
  480. Expr\Join::WITH,
  481. $queryBuilder->expr()->eq(
  482. 'listings.id',
  483. sprintf('(%s)', $listingQueryBuilder
  484. ->andWhere($listingQueryBuilder->expr()->eq('li.location', 'lo.id'))
  485. ->setMaxResults(1)->getQuery()->getDQL())
  486. )
  487. );
  488. $queryBuilder
  489. ->andWhere($queryBuilder->expr()->neq('lo', ':location'))
  490. ->setParameter('location', $location);
  491. if (isset($criteria['excludedSubLocations']) && $criteria['excludedSubLocations']) {
  492. $queryBuilder
  493. ->andWhere($queryBuilder->expr()->notIn('lo', ':subLocations'))
  494. ->setParameter('subLocations', $this->getLocationChildrenIds($location));
  495. }
  496. $queryBuilder->andWhere($queryBuilder->expr()->neq('lo.listingsCounter', 0));
  497. $queryBuilder->andWhere($queryBuilder->expr()->lt('ABS(lo.lat)', 90));
  498. $queryBuilder->andWhere($queryBuilder->expr()->lt('ABS(lo.lon)', 180));
  499. $queryBuilder->andWhere($queryBuilder->expr()->isNotNull('lo.lat'));
  500. $queryBuilder->andWhere($queryBuilder->expr()->isNotNull('lo.lon'));
  501. $queryBuilder
  502. ->having('distance <= :maxDistance')
  503. ->setParameter('maxDistance', $criteria['maxDistance'] * 1000)
  504. ->setParameter('latitude', $location->getCenterLat())
  505. ->setParameter('longitude', $location->getCenterLng());
  506. $queryBuilder->orderBy('distance');
  507. return $queryBuilder->getQuery();
  508. }
  509. public function getNearestLocationsFormulaQuery(QueryBuilder $queryBuilder, Location $location, $distance): QueryBuilder
  510. {
  511. return $queryBuilder
  512. ->having('( 3959 * acos(cos(radians(:latitude))'.
  513. '* cos( radians( lo.lat ) )'.
  514. '* cos( radians( lo.lon )'.
  515. '- radians(:longitude) )'.
  516. '+ sin( radians(:latitude) )'.
  517. '* sin( radians( lo.lat ) ) ) ) < :distance')
  518. ->setParameter('distance', $distance)
  519. ->setParameter('latitude', $location->getCenterLat())
  520. ->setParameter('longitude', $location->getCenterLng());
  521. }
  522. /**
  523. * Get Nearest Compounds.
  524. *
  525. * @return Location|array
  526. */
  527. public function getNearestLocations(array $locations = [], bool $limit = true, int $max = 3)
  528. {
  529. $QueryBuilder = $this->createQueryBuilder('lo')
  530. ->join('lo.nearestLocations', 'nearest_locations', Expr\Join::INNER_JOIN);
  531. $QueryBuilder
  532. ->andWhere($QueryBuilder->expr()->eq('lo.searchable', ':is_searchable'))
  533. ->setParameter('is_searchable', true, \PDO::PARAM_BOOL);
  534. $QueryBuilder
  535. ->andWhere($QueryBuilder->expr()->in('nearest_locations.id', ':locations'))
  536. ->setParameter('locations', $locations);
  537. if ($limit) {
  538. $QueryBuilder->setMaxResults($max);
  539. }
  540. return $QueryBuilder->getQuery()->enableResultCache()->getResult();
  541. }
  542. /**
  543. * @internal param null $locale
  544. *
  545. * @param string $locale
  546. *
  547. * @return array
  548. */
  549. public function getAllSearchableLocationsList($levelLimit = null, $locale = null)
  550. {
  551. $QueryBuilder = $this->createQueryBuilder('lo');
  552. $QueryBuilder
  553. ->andWhere($QueryBuilder->expr()->eq('lo.disabled', ':disabled'))
  554. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  555. if ($levelLimit) {
  556. $QueryBuilder
  557. ->andWhere($QueryBuilder->expr()->lt('lo.level', ':level'))
  558. ->setParameter('level', $levelLimit);
  559. }
  560. $query = $QueryBuilder
  561. ->getQuery()
  562. ->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class);
  563. if ($locale) {
  564. $query->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale);
  565. }
  566. return $query->getResult();
  567. }
  568. /**
  569. * @return array
  570. */
  571. public function getAllLocationsCount()
  572. {
  573. $QueryBuilder = $this->createQueryBuilder('lo')->select('count(lo.id)');
  574. $QueryBuilder
  575. ->andWhere($QueryBuilder->expr()->eq('lo.disabled', ':disabled'))
  576. ->setParameter('disabled', false, \PDO::PARAM_BOOL);
  577. return $QueryBuilder->getQuery()->getSingleScalarResult();
  578. }
  579. /**
  580. * Get Nearest Neighbourhoods or Compounds.
  581. *
  582. * @return Location|array
  583. */
  584. public function getNearestNeighbourhoods(Location $location)
  585. {
  586. $QueryBuilder = $this->createQueryBuilder('lo')
  587. ->join('lo.nearestLocations', 'nearest_locations', Expr\Join::INNER_JOIN);
  588. if ($location->getCompoundFilter()) {
  589. $QueryBuilder
  590. ->andWhere($QueryBuilder->expr()->eq('lo.compoundFilter', ':is_true'))
  591. ->setParameter('is_true', true, \PDO::PARAM_BOOL);
  592. }
  593. if ($location->getNeighborhoodFilter()) {
  594. $QueryBuilder
  595. ->andWhere($QueryBuilder->expr()->eq('lo.neighborhoodFilter', ':is_true'))
  596. ->setParameter('is_true', true, \PDO::PARAM_BOOL);
  597. }
  598. $QueryBuilder
  599. ->andWhere($QueryBuilder->expr()->eq('nearest_locations.id', ':location'))
  600. ->setParameter('location', $location);
  601. $QueryBuilder->setMaxResults(3);
  602. $query = $QueryBuilder->getQuery();
  603. return $query->getResult();
  604. }
  605. /**
  606. * @param bool $forceLocale
  607. *
  608. * @return Location
  609. */
  610. public function getLocationLike($location, $forceLocale = true)
  611. {
  612. $queryBuilder = $this->createQueryBuilder('p')
  613. ->where('p.title LIKE :location')
  614. ->setParameter('location', '%'.$location.'%')
  615. ->getQuery()
  616. ->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class);
  617. if ($forceLocale) {
  618. $queryBuilder->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, 'en');
  619. }
  620. return $queryBuilder->setMaxResults(1)
  621. ->getOneOrNullResult();
  622. }
  623. /**
  624. * Get locations by keyword.
  625. *
  626. * @return Query
  627. */
  628. public function getLocationByKeyword(?string $keyword = null)
  629. {
  630. return $this->createQueryBuilder('l')
  631. ->select('l')
  632. ->where('FIND_IN_SET(:keyword, l.keyword) > 0')
  633. ->setParameter('keyword', $keyword)
  634. ->getQuery();
  635. }
  636. public function getWhereHasBumpableListings()
  637. {
  638. $queryBuilder = $this->createQueryBuilder('l');
  639. return $queryBuilder
  640. ->select('l, li, lf')
  641. ->join('l.listings', 'li', Expr\Join::WITH, 'l.id=li.location')
  642. ->join('li.listingFeatures', 'lf', Expr\Join::WITH, 'li.id=lf.listing')
  643. ->where($queryBuilder->expr()->eq('li.isBumped', ':isBumped'))
  644. ->setParameter(':isBumped', true, \PDO::PARAM_BOOL)
  645. ->andWhere($queryBuilder->expr()->isNull('lf.expiresAt'))
  646. ->andWhere('lf.bumpsCount < lf.bumpUpOccurrences')
  647. ->orWhere($queryBuilder->expr()->isNull('lf.bumpsCount'))
  648. ->andWhere($queryBuilder->expr()->lte('lf.nextBumpDate', ':nextBumpDate'))
  649. ->setParameter(':nextBumpDate', (new \DateTime())->setTime(0, 0, 0))
  650. ->orderBy('lf.nextBumpDate', 'DESC')
  651. ->addOrderBy('lf.id', 'DESC')
  652. ->addOrderBy('lf.listing', 'DESC')
  653. ->setMaxResults(100)
  654. ->getQuery()
  655. ->getResult();
  656. }
  657. /**
  658. * @param bool $status
  659. *
  660. * @throws \Doctrine\ORM\OptimisticLockException
  661. */
  662. public function changeStatus(Location $location, $status = true)
  663. {
  664. $queryBuilder = $this->createQueryBuilder('location');
  665. $locations = $this->getChildren($location);
  666. $locations[] = $location;
  667. $locationsIds = [];
  668. foreach ($locations as $childLocation) {
  669. $locationsIds[] = $childLocation->getId();
  670. }
  671. $disableStatus = $queryBuilder
  672. ->set('location.disabled', (int) $status)
  673. ->update()
  674. ->where($queryBuilder->expr()->in('location.id', $locationsIds))
  675. ->getQuery()
  676. ->execute();
  677. $em = $this->getEntityManager();
  678. foreach ($locations as $childLocation) {
  679. $childLocation->setDisabled((bool) $status);
  680. $em->getEventManager()->dispatchEvent(
  681. Events::postUpdate,
  682. new LifecycleEventArgs($childLocation, $em)
  683. );
  684. }
  685. $em->flush();
  686. return $disableStatus;
  687. }
  688. /**
  689. * Get Location full path.
  690. *
  691. * @param string $locale
  692. * @param string $separator
  693. *
  694. * @return string
  695. */
  696. public function getLocationFullPath(Location $location, $locale, $separator = ' / ')
  697. {
  698. $nodes = $this->getPathQueryBuilder($location)
  699. ->getQuery()
  700. ->setHint(
  701. Query::HINT_CUSTOM_OUTPUT_WALKER,
  702. TranslationWalker::class
  703. )->setHint(
  704. TranslatableListener::HINT_TRANSLATABLE_LOCALE,
  705. $locale
  706. )
  707. ->getArrayResult();
  708. $locationFullPath = $this->buildTree($nodes, [
  709. 'decorate' => true,
  710. 'rootOpen' => '',
  711. 'rootClose' => '',
  712. 'childOpen' => function($node) use ($nodes, $separator) {
  713. if ($node['id'] === $nodes[0]['id']) {
  714. return '';
  715. }
  716. return $separator;
  717. },
  718. 'childClose' => '',
  719. ]);
  720. return implode($separator, array_reverse(explode($separator, $locationFullPath)));
  721. }
  722. /**
  723. * Get Path of Location.
  724. *
  725. * @param string $locale
  726. *
  727. * @return array
  728. */
  729. public function getLocationPathNodes(Location $location, $locale = null)
  730. {
  731. return $this->getPathQueryBuilder($location)
  732. ->getQuery()
  733. ->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class)
  734. ->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale)
  735. ->getArrayResult();
  736. }
  737. public function getLocationPathNodesResult(Location $location, $locale = null)
  738. {
  739. return $this
  740. ->getPathQueryBuilder($location)
  741. ->getQuery()
  742. ->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class)
  743. ->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale)
  744. ->getResult();
  745. }
  746. public function getSameLevelLocations(Location $location, ?int $limit = null, array $excluded = [])
  747. {
  748. $queryBuilder = $this->createQueryBuilder('lo');
  749. $queryBuilder
  750. ->where($queryBuilder->expr()->eq('lo.level', ':level'))
  751. ->setParameter('level', $location->getLevel())
  752. ->andWhere($queryBuilder->expr()->notIn('lo.id', ':excluded'))
  753. ->setParameter('excluded', array_merge($excluded, [$location]));
  754. if ($limit) {
  755. $queryBuilder->setMaxResults($limit);
  756. }
  757. return $queryBuilder->getQuery()->getResult();
  758. }
  759. /**
  760. * Find locations by slug indexed by id.
  761. *
  762. * @return array
  763. */
  764. public function getBySlugs(array $slugs = [])
  765. {
  766. if (empty($slugs)) {
  767. return [];
  768. }
  769. $queryBuilder = $this->createQueryBuilder('location', 'location.id');
  770. return $queryBuilder
  771. ->andWhere($queryBuilder->expr()->in('location.slug', $slugs))
  772. ->getQuery()
  773. ->getResult();
  774. }
  775. /**
  776. * @param string $locale
  777. *
  778. * @return string
  779. */
  780. public function getRootLocation(Location $location, $locale = 'en')
  781. {
  782. $queryBuilder = $this->getPathQueryBuilder($location);
  783. $queryBuilder = $queryBuilder->setMaxResults(self::ONE_RESULT_NUMBER);
  784. $rootLocationQuery = $queryBuilder->getQuery()->setHint(
  785. Query::HINT_CUSTOM_OUTPUT_WALKER,
  786. TranslationWalker::class
  787. )->setHint(
  788. TranslatableListener::HINT_TRANSLATABLE_LOCALE,
  789. $locale
  790. );
  791. return $rootLocationQuery->getOneOrNullResult();
  792. }
  793. /**
  794. * @param int|string $idOrSlug
  795. *
  796. * @throws NonUniqueResultException
  797. */
  798. public function findOneByIdOrSlug($idOrSlug)
  799. {
  800. $queryBuilder = $this->createQueryBuilder('l');
  801. return $queryBuilder
  802. ->andWhere($queryBuilder->expr()->eq('l.slug', ':slug'))
  803. ->setParameter('slug', (string) $idOrSlug)
  804. ->orWhere($queryBuilder->expr()->eq('l.id', ':id'))
  805. ->setParameter('id', $idOrSlug)
  806. ->getQuery()
  807. ->getOneOrNullResult();
  808. }
  809. /**
  810. * @param int $limit
  811. *
  812. * @return array
  813. */
  814. public function getMostSearched($limit = self::RECENT_SEARCHED_LOCATIONS_COUNT)
  815. {
  816. $queryBuilder = $this->createQueryBuilder('l');
  817. return $queryBuilder
  818. ->addSelect('l')
  819. ->groupBy('l.id')
  820. ->orderBy('l.listingsCounter', 'desc')
  821. ->setMaxResults($limit)
  822. ->getQuery()
  823. ->getResult();
  824. }
  825. /**
  826. * Get locations count by keyword.
  827. *
  828. * @return Query
  829. */
  830. public function getLocationCountByKeyword(?string $keyword = null)
  831. {
  832. return $this->createQueryBuilder('l')
  833. ->select('COUNT(l.id)')
  834. ->where('FIND_IN_SET(:keyword, l.keyword) > 0')
  835. ->setParameter('keyword', $keyword)
  836. ->getQuery()
  837. ->setMaxResults(1);
  838. }
  839. /**
  840. * Get locations title by ids.
  841. *
  842. * @return Query
  843. */
  844. public function findTitleById(array $ids, string $locale = 'en')
  845. {
  846. $queryBuilder = $this->createQueryBuilder('l');
  847. return $queryBuilder->select('l.id, l.title')
  848. ->where($queryBuilder->expr()->in('l.id', ':ids'))
  849. ->setParameter('ids', $ids)
  850. ->getQuery()
  851. ->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class)
  852. ->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale);
  853. }
  854. public function getParentLocationByLevel(Location $location, int $level = 1, string $locale = 'en')
  855. {
  856. return $this
  857. ->getPathQueryBuilder($location)
  858. ->setFirstResult($level)
  859. ->setMaxResults(1)
  860. ->getQuery()
  861. ->setHint(
  862. Query::HINT_CUSTOM_OUTPUT_WALKER,
  863. TranslationWalker::class
  864. )
  865. ->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale)
  866. ->getArrayResult();
  867. }
  868. /**
  869. * @return Query
  870. */
  871. public function getParentsIds(Location $location)
  872. {
  873. $queryBuilder = $this->getParents($location);
  874. $queryBuilder->select('location.id');
  875. return $queryBuilder->getQuery();
  876. }
  877. /**
  878. * @return Query
  879. */
  880. public function getParentsBreadCrumbData(Location $location, string $locale = Locales::AR)
  881. {
  882. $queryBuilder = $this->getParents($location);
  883. $queryBuilder->select('location.id, location.title, location.slug');
  884. return $queryBuilder->getQuery()
  885. ->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class)
  886. ->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale)
  887. ;
  888. }
  889. /**
  890. * @return Location|null
  891. *
  892. * @throws NonUniqueResultException
  893. */
  894. public function getFirstRoot()
  895. {
  896. $queryBuilder = $this->createQueryBuilder('location');
  897. return $queryBuilder
  898. ->setMaxResults(1)
  899. ->getQuery()
  900. ->getOneOrNullResult();
  901. }
  902. /**
  903. * @return int|mixed|string
  904. */
  905. public function getPathNodeByLevel(Location $location, int $level = 1)
  906. {
  907. $queryBuilder = $this->getPathQueryBuilder($location);
  908. $queryBuilder->andWhere($queryBuilder->expr()->eq('node.level', ':level'))
  909. ->setParameter('level', $level);
  910. return $queryBuilder->getQuery()->getResult();
  911. }
  912. /**
  913. * @return array
  914. */
  915. public function getLevelOneLocations()
  916. {
  917. $queryBuilder = $this->getEntityManager()->createQueryBuilder();
  918. $queryBuilder->addSelect('l.id');
  919. $queryBuilder->addSelect('l.title');
  920. $queryBuilder->from(Location::class, 'l');
  921. $queryBuilder->andWhere($queryBuilder->expr()->eq('l.level', ':level'))
  922. ->setParameter('level', 1);
  923. $queryBuilder->andWhere($queryBuilder->expr()->eq('l.disabled', ':disabled'))
  924. ->setParameter('disabled', 0);
  925. $query = $queryBuilder
  926. ->getQuery()
  927. ->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, TranslationWalker::class)
  928. ->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, 'en');
  929. return $query->getArrayResult();
  930. }
  931. /**
  932. * @return QueryBuilder
  933. */
  934. public function getLevelOneLocationsQuery()
  935. {
  936. $locationQueryBuilder = $this->getEntityManager()->createQueryBuilder();
  937. $locationQueryBuilder
  938. ->addSelect('locations.title AS locationTitle')
  939. ->from(Location::class, 'locations');
  940. $locationQueryBuilder
  941. ->andWhere($locationQueryBuilder->expr()->eq('locations.disabled', ':disabled'))
  942. ->setParameter('disabled', false);
  943. $locationQueryBuilder
  944. ->andWhere($locationQueryBuilder->expr()->eq('locations.level', ':level'))
  945. ->setParameter('level', 1);
  946. return $locationQueryBuilder;
  947. }
  948. /**
  949. * @return array
  950. */
  951. public function getLocationChildrenDQL(array $aliases)
  952. {
  953. $leftAlias = $aliases['leftAlias'];
  954. $rightAlias = $aliases['rightAlias'];
  955. $rootAlias = $aliases['rootAlias'];
  956. $leftLocationDQL = $this->getEntityManager()->createQueryBuilder()
  957. ->select("$leftAlias.left")
  958. ->from(Location::class, "$leftAlias");
  959. $leftLocationDQL
  960. ->where($leftLocationDQL->expr()->eq("$leftAlias.id", 'locations.id'))
  961. ->getDQL();
  962. $rightLocationDQL = $this->getEntityManager()->createQueryBuilder()
  963. ->select("$rightAlias.right")
  964. ->from(Location::class, "$rightAlias");
  965. $rightLocationDQL
  966. ->where($rightLocationDQL->expr()->eq("$rightAlias.id", 'locations.id'))
  967. ->getDQL();
  968. $rootLocationDQL = $this->getEntityManager()->createQueryBuilder()
  969. ->select("$rootAlias.root")
  970. ->from(Location::class, "$rootAlias");
  971. $rootLocationDQL
  972. ->where($rootLocationDQL->expr()->eq("$rootAlias.id", 'locations.id'))
  973. ->getDQL();
  974. return [
  975. 'leftLocationDQL' => $leftLocationDQL,
  976. 'rightLocationDQL' => $rightLocationDQL,
  977. 'rootLocationDQL' => $rootLocationDQL,
  978. ];
  979. }
  980. public function getIterableLocations(): IterableResult
  981. {
  982. $qb = $this->createQueryBuilder('location');
  983. return $qb->select('location')
  984. ->where($qb->expr()->isNotNull('location.parent'))
  985. ->getQuery()->iterate();
  986. }
  987. /**
  988. * @return mixed[]
  989. *
  990. * @throws \Doctrine\DBAL\DBALException
  991. */
  992. public function getLocationParentsAndChildrenIds(Location $location)
  993. {
  994. $statement = "
  995. SELECT
  996. CASE WHEN locations.root = {$location->getRoot()} AND
  997. locations._right > {$location->getRight()} AND
  998. locations._left < {$location->getLeft()}
  999. THEN locations.id
  1000. ELSE NULL
  1001. END AS parents,
  1002. CASE WHEN locations.root = {$location->getRoot()} AND
  1003. locations._right < {$location->getRight()} AND
  1004. locations._left > {$location->getLeft()}
  1005. THEN locations.id
  1006. ELSE NULL
  1007. END AS children
  1008. FROM locations
  1009. WHERE (locations.root = {$location->getRoot()} AND
  1010. locations._right >= {$location->getRight()} AND
  1011. locations._left <= {$location->getLeft()}) OR
  1012. (locations.root = {$location->getRoot()} AND
  1013. locations._right < {$location->getRight()} AND
  1014. locations._left > {$location->getLeft()});";
  1015. $statement = $this->getEntityManager()->getConnection()->prepare($statement);
  1016. $statement->execute();
  1017. return $statement->fetchAll();
  1018. }
  1019. /**
  1020. * @return Query
  1021. */
  1022. public function getLocationsData(array $locations = [], array $selections = [], bool $translations = false)
  1023. {
  1024. $queryBuilder = $this->createQueryBuilder('location');
  1025. $selectStatement = '';
  1026. foreach ($selections as $index => $selection) {
  1027. $selectStatement .= "location.{$selection}";
  1028. if ($index !== \count($selections) - 1) {
  1029. $selectStatement .= ', ';
  1030. }
  1031. }
  1032. if ($translations) {
  1033. $queryBuilder
  1034. ->join('location.translations', 'translations');
  1035. $queryBuilder
  1036. ->andWhere($queryBuilder->expr()->eq('translations.field', ':field'))
  1037. ->setParameter(':field', 'title')
  1038. ->andWhere($queryBuilder->expr()->eq('translations.locale', ':locale'))
  1039. ->setParameter(':locale', Locales::EN);
  1040. $selectStatement .= ', translations.content, translations.locale';
  1041. }
  1042. $queryBuilder->select($selectStatement);
  1043. if (!empty($locations)) {
  1044. $queryBuilder->andWhere($queryBuilder->expr()->in('location.id', ':id'))
  1045. ->setParameter('id', $locations);
  1046. }
  1047. return $queryBuilder->getQuery();
  1048. }
  1049. /**
  1050. * @param array $selections = []
  1051. *
  1052. * @return Query
  1053. */
  1054. public function getParent(int $locationId, array $selections = [], ?string $locale = null)
  1055. {
  1056. $queryBuilder = $this->createQueryBuilder('location');
  1057. $selectStatement = '';
  1058. foreach ($selections as $index => $selection) {
  1059. $selectStatement .= "location.{$selection}";
  1060. if ($index !== \count($selections) - 1) {
  1061. $selectStatement .= ', ';
  1062. }
  1063. }
  1064. $queryBuilder->select($selectStatement)
  1065. ->andWhere($queryBuilder->expr()->eq('location.parent', ':parent'))
  1066. ->setParameter('id', $locationId);
  1067. $query = $queryBuilder->getQuery();
  1068. if ($locale) {
  1069. $query->setHint(
  1070. Query::HINT_CUSTOM_OUTPUT_WALKER,
  1071. TranslationWalker::class
  1072. );
  1073. $query->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale);
  1074. }
  1075. return $queryBuilder->getQuery();
  1076. }
  1077. public function getUserLiveLocationsWithParents(int $user)
  1078. {
  1079. $queryBuilder = $this->createQueryBuilder('location');
  1080. $queryBuilder->select('location.id as id, IDENTITY(location.parent) as parent, location.level as level');
  1081. $queryBuilder->join('location.listings', 'listings', Expr\Join::WITH, 'location.id=listings.location');
  1082. $queryBuilder->andWhere($queryBuilder->expr()->eq('listings.user', ':user'))
  1083. ->setParameter(':user', $user)
  1084. ->andWhere($queryBuilder->expr()->eq('listings.status', ':status'))
  1085. ->setParameter(':status', ListingStatus::LIVE);
  1086. return $queryBuilder->getQuery()->getResult();
  1087. }
  1088. /**
  1089. * @return array
  1090. */
  1091. public function getChildrenIds($right, $left, $root)
  1092. {
  1093. $queryBuilder = $this->createQueryBuilder('location');
  1094. $queryBuilder->select('location.id');
  1095. $queryBuilder->where($queryBuilder->expr()->lt('location.right', $right));
  1096. $queryBuilder->andWhere($queryBuilder->expr()->gt('location.left', $left));
  1097. $queryBuilder->andWhere($queryBuilder->expr()->eq('location.root', ':rid'));
  1098. $queryBuilder->setParameter('rid', $root);
  1099. return $queryBuilder->getQuery()->getResult();
  1100. }
  1101. /**
  1102. * @return Query
  1103. */
  1104. public function getChildrenObjects(Location $location, $excludedLocations = null)
  1105. {
  1106. $queryBuilder = $this->createQueryBuilder('location');
  1107. $queryBuilder->where($queryBuilder->expr()->lt('location.right', $location->getRight()));
  1108. $queryBuilder->andWhere($queryBuilder->expr()->gt('location.left', $location->getLeft()));
  1109. $queryBuilder->andWhere($queryBuilder->expr()->eq('location.root', ':rid'));
  1110. $queryBuilder->setParameter('rid', $location->getRoot());
  1111. $queryBuilder->andWhere($queryBuilder->expr()->neq('location', ':location'));
  1112. $queryBuilder->setParameter('location', $location);
  1113. $queryBuilder->andWhere($queryBuilder->expr()->notIn('location', $excludedLocations));
  1114. return $queryBuilder->getQuery();
  1115. }
  1116. /**
  1117. * @return QueryBuilder
  1118. */
  1119. public function getLocationsByLevel($criteria)
  1120. {
  1121. [
  1122. 'selectType' => $selectType,
  1123. 'lessThanOrEqualLevel' => $lessThanOrEqualLevel,
  1124. 'moreThanLevel' => $moreThanLevel,
  1125. ] = array_merge([
  1126. 'selectType' => null,
  1127. 'lessThanOrEqualLevel' => null,
  1128. 'moreThanLevel' => null,
  1129. ], $criteria);
  1130. $queryBuilder = $this->createQueryBuilder('locations');
  1131. if ('count' === $selectType) {
  1132. $queryBuilder->select('COUNT(locations.id)');
  1133. }
  1134. if ($lessThanOrEqualLevel) {
  1135. $queryBuilder
  1136. ->andWhere($queryBuilder->expr()->lte('locations.level', ':level'))
  1137. ->setParameter('level', $lessThanOrEqualLevel);
  1138. } elseif ($moreThanLevel) {
  1139. $queryBuilder
  1140. ->andWhere($queryBuilder->expr()->gt('locations.level', ':level'))
  1141. ->setParameter('level', $moreThanLevel);
  1142. }
  1143. return $queryBuilder;
  1144. }
  1145. /**
  1146. * Get all parents ids for location.
  1147. *
  1148. * @param int|Location $location
  1149. */
  1150. public function getAllLocationParentsIds($location, bool $withRoot = true): array
  1151. {
  1152. $location = \is_int($location) ? $this->find($location) : $location;
  1153. $queryBuilder = $this->getPathQueryBuilder($location);
  1154. $queryBuilder->select('node.id');
  1155. $queryBuilder->andWhere($queryBuilder->expr()->neq('node.id', ':location'));
  1156. $queryBuilder->setParameter('location', $location->getId());
  1157. if (false == $withRoot) {
  1158. $queryBuilder->andWhere($queryBuilder->expr()->isNotNull('node.parent'));
  1159. }
  1160. return $queryBuilder->getQuery()->getResult();
  1161. }
  1162. /**
  1163. * @return array
  1164. */
  1165. public function getMostLocationHasListingsForUser(User $user)
  1166. {
  1167. $queryBuilder = $this->createQueryBuilder('location');
  1168. $queryBuilder->join('location.listings', 'listing')->addSelect('listing');
  1169. $queryBuilder->andWhere($queryBuilder->expr()->eq('listing.status', ':status'));
  1170. $queryBuilder->andWhere($queryBuilder->expr()->eq('listing.user', ':user'));
  1171. $queryBuilder->setParameter('status', ListingStatus::LIVE);
  1172. $queryBuilder->setParameter('user', $user);
  1173. $queryBuilder->addSelect('COUNT(listing.id) AS listing_count');
  1174. $queryBuilder->groupBy('location.id');
  1175. $queryBuilder->orderBy('listing_count', 'DESC');
  1176. return $queryBuilder->setMaxResults(self::ONE_RESULT_NUMBER)->getQuery()->getOneOrNullResult();
  1177. }
  1178. /**
  1179. * @return array
  1180. */
  1181. public function getLocationTranslations($entity)
  1182. {
  1183. $queryBuilder = $this->createQueryBuilder('location');
  1184. $queryBuilder->select('location.alias as aliasAr', 'location.title as titleAr', 'location.description as descriptionAr', 'translation.field', 'translation.content');
  1185. $queryBuilder->join('location.translations', 'translation', Expr\Join::WITH, 'translation.object = location.id');
  1186. $queryBuilder->andWhere($queryBuilder->expr()->eq('translation.locale', ':locale'))->setParameter('locale', 'en');
  1187. $queryBuilder->andWhere($queryBuilder->expr()->eq('location.id', ':id'))->setParameter('id', $entity->getId());
  1188. $results = $queryBuilder->getQuery()->getArrayResult();
  1189. $data = [];
  1190. foreach ($results as $result) {
  1191. $data['titleAr'] = $result['titleAr'];
  1192. $data['aliasAr'] = $result['aliasAr'];
  1193. $data['descriptionAr'] = $result['descriptionAr'];
  1194. if ('title' == $result['field']) {
  1195. $data['titleEn'] = $result['content'];
  1196. }
  1197. if ('alias' == $result['field']) {
  1198. $data['aliasEn'] = $result['content'];
  1199. }
  1200. if ('description' == $result['field']) {
  1201. $data['descriptionEn'] = $result['content'];
  1202. }
  1203. }
  1204. $data['titleAr'] ??= '';
  1205. $data['titleEn'] ??= '';
  1206. $data['aliasAr'] ??= '';
  1207. $data['aliasEn'] ??= '';
  1208. $data['descriptionAr'] ??= '';
  1209. $data['descriptionEn'] ??= '';
  1210. return $data;
  1211. }
  1212. /**
  1213. * @return array
  1214. */
  1215. public function getCompoundLocations(array $criteria)
  1216. {
  1217. $queryBuilder = $this->createQueryBuilder('location');
  1218. $queryBuilder->select('location.id');
  1219. if (isset($criteria['title'])) {
  1220. $queryBuilder->andWhere($queryBuilder->expr()->like('location.title', ':title'))
  1221. ->setParameter('title', "%{$criteria['title']}%");
  1222. }
  1223. return $queryBuilder->getQuery()->getResult();
  1224. }
  1225. /**
  1226. * @return void
  1227. */
  1228. public function updateIsCompoundLocation(array $locationIDs)
  1229. {
  1230. $queryBuilder = $this->createQueryBuilder('location');
  1231. return $queryBuilder->update()
  1232. ->set('location.isCompoundLocation', ':status')
  1233. ->setParameter('status', 1)
  1234. ->where($queryBuilder->expr()->in('location', ':locations'))
  1235. ->setParameter('locations', $locationIDs)
  1236. ->getQuery()
  1237. ->execute();
  1238. }
  1239. public function getLocationsBySlugs(array $locationSlugs): array
  1240. {
  1241. $queryBuilder = $this->createQueryBuilder('l');
  1242. $query = $queryBuilder->select('l')
  1243. ->where($queryBuilder->expr()->in('l.slug', ':locationSlugs'))
  1244. ->setParameter('locationSlugs', $locationSlugs)
  1245. ->getQuery();
  1246. return $query->getResult();
  1247. }
  1248. public function getHomeCompoundLocations(int $limit): array
  1249. {
  1250. $queryBuilder = $this->createQueryBuilder('location');
  1251. $query = $queryBuilder
  1252. ->where($queryBuilder->expr()->in('location.homeFilter', ':homeFilter'))
  1253. ->setParameter('homeFilter', true, \PDO::PARAM_BOOL)
  1254. ->andWhere($queryBuilder->expr()->in('location.isCompoundLocation', ':isCompoundLocation'))
  1255. ->setParameter('isCompoundLocation', true, \PDO::PARAM_BOOL)
  1256. ->andWhere($queryBuilder->expr()->isNotNull('location.listing'))
  1257. ;
  1258. $queryBuilder->setMaxResults($limit);
  1259. return $query->getQuery()->enableResultCache(3600)->getResult();
  1260. }
  1261. /**
  1262. * @throws NonUniqueResultException
  1263. * @throws NoResultException
  1264. */
  1265. public function getListingsCount(Location $location): int
  1266. {
  1267. $queryBuilder = $this->createQueryBuilder('location');
  1268. $children = $this->getLocationChildrenIds($location);
  1269. $queryBuilder->select('COUNT(listing.id) as listingsCount')
  1270. ->join('location.listings', 'listing')
  1271. ->andWhere($queryBuilder->expr()->in('location.id', ':locationId'))
  1272. ->andWhere($queryBuilder->expr()->eq('listing.status', ':status'))
  1273. ->setParameters([
  1274. 'locationId' => $children,
  1275. 'status' => ListingStatus::LIVE,
  1276. ]);
  1277. return $queryBuilder->getQuery()->getSingleScalarResult();
  1278. }
  1279. /**
  1280. * @return QueryBuilder
  1281. */
  1282. private function getParents(Location $location)
  1283. {
  1284. $queryBuilder = $this->createQueryBuilder('location');
  1285. $queryBuilder->where($queryBuilder->expr()->lt('location.left', ':left'))
  1286. ->andWhere($queryBuilder->expr()->gt('location.right', ':right'))
  1287. ->andWhere($queryBuilder->expr()->eq('location.root', ':root'))
  1288. ->setParameter('left', $location->getLeft())
  1289. ->setParameter('right', $location->getRight())
  1290. ->setParameter('root', $location->getRoot());
  1291. return $queryBuilder;
  1292. }
  1293. /**
  1294. * Get seller listing locations filtered by nearest parent type.
  1295. */
  1296. public function getSellerLocationsIDsByType(User $user, string $locationType): array
  1297. {
  1298. $queryBuilder = $this->createQueryBuilder('location');
  1299. $queryBuilder
  1300. ->distinct()
  1301. ->join('location.listings', 'l')
  1302. ->where('l.user = :user')
  1303. ->setParameter('user', $user);
  1304. $locations = $queryBuilder->getQuery()->getResult();
  1305. $results = [];
  1306. foreach ($locations as $location) {
  1307. if (!$location instanceof Location) {
  1308. continue;
  1309. }
  1310. $parent = $location->getParentByType($locationType);
  1311. if ($parent) {
  1312. $results[$parent->getId()] = $parent;
  1313. }
  1314. }
  1315. return array_values($results);
  1316. }
  1317. }