<?php
namespace Aqarmap\Bundle\ListingBundle\Service;
use Aqarmap\Bundle\ListingBundle\Constant\ListingSections;
use Aqarmap\Bundle\ListingBundle\Entity\CompoundLocation;
use Aqarmap\Bundle\ListingBundle\Entity\CustomField;
use Aqarmap\Bundle\ListingBundle\Form\CompoundSearchFormType;
use Aqarmap\Bundle\ListingBundle\Form\QuickLeadType;
use Aqarmap\Bundle\ListingBundle\Repository\CompoundLocationRepository;
use Aqarmap\Bundle\ListingBundle\Repository\ListingRepository;
use Aqarmap\Bundle\ListingBundle\Repository\LocationRepository;
use Aqarmap\Bundle\ListingBundle\Repository\PropertyTypeRepository;
use Aqarmap\Bundle\ListingBundle\Repository\SectionRepository;
use Aqarmap\Bundle\MainBundle\Service\MobileDetectionService;
use Aqarmap\Bundle\MainBundle\Service\Setting;
use Aqarmap\Bundle\SearchBundle\Services\CompoundSearchService;
use Aqarmap\Bundle\UserBundle\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface;
use Predis\ClientInterface as RedisClient;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class CompoundService
{
public const CACHING_EXPIRATION_TIME = 86400;
public const FIRST_INDEX = 0;
public const FIRST_PAGE_PAGINATION = 1;
public const REDIRECT_HTTP_CODE = 301;
/**
* @var object
*/
private $redis;
private AdapterInterface $cache;
/**
* @var object
*/
private $serializer;
/**
* @var TranslatorInterface
*/
private $translator;
/**
* @var RequestStack
*/
private $request;
/** @var ListingRepository */
private $listingRepository;
/** @var CompoundLocationRepository */
private $compoundLocationRepository;
/** @var CompoundSearchFormTypeManager */
private $compoundSearchFormTypeManager;
/** @var PropertyTypeRepository */
private $propertyTypeRepository;
/** @var LocationRepository */
private $locationRepository;
/** @var RouterInterface */
private $router;
/** @var FormFactoryInterface */
private $formFactory;
/** @var CompoundSearchService */
private $compoundSearchService;
/** @var SectionRepository */
private $sectionRepository;
/** @var SectionService */
private $sectionService;
/** @var MobileDetectionService */
private $mobileDetectionService;
private $setting;
private $listingManager;
private EntityManagerInterface $entityManager;
public function __construct(
TranslatorInterface $translator,
RequestStack $request,
RedisClient $redis,
AdapterInterface $cache,
SerializerInterface $serializer,
LocationRepository $locationRepository,
PropertyTypeRepository $propertyTypeRepository,
CompoundSearchFormTypeManager $compoundSearchFormTypeManager,
CompoundLocationRepository $compoundLocationRepository,
ListingRepository $listingRepository,
RouterInterface $router,
FormFactoryInterface $formFactory,
SectionRepository $sectionRepository,
SectionService $sectionService,
Setting $setting,
MobileDetectionService $mobileDetectionService,
ListingManager $listingManager,
EntityManagerInterface $entityManager
) {
$this->redis = $redis;
$this->cache = $cache;
$this->serializer = $serializer;
$this->translator = $translator;
$this->request = $request->getCurrentRequest();
if ($this->request) {
$this->translator->setLocale($this->request->getLocale());
}
$this->locationRepository = $locationRepository;
$this->propertyTypeRepository = $propertyTypeRepository;
$this->listingRepository = $listingRepository;
$this->compoundLocationRepository = $compoundLocationRepository;
$this->compoundSearchFormTypeManager = $compoundSearchFormTypeManager;
$this->router = $router;
$this->formFactory = $formFactory;
$this->sectionRepository = $sectionRepository;
$this->sectionService = $sectionService;
$this->setting = $setting;
$this->mobileDetectionService = $mobileDetectionService;
$this->listingManager = $listingManager;
$this->entityManager = $entityManager;
}
/**
* create search form.
*
* @throws \Exception
*/
public function compoundSearchForm($actionRoute = 'compound_Search')
{
$action = $this->getRouter()->generate($actionRoute);
return $this->compoundSearchFormTypeManager
->setAction($action)
->setFormType(CompoundSearchFormType::class)
->setCompoundService($this)
->setMethodType('Post')
->setFinishType($this->getFinishTypesChoices())
->applyOptions()
->createForm();
}
public function getFinishTypesChoices(): array
{
$finishTypes = $this->entityManager->getRepository(CustomField::class)->findOneBy(['name' => 'finish-type']);
return $finishTypes ? $finishTypes->getOptions()['choices'] : [];
}
/**
* check if key exists
* return array value.
*
* @return array|mixed
*/
public function getParameter(array $criteria, $key)
{
return \array_key_exists($key, $criteria) ? $criteria[$key] : [];
}
/**
* Return string separated by comma.
*/
public function handleFormData($data)
{
if (!\is_array($data)) {
$data = json_decode($data);
}
return (\is_array($data) && !empty($data)) ? implode(',', $data) : $data;
}
/**
* Convert strings to array
* Convert the numeric array value to numbers
* Escape the string values.
*/
public function stringCriteriaToInteger($criteria)
{
foreach ($criteria as $key => $value) {
if ('' != $value) {
$value = (!\is_array($value) && 'keywordSearch' !== $key) ? explode(',', $value) : $value;
$criteria[$key] = is_numeric($value[self::FIRST_INDEX]) ? array_map('intval', $value) : $value;
} else {
unset($criteria[$key]);
}
}
return $criteria;
}
/**
* return cached compounds.
*/
public function findCachedCompounds()
{
return $this->getListingRepository()->findCompounds();
}
/**
* return cached compounds count.
*/
public function findCachedCompoundsCount()
{
return $this->getListingRepository()->findCompoundsCount();
}
/**
* return cached compounds count.
*/
public function findCachedCompoundsMaxFields()
{
return $this->getListingRepository()->getMaxCompoundsFields();
}
/**
* get cached compounds locations.
*/
public function getCachedCompoundLocations()
{
$results = $this->compoundLocationRepository->getCompoundLocationsList() ?? [];
$context = SerializationContext::create();
return $this->serializer->serialize($results, 'json', $context->setGroups(['Default', 'List']));
}
/**
* Get cached property types.
*/
public function getCachedPropertyTypes($locale = null)
{
$item = $this->cache->getItem($locale ?? $this->getLocale().'_propertyTypesList');
if (!$item->isHit()) {
$context = SerializationContext::create();
$result = $this->serializer->serialize(
$this->propertyTypeRepository->getPropertyTypesList(),
'json',
$context->setGroups(['CompoundSearch'])
);
$item->set($result);
$this->cache->save($item);
}
return $item->get();
}
/**
* @return array|RedirectResponse
*
* @throws \Exception
* @throws \Psr\Cache\InvalidArgumentException
*/
public function search(Request $request, ?CompoundLocation $location = null, ?User $user = null)
{
$criteria = $request->query->all();
$compoundSearchService = $this->getCompoundSearchService();
$criteria = $this->stringCriteriaToInteger($criteria);
// original criteria
$clonedCriteria = $criteria;
$locationTitle = null;
$locationType = null;
$checkedCompoundLocations = $this->getParameter($clonedCriteria, 'compoundLocations');
if ($location) {
$checkedCompoundLocations = $compoundSearchService->addCheckedLocations($checkedCompoundLocations, $location);
$criteria = $compoundSearchService->addLocationsToCriteria($criteria, $location);
$locationTitle = $location->getTitle();
$locationType = $location->getType();
$criteria['selectedCompoundLocation'] = $location->getId();
}
$compoundSearchForm = $compoundSearchService->setCompoundSearchFormData($this->compoundSearchForm('compound_Search'), $criteria);
$compoundSearchService->enableDeepSearch($criteria);
$results = $compoundSearchService->getCompounds($criteria, $request->query->get('page', self::FIRST_PAGE_PAGINATION));
if (!$location && !empty($criteria['compoundLocations'])) {
$params = $compoundSearchService->getRedirectUrlParams($criteria);
$params = array_filter(array_merge($request->query->all(), $params), 'strlen');
return $this->redirect(
$this->generateUrl('compound_search_with_location', $params),
self::REDIRECT_HTTP_CODE
);
}
if ($request->get('compoundLocations')) {
$canonicalUrl = preg_replace('/\?.*/', '', $request->getUri());
}
$firstRootLocation = null;
if (!empty($results['items']) && !$location && $results[0]) {
$firstRootLocation = $this->getLocationRepository()->getRootLocation($results[0]->getLocation(), $request->getLocale());
}
if ($results->getCurrentPageNumber() > $results->getPageCount() && !empty($results->getPageCount())) {
$request->query->remove('page');
return $this->redirect($this->generateUrl(
$request->attributes->get('_route'),
array_merge(
[
'location' => $location ? $location->getSlug() : null,
],
$request->query->all()
)
), self::REDIRECT_HTTP_CODE);
}
if (self::FIRST_PAGE_PAGINATION == $request->query->get('page')) {
$request->query->remove('page');
return $this->redirect($this->generateUrl(
$request->attributes->get('_route'),
array_merge(
[
'location' => $location ? $location->getSlug() : null,
],
$request->query->all()
)
), self::REDIRECT_HTTP_CODE);
}
$criteria['selectedParagraphId'] = (int) $request->get('custom_paragraph');
$compoundLocationsIds = explode(
',',
$this->getSettingValue('features', 'selected_shown_compound_locations_ids')
);
$compoundLocations = $this->getLocationRepository()->findById($compoundLocationsIds);
shuffle($compoundLocations);
$isNoIndexMetaTag = (bool) $request->get('noindex') || $compoundSearchService->isNoIndexMetaTag($request->query->all());
$isMobile = [
'isMobile' => false,
];
$mobileDetection = $this->mobileDetectionService->mobileDetection($request, $isMobile);
return [
'canonicalUrl' => $canonicalUrl ?? null,
'section' => $this->sectionService->getCachedSection(ListingSections::PROJECTS),
'location' => $location,
'locationTitle' => $locationTitle,
'locationType' => $locationType,
'compoundSearchForm' => $compoundSearchForm->createView(),
'subLinksCompoundLocations' => $compoundLocations,
'listings' => $results,
'listingsCount' => \count($results),
'customParagraph' => $compoundSearchService->getCustomParagraph($criteria),
'cachedCompoundsData' => [
'cachedCompoundsCount' => $this->findCachedCompoundsCount(),
'cachedCompoundsLocations' => $this->getCachedCompoundLocations(),
'cachedPropertyTypes' => $this->getCachedPropertyTypes(),
],
'checkedCompoundLocations' => $checkedCompoundLocations,
'checkedPropertyTypes' => $this->getParameter($clonedCriteria, 'propertyTypes'),
'subLinksSections' => $this->getSectionRepository()->getSearchableSection(),
'firstRootLocation' => $firstRootLocation,
'isNoIndexMetaTag' => $isNoIndexMetaTag,
'form' => $this->createQuickLeadForm('add_quick_lead'),
'isMobile' => $mobileDetection['isMobile'],
'criteria' => $criteria,
'liveListingsPerUser' => $this->listingManager->getLiveListingsCountPerUser($results->getItems()),
];
}
/**
* @return array|array[]
*/
public function prepareCriteria($compoundSearchForm, $request): array
{
return [
'compoundLocations' => $this->handleFormData($compoundSearchForm->get('compoundLocation')->getData()),
'priceLevels' => $this->handleFormData($compoundSearchForm->get('priceLevel')->getData()),
'compoundStatus' => $this->handleFormData($compoundSearchForm->get('compoundStatus')->getData()),
'paymentMethods' => $this->handleFormData($compoundSearchForm->get('paymentMethod')->getData()),
'keywords' => $this->handleFormData($compoundSearchForm->get('keyword')->getData()),
'finishTypes' => $this->handleFormData($compoundSearchForm->get('finishType')->getData()),
'propertyTypes' => $this->handleFormData($compoundSearchForm->get('propertyTypes')->getData()),
'developerExperience' => $this->handleFormData($compoundSearchForm->get('developerExperience')->getData()),
'deliveryYears' => $this->handleFormData($compoundSearchForm->get('deliveryYears')->getData()),
];
}
/**
* Get compound locations with listings count.
*
* @param string $locale
*
* @return string
*/
public function getCachedCompoundLocationsWithListingCount($locale)
{
$cacheKey = sprintf('%s_compoundLocationsWithListingCount', $locale);
$item = $this->cache->getItem($cacheKey);
if (!$item->isHit()) {
$results = [];
$compoundLocations = $this->compoundLocationRepository->getAllEnabled() ?? [];
foreach ($compoundLocations as $compoundLocation) {
$listingCount = $this->getListingRepository()->getListingsCountPerCompoundLocation($compoundLocation);
if ($listingCount) {
$results[] = $this->createCompoundLocationData($compoundLocation, $listingCount);
}
}
$context = SerializationContext::create();
$item->set($this->serializer->serialize($results, 'json', $context->setGroups(['Default', 'List'])));
$this->cache->save($item);
}
return $item->get();
}
/**
* Creates quick lead form.
*
* @throws \Exception
*/
public function createQuickLeadForm($actionRoute): FormView
{
return $this->formFactory()->create(QuickLeadType::class, null, [
'action' => $this->getRouter()->generate($actionRoute),
'method' => 'POST',
])->createView();
}
/**
* Create compound location data Array.
*/
private function createCompoundLocationData(CompoundLocation $compoundLocation, int $listingCount): array
{
return [
'title' => $compoundLocation->getTitle(),
'slug' => $compoundLocation->getSlug(),
'type' => $compoundLocation->getType(),
'listingsCount' => $listingCount,
];
}
/**
* Get Current locale.
*
* @return string
*/
private function getLocale()
{
return $this->translator->getLocale();
}
public function getListingRepository()
{
return $this->listingRepository;
}
/**
* @throws \Exception
*/
private function formFactory()
{
return $this->formFactory;
}
/**
* @throws \Exception
*/
private function getRouter()
{
return $this->router;
}
/**
* @return object|\Predis\Client|null
*
* @throws \Exception
*/
private function getSncRedisContainer()
{
return $this->redis;
}
/**
* @throws \Exception
*/
private function getCompoundSearchService(): CompoundSearchService
{
return $this->compoundSearchService;
}
private function redirect($url, int $status = 302): RedirectResponse
{
return new RedirectResponse($url, $status);
}
private function getSectionRepository(): SectionRepository
{
return $this->sectionRepository;
}
private function getLocationRepository(): LocationRepository
{
return $this->locationRepository;
}
/**
* @return mixed|null
*
* @throws \Exception
*/
private function getSettingValue(string $group, string $name)
{
return $this->getSettingService()->getSetting($group, $name);
}
/**
* @throws \Exception
*/
private function getSettingService(): Setting
{
return $this->setting;
}
/**
* @param array $parameters
* @param int $referenceType
*
* @return string|void
*
* @throws \Exception
*/
private function generateUrl($route, $parameters = [], $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
return $this->getRouter()->generate($route, $parameters, $referenceType);
}
/**
* @required
*/
public function setCompoundSearchService(CompoundSearchService $compoundSearchService): void
{
$this->compoundSearchService = $compoundSearchService;
}
/**
* @param Request $request
*
* @return int|null
*/
public function getIdFromRequestOrLocation($request, $compoundlocation)
{
$location = $compoundlocation->getLocation();
$locationId = $location ? $location->getId() : null;
if ($request->request->has('compoundLocation')) {
$location = $request->request->get('compoundLocation');
$locationId = $location['location'] ?? null;
}
return $locationId;
}
}