src/Aqarmap/Bundle/ListingBundle/Service/CompoundService.php line 33

Open in your IDE?
  1. <?php
  2. namespace Aqarmap\Bundle\ListingBundle\Service;
  3. use Aqarmap\Bundle\ListingBundle\Constant\ListingSections;
  4. use Aqarmap\Bundle\ListingBundle\Entity\CompoundLocation;
  5. use Aqarmap\Bundle\ListingBundle\Entity\CustomField;
  6. use Aqarmap\Bundle\ListingBundle\Form\CompoundSearchFormType;
  7. use Aqarmap\Bundle\ListingBundle\Form\QuickLeadType;
  8. use Aqarmap\Bundle\ListingBundle\Repository\CompoundLocationRepository;
  9. use Aqarmap\Bundle\ListingBundle\Repository\ListingRepository;
  10. use Aqarmap\Bundle\ListingBundle\Repository\LocationRepository;
  11. use Aqarmap\Bundle\ListingBundle\Repository\PropertyTypeRepository;
  12. use Aqarmap\Bundle\ListingBundle\Repository\SectionRepository;
  13. use Aqarmap\Bundle\MainBundle\Service\MobileDetectionService;
  14. use Aqarmap\Bundle\MainBundle\Service\Setting;
  15. use Aqarmap\Bundle\SearchBundle\Services\CompoundSearchService;
  16. use Aqarmap\Bundle\UserBundle\Entity\User;
  17. use Doctrine\ORM\EntityManagerInterface;
  18. use JMS\Serializer\SerializationContext;
  19. use JMS\Serializer\SerializerInterface;
  20. use Predis\ClientInterface as RedisClient;
  21. use Symfony\Component\Cache\Adapter\AdapterInterface;
  22. use Symfony\Component\Form\FormFactoryInterface;
  23. use Symfony\Component\Form\FormView;
  24. use Symfony\Component\HttpFoundation\RedirectResponse;
  25. use Symfony\Component\HttpFoundation\Request;
  26. use Symfony\Component\HttpFoundation\RequestStack;
  27. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  28. use Symfony\Component\Routing\RouterInterface;
  29. use Symfony\Contracts\Translation\TranslatorInterface;
  30. class CompoundService
  31. {
  32.     public const CACHING_EXPIRATION_TIME 86400;
  33.     public const FIRST_INDEX 0;
  34.     public const FIRST_PAGE_PAGINATION 1;
  35.     public const REDIRECT_HTTP_CODE 301;
  36.     /**
  37.      * @var object
  38.      */
  39.     private $redis;
  40.     private AdapterInterface $cache;
  41.     /**
  42.      * @var object
  43.      */
  44.     private $serializer;
  45.     /**
  46.      * @var TranslatorInterface
  47.      */
  48.     private $translator;
  49.     /**
  50.      * @var RequestStack
  51.      */
  52.     private $request;
  53.     /** @var ListingRepository */
  54.     private $listingRepository;
  55.     /** @var CompoundLocationRepository */
  56.     private $compoundLocationRepository;
  57.     /** @var CompoundSearchFormTypeManager */
  58.     private $compoundSearchFormTypeManager;
  59.     /** @var PropertyTypeRepository */
  60.     private $propertyTypeRepository;
  61.     /** @var LocationRepository */
  62.     private $locationRepository;
  63.     /** @var RouterInterface */
  64.     private $router;
  65.     /** @var FormFactoryInterface */
  66.     private $formFactory;
  67.     /** @var CompoundSearchService */
  68.     private $compoundSearchService;
  69.     /** @var SectionRepository */
  70.     private $sectionRepository;
  71.     /** @var SectionService */
  72.     private $sectionService;
  73.     /** @var MobileDetectionService */
  74.     private $mobileDetectionService;
  75.     private $setting;
  76.     private $listingManager;
  77.     private EntityManagerInterface $entityManager;
  78.     public function __construct(
  79.         TranslatorInterface $translator,
  80.         RequestStack $request,
  81.         RedisClient $redis,
  82.         AdapterInterface $cache,
  83.         SerializerInterface $serializer,
  84.         LocationRepository $locationRepository,
  85.         PropertyTypeRepository $propertyTypeRepository,
  86.         CompoundSearchFormTypeManager $compoundSearchFormTypeManager,
  87.         CompoundLocationRepository $compoundLocationRepository,
  88.         ListingRepository $listingRepository,
  89.         RouterInterface $router,
  90.         FormFactoryInterface $formFactory,
  91.         SectionRepository $sectionRepository,
  92.         SectionService $sectionService,
  93.         Setting $setting,
  94.         MobileDetectionService $mobileDetectionService,
  95.         ListingManager $listingManager,
  96.         EntityManagerInterface $entityManager
  97.     ) {
  98.         $this->redis $redis;
  99.         $this->cache $cache;
  100.         $this->serializer $serializer;
  101.         $this->translator $translator;
  102.         $this->request $request->getCurrentRequest();
  103.         if ($this->request) {
  104.             $this->translator->setLocale($this->request->getLocale());
  105.         }
  106.         $this->locationRepository $locationRepository;
  107.         $this->propertyTypeRepository $propertyTypeRepository;
  108.         $this->listingRepository $listingRepository;
  109.         $this->compoundLocationRepository $compoundLocationRepository;
  110.         $this->compoundSearchFormTypeManager $compoundSearchFormTypeManager;
  111.         $this->router $router;
  112.         $this->formFactory $formFactory;
  113.         $this->sectionRepository $sectionRepository;
  114.         $this->sectionService $sectionService;
  115.         $this->setting $setting;
  116.         $this->mobileDetectionService $mobileDetectionService;
  117.         $this->listingManager $listingManager;
  118.         $this->entityManager $entityManager;
  119.     }
  120.     /**
  121.      * create search form.
  122.      *
  123.      * @throws \Exception
  124.      */
  125.     public function compoundSearchForm($actionRoute 'compound_Search')
  126.     {
  127.         $action $this->getRouter()->generate($actionRoute);
  128.         return $this->compoundSearchFormTypeManager
  129.             ->setAction($action)
  130.             ->setFormType(CompoundSearchFormType::class)
  131.             ->setCompoundService($this)
  132.             ->setMethodType('Post')
  133.             ->setFinishType($this->getFinishTypesChoices())
  134.             ->applyOptions()
  135.             ->createForm();
  136.     }
  137.     public function getFinishTypesChoices(): array
  138.     {
  139.         $finishTypes $this->entityManager->getRepository(CustomField::class)->findOneBy(['name' => 'finish-type']);
  140.         return $finishTypes $finishTypes->getOptions()['choices'] : [];
  141.     }
  142.     /**
  143.      * check if key exists
  144.      * return array value.
  145.      *
  146.      * @return array|mixed
  147.      */
  148.     public function getParameter(array $criteria$key)
  149.     {
  150.         return \array_key_exists($key$criteria) ? $criteria[$key] : [];
  151.     }
  152.     /**
  153.      * Return string separated by comma.
  154.      */
  155.     public function handleFormData($data)
  156.     {
  157.         if (!\is_array($data)) {
  158.             $data json_decode($data);
  159.         }
  160.         return (\is_array($data) && !empty($data)) ? implode(','$data) : $data;
  161.     }
  162.     /**
  163.      * Convert strings to array
  164.      * Convert the numeric array value to numbers
  165.      * Escape the string values.
  166.      */
  167.     public function stringCriteriaToInteger($criteria)
  168.     {
  169.         foreach ($criteria as $key => $value) {
  170.             if ('' != $value) {
  171.                 $value = (!\is_array($value) && 'keywordSearch' !== $key) ? explode(','$value) : $value;
  172.                 $criteria[$key] = is_numeric($value[self::FIRST_INDEX]) ? array_map('intval'$value) : $value;
  173.             } else {
  174.                 unset($criteria[$key]);
  175.             }
  176.         }
  177.         return $criteria;
  178.     }
  179.     /**
  180.      * return cached compounds.
  181.      */
  182.     public function findCachedCompounds()
  183.     {
  184.         return $this->getListingRepository()->findCompounds();
  185.     }
  186.     /**
  187.      * return cached compounds count.
  188.      */
  189.     public function findCachedCompoundsCount()
  190.     {
  191.         return $this->getListingRepository()->findCompoundsCount();
  192.     }
  193.     /**
  194.      * return cached compounds count.
  195.      */
  196.     public function findCachedCompoundsMaxFields()
  197.     {
  198.         return $this->getListingRepository()->getMaxCompoundsFields();
  199.     }
  200.     /**
  201.      * get cached compounds locations.
  202.      */
  203.     public function getCachedCompoundLocations()
  204.     {
  205.         $results $this->compoundLocationRepository->getCompoundLocationsList() ?? [];
  206.         $context SerializationContext::create();
  207.         return $this->serializer->serialize($results'json'$context->setGroups(['Default''List']));
  208.     }
  209.     /**
  210.      * Get cached property types.
  211.      */
  212.     public function getCachedPropertyTypes($locale null)
  213.     {
  214.         $item $this->cache->getItem($locale ?? $this->getLocale().'_propertyTypesList');
  215.         if (!$item->isHit()) {
  216.             $context SerializationContext::create();
  217.             $result $this->serializer->serialize(
  218.                 $this->propertyTypeRepository->getPropertyTypesList(),
  219.                 'json',
  220.                 $context->setGroups(['CompoundSearch'])
  221.             );
  222.             $item->set($result);
  223.             $this->cache->save($item);
  224.         }
  225.         return $item->get();
  226.     }
  227.     /**
  228.      * @return array|RedirectResponse
  229.      *
  230.      * @throws \Exception
  231.      * @throws \Psr\Cache\InvalidArgumentException
  232.      */
  233.     public function search(Request $request, ?CompoundLocation $location null, ?User $user null)
  234.     {
  235.         $criteria $request->query->all();
  236.         $compoundSearchService $this->getCompoundSearchService();
  237.         $criteria $this->stringCriteriaToInteger($criteria);
  238.         // original criteria
  239.         $clonedCriteria $criteria;
  240.         $locationTitle null;
  241.         $locationType null;
  242.         $checkedCompoundLocations $this->getParameter($clonedCriteria'compoundLocations');
  243.         if ($location) {
  244.             $checkedCompoundLocations $compoundSearchService->addCheckedLocations($checkedCompoundLocations$location);
  245.             $criteria $compoundSearchService->addLocationsToCriteria($criteria$location);
  246.             $locationTitle $location->getTitle();
  247.             $locationType $location->getType();
  248.             $criteria['selectedCompoundLocation'] = $location->getId();
  249.         }
  250.         $compoundSearchForm $compoundSearchService->setCompoundSearchFormData($this->compoundSearchForm('compound_Search'), $criteria);
  251.         $compoundSearchService->enableDeepSearch($criteria);
  252.         $results $compoundSearchService->getCompounds($criteria$request->query->get('page'self::FIRST_PAGE_PAGINATION));
  253.         if (!$location && !empty($criteria['compoundLocations'])) {
  254.             $params $compoundSearchService->getRedirectUrlParams($criteria);
  255.             $params array_filter(array_merge($request->query->all(), $params), 'strlen');
  256.             return $this->redirect(
  257.                 $this->generateUrl('compound_search_with_location'$params),
  258.                 self::REDIRECT_HTTP_CODE
  259.             );
  260.         }
  261.         if ($request->get('compoundLocations')) {
  262.             $canonicalUrl preg_replace('/\?.*/'''$request->getUri());
  263.         }
  264.         $firstRootLocation null;
  265.         if (!empty($results['items']) && !$location && $results[0]) {
  266.             $firstRootLocation $this->getLocationRepository()->getRootLocation($results[0]->getLocation(), $request->getLocale());
  267.         }
  268.         if ($results->getCurrentPageNumber() > $results->getPageCount() && !empty($results->getPageCount())) {
  269.             $request->query->remove('page');
  270.             return $this->redirect($this->generateUrl(
  271.                 $request->attributes->get('_route'),
  272.                 array_merge(
  273.                     [
  274.                         'location' => $location $location->getSlug() : null,
  275.                     ],
  276.                     $request->query->all()
  277.                 )
  278.             ), self::REDIRECT_HTTP_CODE);
  279.         }
  280.         if (self::FIRST_PAGE_PAGINATION == $request->query->get('page')) {
  281.             $request->query->remove('page');
  282.             return $this->redirect($this->generateUrl(
  283.                 $request->attributes->get('_route'),
  284.                 array_merge(
  285.                     [
  286.                         'location' => $location $location->getSlug() : null,
  287.                     ],
  288.                     $request->query->all()
  289.                 )
  290.             ), self::REDIRECT_HTTP_CODE);
  291.         }
  292.         $criteria['selectedParagraphId'] = (int) $request->get('custom_paragraph');
  293.         $compoundLocationsIds explode(
  294.             ',',
  295.             $this->getSettingValue('features''selected_shown_compound_locations_ids')
  296.         );
  297.         $compoundLocations $this->getLocationRepository()->findById($compoundLocationsIds);
  298.         shuffle($compoundLocations);
  299.         $isNoIndexMetaTag = (bool) $request->get('noindex') || $compoundSearchService->isNoIndexMetaTag($request->query->all());
  300.         $isMobile = [
  301.             'isMobile' => false,
  302.         ];
  303.         $mobileDetection $this->mobileDetectionService->mobileDetection($request$isMobile);
  304.         return [
  305.             'canonicalUrl' => $canonicalUrl ?? null,
  306.             'section' => $this->sectionService->getCachedSection(ListingSections::PROJECTS),
  307.             'location' => $location,
  308.             'locationTitle' => $locationTitle,
  309.             'locationType' => $locationType,
  310.             'compoundSearchForm' => $compoundSearchForm->createView(),
  311.             'subLinksCompoundLocations' => $compoundLocations,
  312.             'listings' => $results,
  313.             'listingsCount' => \count($results),
  314.             'customParagraph' => $compoundSearchService->getCustomParagraph($criteria),
  315.             'cachedCompoundsData' => [
  316.                 'cachedCompoundsCount' => $this->findCachedCompoundsCount(),
  317.                 'cachedCompoundsLocations' => $this->getCachedCompoundLocations(),
  318.                 'cachedPropertyTypes' => $this->getCachedPropertyTypes(),
  319.             ],
  320.             'checkedCompoundLocations' => $checkedCompoundLocations,
  321.             'checkedPropertyTypes' => $this->getParameter($clonedCriteria'propertyTypes'),
  322.             'subLinksSections' => $this->getSectionRepository()->getSearchableSection(),
  323.             'firstRootLocation' => $firstRootLocation,
  324.             'isNoIndexMetaTag' => $isNoIndexMetaTag,
  325.             'form' => $this->createQuickLeadForm('add_quick_lead'),
  326.             'isMobile' => $mobileDetection['isMobile'],
  327.             'criteria' => $criteria,
  328.             'liveListingsPerUser' => $this->listingManager->getLiveListingsCountPerUser($results->getItems()),
  329.         ];
  330.     }
  331.     /**
  332.      * @return array|array[]
  333.      */
  334.     public function prepareCriteria($compoundSearchForm$request): array
  335.     {
  336.         return [
  337.             'compoundLocations' => $this->handleFormData($compoundSearchForm->get('compoundLocation')->getData()),
  338.             'priceLevels' => $this->handleFormData($compoundSearchForm->get('priceLevel')->getData()),
  339.             'compoundStatus' => $this->handleFormData($compoundSearchForm->get('compoundStatus')->getData()),
  340.             'paymentMethods' => $this->handleFormData($compoundSearchForm->get('paymentMethod')->getData()),
  341.             'keywords' => $this->handleFormData($compoundSearchForm->get('keyword')->getData()),
  342.             'finishTypes' => $this->handleFormData($compoundSearchForm->get('finishType')->getData()),
  343.             'propertyTypes' => $this->handleFormData($compoundSearchForm->get('propertyTypes')->getData()),
  344.             'developerExperience' => $this->handleFormData($compoundSearchForm->get('developerExperience')->getData()),
  345.             'deliveryYears' => $this->handleFormData($compoundSearchForm->get('deliveryYears')->getData()),
  346.         ];
  347.     }
  348.     /**
  349.      * Get compound locations with listings count.
  350.      *
  351.      * @param string $locale
  352.      *
  353.      * @return string
  354.      */
  355.     public function getCachedCompoundLocationsWithListingCount($locale)
  356.     {
  357.         $cacheKey sprintf('%s_compoundLocationsWithListingCount'$locale);
  358.         $item $this->cache->getItem($cacheKey);
  359.         if (!$item->isHit()) {
  360.             $results = [];
  361.             $compoundLocations $this->compoundLocationRepository->getAllEnabled() ?? [];
  362.             foreach ($compoundLocations as $compoundLocation) {
  363.                 $listingCount $this->getListingRepository()->getListingsCountPerCompoundLocation($compoundLocation);
  364.                 if ($listingCount) {
  365.                     $results[] = $this->createCompoundLocationData($compoundLocation$listingCount);
  366.                 }
  367.             }
  368.             $context SerializationContext::create();
  369.             $item->set($this->serializer->serialize($results'json'$context->setGroups(['Default''List'])));
  370.             $this->cache->save($item);
  371.         }
  372.         return $item->get();
  373.     }
  374.     /**
  375.      * Creates quick lead form.
  376.      *
  377.      * @throws \Exception
  378.      */
  379.     public function createQuickLeadForm($actionRoute): FormView
  380.     {
  381.         return $this->formFactory()->create(QuickLeadType::class, null, [
  382.             'action' => $this->getRouter()->generate($actionRoute),
  383.             'method' => 'POST',
  384.         ])->createView();
  385.     }
  386.     /**
  387.      * Create compound location data Array.
  388.      */
  389.     private function createCompoundLocationData(CompoundLocation $compoundLocationint $listingCount): array
  390.     {
  391.         return [
  392.             'title' => $compoundLocation->getTitle(),
  393.             'slug' => $compoundLocation->getSlug(),
  394.             'type' => $compoundLocation->getType(),
  395.             'listingsCount' => $listingCount,
  396.         ];
  397.     }
  398.     /**
  399.      * Get Current locale.
  400.      *
  401.      * @return string
  402.      */
  403.     private function getLocale()
  404.     {
  405.         return $this->translator->getLocale();
  406.     }
  407.     public function getListingRepository()
  408.     {
  409.         return $this->listingRepository;
  410.     }
  411.     /**
  412.      * @throws \Exception
  413.      */
  414.     private function formFactory()
  415.     {
  416.         return $this->formFactory;
  417.     }
  418.     /**
  419.      * @throws \Exception
  420.      */
  421.     private function getRouter()
  422.     {
  423.         return $this->router;
  424.     }
  425.     /**
  426.      * @return object|\Predis\Client|null
  427.      *
  428.      * @throws \Exception
  429.      */
  430.     private function getSncRedisContainer()
  431.     {
  432.         return $this->redis;
  433.     }
  434.     /**
  435.      * @throws \Exception
  436.      */
  437.     private function getCompoundSearchService(): CompoundSearchService
  438.     {
  439.         return $this->compoundSearchService;
  440.     }
  441.     private function redirect($urlint $status 302): RedirectResponse
  442.     {
  443.         return new RedirectResponse($url$status);
  444.     }
  445.     private function getSectionRepository(): SectionRepository
  446.     {
  447.         return $this->sectionRepository;
  448.     }
  449.     private function getLocationRepository(): LocationRepository
  450.     {
  451.         return $this->locationRepository;
  452.     }
  453.     /**
  454.      * @return mixed|null
  455.      *
  456.      * @throws \Exception
  457.      */
  458.     private function getSettingValue(string $groupstring $name)
  459.     {
  460.         return $this->getSettingService()->getSetting($group$name);
  461.     }
  462.     /**
  463.      * @throws \Exception
  464.      */
  465.     private function getSettingService(): Setting
  466.     {
  467.         return $this->setting;
  468.     }
  469.     /**
  470.      * @param array $parameters
  471.      * @param int $referenceType
  472.      *
  473.      * @return string|void
  474.      *
  475.      * @throws \Exception
  476.      */
  477.     private function generateUrl($route$parameters = [], $referenceType UrlGeneratorInterface::ABSOLUTE_PATH)
  478.     {
  479.         return $this->getRouter()->generate($route$parameters$referenceType);
  480.     }
  481.     /**
  482.      * @required
  483.      */
  484.     public function setCompoundSearchService(CompoundSearchService $compoundSearchService): void
  485.     {
  486.         $this->compoundSearchService $compoundSearchService;
  487.     }
  488.     /**
  489.      * @param Request $request
  490.      *
  491.      * @return int|null
  492.      */
  493.     public function getIdFromRequestOrLocation($request$compoundlocation)
  494.     {
  495.         $location $compoundlocation->getLocation();
  496.         $locationId $location $location->getId() : null;
  497.         if ($request->request->has('compoundLocation')) {
  498.             $location $request->request->get('compoundLocation');
  499.             $locationId $location['location'] ?? null;
  500.         }
  501.         return $locationId;
  502.     }
  503. }