src/Aqarmap/Bundle/ListingBundle/Service/ListingManager.php line 2840

Open in your IDE?
  1. <?php
  2. namespace Aqarmap\Bundle\ListingBundle\Service;
  3. use App\Entity\Lead\Lead;
  4. use App\Message\Listing\SyncListingProjectMessage;
  5. use Aqarmap\Bundle\CreditBundle\Constant\CreditDescriptions;
  6. use Aqarmap\Bundle\CreditBundle\Constant\CreditStatus;
  7. use Aqarmap\Bundle\CreditBundle\Entity\Credit;
  8. use Aqarmap\Bundle\CreditBundle\Entity\Service;
  9. use Aqarmap\Bundle\CreditBundle\Services\CreditManager;
  10. use Aqarmap\Bundle\FeatureToggleBundle\Service\FeatureToggleManager;
  11. use Aqarmap\Bundle\ListingBundle\Constant\CreditDescriptionTypes;
  12. use Aqarmap\Bundle\ListingBundle\Constant\ImpressionTypes;
  13. use Aqarmap\Bundle\ListingBundle\Constant\InteractionTypes;
  14. use Aqarmap\Bundle\ListingBundle\Constant\ListingCategories;
  15. use Aqarmap\Bundle\ListingBundle\Constant\ListingCustomFields;
  16. use Aqarmap\Bundle\ListingBundle\Constant\ListingFeaturedStatus;
  17. use Aqarmap\Bundle\ListingBundle\Constant\ListingFeaturedTypes;
  18. use Aqarmap\Bundle\ListingBundle\Constant\ListingFeatures;
  19. use Aqarmap\Bundle\ListingBundle\Constant\ListingFeesTypes;
  20. use Aqarmap\Bundle\ListingBundle\Constant\ListingPendingStatus;
  21. use Aqarmap\Bundle\ListingBundle\Constant\ListingSortingOptions;
  22. use Aqarmap\Bundle\ListingBundle\Constant\ListingStatus;
  23. use Aqarmap\Bundle\ListingBundle\Constant\MarketPropertyTypes;
  24. use Aqarmap\Bundle\ListingBundle\Constant\PhotoTypes;
  25. use Aqarmap\Bundle\ListingBundle\Contracts\PhoneManagerInterface;
  26. use Aqarmap\Bundle\ListingBundle\Document\ListingActivityLog;
  27. use Aqarmap\Bundle\ListingBundle\Entity\CallRequest;
  28. use Aqarmap\Bundle\ListingBundle\Entity\Favourite;
  29. use Aqarmap\Bundle\ListingBundle\Entity\File;
  30. use Aqarmap\Bundle\ListingBundle\Entity\FreeListing;
  31. use Aqarmap\Bundle\ListingBundle\Entity\Listing;
  32. use Aqarmap\Bundle\ListingBundle\Entity\ListingAttribute;
  33. use Aqarmap\Bundle\ListingBundle\Entity\ListingFeature;
  34. use Aqarmap\Bundle\ListingBundle\Entity\ListingPhone;
  35. use Aqarmap\Bundle\ListingBundle\Entity\ListingPhoto;
  36. use Aqarmap\Bundle\ListingBundle\Entity\Location;
  37. use Aqarmap\Bundle\ListingBundle\Entity\Photo;
  38. use Aqarmap\Bundle\ListingBundle\Entity\PropertyType;
  39. use Aqarmap\Bundle\ListingBundle\Entity\Rejection;
  40. use Aqarmap\Bundle\ListingBundle\Entity\Section;
  41. use Aqarmap\Bundle\ListingBundle\Event\ListingEvent;
  42. use Aqarmap\Bundle\ListingBundle\Event\ListingFeatureEvent;
  43. use Aqarmap\Bundle\ListingBundle\Message\ParentListingUpdated;
  44. use Aqarmap\Bundle\ListingBundle\Model\Contracts\BumpUpModelInterface;
  45. use Aqarmap\Bundle\ListingBundle\Model\LeadModel;
  46. use Aqarmap\Bundle\ListingBundle\Model\ListingSortingOption;
  47. use Aqarmap\Bundle\ListingBundle\Model\ListingSortingValues;
  48. use Aqarmap\Bundle\ListingBundle\Model\NonListingLeadInterface;
  49. use Aqarmap\Bundle\ListingBundle\Repository\CallRequestLeadRepository;
  50. use Aqarmap\Bundle\ListingBundle\Repository\FavouriteRepository;
  51. use Aqarmap\Bundle\ListingBundle\Repository\FreeListingRepository;
  52. use Aqarmap\Bundle\ListingBundle\Repository\InteractionRepository;
  53. use Aqarmap\Bundle\ListingBundle\Repository\ListingImpressionRepository;
  54. use Aqarmap\Bundle\ListingBundle\Repository\ListingLeadRepository;
  55. use Aqarmap\Bundle\ListingBundle\Repository\ListingPhotoRepository;
  56. use Aqarmap\Bundle\ListingBundle\Repository\ListingRepository;
  57. use Aqarmap\Bundle\ListingBundle\Repository\LocationRepository;
  58. use Aqarmap\Bundle\ListingBundle\Repository\MessageLeadRepository;
  59. use Aqarmap\Bundle\ListingBundle\Repository\PhoneLeadRepository;
  60. use Aqarmap\Bundle\ListingBundle\Repository\PhoneRepository;
  61. use Aqarmap\Bundle\ListingBundle\Repository\PropertyTypeRepository;
  62. use Aqarmap\Bundle\ListingBundle\Repository\TopSellerLeadRepository;
  63. use Aqarmap\Bundle\ListingBundle\Service\Contracts\ListingFeatureServiceInterface;
  64. use Aqarmap\Bundle\ListingBundle\Service\V4\CostPerLeadService;
  65. use Aqarmap\Bundle\MainBundle\Constant\ActivityType;
  66. use Aqarmap\Bundle\MainBundle\Constant\CustomParagraphPlaceTypes;
  67. use Aqarmap\Bundle\MainBundle\Constant\Locales;
  68. use Aqarmap\Bundle\MainBundle\Constant\StreamingEventTopic;
  69. use Aqarmap\Bundle\MainBundle\Contract\EventStreamingClientFactoryInterface;
  70. use Aqarmap\Bundle\MainBundle\Contract\ProducerFactoryInterface;
  71. use Aqarmap\Bundle\MainBundle\Model\Listing\V4\ListingDataMapper;
  72. use Aqarmap\Bundle\MainBundle\Repository\CustomParagraphRepository;
  73. use Aqarmap\Bundle\MainBundle\Service\ActivityLogger;
  74. use Aqarmap\Bundle\MainBundle\Service\NumberToWord;
  75. use Aqarmap\Bundle\MainBundle\Service\Setting;
  76. use Aqarmap\Bundle\MessageBundle\Service\Composer;
  77. use Aqarmap\Bundle\NeighborhoodBundle\Entity\LocationStatistics;
  78. use Aqarmap\Bundle\NeighborhoodBundle\Service\LocationStatisticsManager;
  79. use Aqarmap\Bundle\NotificationBundle\Events\ListingNotificationEvent;
  80. use Aqarmap\Bundle\NotificationBundle\Types\ListingWasPublished;
  81. use Aqarmap\Bundle\NotificationBundle\Types\ListingWasRejected;
  82. use Aqarmap\Bundle\OTPBundle\Contract\OtpServiceInterface;
  83. use Aqarmap\Bundle\SearchBundle\Model\ListingSearchFaqsCriteria;
  84. use Aqarmap\Bundle\SearchBundle\Services\ListingFaqService;
  85. use Aqarmap\Bundle\UserBundle\Constant\UserServicesType;
  86. use Aqarmap\Bundle\UserBundle\Constant\UserTypes;
  87. use Aqarmap\Bundle\UserBundle\Entity\User;
  88. use Aqarmap\Bundle\UserBundle\Services\UserManager;
  89. use Aqarmap\Bundle\UserBundle\Services\UserServicesManager;
  90. use Doctrine\Common\Collections\ArrayCollection;
  91. use Doctrine\ODM\MongoDB\DocumentManager;
  92. use Doctrine\ORM\EntityManagerInterface;
  93. use Doctrine\ORM\EntityRepository;
  94. use Doctrine\ORM\Internal\Hydration\IterableResult;
  95. use Doctrine\ORM\OptimisticLockException;
  96. use Doctrine\ORM\ORMException;
  97. use Doctrine\ORM\QueryBuilder;
  98. use Gedmo\Translatable\TranslatableListener;
  99. use JMS\Serializer\SerializationContext;
  100. use JMS\Serializer\SerializerInterface;
  101. use Knp\Bundle\PaginatorBundle\Pagination\SlidingPagination;
  102. use Knp\Component\Pager\PaginatorInterface;
  103. use Psr\Log\LoggerInterface;
  104. use Symfony\Component\DependencyInjection\ContainerInterface;
  105. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  106. use Symfony\Component\HttpFoundation\Request;
  107. use Symfony\Component\HttpFoundation\RequestStack;
  108. use Symfony\Component\HttpFoundation\Response;
  109. use Symfony\Component\Messenger\MessageBusInterface;
  110. use Symfony\Component\Routing\RouterInterface;
  111. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  112. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  113. use Symfony\Component\Security\Core\User\UserInterface;
  114. use Symfony\Contracts\Cache\CacheInterface;
  115. use Symfony\Contracts\Cache\ItemInterface;
  116. use Symfony\Contracts\HttpClient\HttpClientInterface;
  117. use Symfony\Contracts\Translation\TranslatorInterface;
  118. class ListingManager
  119. {
  120.     public const ALLOWED_STATUS_FOR_APPROVAL = [
  121.         ListingStatus::PENDING,
  122.         ListingStatus::PENDING_PAYMENT,
  123.         ListingStatus::REJECTED,
  124.         ListingStatus::PENDING_PHOTOS,
  125.     ];
  126.     public const DECIMALS_PLACES_NUMBER 2;
  127.     public const MAP_STRING_DAYS_WITH_NUMBER = [
  128.         '7Days' => 7,
  129.         '30Days' => 30,
  130.         'ALLTIME' => null,
  131.         'ONLYLIVE' => null,
  132.     ];
  133.     public const TOP_PICKS_LABEL 'topPicks';
  134.     public const PERCENTAGE_FACTOR 100;
  135.     public const EXPIRATION_DATE_FORMAT 'Y-m-d';
  136.     public const ADD_TEN_YEARS '+10 years';
  137.     /**
  138.      * Month Range.
  139.      *
  140.      * @var int
  141.      */
  142.     public const MONTH_RANGE 4;
  143.     private const LISTING_COUNTER_CACHE_KEY 'listing_counter';
  144.     /** @var ContainerInterface */
  145.     public $container;
  146.     protected $dispatcher;
  147.     /** @var EntityManagerInterface */
  148.     protected $em;
  149.     /** @var DocumentManager */
  150.     protected $dm;
  151.     /** @var EntityRepository */
  152.     protected $repo;
  153.     /** @var ListingRuleMatcher */
  154.     protected $matcher;
  155.     /** @var CreditManager */
  156.     protected $creditManager;
  157.     /** @var ActivityLogger */
  158.     protected $activityLogger;
  159.     /** @var TranslatorInterface */
  160.     protected $translator;
  161.     /** @var Listing */
  162.     protected $listing;
  163.     /** @var string */
  164.     protected $class;
  165.     /** @var ListingFeatureServiceInterface */
  166.     protected $listingFeatureService;
  167.     /** @var BumpUpModelInterface */
  168.     protected $pumpUpModel;
  169.     /** @var Setting */
  170.     protected $settings;
  171.     /** @var LocationStatisticsManager */
  172.     protected $locationStatisticsManager;
  173.     /** @var AuthorizationChecker */
  174.     protected $authorizationChecker;
  175.     /** @var TokenStorageInterface */
  176.     protected $tokenStorage;
  177.     protected UserManager $userManager;
  178.     /** @var UserServicesManager */
  179.     protected $userServicesManager;
  180.     /** @var LoggerInterface */
  181.     protected $logger;
  182.     /** @var EventStreamingClientFactoryInterface */
  183.     protected $eventStreamingFactory;
  184.     /** @var FeatureToggleManager */
  185.     protected $featureToggleManager;
  186.     /** @var CompoundStatusLogService */
  187.     protected $compoundStatusLogService;
  188.     /** @var Composer */
  189.     protected $messageComposer;
  190.     /** @var OtpServiceInterface */
  191.     protected $otpService;
  192.     /** @var LeadService */
  193.     protected $leadService;
  194.     /** @var ListingRepository */
  195.     protected $listingRepository;
  196.     /** @var ListingPhotoRepository */
  197.     protected $listingPhotoRepository;
  198.     /** @var InteractionRepository */
  199.     protected $interactionRepository;
  200.     /** @var LocationRepository */
  201.     protected $locationRepository;
  202.     /** @var FavouriteRepository */
  203.     protected $favouriteRepository;
  204.     /** @var PhoneRepository */
  205.     protected $phoneRepository;
  206.     /** @var CallRequestLeadRepository */
  207.     protected $callRequestLeadRepository;
  208.     /** @var PhoneLeadRepository */
  209.     protected $phoneLeadRepository;
  210.     /** @var MessageLeadRepository */
  211.     protected $messageLeadRepository;
  212.     /** @var FreeListingRepository */
  213.     protected $freeListingRepository;
  214.     /** @var RequestStack */
  215.     protected $requestStack;
  216.     /** @var TranslatableListener */
  217.     protected $translatableListener;
  218.     /** @var CallRequestManager */
  219.     protected $callRequestManager;
  220.     /** @var PhoneManagerInterface */
  221.     protected $phoneManager;
  222.     /** @var RouterInterface */
  223.     protected $router;
  224.     /** @var NumberToWord */
  225.     protected $numberToWordService;
  226.     /** @var PaginatorInterface */
  227.     protected $paginator;
  228.     /** @var ProducerFactoryInterface */
  229.     protected $producerFactory;
  230.     /** @var array */
  231.     protected $locales;
  232.     /**
  233.      * @var CostPerLeadService
  234.      */
  235.     protected $costPerLeadService;
  236.     /**
  237.      * @var ListingImpressionRepository
  238.      */
  239.     protected $listingImpressionRepository;
  240.     /** @var ListingContactRateService */
  241.     protected $listingContactRateService;
  242.     private $cache;
  243.     /**
  244.      * @var TopSellerLeadRepository
  245.      */
  246.     protected $topSellerLeadRepository;
  247.     /**
  248.      * @var ListingLeadRepository
  249.      */
  250.     protected $listingLeadRepository;
  251.     protected HttpClientInterface $searchClient;
  252.     protected MessageBusInterface $messageBus;
  253.     private EntityManagerInterface $entityManager;
  254.     /**
  255.      * @var SerializerInterface
  256.      */
  257.     private $serializer;
  258.     /**
  259.      * @var CustomParagraphRepository
  260.      */
  261.     private $customParagraphRepository;
  262.     /**
  263.      * @var ListingFaqService
  264.      */
  265.     private $listingFaqService;
  266.     private PropertyTypeRepository $propertyTypeRepository;
  267.     public function __construct(
  268.         ContainerInterface $container,
  269.         EventDispatcherInterface $dispatcher,
  270.         EntityManagerInterface $em,
  271.         ListingRuleMatcher $matcher,
  272.         CreditManager $creditManager,
  273.         DocumentManager $dm,
  274.         ActivityLogger $activityLogger,
  275.         TranslatorInterface $translator,
  276.         ListingFeatureServiceInterface $listingFeatureService,
  277.         BumpUpModelInterface $pumpUpModel,
  278.         Setting $settings,
  279.         LocationStatisticsManager $locationStatisticsManager,
  280.         AuthorizationCheckerInterface $authorizationChecker,
  281.         TokenStorageInterface $tokenStorage,
  282.         UserServicesManager $userServicesManager,
  283.         LoggerInterface $logger,
  284.         EventStreamingClientFactoryInterface $eventStreamingFactory,
  285.         FeatureToggleManager $featureToggleManager,
  286.         CompoundStatusLogService $compoundStatusLogService,
  287.         Composer $messageComposer,
  288.         OtpServiceInterface $otpService,
  289.         ListingRepository $listingRepository,
  290.         ListingPhotoRepository $listingPhotoRepository,
  291.         InteractionRepository $interactionRepository,
  292.         LocationRepository $locationRepository,
  293.         PhoneRepository $phoneRepository,
  294.         FavouriteRepository $favouriteRepository,
  295.         CallRequestLeadRepository $callRequestLeadRepository,
  296.         PhoneLeadRepository $phoneLeadRepository,
  297.         MessageLeadRepository $messageLeadRepository,
  298.         FreeListingRepository $freeListingRepository,
  299.         RequestStack $requestStack,
  300.         TranslatableListener $translatableListener,
  301.         PhoneManagerInterface $phoneManager,
  302.         RouterInterface $router,
  303.         NumberToWord $numberToWordService,
  304.         PaginatorInterface $paginator,
  305.         ProducerFactoryInterface $producerFactory,
  306.         CostPerLeadService $costPerLeadService,
  307.         ListingImpressionRepository $listingImpressionRepository,
  308.         array $locales,
  309.         ListingContactRateService $listingContactRateService,
  310.         CacheInterface $cache,
  311.         ListingLeadRepository $listingLeadRepository,
  312.         TopSellerLeadRepository $topSellerLeadRepository,
  313.         HttpClientInterface $searchClient,
  314.         MessageBusInterface $messageBus,
  315.         EntityManagerInterface $entityManager,
  316.         SerializerInterface $serializer,
  317.         CustomParagraphRepository $customParagraphRepository,
  318.         ListingFaqService $listingFaqService,
  319.         PropertyTypeRepository $propertyTypeRepository
  320.     ) {
  321.         $this->container $container;
  322.         $this->dispatcher $dispatcher;
  323.         $this->em $em;
  324.         $this->dm $dm;
  325.         $this->matcher $matcher;
  326.         $this->creditManager $creditManager;
  327.         $this->class 'Aqarmap\Bundle\ListingBundle\Entity\Listing';
  328.         $this->listingRepository $listingRepository;
  329.         $this->activityLogger $activityLogger;
  330.         $this->translator $translator;
  331.         $this->listingFeatureService $listingFeatureService;
  332.         $this->pumpUpModel $pumpUpModel;
  333.         $this->settings $settings;
  334.         $this->locationStatisticsManager $locationStatisticsManager;
  335.         $this->authorizationChecker $authorizationChecker;
  336.         $this->tokenStorage $tokenStorage;
  337.         $this->userServicesManager $userServicesManager;
  338.         $this->logger $logger;
  339.         $this->eventStreamingFactory $eventStreamingFactory;
  340.         $this->featureToggleManager $featureToggleManager;
  341.         $this->compoundStatusLogService $compoundStatusLogService;
  342.         $this->messageComposer $messageComposer;
  343.         $this->otpService $otpService;
  344.         $this->listingPhotoRepository $listingPhotoRepository;
  345.         $this->interactionRepository $interactionRepository;
  346.         $this->locationRepository $locationRepository;
  347.         $this->requestStack $requestStack;
  348.         $this->favouriteRepository $favouriteRepository;
  349.         $this->phoneRepository $phoneRepository;
  350.         $this->callRequestLeadRepository $callRequestLeadRepository;
  351.         $this->phoneLeadRepository $phoneLeadRepository;
  352.         $this->messageLeadRepository $messageLeadRepository;
  353.         $this->freeListingRepository $freeListingRepository;
  354.         $this->translatableListener $translatableListener;
  355.         $this->phoneManager $phoneManager;
  356.         $this->router $router;
  357.         $this->numberToWordService $numberToWordService;
  358.         $this->paginator $paginator;
  359.         $this->producerFactory $producerFactory;
  360.         $this->costPerLeadService $costPerLeadService;
  361.         $this->locales $locales;
  362.         $this->listingImpressionRepository $listingImpressionRepository;
  363.         $this->listingContactRateService $listingContactRateService;
  364.         $this->cache $cache;
  365.         $this->listingLeadRepository $listingLeadRepository;
  366.         $this->topSellerLeadRepository $topSellerLeadRepository;
  367.         $this->searchClient $searchClient;
  368.         $this->messageBus $messageBus;
  369.         $this->entityManager $entityManager;
  370.         $this->serializer $serializer;
  371.         $this->customParagraphRepository $customParagraphRepository;
  372.         $this->listingFaqService $listingFaqService;
  373.         $this->propertyTypeRepository $propertyTypeRepository;
  374.     }
  375.     /**
  376.      * @return Listing
  377.      */
  378.     public function createListing()
  379.     {
  380.         $class $this->class;
  381.         return new $class();
  382.     }
  383.     /**
  384.      * @return ActivityLogger
  385.      */
  386.     public function getActivityLogger()
  387.     {
  388.         return $this->activityLogger;
  389.     }
  390.     /**
  391.      * @return Listing
  392.      */
  393.     public function createDraft(Listing $listing)
  394.     {
  395.         $listing->setStatus(ListingStatus::DRAFT);
  396.         $this->saveListing($listing);
  397.         return $listing;
  398.     }
  399.     /**
  400.      * @return Listing
  401.      */
  402.     public function changeLocation(Listing $listingLocation $location)
  403.     {
  404.         $listing->setLocation($location);
  405.         $this->saveListing($listing);
  406.         return $listing;
  407.     }
  408.     /**
  409.      * @return Listing
  410.      */
  411.     public function changePropertyType(Listing $listingPropertyType $propertyType)
  412.     {
  413.         $listing->setPropertyType($propertyType);
  414.         $this->saveListing($listing);
  415.         return $listing;
  416.     }
  417.     /**
  418.      * @return Listing
  419.      *
  420.      * @internal param PropertyType $propertyType
  421.      */
  422.     public function changeSection(Listing $listingSection $section)
  423.     {
  424.         // Getting listing rules and matching it with listing, and check if the listing can be Featured or not
  425.         $listingRule $this->getMatcher()->match($listing);
  426.         $listingOwner $listing->getUser();
  427.         $availableBalance $this->getCreditManager()->getBalance($listingOwner);
  428.         $sectionChangePeriod $this->settings->getSetting('general''change_section_period');
  429.         if ($listing->getPublishedAt() && $listing->getPublishedAt() < new \DateTime('-'.$sectionChangePeriod.' days')) {
  430.             throw new \RuntimeException('This listing published more than '.$sectionChangePeriod.' days ago, can not change section');
  431.         }
  432.         $listingFees 0;
  433.         $listingBalance 0;
  434.         foreach ($listing->getCurrentListingFeatures() as $listingFeature) {
  435.             $listingFeatureCredit $listingFeature->getCredit();
  436.             $listingBalance += abs($listingFeatureCredit->getAmount());
  437.             $listingFees += $listingRule[ListingFeesTypes::getLabel($listingFeature->getType())];
  438.         }
  439.         // If the listing owner doesn't have enough credits
  440.         if ($listingFees > ($availableBalance $listingBalance)) {
  441.             throw new \RuntimeException('There\'s no enough credits to make this listing featured.');
  442.         }
  443.         foreach ($listing->getCurrentListingFeatures() as $listingFeature) {
  444.             $this->cancelAllFeatures($listingFeature);
  445.         }
  446.         foreach ($listing->getCurrentListingFeatures() as $listingFeature) {
  447.             $this->ReCreateFeatures($listingFeature);
  448.         }
  449.         $listing->setSection($section);
  450.         $this->saveListing($listing);
  451.         return $listing;
  452.     }
  453.     /**
  454.      * cancel Features for listing.
  455.      *
  456.      * @param ListingFeature $listingFeature
  457.      */
  458.     public function cancelAllFeatures($listingFeature): void
  459.     {
  460.         $listingFeature->getCredit()->setStatus(CreditStatus::CANCELLED);
  461.         $listingFeature->getCredit()->setDescription($listingFeature->getCredit()->getDescription());
  462.         $this->em->persist($listingFeature->getCredit());
  463.         $this->em->flush($listingFeature->getCredit());
  464.     }
  465.     /**
  466.      * Recreate Features for listing.
  467.      *
  468.      * @param ListingFeature $listingFeature
  469.      */
  470.     public function ReCreateFeatures($listingFeature): void
  471.     {
  472.         $listingRule $this->getMatcher()->match($listingFeature->getListing());
  473.         $newCredits $this->getCreditManager()->deduction(
  474.             $listingFeature->getListing()->getUser(),
  475.             $listingRule[ListingFeesTypes::getLabel($listingFeature->getType())],
  476.             CreditDescriptionTypes::getLabel($listingFeature->getType()),
  477.             CreditStatus::SUCCESS,
  478.             false
  479.         );
  480.         foreach ($newCredits as $newCredit) {
  481.             $this->em->persist($newCredit);
  482.             $this->em->flush($newCredit);
  483.             $this->addFeature($listingFeature->getListing(), $listingFeature->getType(), $listingFeature->getExpiresAt(), $newCredit);
  484.             $listingFeature->setExpiresAt(new \DateTime());
  485.             $this->em->persist($listingFeature);
  486.             $this->em->flush($listingFeature);
  487.         }
  488.     }
  489.     /**
  490.      * Change listing area.
  491.      *
  492.      * @return Listing
  493.      */
  494.     public function changeArea(Listing $listing$area)
  495.     {
  496.         $listing->setArea($area);
  497.         $this->saveListing($listing);
  498.         return $listing;
  499.     }
  500.     /**
  501.      * Change listing payment method.
  502.      *
  503.      * @return Listing
  504.      */
  505.     public function changePaymentMethod(Listing $listing$paymentMethod)
  506.     {
  507.         $listing->setPaymentMethod($paymentMethod);
  508.         $this->saveListing($listing);
  509.         return $listing;
  510.     }
  511.     /**
  512.      * @param bool $flush
  513.      *
  514.      * @throws ORMException
  515.      * @throws OptimisticLockException
  516.      */
  517.     public function saveListing(Listing $listing$flush true$category null): void
  518.     {
  519.         $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.pre_save');
  520.         $listing->setPricePerMeter($listing->calculatePricePerMeter() ?? null);
  521.         $listing->generateCoordinates();
  522.         $listing $this->handleListingFeatureOnSave($listing);
  523.         $this->em->persist($listing);
  524.         false === $flush ?: $this->em->flush();
  525.         $this->setProjectUnitsOwner($listing);
  526.         $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.after_save');
  527.         if ($listing->getParent()) {
  528.             $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.project.re_calculate.min_price_area');
  529.             $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.project.calculate.average.pricePerMeter');
  530.         }
  531.         if (UserTypes::BUYER === $listing->getUser()->getUserType()) {
  532.             $this->getUserManagerService()->changeUserType($listing->getUser(), UserTypes::INDIVIDUAL);
  533.         }
  534.     }
  535.     /**
  536.      * Change listing status.
  537.      *
  538.      * @param bool $flush
  539.      * @param bool $force
  540.      */
  541.     public function changeStatus(Listing $listing$status$flush true$force false, array $rejectionReasons = []): void
  542.     {
  543.         $token $this->tokenStorage->getToken();
  544.         $isAdmin $token && ($token->getUser()->hasRole('ROLE_ADMIN') || $token->getUser()->hasRole('ROLE_SUPER_ADMIN'));
  545.         if (!$force && $listing->getStatus() === $status) {
  546.             return;
  547.         }
  548.         $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.pre_change_status');
  549.         if (
  550.             ListingStatus::LIVE == $listing->getStatus()
  551.             && ListingFeaturedTypes::NOT_FEATURED != $listing->getFeatured()
  552.         ) {
  553.             $this->pauseFeatured($listing);
  554.         } elseif (
  555.             $status
  556.             && ListingStatus::LIVE == $status
  557.             && ListingFeaturedTypes::NOT_FEATURED != $listing->getFeatured()
  558.         ) {
  559.             $this->unPauseFeatured($listing);
  560.         }
  561.         if (!\in_array($status, [ListingStatus::ADMIN_DELETEDListingStatus::USER_DELETED])) {
  562.             $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.undelete');
  563.         }
  564.         switch (true) {
  565.             // If pending/draft/rejected/expired == to ==> live
  566.             case ListingStatus::LIVE == $status && \in_array($listing->getStatus(), [
  567.                 ListingStatus::PENDING,
  568.                 ListingStatus::DRAFT,
  569.                 ListingStatus::REJECTED,
  570.                 ListingStatus::EXPIRED,
  571.             ]):
  572.                 $this->dispatcher->dispatch((new ListingWasPublished())->setSubject($listing)->prepareData(), 'listing.was.published');
  573.                 $this->dispatcher->dispatch(new ListingNotificationEvent($listing), 'aqarmap.listing.publish');
  574.                 break;
  575.             case ListingStatus::EXPIRED == $status:
  576.                 $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.expired');
  577.                 break;
  578.             case ListingStatus::REJECTED == $status:
  579.                 $listingWasRejectedEvent = (new ListingWasRejected())->setSubject($listing);
  580.                 $this->dispatcher->dispatch($listingWasRejectedEvent'listing.was.rejected');
  581.                 $this->dispatcher->dispatch(new ListingEvent($listing$rejectionReasons), 'aqarmap.listing.rejected');
  582.                 $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing_got_rejected');
  583.                 break;
  584.             case ListingStatus::DRAFT == $listing->getStatus():
  585.                 $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.created');
  586.                 break;
  587.             case ListingStatus::PENDING_PHOTOS == $status:
  588.                 $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.pending_photos');
  589.                 break;
  590.             case ListingStatus::PENDING_PAYMENT == $status:
  591.                 $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.pending_payment');
  592.                 break;
  593.             case ListingStatus::LIVE == $status && ListingStatus::PENDING_PAYMENT == $listing->getStatus():
  594.                 $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.free_publish');
  595.                 break;
  596.             case ListingStatus::LIVE == $status && ListingStatus::PENDING_PHOTOS == $listing->getStatus():
  597.                 $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.publish_without_photos');
  598.                 break;
  599.             case ListingStatus::ADMIN_DELETED == $status:
  600.                 $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.delete');
  601.                 break;
  602.             case ListingStatus::USER_DELETED == $status:
  603.                 $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.delete_by_user');
  604.                 break;
  605.             case ListingStatus::PENDING == $status:
  606.                 $this->dispatcher->dispatch(new ListingEvent($listing, [], $isAdmin), 'aqarmap.listing.pending_approval');
  607.                 break;
  608.         }
  609.         $listing->setStatus($status);
  610.         $listing->setUpdatedAt(new \DateTime());
  611.         if (ListingCategories::PROJECTS == $listing->getCategory()) {
  612.             $this->handleProjectDates($listing);
  613.         }
  614.         $this->em->persist($listing);
  615.         false === $flush ?: $this->em->flush();
  616.         $this->logListingIfProject($listing);
  617.         if ($this->featureToggleManager->isEnabled('web.events_streaming') && $listing->getId()) {
  618.             $this->dispatchListingStatusChangeEvent($listing);
  619.         }
  620.         $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.after_change_status');
  621.     }
  622.     /**
  623.      * Dispatch Listing Status Change Event.
  624.      *
  625.      *@param Listing
  626.      */
  627.     private function dispatchListingStatusChangeEvent(Listing $listing): void
  628.     {
  629.         $this->eventStreamingFactory->create(StreamingEventTopic::LISTING_STATUS_CHANGED)->publish(serialize([
  630.             'type' => StreamingEventTopic::LISTING_STATUS_CHANGED,
  631.             'payload' => [
  632.                 'id' => $listing->getId(),
  633.                 'location' => $listing->getLocation() ? $listing->getLocation()->getId() : null,
  634.                 'propertyType' => $listing->getPropertyType()->getId(),
  635.                 'section' => $listing->getSection()->getId(),
  636.                 'user' => $listing->getUser()->getId(),
  637.             ],
  638.         ]));
  639.     }
  640.     public function changePendingPaymentStatus(Listing $listing$status): void
  641.     {
  642.         $listing->setPendingPaymentStatus($status);
  643.         $this->em->persist($listing);
  644.         $this->em->flush();
  645.     }
  646.     public function changePendingPhotosStatus(Listing $listing$status): void
  647.     {
  648.         $listing->setPendingPhotosStatus($status);
  649.         $this->em->persist($listing);
  650.         $this->em->flush();
  651.     }
  652.     public function changeRelistStatus(Listing $listing$status): void
  653.     {
  654.         $listing->setRelistStatus($status);
  655.         $this->em->persist($listing);
  656.         $this->em->flush();
  657.     }
  658.     public function remove(Listing $listing$status): void
  659.     {
  660.         $this->changeStatus($listing$statusfalse);
  661.         $listing->setDeletedAt(new \DateTime());
  662.         $location $listing->getLocation();
  663.         $locationListingId $location->getListing() ? $location->getListing()->getId() : null;
  664.         if ($location && $locationListingId == $listing->getId()) {
  665.             $location->setListing(null);
  666.             $this->em->persist($location);
  667.         }
  668.         $this->em->persist($listing);
  669.         $this->em->flush();
  670.     }
  671.     public function setMainPhoto(ListingPhoto $listingPhoto): void
  672.     {
  673.         // If there's already a main photo swap orders
  674.         if ($currentMainPhoto $listingPhoto->getListing()->getMainPhoto()) {
  675.             if (PhotoTypes::MAIN_PHOTO == $currentMainPhoto->getType()) {
  676.                 $this->swapListingPhotos($listingPhoto$listingPhoto->getListing()->getMainPhoto());
  677.                 $this->em->persist($currentMainPhoto);
  678.             }
  679.         }
  680.         $this->changePhotoType($listingPhotoPhotoTypes::MAIN_PHOTO);
  681.     }
  682.     /**
  683.      * @param bool $flush
  684.      */
  685.     public function swapListingPhotos(ListingPhoto $firstListingPhotoListingPhoto $secondListingPhoto$flush false): void
  686.     {
  687.         // Swapping Orders
  688.         $tempOrder $firstListingPhoto->getOrder();
  689.         $firstListingPhoto->setOrder($secondListingPhoto->getOrder());
  690.         $secondListingPhoto->setOrder($tempOrder);
  691.         $this->em->persist($firstListingPhoto);
  692.         $this->em->persist($secondListingPhoto);
  693.         if ($flush) {
  694.             $this->em->flush();
  695.         }
  696.     }
  697.     /**
  698.      * @param ListingPhoto $listingPhoto |null
  699.      * @param bool $flush
  700.      */
  701.     public function changePhotoType(?ListingPhoto $listingPhoto$type$flush true): void
  702.     {
  703.         if (!$listingPhoto) {
  704.             return;
  705.         }
  706.         if ($this->isValidPhotoType($type)) {
  707.             foreach ($listingPhoto->getListing()->getPhotos() as $iterablePhoto) {
  708.                 if ($iterablePhoto->getType() == $type) {
  709.                     $iterablePhoto->setType(null);
  710.                 }
  711.             }
  712.             $listingPhoto->setType($type);
  713.             if ($listingPhoto->getListing()->getChildren() && PhotoTypes::LOGO_PHOTO == $type) {
  714.                 $this->setLogoPhoto($listingPhoto);
  715.             }
  716.             $this->em->persist($listingPhoto);
  717.             if ($flush) {
  718.                 $this->em->flush();
  719.             }
  720.         }
  721.     }
  722.     public function setLogoPhoto($listingPhoto): void
  723.     {
  724.         foreach ($listingPhoto->getListing()->getChildren() as $child) {
  725.             $listingPhotosToBeLogo $this->listingPhotoRepository->findOneBy([
  726.                 'listing' => $child,
  727.                 'file' => $listingPhoto->getFile(),
  728.             ]);
  729.             try {
  730.                 $this->changePhotoType($listingPhotosToBeLogoPhotoTypes::LOGO_PHOTOfalse);
  731.             } catch (\Exception $exception) {
  732.                 $this->logger->error($exception->getMessage().' '.$child->getId());
  733.             }
  734.         }
  735.     }
  736.     /**
  737.      * Reset listing description numbers.
  738.      */
  739.     public function removeNumbers(Listing $listing): void
  740.     {
  741.         $description preg_replace('/(\d{7,})/u'''$listing->getDescription());
  742.         $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.remove_description_numbers');
  743.         $listing->setDescription($description);
  744.         $this->em->flush();
  745.         foreach ($this->locales as $locale) {
  746.             $this->translatableListener->setTranslatableLocale($locale);
  747.             $listing->setTranslatableLocale($locale);
  748.             $this->em->refresh($listing);
  749.             $description preg_replace('/(\d{7,})/u'''$listing->getDescription());
  750.             $listing->setDescription($description);
  751.             $this->em->flush();
  752.         }
  753.     }
  754.     public function addFeature(Listing $listingint $type, ?\DateTime $expiresAt, ?Credit $credit null): void
  755.     {
  756.         $currentDate = new \DateTime();
  757.         $expiresAt ListingCategories::PROJECTS == $listing->getCategory() ? $currentDate->modify(self::ADD_TEN_YEARS) : $expiresAt;
  758.         $listingFeature = new ListingFeature();
  759.         $listingFeature
  760.             ->setType($type)
  761.             ->setCreatedAt(new \DateTime())
  762.             ->setListing($listing)
  763.             ->setCredit($credit)
  764.             ->setExpiresAt($expiresAt)
  765.             ->setLeadsCountSnapshot($listing->getLeadsCounter());
  766.         if ($this->requiresFeaturingReview($this->listingFeatureService->getListingFeaturedType($type))) {
  767.             $listingFeature->setFeaturingStatus(ListingFeaturedStatus::PENDING);
  768.         }
  769.         $this->em->persist($listingFeature);
  770.         $listing->addListingFeature($listingFeature);
  771.         if ($expiresAt && $listing->getExpiresAt() < $listingFeature->getExpiresAt()) {
  772.             $listing->setExpiresAt($expiresAt);
  773.         }
  774.         $this->saveListing($listing);
  775.         $this->dispatcher->dispatch(new ListingFeatureEvent($listingFeature), 'aqarmap.listing.feature.bumpup');
  776.     }
  777.     /**
  778.      * @return \Doctrine\Common\Collections\Collection
  779.      */
  780.     public function getSimilarListings(Listing $listing)
  781.     {
  782.         $criteria = [
  783.             'minArea' => $listing->getArea() * 0.65,
  784.             'maxArea' => $listing->getArea() * 1.35,
  785.             'location' => $listing->getLocation(),
  786.             'propertyType' => $listing->getPropertyType(),
  787.             'section' => $listing->getSection(),
  788.             'minPrice' => $listing->getPrice() * 0.65,
  789.             'maxPrice' => $listing->getPrice() * 1.35,
  790.             'exclude' => [$listing->getId()],
  791.             'limit' => 3,
  792.         ];
  793.         $similarListings $this->listingRepository->search($criteria)->getQuery()->getResult();
  794.         if (\count($similarListings) < && $listing->getLocation()->getParent()) {
  795.             $criteria['location'] = $listing->getLocation()->getParent();
  796.             $criteria = [
  797.                 'minArea' => $listing->getArea() * 0.75,
  798.                 'maxArea' => $listing->getArea() * 1.25,
  799.                 'location' => $listing->getLocation()->getParent(),
  800.                 'minPrice' => $listing->getPrice() * 0.75,
  801.                 'maxPrice' => $listing->getPrice() * 1.25,
  802.             ];
  803.             $similarListings $this->listingRepository->search($criteria)->getQuery()->getResult();
  804.         }
  805.         return $similarListings;
  806.     }
  807.     public function updateListingLeadsCounter(Listing $listing$flush true): void
  808.     {
  809.         $leadRepository $this->entityManager->getRepository(Lead::class);
  810.         $totalLeads $leadRepository->countLeadsByListing($listing);
  811.         $listing
  812.             ->setPhoneCounter($totalLeads)
  813.             ->setLeadsCounter($totalLeads)
  814.         ;
  815.         $this->em->persist($listing);
  816.         if ($flush) {
  817.             $this->em->flush();
  818.         }
  819.     }
  820.     public function updateListingInteractionsCounter(Listing $listing$interactionType$flush true): void
  821.     {
  822.         $interactionTypes = [$interactionType];
  823.         $count $this->interactionRepository->getListingInteractionCount(
  824.             $interactionTypes,
  825.             $listing
  826.         );
  827.         switch ($interactionType) {
  828.             case InteractionTypes::LISTING_VIEWS:
  829.                 $listing->setViews($count);
  830.                 break;
  831.             default:
  832.                 break;
  833.         }
  834.         $this->em->persist($listing);
  835.         if ($flush) {
  836.             $this->em->flush();
  837.         }
  838.     }
  839.     public function addParticipant(Listing $listingUser $user): void
  840.     {
  841.         $listing->addParticipant($user);
  842.         $this->em->persist($listing);
  843.         $this->em->flush();
  844.     }
  845.     /**
  846.      * @param array $status
  847.      * @param array $searchCriteria
  848.      * @param bool $exclude
  849.      * @param bool $mergeDraftAndDeleted
  850.      */
  851.     public function getUserListings(User $user$status = [], $searchCriteria = [], $exclude false$mergeDraftAndDeleted true)
  852.     {
  853.         if ($exclude && $mergeDraftAndDeleted) {
  854.             $draftAndDeleted = [
  855.                 ListingStatus::DRAFT,
  856.                 ListingStatus::USER_DELETED,
  857.                 ListingStatus::ADMIN_DELETED,
  858.             ];
  859.             $status array_merge($draftAndDeleted$status);
  860.         }
  861.         if (!empty($searchCriteria['location'])) {
  862.             $location $this->locationRepository->find($searchCriteria['location']);
  863.             $searchCriteria['location'] = $this->locationRepository->getLocationChildren($location);
  864.         }
  865.         $locale $this->requestStack->getCurrentRequest()->getLocale();
  866.         return $this->listingRepository->getListingsForMyListingsPage(array_merge(['user' => $user'status' => (array) $status'exclude' => $exclude], $searchCriteria), $locale);
  867.     }
  868.     /**
  869.      * @param array $status
  870.      * @param array $searchCriteria
  871.      * @param bool $exclude
  872.      * @param bool $mergeDraftAndDeleted
  873.      */
  874.     public function getUserListingsQuery(User $user$status = [], $searchCriteria = [], $exclude false$mergeDraftAndDeleted true)
  875.     {
  876.         if ($exclude && $mergeDraftAndDeleted) {
  877.             $draftAndDeleted = [
  878.                 ListingStatus::DRAFT,
  879.                 ListingStatus::USER_DELETED,
  880.                 ListingStatus::ADMIN_DELETED,
  881.             ];
  882.             $status array_merge($draftAndDeleted$status);
  883.         }
  884.         if (!empty($searchCriteria['location'])) {
  885.             $location $this->em->getReference('AqarmapListingBundle:Location'$searchCriteria['location']);
  886.             $searchCriteria['location'] = $this->em->getRepository(Location::class)->getLocationChildren($location);
  887.         }
  888.         $locale $this->requestStack->getCurrentRequest()->getLocale();
  889.         return $this->em->getRepository(Listing::class)->getListingsForMyListingsPageQuery(array_merge(['user' => $user'status' => $status'exclude' => $exclude], $searchCriteria), $locale);
  890.     }
  891.     /**
  892.      * Make a listing featured, and deduct fees.
  893.      *
  894.      * @return bool
  895.      *
  896.      * @throws \Exception
  897.      */
  898.     public function makeItFeatured(Listing $listing, array $rules)
  899.     {
  900.         $expiredAt null;
  901.         $featuredFees $rules['featuredFees'];
  902.         $listingFeaturedType $rules['listingFeaturedType'];
  903.         $featuredDuration $rules['featuredDuration'];
  904.         $listingFeature $rules['listingFeature'];
  905.         // Check if the listing has a valid credit transaction for
  906.         // making a listing featured to avoid double charging
  907.         if ($this->isFeatured($listing)) {
  908.             throw new \RuntimeException($this->translator->trans('listing.featured_failure_statement.already_featured', ['%listingId%' => $listing->getId()]));
  909.         }
  910.         if (empty($featuredFees) || empty($featuredDuration)) {
  911.             throw new \RuntimeException($this->translator->trans('listing.featured_failure_statement.not_available'));
  912.         }
  913.         $listingOwner $listing->getUser();
  914.         $availableBalance $this->getCreditManager()->getBalance($listingOwner);
  915.         if ($featuredFees $availableBalance) {
  916.             throw new \RuntimeException($this->translator->trans('listing.featured_failure_statement.no_enough_credit'));
  917.         }
  918.         $requiresFeaturingReview $this->requiresFeaturingReview($listingFeaturedType);
  919.         // Let's take our money :)
  920.         $credits $this->getCreditManager()->deduction(
  921.             $listingOwner,
  922.             $featuredFees,
  923.             $this->translator->trans('credit.deduction.featured', [':featured_type:' => $rules['featuredText']]),
  924.             true
  925.         );
  926.         foreach ($credits as $credit) {
  927.             if (!$credit instanceof Credit) {
  928.                 throw new \RuntimeException($this->translator->trans('credit.operation_failed'));
  929.             }
  930.             if (ListingStatus::LIVE == $listing->getStatus() && !$requiresFeaturingReview) {
  931.                 $expiredAt = new \DateTime('+'.$featuredDuration.' days');
  932.             }
  933.             $this->addFeature($listing$listingFeature$expiredAt$credit);
  934.         }
  935.         if (!$requiresFeaturingReview) {
  936.             $listing->setFeatured($listingFeaturedType);
  937.         }
  938.         $listing->setIsTopPicks(false);
  939.         $this->saveListing($listingtrue);
  940.         return true;
  941.     }
  942.     public function handleDepthProductServices(Listing $listingService $service): void
  943.     {
  944.         $ruleLabel ListingFeaturedTypes::getRuleLabel($service->getFeaturingType());
  945.         $rules $this->listingFeatureService->getFeaturedListingRules($listing, [$ruleLabel]);
  946.         $rules = [
  947.             'featuredFees' => $rules[$ruleLabel]['fees'],
  948.             'featuredDuration' => $rules[$ruleLabel]['duration'],
  949.             'listingFeaturedType' => $rules[$ruleLabel]['listingFeaturedType'],
  950.             'listingFeature' => $rules[$ruleLabel]['listingFeature'],
  951.             'featuredText' => ListingFeaturedTypes::getFeaturedText($service->getFeaturingType()),
  952.         ];
  953.         try {
  954.             $this->makeItFeatured($listing$rules);
  955.         } catch (\Exception $exception) {
  956.         }
  957.     }
  958.     /**
  959.      * Pump up listing.
  960.      *
  961.      * @return bool
  962.      *
  963.      * @throws \Exception
  964.      */
  965.     public function pumpUp(Listing $listing)
  966.     {
  967.         if ($listing->getLastAutoBumpUp()->isBumpable()) {
  968.             throw new \RuntimeException('This listing has already been pumped up.');
  969.         }
  970.         $listingRules $this->getMatcher()->match($listing);
  971.         $pumpUpFees $listingRules['pump_up_fees'];
  972.         $pumpUpOccurrence $listingRules['pump_up_occurrence'];
  973.         $pumpUpDuration $listingRules['pump_up_duration'];
  974.         $listingOwner $listing->getUser();
  975.         $credits $this->getCreditManager()->deduction(
  976.             $listingOwner,
  977.             $pumpUpFees,
  978.             'Fees for making a listing pump up',
  979.             true
  980.         );
  981.         foreach ($credits as $credit) {
  982.             if (!$credit instanceof Credit) {
  983.                 throw new \RuntimeException($this->translator->trans('credit.operation_failed'));
  984.             }
  985.             $credit->setStatus(CreditStatus::SUCCESS);
  986.             $listing->setBumped(true);
  987.             $pumpUpService $this->listingFeatureService;
  988.             $pumpUpModel $this->pumpUpModel;
  989.             $pumpUpModel
  990.                 ->setListing($listing)
  991.                 ->setDuration($pumpUpDuration)
  992.                 ->setOccurrences($pumpUpOccurrence)
  993.                 ->setOwner($listingOwner)
  994.                 ->setCredit($credit);
  995.             $pumpUpService->createBumpUp($pumpUpModel);
  996.         }
  997.         $this->saveListing($listingtrue);
  998.         $activityLogger $this->activityLogger;
  999.         $activityLogger->record(ActivityType::LISTING_BUMP_UP$listing->getId());
  1000.         $pumpUpService->logBumpActivity('aqarmap.listing.bump_up_started'$listing$listingOwner);
  1001.         return true;
  1002.     }
  1003.     /**
  1004.      * Check if the listing has a valid transaction for making a listing Featured.
  1005.      *
  1006.      * @return bool
  1007.      *
  1008.      * @throws \Exception
  1009.      */
  1010.     public function isFeatured(Listing $listing$type null)
  1011.     {
  1012.         if ($listing->getFeaturedPublicationCredit($type)) {
  1013.             return true;
  1014.         }
  1015.         return false;
  1016.     }
  1017.     /**
  1018.      * @return Listing
  1019.      */
  1020.     public function relist(Listing $listing, array $criteria = [])
  1021.     {
  1022.         $isAdminRelist = isset($criteria['isAdminRelist']) && true == $criteria['isAdminRelist'];
  1023.         // Clone and set relist parent.
  1024.         $clonedListing $this->cloneListing($listing);
  1025.         if ($isAdminRelist) {
  1026.             $clonedListing->setPublishedAt(new \DateTime());
  1027.         }
  1028.         if (ListingCategories::UNLIMITED == $clonedListing->getCategory()) {
  1029.             $clonedListing $this->handleUnlimitedListingOnRelist($clonedListing);
  1030.         }
  1031.         $clonedListing->setRelistParent($listing);
  1032.         if (isset($criteria['price']) && is_numeric($criteria['price'])) {
  1033.             // Setting the new price to the cloned listing.
  1034.             $clonedListing->setPrice($criteria['price']);
  1035.         }
  1036.         if ($isAdminRelist) {
  1037.             $this->changeStatus($clonedListingListingStatus::LIVEtrue);
  1038.         } else {
  1039.             $this->changeStatus($clonedListingListingStatus::PENDINGtrue);
  1040.         }
  1041.         $this->saveListing($clonedListingfalse);
  1042.         // Removing this relisted listing from the section and avoid catching it again
  1043.         $listing->setRelistStatus(ListingPendingStatus::CHECKED);
  1044.         $this->changeStatus($listingListingStatus::ADMIN_DELETEDfalse);
  1045.         if ($isAdminRelist) {
  1046.             $this->remove($listingListingStatus::ADMIN_DELETED);
  1047.         }
  1048.         $this->em->persist($listing);
  1049.         $this->em->flush();
  1050.         return $clonedListing;
  1051.     }
  1052.     /**
  1053.      * @return Listing
  1054.      */
  1055.     public function cloneListing(Listing $listing)
  1056.     {
  1057.         $clonedListing = new Listing();
  1058.         $clonedListing->setStatus($listing->getStatus());
  1059.         $clonedListing->setAddress($listing->getAddress());
  1060.         $clonedListing->setArea($listing->getArea());
  1061.         $clonedListing->setCategory($listing->getCategory());
  1062.         $clonedListing->setCenterLat($listing->getCenterLat());
  1063.         $clonedListing->setCenterLng($listing->getCenterLng());
  1064.         $clonedListing->setDescription($listing->getDescription());
  1065.         $clonedListing->setIp($listing->getIp());
  1066.         $clonedListing->setLocation($listing->getLocation());
  1067.         $clonedListing->setPrice($listing->getPrice());
  1068.         $clonedListing->setPropertyType($listing->getPropertyType());
  1069.         $clonedListing->setSection($listing->getSection());
  1070.         $clonedListing->setSellerRole($listing->getSellerRole());
  1071.         $clonedListing->setTitle($listing->getTitle());
  1072.         $clonedListing->setUser($listing->getUser());
  1073.         $clonedListing->setVideoUrl($listing->getVideoUrl());
  1074.         // cloning  photos
  1075.         $photosClone = new ArrayCollection();
  1076.         foreach ($listing->getPhotos() as $item) {
  1077.             $itemClone = clone $item;
  1078.             $photosClone->add($itemClone);
  1079.         }
  1080.         $clonedListing->setPhotos($photosClone);
  1081.         // cloning  attributes
  1082.         $attributesClone = new ArrayCollection();
  1083.         foreach ($listing->getAttributes() as $item) {
  1084.             $itemClone = clone $item;
  1085.             $attributesClone->add($itemClone);
  1086.         }
  1087.         $clonedListing->setAttributes($attributesClone);
  1088.         // cloning phones
  1089.         $phonesClone = new ArrayCollection();
  1090.         foreach ($listing->getPhones() as $item) {
  1091.             $itemClone = clone $item;
  1092.             $phonesClone->add($itemClone);
  1093.         }
  1094.         $clonedListing->setPhones($phonesClone);
  1095.         // cloning locations
  1096.         $locationClone = new ArrayCollection();
  1097.         foreach ($listing->getLocations() as $item) {
  1098.             $itemClone = clone $item;
  1099.             $locationClone->add($itemClone);
  1100.         }
  1101.         $clonedListing->setLocations($locationClone);
  1102.         // cloning translations
  1103.         $translationClone = new ArrayCollection();
  1104.         foreach ($listing->getTranslations() as $item) {
  1105.             $itemClone = clone $item;
  1106.             $translationClone->add($itemClone);
  1107.         }
  1108.         $clonedListing->setTranslations($translationClone);
  1109.         return $clonedListing;
  1110.     }
  1111.     /**
  1112.      * Check if listing is live for number of days(5 days).
  1113.      */
  1114.     public function checkListingLiveDays(Listing $listingint $days 5): bool
  1115.     {
  1116.         if (null != $listing->getPublishedAt() && ListingStatus::LIVE == $listing->getStatus()) {
  1117.             $daysAgo = new \DateTime(sprintf('-%d Days'$days));
  1118.             return $listing->getPublishedAt() < $daysAgo;
  1119.         }
  1120.         return false;
  1121.     }
  1122.     /**
  1123.      * @return CreditManager
  1124.      */
  1125.     public function getCreditManager()
  1126.     {
  1127.         return $this->creditManager;
  1128.     }
  1129.     /**
  1130.      * @return ListingRuleMatcher
  1131.      */
  1132.     public function getMatcher()
  1133.     {
  1134.         return $this->matcher;
  1135.     }
  1136.     // --------------------------------------------------------------------------
  1137.     /**
  1138.      * @param LeadModel $leadModel
  1139.      *
  1140.      * @return CallRequest|Composer|bool|object
  1141.      */
  1142.     public function quickCreateLead($leadModel)
  1143.     {
  1144.         if (null != $leadModel->getMessage() || 'Home.eg-New-Homes' == $leadModel->getCampaign()) {
  1145.             // Send contact seller message and send a lead
  1146.             $composer $this->messageComposer;
  1147.             $composer
  1148.                 ->setSender($leadModel->getUser())
  1149.                 ->compose($leadModel->getMessage(), $leadModel->getListing(), $leadModel->getCampaign());
  1150.         } else {
  1151.             // Create Call Request
  1152.             $callRequest = new CallRequest();
  1153.             $callRequest->setUser($leadModel->getUser());
  1154.             $callRequest->setListing($leadModel->getListing());
  1155.             $callRequest->setLeadFullName($leadModel->getName());
  1156.             $callRequest->setLeadEmail($leadModel->getEmail());
  1157.             $callRequest->setPhone($leadModel->getPhone());
  1158.             $this->callRequestManager->submitCallRequest($callRequest$leadModel->getCampaign());
  1159.         }
  1160.         return true;
  1161.     }
  1162.     // --------------------------------------------------------------------------
  1163.     /**
  1164.      * Get listings IDs from SlidingPagination.
  1165.      *
  1166.      * @return array
  1167.      */
  1168.     public function getIdsFromPagination(SlidingPagination $listings)
  1169.     {
  1170.         $listingsId = [];
  1171.         foreach ($listings as $listing) {
  1172.             $listingsId[] = $listing->getId();
  1173.         }
  1174.         return $listingsId;
  1175.     }
  1176.     /**
  1177.      * @return int
  1178.      */
  1179.     public function getMaxListingPhotoOrder(Listing $listing)
  1180.     {
  1181.         $lastPhoto $this->listingPhotoRepository->findOneBy(['listing' => $listing], ['order' => 'DESC']);
  1182.         if ($lastPhoto) {
  1183.             return $lastPhoto->getOrder();
  1184.         }
  1185.         return 0;
  1186.     }
  1187.     public function syncListing(Listing $targetListingListing $sourceListing$syncedFields$arguments = []): void
  1188.     {
  1189.         if (\in_array('photos'$syncedFields)) {
  1190.             $listingPhotos $sourceListing->getPhotos();
  1191.             if (isset($arguments['photos'])) {
  1192.                 $listingPhotos $arguments['photos'];
  1193.             }
  1194.             $this->syncPhotos($targetListing$sourceListing$listingPhotos);
  1195.         }
  1196.         if (\in_array('translations'$syncedFields)) {
  1197.             if (isset($arguments['monolingual'])) {
  1198.                 $targetListing->setAddress($sourceListing->getAddress());
  1199.                 $targetListing->setDescription($sourceListing->getDescription());
  1200.                 $this->em->persist($targetListing);
  1201.                 $this->em->flush($targetListing);
  1202.             } else {
  1203.                 foreach ($this->locales as $locale) {
  1204.                     $this->translatableListener->setTranslatableLocale($locale);
  1205.                     $this->em->refresh($sourceListing);
  1206.                     $targetListing->setAddress($sourceListing->getAddress());
  1207.                     $targetListing->setDescription($sourceListing->getDescription());
  1208.                     $this->em->persist($targetListing);
  1209.                     $this->em->flush($targetListing);
  1210.                 }
  1211.             }
  1212.         }
  1213.         if (\in_array('attributes'$syncedFields)) {
  1214.             // Only Sync attributes if the property types are equal
  1215.             if ($sourceListing->getPropertyType() == $targetListing->getPropertyType()) {
  1216.                 /** @var ListingAttribute $attribute */
  1217.                 foreach ($sourceListing->getAttributes() as $attribute) {
  1218.                     $targetAttribute = new ListingAttribute();
  1219.                     $targetAttribute->setCustomField($attribute->getCustomField());
  1220.                     $targetAttribute->setValue($attribute->getValue());
  1221.                     $targetListing->addAttribute($targetAttribute);
  1222.                 }
  1223.             }
  1224.         }
  1225.         if (\in_array('phoneNumbers'$syncedFields)) {
  1226.             $listingPhones $sourceListing->getPhones();
  1227.             if (isset($arguments['phoneNumbers'])) {
  1228.                 $listingPhones $arguments['phoneNumbers'];
  1229.             }
  1230.             /* @var ListingPhone $phone */
  1231.             $this->syncPhones($targetListing$sourceListing$listingPhones);
  1232.         }
  1233.         if (\in_array('participants'$syncedFields)) {
  1234.             $participants $sourceListing->getParticipants();
  1235.             if (isset($arguments['participants'])) {
  1236.                 $participants $arguments['participants'];
  1237.             }
  1238.             $this->syncParticipants($targetListing$sourceListing$participants);
  1239.         }
  1240.         $this->saveListing($targetListing);
  1241.     }
  1242.     /**
  1243.      * @throws ORMException
  1244.      * @throws OptimisticLockException
  1245.      */
  1246.     public function syncListingForProject(array $criteria): bool
  1247.     {
  1248.         $targetListingId $criteria['targetListingId'];
  1249.         $sourceListingId $criteria['sourceListingId'];
  1250.         $syncedFields $criteria['syncedFields'];
  1251.         $targetListingSavedAt $criteria['targetListingSavedAt'] ?? null;
  1252.         $sourceListing $this->listingRepository->findOneBy(['id' => $sourceListingId]);
  1253.         $targetListing $this->listingRepository->findOneBy(['id' => $targetListingId]);
  1254.         if (!$this->isReadyForSyncListingForProject($sourceListing$targetListing$targetListingSavedAt)) {
  1255.             return false;
  1256.         }
  1257.         if (\in_array('photos'$syncedFields)) {
  1258.             $listingPhotos $sourceListing->getPhotos();
  1259.             $this->syncPhotos($targetListing$sourceListing$listingPhotos);
  1260.         }
  1261.         if (\in_array('translations'$syncedFields)) {
  1262.             foreach ($this->locales as $locale) {
  1263.                 $this->translatableListener->setTranslatableLocale($locale);
  1264.                 $this->em->refresh($sourceListing);
  1265.                 $targetListing->setAddress($sourceListing->getAddress());
  1266.                 $targetListing->setDescription($sourceListing->getDescription());
  1267.                 $this->em->persist($targetListing);
  1268.                 $this->em->flush();
  1269.             }
  1270.         }
  1271.         if (\in_array('attributes'$syncedFields)) {
  1272.             // Only Sync attributes if the property types are equal
  1273.             if ($sourceListing->getPropertyType() == $targetListing->getPropertyType()) {
  1274.                 $targetListing $this->cloneAttributes($sourceListing$targetListing);
  1275.             }
  1276.         }
  1277.         if (\in_array('phoneNumbers'$syncedFields)) {
  1278.             $listingPhones $sourceListing->getPhones();
  1279.             /* @var ListingPhone $phone */
  1280.             $this->syncPhones($targetListing$sourceListing$listingPhones);
  1281.         }
  1282.         if (\in_array('participants'$syncedFields)) {
  1283.             $participants $sourceListing->getParticipants();
  1284.             $this->syncParticipants($targetListing$sourceListing$participants);
  1285.         }
  1286.         $this->saveListing($targetListing);
  1287.         return true;
  1288.     }
  1289.     public function filterListingPhones(Listing $listing): void
  1290.     {
  1291.         $listingPhones $listing->getPhones()->toArray();
  1292.         $listing->removePhones();
  1293.         $filteredPhones $this->phoneManager->addListingPhonesList($listingPhones''$listing);
  1294.         $listing->setPhones($filteredPhones);
  1295.     }
  1296.     /**
  1297.      * @return array
  1298.      */
  1299.     public function suggestListingPhoneNumbers($userPhonesListing $listing)
  1300.     {
  1301.         $listingPhones = [];
  1302.         foreach ($userPhones as $phone) {
  1303.             $this->phoneManager->addNewListingPhone(
  1304.                 $phone,
  1305.                 '',
  1306.                 $listing,
  1307.                 true
  1308.             );
  1309.             $listingPhones[] = $phone;
  1310.         }
  1311.         return $listingPhones;
  1312.     }
  1313.     public function savePhotos(array $photosListing $listing): void
  1314.     {
  1315.         $listingPhotos = [];
  1316.         $maxOrder $this->getMaxListingPhotoOrder($listing);
  1317.         foreach ($photos as $index => $file) {
  1318.             $photo = new Photo();
  1319.             $photo->setFile($file);
  1320.             $listingPhoto = new ListingPhoto();
  1321.             $listingPhoto->setFile($photo);
  1322.             $listingPhoto->setCaption($photo->getFile()->getClientOriginalName());
  1323.             $listingPhoto->setOrder($maxOrder $index 1);
  1324.             $listingPhotos[] = $listingPhoto;
  1325.             $listing->addPhoto($listingPhoto);
  1326.         }
  1327.         $this->saveListing($listing);
  1328.     }
  1329.     /**
  1330.      * Sync locations.
  1331.      */
  1332.     public function syncLocations(Listing $parentListingLocation $locationbool $flush false): void
  1333.     {
  1334.         $batchSize 20;
  1335.         $loopIndex 1;
  1336.         foreach ($parentListing->getChildren() as $childListing) {
  1337.             if ($parentListing->hasLocation($location) && !$childListing->hasLocation($location)) {
  1338.                 $childListing->addLocation($location);
  1339.             } elseif (!$parentListing->hasLocation($location) && $childListing->hasLocation($location)) {
  1340.                 $childListing->removeLocation($location);
  1341.             }
  1342.             $this->em->persist($childListing);
  1343.             if (($loopIndex $batchSize) === 0) {
  1344.                 $this->em->flush();
  1345.                 $this->em->clear();
  1346.             }
  1347.             ++$loopIndex;
  1348.         }
  1349.         if ($flush) {
  1350.             $this->em->flush();
  1351.             $this->em->clear();
  1352.         }
  1353.     }
  1354.     /**
  1355.      * Sync slug.
  1356.      */
  1357.     public function syncSlug(Listing $parentListing): void
  1358.     {
  1359.         $this->listingRepository->updateChildrenSlug($parentListing);
  1360.     }
  1361.     /**
  1362.      * Sync video URL.
  1363.      */
  1364.     public function syncVideoUrl(Listing $parentListing): void
  1365.     {
  1366.         $this->listingRepository->updateChildrenVideoUrl($parentListing);
  1367.     }
  1368.     public function syncPhotos(Listing $targetListingListing $sourceListing$listingPhotos): void
  1369.     {
  1370.         $maxOrder $this->getMaxListingPhotoOrder($targetListing);
  1371.         $index 1;
  1372.         /** @var ListingPhoto $listingPhoto */
  1373.         foreach ($listingPhotos as $listingPhoto) {
  1374.             $listingPhotoRepo $this->em->getRepository(ListingPhoto::class);
  1375.             $sourceListingPhoto $listingPhotoRepo->findOneBy([
  1376.                 'file' => $listingPhoto->getFile(),
  1377.                 'listing' => $sourceListing,
  1378.             ]);
  1379.             $targetListingPhoto $listingPhotoRepo->findOneBy([
  1380.                 'file' => $listingPhoto->getFile(),
  1381.                 'listing' => $targetListing,
  1382.             ]);
  1383.             if ($sourceListingPhoto && !$targetListingPhoto) {
  1384.                 $targetPhoto = new ListingPhoto();
  1385.                 $targetPhoto->setFile($listingPhoto->getFile());
  1386.                 $targetPhoto->setCaption($listingPhoto->getCaption());
  1387.                 $targetPhoto->setOrder($maxOrder $index++);
  1388.                 $targetPhoto->setType($listingPhoto->getType());
  1389.                 $targetListing->addPhoto($targetPhoto);
  1390.             } elseif (!$sourceListingPhoto && $targetListingPhoto) {
  1391.                 $targetListing->removePhoto($targetListingPhoto);
  1392.             }
  1393.         }
  1394.     }
  1395.     /**
  1396.      * Update Listing Translations.
  1397.      */
  1398.     public function updateListingTranslations(Listing $listing): void
  1399.     {
  1400.         $currentLocale $this->requestStack->getCurrentRequest()->getLocale();
  1401.         $reversedLocale null;
  1402.         // Identifying Submitted Locale
  1403.         foreach ($this->locales as $locale) {
  1404.             if ($currentLocale != $locale) {
  1405.                 $reversedLocale $locale;
  1406.             }
  1407.         }
  1408.         $currentTitle $listing->getTitle();
  1409.         $listing->setTranslatableLocale($reversedLocale);
  1410.         $this->em->refresh($listing);
  1411.         $translatedTitle $listing->getTitle();
  1412.         if (null != $reversedLocale && (null == $listing->getTitle() || $currentTitle == $translatedTitle)) {
  1413.             $title $this->listingTitleTranslations($listing$reversedLocale);
  1414.             $description $this->listingDescriptionTranslations($listing$reversedLocale);
  1415.             $address $this->listingAddressTranslations($listing$reversedLocale);
  1416.             $listing->setTitle($title);
  1417.             $listing->setDescription($description);
  1418.             $listing->setAddress($address);
  1419.             $listing->setTranslatableLocale($reversedLocale);
  1420.             $this->em->persist($listing);
  1421.             $this->em->flush();
  1422.         }
  1423.     }
  1424.     /**
  1425.      * Update Listing Translations Fields.
  1426.      */
  1427.     public function updateListingTranslationsFields(Listing $listing, array $fields): void
  1428.     {
  1429.         if ('ar' == $fields['reversedLocale']) {
  1430.             $listing->setDescription($fields['description-ar']);
  1431.             $listing->setTitle($fields['title-ar']);
  1432.             $address $this->listingAddressTranslations($listing$fields['reversedLocale']);
  1433.         } else {
  1434.             $listing->setDescription($fields['description-en']);
  1435.             $listing->setTitle($fields['title-en']);
  1436.             $address $this->listingAddressTranslations($listing$fields['reversedLocale']);
  1437.         }
  1438.         $listing->setAddress($address);
  1439.         $listing->setTranslatableLocale($fields['reversedLocale']);
  1440.         $this->em->persist($listing);
  1441.         $this->em->flush();
  1442.     }
  1443.     /**
  1444.      * Generate auto-translation to listing title.
  1445.      *
  1446.      * @param null $locale
  1447.      */
  1448.     public function listingTitleTranslations(Listing $listing$locale null)
  1449.     {
  1450.         if (null == $locale) {
  1451.             $locale $this->requestStack->getCurrentRequest()->getLocale();
  1452.         }
  1453.         $this->translatableListener->setTranslatableLocale($locale);
  1454.         $this->em->refresh($listing->getLocation());
  1455.         $this->em->refresh($listing->getPropertyType());
  1456.         $this->em->refresh($listing->getSection());
  1457.         return $this->generateTranslatedListingTitle($listing$locale);
  1458.     }
  1459.     /**
  1460.      * Generate auto-translation to listing title.
  1461.      *
  1462.      * @param null $locale
  1463.      */
  1464.     public function unitTitleTranslations(Listing $listingListing $sourceListing$locale null)
  1465.     {
  1466.         if (null == $locale) {
  1467.             $locale $this->requestStack->getCurrentRequest()->getLocale();
  1468.         }
  1469.         $this->translatableListener->setTranslatableLocale($locale);
  1470.         $this->em->refresh($sourceListing);
  1471.         $this->em->refresh($listing->getLocation());
  1472.         $this->em->refresh($listing->getPropertyType());
  1473.         $this->em->refresh($listing->getSection());
  1474.         return $this->generateTranslatedUnitTitle($listing$sourceListing$locale);
  1475.     }
  1476.     /**
  1477.      * Generate auto-translation to listing description.
  1478.      *
  1479.      * @param null $locale
  1480.      */
  1481.     public function listingDescriptionTranslations(Listing $listing$locale null)
  1482.     {
  1483.         if (null == $locale) {
  1484.             $locale $this->requestStack->getCurrentRequest()->getLocale();
  1485.         }
  1486.         $this->translatableListener->setTranslatableLocale($locale);
  1487.         $this->em->refresh($listing->getLocation());
  1488.         $this->em->refresh($listing->getPropertyType());
  1489.         $this->em->refresh($listing->getSection());
  1490.         $listingDescriptions = [];
  1491.         $context $this->router->getContext();
  1492.         $context->setParameter('_locale'$locale);
  1493.         $sizeUnit $this->settings->getSetting('general''measurement_unit');
  1494.         $view $listing->getPropertyView()
  1495.             ? $this->translator->trans('layout.property_view', [], null$locale).' '.$this->translator->trans($listing->getPropertyViewLabel(), [], null$locale)
  1496.             : '';
  1497.         if (
  1498.             $listing->getPropertyType()
  1499.             && $listing->getAttribute('finish-type')
  1500.             && $listing->getArea()
  1501.             && $listing->getAttribute('floor')
  1502.             && $listing->getAttribute('rooms')
  1503.             && $listing->getAttribute('baths')
  1504.         ) {
  1505.             $listingDescriptions[] = $this->translator->trans('layout.description.translations.property_type_size_finish_floor_view_room_bath', [
  1506.                 '%property_type%' => $listing->getPropertyType()->getTitle(),
  1507.                 '%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null$locale),
  1508.                 '%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null$locale),
  1509.                 '%view%' => $view,
  1510.                 '%floor%' => $this->numberToWordService->convertToRank($listing->getAttribute('floor')->getValue(), $locale),
  1511.                 '%rooms%' => $this->numberToWordService->convertToNum($listing->getAttribute('rooms')->getValue(), $locale),
  1512.                 '%baths%' => $this->numberToWordService->convertToNum($listing->getAttribute('baths')->getValue(), $locale),
  1513.             ], null$locale);
  1514.         }
  1515.         if (
  1516.             $listing->getSection()
  1517.             && $listing->getPropertyType()
  1518.             && $listing->getAttribute('finish-type')
  1519.             && $listing->getArea()
  1520.             && $listing->getAttribute('floor')
  1521.             && $listing->getAttribute('rooms')
  1522.             && $listing->getAttribute('baths')
  1523.         ) {
  1524.             $listingDescriptions[] = $this->translator->trans(
  1525.                 'layout.description.translations.property_type_section_size_finish_floor_view_room_bath',
  1526.                 [
  1527.                     '%property_type%' => $listing->getPropertyType()->getTitle(),
  1528.                     '%section%' => $listing->getSection()->getTitle(),
  1529.                     '%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null$locale),
  1530.                     '%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null$locale),
  1531.                     '%view%' => $view,
  1532.                     '%floor%' => $this->numberToWordService->convertToRank($listing->getAttribute('floor')->getValue(), $locale),
  1533.                     '%rooms%' => $this->numberToWordService->convertToNum($listing->getAttribute('rooms')->getValue(), $locale),
  1534.                     '%baths%' => $this->numberToWordService->convertToNum($listing->getAttribute('baths')->getValue(), $locale),
  1535.                 ],
  1536.                 null,
  1537.                 $locale
  1538.             );
  1539.         }
  1540.         if (
  1541.             $listing->getLocation()
  1542.             && $listing->getSection()
  1543.             && $listing->getPropertyType()
  1544.             && $listing->getAttribute('finish-type')
  1545.             && $listing->getArea()
  1546.             && $listing->getAttribute('floor')
  1547.             && $listing->getAttribute('rooms')
  1548.             && $listing->getAttribute('baths')
  1549.         ) {
  1550.             $listingDescriptions[] = $this->translator->trans('layout.description.translations.finish_size_location_view_floor_room_bath', [
  1551.                 '%in%' => $listing->getLocation() ? $this->translator->trans('listing.in', [], null$locale) : null,
  1552.                 '%location%' => $listing->getLocation() ? $listing->getLocation()->getTitle() : null,
  1553.                 '%property_type%' => $listing->getPropertyType()->getTitle(),
  1554.                 '%section%' => $listing->getSection()->getTitle(),
  1555.                 '%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null$locale),
  1556.                 '%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null$locale),
  1557.                 '%view%' => $view,
  1558.                 '%floor%' => $this->numberToWordService->convertToRank($listing->getAttribute('floor')->getValue(), $locale),
  1559.                 '%rooms%' => $this->numberToWordService->convertToNum($listing->getAttribute('rooms')->getValue(), $locale),
  1560.                 '%baths%' => $this->numberToWordService->convertToNum($listing->getAttribute('baths')->getValue(), $locale),
  1561.             ], null$locale);
  1562.         }
  1563.         if (
  1564.             $listing->getArea()
  1565.         ) {
  1566.             $listingDescriptions[] = $this->translator->trans('layout.description.translations.property_type_section_location_size_view', [
  1567.                 '%in%' => $listing->getLocation() ? $this->translator->trans('listing.in', [], null$locale) : null,
  1568.                 '%location%' => $listing->getLocation() ? $listing->getLocation()->getTitle() : null,
  1569.                 '%property_type%' => $listing->getPropertyType()->getTitle(),
  1570.                 '%section%' => $listing->getSection()->getTitle(),
  1571.                 '%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null$locale),
  1572.                 '%view%' => $view,
  1573.             ], null$locale);
  1574.         }
  1575.         if (
  1576.             $listing->getLocation()
  1577.             && $listing->getPropertyType()
  1578.             && $listing->getAttribute('finish-type')
  1579.             && $listing->getArea()
  1580.             && $listing->getAttribute('floor')
  1581.             && $listing->getAttribute('rooms')
  1582.             && $listing->getAttribute('baths')
  1583.         ) {
  1584.             $listingDescriptions[] = $this->translator->trans('layout.description.translations.propery_type_finish_size_location_view_floor_room_bath', [
  1585.                 '%in%' => $listing->getLocation() ? $this->translator->trans('listing.in', [], null$locale) : null,
  1586.                 '%location%' => $listing->getLocation() ? $listing->getLocation()->getTitle() : null,
  1587.                 '%property_type%' => $listing->getPropertyType()->getTitle(),
  1588.                 '%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null$locale),
  1589.                 '%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null$locale),
  1590.                 '%view%' => $view,
  1591.                 '%floor%' => $this->numberToWordService->convertToRank($listing->getAttribute('floor')->getValue(), $locale),
  1592.                 '%rooms%' => $this->numberToWordService->convertToNum($listing->getAttribute('rooms')->getValue(), $locale),
  1593.                 '%baths%' => $this->numberToWordService->convertToNum($listing->getAttribute('baths')->getValue(), $locale),
  1594.             ], null$locale);
  1595.         }
  1596.         return !empty($listingDescriptions) ? $listingDescriptions[array_rand($listingDescriptions)] : '';
  1597.     }
  1598.     /**
  1599.      * Generate auto-translation to listing address.
  1600.      *
  1601.      * @param null $locale
  1602.      */
  1603.     public function listingAddressTranslations($listing$locale null)
  1604.     {
  1605.         if (null == $locale) {
  1606.             $locale $this->requestStack->getCurrentRequest()->getLocale();
  1607.         }
  1608.         $this->em->refresh($listing->getLocation());
  1609.         if ($listing->getLocation()->getParent()) {
  1610.             $this->em->refresh($listing->getLocation()->getParent());
  1611.             $listingAddresses[] = $listing->getLocation()->getTitle().$this->translator->trans('common.comma_separator', [], null$locale).$listing->getLocation()->getParent()->getTitle();
  1612.         } else {
  1613.             $listingAddresses[] = $listing->getLocation()->getTitle();
  1614.         }
  1615.         return $listingAddresses[array_rand($listingAddresses)];
  1616.     }
  1617.     /**
  1618.      * Get all listing translations.
  1619.      *
  1620.      * @return array
  1621.      */
  1622.     public function getListingTranslations(Listing $listing)
  1623.     {
  1624.         $currentLocale $this->requestStack->getCurrentRequest()->getLocale();
  1625.         $listingTranslations = [];
  1626.         // Store current listing translation
  1627.         $listingTranslations[$currentLocale] = $listing;
  1628.         // Identifying Submitted Locale
  1629.         foreach ($this->locales as $locale) {
  1630.             if ($locale != $currentLocale) {
  1631.                 $this->translatableListener->setTranslatableLocale($locale);
  1632.                 $this->em->refresh($listing);
  1633.                 /* @var $listingTranslation \Aqarmap\Bundle\ListingBundle\Entity\Listing */
  1634.                 $listingTranslations[$locale] = $this->cloneListing($listing);
  1635.             }
  1636.         }
  1637.         $this->translatableListener->setTranslatableLocale($currentLocale);
  1638.         $this->em->refresh($listing);
  1639.         return $listingTranslations;
  1640.     }
  1641.     /**
  1642.      * Validate All listing Photo orders and if empty initialize it.
  1643.      */
  1644.     public function validatePhotoOrders(Listing $listing): void
  1645.     {
  1646.         /** @var ListingPhoto $listingPhoto */
  1647.         foreach ($listing->getPhotos() as $listingPhoto) {
  1648.             if (!$listingPhoto->getOrder()) {
  1649.                 $maxOrder $this->getMaxListingPhotoOrder($listing);
  1650.                 $listingPhoto->setOrder($maxOrder 1);
  1651.                 $this->em->persist($listingPhoto);
  1652.                 $this->em->flush();
  1653.             }
  1654.         }
  1655.     }
  1656.     /**
  1657.      * @param bool $disableDeleted
  1658.      *
  1659.      * @return array
  1660.      *
  1661.      * @throws \Exception
  1662.      *
  1663.      * @internal param array $criteria
  1664.      */
  1665.     public function getUserListingsRatesCounts(User $user$startDate null, ?string $period null$disableDeleted true)
  1666.     {
  1667.         $insights $this->getUserListingsInsights($user$startDate$period$disableDeleted);
  1668.         $averageClickRate $insights['impressions'] ? round(($insights['views'] / $insights['impressions']) * 1002) : 0;
  1669.         $averageContactRate $insights['views'] ? round(($insights['leads'] / $insights['views']) * 1002) : 0;
  1670.         return [
  1671.             'total_leads' => number_format((float) $insights['leads']),
  1672.             'total_views' => number_format((float) $insights['views']),
  1673.             'total_impressions' => number_format((float) $insights['impressions']),
  1674.             'average_click_rate' => $averageClickRate,
  1675.             'average_contact_rate' => $averageContactRate,
  1676.             'average_cost_per_lead' => (float) $insights['average_cost_per_lead'],
  1677.         ];
  1678.     }
  1679.     /**
  1680.      * @param bool $disableDeleted
  1681.      *
  1682.      * @return array
  1683.      *
  1684.      * @throws \Exception
  1685.      *
  1686.      * @internal param array $criteria
  1687.      */
  1688.     public function getUserListingsInsights(User $user$startDate null, ?string $period null$disableDeleted true)
  1689.     {
  1690.         // Count All Leads
  1691.         $leadRepository $this->em->getRepository(Lead::class);
  1692.         $totalLeads $leadRepository->getLeadsCountBaseQueryBuilder([$user->getId()])->getQuery()->getSingleScalarResult() ?? 0;
  1693.         if ($disableDeleted) {
  1694.             if ($this->em->getFilters()->isEnabled('softdeleteable')) {
  1695.                 $this->em->getFilters()->disable('softdeleteable');
  1696.             }
  1697.         }
  1698.         // Count all Impressions
  1699.         $totalImpressions $this->getTotalImpressionsCount([
  1700.             'user' => $user'startDate' => $startDate'period' => $period,
  1701.         ]);
  1702.         // Count All Views
  1703.         $totalViews $this->listingRepository->countViews([
  1704.             'user_id' => $user,
  1705.             'start_date' => $startDate,
  1706.             'end_date' => date('Y-m-d'),
  1707.             'excludeParent' => false,
  1708.             'status' => 'ONLYLIVE' == $period ListingStatus::LIVE null,
  1709.         ]);
  1710.         // Get Average Cost per Lead
  1711.         $averageCostPerLead $this->costPerLeadService->getAverageCostPerLead($user, [
  1712.             'user' => $user,
  1713.             'haveLeads' => true,
  1714.             'status' => 'ONLYLIVE' == $period ListingStatus::LIVE null,
  1715.             'inLastDays' => !empty($period) ? self::MAP_STRING_DAYS_WITH_NUMBER[$period] : null,
  1716.         ]);
  1717.         return [
  1718.             'impressions' => $totalImpressions,
  1719.             'views' => $totalViews,
  1720.             'leads' => $totalLeads,
  1721.             'average_cost_per_lead' => $averageCostPerLead,
  1722.         ];
  1723.     }
  1724.     public function addLiteListingTitleAndDescriptionTranslation(Listing $listing): void
  1725.     {
  1726.         // arabic
  1727.         $listing->setTitle($this->listingTitleTranslations($listing'ar'));
  1728.         $listing->setDescription($this->listingDescriptionTranslations($listing'ar'));
  1729.         $this->saveListing($listingtrue);
  1730.         // english
  1731.         $listing->setTranslatableLocale('en');
  1732.         $this->em->refresh($listing);
  1733.         $listing->setTitle($this->listingTitleTranslations($listing'en'));
  1734.         $listing->setDescription($this->listingDescriptionTranslations($listing'en'));
  1735.         $this->saveListing($listingtrue);
  1736.     }
  1737.     /**
  1738.      * Generate custom pagination.
  1739.      *
  1740.      * @param array $data
  1741.      * @param int $limit
  1742.      * @param null $page
  1743.      * @param array $options
  1744.      * @param array $parameters
  1745.      *
  1746.      * @return \Knp\Component\Pager\Pagination\PaginationInterface
  1747.      */
  1748.     public function generateCustomPagination($data = [], $limit 10$page null$options = [], $parameters = [])
  1749.     {
  1750.         $pagination $this->paginator->paginate(
  1751.             $data,
  1752.             $page,
  1753.             $limit
  1754.         );
  1755.         $pagination->setTotalItemCount(\count($data));
  1756.         foreach ($options as $option => $value) {
  1757.             $pagination->setPaginatorOptions([
  1758.                 $option => $value,
  1759.             ]);
  1760.         }
  1761.         foreach ($parameters as $parameter => $value) {
  1762.             $pagination->setParam($parameter$value);
  1763.             // Adding tab anchor
  1764.             if ($parameters['tab']) {
  1765.                 $pagination->setParam('_fragment'$value);
  1766.             }
  1767.         }
  1768.         return $pagination;
  1769.     }
  1770.     public function generateCustomPaginationForQuery($queryint $limit 10$page 1$options = [], $parameters = [])
  1771.     {
  1772.         $pagination $this->paginator->paginate(
  1773.             $query,
  1774.             $page,
  1775.             $limit
  1776.         );
  1777.         foreach ($options as $option => $value) {
  1778.             $pagination->setPaginatorOptions([
  1779.                 $option => $value,
  1780.             ]);
  1781.         }
  1782.         foreach ($parameters as $parameter => $value) {
  1783.             $pagination->setParam($parameter$value);
  1784.             // Adding tab anchor
  1785.             if ($parameters['tab']) {
  1786.                 $pagination->setParam('_fragment'$value);
  1787.             }
  1788.         }
  1789.         return $pagination;
  1790.     }
  1791.     /**
  1792.      * Handle delete listing action.
  1793.      *
  1794.      * @return ListingAction
  1795.      */
  1796.     public function deleteListingReason(Listing $listing$formData)
  1797.     {
  1798.         switch ($formData['delete_reason']) {
  1799.             case 'sold':
  1800.                 $this->changeStatus($listingListingStatus::SOLDfalse);
  1801.                 $listing->setSourceOfSale($formData['source_of_sale']);
  1802.                 $listing->setFinalPrice($formData['final_selling_price']);
  1803.                 break;
  1804.             case 'other':
  1805.                 $listing->setDeleteReasonDetails($formData['other_reason']);
  1806.                 break;
  1807.         }
  1808.         $listing->setDeleteReason($formData['delete_reason']);
  1809.         $this->em->persist($listing);
  1810.         $this->em->flush();
  1811.         return $listing;
  1812.     }
  1813.     /**
  1814.      * Subtract credit and change status.
  1815.      */
  1816.     public function subtractCreditAndChangeStatus(Listing $listing): void
  1817.     {
  1818.         // Don't double deduct credits
  1819.         if ($listing->getPublicationCredit()) {
  1820.             return;
  1821.         }
  1822.         $listingRule $this->matcher->match($listing);
  1823.         $available_balance $this->creditManager->getBalance($listing->getUser());
  1824.         // Making sure that the approved payment will cover the listing fees
  1825.         if ($listingRule['publication_fees'] <= $available_balance) {
  1826.             $credits $this->creditManager->deduction($listing->getUser(), $listingRule['publication_fees'], 'Listing Fees');
  1827.             foreach ($credits as $credit) {
  1828.                 if ($credit instanceof Credit) {
  1829.                     $credit->setStatus(CreditStatus::SUCCESS);
  1830.                     $this->em->persist($credit);
  1831.                     $this->em->flush($credit);
  1832.                     $this->addFeature($listingListingFeatures::PAIDnull$credit);
  1833.                 }
  1834.             }
  1835.             $listing->setCategory(ListingCategories::PAID);
  1836.             $this->saveListing($listing);
  1837.         }
  1838.     }
  1839.     /**
  1840.      * My Listings Tabs.
  1841.      *
  1842.      * @param float $version
  1843.      *
  1844.      * @return array
  1845.      */
  1846.     public function myListingsTabs($version$user null)
  1847.     {
  1848.         $listingRepository $this->em->getRepository(Listing::class);
  1849.         $expired = [
  1850.             'status' => ListingStatus::EXPIRED,
  1851.             'label' => $this->translator->trans(ListingStatus::EXPIRED_LABEL),
  1852.         ];
  1853.         $pending = [
  1854.             'status' => ListingStatus::PENDING,
  1855.             'label' => $this->translator->trans(ListingStatus::PENDING_LABEL),
  1856.         ];
  1857.         $pendingPhoto = [
  1858.             'status' => ListingStatus::PENDING_PHOTOS,
  1859.             'label' => $this->translator->trans(ListingStatus::PENDING_PHOTOS_LABEL),
  1860.         ];
  1861.         if ($version >= 2.13) {
  1862.             // Live  (Default tab)
  1863.             $tabs[] = [
  1864.                 'id' => 1,
  1865.                 'name' => $this->translator->trans('label.status.live'),
  1866.                 'statuses' => [
  1867.                     [
  1868.                         'status' => ListingStatus::LIVE,
  1869.                         'label' => $this->translator->trans(ListingStatus::LIVE_LABEL),
  1870.                     ],
  1871.                 ],
  1872.             ];
  1873.             // Pending
  1874.             $tabs[] = [
  1875.                 'id' => 2,
  1876.                 'name' => $this->translator->trans('label.status.pending'),
  1877.                 'statuses' => [
  1878.                     $pending,
  1879.                     $pendingPhoto,
  1880.                     [
  1881.                         'status' => ListingStatus::PENDING_PAYMENT,
  1882.                         'label' => $this->translator->trans(ListingStatus::PENDING_PAYMENT_LABEL),
  1883.                     ],
  1884.                 ],
  1885.             ];
  1886.             // Rejected Section
  1887.             $tabs[] = [
  1888.                 'id' => 3,
  1889.                 'name' => $this->translator->trans('label.status.reject'),
  1890.                 'statuses' => [
  1891.                     [
  1892.                         'status' => ListingStatus::REJECTED,
  1893.                         'label' => $this->translator->trans(ListingStatus::REJECTED_LABEL),
  1894.                     ],
  1895.                 ],
  1896.                 'listings_count' => $user instanceof User ?
  1897.                     (int) $listingRepository->ListingsCountQuery(['user_id' => $user->getId(), 'status' => ListingStatus::REJECTED]) : 0,
  1898.             ];
  1899.             // Expired Section
  1900.             $tabs[] = [
  1901.                 'id' => 4,
  1902.                 'name' => $this->translator->trans('label.status.expired'),
  1903.                 'statuses' => [
  1904.                     $expired,
  1905.                     [
  1906.                         'status' => ListingStatus::SOLD,
  1907.                         'label' => $this->translator->trans(ListingStatus::SOLD_LABEL),
  1908.                     ],
  1909.                 ],
  1910.             ];
  1911.             // Deleted Section
  1912.             $tabs[] = [
  1913.                 'id' => 5,
  1914.                 'name' => $this->translator->trans('label.status.user_deleted'),
  1915.                 'statuses' => [
  1916.                     [
  1917.                         'status' => ListingStatus::USER_DELETED,
  1918.                         'label' => $this->translator->trans(ListingStatus::USER_DELETED_LABEL),
  1919.                     ],
  1920.                 ],
  1921.             ];
  1922.         } else {
  1923.             // Live and pending tab (Default tab)
  1924.             $tabs[] = [
  1925.                 'id' => 1,
  1926.                 'name' => $this->translator->trans('layout.my_active_listings'),
  1927.                 'statuses' => [
  1928.                     [
  1929.                         'status' => ListingStatus::LIVE,
  1930.                         'label' => $this->translator->trans(ListingStatus::LIVE_LABEL),
  1931.                     ],
  1932.                     $pending,
  1933.                     $pendingPhoto,
  1934.                     [
  1935.                         'status' => ListingStatus::PENDING_PAYMENT,
  1936.                         'label' => $this->translator->trans(ListingStatus::PENDING_PAYMENT_LABEL),
  1937.                     ],
  1938.                 ],
  1939.             ];
  1940.             // Expired Section
  1941.             $tabs[] = [
  1942.                 'id' => 2,
  1943.                 'name' => $this->translator->trans('layout.my_expired_listings'),
  1944.                 'statuses' => [
  1945.                     $expired,
  1946.                     [
  1947.                         'status' => ListingStatus::SOLD,
  1948.                         'label' => $this->translator->trans(ListingStatus::SOLD_LABEL),
  1949.                     ],
  1950.                 ],
  1951.             ];
  1952.             // Deleted Section
  1953.             $tabs[] = [
  1954.                 'id' => 3,
  1955.                 'name' => $this->translator->trans('layout.my_deleted_listings'),
  1956.                 'statuses' => [
  1957.                     [
  1958.                         'status' => ListingStatus::USER_DELETED,
  1959.                         'label' => $this->translator->trans(ListingStatus::USER_DELETED_LABEL),
  1960.                     ],
  1961.                 ],
  1962.             ];
  1963.         }
  1964.         return $tabs;
  1965.     }
  1966.     /**
  1967.      * Dynamic set listing relations.
  1968.      *
  1969.      * @param array $fields
  1970.      *
  1971.      * @return $this
  1972.      */
  1973.     public function updateRelations($fields)
  1974.     {
  1975.         if (!isset($fields['_relation'])) {
  1976.             return $this;
  1977.         }
  1978.         foreach ($fields['_relation'] as $field => $val) {
  1979.             $field ucwords($field);
  1980.             $entity $this->em->getRepository("AqarmapListingBundle:{$field}")->find($val);
  1981.             $fn 'set'.$field;
  1982.             if (method_exists($this->listing$fn) && $entity) {
  1983.                 $this->listing->$fn($entity);
  1984.             }
  1985.         }
  1986.         return $this;
  1987.     }
  1988.     /**
  1989.      * Update listing.
  1990.      *
  1991.      * @return Listing
  1992.      */
  1993.     public function update(Listing $listing, array $fields)
  1994.     {
  1995.         $this->listing $listing;
  1996.         $this->updateFields($fields)
  1997.             ->updateAttributes($fields)
  1998.             ->updateRelations($fields)
  1999.             ->updatePhone($fields)
  2000.             ->saveListing($listing);
  2001.         return $listing;
  2002.     }
  2003.     /**
  2004.      * Generate auto-translation to listing title in all locales.
  2005.      */
  2006.     public function getAllListingTitleTranslations(Listing $listing)
  2007.     {
  2008.         $listingTitles = [];
  2009.         foreach ($this->locales as $locale) {
  2010.             $this->translatableListener->setTranslatableLocale($locale);
  2011.             $this->em->refresh($listing->getLocation());
  2012.             $this->em->refresh($listing->getPropertyType());
  2013.             $this->em->refresh($listing->getSection());
  2014.             $listingTitles[$locale] = $this->generateTranslatedListingTitle($listing$locale);
  2015.         }
  2016.         return $listingTitles;
  2017.     }
  2018.     /*
  2019.      * Make a listing featured for free.
  2020.      *
  2021.      * @param Listing $listing
  2022.      *
  2023.      * @return bool
  2024.      */
  2025.     public function AddFeaturedListingForFree(Listing $listing$featuredType)
  2026.     {
  2027.         $listingRule $this->listingFeatureService->getFeaturedListingRules($listing, [$featuredType $featuredType 'featured']);
  2028.         $listingOwner $listing->getUser();
  2029.         $credits $this->getCreditManager()->deduction(
  2030.             $listingOwner,
  2031.             (float) 0,
  2032.             'Adding a featured listing for free',
  2033.             true
  2034.         );
  2035.         if (!$listingRule[$featuredType]['duration']) {
  2036.             $listingRule[$featuredType]['duration'] = 10;
  2037.         }
  2038.         $expiredAt null;
  2039.         if (ListingStatus::LIVE == $listing->getStatus()) {
  2040.             if (!$listing->getParent()) {
  2041.                 $expiredAt = new \DateTime('+'.$listingRule[$featuredType]['duration'].' days');
  2042.             } else {
  2043.                 $expiredAt = new \DateTime('+10 years');
  2044.             }
  2045.         }
  2046.         foreach ($credits as $credit) {
  2047.             $this->addFeature($listing$listingRule[$featuredType]['listingFeature'], $expiredAt$credit);
  2048.         }
  2049.         $this->saveListing($listingtrue);
  2050.         return true;
  2051.     }
  2052.     /**
  2053.      * @return bool
  2054.      */
  2055.     public function isPumpUpAvailable(array $listingRules)
  2056.     {
  2057.         $pumpUpFees $listingRules['pump_up_fees'];
  2058.         $pumpUpDuration $listingRules['pump_up_duration'];
  2059.         $pumpUpOccurrence $listingRules['pump_up_occurrence'];
  2060.         if (
  2061.             empty($pumpUpFees)
  2062.             || empty($pumpUpDuration)
  2063.             || empty($pumpUpOccurrence)
  2064.         ) {
  2065.             return false;
  2066.         }
  2067.         return true;
  2068.     }
  2069.     /**
  2070.      * @return bool
  2071.      */
  2072.     public function isAffordable(User $listingOwnerstring $fees)
  2073.     {
  2074.         $creditManager $this->creditManager;
  2075.         $availableBalance $creditManager->getBalance($listingOwner);
  2076.         $settingsManager $this->settings;
  2077.         $enforceCredit $settingsManager->getSetting('features''enforce_credits_expiration');
  2078.         if (
  2079.             $fees $availableBalance
  2080.             || ($enforceCredit && === $listingOwner->getAbsoluteCreditExpiryDays())
  2081.         ) {
  2082.             return false;
  2083.         }
  2084.         return true;
  2085.     }
  2086.     /**
  2087.      * @return array
  2088.      */
  2089.     public function getListingRules(Listing $listing)
  2090.     {
  2091.         return $this->matcher->match($listing);
  2092.     }
  2093.     /**
  2094.      * @return bool
  2095.      */
  2096.     public function isListingEvaluable(Listing $listing)
  2097.     {
  2098.         return $listing->getPropertyType()->getIsEvaluable()
  2099.             && $listing->getPrice()
  2100.             && !\in_array($listing->getStatus(), [ListingStatus::DRAFTListingStatus::REJECTEDListingStatus::ADMIN_DELETEDListingStatus::USER_DELETEDListingStatus::EXPIRED]);
  2101.     }
  2102.     /**
  2103.      * @return LocationStatistics[]|array|null
  2104.      */
  2105.     public function evaluateListingPrice(Listing $listing)
  2106.     {
  2107.         $locationStatisticsRepository $this->em->getRepository('AqarmapNeighborhoodBundle:LocationStatistics');
  2108.         $locationStatistics $locationStatisticsRepository->findBy(['location' => $listing->getLocation()]);
  2109.         if (!$locationStatistics) {
  2110.             /**
  2111.              * @var LocationStatistics $locationStatistics
  2112.              */
  2113.             $locationStatistics $this->getListingNearestStatistics($listing);
  2114.         }
  2115.         return $locationStatistics $locationStatistics->getAvgPrice() : null;
  2116.     }
  2117.     /**
  2118.      * @return bool
  2119.      */
  2120.     public function requiresFeaturingReview(int $featuredType)
  2121.     {
  2122.         $requiresFeaturingApproval $this->settings->getSetting('requires_featuring_approval'$featuredType);
  2123.         if ($requiresFeaturingApproval) {
  2124.             return true;
  2125.         }
  2126.         return false;
  2127.     }
  2128.     /**
  2129.      * @return bool
  2130.      */
  2131.     public function userHasFeesToFeature(User $userListing $listing)
  2132.     {
  2133.         $listingRules $this->listingFeatureService->getFeaturedListingRules($listing, ['featured''premium''sponsored''spotlight''sold_by_owner']);
  2134.         $haveFeesToFeature false;
  2135.         foreach ($listingRules as $listingRule) {
  2136.             if ($fees $listingRule['fees']) {
  2137.                 $haveFeesToFeature $this->isAffordable($user$fees);
  2138.                 if ($haveFeesToFeature) {
  2139.                     break;
  2140.                 }
  2141.             }
  2142.         }
  2143.         return $haveFeesToFeature;
  2144.     }
  2145.     /**
  2146.      * @return array
  2147.      */
  2148.     public function getLeadAnalytics(Listing $listing)
  2149.     {
  2150.         $supportedFeatured array_keys(ListingFeaturedTypes::getAnalyticsChoices());
  2151.         $dataLayer['listingSection'] = $listing->getSection() ? $listing->getSection()->getSlug() : null;
  2152.         if ($listing->getIsTopPicks()) {
  2153.             $dataLayer['listingSegment'] = self::TOP_PICKS_LABEL;
  2154.             return $dataLayer;
  2155.         }
  2156.         if (\in_array($listing->getFeatured(), $supportedFeatured)) {
  2157.             $dataLayer['listingSegment'] = ListingFeaturedTypes::getAnalyticsLabel($listing->getFeatured());
  2158.             return $dataLayer;
  2159.         }
  2160.         array_keys(ListingCategories::getAnalyticsChoices());
  2161.         if (\in_array($listing->getCategory(), $supportedFeatured)) {
  2162.             $dataLayer['listingSegment'] = ListingCategories::getAnalyticsLabel($listing->getCategory());
  2163.             return $dataLayer;
  2164.         }
  2165.         if ($listing->getPendingPaymentStatus()) {
  2166.             $dataLayer['listingSegment'] = 'passfree';
  2167.             return $dataLayer;
  2168.         }
  2169.         return $dataLayer;
  2170.     }
  2171.     public function pauseFeatured(Listing $listing): void
  2172.     {
  2173.         $listingFeatures $listing->getListingFeatures();
  2174.         $today = new \DateTime();
  2175.         /** @var ListingFeature $listingFeature */
  2176.         foreach ($listingFeatures as $listingFeature) {
  2177.             if (
  2178.                 !$listingFeature->getPausedAt()
  2179.                 && $listingFeature->getExpiresAt() >= $today
  2180.                 && $listingFeature->getCreatedAt() <= $today
  2181.             ) {
  2182.                 $listingFeature->setPausedAt($today);
  2183.                 $this->em->persist($listingFeature);
  2184.             }
  2185.         }
  2186.     }
  2187.     public function unPauseFeatured(Listing $listing): void
  2188.     {
  2189.         $listingFeatures $listing->getListingFeatures();
  2190.         /** @var ListingFeature $listingFeature */
  2191.         foreach ($listingFeatures as $listingFeature) {
  2192.             $pausedAt $listingFeature->getPausedAt();
  2193.             $expiredAt $listingFeature->getExpiresAt();
  2194.             if (!$pausedAt) {
  2195.                 continue;
  2196.             }
  2197.             $remainingDays $pausedAt->diff($expiredAt);
  2198.             $newExpiredAt = (new \DateTime());
  2199.             $newExpiredAt->add($remainingDays);
  2200.             $listingFeature->setExpiresAt($newExpiredAt);
  2201.             $listingFeature->setPausedAt(null);
  2202.             $this->em->persist($listingFeature);
  2203.         }
  2204.     }
  2205.     public function handleListingUnFeaturing(Listing $listing): void
  2206.     {
  2207.         $listing->setFeatured(null);
  2208.         $now = new \DateTime();
  2209.         /** @var ListingFeature $listingFeature */
  2210.         foreach ($listing->getListingFeatures() as $listingFeature) {
  2211.             if ($listingFeature->getExpiresAt() && $listingFeature->getExpiresAt() > $now) {
  2212.                 $listingFeature->setExpiresAt($now);
  2213.                 $this->em->persist($listingFeature);
  2214.             }
  2215.         }
  2216.         $this->em->flush();
  2217.     }
  2218.     /**
  2219.      * Check If  Listing Featuring Rejection Reason is exist.
  2220.      *
  2221.      **/
  2222.     public function isRejectionReasonExistPerListing(Listing $listingRejection $rejection)
  2223.     {
  2224.         $currentRejections $listing->getRejections();
  2225.         foreach ($currentRejections as $currentRejection) {
  2226.             if ($rejection == $currentRejection) {
  2227.                 return true;
  2228.             }
  2229.         }
  2230.         return false;
  2231.     }
  2232.     /**
  2233.      * Set Listing Featuring Rejection Reason.
  2234.      *
  2235.      **/
  2236.     public function setListingFeaturingRejectionReason(Listing $listingRejection $rejection): void
  2237.     {
  2238.         $existRejection $this->isRejectionReasonExistPerListing($listing$rejection);
  2239.         if (!$existRejection) {
  2240.             $listing->addRejection($rejection);
  2241.             $this->em->persist($listing);
  2242.             $this->em->flush();
  2243.         }
  2244.     }
  2245.     public function setUserActivities(?array $listings, ?array $listingsIds): ?array
  2246.     {
  2247.         $this->userManager $this->getUserManagerService();
  2248.         if (!$listingsIds || !$listings || !$this->userManager->isLoggedIn()) {
  2249.             return $listings;
  2250.         }
  2251.         $criteria = [
  2252.             'user' => $this->tokenStorage->getToken()->getUser(),
  2253.             'listingsIds' => $listingsIds,
  2254.         ];
  2255.         $listings $this->setlistingNote($listings$criteria);
  2256.         return $this->setFavouriteListings($listings$criteria);
  2257.     }
  2258.     public function setUserActivitiesFromPagination(SlidingPagination $pagination): SlidingPagination
  2259.     {
  2260.         $listings = [];
  2261.         $listingsIds = [];
  2262.         $items $pagination->getItems();
  2263.         $userKey null;
  2264.         $favouriteOwner null;
  2265.         $item null;
  2266.         foreach ($items as $item) {
  2267.             if ($item instanceof NonListingLeadInterface) {
  2268.                 continue;
  2269.             }
  2270.             $listingsIds[] = $item->getListing()->getId();
  2271.             $listings[] = $item->getListing();
  2272.         }
  2273.         if ($item) {
  2274.             $favouriteOwner $item->getUser()->getFullName();
  2275.             if (method_exists($item'getUserKey')) {
  2276.                 $userKey $item->getUserKey();
  2277.             }
  2278.         }
  2279.         $listingsWithActivities $this->setUserActivities($listings$listingsIds);
  2280.         $pagination->setItems($listingsWithActivities);
  2281.         $pagination->setCustomParameters([
  2282.             'user_key' => $userKey,
  2283.             'favourite_owner' => $favouriteOwner,
  2284.         ]);
  2285.         return $pagination;
  2286.     }
  2287.     public function updateRateStatus(Listing $listing, ?int $status)
  2288.     {
  2289.         $listing->setRateStatus($status);
  2290.         return $this->persist($listing);
  2291.     }
  2292.     public function updateIsRateReviewed(Listing $listingbool $isRateReviewed)
  2293.     {
  2294.         $listing->setIsRateReviewed($isRateReviewed);
  2295.         return $this->persist($listing);
  2296.     }
  2297.     public function persist(Listing $listing)
  2298.     {
  2299.         $this->em->persist($listing);
  2300.         return $this->em->flush();
  2301.     }
  2302.     /**
  2303.      * @return Response
  2304.      */
  2305.     public function export(IterableResult $listings)
  2306.     {
  2307.         $handle fopen('php://memory''r+');
  2308.         fputcsv($handle, [
  2309.             'Listing ID',
  2310.             'Title',
  2311.             'Status',
  2312.             'Last Action Date',
  2313.             'User ID',
  2314.             'User Email',
  2315.         ]);
  2316.         foreach ($listings as $listing) {
  2317.             $listing current($listing)['listing'];
  2318.             fputcsv($handle, [
  2319.                 $listing->getId(),
  2320.                 $listing->getTitle(),
  2321.                 $this->translator->trans($listing->getStatusLabel()),
  2322.                 $listing->getUpdatedAt() ? $listing->getUpdatedAt()->format('Y-m-d H:i:s') : null,
  2323.                 $listing->getUser()->getId(),
  2324.                 $listing->getUser()->getEmail(),
  2325.             ]);
  2326.             $this->em->detach($listing);
  2327.         }
  2328.         rewind($handle);
  2329.         $content stream_get_contents($handle);
  2330.         fclose($handle);
  2331.         return new Response($contentResponse::HTTP_OK, [
  2332.             'Content-Type' => 'application/force-download',
  2333.             'Content-Disposition' => 'attachment; filename="Paid Listings - '.date('Y-m-d H-i-s').'.csv"',
  2334.         ]);
  2335.     }
  2336.     public function findIterable(array $criteria)
  2337.     {
  2338.         return $this
  2339.             ->em
  2340.             ->getRepository(Listing::class)
  2341.             ->search($criteria)
  2342.             ->getQuery()
  2343.             ->iterate();
  2344.     }
  2345.     /**
  2346.      * @return Listing
  2347.      */
  2348.     public function updateRejectedAt(Listing $listing, \DateTime $rejectedAt)
  2349.     {
  2350.         $listing->setRejectedAt($rejectedAt);
  2351.         $this->saveListing($listing);
  2352.         return $listing;
  2353.     }
  2354.     /**
  2355.      * Get Range Of Percentage Value.
  2356.      */
  2357.     public function getRangeOfPercentageOfValue(?int $value nullint $percentage 10): array
  2358.     {
  2359.         $valueToGetRange = ($percentage 100) * $value;
  2360.         return [
  2361.             'min' => round($value $valueToGetRange),
  2362.             'max' => round($value $valueToGetRange),
  2363.         ];
  2364.     }
  2365.     /**
  2366.      * Get Similar Listings Data.
  2367.      *
  2368.      * @return array
  2369.      */
  2370.     public function getSimilarListingsData(Listing $listingint $limit 10)
  2371.     {
  2372.         $similarListingsCriteria $this->prepareSimilarListingsCriteria($listing);
  2373.         return $this->listingRepository->getSimilarListingsData($similarListingsCriteria$limit)->getResult();
  2374.     }
  2375.     /**
  2376.      * Get Similar Listings Data.
  2377.      */
  2378.     public function countSimilarListings(Listing $listingint $limit 10): int
  2379.     {
  2380.         $similarListingsCriteria $this->prepareSimilarListingsCriteria($listing);
  2381.         return (int) $this->listingRepository->getSimilarListingsData($similarListingsCriteria$limittrue)->getSingleScalarResult();
  2382.     }
  2383.     /**
  2384.      * @throws ORMException
  2385.      * @throws OptimisticLockException
  2386.      */
  2387.     public function setProjectUnitsOwner(Listing $project): void
  2388.     {
  2389.         if (!$project->isProject() || !$project->getChildren()) {
  2390.             return;
  2391.         }
  2392.         $owner $project->getUser();
  2393.         /** @var Listing $unit */
  2394.         foreach ($project->getChildren() as $unit) {
  2395.             $unit->setUser($owner);
  2396.             $this->em->persist($unit);
  2397.             $this->em->flush();
  2398.         }
  2399.     }
  2400.     public function incrementViewsCounters(Listing $listing): void
  2401.     {
  2402.         $listing->setViews($listing->getViews() + 1);
  2403.         $this->em->persist($listing);
  2404.         $this->em->flush();
  2405.     }
  2406.     /**
  2407.      * @return SlidingPagination
  2408.      */
  2409.     public function setStaticNotesFromPaginator(SlidingPagination $pagination, array $listingsWithNotes)
  2410.     {
  2411.         $items $pagination->getItems();
  2412.         $listings = [];
  2413.         /* @var Listing|null $item */
  2414.         foreach ($items as $listing) {
  2415.             if ($listing instanceof Listing) {
  2416.                 $listing->setUserNote($this->getStaticNoteByListingId($listing->getId(), $listingsWithNotes));
  2417.                 $listings[] = $listing;
  2418.             }
  2419.         }
  2420.         $pagination->setItems($listings);
  2421.         return $pagination;
  2422.     }
  2423.     public function getOtherUnits(Listing $listing, ?string $locale 'en'): array
  2424.     {
  2425.         $otherUnits $this->em->getRepository(Listing::class)->getOtherUnits($listing$locale) ?? [];
  2426.         return array_group_by($otherUnits'propertyTypeTitle');
  2427.     }
  2428.     /**
  2429.      * Publish Sync Project Listing to Queue.
  2430.      */
  2431.     public function publishSyncProjectListingToQueue(array $criteria): void
  2432.     {
  2433.         $this->messageBus->dispatch(new SyncListingProjectMessage($criteria));
  2434.     }
  2435.     /**
  2436.      * Deduct Credit.
  2437.      *
  2438.      * @return Response
  2439.      */
  2440.     public function deductCredit(array $criteria)
  2441.     {
  2442.         $listingRule $this->matcher->match($criteria['listing']);
  2443.         $userPackages $this->em->getRepository(\Aqarmap\Bundle\UserBundle\Entity\UserPackages::class);
  2444.         $available_credit $userPackages->getTotalCreditsByUser($criteria['user']);
  2445.         $enforceCredit $this->settings->getSetting('features''enforce_credits_expiration');
  2446.         $this->translator->setLocale($criteria['locale']);
  2447.         if ($enforceCredit && === $criteria['user']->getAbsoluteCreditExpiryDays()) {
  2448.             return [
  2449.                 'status' => 'credit_can_not_use',
  2450.                 'message' => $this->translator->trans('credit.credit_can_not_use_without_link'),
  2451.             ];
  2452.         }
  2453.         if ($criteria['listing']->getPublicationCredit()) {
  2454.             return [
  2455.                 'status' => 'already_paid',
  2456.                 'message' => $this->translator->trans('credit.already_paid'),
  2457.             ];
  2458.         }
  2459.         if ($listingRule['publication_fees'] > $available_credit) {
  2460.             return [
  2461.                 'status' => 'not_enough_credits',
  2462.                 'message' => $this->translator->trans('credit.not_enough_credits'),
  2463.             ];
  2464.         }
  2465.         $credits $this->creditManager
  2466.             ->deduction(
  2467.                 $criteria['listing']->getUser(),
  2468.                 $listingRule['publication_fees'],
  2469.                 'Listing Fees'
  2470.             );
  2471.         if ($credits) {
  2472.             foreach ($credits as $credit) {
  2473.                 if ($credit instanceof Credit) {
  2474.                     $credit->setStatus(CreditStatus::SUCCESS);
  2475.                     $this->addFeature($criteria['listing'], ListingFeatures::PAIDnull$credit);
  2476.                 }
  2477.             }
  2478.             return [
  2479.                 'status' => 'credit_deducted_successfully',
  2480.                 'message' => $this->translator->trans('credit.credit_deducted_successfully'),
  2481.             ];
  2482.         }
  2483.         return [
  2484.             'status' => 'something_wrong_happened',
  2485.             'message' => 'Something Wrong happened While deduction',
  2486.         ];
  2487.     }
  2488.     /**
  2489.      * @return float|int
  2490.      *
  2491.      * @throws \Doctrine\ODM\MongoDB\MongoDBException
  2492.      * @throws \Exception
  2493.      */
  2494.     public function getApprovalRejectionWaitingTime(array $criteria)
  2495.     {
  2496.         $activityRepo $this->dm->getRepository(ListingActivityLog::class);
  2497.         $approvalsCount $activityRepo->filter($criteriatrue)->execute();
  2498.         $arwtResult $activityRepo->getSumActionTime($criteria)->execute()->getSingleResult();
  2499.         $arwt = isset($arwtResult$arwtResult['actionTime']) ? $arwtResult['actionTime'] : 0;
  2500.         return $approvalsCount $arwt $approvalsCount 0;
  2501.     }
  2502.     public function delete(Listing $listing, array $reasons): void
  2503.     {
  2504.         $this->changeStatus($listingListingStatus::USER_DELETEDfalse);
  2505.         $listing->setDeleteReasonDetails($reasons['other_reason']);
  2506.         $listing->setDeleteReason($reasons['reason']);
  2507.         $listing->setFinalPrice($reasons['final_price']);
  2508.         $listing->setSourceOfSale($reasons['source_of_sale']);
  2509.         $this->em->persist($listing);
  2510.         $this->em->flush();
  2511.     }
  2512.     public function handleListingSortingOptions(): array
  2513.     {
  2514.         $sortingOptions ListingSortingOptions::buildSortingOptions();
  2515.         /**
  2516.          * @var ListingSortingValues $sortingOption
  2517.          */
  2518.         foreach ($sortingOptions as $sortingOption) {
  2519.             $options = [];
  2520.             foreach ($sortingOption->getOptions() as $option => $value) {
  2521.                 $options[] = new ListingSortingOption($this->translator->trans($option), $value);
  2522.             }
  2523.             $sortingOption->setOptions($options);
  2524.         }
  2525.         return $sortingOptions;
  2526.     }
  2527.     /**
  2528.      * Prepare Listing Statuses Array.
  2529.      *
  2530.      * @param array $userListingStatuses
  2531.      */
  2532.     public function prepareListingStatusesArray($userListingStatuses): array
  2533.     {
  2534.         return [
  2535.             ['status' => ListingStatus::getName(ListingStatus::LIVE), 'count' => 0],
  2536.             ['status' => ListingStatus::getName(ListingStatus::PENDING), 'count' => 0],
  2537.             ['status' => ListingStatus::getName(ListingStatus::DRAFT), 'count' => 0],
  2538.             ['status' => 'deleted''count' => 0],
  2539.             ['status' => ListingStatus::getName(ListingStatus::EXPIRED), 'count' => 0],
  2540.             ['status' => ListingStatus::getName(ListingStatus::REJECTED), 'count' => $userListingStatuses['count']],
  2541.         ];
  2542.     }
  2543.     /**
  2544.      * @return bool
  2545.      */
  2546.     public function hasValidFreeListing(User $userListing $listing)
  2547.     {
  2548.         if ($this->em->getRepository(Listing::class)
  2549.             ->hasValidListing($user$listing)
  2550.         ) {
  2551.             return true;
  2552.         }
  2553.         return false;
  2554.     }
  2555.     /**
  2556.      * @return bool
  2557.      */
  2558.     public function isAvailableToAddFreeListing(string $phoneint $userId, ?Listing $listing null)
  2559.     {
  2560.         $freeListingRepository $this->em->getRepository(FreeListing::class);
  2561.         $isPhoneExist $freeListingRepository->findBy(['phoneNumber' => $phone], ['id' => 'DESC'], 10);
  2562.         if ((!empty($isPhoneExist) && $this->getDatesDifferenceInMonth($isPhoneExist[0]->getCreatedAt()) < self::MONTH_RANGE)
  2563.             || ($listing && $listing->getPublicationCredit())
  2564.         ) {
  2565.             return false;
  2566.         }
  2567.         $user $this->em->getRepository(User::class)->findOneBy(['id' => $userId]);
  2568.         $otpService $this->otpService;
  2569.         if ($otpService->isCodeSentForListing($listing)) {
  2570.             return true;
  2571.         }
  2572.         return $otpService->sendVerificationCode($user$phone$listing);
  2573.     }
  2574.     public function createFirstListingForFree(Listing $listing): void
  2575.     {
  2576.         $listing->setCategory(ListingCategories::FIRST_LISTING_FOR_FREE);
  2577.         $listingEvent = new ListingEvent($listing);
  2578.         $this->dispatcher->dispatch($listingEvent'aqarmap.listing.submitted');
  2579.         $this->saveListing($listing);
  2580.         $credits $this->creditManager->deduction(
  2581.             $listing->getUser(),
  2582.             0,
  2583.             CreditDescriptions::FIRST_LISTING_FOR_FREE_LABEL,
  2584.             CreditStatus::SUCCESS
  2585.         );
  2586.         foreach ($credits as $credit) {
  2587.             if ($credit instanceof Credit) {
  2588.                 $this->addFeature($listingListingFeatures::PAIDnull$credit);
  2589.             }
  2590.         }
  2591.         $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.first_listing_for_free');
  2592.     }
  2593.     public function isListingUserEbawab(Listing $listing): bool
  2594.     {
  2595.         return $this->isListingEmailIncludes($listing'ebawab@aqarmap.com');
  2596.     }
  2597.     public function isListingUserManualScraped(Listing $listing): bool
  2598.     {
  2599.         return $this->isListingEmailIncludes($listing'+cc@aqarmap.com');
  2600.     }
  2601.     /**
  2602.      * Update listing seller role.
  2603.      */
  2604.     public function updateSellerRole(User $userint $sellerRole): void
  2605.     {
  2606.         if ($this->em->getFilters()->isEnabled('softdeleteable')) {
  2607.             $this->em->getFilters()->disable('softdeleteable');
  2608.         }
  2609.         $this->listingRepository->updateSellerRole($user$sellerRole);
  2610.         $this->em->getFilters()->enable('softdeleteable');
  2611.     }
  2612.     /**
  2613.      * Get listings by ids sorted as is.
  2614.      *
  2615.      * @return QueryBuilder
  2616.      */
  2617.     public function getListingsByIdsSortedAsIs(array $parentIds)
  2618.     {
  2619.         $commaSeparatedParentIds implode(','$parentIds);
  2620.         return $this->listingRepository->getListingsByIdsSortedAsIs($parentIds$commaSeparatedParentIds);
  2621.     }
  2622.     public function setEligibleMortgageToNull(Listing $listing): void
  2623.     {
  2624.         // force delete EligibleForMortgage to null
  2625.         $listing->setEligibleForMortgage();
  2626.         $this->saveListing($listing);
  2627.     }
  2628.     public function approveListing(Listing $listing): void
  2629.     {
  2630.         if (!\in_array($listing->getStatus(), self::ALLOWED_STATUS_FOR_APPROVAL)) {
  2631.             return;
  2632.         }
  2633.         $this->changeStatus($listingListingStatus::LIVE);
  2634.         if ($listing->getParent()) {
  2635.             $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.project.re_calculate.min_price_area');
  2636.             $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.project.calculate.average.pricePerMeter');
  2637.         }
  2638.     }
  2639.     public function extractListingCriteria(Listing $listing): array
  2640.     {
  2641.         $criteria = [];
  2642.         $criteria['status'] = $listing->getStatus();
  2643.         $criteria['section'] = $listing->getSection()->getId();
  2644.         $criteria['propertyType'] = $listing->getPropertyType()->getId();
  2645.         $criteria['location'] = $listing->getLocation()->getId();
  2646.         $criteria['price']['min'] = $listing->getPrice() - $listing->getPrice() / 30;
  2647.         $criteria['price']['max'] = $listing->getPrice() + $listing->getPrice() / 30;
  2648.         $criteria['orderBy'] = ['l.publishedAt' => 'DESC'];
  2649.         $criteria['excludedListingsIds'][] = $listing->getId();
  2650.         return $criteria;
  2651.     }
  2652.     public function getLiveListingsCountPerUser(array $listings): array
  2653.     {
  2654.         if (empty($listings)) {
  2655.             return [];
  2656.         }
  2657.         $listingsUserIds = [];
  2658.         foreach ($listings as $listing) {
  2659.             if (!$listing instanceof Listing) {
  2660.                 return [];
  2661.             }
  2662.             $listingsUserIds[] = $listing->getUser()->getId();
  2663.         }
  2664.         if ($this->em->getFilters()->isEnabled('softdeleteable')) {
  2665.             $this->em->getFilters()->disable('softdeleteable');
  2666.         }
  2667.         $listingCountPerUser $this->listingRepository->getLiveListingsCountPerUser(array_unique($listingsUserIds));
  2668.         $this->em->getFilters()->enable('softdeleteable');
  2669.         return $this->turnIntoUserToListingsMap($listingCountPerUser);
  2670.     }
  2671.     /**
  2672.      * @return float
  2673.      */
  2674.     public function getListingsPhotosAverage(User $user)
  2675.     {
  2676.         $criteria = ['user_id' => $user->getId(), 'status' => ListingStatus::LIVE];
  2677.         $listingsCount $this->listingRepository->ListingsCountQuery($criteria);
  2678.         $photosCount $this->listingPhotoRepository->getListingsPhotosCount($criteria);
  2679.         if (empty($photosCount) || empty($listingsCount)) {
  2680.             return 0;
  2681.         }
  2682.         $average = (float) $photosCount $listingsCount;
  2683.         return (float) number_format($averageself::DECIMALS_PLACES_NUMBER);
  2684.     }
  2685.     /**
  2686.      * @throws OptimisticLockException
  2687.      * @throws ORMException
  2688.      */
  2689.     public function initializeListingForCloning(Listing $targetListingPropertyType $propertyTypeSection $sectionUserInterface $user): Listing
  2690.     {
  2691.         $listing = new Listing();
  2692.         $listing->setLocation($targetListing->getLocation());
  2693.         $listing->setPropertyType($propertyType);
  2694.         $listing->setSection($section);
  2695.         $listing->setUser($user);
  2696.         $listing->setStatus(ListingStatus::DRAFT);
  2697.         if ($targetListing->getParent()) {
  2698.             $listing->setMarketPropertyType(MarketPropertyTypes::PRIMARY);
  2699.             $listing->setPrice($targetListing->getPrice());
  2700.             $listing->setArea($targetListing->getArea());
  2701.             $listing->setPaymentMethod($targetListing->getPaymentMethod());
  2702.             $listing->setPropertyView($targetListing->getPropertyView());
  2703.         } elseif (!$this->listingRepository->isCompound($targetListing)) {
  2704.             $listing $this->cloneAttributes($targetListing$listing);
  2705.             $listing->setPaymentMethod($targetListing->getPaymentMethod());
  2706.             $listing->setMarketPropertyType($targetListing->getMarketPropertyType());
  2707.         }
  2708.         $this->saveListing($listing);
  2709.         return $listing;
  2710.     }
  2711.     public function cloneAttributes(Listing $sourceListingListing $targetListing): Listing
  2712.     {
  2713.         foreach ($sourceListing->getAttributes() as $attribute) {
  2714.             $targetAttribute = new ListingAttribute();
  2715.             $targetAttribute->setCustomField($attribute->getCustomField());
  2716.             $targetAttribute->setValue($attribute->getValue());
  2717.             $targetListing->addAttribute($targetAttribute);
  2718.         }
  2719.         return $targetListing;
  2720.     }
  2721.     public function buildUnitCustomAttributes(Listing $entity)
  2722.     {
  2723.         $custom_fields $this->em->getRepository(\Aqarmap\Bundle\ListingBundle\Entity\CustomField::class)->findAll();
  2724.         $exists_attributes = [];
  2725.         foreach ($entity->getAttributes() as $listing_attribute) {
  2726.             if (\in_array($listing_attribute->getCustomField()->getName(), ListingCustomFields::getFormattedAttributesNames())) {
  2727.                 $exists_attributes[] = $listing_attribute->getCustomField()->getId();
  2728.             }
  2729.         }
  2730.         foreach ($custom_fields as $custom_field) {
  2731.             if (\in_array($custom_field->getId(), $exists_attributes)) {
  2732.                 continue;
  2733.             }
  2734.             if (\in_array($custom_field->getName(), ListingCustomFields::getFormattedAttributesNames())) {
  2735.                 $attribute = new ListingAttribute();
  2736.                 $attribute
  2737.                     ->setCustomField($custom_field)
  2738.                     ->setListing($entity);
  2739.                 $this->em->persist($attribute);
  2740.                 $entity->addAttribute($attribute);
  2741.             }
  2742.         }
  2743.         return $entity;
  2744.     }
  2745.     public function addLiteListingTitleTranslation(Listing $listingListing $sourceListing): void
  2746.     {
  2747.         $listing->setTitle($this->unitTitleTranslations($listing$sourceListingLocales::AR));
  2748.         $this->saveListing($listingtrue);
  2749.         $listing->setTranslatableLocale(Locales::EN);
  2750.         $this->em->refresh($listing);
  2751.         $listing->setTitle($this->unitTitleTranslations($listing$sourceListingLocales::EN));
  2752.         $this->saveListing($listingtrue);
  2753.     }
  2754.     /**
  2755.      * @return Listing
  2756.      */
  2757.     public function syncProject(Listing $listingListing $sourceListing)
  2758.     {
  2759.         $listing->setStatus(ListingStatus::PENDING);
  2760.         $listing->setUser($sourceListing->getUser());
  2761.         $listing->setVideoUrl($sourceListing->getVideoUrl());
  2762.         $listing->setLocation($sourceListing->getLocation());
  2763.         $listing->setExpiresAt($sourceListing->getExpiresAt());
  2764.         $listing->setCenterLat($sourceListing->getCenterLat());
  2765.         $listing->setCenterLng($sourceListing->getCenterLng());
  2766.         $listing->setSellerRole($sourceListing->getSellerRole());
  2767.         $listing->setIsCommercial($sourceListing->isCommercial());
  2768.         $listing->setPaymentMethod($sourceListing->getPaymentMethod());
  2769.         return $listing;
  2770.     }
  2771.     public function addListingPhotos(Listing $listing, array $photos): array
  2772.     {
  2773.         $listingPhotos = [];
  2774.         $maxListingPhotoOrder $this->getMaxListingPhotoOrder($listing);
  2775.         foreach ($photos as $index => $file) {
  2776.             $photo = new Photo();
  2777.             $photo->setFile($file);
  2778.             $listingPhoto $this->addListingPhoto($listing$photo$maxListingPhotoOrder$index);
  2779.             $listingPhotos[] = $listingPhoto;
  2780.             $listing->addPhoto($listingPhoto);
  2781.         }
  2782.         return $listingPhotos;
  2783.     }
  2784.     public function getSerializedLocationChildren(Location $location)
  2785.     {
  2786.         $locationChildren $location $location->getChildren() : [];
  2787.         return $this->serializer->serialize(
  2788.             $locationChildren,
  2789.             'json',
  2790.             SerializationContext::create()
  2791.                 ->setGroups('MiniList')
  2792.                 ->enableMaxDepthChecks()
  2793.         );
  2794.     }
  2795.     public function getListingPropertyTypesChips(Request $request, ?Location $location null, ?Section $section null, ?PropertyType $propertyType null)
  2796.     {
  2797.         foreach (['location''section''propertyType'] as $param) {
  2798.             $request->query->remove($param);
  2799.         }
  2800.         if ($location) {
  2801.             $request->query->set('location'$location->getId());
  2802.         }
  2803.         if ($section) {
  2804.             $request->query->set('section'$section->getId());
  2805.         }
  2806.         if ($propertyType) {
  2807.             $request->query->set('propertyType'$propertyType->getId());
  2808.         }
  2809.         $result = [];
  2810.         $types $this->getPropertiesChipsElasticResponse($request);
  2811.         $typesArray = [];
  2812.         foreach ($types as $id => $doc_count) {
  2813.             $typesArray[$id] = $doc_count;
  2814.         }
  2815.         arsort($typesArray);
  2816.         $propertyTypes $this->propertyTypeRepository->findPropertyTypes(array_keys($typesArray));
  2817.         foreach ($propertyTypes as $propertyType) {
  2818.             $id $propertyType->getId();
  2819.             $result[] = [
  2820.                 'id' => $id,
  2821.                 'title' => $propertyType->getTitle(),
  2822.                 'slug' => $propertyType->getSlug(),
  2823.                 'searchResultCount' => $typesArray[$id] ?? 0,
  2824.                 'level' => $propertyType->getLevel(),
  2825.             ];
  2826.         }
  2827.         return $result;
  2828.     }
  2829.     public function getLocationParents(Location $locaionstring $locale Locales::AR)
  2830.     {
  2831.         return $this->locationRepository->getParentsBreadCrumbData($locaion$locale)->getResult();
  2832.     }
  2833.     public function getSerializedCustomParagraph(Section $sectionPropertyType $propertyType, ?Location $location null)
  2834.     {
  2835.         $customParagraph $this->customParagraphRepository->get([
  2836.             'location' => $location $location->getId() : null,
  2837.             'propertyType' => $propertyType->getId(),
  2838.             'section' => $section->getId(),
  2839.             'place' => CustomParagraphPlaceTypes::SEARCH_RESULTS,
  2840.         ])->getQuery()->setMaxResults(1)->getOneOrNullResult() ?? [];
  2841.         return $this->serializer->serialize(
  2842.             $customParagraph,
  2843.             'json',
  2844.             SerializationContext::create()
  2845.                 ->setGroups('Default')
  2846.                 ->enableMaxDepthChecks()
  2847.         );
  2848.     }
  2849.     public function getFaqs(Section $sectionPropertyType $propertyType, ?Location $location null$locale Locales::AR)
  2850.     {
  2851.         $searchFaqsCriteria = new ListingSearchFaqsCriteria();
  2852.         $searchFaqsCriteria->setSection($section->getId());
  2853.         $searchFaqsCriteria->setPropertyType($propertyType->getId());
  2854.         $searchFaqsCriteria->setLocation($location $location->getId() : null);
  2855.         $faqData $this->listingFaqService->generateFaqData($searchFaqsCriteria$locale);
  2856.         return [
  2857.             'listings_count' => $faqData->getListingsCount(),
  2858.             'featured_listings_count' => $faqData->getFeaturedListingsCount(),
  2859.             'average_price_per_meter' => $faqData->getAveragePricePerMeter(),
  2860.             'price_change' => $faqData->getPriceChange(),
  2861.             'sub_locations' => $faqData->getSubLocations(),
  2862.             'sub_property_types' => $faqData->getSubPropertyTypes(),
  2863.             'top_companies' => $faqData->getTopCompanies(),
  2864.         ];
  2865.     }
  2866.     public function getSerializedResolvedSlugs(Section $sectionPropertyType $propertyType$locationsSlugs)
  2867.     {
  2868.         $data = [
  2869.             'section' => $section,
  2870.             'propertyType' => $propertyType,
  2871.             'locations' => [],
  2872.         ];
  2873.         if (!empty($locationsSlugs) && $this->validLocationSlugArray($locationsSlugs)) {
  2874.             $data['locations'] = $this->locationRepository->getLocationsBySlugs($locationsSlugs);
  2875.         }
  2876.         return $this->serializer->serialize(
  2877.             $data,
  2878.             'json',
  2879.             SerializationContext::create()
  2880.                 ->setGroups('SlugResolver')
  2881.                 ->enableMaxDepthChecks()
  2882.         );
  2883.     }
  2884.     private function validLocationSlugArray($array): bool
  2885.     {
  2886.         return \count($array) > && array_reduce($array, fn ($carry$item) => $carry && \is_string($item) && '' !== $itemtrue);
  2887.     }
  2888.     private function addListingPhoto(Listing $listingPhoto $photoint $maxOrderint $photoIndex): ListingPhoto
  2889.     {
  2890.         $listingPhoto = new ListingPhoto();
  2891.         $listingPhoto->setFile($photo);
  2892.         $listingPhoto->setCaption($photo->getFile()->getClientOriginalName());
  2893.         $listingPhoto->setOrder($maxOrder $photoIndex 1);
  2894.         $listingPhoto->setListing($listing);
  2895.         return $listingPhoto;
  2896.     }
  2897.     /**
  2898.      * @param Listing
  2899.      */
  2900.     private function logListingIfProject(Listing $listing): void
  2901.     {
  2902.         if ($listing->isProject()) {
  2903.             $this->compoundStatusLogService->insert($listing);
  2904.         }
  2905.     }
  2906.     /**
  2907.      * Check existence of source and target listings.
  2908.      *
  2909.      * @param Listing|null $sourceListing
  2910.      * @param Listing|null $targetListing
  2911.      */
  2912.     private function isValidCriteriaForSyncListings($sourceListing$targetListing): bool
  2913.     {
  2914.         return null !== $sourceListing && null !== $targetListing;
  2915.     }
  2916.     /**
  2917.      * Check if saved time is greater than updated time.
  2918.      *
  2919.      * @param Listing|null $target
  2920.      */
  2921.     private function targetListingUpdatedTimeGreaterThanSavedTime(int $savedAtTimeStampListing $target): bool
  2922.     {
  2923.         $targetListingUpdateAt $target->getUpdatedAt();
  2924.         return $savedAtTimeStamp && $targetListingUpdateAt->getTimestamp() > $savedAtTimeStamp;
  2925.     }
  2926.     /**
  2927.      * @param Listing|null $sourceListing
  2928.      * @param Listing|null $targetListing
  2929.      * @param int $targetSavedAtTimeStamp
  2930.      */
  2931.     private function isReadyForSyncListingForProject($sourceListing$targetListing$targetSavedAtTimeStamp): bool
  2932.     {
  2933.         return $this->isValidCriteriaForSyncListings($sourceListing$targetListing) && !$this->targetListingUpdatedTimeGreaterThanSavedTime($targetSavedAtTimeStamp$targetListing);
  2934.     }
  2935.     /**
  2936.      * @return Listing
  2937.      */
  2938.     private function handleListingFeatureOnSave(Listing $listing)
  2939.     {
  2940.         /**
  2941.          * @var ListingFeature $listingFeature
  2942.          */
  2943.         if ($listingFeature $listing->getCurrentListingFeature()) {
  2944.             $listing->setFeatured($this->listingFeatureService->getListingFeaturedType($listingFeature->getType()));
  2945.         }
  2946.         return $listing;
  2947.     }
  2948.     /**
  2949.      * @return bool
  2950.      */
  2951.     private function isValidPhotoType($type)
  2952.     {
  2953.         if (\in_array($typePhotoTypes::$photoTypes)) {
  2954.             return true;
  2955.         }
  2956.         return false;
  2957.     }
  2958.     /**
  2959.      * @return Listing
  2960.      */
  2961.     private function handleUnlimitedListingOnRelist(Listing $listing)
  2962.     {
  2963.         $user $listing->getUser();
  2964.         if (
  2965.             $this->userServicesManager->hasActiveService(UserServicesType::UNLIMITED_LISTINGS$user)
  2966.             && $this->em->getRepository(\Aqarmap\Bundle\UserBundle\Entity\UserServices::class)->passUnlimitedListing($listing$this->creditManager$this)
  2967.         ) {
  2968.             return $listing;
  2969.         }
  2970.         return $this->handleListingCategory($listing);
  2971.     }
  2972.     /**
  2973.      * @return Listing
  2974.      */
  2975.     private function handleListingCategory(Listing $listing)
  2976.     {
  2977.         $rules $this->getListingRules($listing);
  2978.         if ($rules['publication_fees'] > 0) {
  2979.             $listing->setCategory(ListingCategories::PAID);
  2980.         } else {
  2981.             $listing->setCategory(ListingCategories::NORMAL);
  2982.         }
  2983.         return $listing;
  2984.     }
  2985.     /**
  2986.      * @param array $listingPhones
  2987.      */
  2988.     private function syncPhones(Listing $targetListingListing $sourceListing$listingPhones): void
  2989.     {
  2990.         /** @var ListingPhone $listingPhone */
  2991.         foreach ($listingPhones as $listingPhone) {
  2992.             $listingPhoneRepo $this->em->getRepository(ListingPhone::class);
  2993.             $sourceListingPhone $listingPhoneRepo->findOneBy([
  2994.                 'phone' => $listingPhone->getPhone(),
  2995.                 'listing' => $sourceListing,
  2996.             ]);
  2997.             $targetListingPhone $listingPhoneRepo->findOneBy([
  2998.                 'phone' => $listingPhone->getPhone(),
  2999.                 'listing' => $targetListing,
  3000.             ]);
  3001.             if ($sourceListingPhone && !$targetListingPhone) {
  3002.                 $targetListingPhone = new ListingPhone();
  3003.                 $targetListingPhone
  3004.                     ->setListing($targetListing)
  3005.                     ->setPhone($listingPhone->getPhone());
  3006.                 $targetListing->addPhone($targetListingPhone);
  3007.             } elseif (!$sourceListingPhone && $targetListingPhone) {
  3008.                 $targetListing->removePhone($targetListingPhone);
  3009.             }
  3010.         }
  3011.     }
  3012.     private function syncParticipants(Listing $targetListingListing $sourceListing$participants): void
  3013.     {
  3014.         /** @var User $participant */
  3015.         foreach ($participants as $participant) {
  3016.             if ($sourceListing->hasParticipant($participant) && !$targetListing->hasParticipant($participant)) {
  3017.                 $targetListing->addParticipant($participant);
  3018.             } elseif (!$sourceListing->hasParticipant($participant) && $targetListing->hasParticipant($participant)) {
  3019.                 $targetListing->removeParticipant($participant);
  3020.             }
  3021.         }
  3022.     }
  3023.     /**
  3024.      * Dynamic set listing fields.
  3025.      *
  3026.      * @return $this
  3027.      */
  3028.     private function updateFields(array $fields)
  3029.     {
  3030.         foreach ($fields as $field => $val) {
  3031.             $fn 'set'.ucwords($field);
  3032.             if (method_exists($this->listing$fn)) {
  3033.                 $this->listing->$fn($val);
  3034.             }
  3035.         }
  3036.         return $this;
  3037.     }
  3038.     /**
  3039.      * Dynamic set listing attributes.
  3040.      *
  3041.      * @return $this
  3042.      */
  3043.     private function updateAttributes(array $fields)
  3044.     {
  3045.         foreach ($this->listing->getAttributes() as $attribute) {
  3046.             $key $attribute->getCustomField()->getName();
  3047.             if (isset($fields['_attr'][$key])) {
  3048.                 $attribute->setValue($fields['_attr'][$key]);
  3049.                 $this->listing->addAttribute($attribute);
  3050.             }
  3051.         }
  3052.         return $this;
  3053.     }
  3054.     /**
  3055.      * Set listing phones.
  3056.      *
  3057.      * @param array $fields
  3058.      *
  3059.      * @return $this
  3060.      */
  3061.     private function updatePhone($fields)
  3062.     {
  3063.         if (!isset($fields['_phone'])) {
  3064.             return $this;
  3065.         }
  3066.         $phone $fields['_phone'];
  3067.         $entity $this->em->getRepository(\Aqarmap\Bundle\ListingBundle\Entity\Phone::class)
  3068.             ->find($phone['id']);
  3069.         $entity->setNumber($phone['number']);
  3070.         $this->em->persist($entity);
  3071.         $this->em->flush();
  3072.         return $this;
  3073.     }
  3074.     /**
  3075.      * Generate Translation For Listing Title.
  3076.      *
  3077.      * @param string $locale
  3078.      *
  3079.      * @return array
  3080.      */
  3081.     private function generateTranslatedUnitTitle(Listing $listingListing $sourceListing$locale)
  3082.     {
  3083.         $listingLocation $listing->getLocation();
  3084.         $listingPropertyType $listing->getPropertyType();
  3085.         $listingSection $listing->getSection();
  3086.         $listingArea $listing->getArea();
  3087.         $sourceListing->getTitle();
  3088.         $sizeUnit $this->settings->getSetting('general''measurement_unit');
  3089.         $listingTitles = [];
  3090.         if ($listingPropertyType && $listingSection && $listingArea && $sourceListing) {
  3091.             $listingTitles[] = $this->translator->trans('layout.title.translations.unit_title', [
  3092.                 '%property_type%' => $listingPropertyType->getTitle(),
  3093.                 '%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null$locale),
  3094.                 '%section%' => $listingSection->getTitle(),
  3095.                 '%in%' => $listingLocation $this->translator->trans('listing.in', [], null$locale) : null,
  3096.                 '%parentListingTitle%' => $sourceListing->getTitle() ?: null,
  3097.                 '%parentListingAddress%' => $sourceListing->getAddress() ?: null,
  3098.             ], null$locale);
  3099.         }
  3100.         return $listingTitles[array_rand($listingTitles)];
  3101.     }
  3102.     /**
  3103.      * Generate Translation For Listing Title.
  3104.      *
  3105.      * @param string $locale
  3106.      *
  3107.      * @return array
  3108.      */
  3109.     private function generateTranslatedListingTitle(Listing $listing$locale)
  3110.     {
  3111.         $listingLocation $listing->getLocation();
  3112.         $listingPropertyType $listing->getPropertyType();
  3113.         $listingSection $listing->getSection();
  3114.         $listingArea $listing->getArea();
  3115.         $listingFinishType $listing->getAttribute('finish-type');
  3116.         $sizeUnit $this->settings->getSetting('general''measurement_unit');
  3117.         $listingTitles = [];
  3118.         if ($listingPropertyType && $listingSection && $listingLocation) {
  3119.             $listingTitles[] = $this->translator->trans('layout.title.translations.property_type_section_location', [
  3120.                 '%property_type%' => $listingPropertyType->getTitle(),
  3121.                 '%section%' => $listingSection->getTitle(),
  3122.                 '%in%' => $listingLocation $this->translator->trans('listing.in', [], null$locale) : null,
  3123.                 '%location%' => $listingLocation $listingLocation->getTitle() : null,
  3124.             ], null$locale);
  3125.         }
  3126.         if ($listingPropertyType && $listingSection && $listingLocation && $listingArea) {
  3127.             $listingTitles[] = $this->translator->trans('layout.title.translations.property_type_size_section_location', [
  3128.                 '%property_type%' => $listingPropertyType->getTitle(),
  3129.                 '%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null$locale),
  3130.                 '%section%' => $listingSection->getTitle(),
  3131.                 '%in%' => $listingLocation $this->translator->trans('listing.in', [], null$locale) : null,
  3132.                 '%location%' => $listingLocation $listingLocation->getTitle() : null,
  3133.             ], null$locale);
  3134.         }
  3135.         if ($listingPropertyType && $listingSection && $listingLocation && $listingFinishType) {
  3136.             $listingTitles[] = $this->translator->trans('layout.title.translations.property_type_finish_section_location', [
  3137.                 '%property_type%' => $listingPropertyType->getTitle(),
  3138.                 '%finish%' => $this->translator->trans($listingFinishType->getValue(), [], null$locale),
  3139.                 '%section%' => $listingSection->getTitle(),
  3140.                 '%in%' => $listingLocation $this->translator->trans('listing.in', [], null$locale) : null,
  3141.                 '%location%' => $listingLocation $listingLocation->getTitle() : null,
  3142.             ], null$locale);
  3143.         }
  3144.         if ($listingPropertyType && $listingLocation && $listingFinishType && $listingArea) {
  3145.             $listingTitles[] = $this->translator->trans('layout.title.translations.property_type_size_finish_location', [
  3146.                 '%property_type%' => $listingPropertyType->getTitle(),
  3147.                 '%finish%' => $this->translator->trans($listingFinishType->getValue(), [], null$locale),
  3148.                 '%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null$locale),
  3149.                 '%in%' => $listingLocation $this->translator->trans('listing.in', [], null$locale) : null,
  3150.                 '%location%' => $listingLocation $listingLocation->getTitle() : null,
  3151.             ], null$locale);
  3152.         }
  3153.         if ($listingSection && $listingPropertyType && $listingLocation && $listing->getAttribute('finish-type') && $listing->getArea()) {
  3154.             $listingTitles[] = $this->translator->trans('layout.title.translations.section_property_type_size_finish_location', [
  3155.                 '%section%' => $listingSection->getTitle(),
  3156.                 '%property_type%' => $listingPropertyType->getTitle(),
  3157.                 '%finish%' => $this->translator->trans($listingFinishType->getValue(), [], null$locale),
  3158.                 '%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null$locale),
  3159.                 '%in%' => $listingLocation $this->translator->trans('listing.in', [], null$locale) : null,
  3160.                 '%location%' => $listingLocation $listingLocation->getTitle() : null,
  3161.             ], null$locale);
  3162.         }
  3163.         if ($listingSection && $listingPropertyType && $listingFinishType && $listingArea) {
  3164.             $listingTitles[] = $this->translator->trans('layout.title.translations.section_property_type_size_finish', [
  3165.                 '%section%' => $listingSection->getTitle(),
  3166.                 '%property_type%' => $listingPropertyType->getTitle(),
  3167.                 '%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null$locale),
  3168.                 '%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null$locale),
  3169.             ], null$locale);
  3170.         }
  3171.         if ($listingSection && $listingPropertyType && $listing->getAttribute('finish-type') && $listing->getArea()) {
  3172.             $listingTitles[] = $this->translator->trans('layout.title.translations.property_type_location_size_finish_section', [
  3173.                 '%property_type%' => $listingPropertyType->getTitle(),
  3174.                 '%in%' => $listingLocation $this->translator->trans('listing.in', [], null$locale) : null,
  3175.                 '%location%' => $listingLocation $listingLocation->getTitle() : null,
  3176.                 '%finish%' => $this->translator->trans($listingFinishType->getValue(), [], null$locale),
  3177.                 '%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null$locale),
  3178.                 '%section%' => $listingSection->getTitle(),
  3179.             ], null$locale);
  3180.         }
  3181.         return $listingTitles[array_rand($listingTitles)];
  3182.     }
  3183.     /**
  3184.      * @return float|int|null
  3185.      */
  3186.     private function getListingNearestStatistics(Listing $listing)
  3187.     {
  3188.         $locationStatisticsRepository $this->em->getRepository('AqarmapNeighborhoodBundle:LocationStatistics');
  3189.         $listingLocation $listing->getLocation();
  3190.         $listingPricePerMeter $listing->getPricePerMeter() ?: $listing->calculatePricePerMeter();
  3191.         $listingValuation null;
  3192.         while ($listingLocation) {
  3193.             /**
  3194.              * @var LocationStatistics $locationStatistic
  3195.              */
  3196.             $locationStatistic $locationStatisticsRepository->findBy(['location' => $listingLocation]);
  3197.             if ($this->locationStatisticsManager->isLocationStatisticReliable($locationStatistic)) {
  3198.                 $listingValuation $this->calculateValuationDifference($listingPricePerMeter$locationStatistic->getAvgPrice());
  3199.                 break;
  3200.             }
  3201.             $listingLocation $listingLocation->getParent();
  3202.         }
  3203.         return $listingValuation;
  3204.     }
  3205.     /**
  3206.      * @return float|int
  3207.      */
  3208.     private function calculateValuationDifference($listingPricePerMeter$statisticsPricePerMeter)
  3209.     {
  3210.         return min($listingPricePerMeter$statisticsPricePerMeter) / max($listingPricePerMeter$statisticsPricePerMeter) * 100;
  3211.     }
  3212.     /**
  3213.      * @return array
  3214.      */
  3215.     private function setFavouriteListings(array $listings, array $criteria)
  3216.     {
  3217.         $listingFavouriteIds $this
  3218.             ->em
  3219.             ->getRepository(Favourite::class)
  3220.             ->findByIds($criteria)
  3221.             ->getArrayResult();
  3222.         $hydratedListingFavouriteIds array_map('current'$listingFavouriteIds);
  3223.         foreach ($listings as $listing) {
  3224.             if (\in_array($listing->getId(), $hydratedListingFavouriteIds)) {
  3225.                 $listing->setIsFavourite(true);
  3226.             }
  3227.         }
  3228.         return $listings;
  3229.     }
  3230.     /**
  3231.      * @return array
  3232.      */
  3233.     private function setlistingNote(array $listings, array $criteria)
  3234.     {
  3235.         $listingNotes $this
  3236.             ->em
  3237.             ->getRepository(\Aqarmap\Bundle\ListingBundle\Entity\ListingNote::class)
  3238.             ->findByIds($criteria)
  3239.             ->getArrayResult();
  3240.         $flattenedListingNotes array_column($listingNotes'note''listingId');
  3241.         foreach ($listings as $listing) {
  3242.             if (\array_key_exists($listing->getId(), $flattenedListingNotes)) {
  3243.                 $listing->setUserNote($flattenedListingNotes[$listing->getId()]);
  3244.             }
  3245.         }
  3246.         return $listings;
  3247.     }
  3248.     /**
  3249.      * Prepare Similar Listings Criteria.
  3250.      *
  3251.      * @return array
  3252.      */
  3253.     private function prepareSimilarListingsCriteria(Listing $listing)
  3254.     {
  3255.         $areaRange = !empty($listing->getArea()) ? $this->getRangeOfPercentageOfValue($listing->getArea()) : [];
  3256.         $priceRange = !empty($listing->getPrice()) ? $this->getRangeOfPercentageOfValue((int) $listing->getPrice()) : [];
  3257.         return $criteria = [
  3258.             'section' => $listing->getSection(),
  3259.             'propertyType' => $listing->getPropertyType(),
  3260.             'paymentMethod' => $listing->getPaymentMethod(),
  3261.             'minArea' => $areaRange['min'] ?? 0,
  3262.             'maxArea' => $areaRange['max'] ?? 0,
  3263.             'minPrice' => $priceRange['min'] ?? 0,
  3264.             'maxPrice' => $priceRange['max'] ?? 0,
  3265.             'location' => $listing->getLocation(),
  3266.             'excludedListing' => $listing,
  3267.         ];
  3268.     }
  3269.     /**
  3270.      * @return string|null
  3271.      */
  3272.     private function getStaticNoteByListingId(int $listingId, array $listingsWithNotes)
  3273.     {
  3274.         return $listingsWithNotes[array_search($listingIdarray_column($listingsWithNotes'listingId'))]['note']
  3275.             ?? null;
  3276.     }
  3277.     /**
  3278.      * @return int
  3279.      */
  3280.     private function getDatesDifferenceInMonth(\DateTime $date)
  3281.     {
  3282.         return $date->diff(new \DateTime())->m;
  3283.     }
  3284.     private function isListingEmailIncludes(Listing $listingstring $partialEmail): bool
  3285.     {
  3286.         $email $listing->getUser()->getEmailCanonical() ?? null;
  3287.         if ($email && str_contains($email$partialEmail)) {
  3288.             return true;
  3289.         }
  3290.         return false;
  3291.     }
  3292.     /**
  3293.      * @return UserManager
  3294.      */
  3295.     private function getUserManagerService()
  3296.     {
  3297.         return $this->userManager;
  3298.     }
  3299.     /**
  3300.      * @required
  3301.      */
  3302.     public function setLeadService(LeadService $leadService): void
  3303.     {
  3304.         $this->leadService $leadService;
  3305.     }
  3306.     /**
  3307.      * @return array
  3308.      */
  3309.     private function turnIntoUserToListingsMap(array $listingsCountPerUser)
  3310.     {
  3311.         $map = [];
  3312.         foreach ($listingsCountPerUser as $item) {
  3313.             $map[$item['userId']] = $item['liveListingsCount'];
  3314.         }
  3315.         return $map;
  3316.     }
  3317.     /**
  3318.      * @required
  3319.      */
  3320.     public function autowireCallRequestManager(CallRequestManager $callRequestManager): void
  3321.     {
  3322.         $this->callRequestManager $callRequestManager;
  3323.     }
  3324.     /**
  3325.      * @required
  3326.      */
  3327.     public function setUserManagerService(UserManager $userManager): void
  3328.     {
  3329.         $this->userManager $userManager;
  3330.     }
  3331.     /**
  3332.      * @param array $criteria
  3333.      *
  3334.      * @return int
  3335.      */
  3336.     private function getTotalImpressionsCount($criteria)
  3337.     {
  3338.         if ($criteria['startDate']) {
  3339.             $listingIds $this->listingRepository->getListingIds([
  3340.                 'user' => $criteria['user'],
  3341.                 'impressionUpdatedAt' => self::MAP_STRING_DAYS_WITH_NUMBER[$criteria['period']],
  3342.             ]);
  3343.             $periodInDays self::MAP_STRING_DAYS_WITH_NUMBER[$criteria['period']];
  3344.             $totalImpressions $this->listingImpressionRepository->countListingsTotalImpressions([
  3345.                 'listingIds' => array_flatten($listingIds),
  3346.                 'start_date' => date('Y-m-d h:i:s'strtotime(sprintf('-%d Days'$periodInDays))),
  3347.                 'end_date' => date('Y-m-d h:i:s'),
  3348.             ]);
  3349.         } else {
  3350.             $totalImpressions $this->listingRepository->getListingsTotalImpressions([
  3351.                 'user' => $criteria['user'],
  3352.                 'column' => ImpressionTypes::getType($criteria['period']),
  3353.                 'listing_status' => 'ONLYLIVE' == $criteria['period'] ? ListingStatus::LIVE null,
  3354.             ]);
  3355.         }
  3356.         return $totalImpressions;
  3357.     }
  3358.     /**
  3359.      * @return Listing $listing
  3360.      */
  3361.     private function handleProjectDates(Listing $listing)
  3362.     {
  3363.         $nextTenYearsDate date(self::EXPIRATION_DATE_FORMATstrtotime(self::ADD_TEN_YEARS));
  3364.         $listing->setExpiresAt(new \DateTime(date(self::EXPIRATION_DATE_FORMATstrtotime($nextTenYearsDate))));
  3365.         if (null === $listing->getPublishedAt()) {
  3366.             $listing->setPublishedAt(new \DateTime());
  3367.         }
  3368.         return $listing;
  3369.     }
  3370.     public function getListingsCount()
  3371.     {
  3372.         return $this->cache->get(self::LISTING_COUNTER_CACHE_KEY, function (ItemInterface $item) {
  3373.             $item->expiresAfter(3600);
  3374.             return $this->listingRepository->count(['status' => ListingStatus::LIVE]);
  3375.         });
  3376.     }
  3377.     /**
  3378.      * @description return true if user not has any live listing and has a valid free listing
  3379.      */
  3380.     public function isEligibleForFreePublishing(Listing $listing): bool
  3381.     {
  3382.         $criteria = [
  3383.             'user_id' => $listing->getUser()->getId(),
  3384.             'status' => [
  3385.                 ListingStatus::DRAFT,
  3386.             ],
  3387.             'reverseStatus' => true,
  3388.         ];
  3389.         $listingCount = (int) $this->listingRepository->getListingsQuery($criteria)->getQuery()->getSingleScalarResult();
  3390.         return $listingCount <= 1;
  3391.     }
  3392.     public function getListingMappedData(): ListingDataMapper
  3393.     {
  3394.         $mapped = new ListingDataMapper();
  3395.         $mapped->setMapListingAttributes(true);
  3396.         $mapped->setMapListingMainPhoto(true);
  3397.         $mapped->setMapListingPhotos(true);
  3398.         return $mapped;
  3399.     }
  3400.     public function getListingsElasticResponse(Request $request): array
  3401.     {
  3402.         $listingsIds $this->searchClient->request(
  3403.             'GET',
  3404.             '/api/listing',
  3405.             [
  3406.                 'query' => array_merge(
  3407.                     $request->query->all(),
  3408.                     ['limit' => min($request->query->getInt('limit'20), 100)]
  3409.                 ),
  3410.             ]
  3411.         );
  3412.         return $listingsIds->toArray();
  3413.     }
  3414.     public function getRelatedListingsElasticResponse(Listing $listing): array
  3415.     {
  3416.         $listingsIds $this->searchClient->request(
  3417.             'GET',
  3418.             sprintf('api/listing/%d/related-listings'$listing->getId())
  3419.         );
  3420.         return $listingsIds->toArray();
  3421.     }
  3422.     public function getListingsDebugElasticResponse(Request $request): array
  3423.     {
  3424.         $debugData $this->searchClient->request(
  3425.             'GET',
  3426.             '/api/listing/debug',
  3427.             [
  3428.                 'query' => $request->query->all(),
  3429.             ]
  3430.         );
  3431.         return $debugData->toArray();
  3432.     }
  3433.     public function getPropertiesChipsElasticResponse(Request $request): array
  3434.     {
  3435.         $request->query->set('aggregatedField''propertyType');
  3436.         $propertyTypes $this->searchClient->request(
  3437.             'GET',
  3438.             '/api/listing/histogram',
  3439.             [
  3440.                 'query' => $request->query->all(),
  3441.             ]
  3442.         );
  3443.         return $propertyTypes->toArray();
  3444.     }
  3445.     public function bulkDeletePhotos(array $photos): void
  3446.     {
  3447.         foreach ($photos as $photo) {
  3448.             $listing $photo->getListing();
  3449.             $listing->removePhoto($photo);
  3450.             $this->em->persist($listing);
  3451.             $this->em->flush();
  3452.             $this->messageBus->dispatch(new ParentListingUpdated($photo));
  3453.         }
  3454.     }
  3455.     /**
  3456.      * @throws \Exception
  3457.      */
  3458.     public function featuredAsSeller(Listing $listingint $featuredType): array
  3459.     {
  3460.         $listingFeature $this->listingFeatureService->getListingFeatured($featuredType);
  3461.         $listingRule $this->matcher->match($listing);
  3462.         $user $listing->getUser();
  3463.         $fees $this->listingFeatureService->getFeaturedTypeFees($listingRule$listingFeature);
  3464.         $duration $this->listingFeatureService->getFeaturedTypeDuration($listingRule$listingFeature);
  3465.         if (empty($fees) || empty($duration) || !$this->listingFeatureService->isFeaturedOffersAvailable($listing)) {
  3466.             return [
  3467.                 'type' => 'danger',
  3468.                 'message' => "No featured offers available for this listing ({$listing->getId()}) ",
  3469.             ];
  3470.         }
  3471.         if (!$this->isAffordable($user$fees)) {
  3472.             return [
  3473.                 'type' => 'danger',
  3474.                 'message' => "Not enough credit to make this listing ({$listing->getId()})  featured",
  3475.             ];
  3476.         }
  3477.         if ($this->isFeatured($listing)) {
  3478.             return [
  3479.                 'type' => 'danger',
  3480.                 'message' => $this->translator->trans('listing.featured_failure_statement.already_featured', [
  3481.                     '%listingId%' => $listing->getId(),
  3482.                 ]),
  3483.             ];
  3484.         }
  3485.         $this->makeItFeatured($listing, [
  3486.             'featuredFees' => $fees,
  3487.             'featuredDuration' => $duration,
  3488.             'listingFeaturedType' => $featuredType,
  3489.             'listingFeature' => $this->listingFeatureService->getListingFeatured($featuredType),
  3490.             'featuredText' => $this->translator->trans(ListingFeaturedTypes::getFeaturedText($featuredType)),
  3491.         ]);
  3492.         return [
  3493.             'type' => 'success',
  3494.             'message' => "Listing Id ({$listing->getId()}) has been set featured successfully",
  3495.         ];
  3496.     }
  3497.     /**
  3498.      * @throws OptimisticLockException
  3499.      * @throws ORMException
  3500.      * @throws \Exception
  3501.      */
  3502.     public function featuredAsAdmin(Listing $listingint $featuredType): string
  3503.     {
  3504.         if ($featureData $this->getFeaturingData($featuredType)) {
  3505.             $this->activityLogger->record($featureData['activityType'], $listing->getId());
  3506.             $message $featureData['message'];
  3507.             $listing->setFeatured($featuredType);
  3508.             $this->dispatcher->dispatch(new ListingEvent($listing), $featureData['event']);
  3509.             if (!empty($featureData['addFeatured'])) {
  3510.                 $this->AddFeaturedListingForFree($listing$featureData['featureKey']);
  3511.             }
  3512.         } else {
  3513.             $this->activityLogger->record(ActivityType::LISTING_UNFEATURED$listing->getId());
  3514.             $message 'Listing has been set un featured';
  3515.             $this->handleListingUnFeaturing($listing);
  3516.             $this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.unfeatured');
  3517.         }
  3518.         $this->saveListing($listing);
  3519.         return $message;
  3520.     }
  3521.     public function getFeaturingData($featuredType): ?array
  3522.     {
  3523.         switch ($featuredType) {
  3524.             case ListingFeaturedTypes::FEATURED:
  3525.                 return [
  3526.                     'activityType' => ActivityType::LISTING_MAKE_FEATURED,
  3527.                     'message' => 'Listing has been set featured',
  3528.                     'event' => 'aqarmap.listing.featured',
  3529.                     'featureKey' => ListingFeatures::FEATURED_KEY,
  3530.                     'addFeatured' => true,
  3531.                 ];
  3532.             case ListingFeaturedTypes::SUPER_FEATURED:
  3533.                 return [
  3534.                     'activityType' => ActivityType::LISTING_MAKE_SUPER_FEATURED,
  3535.                     'message' => 'Listing has been set super featured',
  3536.                     'event' => 'aqarmap.listing.super_featured',
  3537.                     'addFeatured' => false,
  3538.                 ];
  3539.             case ListingFeaturedTypes::PREMIUM:
  3540.                 return [
  3541.                     'activityType' => ActivityType::LISTING_PREMIUM,
  3542.                     'message' => 'Listing has been set premium',
  3543.                     'event' => 'aqarmap.listing.premium',
  3544.                     'featureKey' => ListingFeatures::PREMIUM_KEY,
  3545.                     'addFeatured' => true,
  3546.                 ];
  3547.             case ListingFeaturedTypes::SPONSORED:
  3548.                 return [
  3549.                     'activityType' => ActivityType::LISTING_SPONSORED,
  3550.                     'message' => 'Listing has been set sponsored',
  3551.                     'event' => 'aqarmap.listing.sponsored',
  3552.                     'featureKey' => ListingFeatures::SPONSORED_KEY,
  3553.                     'addFeatured' => true,
  3554.                 ];
  3555.             case ListingFeaturedTypes::SPOTLIGHT:
  3556.                 return [
  3557.                     'activityType' => ActivityType::LISTING_SPOTLIGHT,
  3558.                     'message' => 'Listing has been set spotlight',
  3559.                     'event' => 'aqarmap.listing.spotlight',
  3560.                     'featureKey' => ListingFeatures::SPOTLIGHT_KEY,
  3561.                     'addFeatured' => true,
  3562.                 ];
  3563.             default:
  3564.                 return null;
  3565.         }
  3566.     }
  3567.     public function addOrReplaceBrochure(Listing $listing$brochure): Listing
  3568.     {
  3569.         $entityManager $this->entityManager;
  3570.         $oldFile $listing->getBrochure();
  3571.         $fileEntity = new File();
  3572.         $fileEntity->setFile($brochure);
  3573.         $listing->setBrochure($fileEntity);
  3574.         if ($oldFile instanceof File) {
  3575.             $entityManager->remove($oldFile);
  3576.         }
  3577.         return $listing;
  3578.     }
  3579.     public function translateListingDescriptionData($requestListing $listing): Listing
  3580.     {
  3581.         foreach (Locales::getLocals() as $locale) {
  3582.             $this->translatableListener->setTranslatableLocale($locale);
  3583.             $this->em->refresh($listing);
  3584.             $listing->setTitle($request->get('title-'.$locale));
  3585.             $listing->setDescription($request->get('description-'.$locale));
  3586.             $listing->setAddress($request->get('address-'.$locale));
  3587.             $this->em->persist($listing);
  3588.             $this->em->flush($listing);
  3589.         }
  3590.         $listing->setTranslatableLocale($request->getLocale());
  3591.         return $listing;
  3592.     }
  3593.     public function removeBrochure(Listing $listing): Listing
  3594.     {
  3595.         $entityManager $this->entityManager;
  3596.         $file $listing->getBrochure();
  3597.         $listing->setBrochure(null);
  3598.         if ($file instanceof File) {
  3599.             $entityManager->remove($file);
  3600.         }
  3601.         return $listing;
  3602.     }
  3603. }